ng_source_rcvdata races unsynchronized on snd_queue (latent: file is orphaned and non-compiling)
| Field | Value |
|---|---|
| ID | DF-0601 |
| Status | new |
| Severity | Info |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L |
| CWE | CWE-362 Concurrent Execution using Shared Resource with Improper Synchronization (Race Condition) |
| File | sys/netgraph7/ng_source.c |
| Lines | 270-284 (constructor), 545-570 (rcvdata), 666-678 (clr_data) |
| Area | netgraph7 (ng_source packet-source node) |
| Confidence | likely |
| Discovered | 2026-07-02 |
| Reported | pending |
β Important caveat β file is currently orphaned and non-compiling
sys/netgraph7/ng_source.c is NOT listed in sys/conf/files (verified:
grep -rn ng_source sys/conf/ returns nothing). Additionally, line 743
does not compile: it references the undeclared identifier ifq when the
only local in scope is ifsq (assigned on lines 740-741):
738: if (sc->output_ifp != NULL) {
739: struct ifaltq_subqueue *ifsq =
740: ifq_get_subq_default(&sc->output_ifp->if_snd);
741:
742: packets = ifq->ifq_maxlen - ifq->ifq_len; /* BUG: ifq undeclared */
743: } else
The file is therefore dead, non-compiling code. Both findings below are
latent and would only become reachable if a maintainer fixes the typo and
adds netgraph7/ng_source.c to the build. They are filed as Info
(hardening / latent-defect) items to prevent regression when the file is
re-animated.
Summary
ng_source_constructor does not call NG_NODE_FORCE_WRITER, so the
netgraph7 framework (ng_base.c:2994, 1840-1846) dispatches NGQF_DATA
items as readers via lwkt_gettoken_shared β which permits multiple CPUs
to enter ng_source_rcvdata concurrently. That handler mutates
sc->snd_queue, sc->queueOctets, and sc->last_packet with the
unlocked _IF_ENQUEUE macro and plain assignments, so concurrent
senders on the input hook can corrupt the ifqueue m_nextpkt/tail linkage
and tear counters.
Root cause
ng_source_constructor (ng_source.c:269-284) sets only
sc->snd_queue.ifq_maxlen and never calls NG_NODE_FORCE_WRITER(node);
compare ng_nat.c:304, ng_bridge.c:329, ng_pred1.c:196 which all do.
ng_source_rcvdata (ng_source.c:545-570) is therefore invoked as a
reader; on the input-hook path it executes:
565: _IF_ENQUEUE(&sc->snd_queue, m);
566: sc->queueOctets += m->m_pkthdr.len;
567: sc->last_packet = m;
with no lock. _IF_ENQUEUE is the unlocked variant β it does
if (ifq->ifq_tail == NULL) ifq->ifq_head = m; else
ifq->ifq_tail->m_nextpkt = m; ifq->ifq_tail = m; and is not atomic.
Two concurrent rcvdata invocations from different CPUs can therefore both
read ifq_tail==NULL (losing one packet's linkage) or both write
->m_nextpkt of the same tail (producing a corrupt/broken chain).
The same snd_queue is later dequeued by ng_source_send via _IF_DEQUEUE
in the callout/writer context, so a corrupted chain yields either a panic
(m_nextpkt pointing into garbage) or a lost/double-freed mbuf.
clr_data (ng_source.c:666-678) is a writer and so is serialized, but it
cannot prevent two readers from racing with each other in the window before
it acquires the writer token.
Threat model & preconditions
- Attacker position: privileged user (root, or whoever can open the ng_socket control node and construct netgraph topologies).
- Privileges gained or impact: local kernel memory corruption β panic, and (with the right slab layout) potentially UAF/double-free of an mbuf.
- Required config or capabilities: a kernel with netgraph7 and
ng_sourcecompiled in (currently impossible β the file is orphaned and non-compiling). The node wired up downstream of a node that fans out across CPUs (multi-queue ether ingress, tee, hub). - Reachability: load many templates / pump packets into the input hook from many threads.
Proof of concept
No PoC can be built today β the file does not compile. If a maintainer
fixes the ifqβifsq typo at line 742 and adds
netgraph7/ng_source.c optional netgraph7_source to sys/conf/files, the
PoC would be:
- As root:
ngctl mkpeer source input output; connect the source's output to an ng_ether/ng_eiface; connect a tee/hub or a ksocket on the input side so that two or more CPUs can pump packets in. - From many threads (or via an interface receiving traffic) blast distinct mbufs into the input hook concurrently.
- Within seconds-to-minutes observe either
kernel: m_free: m_nextpkt not NULLstyle panics,follow mbuf chainpanics, orBad link in mbuf chainfrom the ifqueue traversal inng_source_send.
Impact
- Blast radius: currently zero (file is orphaned/non-compiling). If re-animated: any SMP DragonFly system using ng_source with multi-CPU packet fan-in.
- Severity rationale: Info β hardening / latent-defect item. No demonstrated impact in the current tree because the file does not build.
- Reliability: currently zero. If re-animated, race reliability depends on CPU affinity / packet fan-out.
Recommended fix
Force single-threaded execution for the node by adding
NG_NODE_FORCE_WRITER in the constructor. This matches the established
pattern in netgraph7 (ng_nat.c:304, ng_bridge.c:329, ng_pred1.c:196,
ng_deflate.c:183) and matches this node's own assumption of serialization
(it touches snd_queue, queueOctets, last_packet,
embed_counter[].next_val, and stats without any internal locks).
--- a/sys/netgraph7/ng_source.c
+++ b/sys/netgraph7/ng_source.c
@@ -276,6 +276,8 @@ ng_source_constructor(node_p node)
NG_NODE_SET_PRIVATE(node, sc);
sc->node = node;
sc->snd_queue.ifq_maxlen = 2048; /* XXX not checked */
ng_callout_init(&sc->intr_ch);
+
+ NG_NODE_FORCE_WRITER(node);
return (0);
}
(Strictly, the embedded-counter next_val is also mutated from the callout
(ng_source_mod_counter at ng_source.c:852) and read/written from rcvmsg
SET_COUNTER (ng_source.c:474) β both are writers under FORCE_WRITER, so
the change closes that race too.)
Before doing this, also fix the ifqβifsq typo at line 742 and add
the file to sys/conf/files β otherwise the FORCE_WRITER addition has no
effect because the file doesn't compile.
References
sys/netgraph7/netgraph/ng_base.c:2994, 1840-1846β the WRITER/READER dispatch model andlwkt_gettoken_shared.sys/netgraph7/ng_nat.c:304,ng_bridge.c:329,ng_pred1.c:196,ng_deflate.c:183β sibling nodes that correctly callNG_NODE_FORCE_WRITER.
Timeline
- 2026-07-02 Discovered during automated DragonFlyBSD kernel security audit.
- 2026-07-02 Reported to DragonFlyBSD security contact (pending) as a latent-defect item (file is currently orphaned/non-compiling).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0601 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | readme | original finding README (no PoC β file is dead/non-compiling) | 602 B | β raw |
| VERDICT.md | verdict | exhaustive dead-code proof + static analysis verification of all 4 claims | 7.1 KB | β raw |
| build.sh | build-script | no-op build (no PoC binary β dead code) | 321 B | view raw |
| run.sh | run-script | no-op run (no PoC β dead code) | 471 B | view raw |
| fix.diff | suggested-fix | add NG_NODE_FORCE_WRITER(node) to constructor β hardening fix for dead code; passes git apply --check | 294 B | view raw |
| env.txt | environment | guest uname, kldstat, netgraph7 load-failure proof, 0 ng7 symbols in kernel | 883 B | view raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-0601 β PoC: (none β file is orphaned/non-compiling)
No PoC can be built today. sys/netgraph7/ng_source.c is not listed in
sys/conf/files and has a compile error at line 742 (ifq undeclared;
should be ifsq). The file is dead, non-compiling code; the race defect
is latent and would only become reachable if a maintainer fixes the typo
and adds the file to the build.
See findings/DF-0601-ng7-source-rcvdata-no-force-writer-race.md for the
analysis and the fix diff. The PoC sketch in the finding markdown covers
the steps that would reproduce the race once the file is re-animated.
DF-0601 β VERDICT: NOT REPRODUCED (latent: file is dead, orphaned, non-compiling code)
Verdict
NOT REPRODUCED β latent hardening item. The race condition described in the
finding is statically correct (every source-level claim verified below), but
the file sys/netgraph7/ng_source.c is dead, orphaned, and non-compiling
code that cannot be reached on any currently-buildable DragonFlyBSD kernel
configuration. There is no triggerable primitive, no panic, no leak β the bug
is a latent defect that would only become live if a maintainer re-animates the
file (fixes the compile typo, adds it to conf/files, adds a NETGRAPH7_SOURCE
option, enables NETGRAPH7 in a kernel config). The finding is correctly filed
as Info severity.
Why it cannot reproduce β exhaustive dead-code proof
The finding's own caveat (lines 17-37 of the finding markdown) is fully confirmed by source tracing:
(1) ng_source.c is NOT in the build system
$ grep -rn ng_source sys/conf/ (empty β no match in any conf/ file) $ grep -n "netgraph7" sys/conf/files | grep source (empty β every other ng7 node IS listed, ng_source is conspicuously absent) $ grep -n "NETGRAPH7_SOURCE" sys/conf/options (empty β there is no such option; all ~50 other ng7 node types have one)
Even if a maintainer added options NETGRAPH7_SOURCE to a kernel config, the
build would not compile the file because conf/files has no
netgraph7/ng_source.c entry.
(2) The file has a hard compile error at line 743
Inside ng_source_intr (ng_source.c:726), the only local in scope at line 743
is ifsq (declared at lines 740-741 via ifq_get_subq_default). Line 743
references the undeclared identifier ifq:
739: if (sc->output_ifp != NULL) {
740: struct ifaltq_subqueue *ifsq =
741: ifq_get_subq_default(&sc->output_ifp->if_snd);
742:
743: packets = ifq->ifq_maxlen - ifq->ifq_len; /* BUG: ifq undeclared */
744: } else
This is a guaranteed error: 'ifq' undeclared compile failure. The file
cannot be compiled as-is.
(3) The default kernel does not enable NETGRAPH7 at all
$ grep -in netgraph sys/config/X86_64_GENERIC (empty)
The default X86_64_GENERIC kernel has no options NETGRAPH7 line, so
netgraph7/netgraph/ng_base.c (the framework dispatcher, listed as
optional netgraph7) is not compiled into the kernel. The guest confirms:
nm /boot/kernel/kernel | grep netgraph7 returns nothing, and
kldload netgraph7 fails with "No such file or directory".
(4) No ng_source module exists on the guest
guest$ ls /boot/kernel/ | grep source (empty)
guest$ find sys/ -name "*ng_source*" β only sys/netgraph7/ng_source.{c,h}
Neither old netgraph (sys/netgraph/) nor netgraph7 has a loadable/compiled
ng_source on this guest.
Static analysis β all 4 source claims verified (the bug IS real if re-animated)
Despite being dead code, the race-condition observation is correct:
Claim 1: Constructor lacks NG_NODE_FORCE_WRITER β
ng_source_constructor (ng_source.c:269-284) sets sc->snd_queue.ifq_maxlen
and calls ng_callout_init, but never calls NG_NODE_FORCE_WRITER(node).
Compare sibling nodes that correctly force writer serialization:
| Node | Constructor line | Has FORCE_WRITER? |
|---|---|---|
ng_nat.c |
304 | β yes |
ng_bridge.c |
329 | β yes |
ng_pred1.c |
196 | β yes |
ng_deflate.c |
183 | β yes |
ng_mppc.c |
211 | β yes |
ng_hci_main.c |
151 | β yes |
ng_source.c |
284 | β NO |
Claim 2: rcvdata uses unlocked _IF_ENQUEUE β
ng_source_rcvdata (ng_source.c:545-570) mutates shared state with no lock:
565: _IF_ENQUEUE(&sc->snd_queue, m); /* unlocked macro */
566: sc->queueOctets += m->m_pkthdr.len; /* plain racy assignment */
567: sc->last_packet = m; /* plain racy assignment */
_IF_ENQUEUE is the unlocked variant β it does
if (ifq->ifq_tail == NULL) ifq->ifq_head = m; else ifq->ifq_tail->m_nextpkt = m;
ifq->ifq_tail = m; with no atomicity.
Claim 3: Framework dispatches data as readers without FORCE_WRITER β
The netgraph7 dispatch (ng_base.c:1993-1999) checks NGF_FORCE_WRITER:
1993: if (((item->el_flags & NGQF_RW) == NGQF_WRITER) ||
1994: (node->nd_flags & NGF_FORCE_WRITER) ||
1995: (hook && (hook->hk_flags & HK_FORCE_WRITER))) {
1996: ng_acquire_write(node); /* exclusive: lwkt_gettoken */
1997: } else {
1998: ng_acquire_read(node); /* shared: lwkt_gettoken_shared */
1999: }
Data items are created as readers by default (ng_base.c:2994:
item->el_flags |= NGQF_READER). Without NG_NODE_FORCE_WRITER, ng_source's
rcvdata runs under ng_acquire_read β lwkt_gettoken_shared
(ng_base.c:1845), which permits concurrent execution across CPUs.
Claim 4: _IF_ENQUEUE is non-atomic β race on snd_queue linkage β
Two concurrent rcvdata invocations can both read ifq_tail==NULL (losing one
packet's linkage) or both write ->m_nextpkt of the same tail (corrupt chain).
The same snd_queue is later dequeued by ng_source_send/ng_source_intr
via _IF_DEQUEUE (lines 671, 789), so a corrupted chain yields panic or
double-free.
Classification
This is Phase 4 case (d): genuinely not reachable on this kernel β and stronger: not reachable on ANY buildable kernel because the file is not in the build system and has a pre-existing compile error. This is a valid latent defect / hardening item (Info severity), exactly as the finding is filed.
Exploit chain
none β this is a latent race condition in dead code. There is no triggerable primitive, so no escalation chain is possible. This is the valid hard-blocker case: "The vulnerable code path is dead/unreachable at runtime on this guest AND no harness can exercise it" (a race in a netgraph dispatch handler cannot be isolated to a unit-test harness; it requires the full netgraph framework + multi-CPU packet fan-in).
PoC changes
none β no PoC can be built (the finding's README correctly states this).
No source files were added or modified. Only fix.diff, VERDICT.md,
build.sh, run.sh, env.txt, and manifest.json were added to the evidence
pack.
Fix
fix.diff adds NG_NODE_FORCE_WRITER(node) to the constructor β the minimal,
targeted hardening fix matching every sibling netgraph7 node. This is a
defense-in-depth measure for dead code; it cannot be build-validated
because the file is not in conf/files and has the pre-existing ifq typo at
line 743.
fix_status: not_testable β the file is dead code (not in the build system,
has a compile error, NETGRAPH7 not in GENERIC), so building a single-fix kernel
and re-running a PoC is impossible. The diff passes git apply --check
(rc=0) and matches the finding's recommended fix exactly.
Before this fix has any effect, a maintainer must also:
1. Fix the ifqβifsq typo at ng_source.c:743.
2. Add netgraph7/ng_source.c optional netgraph7_source to sys/conf/files.
3. Add NETGRAPH7_SOURCE opt_netgraph.h to sys/conf/options.
4. Enable options NETGRAPH7 + options NETGRAPH7_SOURCE in a kernel config.
Fix verification
not_testablenot_testable: fix.diff git apply --check rc=0 but file not compiled in any kernel. Defense-in-depth only.
git apply --check rc=0. No kernel build possible (file not in conf/files).
Confirmed kernel references
- sys/netgraph7/ng_source.c:269
- sys/netgraph7/ng_source.c:280
- sys/netgraph7/ng_source.c:545
- sys/netgraph7/ng_source.c:565
- sys/netgraph7/ng_source.c:567
- sys/netgraph7/ng_source.c:743
- sys/netgraph7/netgraph/ng_base.c:1993
- sys/netgraph7/netgraph/ng_base.c:1845
- sys/netgraph7/ng_nat.c:304
- sys/netgraph7/bridge/ng_bridge.c:329
- sys/conf/files
- sys/conf/options:280
Detail
Exploit chain
none -- dead code, valid hard blocker.
Evidence (decisive lines)
grep ng_source sys/conf/ = empty. nm kernel: 0 netgraph7. kldload netgraph7: No such file. :743 ifq undeclared compile error.
PoC changes
Authored: fix.diff (NG_NODE_FORCE_WRITER in constructor), VERDICT.md, manifest.json.
Verified recommended fix
Add NG_NODE_FORCE_WRITER(node) to ng_source_constructor matching sibling nodes. Also needs :743 ifq->ifsq fix + conf/files + conf/options wiring. Full diff in findings/poc/DF-0601/fix.diff.
Verdict
NOT REPRODUCED -- dead/orphaned code. ng_source.c not in sys/conf/files, no NETGRAPH7_SOURCE option, compile error at :743 (ifq undeclared), NETGRAPH7 not in GENERIC. Source claims all correct (missing FORCE_WRITER, unlocked snd_queue). Latent Info hardening.
No comments yet.