# DF-3543 — ng_one2many (v1) per-link stats linkNum validation bug

## Verdict: REPRODUCED (functional; no memory-safety impact)

`sys/netgraph/one2many/ng_one2many.c:339-340` gates the
`NGM_ONE2MANY_GET_STATS / CLR_STATS / GETCLR_STATS` link selection on

```c
else if (linkNum == 0
    && linkNum < NG_ONE2MANY_MAX_LINKS) {
```

`linkNum == 0` should be `linkNum >= 0` (the ng7 twin at
`sys/netgraph7/one2many/ng_one2many.c:335` has `linkNum >= 0`).
Consequence: statistics for "many" links 1..63 can never be read or
cleared — every query returns `EINVAL` even though the link is connected
and passing traffic.  The "one" link (-1) still works.

Reproduced live on the guest (v1 stack, `kldload ng_one2many`):

```
GET_STATS linkNum=0  -> Undefined error: 0 (expected: OK)
GET_STATS linkNum=1  -> Invalid argument (expected: OK, got rejection => BUG)
GET_STATS linkNum=-1 -> Undefined error: 0 (expected: OK for 'one' link)
```

(see `run.log`; `statsbug.c` builds a node with `many0` **and** `many1`
connected, then queries both.)

Impact: functional/availability of monitoring only.  The bug is
*fail-closed* — it rejects requests rather than accepting out-of-range
indices, so there is no OOB consequence.  Severity Info.

## Pass-2 race hunt around this file (negative results, documented)

The orchestrator asked specifically whether the DF-3539 (v1 ng_async
unserialized rcvmsg-vs-rcvdata) pattern manifests here.  It does not,
for three verified reasons:

1. **No XMIT_ALL in v1.**  `sys/netgraph/one2many/ng_one2many.h:62`
   defines only `NG_ONE2MANY_XMIT_ROUNDROBIN`, and
   `ng_one2many_rcvmsg` rejects every other `xmitAlg`
   (`ng_one2many.c:285-291`).  The ng7 DF-0676 peeked-mbuf double-free
   class is structurally absent: v1 `rcvdata` consumes the mbuf exactly
   once through `NG_SEND_DATA` (`ng_one2many.c:425`).

2. **Divide-by-zero candidate is dead on the shipped build.**
   `rcvdata` checks `numActiveMany != 0` at `:411` and computes
   `% priv->numActiveMany` at `:416`; a concurrent
   `ng_one2many_update_many` (`:490`, from `NGM_SET_CONFIG` `:308`,
   `newhook` `:253`, or `disconnect` `:468` — all executed synchronously
   in the sender's thread per `sys/netgraph/netgraph/ng_base.c:1207`
   and `:1678`, with no lock) could zero it in between *in C terms*.
   But `objdump` of the shipped `ng_one2many.ko` (`disasm.txt`) shows
   the compiler loads `numActiveMany` once at `+0x87` and re-uses the
   cached `%dx` as the `idiv` divisor at `+0xc1` — the checked value and
   the divisor are the same register, so `% 0` cannot execute on this
   kernel.  (Compiler-dependent UB in the abstract machine; not a bug
   on the shipped artifact.)

3. **SHUTDOWN-vs-rcvdata UAF is defused by teardown ordering.**
   `ng_one2many_rmnode` (`:433-445`) → `ng_cutlinks` →
   `ng_destroy_hook` sets `HK_INVALID` on both hook ends *before*
   freeing them (`ng_base.c:819-827`), the disconnect callbacks NULL
   `priv->many[i].hook` / `priv->one.hook` and zero `numActiveMany`
   *before* `kfree(priv)` runs, and `ng_send_data` handles both the
   NULL-hook and `HK_INVALID` cases safely (`ng_base.c:1684-1696`).
   On DragonFly, user-thread syscalls are non-preemptive with respect
   to other user threads, so a writer that entered `rcvdata` on CPU A
   always finishes its ~1-2µs critical section before the closer's
   ~10µs+ teardown on CPU B reaches `kfree(priv)`.  A 13-minute
   live attempt (`race4.c`, `race-attempt.log`) drove **6.49M** packets
   into `ng_one2many_rcvdata` against **58,824** concurrent
   `NGM_SHUTDOWN` executions of `kfree(priv)` with same-thread slab
   re-spray (0x42-filled zone-4096/64 allocations) and `ping -f`
   interrupt pressure: no panic, no guest instability, ssh alive
   throughout.  The C-level race exists, but no reachable bad outcome
   on this kernel was demonstrable — DF-3539 remains the umbrella
   finding for the v1-framework class and is not re-reported here.

## Fix

One-line: `linkNum == 0` → `linkNum >= 0` (`fix.diff`).  Not
kernel-rebuild-validated (Info-severity finding; the change is
self-evident and the live demo already proves both branches of the
predicate).

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