# 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:

```c
	_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
