# DF-5844 — VERDICT

**Status: reproduced** (panic / kernel stack memory corruption from an
unprivileged sysctl next-walk; impact capped at `panic` for this rig because
planting the >12-deep tree requires a root-loaded KLD — see Honest scoping).

## Root cause (path:line)

- `sys/kern/kern_sysctl.c:786` — `int newoid[CTL_MAXNAME]` (12 ints; `CTL_MAXNAME = 12`,
  sys/sys/sysctl.h:55) in `sysctl_sysctl_next()`.
- `sys/kern/kern_sysctl.c:719-727` — `sysctl_sysctl_next_ls()` writes `*next =
  oidp->oid_number` (line 727) at `next[level-1]`; `level` starts at 1.
- The `!namelen` branch (kern_sysctl.c:733-744) and the `oidp->oid_number >
  *name` branch (kern_sysctl.c:749-758) recurse into `SYSCTL_CHILDREN(oidp)`
  with `next+1, level+1` **without consuming a name component** in the former
  case, so recursion depth tracks the MIB *tree* depth. No check of `level`
  against `CTL_MAXNAME` exists anywhere in the function.
- `sys___sysctl` caps the user's `namelen` at 12 (kern_sysctl.c:1494), but
  that does not bound this walk: `sysctl_sysctl_next` is reached with
  `namelen = 0` for a bare `{CTL_SYSCTL, CTL_SYSCTL_NEXT}` query, and the
  `!namelen` descent recurses to full tree depth.

Tree depth ≥ 13 ⇒ `newoid[12]`, `newoid[13]`, … are written past the array,
one 32-bit `oid_number` per level, marching up `sysctl_sysctl_next`'s stack
frame (saved registers, saved RBP, return addresses of its callers), then
`SYSCTL_OUT(req, newoid, j * sizeof(int))` (kern_sysctl.c:791) would
additionally *copy out* `j > 12` ints from beyond the array (stack info leak)
if the smash itself didn't fault first.

## Reproduction (baseline, unpatched kernel #0, Jul 2 2026)

1. Guest measured natural MIB depth = **5** (`sysctl -aN | awk -F.` max
   components) — no in-tree subsystem nests >12, so the rig plants one.
2. `deepoid.ko` (root, rig only): 24 nested dynamic nodes, explicit oid
   numbers `0x41414100+i` (chain[12] = 0x4141410c, chain[13] = 0x4141410d, …).
3. Trigger as `nobody`: `/tmp/trigger` — a plain `sysctl({1,3,...})` next-walk
   loop; no privileges needed (read-only walker, no caps check on this path:
   sysctl_root write-gates at kern_sysctl.c:1439-1450 don't apply to reads).

Result (serial console, `panic.txt`):

```
deepoid: loaded 24-deep chain (oid 0x41414100+)
Fatal trap 9: general protection fault while in kernel mode
frame pointer = 0x10:0x4141410d4141410c
Stopped at  sysctl_sysctl_next+0x63:  ret
```

RBP = `0x4141410d4141410c` — literally the planted oid numbers of chain
levels 13 and 12: **proof the OOB writes are fully value-controlled by the
oid creator and land exactly at `newoid[12..13]`**, hitting the saved frame
pointer; the fault is the `ret` in `sysctl_sysctl_next` popping attacker
data. Same panic reproduced twice (root-run and nobody-run) — see
`panic.root-run.txt`, `panic.txt`.

## Primitive characterization (why this matters beyond the rig)

- **What**: ascending, one-int-per-level overwrite of the kernel stack above
  a 48-byte local array, values = oid numbers (fully chosen by whoever can
  create the deep chain), count = depth−12 ints.
- **Where**: fixed offset in `sysctl_sysctl_next`'s frame — saved registers,
  frame pointer, return address of `sysctl_sysctl_next` itself, then callers
  (`sysctl_root`, `userland_sysctl`, syscall trap frame). With depth ~60+ the
  writes reach the trap frame's return-to-user path.
- **Who triggers**: *any* user — `sysctl -aN`, libkvm-based tools, anything
  that walks the MIB. No privilege gate exists on the walker.
- **Honest scoping (hard blocker for uid=0)**: to turn this into
  unpriv→root, the *victim system* must first contain a >12-deep sysctl
  tree, and creating one requires `kldload`/root via `sysctl_add_oid`
  (1,256 kernel callers, none unpriv-triggered; natural depth measured 5).
  So the escalation chain ends at "root must plant the weapon" — on a stock
  system no unprivileged user can build the deep tree. It is a landmine for
  any future subsystem that auto-generates nested oid trees (à la FreeBSD's
  `dev.foo.0.bar` auto-trees), and an immediate kernel-smash for
  third-party modules that nest >12. Severity filed Medium accordingly.

## Fix validation

`fix.diff`: reject `level > CTL_MAXNAME` at `sysctl_sysctl_next_ls` entry
(the too-deep subtree is then reported empty — consistent with the
12-component naming limit). Applied to guest `/usr/src`, kernel rebuilt
(`make nativekernel`, BUILD_RC=0), installed, rebooted (#1, Oct 14 01:16).
Identical rig + identical `su -m nobody -c /tmp/trigger`:

- baseline: Fatal trap 9, RBP = 0x4141410d4141410c, DDB, guest down.
- patched: `walk finished after 1834 oids: No such file or directory`,
  TRIG_RC=0, `sysctl -aN | wc -l` = 1829, guest up. (`fix_validation.log`)

Bad behavior gone; fix confirmed effective.

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