# DF-3280 — use-after-free walk in `if_getgroupmembers()` (SIOCGIFGMEMB)

**File:** `sys/net/if.c` (pass 2)
**Severity:** Medium · **CWE:** CWE-416 / CWE-367 · **Bucket:** memcorrupt (stale-pointer read)

## Bug

`if_getgroupmembers()` (`sys/net/if.c:1314-1370`) implements the two-phase
SIOCGIFGMEMB protocol (query length, then fetch members):

1. First critical section (LK_SHARED, `if.c:1322-1337`): finds the target
   `struct ifg_group *ifg` in `ifg_head` and counts the members into `len`.
2. **Releases** `ifgroup_lock` (`if.c:1337`), then `kmalloc(len)`s the reply
   buffer (`if.c:1346`).
3. Second critical section (`if.c:1350-1364`): walks **`ifg->ifg_members`**
   through the pointer cached in step 1 — **the pointer is never
   re-validated.**

If the last member is removed between the two sections (SIOCDIFGROUP /
`if_detach()` group teardown), `if_delgroup_locked()` drops `ifg_refcnt` to 0
(`if.c:1234`) and `if_destroygroup()` **kfrees the group** (`if.c:1139`).
The fill pass then walks freed memory and copies what it finds to the
(unprivileged!) caller.

The sibling function `if_getgroups()` is written correctly: its second pass
re-walks `&ifp->if_groups` under the re-acquired lock and holds no cached
group pointer (`if.c:1289-1303`). Only `if_getgroupmembers()` caches `ifg`.

## Threat model

* Reader side: **any unprivileged user** — SIOCGIFGMEMB is dispatched at
  `if.c:2019-2020` before any privilege check.
* Mutator side: **root** — SIOCAIFGROUP/SIOCDIFGROUP require
  `SYSCAP_NONET_IFCONFIG` (`if.c:2387`, `if.c:2396`); `if_detach()` group
  teardown is also privileged.
* Consequence: stale kernel-heap contents copied to an unprivileged caller;
  the walk itself (`ifgm->ifgm_ifp->if_xname` through a dangling `ifg`) is a
  potential panic when the freed chunk is reused with non-group contents.

## Reproduce

```
build.sh:  cc -O2 -o /root/gmembr_churn  /root/gmembr_churn.c
           cc -O2 -o /root/gmembr_reader /root/gmembr_reader.c
run.sh:    (root) nohup /root/gmembr_churn rgrpA lo0 >/root/churn.log 2>&1 &
           (user) /home/maxx/gmembr_reader rgrpA
expected:  reader prints "ANOMALY ... member[i]='<garbage>'" — data read
           through the dangling group pointer (only legit member is "lo0").
           A panic is possible; not observed (type-stable reuse, see VERDICT).
```

Observed anomalies: baseline runs 1/2/3 → 2 / 9 / 3 stale reads
(`run.log`, `run.2.log`, `run.3.log`). Fix kernel (fix.diff applied):
**0 anomalies** in `run.fix.log` with 1315 successful fetches.

**NOTE (DF-0272 landmine):** SIOCAIFGROUP error returns (EEXIST/EINVAL) leak
`ifnet_mtx` (known DF-0272, unfixed in tree). Group names must not end in a
digit (`if.c:1155`) — our first attempt used "g3280" (EINVAL on every add →
permanent ifnet_mtx wedge). Single churn process per ifp+group avoids EEXIST.

## Fix

`fix.diff` — re-look up the group and recompute/verify the member count under
the re-acquired `ifgroup_lock` before filling (mirrors `if_getgroups`).
Validated: built as kernel #1 (Sep 10 03:02:42), anomalies gone, group
machinery churned 3M cycles error-free (`churnFIX.log`).
