DF-2647 / manifest.json
{ "finding_id": "DF-2647", "verdict": "reproduced", "impact": "panic", "impact_detail": "Controlled kernel heap OOB write up to 65279 bytes with attacker-chosen length and content (on-media bytes), repeatable per ioctl. Stock INVARIANTS kernel: deterministic KKASSERT panic. Production non-INVARIANTS kernel: silent corruption, allocator GPF (trap 9 in _kmalloc) after repeated smears, or in-copy stop (memmove) at 0xFFFF. Root-gated ioctl + hostile-image threat model (ring-0 ceiling), not an unprivileged uid=0 path.", "reproduce": { "build": "guest: sh mkbase2647.sh (64MB vol testvol + PFS zz_pwn); host: python3 forge_df2647.py base2647.img h2_2647_0300.img 0x300 / h2_2647_ffff.img 0xffff; guest: cc -O2 -o pfsget_scan pfsget_scan.c; optional kernels: config DF2647_NOINV (X86_64_GENERIC minus INVARIANTS/DEBUG) via make nativekernel", "run": "guest root: vnconfig -c vn0 IMG && mount -t hammer2 /dev/vn0@testvol /mnt/h2 && ./pfsget_scan /mnt/h2 1 (stock: instant panic; no-INV 0xFFFF: stop in memmove; no-INV 0x300 xN: silent corruption then _kmalloc GPF)", "expected": "Stock: panic assertion 'ripdata->meta.name_len < sizeof(pfs->name)' at hammer2_ioctl.c:494 (panic_inv.txt). No-INV: memmove stop (panic_ffff_noinv.txt) / _kmalloc trap 9 (panic_noinv.txt) / silent survival. Fixed kernel: 'hammer2: pfs_get: corrupt name_len 65535, clamped' on console, no crash over 100 scans (fix_run.log)." }, "kernel_refs": [ "sys/vfs/hammer2/hammer2_ioctl.c:487-497", "sys/vfs/hammer2/hammer2_ioctl.c:117-120", "sys/vfs/hammer2/hammer2_ioctl.h:92-106", "sys/vfs/hammer2/hammer2_disk.h:957", "sys/vfs/hammer2/hammer2_disk.h:1012", "sys/kern/sys_generic.c:668-697", "sys/vfs/hammer2/hammer2_vfsops.c:495" ], "fix": "fix.diff: replace the INVARIANTS-only KKASSERT with a real bound (clamp name_len to sizeof(pfs->name)-1 + kprintf). VALIDATED on rebuilt non-INVARIANTS kernel: clamp message fires with the attacker value, 100x 0xFFFF scans + 0x300 run survive with clean unmount (fix_run.log); the KKASSERT line no longer exists so the stock INVARIANTS panic is structurally removed as well.", "artifacts": [ "README.md", "VERDICT.md", "mkbase2647.sh", "forge_df2647.py", "pfsget_scan.c", "pfslookup_victim.c", "pfsnap_victim.c", "build.sh", "run_surgical.sh", "run_surgical2.sh", "run_catastrophic.sh", "base2647.img", "h2_2647_0300.img", "h2_2647_ffff.img", "run.log", "build_noinv.log", "fix_build.log", "fix_run.log", "panic_inv.txt", "panic_noinv.txt", "panic_ffff_noinv.txt", "env.txt", "code_hashes.txt", "fix.diff", "manifest.json", "verdict.json" ] } |