# DF-5675 — VERDICT

**reproduced / impact=panic / confidence=certain**

## What was proven, and how

1. **The asymmetry is real in the shipped source.** Internalize of a
   20000-deep `<array>` chain succeeds (iterative prop_stack) — printed live on
   the guest from the same-source rig (`run.userland.log`:
   "internalize OK (iterative parser handles any depth)"). Externalize of the
   same object SIGSEGVs under a 256KB stack rlimit (exit 139), while depth 300
   externalizes fine. Same translation units the kernel compiles
   (`/usr/src/sys/libprop`).

2. **The kernel path exists and echoes attacker XML.**
   `dmioctl` (sys/dev/disk/dm/device-mapper.c:267-273):
   `prop_dictionary_copyin_ioctl(pref, cmd, &dm_dict_in)` … then
   **unconditionally** `prop_dictionary_copyout_ioctl(pref, cmd, dm_dict_in)` —
   the externalize walk runs over the full attacker-controlled object graph.
   `/dev/mapper/control` is created 0640 root:operator
   (device-mapper.c:181) — same reachability bar DF-2227/DF-2231 were filed
   with (any member of group operator; root trivially).

3. **The kernel stack overflow, attributed by depth.** Serial console
   (panic.2.txt): depth 64 survived, depth 128 survived, depth 192 →
   `Fatal double fault`, `rsp = 0xfffff80117e6b000` — a page-aligned rsp is the
   canonical "stack pointer ran off its 16KB kstack" signature
   (UPAGES=4, sys/cpu/x86_64/include/param.h:126; the double-fault IST handler
   catches the fault-on-fault). Measured kernel frame for
   `_prop_array_externalize` = 64B (objdump at 0xffffffff809d91c0: push
   rbp,r15,r14,r13,r12,rbx + sub $8 + retaddr), giving a theoretical cliff of
   ~16KB/64B ≈ 256 minus the ioctl entry path — observed between 128 and 192,
   exactly consistent. The first crash (panic.txt) was the ladder run ending in
   the same signature at rsp=0xfffff8011891d000.

4. **Exploitability ceiling.** Each recursion frame writes saved registers and
   locals *below* the current rsp, i.e. past the end of the kstack into
   adjacent kmem (thread kstacks are kmem_alloc'd), before the eventual guard
   fault. On this guest the observable outcome is a deterministic panic
   (impact=panic). A privileged-adjacent (operator) local user gets a reliable
   one-ioctl kernel kill with ~3KB of input; turning the below-stack writes
   into controlled corruption was not pursued because the frames write
   downward in lockstep with the fault and the double-fault handler wins
   before anything reusable can be groomed underneath.

## Fix validation (fix.diff, validated on-guest)

* baseline: depth 192 → Fatal double fault (guest dead, needs hard reset).
* `fix.diff` applied inside guest /usr/src, `make -j8 nativekernel
  KERNCONF=X86_64_GENERIC` (319s), installkernel, reboot (#1 Fri Oct 9 22:16).
* patched kernel: depth 192 **and** 2048 → `ioctl returned errno=12` (ENOMEM —
  externalize refuses, copyout fails gracefully), guest stays up, `uptime`
  healthy (fixtest.console.txt).
* non-regression: depth 32 / 60 → `errno=0`, the dict is still externalized
  and echoed back.

Root-cause line refs: prop_object.c:617-659 (iterative internalize that accepts
any depth) vs prop_array.c:174-186 / prop_dictionary.c:455-467 (recursive
externalize); echo path device-mapper.c:267-273; 16KB kstack
sys/cpu/x86_64/include/param.h:126 + sys/sys/thread.h:472.

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