DF-5682 / verdict.json
{ "finding_id": "DF-5682", "status": "reproduced", "reproduced": true, "impact": "none", "confidence": "certain", "verdict": "prop_number_unsigned() (prop_number.c:379-384) is the only prop_number accessor missing the prop_object_is_number() guard its seven siblings have; on a type-confused argument it reads the pnv_is_unsigned bitfield at +40..43, i.e. 4 bytes entirely past the 40-byte struct _prop_string allocation (16-byte _prop_bool likewise). Deterministically proven on the same-source userland rig: with a guard-page heap, the guarded control accessor returns 0 without trapping while prop_number_unsigned((prop_number_t)string) SIGSEGVs on the guard page (exit 139), and the exact fix.diff (guard added) removes the trap (returns false, exit 0). Kernel impact today is none reachable: zero in-kernel callers outside libprop (only create-side, type-safe kern_udev.c:195,215), so this is filed as hardening/defense-in-depth - one future caller away from a kernel heap OOB read on user-influencible plist data.", "exploit_chain": "", "evidence": "run.log: baseline [*] control: guarded prop_number_integer_value(str) -> 0 (no trap) then [*] probe: UNguarded prop_number_unsigned(str) ... followed by --- baseline exit=139 (SIGSEGV on guard page); fixed variant prints -> 0 (NO TRAP) exit=0. Layout math in README.md (+40..43 vs 40-byte object).", "kernel_refs": [ "sys/libprop/prop_number.c:49-62", "sys/libprop/prop_number.c:379-384", "sys/libprop/prop_number.c:432", "sys/libprop/prop_number.c:450", "sys/libprop/prop_string.c:40-50", "sys/libprop/prop_object_impl.h:364-365" ], "poc_changes": "Authored fresh (no seed): typeconf.c harness + pguard_object_impl.h (userland _PROP_POOL_GET/_PROP_POOL_PUT replaced by a page-guard allocator for deterministic OOB detection - guest base cc 8.3 has no usable ASAN runtime) + pfix_number.c (fix under test applied to a copy of the identical source).", "attempts": 1, "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": 1, "guest_dirty": 0, "build_cmd": "sh /tmp/build5682.sh (cc -O2 -fPIC -I. -I/usr/src/sys each .c of two guard-page libprop copies from /usr/src/sys/libprop; link typeconf, typeconf_fix)", "run_cmd": "sh /tmp/run5682.sh", "code_hash": "bd31568c9d6e8485536af8d872705bd80fed8fb2b7ae54714c1235d617d495aa", "notes": "Reproduced refers to the memory-safety primitive (type-confused OOB read) on the same source in the established userland rig; impact=none because no in-kernel caller exists today (verified via rg over sys/). Same defect exists verbatim in userland libprop.so built from this source. Guard-page instrumentation touches only the scratch build copies; sys/ untouched.", "recommended_fix": "Add the prop_object_is_number() guard to prop_number_unsigned(), matching every sibling accessor (see fix.diff).", "fix_status": "fixed", "fix_kernel_uname": null, "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Fix validated on the same source in-rig: baseline (stock prop_number.c) traps SIGSEGV exit 139 on the guard page at the +40 read; patched copy (exact fix.diff applied) returns false with no trap, exit 0, control accessor unchanged. Pure C-level change on a userland-buildable path, representative of the kernel build.", "fix_evidence": "run.log sections 1 (baseline exit=139 after probe: UNguarded) and 2 (fixed: -> 0 (NO TRAP), exit=0); fix.diff = the guard added at prop_number.c:379-384; pfix_number.c is the patched copy used in the rig." } |