# DF-0502 — ng_fec_shutdown infinite loop when a member interface is destroyed

## Verdict: INCONCLUSIVE at runtime — bug is unambiguous by inspection (certain); netgraph node-creation bootstrap is blocked on this guest

## The bug (unambiguous, certain)
`sys/netgraph7/ng_fec.c` `ng_fec_shutdown()` (lines 1335-1339):

```c
while (!TAILQ_EMPTY(&b->ng_fec_ports)) {
    p = TAILQ_FIRST(&b->ng_fec_ports);
    ng_fec_ether_cmdmulti(priv->ifp, p, 0);
    ng_fec_delport(priv, p->fec_if->if_xname);   /* re-resolves by name */
}
```

`ng_fec_delport()` (line 481) re-resolves the member via `bifp = ifunit(iface)`.
If the member interface has already been destroyed, `ifunit()` returns `NULL`
and `delport` returns `ENOENT` (line 485) **without removing the portlist entry**
(`TAILQ_REMOVE` at :515 is skipped). So `TAILQ_FIRST` returns the **same** `p`
forever ⇒ **infinite loop** in kernel context (unkillable). Additionally
`p->fec_if` is a dangling pointer to the freed `ifnet` (no refcount is held —
`new->fec_if = bifp` at :450), so `p->fec_if->if_xname` is a **use-after-free
read** on every iteration.

This is deductively certain from the source: the loop's termination condition
(`TAILQ_EMPTY`) can never become true once a member is unresolvable.

## Privilege boundary
Creating/destroying an ng_fec node and member interfaces requires root
(`ngctl`/`kldload`/`ifconfig destroy`). Root-only ⇒ root→kernel self-DoS /
hardening gap; no unpriv→root escalation.

## Why not reproduced on this guest
The live trigger needs an ng_fec **node** to exist (with a member port that is
then destroyed before shutdown). Creating an ng_fec node requires `mkpeer`,
which on this DragonFly netgraph7 build is **non-functional for interface-backed
node types**:

`sys/netgraph7/ether/ng_ether.c` `ng_ether_rcvmsg()` (line 560-562):
```c
default:            /* any typecookie != NGM_ETHER_COOKIE */
    error = EINVAL;
    break;
```
`ng_ether` is the only hookable node family available on the guest (one per
ethernet/tap interface), and it **eats generic control messages** — including
`NGM_MKPEER` (NGM_GENERIC_COOKIE) — returning `EINVAL` without forwarding them
to the generic netgraph handler. Confirmed empirically: `ngctl mkpeer tap0:
upper fec lower`, `... echo x`, `... tee x` all fail with `send msg: Invalid
argument`, while `ngctl connect tap0: tap1: upper lower` succeeds. With no way
to instantiate an ng_fec node, the shutdown infinite loop cannot be exercised.

`repro_attempt.sh` records the exact commands and the blocker.

## Fix (applies + compiles + boots)
`fix.diff` makes `ng_fec_shutdown` remove portlist entries directly instead of
re-resolving each member by name:

```c
while ((p = TAILQ_FIRST(&b->ng_fec_ports)) != NULL) {
    ng_fec_ether_cmdmulti(priv->ifp, p, 0);
    TAILQ_REMOVE(&b->ng_fec_ports, p, fec_list);
    kfree(p, M_NETGRAPH);
    b->fec_ifcnt--;
}
```
This both breaks the infinite loop (entry is always removed) and avoids the
`p->fec_if` UAF read (no dereference of the dangling pointer). Built into the
combined single-fix kernel (#1); boots clean. `fix_status = not_testable`
(the trigger topology cannot be set up on this guest).

## Files
- `repro_attempt.sh` — the ngctl/ifconfig sequence + the observed EINVAL blocker
- `fix.diff` — direct-remove in shutdown
- `README.md`, `VERDICT.md`, `manifest.json`
