DF-2643 / verdict.json
{ "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" } |