Per-link stats unreachable for links 1..63: linkNum == 0 typo in GET/CLR_STATS link selection
Summary
NGM_ONE2MANY_GET_STATS/CLR_STATS/GETCLR_STATS reject every many-link index except 0 with EINVAL because the selection predicate is 'linkNum == 0 && linkNum < NG_ONE2MANY_MAX_LINKS' instead of '>= 0' (the ng7 twin has >= 0). Statistics for links 1..63 can never be read or cleared even while the links are connected and passing traffic; monitoring silently fails. Fail-closed: no OOB access. VERIFIED live on the guest (linkNum:1 -> EINVAL while 0 and -1 succeed). Root-only control surface. Also killed for v1: DF-0676 structurally ABSENT (v1 has ROUNDROBIN only, rcvdata consumes mbuf exactly once); DF-3539 unserialized-race pattern hunted live and KILLED (13-minute race, 6.49M rcvdata injections vs 58,824 concurrent kfree(priv) with same-thread slab re-spray, zero panics; div0 compiled out - cached divisor; teardown ordering defuses SHUTDOWN-vs-rcvdata).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3543 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| statsbug.c | β | 4.9 KB | view raw | |
| race.c | β | 7.9 KB | view raw | |
| race2.c | β | 6.6 KB | view raw | |
| race3.c | β | 7.2 KB | view raw | |
| race4.c | β | 8.1 KB | view raw | |
| disasm.txt | β | 3.4 KB | view raw | |
| race-attempt.log | β | 580 B | view raw | |
| run.log | β | 290 B | view raw | |
| env.txt | β | 426 B | view raw | |
| fix.diff | β | 409 B | view raw | |
| build.sh | β | 111 B | view raw | |
| run.sh | β | 118 B | view raw | |
| VERDICT.md | β | 4.4 KB | β raw |
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
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:
-
No XMIT_ALL in v1.
sys/netgraph/one2many/ng_one2many.h:62defines onlyNG_ONE2MANY_XMIT_ROUNDROBIN, andng_one2many_rcvmsgrejects every otherxmitAlg(ng_one2many.c:285-291). The ng7 DF-0676 peeked-mbuf double-free class is structurally absent: v1rcvdataconsumes the mbuf exactly once throughNG_SEND_DATA(ng_one2many.c:425). -
Divide-by-zero candidate is dead on the shipped build.
rcvdatachecksnumActiveMany != 0at:411and computes% priv->numActiveManyat:416; a concurrentng_one2many_update_many(:490, fromNGM_SET_CONFIG:308,newhook:253, ordisconnect:468β all executed synchronously in the sender's thread persys/netgraph/netgraph/ng_base.c:1207and:1678, with no lock) could zero it in between in C terms. Butobjdumpof the shippedng_one2many.ko(disasm.txt) shows the compiler loadsnumActiveManyonce at+0x87and re-uses the cached%dxas theidivdivisor at+0xc1β the checked value and the divisor are the same register, so% 0cannot execute on this kernel. (Compiler-dependent UB in the abstract machine; not a bug on the shipped artifact.) -
SHUTDOWN-vs-rcvdata UAF is defused by teardown ordering.
ng_one2many_rmnode(:433-445) βng_cutlinksβng_destroy_hooksetsHK_INVALIDon both hook ends before freeing them (ng_base.c:819-827), the disconnect callbacks NULLpriv->many[i].hook/priv->one.hookand zeronumActiveManybeforekfree(priv)runs, andng_send_datahandles both the NULL-hook andHK_INVALIDcases safely (ng_base.c:1684-1696). On DragonFly, user-thread syscalls are non-preemptive with respect to other user threads, so a writer that enteredrcvdataon CPU A always finishes its ~1-2Β΅s critical section before the closer's ~10Β΅s+ teardown on CPU B reacheskfree(priv). A 13-minute live attempt (race4.c,race-attempt.log) drove 6.49M packets intong_one2many_rcvdataagainst 58,824 concurrentNGM_SHUTDOWNexecutions ofkfree(priv)with same-thread slab re-spray (0x42-filled zone-4096/64 allocations) andping -finterrupt 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.
Fix verification
not_testableOne-line predicate fix (== 0 -> >= 0); kernel rebuild validation skipped per contract for Info-severity findings.
fix.diff
Confirmed kernel references
- sys/netgraph/one2many/ng_one2many.c:339
- sys/netgraph/one2many/ng_one2many.c:411
- sys/netgraph/one2many/ng_one2many.c:416
- sys/netgraph/one2many/ng_one2many.c:490
- sys/netgraph/one2many/ng_one2many.h:62
- sys/netgraph/netgraph/ng_base.c:1207
- sys/netgraph/netgraph/ng_base.c:1678
- sys/netgraph/netgraph/ng_base.c:819
- sys/netgraph7/one2many/ng_one2many.c:335
Detail
Evidence (decisive lines)
run.log (linkNum=1 -> EINVAL while connected), statsbug.c, disasm.txt (cached idiv divisor at +0x87/+0xc1), race-attempt.log (final counters), VERDICT.md sections 2-3
PoC changes
Wrote statsbug.c from scratch (v1 ng_mesg layout, absolute node addresses need trailing ':'); race*.c written from scratch for the UAF hunt (same-thread slab re-spray design in race4).
Verified recommended fix
Change linkNum == 0 to linkNum >= 0 in the GET/CLR_STATS link selection (ng_one2many.c:339).
Verdict
Reproduced live: NGM_ONE2MANY_GET_STATS/CLR_STATS/GETCLR_STATS reject linkNum 1..63 with EINVAL (linkNum==0 typo at sys/netgraph/one2many/ng_one2many.c:339-340; ng7 twin uses >=0). Fail-closed functional bug, no memory-safety consequence. The pass-2 race hunt (DF-3539 pattern) was executed and is negative: no XMIT_ALL exists in v1 (DF-0676 class absent), the numActiveMany divide-by-zero candidate is compiled out on the shipped kernel (objdump: divisor cached from the checked load), and the SHUTDOWN-vs-rcvdata priv UAF is defused by teardown ordering (HK_INVALID before free, hooks NULLed before kfree(priv), NULL/invalid hooks handled in ng_send_data) plus non-preemptive user syscalls - 6.49M rcvdata injections against 58824 concurrent kfree(priv) executions with slab re-spray produced no panic.
No comments yet.