# DF-5706 — sys/libkern/memcchr.c live longword over-read (proven benign) + hardening

## What this pack proves (and what it does NOT claim)

`sys/libkern/memcchr.c` — unlike its sibling `bcmp.c` (DF-5700, latent) — is
**compiled into every DragonFlyBSD kernel** (`sys/conf/files:2212`) and
**present in the shipped/running guest kernel** (`ffffffff809d5bf0 T memcchr`,
disassembly matches this C source; see `logs/kernel_liveness.txt`).

Its word-at-a-time scanner performs a **live over-read**:

* backward: the first-word align-down load at `memcchr.c:83-85` reads up to
  7 bytes **before** `begin`;
* forward: the word-walk load at `memcchr.c:90-91` runs while `lp < end`,
  so the last granule can extend up to 7 bytes **past** `begin + n`.

Measured on the pristine source (rig, guest): 2,282,570 below-begin loads,
61,553 past-end loads, max excursion 7 B in both directions, across
2,608,384 differential cases (`logs/instr.out`).

## Why it is benign (the claim of this finding: hardening, no impact)

1. **Page containment (proof + empirical).** Every load is a naturally
   aligned `sizeof(long)` granule that contains at least one in-bounds byte
   of `[begin, begin+n)` (the first granule contains `begin`, the last
   contains `end-1`). Since 8 | 4096, a granule never straddles a page, so
   every load stays inside a page that holds valid buffer bytes — it cannot
   fault and cannot touch an unmapped page. The instrumented rig additionally
   asserts zero loads outside `[align_down(begin), align_up(end))`
   (`g_outside_granule == 0`), and a 762,160-call guard sweep with PROT_NONE
   pages flanking the data page on **both** sides (offsets 0..4095 ×
   page-edge-critical lengths × 5 content modes) recorded **zero** faults.
2. **Result filtering.** Over-read bytes never escape the function: mismatches
   found at `p >= end` are rejected by the `done:` filter at `memcchr.c:114`.
   The rig observed the filter fire **10,948 times** and return `NULL` every
   time (0 wrong returns across all sweeps).
3. **No disclosure.** The loaded words only steer control flow; no byte value
   past `end` is ever returned or copied out.
4. **Consumers are bounded.** All four call sites live in
   `sys/vfs/ext2fs/ext2_alloc.c` (`:1358`, `:1362` inode-bitmap scan of
   `ext2_nodealloccg`; `:1519`, `:1523` block-bitmap scan of
   `ext2_mapsearch`). Their lengths are bounded to `[1, bsize]` by mount-time
   validation: `e2fs_fpg == e2fs_bpg == bsize*8` enforced at
   `ext2_vfsops.c:558-572`, `e2fs_ipg ∈ [ipb, bsize*8]` at `:582-587`
   (also excluding the div-by-zero at `ext2_alloc.c:1352`), and
   `ext2_mapsearch`'s `bpref` is normalized to the cg by its only caller
   `ext2_alloccg` (`ext2_alloc.c:1029-1032`). Buffers are `bsize`-byte
   buffer-cache `bread` blocks, so the ≤7-byte same-page over-read stays
   inside the mapped buffer-cache page.

Also documented (same family as DF-5700's dead branch): the `goto done;` at
`memcchr.c:102` is unreachable — entering the body requires `*lp != word`,
which guarantees one of the 8 TESTBYTEs jumps first. Harmless; kept as belt
and braces.

This finding does **NOT** share DF-5701's truncation shape: `memcchr`'s
length parameter is `size_t` end-to-end (`memcchr.c:60`), and no consumer can
feed a negative or > bsize length given the mount checks above.

## How to reproduce

Run on the host (pack copied anywhere) with the QEMU guest up, or directly
inside the guest:

```sh
# host: ship the pack in (once)
tar czf df5706.tgz -C findings/poc DF-5706
cat df5706.tgz | ssh -F dfbsd-qemu/config dfbsd-maxx \
    'rm -rf /tmp/rig5706 && mkdir -p /tmp/rig5706 && tar xzf - -C /tmp/rig5706'
dfbsd-qemu/vm.sh run_user 'cd /tmp/rig5706/DF-5706 && sh ./build.sh && sh ./run.sh'
```

Expected (see `run.log`, stable across `run.2.log`):

```
guard sweep: 762160 calls (NULL=317387 ptr=444773), wrong returns=0
RESULT: PASS - no load escaped the valid page ...
exhaustive differential : 608384 cases, 0 failures
randomized differential : 2000000 cases, 0 failures
  loads below begin      : 2282570 (max 7 B before begin)
  loads past end         : 61553 (max 7 B past end)
  done-filter rejections : 10948 (...)
  loads outside [align_down(begin), align_up(end)) : 0 (MUST be 0)
RESULT: OVERREAD-CONFIRMED (live, <=7B both directions, granule-contained, result-filtered); semantics exact vs reference
fixed differential: 2608384 cases, 0 failures
fixed loads: below-start=0 past-end=0 in-range=11402250 outside-granule=0 ...
RESULT: FIX-PASS - zero loads outside [begin,begin+n), semantics exact
RESULT: PASS - no load escaped the valid page ...   (guard sweep on FIXED build)
```

## Rig construction

* `extract.sh` + `build.sh` compile the **verbatim** kernel body
  (`sys/libkern/memcchr.c:36-117`, byte-identical — `memcchr_body.c`,
  re-verified by diff; guest `/usr/src` md5 `a200de772658559da4a1217004b8469d`
  == repo) with `shim/prelude.h` replacing only the four kernel-only includes
  at `:30-34`.
* `memcchr_instr.c` is the same algorithm with every load site wrapped
  (`LDC`/`LDB` + `NOTE`); zero differential failures over 2.6M cases proves
  the instrumentation did not change behavior.
* `guard_harness.c` flanks one usable page with PROT_NONE guards on both
  sides and sweeps every begin-offset × page-edge-critical lengths × content
  modes (incl. the killer mode: canaries ≠ c everywhere outside
  `[begin,end)`).
* `fix.diff` (validated, never applied to `sys/`): bytewise head alignment,
  full-word loop only while `(lp + 1) <= end`, bytewise tail — zero loads
  outside `[begin, begin+n)`, semantics identical.

## Files

* `build.sh`, `run.sh`, `extract.sh`, `shim/prelude.h` — rig
* `memcchr_kernel.c` (pristine verbatim body, generated), `memcchr_body.c`
* `memcchr_instr.c`, `instr_driver.c` — measurement + differential
* `guard_harness.c` — containment sweep (used for pristine AND fixed)
* `memcchr_fixed.c` (generated: fix.diff applied), `memcchr_fixed_instr.c`,
  `fixed_driver.c` — fix validation
* `fix.diff` — the fix (git-apply-able against the repo root)
* `build.log`, `run.log`, `run.2.log`, `logs/*.out`, `journal.txt`,
  `logs/kernel_liveness.txt`, `logs/scratch/` (patched + orig kernel file)
* `env.txt` — guest uname, cc, kernel symbol proof
* `VERDICT.md`, `manifest.json`, `verdict.json`
