Mutation Strategies¶
Crucible ships 46 structure-aware mutation strategies for the GGUF file format, organized into 6 categories. Each strategy targets a specific structural component to trigger a known class of bugs.
Category weights
The mutator selects categories using weighted random sampling: Metadata (35%), TensorInfo (35%), Header (10%), Consistency (10%), Alignment (5%), Data (5%). Higher weights target sections with the most parser complexity and historical bug density.
Header Strategies¶
Target the 24-byte fixed header that every GGUF parser reads first.
| Strategy | Description | Bug Pattern |
|---|---|---|
header.version | Fuzz version field: 0, 1, 2, UINT32_MAX, random | Version handling bugs |
header.tensor_count | Mismatch count with actual tensor info blocks | Buffer overflow on allocation |
header.metadata_kv_count | Mismatch count with actual KV pairs | Over-read past metadata section |
header.magic_corrupt | Partial magic corruption (keep 1-2 valid bytes) | Error path bugs |
header.version_mismatch | Set version that disagrees with field sizes used | Version-dependent parsing bugs |
Metadata Strategies¶
Target the variable-length key-value section. This is the largest attack surface in most GGUF parsers.
| Strategy | Description | Bug Pattern |
|---|---|---|
metadata.key_length | Empty keys, 1MB+ keys, embedded null bytes, declared-length mismatches (encoded length != actual key bytes) | String handling overflow, parser field desync |
metadata.key_content | Non-UTF8 sequences, null sleds, path traversal strings, surrogate pairs | Encoding bugs |
metadata.value_type | Invalid enum values (14+, UINT32_MAX), type confusion between similar types | Type confusion / wrong getter |
metadata.string_value | Empty strings, 10MB strings, embedded nulls, non-UTF8 | String processing overflow |
metadata.array | Empty arrays, nested arrays, element type mismatch, large arrays (100K+ elements) | Array handling overflow |
metadata.int_overflow | UINT32_MAX, UINT64_MAX, INT64_MIN in integer fields | Integer overflow |
metadata.alignment_poison | Set general.alignment to 0, 1, 3, 7, UINT32_MAX | Division by zero, huge allocation |
metadata.key_shadow | Duplicate keys like general.architecture with conflicting value types | Duplicate key confusion |
metadata.add_extra | Inject 50-250 extra KV pairs with random types and large values | Parser stress / OOM |
metadata.invalid_utf8 | Inject non-UTF-8 byte sequences in string values | Encoding validation bugs |
metadata.reorder | Randomize the order of metadata key-value pairs | Order-dependent parsing bugs |
metadata.deep_array | Create arrays nested to extreme depth; arrays whose declared element count exceeds actual data (1M declared, 2 emitted) | Stack overflow in recursive parsing, read past end of array |
metadata.string_truncated | Declared string length exceeds actual bytes available (via on-wire length override) | Read past end of metadata section |
TensorInfo Strategies¶
Target the per-tensor descriptor blocks that parsers use to locate and allocate tensor data.
| Strategy | Description | Bug Pattern |
|---|---|---|
tensorinfo.n_dims | Set dimensions to 0, 5+, UINT32_MAX (spec allows 1-4) | Out-of-bounds dimension read |
tensorinfo.dim_overflow | Set individual dimension values to 0 or UINT64_MAX | Allocation size bugs |
tensorinfo.type | Invalid ggml_type enum values (5, 15, 255, UINT32_MAX) | Type lookup crash |
tensorinfo.offset | Offset beyond file size, UINT64_MAX, overlapping with other tensors | Out-of-bounds read |
tensorinfo.name | Empty names, 1MB names, embedded nulls, non-UTF8, duplicate names | String overflow, dedup bugs |
tensorinfo.dim_product_overflow | Dimension values whose product overflows uint64 | Undersized alloc + oversized read/write |
tensorinfo.name_collision | Give two tensors the same name | Deduplication and lookup bugs |
tensorinfo.offset_wraparound | Offset + size wraps uint64, bypassing bounds checks | Out-of-bounds memory access |
Alignment Strategies¶
Target the padding calculations between the metadata section and tensor data.
| Strategy | Description | Bug Pattern |
|---|---|---|
alignment.padding | Set alignment to 0, prime numbers, UINT32_MAX, OS page size | Padding calculation crash |
alignment.extra_padding | Insert random non-zero bytes before tensor data section | Offset miscalculation |
alignment.missing_padding | Metadata claims alignment but padding bytes are absent | Missing padding handling |
Data Strategies¶
Target the raw tensor data blob at the end of the file.
| Strategy | Description | Bug Pattern |
|---|---|---|
data.truncate | Truncate data section mid-tensor | Read past end of file |
data.overlap | Multiple tensors pointing to the same offset | Double-read, data confusion |
data.zero_length | Empty data section with non-zero tensor count | Null pointer / zero-size alloc |
data.shorter | Data section shorter than the sum of all tensor sizes | Partial read overflow |
data.garbage_fill | Fill data section with random bytes | Data corruption handling |
data.nan_inf | Inject NaN and Infinity values into tensor data | Special float handling bugs |
Consistency Strategies¶
Violate cross-section invariants that parsers may assume hold true.
| Strategy | Description | Bug Pattern |
|---|---|---|
consistency.tensor_count | Header tensor count != actual tensor info block count | Over-read / under-read |
consistency.metadata_count | Header metadata count != actual KV pair count | Parser desync |
consistency.offset_beyond | Tensor offset + tensor data size > total file size | Out-of-bounds read |
consistency.tensor_size | Dimensions claim X bytes but actual data region is Y bytes | Size mismatch overflow |
consistency.duplicate_offset | Multiple tensors claim the same offset range | Aliased memory access |
consistency.alignment_disagree | Metadata alignment value != actual file padding alignment | Offset miscalculation |
Model-Loader Strategies¶
Target the model-loading path (llama_model_load) beyond raw GGUF parsing. These strategies mutate architecture keys, hyperparameters, vocabulary fields, and layer counts to trigger integer overflows and assertion failures in the model loading and architecture dispatch code.
Category weighting
Model-loader strategies are registered under the Metadata category for weighting purposes (35% selection probability). They share the metadata budget because they operate on metadata key-value pairs that feed into model initialization.
| Strategy | Description | Bug Pattern |
|---|---|---|
model.architecture | Set general.architecture to unknown, empty, or malformed values (null bytes, path traversal strings) | Architecture dispatch crash, unhandled enum |
model.hyperparam | Set hyperparameter keys (embedding_length, head_count, block_count, etc.) to 0, UINT32_MAX, or other boundary values | Integer overflow in allocation sizing |
model.vocab | Mutate tokenizer keys (tokenizer.ggml.model, bos_token_id, eos_token_id) with invalid strings or extreme integer values | Tokenizer initialization crash |
model.layer_count | Set block_count to 0, UINT32_MAX, or values mismatched with actual tensor count | Layer iteration overflow, assertion failure |
model.tensor_name_schema | Corrupt tensor names to break the blk.N.attn_q.weight naming convention parsers rely on | Tensor lookup failure, null dereference |
Strategy Summary¶
| Category | Count | Weight | Primary Target |
|---|---|---|---|
| Header | 5 | 10% | Fixed header fields |
| Metadata | 13 | 35% | Key-value parsing |
| TensorInfo | 8 | 35% | Tensor descriptor blocks |
| Alignment | 3 | 5% | Padding between sections |
| Data | 6 | 5% | Raw tensor data blob |
| Consistency | 6 | 10% | Cross-section invariants |
| Model-Loader | 5 | (shared with Metadata) | Model initialization path |
| Total | 46 | 100% |
Composability
The mutator may apply multiple strategies in a single pass. Combined mutations (e.g., header.tensor_count + consistency.offset_beyond) often trigger bugs that no single mutation would reach alone.
Header count synchronization
By default, the mutator calls SyncCounts() after all mutations to ensure Header.MetadataKVCount and Header.TensorCount match the actual slice lengths. Strategies that append metadata or tensors (e.g., metadata.add_extra, metadata.key_shadow, tensorinfo.name_collision) automatically get correct header counts. Strategies that intentionally set wrong counts (consistency.tensor_count, consistency.metadata_count, header.tensor_count, header.metadata_kv_count, metadata.string_truncated) opt out of this sync via the CountKeeper interface.