Crucible¶
Structure-aware fuzzing for the machine learning model supply chain and inference stack
Crucible is a structure-aware fuzzer for the code that turns an untrusted model file into a running model. That path runs through hand-written parsers, loaders, and quantization and compute kernels inside the tools people actually deploy: llama.cpp, whisper.cpp, stable-diffusion.cpp, ONNX Runtime, TensorFlow Lite, and the serving stacks around them. Crucible mutates model files at the semantic level so the malformed input reaches deep code paths instead of bouncing off a magic-byte check, then replays and triages every crash by hand.
6 CVEs Assigned
15 Fixes Merged Upstream
184 Distinct Findings
72 Projects
Why Crucible?¶
The model file is untrusted input, and it reaches memory-unsafe code
Every time someone loads a model through Ollama, LM Studio, llama.cpp, whisper.cpp, stable-diffusion.cpp, ONNX Runtime, or TensorFlow Lite, C and C++ code parses, dequantizes, and runs attacker-controlled bytes: GGUF and ONNX and SafeTensors files, quantization metadata, tensor shapes, and RPC wire messages.
Known parser bugs in this space carry CVSS scores from 7.5 to 9.8: heap overflows, integer overflows, out-of-bounds reads and writes, all reachable by opening a crafted file. These are shipped bugs in software that millions of people run locally and in production. They keep appearing because the code is hand-written, the invariants are implicit, and the input is fully attacker-controlled.
Generic fuzzers stall on the format. Point AFL++ or libFuzzer at a binary model format and most of their inputs die on the first few bytes. Crucible instead:
- Parses valid seed files into typed structures (headers, metadata key-value pairs, tensor info blocks, alignment, data regions)
- Mutates at the semantic level: corrupting cross-field invariants, injecting type-confused metadata, overflowing dimension products, and forging the quantization fields that drive kernel addressing
- Serializes back to binary that is structurally valid enough to reach deep parser, loader, and kernel code
The surface, from parse to run¶
Security work in ML has concentrated on model behavior (adversarial examples, prompt injection) and the web layer (SSRF, path traversal). The layer in between, the binary parsers and the quantization and compute kernels that every inference stack depends on, has had far less systematic attention. That is the surface Crucible targets, and it splits into two stages:
- Load time. Parsers and loaders read the file's structure. A crafted GGUF, ONNX, SafeTensors, or TFLite file with corrupted offsets, dimension products, or metadata drives out-of-bounds reads and writes before the model ever runs. Most inference stacks are now hardening this stage with up-front verification, and Crucible tracks who has and who has not.
- Run time. After a file parses cleanly, its quantization metadata (group sizes, block sizes, scale and zero-point shapes, group indices) still flows straight into the pointer arithmetic of dequantization and GEMM kernels. A value that is structurally valid but semantically hostile walks a kernel off the end of a buffer during ordinary inference. This is the surface up-front verification cannot cover, and it is where the durable bugs are.
The reliable tell across both stages is a differential: right next to the vulnerable code sits a sibling path that validates the same value the vulnerable path uses raw. The GPU kernel bounds an index the CPU kernel does not; one branch of a loader has a check its twin omits. The divergence is a proof that the missing check is an oversight, not a design decision, and it turns a fuzzing crash into a directed, explainable finding.
Impact¶
Crucible's findings are real memory-safety and denial-of-service bugs in real parsers, disclosed responsibly. The public track record:
- 6 CVEs assigned: three in whisper.cpp (CVE-2026-10298, CVE-2026-17512, CVE-2026-17513), two in llama.cpp (CVE-2026-17500, CVE-2026-17501), and a heap over-read in ONNX shape inference (CVE-2026-14647).
- 15 findings fixed upstream in merged pull requests, across stable-diffusion.cpp, ONNX, ONNX Runtime, TensorFlow Lite, mistral.rs, Ollama, GPTQModel, acestep.cpp, and tract. Four of those are patches written here and merged by the maintainer, not just reports acted on.
- 184 distinct findings across 72 projects, ranging from heap out-of-bounds reads and writes to reachable assertions and denial-of-service crashes. Most remain under coordinated disclosure and are not detailed publicly.
See Crucible Findings for the disclosed set with links to each issue and fix, and Known CVEs for the published prior art that maps Crucible's target surface.
Quick Start¶
Get from zero to fuzzing in four commands:
Prerequisites
You need a local clone of the target project for the C and C++ harnesses (for example llama.cpp). Set the target path when building. The Go native harness requires only the Go toolchain.
How it works¶
Structure-Aware Mutation Engine¶
Crucible does not flip random bytes. Every mutation operates on a parsed structure: modifying header fields, injecting malformed metadata, corrupting tensor dimensions, forging quantization fields, and breaking the cross-field invariants that parsers and kernels rely on.
Weighted Strategy Selection¶
Not all mutation categories are equal. Metadata and tensor-info mutations dominate selection because that is where the bug density is highest:
| Category | Weight | Focus |
|---|---|---|
| Metadata | 35% | String handling, type confusion, model-loader targeting |
| Tensor Info | 35% | Dimension overflows, offset manipulation, type fuzzing |
| Header | 10% | Version, counts, magic corruption |
| Consistency | 10% | Cross-field mismatches, where the worst bugs hide |
| Alignment | 5% | Padding and stride calculation bugs |
| Data | 5% | Truncation, overlap, size mismatches |
Crash Deduplication by Stack Hash¶
The triage engine parses ASan and UBSan output, extracts stack frames, and computes a stable hash. Thousands of duplicate crash artifacts collapse to a handful of unique bugs, so the assessment focuses on real defects, not counts.
The Differential as a Review Technique¶
When one code path validates a file-controlled value and a sibling path does not, the unvalidated path is where the bug is, and the sibling is the specification for the fix. Crucible surfaces the crash; the differential explains why it is real and how to close it.
Disclosure-Ready Triage¶
Triage output includes crash ID, proposed severity, affected function, source location, stack trace, and a reproducer reference, formatted for responsible disclosure. Every finding is replayed on a HEAD build and read at the source before it is named.
Active Research¶
Additional findings are under coordinated disclosure with upstream maintainers and vendors. Specifics are published only after each disclosure completes.
Crucible is a Halo Forge Labs project.