| Title | ggml-org xxxxxxxxxx llama.cpp up to main commit 8212c7802455255460ab8e18fc34754560031b34 (latest release b11160, 2026-09-24) Out-of-Bounds Write (CWE-787) |
|---|
| Description | A vulnerability was found in ggml-org/llama.cpp up to main commit 8212c7802455255460ab8e18fc34754560031b34 (latest release b11160, 2026-09-24) and classified as high 7.8. Affected is the gen-audio streaming-state export of the multimodal projector runtime (tools/mtmd), reachable through the pocket-tts text-to-speech path of llama-tts. The manipulation of the mmproj tensor a.gen.wav.upsample.weight (with a.gen.wav.quant_out.weight, a.gen.emb_mean, a.gen.emb_std) leads to a heap out-of-bounds write whose length and contents are chosen by the projector file, repeated on every streaming flush of the audio generator, and the defect escalates to arbitrary command execution: a weaponized projector artifact was demonstrated executing an attacker-chosen command inside the victim process from the official command line alone, with no debugger and no memory patching. The attack is launched locally: the victim has to run the documented llama-tts command line with an attacker-supplied mmproj file passed to --mmproj alongside the victim's own pocket-tts backbone model.
Technical Details
- Affected file/function (measured at tested commit 3d82ef62d47fd74e18f36c5eccbdcf965b617b17 and re-measured at current main 8212c7802455255460ab8e18fc34754560031b34 on 2026-09-25; the construct is unchanged and no release contains a fix):
list_pockettts_state_slots() sizes the streaming-state slot "up" from the declared weight shape as [gen_upsample_w->ne[0] - mimi_downsample, gen_upsample_w->ne[2]] (tools/mtmd/models/pockettts-gen.cpp:125). The graph builder's conv_transpose1d() instead treats the weight as non-depthwise whenever ne[1] != 1 and takes the output-channel count from ne[1] (tools/mtmd/models/pockettts-seanet.cpp:53-57), so the graph's state_out_up tensor shape follows ne[1]. The host-side export in clip_encode() sizes the state_out vector from the declared slot shapes (state_out.resize(total), tools/mtmd/clip.cpp:5835-5841 at the tested commit; :5865-5872 at current main) and then copies ggml_nbytes(t) bytes of the actual graph tensors sequentially into it (ggml_backend_tensor_get at clip.cpp:5849 / :5880) with no check that offset + nb stays within state_out, so nothing makes the allocation and the copies agree. A projector whose upsample weight is declared F32 [K, 512, 2] instead of the genuine depthwise [32, 1, 512] makes the host reserve a [K-16, 2] slot (192 bytes) while the graph produces a [K-16, 512] state tensor (49,152 bytes for K = 40): each export writes roughly 49 KB past a 192-byte reservation, once per GEN_WAV streaming flush. Three further tensors (a.gen.wav.quant_out.weight as an identity ne=[32,1], a.gen.emb_mean = [1,0,...], a.gen.emb_std = zeros) pin the transposed-convolution input to a known value, which makes every written 32-bit word exactly attacker-chosen, with a different payload per flush. The same export serves the qwen3-tts gen path (list_gen_state_slots, tools/mtmd/clip.cpp:4408-4413 at the tested commit).
- Vulnerable parameter: mmproj GGUF tensors a.gen.wav.upsample.weight (with a.gen.wav.quant_out.weight, a.gen.emb_mean, a.gen.emb_std)
- Attack vector: Local
- Privileges required: None
- Trigger condition: default configuration (--no-mmproj-offload was used in the lab for determinism only); one normal user interaction (generating speech from an attacker-supplied projector). The victim's own backbone must be a gen-audio (pocket-tts) model - the genuine kyutai/pocket-tts conversion was the victim; the attacker's file is only the mmproj, built by rewriting four tensors of an otherwise genuine projector file.
- Escalation (achieved in the laboratory, fixed environment): the controlled, repeating, per-flush-programmable heap overwrite was escalated end-to-end to arbitrary command execution on a pinned environment (llama.cpp 3d82ef62d47fd74e18f36c5eccbdcf965b617b17, Ubuntu 20.04, glibc 2.31, clang 16 RelWithDebInfo, ASLR disabled in the container). The chain: the overwrite corrupts allocator metadata and poisons tcache; a freed ggml backend object is reclaimed with a forged structure; a forged std::set red-black-tree node makes the tree rebalancing write an attacker-chosen pointer into the backend's registry; a fake function-pointer entry redirects a call through glibc's setcontext to regain RIP/RSP/RDI; the payload then calls system() with an attacker-chosen command. The weaponized run used no debugger and no memory patching, and the proof command's output (uid=0 in the container) was written to a file. The exploit is deterministic for the pinned build and environment only (hardcoded addresses; other builds require recalibration) - the vulnerability itself is not environment-specific, only this weaponization is.
- Origin of the weakness: original code of the project (pocket-tts support arrived in commit stream #26871, 2026-08; qwen3-tts in #26254). No prior advisory or public report of this exact issue exists (the repository's 13 published GitHub security advisories cover none of tools/mtmd; issue-tracker and global searches on 2026-09-24/25 returned zero hits). Reported to the vendor on 2026-09-24 through public PR #29360 - the repository's security policy states the private vulnerability reporting program is disabled and directs fixes to public PRs; the PR describes the memory-safety mechanism but no exploitation technique. The escalation summarized above has not been disclosed anywhere prior to this submission.
- Validation: three levels, all in isolated container laboratories. (1) AddressSanitizer at the pinned commit: heap-buffer-overflow in the state_out copy path with the crafted projector; well-formed projectors unaffected. (2) Release build under gdb: the overwrite is byte-deterministic across runs, and a crafted implant kept the corrupted heap self-consistent through an entire generation (8 flushes x 64 rounds). (3) Native end-to-end (no debugger, no patching): the weaponized artifact executed an attacker-chosen command in the victim process from the official command line alone; the process exits 139 afterwards, expected from the forged context returning to a null address. The fix proposed in PR #29360 sizes state_out from the graph tensors, closing the write at its source.
Impact
- Confidentiality: High (arbitrary command execution demonstrated in the laboratory: full access to the victim process's memory and privileges)
- Integrity: High (attacker-chosen 32-bit words over live heap objects and allocator state, escalating to arbitrary command execution)
- Availability: High (reliable process abort/crash at or after generation; exploitation itself redirects the process)
CVSS v3.1
Score: 7.8 (High)
Vector: CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
Timeline
- Discovered: 2026-09-23 (memory-safety defect); escalation to code execution completed 2026-09-24
- Vendor notified: 2026-09-24 (public PR #29360, opened 2026-09-24T08:16Z - the only channel: the repository's security policy disables private vulnerability reporting and directs fixes to public PRs)
- Patch released: [pending - PR #29360 open as of 2026-09-25; no release contains a fix]
- Public disclosure: [pending - this VulDB submission; the memory-safety mechanism is public since PR #29360 (2026-09-24), the escalation is first disclosed here]
Countermeasure
Merge PR #29360: resolve the graph tensors first and size state_out from ggml_nbytes() of exactly those tensors, so the allocation and the copies cannot disagree by construction (the PR additionally rejects negative feature_layer values). Additionally validate depthwise shape and channel relations when loading pocket-tts weights, and bounds-check each state-tensor copy against the destination buffer. Until a fix ships, do not run the llama-tts / gen-audio paths with mmproj files from untrusted sources, or run them only in an isolated environment. |
|---|
| Source | ⚠️ https://github.com/3em0/cve_repo/tree/main/2026/llama.cpp.pockettts_state_heap_oob_rce |
|---|
| User | Dem00000 (UID 98563) |
|---|
| Submission | 09/25/2026 18:56 (1 month ago) |
|---|
| Moderation | 10/25/2026 07:55 (30 days later) |
|---|
| Status | Accepted |
|---|
| VulDB entry | 426164 [ggml-org llama.cpp up to b9060 Pocket-TTS pockettts-gen.cpp list_pockettts_state_slots mmproj out-of-bounds write] |
|---|
| Points | 20 |
|---|