# DF-0727 — NULL-pointer dereference in NGM_FLOW_COOKIE handler (ng_tee)

## Verdict: REPRODUCED (panic / local DoS, root-only)

## The bug

`sys/netgraph7/tee/ng_tee.c:262-271` — the `NGM_FLOW_COOKIE` case in
`ng_tee_rcvmsg()`:

```c
case NGM_FLOW_COOKIE:
    if (lasthook == sc->left.hook || lasthook == sc->right.hook)  {   // line 263
        hi_p const hinfo = NG_HOOK_PRIVATE(lasthook);                  // line 264  <-- NULL deref
        if (hinfo && hinfo->dest) {
            ...
        }
    }
    break;
```

When `lasthook` is NULL (message addressed by node name — no hook segment
in the path) **and** the tee node's `left`/`right` hooks were never
connected (`sc->left.hook == NULL`, `sc->right.hook == NULL`), the
condition `NULL == NULL` evaluates TRUE, and
`NG_HOOK_PRIVATE(lasthook)` expands to `lasthook->hk_private` =
`NULL->hk_private` (offset 0x20) → **page fault / kernel panic**.

The defensive `if (hinfo && hinfo->dest)` on line 265 is dead code — the
crash on line 264 precedes it.

### How `lasthook` becomes NULL

`ng_address_path()` (`sys/netgraph7/netgraph/ng_base.c:1689-1822`) calls
`ng_path_parse()`. When the address is a bare node name with trailing colon
(e.g. `"tee0:"`), `ng_path_parse` yields `nodename="tee0"`, `path=NULL`.
`ng_path2noderef` then sets `*lasthook = (hook ? NG_HOOK_PEER(hook) : NULL)`
= NULL (line 1819-1820, `hook` was never assigned since no path segments
were traversed). The item is delivered to `ng_tee_rcvmsg` with
`lasthook=NULL`.

`ng_address_ID()` (`ng_base.c:3125`) also clears the hook via
`NGI_CLR_HOOK(item)`, so messages addressed by numeric node ID also have
`lasthook=NULL`.

### Sibling guard

The sibling `ng_UI.c:145` has the correct guard:
```c
if ((msg->header.typecookie == NGM_FLOW_COOKIE) && lasthook) {
```
`ng_tee` omits the `lasthook` (NULL) check.

## Reachability

- **Root-only.** The AF_NETGRAPH control socket requires
  `caps_priv_check(SYSCAP_RESTRICTEDROOT)` (`ng_socket.c:182`).
  An unprivileged user cannot open it.
- **Netgraph7 required.** The bug is in `sys/netgraph7/tee/ng_tee.c`.
  DragonFly's *default* netgraph is the legacy `sys/netgraph/` (NG_VERSION=2),
  whose `ng_tee.c` does **not** have the `NGM_FLOW_COOKIE` handler at all.
  Netgraph7 is opt-in (`option NETGRAPH7` in the kernel config, or loading
  the netgraph7 KLD modules built from `sys/netgraph7/`). An admin who
  chooses netgraph7 is vulnerable.

## Impact

**Deterministic kernel panic** (local DoS, root → kernel). No escalation:
NULL deref is a read, no write primitive. The finding's Low severity is
appropriate (root-only DoS on a non-default netgraph stack).

## Reproduction

1. Build and load the netgraph7 modules (`netgraph.ko`, `ng_socket.ko`,
   `ng_tee.ko` from `sys/netgraph7/`).
2. `cc -o ng_tee_flow_nullderef ng_tee_flow_nullderef.c`
3. `./ng_tee_flow_nullderef` (as root)

The PoC creates a tee node with only the `left2right` hook connected
(`left`/`right` never connected), names it `tee0`, then sends an
`NGM_FLOW_COOKIE` message addressed by node name (`tee0:`, no hook segment)
so `lasthook=NULL`. The kernel panics:

```
Fatal trap 12: page fault while in kernel mode
fault virtual address = 0x20
Stopped at ng_tee_rcvmsg+0x83: movq 0x20(%rdx),%rax
```

The fault address 0x20 = offset of `hk_private` in `struct ng_hook`;
`%rdx` = 0 (NULL `lasthook`).

## Fix

Add a `lasthook != NULL` guard (matches the sibling `ng_UI.c:145` pattern):

```diff
--- a/sys/netgraph7/tee/ng_tee.c
+++ b/sys/netgraph7/tee/ng_tee.c
@@ -260,7 +260,7 @@
 		}
 		break;
 	case NGM_FLOW_COOKIE:
-		if (lasthook == sc->left.hook || lasthook == sc->right.hook)  {
+		if (lasthook != NULL && (lasthook == sc->left.hook || lasthook == sc->right.hook)) {
 			hi_p const hinfo = NG_HOOK_PRIVATE(lasthook);
```

## Fix validation

- **Baseline (unfixed netgraph7 ng_tee):** PoC panics the kernel
  (`ng_tee_rcvmsg+0x83: movq 0x20(%rdx),%rax`, fault va=0x20). Guest down.
- **Patched (fixed ng_tee with `lasthook != NULL` guard):** PoC exits 0
  cleanly, no panic, guest stays up. Confirmed over 2 runs.

See `fix.diff` for the standalone git-apply-able diff.
