You need to agree to share your contact information to access this model
This repository is publicly accessible, but you have to accept the conditions to access its files and content.
This repository contains a deliberately malformed GGUF file used only to demonstrate a memory-safety bug in llama.cpp for coordinated disclosure.
Log in or Sign Up to review the conditions and access this model content.
β οΈ Security PoC β NOT a usable model
This is not a language model. evil_dflash.gguf is a deliberately crafted, malformed
DFlash draft GGUF whose only purpose is to trigger a heap out-of-bounds write in
ggml-org/llama.cpp when the victim runs DFlash
speculative decoding with it. It produces no useful output and must not be used for inference.
It is published here solely as the reproducible proof-of-concept artifact for a coordinated vulnerability report (huntr / GGUF model-file-format bounty). Do not load it with any llama.cpp build you care about.
Vulnerability summary
- Project:
ggml-org/llama.cpp(commitc15c5c7; the vulnerable line is also onmasterHEADe3546c7). - Weakness: CWE-190 (integer overflow) β CWE-787 (out-of-bounds write).
- Component: DFlash speculative decoding (
common/speculative.cpp), DFlash arch loader (src/models/dflash.cpp). DFlash was added in llama.cpp PR #22105.
A DFlash draft model's GGUF metadata key dflash.target_layers is an int32 array whose length
is attacker-controlled and unbounded (read via the vector overload of
llama_model_loader::get_arr, no cap). That length feeds a 32-bit signed multiply:
// common/speculative.cpp:941
n_embd_enc = (int32_t) target_layer_ids_n * n_embd_tgt; // 1048577 * 4096 == 2^32 + 4096 β wraps to 4096
The wrapped width under-sizes a heap buffer, but the feature-copy loop then runs the full,
un-wrapped count (~1.05M iterations), writing target-model activation slices past the end of
the allocation:
// common/speculative.cpp:1050-1059
features_buf.resize((size_t) n_chunk * n_embd_enc); // sized from the wrapped 4096
for (uint32_t k = 0; k < target_layer_ids_n; ++k) { // full ~1.05M
...
std::memcpy(dst, src, (size_t) n_embd_tgt * sizeof(float)); // heap OOB WRITE
}
The per-iteration if (!layer) GGML_ABORT guard is bypassed by filling target_layers with a
single valid target layer id (e.g. 0) repeated. EAGLE3 uses the same fused-encoder pattern but
rejects any target_layers not exactly length 3 (src/models/eagle3.cpp:9-11); DFlash has no
such cap.
The attacker controls how far the writes run past the buffer; the written bytes are the target model's per-layer activations (not attacker-chosen bytes). Demonstrated impact is a deterministic heap out-of-bounds write / memory corruption / crash. Code execution is not demonstrated.
Configuration gate (honest scope)
DFlash is not enabled by default. The victim must run llama.cpp with both:
--model-draft <this evil_dflash.gguf> --spec-type draft-dflash
Once configured, the first ordinary text-generation request trips the write. This is a malicious-draft-model bug in llama.cpp's documented untrusted-GGUF threat model, with the added precondition that DFlash decoding is turned on.
Attack scenario
DFlash is a newly merged speculative-decoding mode promoted for inference speed-ups, so users have a
concrete reason to download and run third-party DFlash draft models from model hubs β exactly as they
already do for main models. An attacker publishes a normal-looking DFlash draft whose
dflash.target_layers is a valid target layer id (e.g. 0) repeated ~1.05 million times. A victim
who adopts DFlash speculative decoding starts their server or CLI with
--model-draft <attacker>.gguf --spec-type draft-dflash against any target model of hidden size 4096.
On the first ordinary generation request, the 32-bit encoder-width multiply wraps, features_buf
is under-sized, and the copy loop writes ~1.05M target-activation slices past the allocation β a
deterministic heap out-of-bounds write at common/speculative.cpp:1059. The victim's prompt is only
the trigger; the corruption's extent is fixed entirely by the attacker's draft metadata. This is
llama.cpp's documented untrusted-model threat model, with the single extra precondition that DFlash
decoding is enabled (off by default).
Files in this repo
| File | What it is |
|---|---|
evil_dflash.gguf |
The malicious DFlash draft (390 MB): target_layers = 1,048,577 Γ 0, fc.weight shaped {4096,4096} to survive load-time validation. |
minimal_target_4096.gguf |
A tiny (0.36 MiB) benign 1-layer target model, hidden size 4096, used as the DFlash target in the live PoC. |
make_evil_dflash_gguf.py |
Regenerates evil_dflash.gguf from scratch (deterministic). |
make_minimal_dflash_target_gguf.py |
Regenerates minimal_target_4096.gguf. |
dflash_overflow_poc.cpp, build_dflash_poc.sh, dflash_asan.log |
PoC 1 β focused arithmetic harness reproducing speculative.cpp:941/1050/1059 verbatim (no llama.cpp build needed). |
dflash_loader_poc.c, build_dflash_loader.sh, dflash_loader.trimmed.log |
PoC 2 β loads evil_dflash.gguf through the real ASAN libllama.so; proves the load-time wrap + fc.weight-shape bypass are real (LOAD OK). |
dflash_live_poc.cpp, build_dflash_live_poc.sh, dflash_live_asan.log |
PoC 3 β loads both real GGUFs, runs llama.cpp's compiled common_speculative_process(), ASAN catches the OOB write at the real common/speculative.cpp:1059. |
run_dflash_live_server.sh |
Optional: drives the full llama-server HTTP wrapper (run outside a network-restricted sandbox). |
Reproduce
Prerequisite: build llama.cpp @ c15c5c7 with AddressSanitizer:
git clone https://github.com/ggml-org/llama.cpp && cd llama.cpp
git checkout c15c5c7
cmake -B build-asan -DLLAMA_SANITIZE_ADDRESS=ON -DGGML_NATIVE=OFF
cmake --build build-asan -j # llama + ggml shared libs are enough
Then, from this repo's directory, point the scripts at your llama.cpp checkout and its ASAN
build via the LLAMA_CPP_DIR / BUILD_DIR environment variables (no need to edit the scripts).
The model generators use a pip-installed gguf if present (pip install gguf), otherwise the
gguf-py inside LLAMA_CPP_DIR:
export LLAMA_CPP_DIR=~/llama.cpp # your checkout @ c15c5c7
export BUILD_DIR=$LLAMA_CPP_DIR/build-asan # the -DLLAMA_SANITIZE_ADDRESS=ON build
pip install gguf # (or rely on $LLAMA_CPP_DIR/gguf-py)
# PoC 1 β arithmetic + OOB, no llama.cpp build needed (fully standalone)
bash build_dflash_poc.sh # β dflash_asan.log: heap-buffer-overflow WRITE, wrapped width = 4096
# PoC 2 β malicious model loads through the real loader
python3 make_evil_dflash_gguf.py # (re)generate evil_dflash.gguf (or use the uploaded one)
bash build_dflash_loader.sh # β "[poc] LOAD OK ... n_embd=4096"
# PoC 3 β real two-model DFlash path hits the real sink
python3 make_minimal_dflash_target_gguf.py
bash build_dflash_live_poc.sh # β dflash_live_asan.log: OOB WRITE at common/speculative.cpp:1059
Expected PoC 3 output (abridged):
common_speculative_impl_draft_dflash: - block_size=16, mask_token_id=-1, n_extract=1048577
[poc] target decode OK; entering common_speculative_process
==ERROR: AddressSanitizer: heap-buffer-overflow
WRITE of size 16384 at 0x... 0 bytes to the right of 32768-byte region
#2 common_speculative_impl_draft_dflash::process ... common/speculative.cpp:1059
#3 common_speculative_process ... common/speculative.cpp:2508
allocated by ... common/speculative.cpp:1050
Suggested fix
Bound the target_layers length and perform the width math in 64-bit, rejecting (not
saturating) oversized input, at both the load-time (src/models/dflash.cpp:14) and runtime
(common/speculative.cpp:941) multiplies. A small cap on the layer count is the cleanest fix β real
DFlash models extract only a handful of layers.
Disclosure
Reported via coordinated disclosure. No public duplicate was found as of 2026-07-12 (distinct from
PR #25513, which is the unrelated block_count/n_layer_all family β different file, root cause,
and fix boundary). This "no match" is time-bounded and cannot rule out a private report.
Security research artifact. Provided as-is for defensive/coordinated-disclosure purposes only.
- Downloads last month
- 1
We're not able to determine the quantization variants.