Package: src:whisper.cpp
Version: 1.8.4+dfsg-1
Severity: grave
Tags: patch security

Hi,

Please find attached a patch that fixes CVE-2025-14569, a use-after-free
vulnerability in the read_audio_data() function in common-whisper.cpp.

The fix adds a decoder_initialized flag, verifies ma_decoder_init_file()
return value explicitly, only calls ma_decoder_uninit() when the decoder
was successfully initialized, and adds proper cleanup on error paths.

The upstream (ggml-org) was notified via GitHub issue #3501 in November
2025 but has not responded. This patch is ready for Debian packaging.

Best regards,
Claudio Ferreira
From: Claudio Filho <[email protected]>
Date: Sun, 11 May 2026 14:54:00 -0300
Subject: Fix use-after-free in read_audio_data (CVE-2025-14569)

The read_audio_data() function in common-whisper.cpp declares the
ma_decoder struct on the stack.  When ma_decoder_init_file() internally
tries multiple backends, a partial init followed by postinit failure
can leave dangling pointers (pBackend, pInputCache) in the stack struct.
Subsequent operations may then call ma_free() on these dangling pointers,
triggering an invalid-free / use-after-free crash (CVE-2025-14569).

Fix by:
 - Tracking decoder initialization state with a bool flag
 - Checking ma_decoder_init_file() return value explicitly
 - Only calling ma_decoder_uninit() when the decoder was successfully
   initialized
 - Cleaning up decoder resources before early returns on error paths
 - Removing the nonsensical fallback that used the filename string as
   audio data (ma_decoder_init_memory(fname.c_str(), fname.size(), ...))

Forwarded: not-yet
Last-Update: 2026-05-11
---
 examples/common-whisper.cpp | 46 ++++++++++++++++++++++++---------------------
 1 file changed, 24 insertions(+), 22 deletions(-)

diff --git a/examples/common-whisper.cpp b/examples/common-whisper.cpp
index baf5801..3420b52 100644
--- a/examples/common-whisper.cpp
+++ b/examples/common-whisper.cpp
@@ -45,6 +45,7 @@ bool read_audio_data(const std::string & fname, std::vector<float>& pcmf32, std:
     ma_result result;
     ma_decoder_config decoder_config;
     ma_decoder decoder;
+    bool decoder_initialized = false;
 
     decoder_config = ma_decoder_config_init(ma_format_f32, stereo ? 2 : 1, WHISPER_SAMPLE_RATE);
 
@@ -63,52 +64,53 @@ bool read_audio_data(const std::string & fname, std::vector<float>& pcmf32, std:
 			audio_data.insert(audio_data.end(), buf, buf + n);
 		}
 
-		if ((result = ma_decoder_init_memory(audio_data.data(), audio_data.size(), &decoder_config, &decoder)) != MA_SUCCESS) {
-
+		result = ma_decoder_init_memory(audio_data.data(), audio_data.size(), &decoder_config, &decoder);
+		if (result != MA_SUCCESS) {
 			fprintf(stderr, "Error: failed to open audio data from stdin (%s)\n", ma_result_description(result));
-
 			return false;
 		}
+		decoder_initialized = true;
 
 		fprintf(stderr, "%s: read %zu bytes from stdin\n", __func__, audio_data.size());
-    }
-    else if (((result = ma_decoder_init_file(fname.c_str(), &decoder_config, &decoder)) != MA_SUCCESS)) {
-#if defined(WHISPER_FFMPEG)
-		if (ffmpeg_decode_audio(fname, audio_data) != 0) {
-			fprintf(stderr, "error: failed to ffmpeg decode '%s'\n", fname.c_str());
-
-			return false;
+	} else {
+		result = ma_decoder_init_file(fname.c_str(), &decoder_config, &decoder);
+		if (result == MA_SUCCESS) {
+			decoder_initialized = true;
 		}
+#if defined(WHISPER_FFMPEG)
+		if (!decoder_initialized) {
+			if (ffmpeg_decode_audio(fname, audio_data) != 0) {
+				fprintf(stderr, "error: failed to ffmpeg decode '%s'\n", fname.c_str());
+				return false;
+			}
 
-		if ((result = ma_decoder_init_memory(audio_data.data(), audio_data.size(), &decoder_config, &decoder)) != MA_SUCCESS) {
-			fprintf(stderr, "error: failed to read audio data as wav (%s)\n", ma_result_description(result));
-
-			return false;
+			result = ma_decoder_init_memory(audio_data.data(), audio_data.size(), &decoder_config, &decoder);
+			if (result != MA_SUCCESS) {
+				fprintf(stderr, "error: failed to read audio data as wav (%s)\n", ma_result_description(result));
+				return false;
+			}
+			decoder_initialized = true;
 		}
-#else
-		if ((result = ma_decoder_init_memory(fname.c_str(), fname.size(), &decoder_config, &decoder)) != MA_SUCCESS) {
-			fprintf(stderr, "error: failed to read audio data as wav (%s)\n", ma_result_description(result));
-
+#endif
+		if (!decoder_initialized) {
+			fprintf(stderr, "error: failed to read audio data from '%s'\n", fname.c_str());
 			return false;
 		}
-#endif
-    }
+	}
 
     ma_uint64 frame_count;
     ma_uint64 frames_read;
 
     if ((result = ma_decoder_get_length_in_pcm_frames(&decoder, &frame_count)) != MA_SUCCESS) {
 		fprintf(stderr, "error: failed to retrieve the length of the audio data (%s)\n", ma_result_description(result));
-
-		return false;
+		goto cleanup;
     }
 
     pcmf32.resize(stereo ? frame_count*2 : frame_count);
 
     if ((result = ma_decoder_read_pcm_frames(&decoder, pcmf32.data(), frame_count, &frames_read)) != MA_SUCCESS) {
 		fprintf(stderr, "error: failed to read the frames of the audio data (%s)\n", ma_result_description(result));
-
-		return false;
+		goto cleanup;
     }
 
     if (stereo) {
@@ -128,9 +130,12 @@ bool read_audio_data(const std::string & fname, std::vector<float>& pcmf32, std:
         }
     }
 
-    ma_decoder_uninit(&decoder);
+cleanup:
+    if (decoder_initialized) {
+        ma_decoder_uninit(&decoder);
+    }
 
-    return true;
+    return result == MA_SUCCESS;
 }

Reply via email to