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

NGM_SOURCE_GET_COUNTER dereferences msg->data without arglen check (latent: file is orphaned and non-compiling)

Field Value
ID DF-0602
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:N
CWE CWE-125 Out-of-bounds Read
File sys/netgraph7/ng_source.c
Lines 479-487
Area netgraph7 (ng_source packet-source node)
Confidence likely
Discovered 2026-07-02
Reported pending

⚠ Important caveat β€” file is currently orphaned and non-compiling

Same as DF-0601: sys/netgraph7/ng_source.c is not in sys/conf/files and has a compile error at line 742 (ifq undeclared; should be ifsq). The file is dead, non-compiling code; this finding is a latent-defect / hardening item.

Summary

The GET_COUNTER handler reads *(uint8_t *)msg->data before verifying that arglen is at least 1. Every other data-bearing command in this switch validates arglen exactly (START:388, SETPPS:421, SET_TIMESTAMP:436, SET_COUNTER:463, SETIFACE:409, GET_IFNAME:513). GET_COUNTER is the lone exception and will read 1 byte past the message buffer if a peer sends the command with arglen==0.

Root cause

sys/netgraph7/ng_source.c:479-487:

case NGM_SOURCE_GET_COUNTER:
    {
        uint8_t index = *(uint8_t *)msg->data;     /* line 481, no arglen check */
        struct ng_source_embed_cnt_info *embed;

        if (index >= NG_SOURCE_COUNTERS) {
            error = EINVAL;
            goto done;
        }
        ...

msg->data is the flexible-array member at the end of struct ng_mesg (ng_message.h:81). The buffer was allocated as kmalloc(sizeof(struct ng_mesg) + arglen, ...) via NG_MKMESSAGE (ng_message.h:394). With arglen==0, the data area is zero bytes; reading msg->data[0] reads 1 byte past the requested allocation. Because kmalloc bucketizes (sizeof(struct ng_mesg)==56 bytes β†’ 64-byte slab), the byte lands in allocator padding rather than unmapped memory, so this does not crash β€” but it is undefined behavior and the byte read is whatever the slab layer left there. The byte only influences the index check (and which sc->embed_counter[] slot is later returned); it is not echoed back to the caller, so there is no direct info leak.

Threat model & preconditions

  • Attacker position: privileged user (root, or any principal allowed to send NGM_SOURCE_COOKIE control messages) sending a raw binary NGM_SOURCE_GET_COUNTER with header.arglen = 0 via ng_socket.
  • Privileges gained or impact: negligible β€” 1 byte of kmalloc padding is read but not returned, and the value only steers which of 4 already-privileged counters is reported (or EINVAL). No crash, no leak, no escalation.
  • Required config or capabilities: a kernel with ng_source compiled in (currently impossible β€” file is orphaned/non-compiling).
  • Reachability: send a binary ng_mesg with header.typecookie=NGM_SOURCE_COOKIE, header.cmd=NGM_SOURCE_GET_COUNTER, header.arglen=0, zero data bytes.

Proof of concept

No PoC can be built today (file does not compile). After fixing the ifq→ifsq typo and adding to the build, the trigger is a single ngctl msg <node>: getcounter 0-style invocation with arglen=0 via raw ng_socket.

Impact

  • Blast radius: currently zero (file is orphaned/non-compiling).
  • Severity rationale: Info β€” hardening / latent-defect item. The OOB read is 1 byte of allocator padding, not returned to userspace, not influencing anything beyond which slot of an already-privileged array is returned.
  • Reliability: 100% once compiled in.

Add an arglen lower-bound check mirroring the pattern used by every other command in this switch:

--- a/sys/netgraph7/ng_source.c
+++ b/sys/netgraph7/ng_source.c
@@ -478,6 +478,11 @@ ng_source_rcvmsg(node_p node, item_p item, hook_p lasthook)
        case NGM_SOURCE_GET_COUNTER:
            {
            uint8_t index;
+           struct ng_source_embed_cnt_info *embed;
+
+           if (msg->header.arglen < sizeof(index)) {
+               error = EINVAL;
+               goto done;
+           }
            index = *(uint8_t *)msg->data;
            struct ng_source_embed_cnt_info *embed;

            if (index >= NG_SOURCE_COUNTERS) {

(A real patch should also drop the duplicate declaration of embed that already exists on the original line 482 β€” the diff above keeps the change surgical.)

Before doing this, also fix the ifq→ifsq typo at line 742 and add the file to sys/conf/files.

References

  • sys/netgraph7/ng_message.h:81, 394 β€” struct ng_mesg flexible-array data[] member and NG_MKMESSAGE allocation.
  • Every other command in ng_source_rcvmsg switch validates arglen exactly β€” GET_COUNTER is the lone exception.

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-0602 Β· 6 files
FileTypeDescriptionSize
README.md readme seeded: states no PoC possible (dead/non-compiling file) 355 B ↓ raw
VERDICT.md verdict full narrative: latent defect confirmed by source trace; dead/orphaned/non-compiling; fix.diff authored; not_testable 8.2 KB ↓ raw
fix.diff suggested-fix git-apply-able arglen lower-bound check for NGM_SOURCE_GET_COUNTER (mirrors sibling commands); matches finding proposal 512 B view raw
env.txt environment uname, cc version, kldload ng_source failure, ls /boot/kernel/ng_source* 372 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 seeded: states no PoC possible (dead/non-compiling file)
↓ download raw

DF-0602 β€” 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.

See findings/DF-0602-ng7-source-get-counter-arglen.md for the analysis and the fix diff.

VERDICT.md verdict full narrative: latent defect confirmed by source trace; dead/orphaned/non-compiling; fix.diff authored; not_testable
↓ download raw

DF-0602 β€” VERDICT

Verdict: NOT REPRODUCED (latent code defect in dead/orphaned/non-compiling file)

The cited code defect is real and confirmed by source-level trace, but it is unreachable on this guest (and on any standard DragonFlyBSD kernel configuration) because the containing file is dead, orphaned, non-compiling code. No runtime reproduction is possible. No escalation is possible. This is a latent-defect / hardening item β€” exactly as the finding markdown states.

Mechanism (confirmed by source trace)

sys/netgraph7/ng_source.c:479-487 β€” the NGM_SOURCE_GET_COUNTER handler:

case NGM_SOURCE_GET_COUNTER:
    {
        uint8_t index = *(uint8_t *)msg->data;     /* line 481: NO arglen check */
        struct ng_source_embed_cnt_info *embed;

        if (index >= NG_SOURCE_COUNTERS) {          /* line 484 */
            error = EINVAL;
            goto done;
        }
        ...

msg->data is the flexible-array member at the end of struct ng_mesg (sys/netgraph7/ng_message.h:81). The message buffer was allocated by the sender via NG_MKMESSAGE as kmalloc(sizeof(struct ng_mesg) + (len), ...) (ng_message.h:394); header.arglen is taken verbatim from the wire and is not re-validated by the receiver before the switch dispatches on header.cmd. With header.arglen == 0, the data[] area is zero bytes and *(uint8_t *)msg->data reads 1 byte past the requested allocation.

This is a genuine code defect because every other data-bearing command in the same switch validates arglen before touching msg->data:

Command Line Check
NGM_SOURCE_START 388 if (msg->header.arglen != sizeof(uint64_t))
NGM_SOURCE_SETIFACE 409 if (msg->header.arglen < 2)
NGM_SOURCE_SETPPS 421 if (msg->header.arglen != sizeof(uint32_t))
NGM_SOURCE_SET_TIMESTAMP 436 if (msg->header.arglen != sizeof(*embed))
NGM_SOURCE_SET_COUNTER 463 if (msg->header.arglen != sizeof(*embed))
NGM_ETHER_GET_IFNAME 513 if (msg->header.arglen < 2)
NGM_SOURCE_GET_COUNTER 481 (none β€” reads msg->data first)

GET_COUNTER is the lone exception.

Why it does NOT reproduce / is unreachable on this guest

The file is dead/orphaned/non-compiling. Verified facts (all confirmed on the with-src baseline, kernel 6.5-DEVELOPMENT #0):

  1. Not in sys/conf/files β€” grep -c ng_source /usr/src/sys/conf/files β‡’ 0. There is no netgraph7_source (or similar) option that would pull it in. The file is therefore never compiled into the static kernel.
  2. Not in sys/netgraph7/Makefile SUBDIR β€” grep -c ng_source .../Makefile β‡’ 0. It is never built as a loadable KLD module.
  3. No standalone module Makefile β€” sys/netgraph7/ng_source.c has no sibling Makefile, unlike echo/, pppoe/, eiface/, etc.
  4. Compile error at line 743 β€” ifq->ifq_maxlen / ifq->ifq_len reference an undeclared identifier (the local is ifsq). The file would not compile even if added to the build.
  5. Not present as a loadable module on the running guest β€” ls /boot/kernel/ng_source* β‡’ "No such file or directory"; kldload -n ng_source β‡’ "can't load ng_source: No such file or directory" (rc=1).
  6. NETGRAPH7 is not in X86_64_GENERIC β€” grep -in netgraph sys/config/X86_64_GENERIC β‡’ no matches; the whole netgraph7 subsystem is opt-in via options NETGRAPH7_* and is off on the default kernel.

Because the vulnerable code can never execute on this guest (or any standard DFBSD kernel), there is no runtime to reproduce against. This is identical in root cause and impact to the sibling finding DF-0601 (same file, same dead-code classification, already recorded not_reproduced).

Impact assessment (latent, if the file were ever compiled in)

  • The OOB read is 1 byte of kmalloc slab padding (sizeof(struct ng_mesg) == 56 β†’ 64-byte slab; with arglen==0 the read lands in the 8-byte bucket padding, not unmapped memory, so it does not crash).
  • The read value only steers the subsequent index >= NG_SOURCE_COUNTERS bounds check (i.e. which of 4 already-privileged counter slots is returned, or EINVAL). It is not echoed back to the sender β€” NG_MKRESPONSE copies from sc->embed_counter[index], not from msg->data.
  • No info leak, no crash, no privilege change. Negligible latent impact.
  • This is correctly classified Info severity.

No escalation (Phase 6 β€” N/A)

This is an OOB-read in dead code. There is no live primitive to characterize, no slab bucket to groom, no victim object to corrupt, no pointer to redirect. Phase 6 does not apply.

Add an arglen lower-bound check mirroring the pattern of every sibling command, placed before the msg->data dereference. The check uses < sizeof(index) (matching SETIFACE/GET_IFNAME's lower-bound style rather than exact-equality, since GET_COUNTER consumes only 1 byte and should tolerate a slightly over-long message):

@@ -478,9 +478,15 @@
            }
        case NGM_SOURCE_GET_COUNTER:
            {
-           uint8_t index = *(uint8_t *)msg->data;
+           uint8_t index;
            struct ng_source_embed_cnt_info *embed;

+           if (msg->header.arglen < sizeof(index)) {
+               error = EINVAL;
+               goto done;
+           }
+           index = *(uint8_t *)msg->data;
+
            if (index >= NG_SOURCE_COUNTERS) {

This matches the finding markdown's ## Recommended fix proposal (same placement, same EINVAL/goto done semantics, same sizeof(index) lower bound). The finding's diff also notes the duplicate embed declaration; my version keeps the single existing declaration and is surgical.

Fix validation: not_testable

A kernel build + runtime test of this single-fix diff is not possible on this guest, for reasons entirely outside this finding's scope:

  1. The file is not in sys/conf/files, so make nativekernel never compiles it β€” patching the arglen check has zero effect on the produced kernel.
  2. The file has an unrelated compile error at line 743 (ifq undeclared) that must be fixed first before the file can compile at all.
  3. Adding the file to conf/files and fixing the line-743 typo are prerequisites that belong to a separate "revive netgraph7/ng_source" effort, not to this single-bug fix.

What WAS validated: - patch -p1 --dry-run --forward < fix.diff β‡’ succeeds ("Hunk #1 succeeded at 478") on both the host read-only sys/ tree and the in-guest /usr/src tree. - Applied to in-guest /usr/src, the patched region reads exactly as intended (arglen check precedes the dereference). - Reverted cleanly (patch -R), leaving the source pristine for the with-src reset.

Because the bug is in dead code and cannot be exercised at runtime on any standard kernel, fix_status = "not_testable" is the honest classification (per the rubric: "PoC can't run on guest at all (latent/remote/missing module); you validated the diff applies + compiles [logic] only, and traced that it closes the code path").

PoC changes

None. The finding markdown and seeded README.md correctly state that no PoC can be built today (file does not compile). I confirmed this and added no trigger source β€” there is nothing to trigger. The only artifact authored is fix.diff (the verified arglen-check patch).

References (confirmed during verification)

  • sys/netgraph7/ng_source.c:479-487 β€” vulnerable GET_COUNTER handler (no arglen check before msg->data deref).
  • sys/netgraph7/ng_source.c:388,409,421,436,463,513 β€” every sibling command validates arglen first (the contrast that proves the defect).
  • sys/netgraph7/ng_source.c:743 β€” unrelated ifq typo that prevents compilation.
  • sys/netgraph7/ng_message.h:69-81 β€” struct ng_mesg with flexible-array data[].
  • sys/netgraph7/ng_message.h:392-407 β€” NG_MKMESSAGE allocates sizeof(struct ng_mesg) + len (so len==0 β‡’ zero-byte data[]).
  • sys/conf/files β€” ng_source absent (grep β‡’ 0).
  • sys/netgraph7/Makefile β€” ng_source absent from SUBDIR (grep β‡’ 0).
  • sys/config/X86_64_GENERIC β€” no NETGRAPH7 options.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

not_testable: fix.diff patch --dry-run rc=0. Cannot build (file not in conf/files + :743 error).

patch -p1 --dry-run applies. Source-level: arglen check precedes deref.
↓ fix.diffn/a -- dead code

Confirmed kernel references

Detail

Exploit chain

none -- dead code, valid hard blocker.

Evidence (decisive lines)

Source: :481 no arglen check, :388/409/421/436/463/513 all validate. grep conf/files: 0. kldload ng_source: No such file.

PoC changes

Authored: fix.diff (add arglen check before deref), VERDICT.md, manifest.json.

Verified recommended fix

Add if(msg->header.arglen < sizeof(index)){error=EINVAL;goto done;} at ng_source.c:481 before msg->data deref. Mirrors siblings. Matches finding proposal. Full diff in findings/poc/DF-0602/fix.diff.

Verdict

NOT REPRODUCED -- dead code (same file as DF-0601). NGM_SOURCE_GET_COUNTER ng_source.c:481 reads (uint8_t)msg->data without arglen check (all 6 sibling commands validate). Real defect but unreachable: not in conf/files, :743 compile error, no ng_source.ko, NETGRAPH7 not in GENERIC.