# DF-3281 — unlocked `ifg_refcnt++` + duplicate-capable group creation in `if_addgroup()`

**File:** `sys/net/if.c` (pass 2)
**Severity:** Low · **CWE:** CWE-362 / CWE-105 · **Bucket:** memcorrupt (latent UAF/double-free)

## Bug

`if_addgroup()` (`sys/net/if.c:1149-1193`):

1. Membership/group scans run under `LK_SHARED` (`if.c:1160-1174`), then the
   lock is **released** (`if.c:1174`).
2. `ifg->ifg_refcnt++` (`if.c:1183`) executes **outside any lock** — a
   non-atomic read-modify-write on a refcount that
   `if_delgroup_locked()` decrements **under** `LK_EXCLUSIVE` (`if.c:1231`)
   — asymmetric protection, classic lost-update.
3. The find-or-create decision (group absent at `if.c:1169-1172` → call
   `if_creategroup`) is also made outside exclusive locking, so two
   concurrent creators can both see "absent" and **create duplicate
   same-named groups**; `if_creategroup()` inserts without a duplicate check
   (`if.c:1116-1118`).

Lost update ⇒ refcount below real membership ⇒ `if_delgroup_locked()` drops
it to 0 while members remain ⇒ `if_destroygroup()` kfrees a group with live
`ifgl`/`ifgm` references ⇒ stale walks / list corruption / double-free on
subsequent deletes.

## Why it could NOT be reproduced from userland (honest negative)

`ifioctl()` holds the **global `ifnet_mtx` across the entire ioctl body**
(`if.c:2029` … `if.c:2450`), so any two SIOCAIFGROUP/SIOCDIFGROUP calls are
fully serialized and can never overlap inside `if_addgroup()`. The unlocked
increment can only race:

* `if_attach()`'s `if_addgroup(ifp, IFG_ALL)` (`if.c:558`) — runs without
  `ifnet_mtx` (taken later at `if.c:689`); concurrent driver attaches on
  different CPUs can overlap, and
* `if_detach()`'s group teardown, which runs **after** `if_detach releases
  `ifnet_mtx` (`if.c:991`) under `ifgroup_lock` only (`if.c:993-996`) —
  i.e. interface destroy/hot-unplug racing a concurrent SIOCAIFGROUP.

Both partners are privileged/kernel-side. PoC `addgrp_race.c` ran two
per-interface racers (lo0, vtnet0) for 45s — ~1.9M add/del cycles each,
**0 anomalous ioctl returns** (`run.log`), guest healthy. (A per-ifp racer
design is mandatory: any benign EEXIST/ENOENT error return leaks `ifnet_mtx`
via the known DF-0272 bug and wedges the guest.)

## Fix

`fix.diff` — `if_creategroup_locked()` helper + `if_addgroup()` restructured
to perform membership scan, group lookup, creation, `ifg_refcnt++`, and both
list insertions atomically under one `LK_EXCLUSIVE` section (allocations
hoisted before the lock; events invoked after release; EEXIST path frees its
allocations). The fix kernel (built together with DF-3280's fix, kernel #1
Sep 10 03:02:42) compiled clean and churned 3M+ group add/del cycles with
zero errors (`../DF-3280/churnFIX.log`) — behavioral baseline for the race
itself does not exist, so fix_status is `not_testable`.
