DF-5678 / verdict.json
{ "finding_id": "DF-5678", "status": "reproduced", "reproduced": true, "impact": "dos", "confidence": "certain", "verdict": "prop_array_set()/prop_array_remove() enforce the idx<pa_count bound only via _PROP_ASSERT==KKASSERT, which is INVARIANTS-gated (sys/sys/systm.h:94-122): the running stock X86_64_GENERIC kernel panics at prop_array.c:648/:701 (both reproduced live via KLD trigger modules, full backtraces in panic.set.txt/panic.remove.txt), while the same source with the assert compiled out (-DNDEBUG analog of a supported non-INVARIANTS kernel build) performs the OOB read at :650/:709, the OOB write at :654, and prop_object_release() of the OOB-read garbage at :657/:719 \u2014 the wild release fired on its own in the NDEBUG rig (SIGSEGV on a heap-garbage pointer), and the guard rig localized the faults to exactly pa_array[512]/pa_array[600] (delta 704 == (600-512)*8). No in-tree kernel caller currently forwards an attacker-derived index (dms fixed 0..2 set-indices are provably safe via the idx==count append at :643; other callers use count-bounded get/add/iterator), so kernel reachability is latent \u2014 one plist-index-forwarding caller away from stock-kernel DoS or, on non-INVARIANTS kernels, attacker-shaped heap corruption. Fixed and validated on a full nativekernel rebuild: panics gone, set() returns false, remove() no-ops, all legal paths unchanged.", "exploit_chain": "", "evidence": "panic.set.txt + panic.remove.txt (stock-kernel KKASSERT panics at prop_array.c:648/:701 with backtraces through df5678_modevent); run.userland.log (assert rig aborts naming :648/:701; NDEBUG rig SIGSEGVs in prop_object_release on OOB-read garbage si_addr=0x89736c6162; guard rig si_addr delta exactly 704 bytes == (600-512)*8 proving slot-exact OOB); run.userland.fixed.log (all variants exit 0, set returns 0, remove no-ops); fixtest.console.txt (fixed kernel #1: both modules load, module completed without panic); fix.diff", "kernel_refs": [ "sys/libprop/prop_array.c:648", "sys/libprop/prop_array.c:650", "sys/libprop/prop_array.c:654", "sys/libprop/prop_array.c:657", "sys/libprop/prop_array.c:701", "sys/libprop/prop_array.c:709", "sys/libprop/prop_array.c:714", "sys/libprop/prop_array.c:719", "sys/libprop/prop_array.c:591", "sys/libprop/prop_object_impl.h:258", "sys/sys/systm.h:94", "sys/config/X86_64_GENERIC:56" ], "poc_changes": "Harnesses written fresh for this file: 3-variant same-source userland differential (assert/NDEBUG/guard \u2014 the guard variant needed a rig-only guarded allocator since the guest base gcc has no usable ASAN, following DF-5676s guard-page pattern), plus two KLD trigger modules. Repairs along the way: MOD_QUIET does not exist on DragonFly (compile error); bsd.kmod.mk drops CFLAGS+= so module modes must be baked into per-variant source copies (first rem-module silently ran SET code); unused-variable -Werror in the REM variant; /tmp is wiped on guest reboots (re-push needed each boot).", "attempts": 4, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026\troot@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 1860, "guest_dirty": 0, "build_cmd": "sh build.sh (guest root /tmp/df5678: 3 same-source libprop rigs + df5678.ko/df5678rem.ko)", "run_cmd": "sh run.sh; kldload /tmp/df5678/kmod_set/df5678.ko; kldload /tmp/df5678/kmod_rem/df5678rem.ko (panics land on vm.sh serial log)", "code_hash": "d0b41ac45a412d290b7ba1573ce8f9d72a417ab3adc2105ffdcdddd5ea70cafc", "notes": "Impact is dos for the proven kernel behavior (stock INVARIANTS panic via KLD); the compiled-out half (non-INVARIANTS kernels) is a heap-OOB-write + arbitrary-pointer-release primitive demonstrated on the same source but with NO unprivileged in-tree trigger today \u2014 reachability audit of all kernel callers (dm_ioctl.c:294/649/708/951/1080-1092, dm_target.c, dm_dev.c, vfs_quota.c, kern_udev.c) found none forwarding a user index. DF-2229 (missing NULL/type guards on prop_array_iterator/make_immutable/mutable/externalize) and DF-5675 (externalize recursion depth; array-side hunk at prop_array.c:174-186) were not re-reported. Pass-2 negatives: 64-bit-only platforms in tree (pc32 gone) kill the capacity*sizeof truncation; EXPAND_STEP wrap needs a >=34GB backing allocation; iterator version-wrap needs 2^32 mutations of one live array; poec_depth error-path imbalance is unreachable-impact (whole externalize fails and ctx is freed); iterator pi_obj refcount balanced (prop_object.c:1220-1225); equals driver lock/finish protocol balanced, its only hazard (shared-subobject concurrent compares) has no kernel caller; internalize attribute-path leaves *obj pre-initialized NULL by the dispatcher.", "recommended_fix": "Replace the two _PROP_ASSERT bounds in prop_array_set/prop_array_remove with real checks mirroring prop_array_get (see fix.diff).", "fix_status": "fixed", "fix_baseline_reproduced": true, "fix_patched_reproduced": false, "fix_verdict": "fix.diff applied inside guest /usr/src, full make -j8 nativekernel KERNCONF=X86_64_GENERIC + installkernel + reboot into #1 (Fri Oct 9 23:00:04): baseline panics at prop_array.c:648/:701 are gone \u2014 both trigger modules load and complete without panic (set(5) returns 0/false, remove(7) no-ops). Patched-source userland rig: all 12 variant/scenario cells exit 0, no asserts, no guard faults, legal in-bounds and idx==count append paths unchanged.", "fix_evidence": "fixtest.console.txt (both modules completed without panic on fixed kernel); run.userland.fixed.log (clean battery); panic.set.txt/panic.remove.txt (baseline); env.txt (fixed-kernel uname, kldstat)" } |