prop_object_release_emergency dispatches pot_emergency_free on a NULL parent when the first walk iteration breaks (CWE-476; kernel-unreachable today, userland malloc-failure edge)
Summary
If the first loop iteration breaks via ocnt!=1 (a concurrent retain - exactly the contention this path exists for) or FREE_DONE, parent is still NULL at the unconditional emergency_free dispatch; the _PROP_ASSERT is a no-op without INVARIANTS. Reproduced deterministically at unit level (assert abort / NDEBUG NULL-deref); kernel-unreachable today (emergency requires M_WAITOK kmalloc to fail, which panics instead) - live for userland libprop under allocation pressure; a dead-code landmine if the allocation ever flips to NOWAIT. parent==NULL early-return fix validated.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5677 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.0 KB | β raw | |
| VERDICT.md | β | 2.0 KB | β raw | |
| df5677_emergency.c | β | 1.6 KB | view raw | |
| build.sh | β | 92 B | view raw | |
| run.sh | β | 90 B | view raw | |
| run.log | β | 1.1 KB | view raw | |
| fix.diff | β | 484 B | view raw | |
| manifest.json | β | 939 B | view raw | |
| verdict.json | β | 3.4 KB | view raw |
DF-5677 β prop_object_release_emergency() NULL-parent dispatch (userland-edge defect, kernel-unreachable today)
Severity: Low Β· Class: NULL-pointer dereference on an error path (CWE-476) File: sys/libprop/prop_object.c:1005-1052
The defect
If the FIRST iteration of the emergency walk breaks via ocnt != 1 (another
holder raced a retain β exactly the contention the emergency path exists for)
or the object frees itself outright (_PROP_OBJECT_FREE_DONE), parent is
still NULL when the function runs off the end of the loop:
_PROP_ASSERT(parent); /* no-op w/o INVARIANTS */
po = parent;
(*po->po_type->pot_emergency_free)(parent); /* NULL->pot_emergency_free */
Reachability (why Low)
prop_object_release only calls the emergency path when pot_free returns
_PROP_OBJECT_FREE_FAILED, which for array/dict happens only when
_prop_stack_push fails β i.e. a malloc failure. In-kernel, _PROP_MALLOC is
kmalloc(M_WAITOK) which cannot return NULL (DragonFly panics on malloc-limit
excess, kern_kmalloc.c:706) β so the whole emergency path is dead code in the
kernel today. In userland it is reachable under allocation failure plus a
concurrent retain racing the restored refcount; the window is narrow but the
consequence is an immediate abort/NULL-deref.
What was proven on the guest (deterministic, unit-level)
Calling the static function (via TU-include) on a refcount-2 array β the deterministic equivalent of the raced state:
- assert build:
Assertion failed: (parent), function prop_object_release_emergency, file proporig/prop_object.c, line 1048β abort (134) - NDEBUG build: SIGSEGV (139) β the raw
(*po->po_type->β¦)(NULL)load
Fix validation
fix.diff: if (parent == NULL) return; before the dispatch β if nothing was
walked there is no parent to emergency-free. Fixed rig: both builds print
"survived (bug not present)", exit 0.
Files
df5677_emergency.c, build.sh, run.sh, fix.diff, run.log
DF-5677 β VERDICT
reproduced (deterministic unit-level) / impact=dos (userland-only; kernel path unreachable today) / confidence=certain
Reproduced
TU-include harness reaches the static prop_object_release_emergency with a
refcount-2 array β the deterministic equivalent of the first-iteration
ocnt != 1 break (a concurrent retain racing the restored refcount):
Assertion failed: (parent), function prop_object_release_emergency, file proporig/prop_object.c, line 1048. -> abort (134) NDEBUG build: -> SIGSEGV (139)
Why kernel impact is nil today (and honestly scoped)
The emergency path is entered from prop_object_release only when pot_free
returns _PROP_OBJECT_FREE_FAILED; for array/dict that requires
_prop_stack_push to fail, i.e. a malloc failure. Kernel _PROP_MALLOC =
kmalloc(M_WAITOK) β cannot return NULL (DragonFly panics on malloc-limit
excess instead, sys/kern/kern_kmalloc.c:706). So in the shipped kernel the
defect is dead code; it is live for userland libprop under memory pressure and
would become kernel-live if the allocation ever flips to M_NOWAIT.
Related observations recorded in the same function (not separately filed):
pot_emergency_free(parent) is invoked without the type lock held, and the
refcount restore (atomic_inc_32, prop_object.c:1046/1102) runs outside the
lock β both matter only on the same malloc-failure/race edge, and DF-5674
already tracks the pot_lock contract hole for this family.
Fix validation
if (parent == NULL) return; β nothing was walked, so there is nothing to
emergency-free. Fixed rig: assert and NDEBUG builds both survive (exit 0).
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
fixedfix.diff (parent==NULL early return) applied to the same-source rig on the guest: baseline aborts/SIGSEGVs, fixed harness survives in both assert and NDEBUG builds (exit 0). Kernel rebuild not performed β the emergency path cannot execute in-kernel (M_WAITOK push cannot fail), so there is no kernel behavior to validate.
run.log (baseline exit=134 assert / 139 NDEBUG; fixed 'survived (bug not present)' exit=0 both); fix.diff
Confirmed kernel references
Detail
Exploit chain
userland libprop under malloc pressure: prop_object_release on a deep container hits _PROP_OBJECT_FREE_FAILED (stack-push malloc fails) -> emergency walk -> concurrent retain on the target restores refcount>1 -> first iteration breaks with parent==NULL -> NULL deref/abort. Kernel chain blocked by M_WAITOK semantics (no FAILED possible).
Evidence (decisive lines)
run.log: 'Assertion failed: (parent), function prop_object_release_emergency, file proporig/prop_object.c, line 1048' exit=134; NDEBUG control 'Segmentation fault' exit=139; fixed rig 'DF5677: survived (bug not present)' exit=0 both builds.
PoC changes
Harness written fresh: TU-include of prop_object.c to reach the static function; refcount-2 array as the deterministic stand-in for the raced state; NDEBUG control build to show the raw NULL deref behind the assert.
Verified recommended fix
Return early when parent==NULL (nothing was walked, nothing to emergency-free) instead of asserting and dereferencing.
Verdict
prop_object_release_emergency dispatches (*po->po_type->pot_emergency_free)(parent) with parent==NULL whenever the first loop iteration breaks (ocnt!=1 concurrent-retain race or DONE) β reproduced deterministically at unit level on the same-source rig: assert(parent) abort at prop_object.c:1048 (exit 134) and raw NULL-deref SIGSEGV under NDEBUG (exit 139). Kernel impact none today: the emergency path requires _prop_stack_push to fail, and kernel _PROP_MALLOC is kmalloc(M_WAITOK) which cannot return NULL (kern_kmalloc.c:706 panics instead) β dead code in-kernel, live for userland libprop under allocation failure plus the retain race. Fixed and validated on the rig: parent==NULL early-return makes both builds survive.
No comments yet.