โฌข DragonFlyBSD Kernel Audit
DF-0055 / fix_run.log
โ† back to finding โ†“ download raw
================================================================
DF-0055 โ€” FIX VALIDATION: PoC on the single-fix kernel #1
================================================================

Patched kernel : DragonFly 6.5-DEVELOPMENT #1: Thu Jul  2 15:06:40 UTC 2026
                 sha256(/boot/kernel/kernel) = ac173e083fb91582c3a82c53de61a5a9a898582ed58c2c1701e34cc77403ed6f
Unpatched base : DragonFly 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026
                 sha256(/boot/kernel/kernel) = 5dc83dac19ad09effd6241c33e0c0669d41b6497ee92d87d3a2e45f287bc22ad
Fix            : findings/poc/DF-0055/fix.diff (move prop_object_release of
                 ev->ev.ev_dict from udev_event_externalize:571 into
                 udev_clean_events_locked before TAILQ_REMOVE + NULL the field)
Build          : make -j6 nativekernel KERNCONF=X86_64_GENERIC  -> rc=0
                 (full output: fix_build.log, 35573 lines, 0 errors)

The patched kernel carries the proplib _PROP_ASSERT(ocnt != 0) guard
(prop_object.c:1085) and the malloc chunk_mark_free double-free detector โ€”
i.e. the SAME diagnostics that caught the baseline panic on #0. So a
surviving UAF/double-free would fire identically. It does not.

----------------------------------------------------------------
RUN 1 (patched #1, 80 create/destroy cycles = 160 udev events)
----------------------------------------------------------------
[*] DF-0055: udev_event_externalize shared-dict UAF trigger
[*] forking two /dev/udev readers (two softcs/markers)...
[*] generating device attach/detach events (tap create/destroy)...
[*] each event is externalized by reader #1 (frees shared dict),
    then by reader #2 (UAF: prop_object_retain on freed dict).
[!] no panic after 80 create/destroy cycles (160 udev events).
[!] On a non-INVARIANTS kernel the freed dict may have been
    silently reused; check dmesg / boot.log for slab/objcache
    warnings or delayed corruption. Run with more iterations or
    an INVARIANTS kernel to force an immediate fault.
RUN1_EXIT=0   (ssh RC=0, guest UP)

----------------------------------------------------------------
RUN 2 (patched #1)
----------------------------------------------------------------
[*] DF-0055: udev_event_externalize shared-dict UAF trigger
[*] forking two /dev/udev readers (two softcs/markers)...
[*] generating device attach/detach events (tap create/destroy)...
[*] each event is externalized by reader #1 (frees shared dict),
    then by reader #2 (UAF: prop_object_retain on freed dict).
[!] no panic after 80 create/destroy cycles (160 udev events).
[!] On a non-INVARIANTS kernel the freed dict may have been
    silently reused; check dmesg / boot.log for slab/objcache
    warnings or delayed corruption. Run with more iterations or
    an INVARIANTS kernel to force an immediate fault.
RUN2_EXIT=0   (ssh RC=0, guest UP)

----------------------------------------------------------------
RUN 3 (patched #1)
----------------------------------------------------------------
[*] DF-0055: udev_event_externalize shared-dict UAF trigger
[*] forking two /dev/udev readers (two softcs/markers)...
[*] generating device attach/detach events (tap create/destroy)...
[*] each event is externalized by reader #1 (frees shared dict),
    then by reader #2 (UAF: prop_object_retain on freed dict).
[!] no panic after 80 create/destroy cycles (160 udev events).
[!] On a non-INVARIANTS kernel the freed dict may have been
    silently reused; check dmesg / boot.log for slab/objcache
    warnings or delayed corruption. Run with more iterations or
    an INVARIANTS kernel to force an immediate fault.
RUN3_EXIT=0   (ssh RC=0, guest UP)

================================================================
boot.log panic check (after all 3 runs, full file)
================================================================
$ grep -ciE "panic:|fatal trap|already free|ocnt != 0|Memory modified" boot.log
0

The only new boot.log lines are benign tap<N> MAC-address messages from the
create/destroy cycles (no kernel warnings of any kind).

================================================================
dmesg check (after all 3 runs)
================================================================
$ dmesg | grep -iE "slab|objcache|corrupt|freed chunk|modified|overflow|underflow"
(empty โ€” no slab/objcache/corruption warnings across 480 udev events)

================================================================
CONCLUSION โ€” FIX VALIDATED
================================================================
- BASELINE (#0 unpatched): PoC panics deterministically on the FIRST reader-2
  event with `ocnt != 0` in prop_object_release (refcount-underflow form of
  the shared-dict UAF). Guest goes DOWN. (see run.baseline.log)
- PATCHED (#1 single-fix): PoC runs to completion 3/3 times (480 udev events
  total), exits 0, guest stays UP, ZERO panics/warnings in boot.log or dmesg.

The fix correctly serializes the single release of the shared event dict into
udev_clean_events_locked (after ALL readers have advanced past the event) and
removes the premature release from udev_event_externalize, so each reader's
externalize is now net-zero on ev_dict (retain on prop_dictionary_set, release
when the temp dict is dropped) and the final reap releases exactly once.

fix_status = fixed