# DF-3425 VERDICT

**Status: REPRODUCED** (kernel panic from use-after-free; race won within the
first 240 s run; 1 attempt)

## The bug (code trace)

- `sys/netinet/ip_encap.c:174-215` — `encap4_input()`, running on NETISR(0)
  for every inbound IPIP/proto-4 packet, walks the global `encaptab` LIST
  **with no lock**, and for each entry calls `ep->func(m, off, proto, ep->arg)`
  (ip_encap.c:180). For gif entries `func` is `gif_encapcheck` and `arg` is
  the `gif_softc *` (in_gif.c:378-379).
- `sys/netinet/ip_encap.c:411-426` — `encap_detach()` does `LIST_REMOVE` +
  `kfree(ep)` with no lock and not even a `crit_enter()`.
- `sys/net/if/gif/if_gif.c:176-203` — `gif_clone_destroy()` (reached from
  `if_clone_destroy()` under `ifnet_lock()` only — sys/net/if_clone.c:104-135
  — *not* the gif ifnet serializer) calls `gif_delete_tunnel()` (kfrees
  `gif_psrc`/`gif_pdst`, if_gif.c:821-827) and `encap_detach()` for both
  cookies, then `kfree(sc)` (if_gif.c:200).
- `sys/net/gif/if_gif.c:744-758` — `gif_set_tunnel()` likewise detaches and
  re-attaches encap entries on reconfiguration under only a `crit_enter()`.

Net effect: a gif being destroyed/reconfigured (root: `ifconfig gifN destroy`,
`ifconfig gifN tunnel A B`, or module unload) races the netisr0 walk:
- the walk can `LIST_NEXT()` a freed entry (kmalloc free-list linkage lands
  exactly on `chain.tqe_next` at offset 0 of `struct encaptab`,
  sys/netinet/ip_encap.h:57-58), or
- fetch `ep->arg` (softc) just before the entry is freed and call
  `gif_encapcheck()` on a freed `gif_softc`.

## Reproduction (this run)

Harness `gif_race.c` (root):
- 30 static gif tunnels (lengthens the walk),
- thread "destroyer": tight `SIOCIFCREATE gif50` + `SIOCSIFPHYADDR` +
  `SIOCIFDESTROY gif50` loop (each cycle: encap_attach_func, encap_detach
  kfree, gif_delete_tunnel kfree, kfree(sc)),
- thread "reconfig": tight `SIOCSIFPHYADDR` alternation on gif51
  (encap detach/attach churn under crit only),
- 4 flood threads: raw IPPROTO_IPV4 sockets sendto 127.0.0.1, delivering a
  continuous IPIP stream into `ip_input` -> `encap4_input()` on netisr0.

Result within the 240 s window — guest panicked (serial console, `panic.txt`):

```
Fatal trap 12: page fault while in kernel mode
cpuid = 0; lapic id = 0
fault virtual address = 0xfffff8011861306c
fault code = supervisor read data, page not present
Stopped at      gif_encapcheck+0x5:  testb $0x1,0x6c(%rcx)
```

`gif_encapcheck+0x5` is the first instruction of
`if ((sc->gif_if.if_flags & IFF_UP) == 0)` (if_gif.c:279) — the freed softc
deref; `cpuid = 0` / `current process = Idle` = the netisr0 walk context.
This is the use-after-free read exactly as traced.

## Exploitability

- Trigger side (flood + walk) is unprivileged-triggerable packet traffic;
  the free side (destroy/reconfig ioctls) requires root
  (sys/net/if.c:2335-2343 `SYSCAP_RESTRICTEDROOT`). The race is therefore a
  *privileged-teardown* hazard: routine `ifconfig gifN destroy` /
  reconfiguration under traffic load can panic or corrupt the kernel.
- Primitive: UAF read of a kfree'd `struct encaptab` (walk continues through
  free-list linkage into arbitrary same-zone chunks) and call-through of
  `ep->func`/`ep->psw->pr_input` from stale chunk contents, plus later
  `m_pkthdr.rcvif` use of the freed softc in the requeued inner packet.
  Reliable code execution was NOT developed: the free side is root-gated,
  the chunk-recycling window is sub-microsecond, and M_IPENCAP/M_GIF chunks
  are recycled by the harness's own loop. Impact recorded as panic (DoS);
  classification as a UAF (memcorrupt family) is appropriate for triage.

## Fix validation (two iterations)

Attempt 1 — a dedicated `encap_token` taken by both the walks and the
mutators — was **rejected by experiment**: the patched kernel itself
panicked with `lwkt_reltoken: illegal release` because netisr transport
processing (`transport_processing_oncpu` → `encap4_input`) already runs
holding `mp_token`, and releasing a second nested token there violates LWKT
tokref ordering (`fix.attempt1.failed.txt`).

Attempt 2 (final `fix.diff`) — `lwkt_gettoken(&mp_token)` ONLY in the three
mutators (`encap_attach`, `encap_attach_func`, `encap_detach`); the walks
already run under `mp_token` implicitly, so mutation and walk are now
serialized without nesting a new token inside netisr. Validated on the
guest: built clean (0 errors), installed as kernel #1 2026-09-12 13:26:06,
and the identical harness ran twice without incident —
- 300 s: flood=68,491,080 cyc=156,307 recfg=6,661,618 err=0 → `done (no
  panic)`, guest up;
- 120 s confirmation: flood=25,621,720 cyc=66,516 recfg=2,656,303 err=0.

Baseline stock kernel #0 panicked in the first 240 s run
(`gif_encapcheck+0x5`). Statistical caveat: a race can never be *proven*
absent, but the patched runs exercised ~6x the event volume that sufficed
to kill the baseline, and the mp_token argument holds by construction
(proven by attempt 1's own assertion trace showing the walker holds
mp_token).

Known residual (documented, not fixed here): a packet already requeued via
`netisr_queue` inside the walk carries `rcvif` = softc and can be touched
after the walk completes; a full fix needs softc reference counting
(compare FreeBSD's if_ref/encap refcount rework).

## Line-accurate references

- ip_encap.c:174-215 walk; :180 func call; :217-227 dispatch
- ip_encap.c:411-426 encap_detach (LIST_REMOVE+kfree, no lock)
- ip_encap.c:329/386 crit_enter is per-CPU only — no cross-CPU exclusion
- if_gif.c:176-203 gif_clone_destroy; :200 kfree(sc)
- if_gif.c:814-843 gif_delete_tunnel; :821-827 kfree(gif_psrc/pdst)
- if_gif.c:716 crit_enter (no cross-CPU serialization for set_tunnel churn)
- if_clone.c:104-135 ifnet_lock-only destroy path
- if.c:2335-2343 root gating of the tunnel ioctls

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