⬢ DragonFlyBSD Kernel Audit
DF-2505 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-2505",
  "status": "reproduced",
  "verdict": "Drive-controlled OOB kernel heap read CONFIRMED at both DVD_STRUCT_BCA and DVD_STRUCT_MANUFACT sites through 100% production code (unmodified atapi-cd.c compiled into the dfrig4_acd KLD; only ata_atapicmd() interposed at the hardware boundary to serve a hostile 2-byte response length -- the DF-2496-proven verbatim-module pattern). BCA: claimed 4096 vs 192-byte allocation -> 3907 OOB-sourced marker bytes land in s->data[188..], and the same bcopy WRITES ~2048 bytes past the 2060-byte M_IOCTLOPS kern_ioctl buffer (guard smashed). MANUFACT: claimed 3000 vs 2052 -> 700 marker bytes past the allocation. Max arm 0xFFFF: s->length=65535 accepted, 65531 bytes read from a 192-byte chunk (0xAA x15936), non-fatal on the contained run. Compliant-length control (188) shows zero false positives. kern_ioctl then copyouts the full 2060-byte buffer to the caller on rc=0 -> up to 1860 bytes/call of kernel heap disclosed to the ioctl caller. CORRECTIONS to the finding: (1) the user-race/TOCTOU arm is refuted on DragonFly -- ap->a_data is a kernel M_IOCTLOPS buffer (sys/kern/sys_generic.c:675), not user memory; (2) reachability requires a malicious/compromised ATAPI device AND the RESTRICTEDROOT open gate (sys/kern/subr_disk.c:1071) plus the MST_READ_DVDROM capability gate, so this is a hostile-drive hardening bug, not an unprivileged-user primitive. Single-fix kernel validated: clamp to length-4/sizeof(s->data) -> all hostile tests clamp cleanly, zero markers, zero smashes.",
  "reproduced": true,
  "impact": "leak",
  "confidence": "certain",
  "exploit_chain": "malicious ATAPI drive (compromised firmware / USB-IDE bridge): respond to READ DVD STRUCTURE (format 3 or 4) with data_length=0xFFFF (DMA stays within the 192/2052-byte allocation length) -> acd_read_structure atapi-cd.c:1583/1588 stores ntohs() verbatim -> bcopy(d+4, s->data, 65535) reads 65531 bytes past the 192-byte heap chunk into the kernel ioctl buffer and writes 65523 bytes past it (kernel heap corruption; content = adjacent heap) -> driver returns 0 -> kern_ioctl copyouts 2060 bytes -> caller receives up to 1860 bytes/call of kernel heap beyond the 188-byte legit payload. Repeatable at will. No control over leaked offset selection beyond the claimed length.",
  "evidence": "run.log (baseline kernel, tests 33/30/31/32 with full production-call readbacks: s->length values, 0xAA marker spans [188..4095]/[2300..2999]/[188..16123], destination guard smash 1/10, control clean); fix_validation.log + fix_run.log (patched kernel: clamped 188/2048, zero markers/smashes); dfrig4_acd.c header documents the seam; build.log full compile of the verbatim production file.",
  "kernel_refs": [
    "sys/dev/disk/nata/atapi-cd.c:1582-1585",
    "sys/dev/disk/nata/atapi-cd.c:1587-1590",
    "sys/dev/disk/nata/atapi-cd.c:1537",
    "sys/dev/disk/nata/atapi-cd.h:41-45",
    "sys/sys/dvdio.h:58-66",
    "sys/kern/sys_generic.c:668-685"
  ],
  "poc_changes": "No seeded sources existed; the rig was authored fresh following findings/poc/DF-2496/dfrig3_ata.c: (a) interposition moved to ata_atapicmd (the ATAPI response boundary this driver trusts) instead of ata_queue_request; (b) s allocated as a faithful kern_ioctl M_IOCTLOPS 2060-byte buffer (kernel malloc type bound via extern); (c) >2048 allocations use the kmem carve path on this guest, so freed-hole sandwiches never place the production MANUFACT chunk -- marker chunks are carved from inside the seam (while d exists, before the production bcopy) as pure instrumentation; the hostile attack input remains the 2-byte claimed length; (d) retry loop for <2048 magazine placement nondeterminism.",
  "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",
  "runtime_sec": 2700,
  "guest_dirty": 0,
  "build_cmd": "cd /root/dfrig4 && make   (KLD; compiles UNMODIFIED /usr/src/sys/dev/disk/nata/atapi-cd.c via #include; needs -I/usr/obj/usr/src/sys/X86_64_GENERIC for opt_ata.h)",
  "run_cmd": "kldload /root/dfrig4/dfrig4_acd.ko; sysctl -n kern.dfrig4_acd_test=33; sysctl -n kern.dfrig4_acd_status   (then 30, 31, 32)",
  "code_hash": "265bbe5a59c8cf6275f26dedee5675bf366cc43523b6eb4f139d1d200ade30aa",
  "notes": "Guest left clean: disk reverted to with-src snapshot and booted after validation. The destination-side write past the 2060-byte buffer (demonstrated at length 4096) means the bug is also kernel heap CORRUPTION (content: adjacent kernel heap bytes, drive-triggered) -- the leak classification reflects the user-visible ceiling. The real QEMU drive is well-behaved (and empty), so the real-device arm could not supply hostile lengths; the rig injects them at exactly the boundary the production code trusts. Real-device gate observations: acd0 announces DVDROM (cap gate would pass) and DVDIOCREADSTRUCTURE additionally sits behind the same RESTRICTEDROOT open gate as DF-2504.",
  "recommended_fix": "Clamp both BCA and MANUFACT copies: copylen = min(ntohs(d->length), length-4) and min(copylen, sizeof(s->data)) before storing s->length and bcopy (fix.diff does exactly this).",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fixed",
  "fix_evidence": "fix_validation.log / fix_run.log: patched kernel #1 (Thu Oct 15 23:37:29 UTC 2026), rig rebuilt against patched /usr/src: test30 s->length clamped 4096->188 with zero markers and zero destination smashes; test31 clamped 3000->2048 clean; test32 clamped 65535->188 clean, survives; test33 control unchanged. Baseline: 3/3 hostile tests proved OOB read (+2/3 dest overflow). fix.diff applied to guest /usr/src only; audit sys/ tree untouched.",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Thu Oct 15 23:37:29 UTC 2026     root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC  x86_64"
}