# DF-0529 — VERDICT

## Verdict: REPRODUCED (panic); root-triggered; fix INCOMPLETE (fix_failed)

## The bug (confirmed in source)

`ng_fec_constructor()` (`sys/netgraph/fec/ng_fec.c:1071`) allocates a single
contiguous `struct ng_fec_private` and then derives an **interior pointer**:

```c
priv = kmalloc(sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO);   // :1079
...
ifp = &priv->arpcom.ac_if;                                      // :1085  (INTERIOR)
```

`struct arpcom` is embedded inside `struct ng_fec_private`, so `ifp` points
into the *middle* of the `priv` allocation, not to a separately-allocated
object.  Both error paths then do:

```c
// :1091-1094  (ng_fec_get_unit failure)
kfree(ifp, M_NETGRAPH);   // interior pointer -> UB for the slab allocator
kfree(priv, M_NETGRAPH);  // double-free of the SAME allocation

// :1099-1102  (ng_make_node_common failure)
ng_fec_free_unit(priv->unit);
kfree(ifp, M_NETGRAPH);   // interior pointer again
kfree(priv, M_NETGRAPH);  // double-free again
```

This is the DF-0529 defect: **interior-pointer `kfree()` + double-free of one
allocation** — a textbook memory-corruption primitive.

## Reproduction (deterministic)

Creating an ng_fec node triggers the constructor unconditionally:

```
# kldload netgraph ; kldload ng_fec
# ngctl mkpeer .: fec myhook peerhook
```

→ **kernel panic on EVERY node creation** (not merely "under memory pressure"
as the finding summary speculated — the error path fires on every call on this
guest, making the bug strictly more severe than filed).  Serial-console proof
(`dfbsd-qemu/boot.log`):

```
panic: trying to free NULL pointer
_kfree() at _kfree+0x558
_kfree() at _kfree+0x558
ng_fec_constructor() at ng_fec_constructor+0x3ae
ng_mkpeer() -> ng_generic_msg() -> ng_send_msg()
```

The slab allocator's `kfree()` (`sys/kern/kern_slaballoc.c:1407`) panics with
"trying to free NULL pointer" because the interior-pointer free corrupts the
zone state and the follow-up `kfree(priv)` dereferences a cascaded NULL.
Reproduced 4+ times across separate boots; signature identical each time.

## Reachability / privilege model

Netgraph node creation goes through the ng_socket **control socket**, which is
gated by a capability check:

```c
// sys/netgraph/socket/ng_socket.c:172
if (caps_priv_check(ai->p_ucred, SYSCAP_RESTRICTEDROOT|__SYSCAP_NULLCRED) != 0)
    error = EPERM;
```

Verified empirically: unprivileged user `maxx` (uid 1001, not in wheel) is
denied — `ngctl: can't create node: Operation not permitted`.  **Only root can
create netgraph nodes.**  Therefore the DF-0529 trigger is **root → kernel**.

## Exploit chain (Phase 6)

- Primitive class: double-free / interior-pointer free of a `M_NETGRAPH`
  slab allocation (`sizeof(struct ng_fec_private)` — large, contains the full
  `struct arpcom`/`ifnet`).
- On GENERIC (INVARIANTS ON): manifests as a deterministic **panic** (DoS).
  The slab INVARIANTS and the NULL-pointer guard catch the corruption before
  grooming can land.
- Escalation to `uid=0`: **BLOCKED by a valid hard blocker** — the write is
  reachable *only from an already-root context* (the ng_socket privilege gate
  requires root; an unprivileged user cannot create an ng_fec node).  Root→kernel
  is game-over by definition; there is no privilege boundary to cross.  This is
  not a circular precondition (it is the actual, verified access model), so the
  honest impact is **root-triggered kernel panic / memory corruption**, not an
  unprivileged escalation.

## Fix validation (Phase 8)

`fix.diff` removes the two `kfree(ifp, M_NETGRAPH)` interior-pointer frees,
leaving a single correct `kfree(priv)` on each error path.  The diff:

- applies cleanly (`patch -p1` — both hunks succeed);
- **compiles** (`make` in `sys/netgraph/fec` → RC=0, `ng_fec.ko` produced);
- was installed hash-verified into `/boot/kernel/ng_fec.ko` with linker hints
  rebuilt (`kldxref`).

**But the panic PERSISTS.**  With the patched module loaded, `ngctl mkpeer .:
fec ...` still panics — the constructor offset merely shifts from `+0x3ae` to
`+0x38e`, proving the patched code is running.  An isolation build that removed
*all* kfrees from the constructor error paths (intentional leak) **also** still
panicked with the same two-`_kfree`/"NULL pointer" signature.

**Conclusion: the finding's root-cause analysis is INCOMPLETE.**  The
documented `kfree(ifp)`/double-`kfree(priv)` is a real defect, but the
constructor has an *additional, undocumented* kfree cascade — most plausibly in
the inlined `static __inline__` `ng_fec_get_unit`/`ng_fec_free_unit` cold paths
(which each contain a guarded `kfree(ng_fec_units, M_NETGRAPH)`), or in the
slab allocator's reaction to the `M_NETGRAPH` zone state after the interior-
pointer corruption.  `fix_status = fix_failed`: the fix correctly addresses the
documented defect but does not stop the constructor panic.  Next iteration:
kgdb the live DDB `db>` prompt to `bt`/`examine` the exact inlined kfree site
and the value being freed, then extend the fix to cover it.

## Knock-on effect

Because the constructor panics on every node creation (and continues to do so
even after DF-0529's documented fix), **DF-0526, DF-0527, and DF-0528 are all
unreachable at runtime on this kernel** — no ng_fec node can ever exist long
enough to pass traffic (`choose_port`) or be torn down (`rmnode`).  See their
individual verdicts.

## PoC changes

Wrote `trigger.sh` (ngctl mkpeer → panic), `build.sh`/`run.sh`, `fix.diff`
(remove interior-pointer kfrees), full logs.  No upstream PoC existed (folder
was empty).
