⬢ DragonFlyBSD Kernel Audit
DF-2213 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-2213",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "none",
  "confidence": "certain",
  "verdict": "Reproduced on the stock INVARIANTS guest with the production-handler KLD dispatch rig (DF-2210 rig class): the production chipsets/ata-cyrix.c compiled verbatim into the rig (sha256 checker-verified against the audited sys/ tree) AND the running kernel own shipped handler dispatched by address (nm-local-symbol 0xffffffff80319490) both execute ata_cyrix_setmode line 98 with the mode every real funnel passes first (ATA_PIO_MAX; the handler then settles it via the kernel own ata_limit_mode and ata_controlcmd through the fabricated ATA_STALL_QUEUE synchronous CONTROL completion). For a PIO4-capable disk the settled mode is 0x0c and the computed index mode & ATA_MODE_MASK is 12 against a five-element piotiming table (valid 0..4) - the shipped binary disassembly shows and $0xf,%eax feeding mov 0x80d0cd20(,%rax,4),%eax - so the handler reads kernel .rodata 28 bytes past the table end (udmatiming[2] = 0x00911030 in the shipped build) and writes that UDMA2 timing constant into the controller PIO timing register 0x20 via ATA_OUTL, where the PIO4 constant 0x00040010 belongs; a PIO0-only disk yields index 8 reading dmatiming[2] = 0x00002020 into the PIO register. The rig captures the register write through the real bus_space_write_4 onto a fabricated MEM-space handle (the controller register file) and cross-checks each written value against live kernel memory at piotiming + 4*index - byte-identical match in every OOB scenario (OOB read CONFIRMED). The full extracted call site (ata-pci.c:855-866) with a UDMA2 request shows the boot state on real hardware: both controller slots 0x20 and 0x24 end up holding UDMA timings, the PIO one illegitimately. Controls prove branch specificity: the WDMA and UDMA branches index their three-element tables in bounds (mode constants 0x20..0x22 and 0x40..0x42 mask to 0..2), only the PIO branch at line 98 is wrong, and the correct idiom is ata_mode2idx (ata-pci.c:719-726) used by every sibling including the NatSemi clone of this driver (ata-national.c:102). Impact ceiling per team directive: NOT an info leak (the OOB value is a build-time .rodata constant never returned to userspace), NOT memory corruption reachable by an attacker (write sink is one fixed hardware register with deterministic content), NOT a privesc and NOT a DoS - closed explicitly as no route to uid=0. The realistic impact on Cyrix 5530 hardware is a wrong PIO timing programmed on every probe (setmode settles >= PIO0), a silent disk data-integrity defect; persisted Low severity stands.",
  "exploit_chain": "None - the OOB read draws a deterministic compile-time constant from kernel .rodata and deposits it in one fixed controller register; no attacker-controlled value, no kernel memory disclosure to userspace, no write primitive beyond the hardware register, no control-flow influence. Not an escalation candidate.",
  "evidence": "run.log: all five scenarios on the stock kernel - go1 (shipped handler) settled mode=0x0c, INDEX USED = 12 <<< OUT OF BOUNDS, reg 0x20 = 0x00911030 vs CORRECT piotiming[4] = 0x00040010, and OOB SOURCE kernel_mem[piotiming+12*4] = 0x00911030 == reg 0x20 write (OOB read CONFIRMED); go2 identical via the verbatim copy; go3 index 8, reg 0x20 = 0x00002020 vs correct 0x00009172, OOB CONFIRMED; go4 both slots 0x20/0x24 = 0x00911030 after the two production calls; go5 control reg 0x24 = 0x00002020 in bounds. kernel_handler_disasm.txt: stock binary shows and $0xf,%eax then mov 0x80d0cd20(,%rax,4),%eax and the .rodata window mapping indices 8..12 onto dmatiming[2]/hole/udmatiming[0..2]. fix_run.log: same scenarios on the patched kernel write piotiming[4]/piotiming[0] to reg 0x20 with the UDMA/WDMA slots and all other behavior identical. fixed_handler_disasm.txt: callq ata_mode2idx feeding the table load. build.log checker lines: verbatim sha256 29f54803... == audited tree.",
  "kernel_refs": [
    "sys/dev/disk/nata/chipsets/ata-cyrix.c:98",
    "sys/dev/disk/nata/chipsets/ata-cyrix.c:67",
    "sys/dev/disk/nata/chipsets/ata-cyrix.c:84",
    "sys/sys/nata.h:200",
    "sys/sys/nata.h:203",
    "sys/dev/disk/nata/ata-pci.c:719",
    "sys/dev/disk/nata/ata-pci.c:855",
    "sys/dev/disk/nata/ata-pci.c:863",
    "sys/dev/disk/nata/chipsets/ata-national.c:102",
    "sys/dev/disk/nata/ata-all.c:523",
    "sys/dev/disk/nata/ata-all.c:949",
    "sys/dev/disk/nata/ata-queue.c:100"
  ],
  "poc_changes": "No seed PoC existed (empty pack); built from scratch on the DF-2210 rig pattern. Adaptations for this finding: the write sink is ATA_OUTL bus_space (not pci_write_config), so the fabricated PCI kobj bus was replaced by a MEM-space bus tag + kernel-buffer handle which the real bus_space_write_4 stores into (cpu/x86_64/include/bus_dma.h:404-412); the r_res1 guard of this file must pass (ctlr->r_res1 fabricated - this file, unlike the DF-2210 national clone, guards its derefs); a third timing table, the kernel .rodata table base is recovered automatically from the shipped handler disassembly (awk over the absolute-indexed load bytes) and verified at runtime against the five source constants; scenario identify data drives the kernel own ata_limit_mode to PIO4, PIO0, WDMA2 and UDMA2 settles to cover the claimed index range 8..12 plus in-bounds controls.",
  "attempts": 2,
  "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": "cd /root/dfrig_cyx && sh build.sh   (make -m /usr/src/share/mk; kldload ./dfrig_cyx.ko)",
  "run_cmd": "sh /root/dfrig_cyx/run.sh 1   (then 2 3 4 5; run.sh nm-resolves the shipped handler and objdump-resolves the .rodata piotiming base, then sysctl kern.dfrig_cyx.go=N; output on console/dmesg)",
  "code_hash": "7780cf39f03004fcccf59bbda41806346a70adb663f5e4a5e6d178259cdf9bed",
  "notes": "Hardware-gated (Cyrix 5530, ATA_CYRIX_5530); established production-handler KLD dispatch rig class (DF-2210 national, DF-252x trm, DF-249x nata). The .rodata neighborhood at indices 8..12 (dmatiming[2]/hole/udmatiming[0..2]) is a property of this kernel build; the index-range defect itself is build-independent. Team directive closed explicitly: not a privesc - deterministic constant, fixed register sink, no userspace disclosure. Interesting inversion vs DF-2210 confirmed: THIS file NULL-guards (r_res1 at line 84) and the guard passes in every scenario; the National clone dropped it.",
  "recommended_fix": "Index with the family idiom: piotiming[ata_mode2idx(mode)] at ata-cyrix.c:98 (matches ata-national.c:102 and every sibling chipset driver).",
  "fix_status": "fixed",
  "fix_validated": 1,
  "fix_diff": "findings/poc/DF-2213/fix.diff",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Tue Oct 20 22:39:26 UTC 2026     root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC  x86_64",
  "fix_verdict": "Applied fix.diff inside the guest /usr/src (one line: piotiming[ata_mode2idx(mode)]), rebuilt with make -j6 nativekernel KERNCONF=X86_64_GENERIC + installkernel (fix_build.log, kernel sha256 bfecc68ad512f8cbfbb2f1447b52411a17f435f891b47a21b1569c3a3abc4fc2), rebooted, rebuilt the rig from the patched source (checker notes the fixed sha d1101d0e...), and re-ran the exact five scenarios: every PIO-branch register write now carries the correct source constant (go1/go2/go4 reg 0x20 = 0x00040010 = piotiming[4]; go3 reg 0x20 = 0x00009172 = piotiming[0]); the in-bounds UDMA/WDMA writes (go4 reg 0x24 = 0x00911030, go5 reg 0x24 = 0x00002020), settled modes, SETXFER counts and dma overrides are bit-identical to stock. The fixed binary shows callq ata_mode2idx feeding the table load (fixed_handler_disasm.txt) versus the stock and $0xf,%eax.",
  "fix_evidence": "fix_run.log (A/B against run.log): stock reg 0x20 = 0x00911030 (OOB udmatiming[2]) / 0x00002020 (OOB dmatiming[2]) versus fixed 0x00040010 / 0x00009172 (correct piotiming[4] / piotiming[0]); controls unchanged. fixed_handler_disasm.txt: callq ata_mode2idx before mov 0x80d0cd20(,%rax,4),%eax. fix_build.log: full untrimmed single-fix kernel build. Reading note: the rig prints the hypothetical raw-mask index as a static annotation in both runs; on the fixed kernel the handler demonstrably no longer uses it, and the MISMATCH cross-check line is the expected fixed-kernel outcome (register no longer equals the OOB slot)."
}