| 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. |