⬢ DragonFlyBSD Kernel Audit
DF-5699 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5699",
  "status": "reproduced",
  "verdict": "prop_dictionary_ingest() (sys/libprop/prop_ingest.c:121-159) obtains each rules value with the borrowing accessor prop_dictionary_get() (prop_ingest.c:133), which copies pde->pde_objref out without prop_object_retain and drops the dictionary rwlock before returning (prop_dictionary.c:933-945, unlock at :944). The walker then runs the arbitrary driver handler (prop_ingest.c:152) for unbounded time on that unpinned pointer with no lock held, repeating for every table entry: any concurrent prop_dictionary_remove()/same-key set/release of the dictionary frees the object underneath the handler \u2014 a use-after-free handed directly to consumer callback code, and handlers are documented to stash the object into driver state, extending the dangling lifetime arbitrarily. Reproduced deterministically (3/3 runs, SIGBUS on attempt 1) on the same-source userland rig (/usr/src/sys/libprop compiled as-is, unprivileged guest user): control (spray, no free) stable; experiment (prop_dictionary_remove under the sleeping handler) faults inside prop_object_type(borrowed) on the 0x4141-reclaimed header. Fixed by pinning (prop_object_retain/release around the dispatch): patched rig survives identical abuse 2/2 with type/value stable and ingest=true. Severity Low because the kernel instantiation is latent today \u2014 sys/conf/files:2249 compiles the file standard into every kernel, but rg shows zero in-tree callers; the same source ships as the base userland libprop (/usr/include/libprop/prop_ingest.h) where the hazard is live now for multi-threaded programs of any privilege.",
  "reproduced": true,
  "impact": "dos",
  "confidence": "certain",
  "exploit_chain": "Same-source userland rig: thread A prop_dictionary_ingest() on {\"victim\": prop_number 0x1337beef00} with a handler that snapshots obj and sleeps 400ms; thread B prop_dictionary_remove(dict,\"victim\") during the sleep (number not interned -> dict held the only ref -> freed on the spot) then sprays 0x41-filled 40..96B allocations; handlers post-sleep prop_object_type(borrowed) derefs po_type=0x4141414141414141 -> SIGBUS (exit 138) on attempt 1, 3/3 runs. In-kernel: no chain exists yet \u2014 zero in-tree callers (rg prop_ingest|PROP_INGEST sys/ outside libprop matches only sys/conf/files:2249); the primitive for a future kernel consumer is freed-object reuse read (and stale pointer retained into driver state) inside a supposedly-safe callback.",
  "evidence": "run.log/run.2.log/run.3.log: control stable then attempt 1 exit=138 ... Bus error (core dumped) between post-sleep read-back begins and the absent survived line \u2014 crash is exactly the UAF read. fix_run.log/fix_run.2.log: FIX VALIDATION PASSED (handler survives, type 1852662386 -> 1852662386, value 0x1337beef00 -> 0x1337beef00, ingest=true). VERDICT.md has the full path:line trace.",
  "kernel_refs": [
    "sys/libprop/prop_ingest.c:121",
    "sys/libprop/prop_ingest.c:131",
    "sys/libprop/prop_ingest.c:133",
    "sys/libprop/prop_ingest.c:147",
    "sys/libprop/prop_ingest.c:152",
    "sys/libprop/prop_dictionary.c:909",
    "sys/libprop/prop_dictionary.c:933",
    "sys/libprop/prop_dictionary.c:944",
    "sys/conf/files:2249"
  ],
  "poc_changes": "No seed existed (pass-2 new finding); harness authored from scratch: control/experiment differential (DF5699_CONTROL env), 0x41 sentinel size-class spray 40..96B, 400ms free window, prop_number victim (not interned \u2014 prop_number.c:273 has no intern tree), crash-or-differential dual detection.",
  "attempts": 3,
  "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": 90,
  "guest_dirty": 0,
  "build_cmd": "sh /tmp/df5699/build.sh",
  "run_cmd": "sh /tmp/df5699/run.sh",
  "code_hash": "ece6127af81ec22135a713669ac703459f7dbd15404f850872ac4989b8105547",
  "notes": "Guest stayed up throughout (userland crash only; /tmp/df5699 incl. core dumps removed before return). Full fixed-kernel build skipped as meaningless: the function has zero in-tree callers, so kernel behavior cannot differ; rig A/B isolates the one-hunk fix exactly. Same borrowed-pointer class as DF-5690/DF-5691 but in the core walk API \u2014 every consumer inherits it for every key. Pass-2 negative results for this file (terminator walk, type-gate semantics, error-path/no-unwind contract, ctx reuse staleness, NULL handling of prop_object_type/prop_dictionary_get) recorded in VERDICT.md.",
  "recommended_fix": "Pin the object across the handler callback: prop_object_retain(obj) before the dispatch at prop_ingest.c:152, prop_object_release(obj) on the handler-failed return and after the successful call (see fix.diff).",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Rig-level A/B: baseline (unmodified /usr/src/sys/libprop) SIGBUS 3/3 at the handlers UAF read; patched rig (identical sources + only the retain/release hunk from fix.diff) survives the identical abuse 2/2 with type/value stable and ingest=true. Kernel rebuild not performed \u2014 zero in-tree callers means no kernel behavior can differ; the rig compiles the exact audited source.",
  "fix_evidence": "fix_run.log + fix_run.2.log end with FIX VALIDATION PASSED: object pinned across handler, no crash, no differential; compare run.log/run.2.log/run.3.log baseline attempt 1 exit=138 ... Bus error (core dumped). fix.diff sha256 a14cf526a13fbbaa21f98e558b643f5d80a3bef86c23e04e83a42eee30c6ed51."
}