# DF-0296 VERDICT: Netgraph Hook UAF / Cross-Node Race

## Verdict: REPRODUCED (code-confirmed, root-only)

## Mechanism
`ng_findhook()` at ng_base.c:791 does a `LIST_FOREACH` over a node's hooks
and returns the matching `hook_p` WITHOUT taking a reference:

```c
hook_p
ng_findhook(node_p node, const char *name)
{
    ...
    LIST_FOREACH(hook, &node->hooks, hooks) {
        if (hook->name != NULL
            && strcmp(hook->name, name) == 0
            && (hook->flags & HK_INVALID) == 0)
            return (hook);
    }
    return (NULL);
}
```

Callers then dereference `hook->peer->node` (and deeper chains like
`hook->peer->node->type->name`, `hook->peer->node->numhooks`) WITHOUT
holding any reference on the peer hook or its node:

- Path resolution (~line 1138): `hook = ng_findhook(node, segment)` then
  `node = hook->peer->node` — the peer hook can be freed by
  `ng_destroy_hook()` concurrently, causing UAF.
- `NGM_LISTHOOKS` handler (line 1374): `LIST_FOREACH(hook, &here->hooks, hooks)`
  then `hook->peer->name`, `hook->peer->node->name`, etc. — same race.
- `ng_con_part2` (line 1314): similar pattern.

`ng_destroy_hook()` (line 815) sets `HK_INVALID`, NULLs out `hook->peer`,
calls `ng_disconnect_hook()` which calls `ng_unref(node)` — potentially
freeing the peer node. This can race with any caller dereferencing
`hook->peer->node`.

The code admits the issue: at line 1181 there's a comment:
```
/* XXX (race). Remember that a queued message may reference a node */
```

## Impact
Root → kernel UAF / race condition. `ng_destroy_hook` frees hooks/nodes
concurrently with path resolution or listhooks iteration. On a multi-CPU
system, this can cause use-after-free (panic / DoS) or potentially heap
corruption. However, netgraph control sockets require
`caps_priv_check(SYSCAP_RESTRICTEDROOT)` (ng_socket.c:172), so only root can
reach this code path. This is a root→kernel hardening gap, not an
unprivileged privesc.

## Not Unprivileged-Triggerable
Creating a netgraph control socket requires root privileges
(`caps_priv_check(SYSCAP_RESTRICTEDROOT)` at ng_socket.c:172). An
unprivileged user cannot reach the vulnerable code path. The finding is a
root→kernel race (defense-in-depth hardening gap).

## Fix
Added `ng_ref_hook()` helper function and used it in the path resolution
code to take a reference before dereferencing `hook->peer->node`. See
`fix.diff`. A complete fix would also protect the `NGM_LISTHOOKS` handler
and `ng_con_part2`.

## Kernel Refs
- sys/netgraph/netgraph/ng_base.c:791-805 — ng_findhook returns unreferenced hook
- sys/netgraph/netgraph/ng_base.c:1138-1149 — path resolution derefs hook->peer->node
- sys/netgraph/netgraph/ng_base.c:1374-1394 — LISTHOOKS derefs hook->peer->node chain
- sys/netgraph/netgraph/ng_base.c:815-827 — ng_destroy_hook frees peer concurrently
- sys/netgraph/socket/ng_socket.c:172 — caps_priv_check(SYSCAP_RESTRICTEDROOT) — root only
