⬢ DragonFlyBSD Kernel Audit
DF-5675 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5675",
  "status": "reproduced",
  "reproduced": true,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "Kernel thread-stack exhaustion reproduced on the guest: a 192-level nested <array> plist (2.9KB XML) sent through NETBSD_DM_IOCTL on /dev/mapper/control (0640 root:operator) kills the kernel with Fatal double fault at a page-aligned rsp (0xfffff80117e6b000) \u2014 the recursion in _prop_array_externalize/_prop_dictionary_externalize (64B/frame, objdump-verified in /boot/kernel/kernel) runs the 16KB kstack off its end, while the iterative internalize path accepts any depth. Depths 64/128 survive, 192+ panics; depth 2048 also panics. fix.diff (externalize depth cap 64) validated by full kernel rebuild+reboot on the guest: depths 192 and 2048 now return clean ENOMEM with the guest healthy, depths 32/60 still externalize and echo back (non-regressed).",
  "exploit_chain": "operator/root opens /dev/mapper/control -> ioctl(NETBSD_DM_IOCTL, plistref{xml with depth-N nested arrays}) -> prop_dictionary_copyin_ioctl internalizes iteratively (any depth OK) -> dmioctl unconditionally copyout_ioctl(dm_dict_in) -> prop_dictionary_externalize recurses pot_extern per nesting level -> kernel stack (16KB) exhausted at ~192 levels -> frames write below kstack into adjacent kmem then fault -> Fatal double fault -> panic. Guest evidence: two independent panics captured; depth ladder attributes the cliff between 128 and 192; userland same-source differential (internalize OK @20000 / externalize SIGSEGV) proves the code asymmetry.",
  "evidence": "panic.txt (first Fatal double fault, rsp=0xfffff8011891d000); panic.2.txt (attributed: DF5675_DEPTH=64/128 SURVIVED, =192 -> DOUBLE FAULT, rsp=0xfffff80117e6b000); fixtest.console.txt (patched kernel: depths 64/192/2048 all rc=0, ENOMEM for deep); run.userland.log (same-source differential: internalize OK depth 20000, externalize SIGSEGV exit 139); build.kernel.log (kernel rebuild for fix).",
  "kernel_refs": [
    "sys/libprop/prop_object.c:617-659",
    "sys/libprop/prop_object.c:1061-1107",
    "sys/libprop/prop_array.c:174-186",
    "sys/libprop/prop_dictionary.c:455-467",
    "sys/libprop/prop_kern.c:486-503",
    "sys/dev/disk/dm/device-mapper.c:181",
    "sys/dev/disk/dm/device-mapper.c:267-273",
    "sys/cpu/x86_64/include/param.h:126",
    "sys/sys/thread.h:472"
  ],
  "poc_changes": "Trigger written fresh (no seed): plistref mirror + _IOWR(0xfd,0,16) ioctl; ladder mode prints each depth to the serial console BEFORE the ioctl so the killing depth is attributable after ssh dies; single-depth mode for fix validation.",
  "attempts": 4,
  "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": 1500,
  "guest_dirty": 0,
  "build_cmd": "sh build.sh  (guest root: same-source libprop rig + cc -O2 df5675_dmcrash.c df5675_userland.c)",
  "run_cmd": "sh run.sh   (kernel run: kldload dm; ./df5675_dmcrash [depth]; panic lands on serial console / vm.sh log)",
  "code_hash": "3f74caf75bceb96703d79d931369b955b33066cb57a649af0d237888b5498ebf",
  "notes": "Reachability bar identical to DF-2227/DF-2231 (operator group or root; all current kernel echo paths are 0640/0600). The 16KB kstack math (UPAGES=4) and the measured 64B kernel frame put the cliff at 128<N<=192 \u2014 observed exactly. Below-stack frame writes precede the fault (latent adjacent-kmem corruption) but the double-fault handler converts the run to a deterministic panic; uid0 escalation not pursued for that reason.",
  "recommended_fix": "Cap externalize nesting depth: refuse to externalize containers nested deeper than _PROP_EXTERNALIZE_MAX_DEPTH (64) so the recursive walk can never exhaust the kernel stack.",
  "fix_status": "fixed",
  "fix_baseline_reproduced": true,
  "fix_patched_reproduced": false,
  "fix_verdict": "fix.diff applied inside guest /usr/src, kernel rebuilt (nativekernel, 319s) and booted: baseline depth-192 Fatal double fault is GONE; depths 192 and 2048 return ENOMEM with guest healthy; depths 32/60 still externalize and echo (errno=0). Guest reset to clean-source snapshot afterwards.",
  "fix_evidence": "fixtest.console.txt (FIXTEST_DEPTH=64/192/2048 all FIXTEST_done rc=0), patched-kernel ioctl outputs (errno=12 for 192/2048, errno=0 for 32/60), build.kernel.log, fix.diff"
}