# DF-3138 VERDICT — REPRODUCED (4-byte OOB heap write + OOB read past a 512-byte kernel buffer; adjacent-object corruption kernel-logged; one kernel crash)

## Setup

Same guest/mount/uid-1001 protocol as DF-3137 (crafted HPFS image, RW
mount via direct mount(2), unprivileged trigger).
Tree per file: fnode(AB_NODES,1) -> P(AB_NODES, busycnt=61, freecnt=1,
an[60] in sector slack -> C) -> C(leaves, busycnt=40 full, freecnt=0).
A 1-byte pwrite at offset 512 makes C split immediately (its insert loop
is skipped), AE_SPLIT propagates to P's frame.

## Observed, single-file run (deterministic)

1. P's frame executes `bcopy(ranp, AB_LASTANP(P), 16)` at
   sys/vfs/hpfs/hpfs_alsubr.c:599. LASTANP = an[60] at [500,508) (slack,
   crafted); the second alnode an[61] = {0xFFFFFFFF, 0x00000200} lands
   at [508,516) — 4 bytes past the 512-byte b_data. 0x200 is the lsn
   allocated for the split AlSec (attacker-chosen via bitmap layout).
2. P overflows (busycnt 62, freecnt 0) and splits itself; the source
   bcopy at :229 reads [268,516) — a 4-byte OOB READ — copying the same
   0x200 into the new AlSec's an[30].an_lsn.
3. The children-reparenting loop dereferences it: breadalsec(0x200)
   returns the cached N1 buffer, whose as_magic had just been corrupted
   by step 1 (the neighbor chunk of P's data was N1's data):
       hpfs_breadstruct: MAGIC DOESN'T MATCH (0x00000200 != 0x37e40aae)
       hpfs_addextentr: FAILED 22 ... hpfs_write: hpfs_extend FAILED 22
   pwrite returns EINVAL; the kernel is still up (the adjacent object
   was a buffer, not a freelist pointer).
4. Proof the 0x200 came from OOB memory: an exhaustive scan of the
   crafted image finds NO sector whose first u32 is 0x00000200; the only
   writer of that value into that location is the :599 bcopy's OOB half,
   and the only reader path is the :229 OOB read-back (dmesg excerpts in
   panic.txt).

## Observed, 40-file run

The unprivileged trigger loop killed the kernel mid-run: the trigger ssh
session died with no output (RUN_EXIT=124), the follow-up ssh could not
complete a banner exchange, and the next boot ran fsck on / (unclean
shutdown — boot.log). With 40 different P-buffer allocations the OOB
write hits varying neighbors (free chunks, freelist words, other live
objects), one of which took the system down. The panic text scrolled out
of the small serial console buffer before it could be captured — crash
evidence = unclean shutdown + guest loss, signature not retained.

## Primitive / escalation

4-byte write of an attacker-chosen small integer (allocated lsn) at
b_data+512, plus a 4-byte OOB read of the same word that is then used as
a pointer (an_lsn dereferenced by breadalsec) — i.e. the OOB read can
also be steered into random-sector reads. A reliable uid0 chain needs
the neighbor chunk to be a freelist/pointer-bearing object (1/64 chance
per allocation for a page-edge chunk; the crash run shows fatal
adjacency occurs in practice) — full uid0 not demonstrated within
budget; corruption + crash are.

## Fix validation

Shared alblk-invariant fix (fix.diff in the DF-3137 pack) rejects
P (busycnt 61 > 60) in hpfs_addextentr before any bcopy. Patched
kernel: identical trigger returns EINVAL ("hpfs_addextentr: INSANE
ALBLK" on console), no corruption, no crash (fix_run.log).

## Verdict

REPRODUCED — memcorrupt. Kernel refs: sys/vfs/hpfs/hpfs_alsubr.c:572
(loop bound), :578 (AB_LASTANP), :592-601 (fixup bcopy), :401-411
(fnode-root twin), :225-235 (split source read), hpfs.h:157
(AB_LASTANP macro), sys/vfs/hpfs/hpfs_vfsops.c:535 (no validation).

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