● Zion Boggan
repos/Cyclops V2/vault/01-archetypes/memory-unsafe-parsers.md
zionboggan.com ↗
121 lines · markdown
History for this file →
1
---
2
archetype: memory-unsafe-parsers
3
primary_gate_families: [1]
4
---
5
 
6
# Archetype - Memory-Unsafe Parsers (Fuzz + ASan → Corruption → RCE)
7
 
8
Targets: image codecs (libwebp, libjpeg-turbo, libpng, libtiff, OpenEXR, libheif, libavif),
9
archive libs (libarchive, zlib/zstd/lz4, minizip, 7-Zip), font shapers (FreeType, HarfBuzz),
10
serialization (protobuf C++, flatbuffers, msgpack-c, CBOR, ASN.1/OpenSSL), media demuxers (FFmpeg),
11
document/XML (libxml2, expat), and any C/C++ library that turns a byte stream into a structure. The
12
thesis: these are **pure functions from attacker bytes to memory operations**, written in C/C++,
13
embedded *everywhere* (a libwebp bug is in every browser and Electron app), and they are the ideal
14
target for the only oracle that never lies - **a sanitizer crash**. This is the purest
15
[[../00-doctrine/SEVERITY-GATE#family-1-remote-code-execution-the-crown|Family 1]] archetype, and it
16
is the engine room behind [[ml-model-loaders]] (model files are just another parser input).
17
 
18
See: [[ml-model-loaders]] (GGUF/safetensors/ONNX parsers - same mechanics, ML inputs). Tooling:
19
`compile_harness`, `fuzz_start`/`fuzz_status`/`fuzz_get_crashes`, `boofuzz_session`, `triage_crash`,
20
`angr_analyze`, `ghidra_decompile`, `r2_analyze`, `concolic_trace`, `run_klee`, `z3_solve`.
21
 
22
---
23
 
24
## Principals & trust boundaries
25
 
26
- **Principals:** the content author (anyone who can hand the app a file/stream - a webpage serving an
27
  image, an email attachment, a document, a network packet), the **parser library**, the host process.
28
- **Core boundary:** *attacker bytes → parse routine → memory operation*. There is no user, no auth,
29
  no session - just a format and a function. The "trust input" is the entire file format spec, and
30
  every length/count/offset/index field in it is attacker-controlled.
31
 
32
**Boundary map template:**
33
```
34
principals:        content-author(untrusted bytes) → parser library → host process
35
trust inputs:      every header field, chunk length, element count, dimension, offset, index,
36
                   compression ratio, and nested-structure depth in the format
37
enforcement pts:   bounds checks before memcpy/index, integer-overflow checks on size math,
38
                   recursion/iteration limits, allocation-size sanity, the "is this offset inside
39
                   the buffer" validations
40
cross-boundary
41
sinks:             memcpy/memmove/memset with file-derived length ; array index from file field ;
42
                   malloc(a*b) where a*b can overflow ; alloca/VLA from file size ; pointer arithmetic
43
                   base+offset ; type/enum field indexing a function/size table ; recursive parse
44
confusion
45
hypotheses:        "chunk_length is trusted before checking remaining buffer";
46
                   "width*height*bpp overflows size_t before the alloc";
47
                   "a negative/huge offset isn't validated against buffer bounds";
48
                   "the type byte indexes a handler table without a range check";
49
                   "decompressed size is trusted from the header"
50
```
51
 
52
---
53
 
54
## Kill chains (all land on Family 1)
55
 
56
### KC-1 - Integer overflow in size math → undersized alloc → heap overflow
57
The single most productive bug class. A `width * height * channels`, `count * elem_size`, or
58
`n0*n1*n2` multiply overflows `size_t`/`int`, the wrapped-small value is passed to `malloc`, then the
59
real (large) data is `memcpy`'d in → heap overflow with attacker bytes.
60
- **CVE class to cite:** the libwebp `BuildHuffmanTable` overflow (CVE-2023-4863, the "everything that
61
  embeds Chromium" bug), countless libtiff/OpenEXR/FFmpeg integer-overflow heap overflows.
62
- **Sinks to CPG-trace:** every `malloc`/`calloc`/`realloc`/`alloca` whose size arg is a *product* or
63
  *sum* of file-derived values; pair each with the subsequent `memcpy`/write loop.
64
 
65
### KC-2 - Missing bounds check before memcpy / OOB index
66
A chunk-length, offset, or array-count field is *trusted* and used to copy/index before validating it
67
against the remaining buffer. → OOB read (info leak / DoS) or OOB write (corruption).
68
- **Sinks:** `memcpy(dst, src, file_len)`, `buf[file_index]`, `base + file_offset` deref; the classic
69
  "read `n`, then loop `n` times reading more without checking we have `n` left".
70
 
71
### KC-3 - Type/enum confusion → wrong-size handler / OOB table index
72
A type/format byte selects a handler or a per-type size from a table; an out-of-range or unexpected
73
value indexes the table OOB or invokes a handler that mis-sizes the data. (ggml `ggml_type` table,
74
image color-type dispatch, ASN.1 tag handling.)
75
 
76
### KC-4 - Decompression / recursion bombs into corruption (not just DoS)
77
A header-declared decompressed size is trusted for an alloc while the actual stream writes more
78
(or a nested-structure depth has no limit and overflows the stack). **Discipline:** a pure
79
zip-bomb/OOM is DoS → cleared-axes, **not** Family 1. Only a *corruption* (controlled OOB write)
80
counts. Don't dress DoS as RCE.
81
 
82
---
83
 
84
## The dynamic oracle - this is the whole archetype
85
 
86
The oracle is **non-negotiable and unambiguous**: a sanitizer crash with a *controlled* fault.
87
 
88
1. `clone_repo` the freshest in-scope parser; pin the commit. Prefer a library that just merged a new
89
   format/feature (unfuzzed code - see [[../06-methods/NOVELTY-DOCTRINE]]).
90
2. `compile_harness` with `-fsanitize=address,undefined` (add MSan for uninit-read hunts). If the
91
   project has an OSS-Fuzz harness, **re-run it on the new commit** - OSS-Fuzz fuzzed yesterday's code,
92
   not today's PR.
93
3. `fuzz_start` (libFuzzer/AFL++) with the parse entry point as the target and **valid sample files as
94
   the seed corpus** (coverage-guided needs good seeds). For structured formats, a `boofuzz_session`
95
   or a format-aware mutator beats dumb flips.
96
4. `fuzz_get_crashes` → `triage_crash`. **Classify the fault:**
97
   - heap/stack **write** to attacker data, or **PC control** → strong Family 1 candidate.
98
   - OOB **read** of secrets → Family 7 (info leak) candidate.
99
   - null-deref / divide-by-zero / assertion / OOM → **DoS → DISPROVEN for RCE**, cleared-axes.
100
5. For a write/PC-control crash, escalate exploitability: `angr_analyze` / `concolic_trace` /
101
   `z3_solve` to show attacker control of the corrupted pointer/size; `ghidra_decompile`/`r2_analyze`
102
   to confirm the primitive. The minimized crashing file is the REPLICA_CONFIRMED artifact.
103
6. The *finding* claim scales with what you proved: "controlled heap overflow, PC control demonstrated"
104
   is Family 1; "OOB read crash, exploitability unproven" stays SOURCE_CONFIRMED until you show the
105
   write/control. Do not over-claim a read-crash as RCE.
106
 
107
---
108
 
109
## BORING traps - do NOT spend time here
110
 
111
- **Calling every crash "RCE".** A null-deref or an `abort()` is a **DoS**, not RCE, and DoS is not a
112
  [[../00-doctrine/SEVERITY-GATE|gate]] family. Triage honestly; most fuzz crashes are DoS.
113
- **OOM / zip-bomb / "huge allocation".** Resource exhaustion ≠ memory corruption. Cleared-axes.
114
- **Re-finding a known CVE in an old library version.** Sibling-CVE / patch-status observation. Fuzz
115
  **fresh** code, new formats, new ports.
116
- **Unreachable code paths.** A crash in a parse path the real application never invokes (a disabled
117
  feature, a non-default codec) is low-value until you show a real consumer reaches it.
118
- **A crash you can't reproduce on a clean rebuild.** Flaky ≠ confirmed; downgrade per
119
  [[../00-doctrine/OPERATING-CORE#3-proof-labels-the-four-states-a-candidate-can-be-in|proof labels]].
120
- **Stopping at "it crashes".** A crash is the *start* of exploitability analysis, not the end. For
121
  the RCE claim, show attacker control of the corruption.