DF-4279 / verdict.json
{ "finding_id": "DF-4279", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "REPRODUCED: CCDIOCSET accepts a negative interleave (signed int, no kernel-side lower-bound validation at ccd.c:1326; ccdconfig(8) rejects it but the raw ioctl does not). The negative ileave reaches ccdbuffer() where cbc=dbtob(ileave-cboff) is negative (ccd.c:1068), producing a negative cb_buf.b_bcount (ccd.c:1071); ccdstart() then grows bcount by |rcount| each iteration (ccd.c:886-888) and walks b_data backwards out of the user buffer, issuing unbounded negative-length device transfers that fault in memmove -> vm_fault NULL-object assertion -> kernel panic. Two identical panics on the stock kernel; with fix.diff hunk 1 (reject ileave<0) the ioctl returns EINVAL, no panic, control path unregressed.", "exploit_chain": "root/operator user -> open /dev/ccdN (0640 root:operator) -> ioctl(CCDIOCSET, ileave=-1, CCD-typed component) -> pread 512 bytes -> negative b_bcount + backward b_data walk in ccdstart -> OOB transfer -> kernel panic (un-killable spin precedes the crash). No uid0 chain; class is privileged local DoS/panic.", "evidence": "panic.txt (full serial-console panic: assertion obj != NULL failed in vm_object_hold_shared, memmove fault); run.2.log (serial sequence: control config ok, ileave=-1 config accepted, immediate panic); README.md/run.sh (self-contained reproduce); fix_patched.log (patched module: ileave=-1 -> CCDIOCSET: Invalid argument, ileave=32 control still works, guest survives); build.log (kernel+module rebuild, RC=0)", "kernel_refs": [ "sys/dev/disk/ccd/ccd.c:1326", "sys/dev/disk/ccd/ccd.c:484", "sys/dev/disk/ccd/ccd.c:514", "sys/dev/disk/ccd/ccd.c:975", "sys/dev/disk/ccd/ccd.c:1068", "sys/dev/disk/ccd/ccd.c:1071", "sys/dev/disk/ccd/ccd.c:886", "sys/dev/disk/ccd/ccd.c:888" ], "poc_changes": "Seed sketch was author-authored here: raw CCDIOCSET trigger with _KERNEL_STRUCTURES include fix (ccdvar.h is not userland-includable otherwise), vn(4)+disklabel32 fstype=ccd component setup (no mdconfig on guest), positive control included.", "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": 95, "guest_dirty": 0, "build_cmd": "cc -O -o /tmp/df4279 /root/df4279/trigger.c", "run_cmd": "sh /root/df4279/run.sh (kldload ccd; vnconfig + disklabel32 fstype=ccd; control ileave=32; trigger ileave=-1)", "code_hash": "a75a4c169b746e8f4df0800fc2b475139b138aa8b9ed980e2e128cc16cce0d94", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff hunk 1 (reject ccio_ileave<0 in CCDIOCSET) validated on the guest: ccd.c rebuilt as a module from patched /usr/src, installed, ileave=-1 now returns EINVAL and the previously-guaranteed panic is gone across repeated runs while the ileave=32 control path is unregressed. Hunk 2 (DF-4280 guard) validated in the same build.", "fix_evidence": "findings/poc/DF-4279/fix_patched.log (patched: CCDIOCSET: Invalid argument + guest survives + uptime shown; baseline: panic.txt/run.2.log); build.log (nativekernel RC=0 + module link)", "notes": "Also observed pre-panic: the reading thread spins un-killably in ccdstart (timeout could not reclaim it) and ccd.c:507 prints stale sc_unit (always ccd0) because sc_unit is assigned at ccd.c:593 after the print. /dev/ccdN perms are 0640 root:operator (subr_disk.c:699-701), so the operator group reaches this without root. Guest restored with vm.sh reset with-src after validation.", "recommended_fix": "Validate ccio_ileave >= 0 in CCDIOCSET (return EINVAL) before configuring; optionally make the ileave guards in ccdinit use unsigned math." } |