--- license: other tags: - security - model-format - ggml - proof-of-concept library_name: other --- # GGML legacy model-controlled `n_dims` stack out-of-bounds in loaders and quantizers ## Submission - Data format: GGML legacy model files - Suggested title: GGML legacy model-controlled `n_dims` stack out-of-bounds in loaders and quantizers - Severity: High - Impact: native memory corruption and reliable denial of service when a victim loads or quantizes a malicious GGML model file - Affected target verified: ggml `v0.11.1`, commit `a056a26f508b6160438ddd8aabbc859d2a2e7a97` - Not claimed: arbitrary code execution ## Executive summary Several GGML legacy model readers trust the model-controlled `n_dims` field before copying tensor dimensions into fixed-size stack arrays. A malicious GGML artifact can set `n_dims` larger than the destination array and cause an out-of-bounds stack write during normal model loading or quantization. This repository includes five minimal malformed GGML files that hit independent loader/tool paths: - GPT-2 loader, fixed local `ne[2]` - GPT-J loader, fixed local `ne[2]` - SAM loader, fixed local `ne[4]` - GPT-2 quantizer helper, fixed local `ne[4]` - GPT-J quantizer helper, fixed local `ne[4]` The issue is artifact-carried: the only attacker-controlled input is the GGML model file passed to the normal loader/quantizer command. ASAN reports stack-buffer-overflow, and release builds crash/abort. ## Why this is security-relevant GGML artifacts are commonly exchanged as model files and are loaded by native C/C++ tools. The parser reaches the vulnerable path before model execution and before meaningful model validation. A service that accepts, previews, converts, or quantizes untrusted GGML models can be crashed by a tiny file. The ASAN traces show a write beyond a stack buffer, which is stronger than a clean parser error or expected unsupported-format rejection. ## Root cause The vulnerable code pattern is: 1. Read `n_dims` from the file. 2. Allocate a fixed local dimension array such as `int32_t ne[2]` or `int32_t ne[4]`. 3. Read `n_dims` dimension entries from the file directly into that fixed array. 4. Validate/use tensor metadata after the out-of-bounds write has already happened. Verified crash locations: - `examples/gpt-2/main-backend.cpp`, local `ne[2]` - `examples/gpt-j/main.cpp`, local `ne[2]` - `examples/sam/sam.cpp`, local `ne[4]` - `examples/common-ggml.cpp`, local `ne[4]`, reached by GPT-2 and GPT-J quantizers ## Repository contents ```text artifacts/ggml_gpt2_loader_ndims3.bin GPT-2 loader PoC artifacts/ggml_gptj_loader_ndims64.bin GPT-J loader PoC artifacts/ggml_sam_loader_ndims64.bin SAM loader PoC artifacts/ggml_gpt2_quant_ndims1024.bin GPT-2 quantizer PoC artifacts/ggml_gptj_quant_ndims1024.bin GPT-J quantizer PoC logs/*.txt ASAN traces modelscan/* ModelScan outputs from verification scripts/* PoC generation scripts SHA256SUMS.txt hash manifest ``` ## Reproduction Build the affected targets with AddressSanitizer: ```bash git clone https://github.com/ggml-org/ggml.git cd ggml git checkout a056a26f508b6160438ddd8aabbc859d2a2e7a97 cmake -S . -B build-asan -DCMAKE_BUILD_TYPE=Debug \ -DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer" \ -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer" \ -DGGML_NATIVE=OFF cmake --build build-asan --target gpt-2-backend gpt-j sam gpt-2-quantize gpt-j-quantize -j ``` Download this repository and run the loader cases: ```bash hf download pragnyanramtha/ggml-legacy-ndims-oob-suite --local-dir ggml-ndims-poc ./build-asan/bin/gpt-2-backend -m ggml-ndims-poc/artifacts/ggml_gpt2_loader_ndims3.bin -p test -n 1 ./build-asan/bin/gpt-j -m ggml-ndims-poc/artifacts/ggml_gptj_loader_ndims64.bin -p test -n 1 ./build-asan/bin/sam -m ggml-ndims-poc/artifacts/ggml_sam_loader_ndims64.bin -i /tmp/nonexistent.jpg ``` Run the quantizer cases: ```bash ./build-asan/bin/gpt-2-quantize ggml-ndims-poc/artifacts/ggml_gpt2_quant_ndims1024.bin /tmp/gpt2-out.bin q4_0 ./build-asan/bin/gpt-j-quantize ggml-ndims-poc/artifacts/ggml_gptj_quant_ndims1024.bin /tmp/gptj-out.bin q4_0 ``` Expected ASAN result for the quantizer path: ```text ERROR: AddressSanitizer: stack-buffer-overflow WRITE of size 4 ggml_common_quantize_0(...) examples/common-ggml.cpp:114 This frame has ... 'ne' (line 112) <== Memory access ... overflows this variable ``` Expected ASAN result for the SAM loader path: ```text ERROR: AddressSanitizer: stack-buffer-overflow sam_model_load(...) examples/sam/sam.cpp:1058 'ne' (line 1054) <== Memory access ... overflows this variable ``` Expected ASAN result for the GPT loader paths: ```text ERROR: AddressSanitizer: stack-buffer-overflow SUMMARY: AddressSanitizer: stack-buffer-overflow ... in memcpy 'ne' (...) <== Memory access ... overflows this variable ``` The captured traces are included in `logs/`. ## Scanner behavior The artifacts were also scanned with ModelScan during local verification. They are malformed GGML parser tests, not executable payloads, and the scanner did not identify the native parser memory corruption. The scanner logs are included under `modelscan/`. ## Hashes ```text 62892adf21342a70b3c6dee5adfa224a435f747a5e6eb7c84e5064f576e99b0c artifacts/ggml_gpt2_loader_ndims3.bin a0f8d2a774f739c6580e51348aa30f455de29c7a774b1f25c3ca27a593fae224 artifacts/ggml_gptj_loader_ndims64.bin 7ea36057dad923ed9feca6157fbccde22c5828bb91f51cb4ead4d8ac419154d2 artifacts/ggml_sam_loader_ndims64.bin d9a5880f374d68f1c618945e301997b454317e203d23ca35b3d7c11bd69b8ddb artifacts/ggml_gpt2_quant_ndims1024.bin 002f3224ac62b36bc9b60da7fa1e5a4ac24f1587552846c10bec8cf3dbf7e928 artifacts/ggml_gptj_quant_ndims1024.bin ``` ## Suggested fix Reject tensors where `n_dims` exceeds the destination capacity before reading dimension entries. Prefer reading into a dynamically sized container only after bounding `n_dims` to a format-level maximum. Add regression tests for oversized `n_dims` in each legacy loader and shared quantizer helper. ## Limitations This is a denial-of-service/native-memory-corruption report. I am not claiming arbitrary code execution. The PoCs are intentionally tiny malformed model files and should only be loaded in a throwaway test environment.