⬢ DragonFlyBSD Kernel Audit
DF-3248 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-3248",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "high",
  "confidence": "certain",
  "verdict": "REPRODUCED: hpfs_genlookupbynames unvalidated dirent walk (the write side of DF-0927s read walk) lets an unprivileged file owner drive hpfs_updateparent into storing 20 attacker-chosen bytes (mtime@+8 u64 + size@+16 u32 + atime@+24 u64) at an attacker-chosen offset (sum of u16 de_reclen hops, <=64KB) past a 2048-byte buffer-cache dirblk chunk. Demonstrated at +2048 (same 4KB page, one object past the chunk): uid-1001 open()+futimes()+close() plants 0x4242424242424242/0xcafebabe/0x4141414141414141, forced deactivation runs the identical hpfs_inactive call path and the planted neighbor bytes flip exactly to the attacker values; guest alive (silent corruption - bdwrite persists only in-bounds bytes). DoS variant observed repeatedly: OOB hop onto garbage reclen==0 chain livelocks the walk with everything held. uid0 route hard-blocked (chunk-relative write; 2048-class chunks isolated 0x6000-0xC6000 apart; no cred-bearing victim within +64KB demonstrable) - realistic composition is corrupting neighboring buffer contents re-parsed by another FS.",
  "exploit_chain": "crafted HPFS image (root dirblk XSTEP/END|DE_DOWN dive into root2 whose XSTEP2 dirent carries the OOB hop; spray dirblks with forged ZFILE dirents for deterministic termination) -> uid-1001 stat precache (caches dirblk chunks) -> harness KLD pins buffers (B_LOCKED) + plants forged dirent at origin+2048 (emulating attacker heap grooming of the neighbor) -> UNPRIV uid-1001 open(/mp/ZFILE) matches the dirent past the chunk (OOB read) -> futimes() sets h_atime/h_mtime (arbitrary u64s) + H_PARCHANGE -> close() defers VOP_INACTIVE -> deactivation (natural recycle/unmount observed, harness-forced for determinism) -> hpfs_updateparent re-runs the OOB walk and executes the 3 stores -> 20-byte OOB heap write confirmed",
  "evidence": [
    "findings/poc/DF-3248/VERDICT.md (full narrative + decisive DF3248F console block)",
    "findings/poc/DF-3248/README.md (chain, harness-vs-attack roles)",
    "findings/poc/DF-3248/run.log (orchestrated decisive run)",
    "findings/poc/DF-3248/run.smoke.log,run.k1.log,run.k2.log (controls + DoS/livelock variant runs)",
    "findings/poc/DF-3248/craft_img.py, poc.c, autopatch.c, finalizer.c, mount_rw.c (sources)",
    "findings/poc/DF-3248/fix.diff (bounds the walk: freeoff validation, per-dirent validation before any deref, DE_DOWNLSN bounds, HPFS_MAXDIRDEPTH=64)"
  ],
  "kernel_refs": [
    "sys/vfs/hpfs/hpfs_lookup.c:73-107 (unbounded dep += de_reclen walk, DE_DOWN dive w/o depth/cycle guard)",
    "sys/vfs/hpfs/hpfs_subr.c:699-709 (hpfs_updateparent stores through the OOB dep + bdwrite)",
    "sys/vfs/hpfs/hpfs_vnops.c:530-544 (unpriv utimes -> h_atime/h_mtime + H_PARCHANGE)",
    "sys/vfs/hpfs/hpfs_vnops.c:598-609 (hpfs_inactive -> hpfs_updateparent)",
    "sys/vfs/hpfs/hpfs.h:114-131 (struct hpfsdirent LP64 offsets mtime@8 size@16 atime@24; DE_DOWNLSN)"
  ],
  "poc_changes": "verdict.json reconstructed by the orchestrator from VERDICT.md/README.md/run.log after the reviewer was rate-limited (5h usage cap) immediately after writing VERDICT.md + fix.diff but before emitting verdict.json/manifest.json; fix_build.log/fix_run.log referenced in README were never copied into the pack - fix validation rests on VERDICT.md section Fix validation (kernel rebuilt in-guest, identical PoC fails EINVAL at first insane advance, in-bounds lookups intact).",
  "attempts": 4,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0 Thu Jul 2 06:02:54 UTC 2026 X86_64_GENERIC (stock INVARIANTS kernel)",
  "runtime_sec": 9000,
  "guest_dirty": 1,
  "build_cmd": "sh run_full.sh (in-guest: cc build of poc + KLDs df3248patch/df3248fin2)",
  "run_cmd": "sh run_full.sh (upload, build modules, mount RW via mount_rw.c, uid-1001 precache+trigger, forced-deactivation verify)",
  "code_hash": null,
  "notes": "Read side of the same walk is DF-0927 (not re-reported); DF-3248 is the write sink via hpfs_updateparent. Same uid0 hard-blocker class as DF-3137 (documented). Guest may need vm.sh reset with-src before the next spawn (harness KLDs + crafted mounts). Attempts: 3+ development iterations (buffer recycling defeated 3 early attempts; B_LOCKED pinning solved it); decisive run reproduced with full console evidence; DoS/livelock variant observed repeatedly during development.",
  "recommended_fix": "fix.diff: validate d_freeoff per bread; validate every dirent fully inside [20,freeoff) with reclen >= offsetof(de_name)+de_namelen and reclen <= freeoff-dep_offset BEFORE any field dereference (loop restructured so the DE_END test follows the sanity check); bounds-check END markers DE_DOWNLSN before read; cap dive depth (HPFS_MAXDIRDEPTH=64) to break DE_DOWN cycles."
}