DF-2491 / verdict.json
{ "finding_id": "DF-2491", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "CONFIRMED on the stock INVARIANTS kernel, six ways. The CHS sub-path of the kernel own static ata_tf_write (sys/dev/disk/nata/ata-lowlevel.c:771-790) divides by IDENTIFY-supplied sectors / sectors*heads with no zero check: current_sectors=0 (word 56, flagged valid by word 53) gives Fatal trap 18 at ata_tf_write+0x1f0 (disassembled: the div feeding (lba%sectors)+1, :783); current_heads=0 gives trap 18 at +0x218 (lba/(sectors*heads), :785). The full production funnel proves the claimed chain end-to-end: a crafted IDENTIFY (word53 54-58 valid, current 16/0, raw 16/63, version_major=0, lba_size=0) makes the kernel own ad_get_geometry fall to its GUARDED raw-words branch so ad_attach SURVIVES (ad0 created; the DF-2489 ata-disk.c:147 site does NOT trip), the gate at ata-disk.c:485-486 sets ATA_D_USE_CHS, and the FIRST I/O dispatches the kernel own chain (ata_controlcmd -> ata_queue_request STALL path -> ata_begin_transaction -> ata_generic_command -> hw.tf_write) into the division by zero. A sha256-gated verbatim #included copy of the production file traps at the identical instruction offsets (byte-identity). No memory-safety primitive exists here: the divisor only selects the fault, quotients are truncated to taskfile bytes - impact is panic/DoS from a malicious ATA device at first I/O (AV:P, same class as DF-2489; panic-close per team directive). fix.diff (extend the ata-disk.c:469-470 guard idiom to ata_tf_write, guard the raw branch, 16/63 clamp) was applied in guest /usr/src, kernel rebuilt (nativekernel, BUILD_RC=0), and A/B validated: all six fatal scenarios now return rc=0 with a clamped CHS taskfile identical to the sane control, and the go1/go4/go6/go8 controls are bit-identical to stock.", "exploit_chain": "Not an exploitation primitive (integer divide fault on a device-controlled divisor; no OOB, no controllable write). Chain to impact: malicious ATA device or IDENTIFY-answering element -> crafted IDENTIFY with version_major=0 (or lba_size=0) forcing ATA_D_USE_CHS plus a zero geometry word -> first I/O to the disk (including the automatic geom partition-table probe) -> #DE in kernel text -> Fatal trap 18, all CPUs stopped, system down. Repeatable at every boot/attach.", "evidence": "panic.go7_funnel_sectors0.txt (money shot: ad0: 0MB <DF2491 ZEROGEOM DISK> attach-survival line followed by Fatal trap 18, Stopped at ata_tf_write+0x1f0: divq %r10,%eax); panic.go2/go3/go5/go9/go10 (remaining variants, both division sites, verbatim-copy IP inside dfrig2491.ko); ata_tf_write_trap_disasm.txt (objdump pinning +0x1f0 to the (lba%sectors)+1 div at :783 and +0x218 to the CYL div at :785, kernel symbol base 0xffffffff8032a910); run_control.log (stock controls: CHS/LBA taskfiles verified against independent math); fix_run.log (fixed kernel: every fatal scenario survives, clamped registers); build.log (checker sha256 equality guest==audited tree); VERDICT.md (full narrative + matrix).", "kernel_refs": [ "sys/dev/disk/nata/ata-lowlevel.c:783", "sys/dev/disk/nata/ata-lowlevel.c:785", "sys/dev/disk/nata/ata-lowlevel.c:790", "sys/dev/disk/nata/ata-lowlevel.c:774", "sys/dev/disk/nata/ata-disk.c:469", "sys/dev/disk/nata/ata-disk.c:485", "sys/dev/disk/nata/ata-queue.c:98", "sys/dev/disk/nata/ata-lowlevel.c:648" ], "poc_changes": "Seed had no runnable PoC. Built the DF-2210/DF-2489-class interposition rig: (1) fabricated channel with kernel-memory register file behind a struct resource r_bustag=X86_64_BUS_SPACE_MEM so the pre-division taskfile OUTBs at :769-770 are satisfied and observable; (2) the kernel own exported ata_generic_hw() wiring plus ata_controlcmd/ata_queue_request STALL dispatch so the kernel own STATIC ata_tf_write runs from kernel text; (3) rig device thread completing non-fatal variants via the kernel own ata_interrupt/ata_end_transaction/ata_finish chain; (4) verbatim #include of production ata-lowlevel.c (5 globals renamed, sha256-gated) for byte-identity; (5) full funnel reusing the DF-2489 IDENTIFY stub seam with the guard-divergence IDENTIFY (word53 valid + current_sectors=0 + raw sane) that passes DF-2489 sites but trips this one. One mid-run rig update: added taskfile dump-on-survival lines (dead code on the fatal paths) before the fixed-kernel A/B; the fatal runs used the initial variant.", "attempts": 16, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 3300, "guest_dirty": 0, "build_cmd": "vm.sh run_root sh /root/dfrig2491/build.sh (make -m /usr/src/share/mk; KLD dfrig2491.ko; boot-time load via loader.conf)", "run_cmd": "vm.sh run_root sh /root/dfrig2491/run.sh 7 (fatal: 2/3/5/7/9/10 one per boot; controls: 1/4/6/8)", "code_hash": "b301bcc73e37208113413405f717ce52dc538fefbe4bbc0c240701885f12e20d", "notes": "Trap IPs: go2/go7/go10 0xffffffff8032ab00 = ata_tf_write+0x1f0 (:783, divq %r10); go3/go9 0xffffffff8032ab28 = +0x218 (:785, divq %rdi); go5 0xffffffff81de5df0 inside dfrig2491.ko (verbatim copy, same +0x1f0). Stock kernel sha256 5dc83dac19ad09effd6241c33e0c0669d41b6497ee92d87d3a2e45f287bc22ad; fixed kernel 79efa98f1ae0c602270ef53ca7b115caa0357c5e4e87d814eab67dae2766900f. Audited ata-lowlevel.c sha256 089a2ac25b1000d7ee4f030359884c54bde214e906af684ff1a670d30755d557 (checker-verified in build.log). Same attacker class as DF-2489 (malicious device at attach/first-I/O, AV:P); DF-2489 covers the ata-disk.c:147/:540 divisions, DF-2491 the ata_tf_write I/O-time sites - the word53/current_* guard divergence makes them independently reachable. Guest reset with-src after the run.", "recommended_fix": "In ata_tf_write, gate the current_* branch on (atavalid & ATA_FLAG_54_58) && current_heads && current_sectors, gate the raw branch on heads && sectors, and fall back to 16/63 when both invalid (the ata-disk.c:469-470 guard idiom); see fix.diff." } |