⬢ DragonFlyBSD Kernel Audit
DF-5719 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5719",
  "status": "reproduced",
  "reproduced": true,
  "impact": "leak",
  "confidence": "certain",
  "verdict": "Pass-2 audit of sys/libkern/{strlen,strnlen,bcd}.c; sole finding: the bcd.c lookup tables are sized exactly for their valid domains (bcd2bin_data[154] == BCD 0x00..0x99, bin2bcd_data[100] == 0..99, hex2ascii_data[37]) but the macros in sys/sys/libkern.h:59-61 index them with no bounds, and consumers pass unvalidated u_char ioctl fields and raw device bytes (scsi_cd.c:2044-2047 CDIOCPLAYTRACKS, scsi_cd.c:2160-2161 CDIOCREADSUBCHANNEL with _IOWR copyback, drive-returned TOC bytes at 2208/2262/2320/2338/2386/2414/2942/2964/2989, and clock.c:558 bcd2bin(rtcin())). Primitive REPRODUCED on the DragonFly guest with the verbatim kernel algorithm: guard-page placement of the verbatim tables + verbatim macros SIGSEGVs for every out-of-domain index (bin2bcd(100/200/255), bcd2bin(0x9a/0xff)) while the last in-bounds probes are clean (run.log rig2). In-kernel path live: the guest ran a virtio-scsi CD with INQUIRY CHINON CD-ROM CDS-535 (faithful simulation of the hardware that sets CD_Q_BCD_TRACKS at probe, scsi_cd.c:222-225/701-707; devlist.txt) and the CDIOCREADSUBCHANNEL sweep reached cdreadsubchannel (EIO sourced from the drive call at scsi_cd.c:2163, which is strictly after the bin2bcd conversion at 2160-2161); the one-byte copyback cannot complete on this emulator because QEMU 7.2 implements no READ SUB-CHANNEL (0x42) opcode at all - established with passrep.c exact-CBB replication (CAM_DATA_RUN_ERR, zero sense), camcontrol probes, and the QEMU v7.2.0 sources. Reachability ceiling: Low - CD_Q_BCD_TRACKS is set only for the 1980s CHINON CDS-535 or by root via sysctl, and DragonFlys cdopen() gates ALL cd(4) opens on caps_priv_check_self(SYSCAP_RESTRICTEDROOT) (scsi_cd.c:1006-1007; verified: unprivileged open() of a chmod-0666 /dev/cd0 returns EPERM, trigger_as_user.log), so no unprivileged path exists on DragonFly (FreeBSD-derived stacks without that gate expose operator-group users). No crash possible in the linked kernel (tables sit mid-.rodata), no write primitive. Fix (fix.diff, git apply -p1) wraps the macro indices into the objects; differential proves identity on the whole valid domain and the fixed macros stay in-bounds under guard placement (rig3/rig4). My initial fix candidate & 0x99 was WRONG (bit-mask, not nibble clamp) and the differential caught it - kept as a documented lesson.",
  "exploit_chain": "",
  "evidence": "findings/poc/DF-5719/run.log lines bin2bcd(200)  (ioctl byte)     : SIGSEGV, bcd2bin(0xff) (garbage CMOS)   : SIGSEGV, bcd2bin vs arithmetic + fix-mask identity over valid BCD 0x00..0x99: MATCH (0 errors), bin2bcd_fixed(255) : in-bounds; trigger_as_user.log open(/dev/cd0): Operation not permitted; devlist.txt <CHINON CD-ROM CDS-535 2.5+>; VERDICT.md for the full narrative incl. the QEMU-7.2-lacks-0x42 investigation (passrep.c/subq_test.c)",
  "kernel_refs": [
    "sys/libkern/bcd.c:9",
    "sys/libkern/bcd.c:22",
    "sys/libkern/bcd.c:36",
    "sys/sys/libkern.h:59",
    "sys/sys/libkern.h:60",
    "sys/sys/libkern.h:61",
    "sys/bus/cam/scsi/scsi_cd.c:2044",
    "sys/bus/cam/scsi/scsi_cd.c:2160",
    "sys/bus/cam/scsi/scsi_cd.c:2208",
    "sys/bus/cam/scsi/scsi_cd.c:2942",
    "sys/bus/cam/scsi/scsi_cd.c:2989",
    "sys/bus/cam/scsi/scsi_cd.c:222",
    "sys/bus/cam/scsi/scsi_cd.c:701",
    "sys/bus/cam/scsi/scsi_cd.c:1006",
    "sys/platform/pc64/isa/clock.c:558",
    "sys/sys/cdio.h:112"
  ],
  "poc_changes": "No seed existed; authored from scratch. Iterations: (1) missing <signal.h> in bcd_rig.c/str_harness.c; (2) first fix mask & 0x99 wrong - bit-mask is not a nibble clamp, caught by the MODE=3 differential, replaced with % 154/% 100 wraps matching fix.diff; (3) passrep.c needed DragonFly include paths (<bus/cam/...>), cam_errbuf, no CCB_CLEAR_ALL_EXCEPT_HDR, and must NOT memset the ccb header (wipes cam_getccbs path_id/target/lun); (4) bcd_trigger sizeof(struct cd_sub_channel_info)=24 on the guest bounds data_len to 4..24.",
  "attempts": 7,
  "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": 2100,
  "guest_dirty": 0,
  "build_cmd": "sh build.sh  (cc -O2 -DMODE=1..4 bcd_rig.c; cc -O2[-DSTRNLEN] str_harness.c; cc -O2 bcd_trigger.c subq_test.c; cc -O2 passrep.c -lcam)",
  "run_cmd": "sh run.sh  (rig1; rig3; rig2; rig4; shl; shn; ./bcd_trigger /dev/cd0)",
  "code_hash": "f62bb31f99635c07902d74d9762ec041745d994b84b579d590949655b7f8636a",
  "notes": "Batch pass-2 of sys/libkern/{strlen,strnlen,bcd}.c. Negative results (clean): strlen.c - live (conf/files:2226, no inline/asm shadow, plain prototype libkern.h:134), libc-identical (34-case randomized differential), reads exactly through the NUL (guard probe); strnlen.c - bounded exactly (reads s[0..maxlen-1] only; 268-case differential IDENTICAL; guard probes: 300-byte buffer at guard + strnlen(buf,300) no fault, +1 faults, 0 no deref). hex2ascii consumers all in-domain (subr_prf.c:518 num%base<=16, kern_conf.c:172 num%32). Guest was booted with the CHINON-quirk drive via dfbsd-qemu/up-chinon.sh (host helper, mirrors vm.sh up); /dev/cd0 chmod 666 during testing; guest restored with vm.sh reset with-src afterwards (guest_dirty 0). No panic, no kernel rebuild (no in-kernel observable on this emulator; see fix_verdict).",
  "recommended_fix": "Wrap the macro indices into the objects in sys/sys/libkern.h: bcd2bin(bcd) -> bcd2bin_data[(u_int)(bcd) % 154], bin2bcd(bin) -> bin2bcd_data[(u_char)(bin) % 100], hex2ascii(hex) -> hex2ascii_data[(u_char)(hex) % 37]; identity on the valid domain, single evaluation (fix.diff).",
  "fix_status": "not_testable",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": null,
  "fix_verdict": "fix.diff authored (git apply --check -p1 passes) and validated at the algorithm level: rig3 proves the wraps are the identity over the entire valid domain (bin 0..99 and BCD 0x00..0x99, 0 errors each) and rig4 proves the fixed macros never leave the objects under the same guard-page placement that SIGSEGVs the verbatim macros. A patched-kernel build/reboot A/B is not testable on this guest: baseline and patched kernels behave identically (both EIO from QEMU 7.2s missing READ SUB-CHANNEL opcode), leaving no in-kernel observable; recorded as not_testable at the kernel level per the DF-5718 precedent for emulator/hardware-gated findings.",
  "fix_evidence": "findings/poc/DF-5719/run.log: bcd2bin vs arithmetic + fix-mask identity over valid BCD 0x00..0x99: MATCH (0 errors), bin2bcd_fixed(255) : in-bounds (value=85), bcd2bin_fixed(0xff): in-bounds; findings/poc/DF-5719/fix.diff"
}