⬢ DragonFlyBSD Kernel Audit
DF-5580 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5580",
  "status": "reproduced",
  "reproduced": true,
  "impact": "none",
  "confidence": "certain",
  "verdict": "The ten shift sites of the branchless masked-select initialization in __udivmodti4 (__udivti3.c:231-244 and :274-278) execute shift counts outside the domain of their 64-bit operands (counts 64..126 from n_utword_bits - sr, and counts like 0xFFFFFFFB from unsigned-wrapped negative sr - n_udword_bits). The UB is live and reproducible: -fsanitize=shift aborts at __udivti3.c:274:25 (shift=64, rc=134) and a recover-mode census records 1,017,186 events covering all ten sites. The results are nonetheless correct on the shipping gcc 8.3 (same compiler that built the running kernel): 2x2,151,999,419 userspace differential vectors vs native unsigned __int128 division with 0 mismatches, and ~268.7M vectors through the running kernels own exported __udivti3 symbol via a KLD harness, also 0 mismatches -- because each poisoned shift is ANDed with a mask that is provably zero whenever the count is out of domain. Impact today is therefore none; the risk is that any future compiler upgrade is entitled to fold/hoist/poison these evaluations and silently corrupt every in-kernel 128-bit division (muldivu64, sys/sys/systm.h:505 -- lapic/i8254 timer reload, kern_clock timekeeping, hyperv vmbus). fix.diff replaces the masked selects with the explicit sr==64/sr<64/sr>64 branches the files own comments already describe; validated: identical 2.15B-vector battery 0 mismatches, UBSan clean (rc=0), and the patched source compiled with kernel CFLAGS agrees with the kernel baseline symbol in kernel context on all vectors.",
  "exploit_chain": "",
  "evidence": "run.log / run.2.log (baseline differential, 2,151,999,419 vectors each, mismatches=0, two seed sets); ub_abort.log + status_ub_abort.txt (UBSan abort rc=134 at __udivti3.c:274:25 shift=64 -- the UB is live); ub_census.txt (1,017,186 shift-UB events across :231 :233 :235 :236 :238 :240 :242 :243 :274 :277, incl. shift=4294967291 negative-wrapped counts); kmod_kldtest_run.log (kernels own __udivti3 @0xffffffff80bdabb0 and fix.diff-patched source compiled with kernel CFLAGS: both 0 mismatches on ~268.7M vectors); run_fixed.log + ub_abort_fixed.log + status_ub_abort_fixed.txt (fix: 0 mismatches on identical battery, UBSan clean rc=0); env.txt (gcc 8.3 = kernel .comment compiler); fix.diff",
  "kernel_refs": [
    "sys/platform/pc64/x86_64/__udivti3.c:231-244",
    "sys/platform/pc64/x86_64/__udivti3.c:274-278",
    "sys/platform/pc64/x86_64/__udivti3.c:203-204",
    "sys/platform/pc64/x86_64/__udivti3.c:250-257",
    "sys/platform/pc64/x86_64/__udivti3.c:301-302",
    "sys/sys/systm.h:505-513",
    "sys/platform/pc64/conf/files:151"
  ],
  "poc_changes": "The finding-seed sketch had to be fully authored: differential harness #includes the kernel .c verbatim (access to static __udivmodti4 for remainder checks); UBSan required hand-written __ubsan_handle_shift_out_of_bounds[_abort] stubs because DragonFly gcc 8.3 ships no libubsan, plus compile-then-link split to avoid the drivers -lubsan; KLD harness (dev_ops/dev_ioctl_args DragonFly idiom) drives the kernels own exported symbol via /dev/df5580 ioctl. Two of my own harness bugs were found and fixed during verification and are documented in VERDICT.md (edge-set array overflow; sr-boundary generator truncating its dividend to 64 bits).",
  "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": 2900,
  "guest_dirty": 0,
  "build_cmd": "sh build.sh  (userspace: cc -O2 -Wall -o df5580_diff df5580_diff.c; KLD: cd kmod/df5580 && make SYSDIR=/usr/src/sys)",
  "run_cmd": "sh run.sh   (full matrix; KLD leg: kldload df5580.ko df5580fix.ko && ./df5580_kldtest 268435456)",
  "code_hash": "8665ad3ff0c1e0cfd100010b2903b095c855c2d15cf351bc0d705b4ec51064ad",
  "notes": "Severity Low / hardening: no wrong division results on the shipping toolchain (proven), but formally UB that a compiler upgrade can legally turn into silent miscompilation of all in-kernel 128-bit division (muldivu64 callers: lapic.c:556,675,704,726; clock.c:351,419,472,824; kern_clock.c:916,1386+; kern_cputimer.c:205; vmbus.c:365). Upstream compiler-rt deleted this branchless-mask implementation years ago. Negative results (checked, killed): no wrong-result bug (~6.7B vectors total, q and r, userspace + kernel contexts); div==0 -> #DE panic is the documented libgcc caller contract (all in-tree callers pass nonzero; DF-5554 class is 64-bit hardware div and does not link __udivti3); no memory-safety surface (no pointers/arrays/lengths; *rem NULL-checked); only __udivti3 exists in-tree (no __umodti3, no 128-bit modulo links); union punning covered by kernel -fno-strict-aliasing (see mod_build.log flags).",
  "recommended_fix": "Replace the masked-select shift blocks at :231-244 and :274-278 with the explicit sr==64 / sr<64 / sr>64 branch initialization (verbatim in fix.diff; matches pre-rewrite upstream compiler-rt and the code already described in the files own comments).",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (baseline kernel; patched source executed in kernel context via KLD built with kernel CFLAGS)",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff removes every out-of-domain shift. Validation: (1) identical 2,151,999,419-vector battery on the patched source -- 0 mismatches vs native division (run_fixed.log); (2) -fsanitize=shift -fno-sanitize-recover=shift on the patched source completes the edge battery clean, rc=0 (ub_abort_fixed.log), vs rc=134 abort at baseline; (3) patched source compiled with kernel CFLAGS (-Werror clean; modfix_build.log) as df5580fix.ko and executed in kernel context agrees with the running kernels baseline __udivti3 symbol and with native division on all ~268.7M KLD vectors (kmod_kldtest_run.log). A full single-fix kernel rebuild was not performed (non-memory-corruption finding; contract mandates kernel rebuilds only for reproduced memory corruption); the KLD route runs the patched code in kernel context with kernel flags, equivalent evidence for a pure codegen change.",
  "fix_evidence": "run_fixed.log (PASS, 0 mismatches, 313s); ub_abort_fixed.log + status_ub_abort_fixed.txt (rc=0, no UBSAN-FIRED); status_ub_abort.txt (baseline rc=134); kmod_kldtest_run.log (PASS: kernel baseline AND patched source match native division); modfix_build.log (kernel CFLAGS -Werror clean build of patched source); fix.diff"
}