β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-0601

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_source compiled 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:

  1. 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.
  2. From many threads (or via an interface receiving traffic) blast distinct mbufs into the input hook concurrently.
  3. Within seconds-to-minutes observe either kernel: m_free: m_nextpkt not NULL style panics, follow mbuf chain panics, or Bad link in mbuf chain from the ifqueue traversal in ng_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.

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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0601 Β· 8 files
FileTypeDescriptionSize
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
README.md readme original finding README (no PoC β€” file is dead/non-compiling)
↓ download 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.

VERDICT.md verdict exhaustive dead-code proof + static analysis verification of all 4 claims
↓ download raw

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_testable
baseline no→ patch + rebuild →patched clean

not_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).
↓ fix.diffn/a -- dead code, file not in build system

Confirmed kernel references

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.