⬢ DragonFlyBSD Kernel Audit
DF-2230 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-2230",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "none",
  "confidence": "certain",
  "verdict": "Reproduced on the stock DragonFly 6.5-DEVELOPMENT kernel through the real in-kernel parser path (KLD calling prop_array_internalize, sha256-gated to the audited source). For decimal plist integers in (INT64_MAX, UINT64_MAX] the kernel stores a SIGNED number with saturated value INT64_MAX and pnv_is_unsigned=0 - wrong on both axes - because the ERANGE rejection in _prop_number_internalize_signed (prop_number.c:541-545) is compiled out under _KERNEL while kernel strtoq (sys/libkern/strtoq.c) saturates silently, which makes the short-circuit && at prop_number.c:584 keep the unsigned fallback permanently dead. The userland build of the identical source file either stores the correct unsigned value (ERANGE -> fallback) or rejects the document; errno=34/signed_rv=0 shown per case by the unit rig. Hex 0x values >= INT64_MAX are correct in both builds, proving the defect is decimal-only. Not a memory-safety bug: the saturated value stays inside the fixed 8-byte union and 1-bit flag; no OOB, UAF, leak, or panic (clean RUN-OK). Impact ceiling is wrong-value logic and kernel/userland plist desync (Medium as filed); no uid=0 chain exists via this primitive. fix.diff (errno-free saturation detection by strtouq re-parse, letting the existing unsigned fallback run) rebuilt and re-tested on the guest: fixed kernel matches userland for every input up to UINT64_MAX and is byte-identical to baseline for boundary, negative, small, and hex cases.",
  "exploit_chain": "",
  "evidence": "run.log (baseline): DF2230|K| rows for 9223372036854775808, 10000000000000000000, 18446744073709551615, 0Xffffffffffffffff all show is_unsigned=0 signed=9223372036854775807 while the DF2230|U| rows for the same inputs show is_unsigned=1 with the exact unsigned values; DF2230|UU| rows show signed_rv=0 errno=34 exactly on those inputs (ERANGE check live in userland, dead in kernel). fix_run.log: same DF2230|K| rows now match userland; -9223372036854775809 row changed from stored INT64_MIN to rejected. build.log line 1 is the sha256 gate (89da58dad14f7d7b60964666bdda9d60c16755505d9cc499fbc4e3f4573c9117). fix_build.log is the full untrimmed fixed-kernel build; env.txt has the guest uname.",
  "kernel_refs": [
    "sys/libprop/prop_number.c:38",
    "sys/libprop/prop_number.c:537-545",
    "sys/libprop/prop_number.c:540",
    "sys/libprop/prop_number.c:583-587",
    "sys/libkern/strtoq.c:96-98",
    "sys/conf/files:2243-2255",
    "sys/libprop/prop_object_impl.h:82-97"
  ],
  "poc_changes": "No evidence pack existed (created from scratch). Dropped the suggested verbatim-copy KLD approach in favor of a strictly stronger rig: libprop is compiled into the stock kernel and prop_array_internalize plus the prop_number accessors are exported symbols, so the KLD drives the real in-kernel object code directly; source identity is enforced by a sha256 gate in build.sh instead of recompiling copied function bodies. Fixed kernel printf -> kprintf for the DragonFly kernel namespace.",
  "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": 2100,
  "guest_dirty": 0,
  "build_cmd": "sh /root/df2230/build.sh (guest, as root; sha256 gate + KLD + 2 userland binaries)",
  "run_cmd": "sh /root/df2230/run.sh (guest, as root; KLD load + userland matrices; same script before and after fix)",
  "code_hash": "61cb8c3244b17d0f074d2f9e1c80f14929aaeaf9bbf78040a96b9b915c543e1b",
  "notes": "Additional instance found during verification: capital-0X hex (0Xffffffffffffffff) is routed into the signed path by the poic_cp[1]==x check at prop_number.c:580 and hits the same dead-fallback saturation; the fix repairs it too. Residual after fix (pre-existing, out of the filed domain, affects hex equally today): decimal or hex > UINT64_MAX still saturates in kernel (now to unsigned UINT64_MAX) while userland rejects; closing it needs errno-free saturation detection in _prop_number_internalize_unsigned as well. Guest was reset with vm.sh reset with-src after validation; post-reset checks confirmed stock kernel and pristine source.",
  "recommended_fix": "In _prop_number_internalize_signed add an #else (kernel) branch that detects strtoq saturation without errno by re-parsing with strtouq (INT64_MAX + strtouq(text)>INT64_MAX, or INT64_MIN + leading minus + strtouq(text+1)>2^63, return false so the existing decimal unsigned fallback runs) - see fix.diff.",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Sat Oct 17 09:40:41 UTC 2026     root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC  x86_64",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff applied inside the guest /usr/src copy, kernel rebuilt with make nativekernel KERNCONF=X86_64_GENERIC (full log fix_build.log) and rebooted; the exact baseline matrix re-run (fix_run.log) shows every decimal in (INT64_MAX, UINT64_MAX] now stored as the correct unsigned value, -9223372036854775809 now rejected, capital-0X hex now unsigned, and boundary, negative, small, and lowercase-hex cases byte-identical to baseline; the kernel column equals the userland column for all inputs up to UINT64_MAX. No regression, bad behavior gone.",
  "fix_evidence": "fix_run.log rows DF2230|K| for 9223372036854775808 (is_unsigned=1 unsigned=9223372036854775808), 10000000000000000000 (is_unsigned=1 unsigned=10000000000000000000), 18446744073709551615 and 0Xffffffffffffffff (is_unsigned=1 unsigned=18446744073709551615), and -9223372036854775809 (array=0 rejected); all other DF2230|K| rows identical to run.log baseline; fix_build.log ends with NATIVEKERNEL-INSTALL-OK."
}