# DF-3280 VERDICT — REPRODUCED (stale-pointer read via SIOCGIFGMEMB; impact: leak-class, panic potential)

## Bottom line

The use-after-free walk in `if_getgroupmembers()` is real and was reproduced
on the stock INVARIANTS guest **three times** (3 independent baseline runs:
2, 9, and 3 stale reads). An unprivileged user running the two-phase
SIOCGIFGMEMB protocol against a group being created/destroyed by a
privileged churner receives **data read through a dangling `ifg_group`
pointer** — observed as successful member fetches whose contents are *not*
the real membership (only possible member is "lo0"; the kernel returned
empty/foreign entries, i.e. heap contents of a freed-and-recreated object).
The `fix.diff` (re-lookup + recount under the re-acquired lock) was built
into a full kernel and **eliminated every anomaly** (0/1315 fetches) while
3M+ group add/del cycles continued error-free.

## How it manifests (mechanism, path-accurate)

1. Reader's phase-2 ioctl enters `if_getgroupmembers()`; finds `ifg` for
   "rgrpA" and counts `len=16` under LK_SHARED (`sys/net/if.c:1322-1335`).
2. Lock released (`if.c:1337`); `kmalloc(16)` runs (`if.c:1346`).
3. Churner's `SIOCDIFGROUP(lo0)` runs `if_delgroup_locked()`: removes the
   member, `ifg_refcnt--` → 0 (`if.c:1231,1234`) → `if_destroygroup()` →
   `kfree(ifg)` (`if.c:1139`).
4. Reader re-acquires LK_SHARED (`if.c:1350`) and walks `ifg->ifg_members`
   through the **stale pointer** (`if.c:1352-1363`) — freed memory; whatever
   the chunk now contains (typically a freshly recreated group object in the
   same 48-byte M_IFNET bucket, mid-creation with an empty member list) is
   copied out at `if.c:1366`. Reader sees `member[0]=''`.

Evidence: `run.log` (6 ANOMALY), `run.2.log` (3 ANOMALY), `run.3.log`
(3 ANOMALY) — `grep ANOMALY`. `churn3.log` shows the mutator side healthy
(2.5M cycles, err=0).

## Why impact is "leak", not uid0_privesc — hard blockers, documented

* **No unprivileged mutator exists.** Every group-destroy path
  (SIOCDIFGROUP at `if.c:2396`, `if_detach()` teardown at `if.c:993`) is
  behind `SYSCAP_NONET_IFCONFIG`/device privilege. The unprivileged attacker
  can only be the reader; the race requires a concurrent privileged actor.
* **Type-stable reuse caps the primitive.** `sizeof(struct ifg_group)`
  (~48B) has its own M_IFNET kmalloc bucket, populated almost exclusively by
  other `ifg_group` objects, so the stale walk overwhelmingly lands on
  valid (recreated) group objects — wrong data out, no fault. 45-160s of
  hammering (runs 1-3, ~10^7 ioctl cycles) produced **no panic**.
* Escalation would require controlling the 48-byte bucket contents with
  attacker-chosen pointer values before the reader's µs-scale window closes
  — no such allocation primitive is reachable from the unprivileged side.

## Fix validation (mandatory — memory-corruption class)

* fix.diff applied to guest `/usr/src/sys/net/if.c`; `make nativekernel
  KERNCONF=X86_64_GENERIC` (BUILD-EXIT=0, 1556s, `kbuild.log`) +
  `make installkernel`; rebooted into
  `DragonFly 6.5-DEVELOPMENT #1: Thu Sep 10 03:02:42 UTC 2026`.
* Exact PoC re-run (`run.fix.log`, `churnFIX.log`): **anom=0** over 1997
  iterations / 1315 successful fetches / 110s; churn 3.0M adds+dels, err=0.
  Baseline bad behavior GONE; EINVAL count rose (15→44 baseline, 119 fixed)
  exactly as expected — the fix converts stale-walk cases into EINVAL.
* Guest reset clean afterwards (`vm.sh reset with-src`).

## Guest observations worth recording

* Known finding **DF-0272 reproduces trivially** on the stock kernel: one
  SIOCAIFGROUP that returns an error (e.g. group name ending in a digit →
  EINVAL at `if.c:1155`, or EEXIST) returns at `if.c:2389/2391` holding
  `ifnet_mtx` → next network ioctl on the box blocks forever in D-state
  (wchan `ifnet`). Observed twice during PoC bring-up (processes wedged;
  `ps` showed `D0 ifnet`). Not re-reported here (known), but the fix status
  note stands: **DF-0272 unfixed in tree and trivially reachable.**
* DF-0273 (SIOCSIFDESCR→SIOCSIFFLAGS fall-through) and DF-0274
  (SIOCADDMULTI sa_len trust; note `sa_equal()` at `sys/sys/socket.h:247`
  bcmps `a1->sa_len` bytes — same family) also unfixed in tree.

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