⬢ DragonFlyBSD Kernel Audit
DF-2643 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-2643",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "likely",
  "verdict": "REPRODUCED mechanically on a rebuilt stock-code kernel using the sanctioned race-stall harness: hammer2_xop_strategy_write (strategy.c:623-626) passes the chain from hammer2_inode_chain() into hammer2_write_file_core() with no NULL guard, unlike the read xop (:340-364). A harness sysctl knob (harness.diff, K2 kernel) releases the just-acquired chain and forces parent=NULL - the exact state the hammer2_admin.c:556-563 XXX comment admits a device-removal/repoint race can produce between xop queue and run. With the knob armed, a buffered write plus sync fired the async strategy write and the kernel took Fatal trap 12 at hammer2_chain_lookup+0x92 (movzbl 0x98(%rbx),%eax), fault VA 0x98 = parent->bref.type with parent NULL (hammer2_chain.c:2429 reached from strategy.c:734), all CPUs stopped, machine dead at db>. Line-accurate refinement: the first deref is the chain_lookup loop-head read at chain.c:2426-2429, not the finding-cited strategy.c:760 (*parentp)->hmp, which is the next deref site. Reachability remains honest-speculative: requires the cluster slot to NULL between xop_start and run (device removal/repoint/teardown racing in-flight writes - root admin ops on multi-device topologies; this snapshot exposes no volume-del so no natural single-device trigger). Fix validated A/B on K3 (harness + guard): same forced-NULL writes fail with EIO via the existing xop completion (xop_strategy_write: error 1 messages, zero Fatal traps, guest alive and responsive), knob-off writes unchanged. Low severity stands: kernel NULL-deref panic (DoS) plus one stuck bio; no memory-safety primitive (fixed-address read fault); no unprivileged or remote trigger.",
  "exploit_chain": "",
  "evidence": "panic.txt (Fatal trap 12, fault VA 0x98, Stopped at hammer2_chain_lookup+0x92, CPU3 stopping CPUs, db>) captured from the K2 serial console; runfix.knob-on.log (K3 same forced-NULL operation: xop_strategy_write error 1 spam, 0 Fatal traps, guest alive); harness.diff (the knob, unlock+drop then parent=NULL); fix.diff (the guard); k3build.log (K3 BUILD_RC=0)",
  "kernel_refs": [
    "sys/vfs/hammer2/hammer2_strategy.c:623-626",
    "sys/vfs/hammer2/hammer2_strategy.c:340-364",
    "sys/vfs/hammer2/hammer2_strategy.c:734",
    "sys/vfs/hammer2/hammer2_strategy.c:760",
    "sys/vfs/hammer2/hammer2_chain.c:2426-2429",
    "sys/vfs/hammer2/hammer2_inode.c:408-428",
    "sys/vfs/hammer2/hammer2_admin.c:556-563",
    "sys/vfs/hammer2/hammer2_strategy.c:675-681"
  ],
  "poc_changes": "No seed rig existed. Built the two-kernel A/B harness: harness.diff adds a runtime sysctl vfs.hammer2.df2643_harness that, only when enabled, does hammer2_chain_unlock+drop and parent=NULL right after the inode_chain acquisition in xop_strategy_write (leak-free; the unlock/drop block it skips matches what inode_chain acquired); the natural multi-device teardown trigger does not exist in this snapshot (sbin/hammer2 has only volume-list, no volume-del), so the interposed-state harness prescribed by the task directive was used. fix.diff is the minimal upstream guard. Both kernels built in-guest with make nativekernel (BUILD_RC=0).",
  "attempts": 3,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #2: Tue Oct 20 03:34:11 UTC 2026     root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC  x86_64",
  "runtime_sec": 300,
  "guest_dirty": 0,
  "build_cmd": "cd /usr/src && patch -p0 < harness.diff && make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel",
  "run_cmd": "./run_panic_test.sh",
  "code_hash": "a623e7c7dde699bb1fa02fe893ac8a722a64b5a0c13b2a1508b153199314701f",
  "notes": "K2 panic kernel = uname #2 03:34:11; K3 fix kernel = uname #1 03:49:24. The sync-livelock seen on K3 knob-on is a harness artifact (every flush retry forced to fail; a real race fails once). Guest was reset to the clean-source snapshot after verification. fix_status: fixed (A/B validated on K3).",
  "recommended_fix": "Add the NULL guard the read xop already has: if hammer2_inode_chain() returns NULL in hammer2_xop_strategy_write, skip hammer2_write_file_core and feed HAMMER2_ERROR_EIO so the existing completion fails the bio with EIO (see fix.diff)",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Tue Oct 20 03:49:24 UTC 2026     root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC  x86_64",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff (NULL guard mirroring the read xop) validated on rebuilt kernel K3 with the same harness knob armed: the forced-NULL write that panicked K2 at hammer2_chain_lookup+0x92 now completes through the existing error path with per-attempt EIO (xop_strategy_write: error 1), zero Fatal traps, guest alive and responsive; knob-off writes succeed unchanged",
  "fix_evidence": "panic.txt (K2 Fatal trap 12 at 0x98) vs runfix.knob-on.log (K3: 20948 EIO messages, 0 Fatal traps, guest alive); k3build.log BUILD_RC=0"
}