⬢ DragonFlyBSD Kernel Audit
DF-2489 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-2489",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "REPRODUCED on the stock INVARIANTS guest, then FIX VALIDATED. Driving the full production funnel (kernel own ata_identify -> real ata_getparam parse of a crafted IDENTIFY -> real newbus -> RUNNING KERNEL own ad_attach, nm 0xffffffff80328d30) with IDENTIFY word3 heads=0 (or word6 sectors=0) and word53=0x0006 produces Fatal trap 18 integer divide fault at ad_attach+0x24b (divq, total_secs/(heads*sectors), ata-disk.c:147); the bootverbose ad_describe divisor traps identically at ad_describe+0x1bf (ata-disk.c:540); the verbatim #included production copy behaves identically. ad_get_geometry else-branch ata-disk.c:477-478 copies the raw u16 IDENTIFY words with no lower bound and nothing between ata_getparam and ad_attach validates them. Panic-class DoS at device attach (boot/hot-plug, malicious or buggy device/bridge firmware, AV:P): no memory corruption, no route to uid=0 (closed: privesc not reachable from a #DE with attacker-controlled divisor only). Fix: clamp heads->16/sectors->63 when 0 at the single source in ad_get_geometry; single-fix kernel rebuilt, the same crafted IDENTIFY now attaches cleanly (ad1 16384MB <DF2489 ZEROGEOM DISK ...> [33288C/16H/63S]) and the control describe output is byte-identical to stock; the clamp is visible in the fixed binary (movb 0x10/0x3f into ad_softc+0x8/+0x9 before the div).",
  "exploit_chain": "n/a - panic only. Crafted ATA IDENTIFY (word3=0 or word6=0, word53 without ATA_FLAG_54_58) -> ata_getparam verbatim copy (ata-all.c:590) -> ad_get_geometry raw else-branch (ata-disk.c:477) -> ad_attach info.d_ncylinders = total_secs/(0*63) (ata-disk.c:147) -> x86 #DE -> Fatal trap 18, all CPUs stopped, DDB. No escalation path: the faulting instruction consumes only the zero divisor; no write primitive.",
  "evidence": "panic_go2_funnel_heads0.txt (kernel own binary, trap 18 at ad_attach+0x24b divq); panic_go3_funnel_sectors0.txt (word6=0, same); panic_go4_verbatim_attach.txt (verbatim copy, same divq); panic_go5_describe_bootverbose.txt (ad_describe+0x1bf divq, the :540 site, after the MB line prints); ad_attach_stock_disasm.txt (trap IP = movslq ecx,esi + div rsi before the device_add_child line-151 sequence - line attribution); run_control.log (sane geometry: clean attach, ad0 16384MB <DF2489 SANE GEOMETRY FW000001> at ata0-master PIO4 + bootverbose 33554432 sectors [33288C/16H/63S] line, RAID scan); fix_run.log (fixed kernel: go2 crafted heads=0 attaches cleanly with clamped [33288C/16H/63S], control byte-identical); ad_attach_fixed_disasm.txt (clamp in the shipped fixed text); build.baseline.log/build.patched.log (sha checker); env.txt",
  "kernel_refs": [
    "sys/dev/disk/nata/ata-disk.c:147",
    "sys/dev/disk/nata/ata-disk.c:540",
    "sys/dev/disk/nata/ata-disk.c:477",
    "sys/dev/disk/nata/ata-disk.c:469",
    "sys/dev/disk/nata/ata-disk.c:489",
    "sys/dev/disk/nata/ata-all.c:590",
    "sys/dev/disk/nata/ata-all.c:683"
  ],
  "poc_changes": "No seed pack existed (verify mode, orchestrator reserved DF-2489 only); rig authored from scratch following the DF-2210/DF-2496 nata verbatim-interposition class. Two rig iterations were needed: (1) scenarios initially fired from the sysctl handler thread wedged the guest during attach-time sysctl/devfs registration - moved to a dedicated kthread; (2) the built-in atapicam driver attaches to every probed channel and xpt_bus_register asserts the MP token - the worker now holds it, mirroring the boot-attach context. No production code altered; nata headers have no include guards so includes were ordered around the verbatim #include.",
  "attempts": 9,
  "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 (stock baseline; fixed kernel #1: Mon Oct 19 18:19:19 UTC 2026 for fix validation)",
  "runtime_sec": 6600,
  "guest_dirty": 0,
  "build_cmd": "guest: sh /root/dfrig2489/build.sh  (KLD dfrig2489.ko, production ata-disk.c #included verbatim, sha256-checked; installs to /boot/kernel + loader.conf for BOOT-time load)",
  "run_cmd": "guest: sh /root/dfrig2489/run.sh {1|2|3|4|5}  (1=control sane, 2=heads0 funnel, 3=sectors0 funnel, 4=verbatim attach, 5=describe bootverbose; fatal scenarios one per boot, panic on serial via vm.sh log)",
  "code_hash": "12a3c4fe5f4d4a9108de5330512e5b28d681684f38e160d8227cbbaa34d977bf",
  "notes": "Impact ceiling panic/DoS at device attach; NOT privesc (team directive: closed - only attacker influence on the faulting div is the zero divisor and the dividend; #DE stops CPUs, no corruption). Fix validated by single-fix kernel rebuild: baseline reproduced=1 (4 variants: go2/go3 kernel-own funnel, go4 verbatim copy, go5 describe site), patched reproduced=0 with clamped [33288C/16H/63S] describe and byte-identical control output. go4 on the fixed kernel later traps at device_doattach+0x10c (read 0x800) - documented rig-fabrication artifact (fake bsd_device without sysctl_ctx, unreachable on stock because :147 traps first); the authoritative fixed-kernel attach proof is the full-funnel go2 which completes cleanly. Related filed findings: DF-1883 (same sites), DF-1604/DF-1738/DF-2491 (other device-geometry divisors), DF-2496 (RAID geometry class), DF-4798 (u16->u8 heads truncation in the same copy; clamp also covers 0x0100->0).",
  "recommended_fix": "Clamp degenerate device CHS geometry at its source in ad_get_geometry(): if adp->heads==0 set 16, if adp->sectors==0 set 63 (covers both the :147 and :540 divisions and keeps d_nheads/d_secpertrack sane)"
}