# DF-5706 VERDICT — memcchr.c live, page-contained longword over-read (Info/hardening)

**Status: reproduced (the over-read property, measured and bounded). Impact: none.**

## Context (pass 2, GLM 5.3)

`sys/libkern/memcchr.c` was re-audited with the primitives-family checklist
that produced DF-5700/5701 on the sibling `sys/libkern/bcmp.c`:
(a) longword walk / over-read containment, (b) length arithmetic
(int vs size_t truncation), (c) return semantics, (d) consumer census.

## (0) Liveness — unlike bcmp.c, memcchr is LIVE

* `sys/conf/files:2212` — `libkern/memcchr.c  standard` (bcmp.c has no entry).
* Shipped guest kernel: `nm /boot/kernel/kernel` → `ffffffff809d5bf0 T memcchr`;
  build object `memcchr.o` → `T memcchr`.
* Disassembly of the running kernel's symbol (`logs/kernel_liveness.txt`)
  shows exactly this C implementation: 8-byte pattern replication
  (`shl $0x8/$0x10/$0x20`), `n==0` early return (`test %rdx,%rdx; je`),
  align-down first-word load (`and $0xfffffffffffffff8`, `cmp %rcx,(%r9)`).
* Only consumer family: ext2 bitmaps — `sys/vfs/ext2fs/ext2_alloc.c:1358,1362`
  (`ext2_nodealloccg`, inode bitmap) and `:1519,1523` (`ext2_mapsearch`,
  block bitmap); both look for the first non-`0xff` byte (a free bit).
  ext2fs ships as `/boot/kernel/ext2fs.ko`; with the default
  `vfs.usermount=0` mounting an image requires root (context for exposure).

## (a) The longword walk — over-read exists, is live, and is provably benign

Loads: first-word align-down `*lp++` at `memcchr.c:83-85`; word-walk `*lp` at
`memcchr.c:90-91` with loop guard `lp < end` (`:90`), so the last granule may
extend up to `sizeof(long)-1` bytes past `begin+n` when `end` is unaligned;
the first granule may start up to 7 bytes before `begin`.

**Containment proof:** every load is a naturally aligned granule containing
≥1 in-bounds byte (first granule contains `begin`; last contains `end-1`);
8 | 4096 ⇒ granules never straddle pages ⇒ each load lies fully inside a page
holding valid buffer bytes. It cannot fault; it cannot reach an unmapped page.

**Empirical (guest rig, pristine verbatim body, md5-identical to repo):**
* instrumented measurement (`logs/instr.out`): 2,282,570 loads below begin
  (max 7 B), 61,553 loads past end (max 7 B), 11,404,517 fully in-range,
  **0 loads outside `[align_down(begin), align_up(end))`**;
* guard sweep (`logs/guard.out`): 762,160 calls, single usable page flanked
  by PROT_NONE guards on both sides, every begin-offset 0..4095 × small +
  page-edge-critical lengths (end landing at page offset 4096-k, k=0..9) ×
  5 content modes → **zero faults**.

**No disclosure:** mismatches located at `p >= end` are rejected by the
`done:` filter (`memcchr.c:114`); the rig observed **10,948 filter
activations, every one correctly returning NULL** (0 wrong returns).
Loaded over-read bytes never leave the function. (A ≤1-bit timing channel on
whether the ≤7 bytes past the buffer equal `c` is theoretically present —
same as every strlen-style scanner — but for the ext2 consumers those bytes
are adjacent bytes of the same buffer-cache block; sub-threshold.)

Conclusion: DF-5700's over-read shape is shared but LIVE here — still not a
vulnerability. Filed as **Info (hardening)** with the containment proof.

## (b) Length arithmetic — DF-5701's shape NOT shared

`memcchr(const void *begin, int c, size_t n)` (`memcchr.c:60`) — `size_t`
end-to-end inside the function; `end = begin + n` (`:84`) cannot wrap for
any caller-reachable n. Consumers pass `int len` but every value is bounded
to `[1, bsize]` by mount-time validation:

* `e2fs_bpg == e2fs_fpg`, both nonzero, and `bpg == bsize*8` exactly
  (`ext2_vfsops.c:556-572`) ⇒ `len = howmany(fpg, NBBY) - start = bsize -
  start ∈ [1, bsize]` (`ext2_alloc.c:1518`); `start = dtogd(fs,bpref)/NBBY`
  is bounded because `ext2_alloccg` (the only caller of mapsearch) forces
  `dtog(fs,bpref) == cg` else `bpref = 0` (`ext2_alloc.c:1029-1030`) and
  converts group-relative (`:1032`).
* `e2fs_ipg ∈ [e2fs_ipb, bsize*8]` (`ext2_vfsops.c:582-587`) ⇒ inode-bitmap
  `len = howmany(ipg - ipref, NBBY) ≤ bsize` (`ext2_alloc.c:1357`); wrap
  calls use `len = start + 1 ≤ ceil(ipg/8)` (`:1360-1362`, `:1521-1523`).
* `ipg ≥ ipb ≥ 1` also excludes the division by zero at `ext2_alloc.c:1352`
  (`ipref %= fs->e2fs_ipg`).

A crafted ext2 image cannot reach memcchr with an out-of-range length: the
superblock is validated at mount, before any bitmap scan.

## (c) Return semantics — correct

Returns pointer to the first byte ≠ c within `[begin, begin+n)`, or NULL.
The byte loops deliberately test into the granule past `end`
(`memcchr.c:86` bound is `lp`, not `end`) but the `p < end` filter at
`:114` guarantees the return domain. Verified by 2,608,384 differential
cases vs a naive reference: **0 disagreements** (608,384 exhaustive:
offsets 0..31 × lengths 0..96 × every mismatch position × 4 c-values with
below-begin/past-end canaries; + 2,000,000 randomized).
`n == 0` returns before any load (`:75-76`) — rig-verified.
Also: the `goto done;` at `:102` is unreachable (body entered only when
`*lp != word`, which forces an earlier TESTBYTE jump) — dead branch, same
family note as DF-5700; harmless.

## Fix validation (rig-level, per the established family pattern)

`fix.diff` replaces the align-down head with bytewise alignment and runs the
word loop only while the whole granule is in range (`(lp + 1) <= end`), plus
a bytewise tail — zero loads outside `[begin, begin+n)` in any direction.
Applied via `patch -p1` to a scratch copy of the pristine kernel file inside
the build (never to `sys/`):

* fixed instrumented: below-start=0, past-end=0, outside-granule=0;
  differential 2,608,384 cases, 0 failures (`logs/fixed.out`);
* fixed guard sweep: 762,160 calls, PASS (`logs/guardfix.out`).

No kernel rebuild was performed: the finding is Info/hardening with zero
demonstrated impact (nothing to observe in-kernel); the rig compiles the
exact kernel source bytes (`memcchr_body.c` byte-identical to
`sys/libkern/memcchr.c:36-117`, re-diffed; guest `/usr/src` md5 matches the
repo md5 `a200de772658559da4a1217004b8469d`), which is the same validation
level DF-5700 used for this family.

## Negative results (classes hunted and killed)

* OOB write: none — function only reads; returns pointers, never writes.
* Uninitialized `p` at `done`: impossible — `done` is reachable only via
  TESTBYTE after `p` is assigned (`:86`, `:92`).
* Pointer overflow `begin + n`: unreachable n (≤ bsize at every call site).
* Consumer OOB from crafted ext2 images: blocked at mount
  (`ext2_vfsops.c:556-587`); div-by-zero (`ext2_alloc.c:1352`) impossible.
* Info leak: over-read bytes never returned (filter at `:114`; measured).
* `~LONGPTR_MASK` int-promotion on 64-bit: `(uintptr_t)begin & ~7UL`-safe
  (int −8 converts to the same mask); `#if LONG_BIT` guards 32/64 only.
* Refcount/locks/privilege: N/A (pure leaf function over caller buffers).

Guest left clean: rig confined to `/tmp/rig5706` (removed after artifact
retrieval); no kernel changes, no panics, guest still up.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
