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

Heap corruption: kfree() of in-mbuf data pointer on every received packet in ng_device_rcvdata

Summary

ng_device_rcvdata allocates buffer kmalloc then immediately reassigns local pointer to mtod(m char*) (m->m_data inside mbuf zone). Then kfree(buffer M_DEVBUF) frees m->m_data through wrong allocator corrupting kernel heap. Originally kmalloced buffer leaked mbuf never m_freemed. m->m_data points into mbuf own m_dat (uma_zalloc mbuf zone) or cluster not kmalloc M_DEVBUF. kfree with M_DEVBUF on non-kmallocd pointer corrupts slab metadata. Triggered on first packet to hook.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2570 Β· 10 files
FileTypeDescriptionSize
trigger.c trigger-source documentation trigger describing the rcvdata kmalloc/mtod/kfree(M_DEVBUF) path and dead-code status 3.4 KB view raw
build.sh build-script cc -O2 -Wall -o trigger trigger.c 314 B view raw
run.sh run-script runs the documentation trigger + live reachability check 1.4 KB view raw
build.log build-log build output (BUILD_EXIT=0) 65 B view raw
run.log run-log run output (RUN_EXIT=0, nm count=0, no /dev/ngd*) 781 B view raw
env.txt environment uname, kern.version, cc, nm count=0, conf/files, netgraph7 bug-absence grep 1.1 KB view raw
module_build_failure.txt build-log proof the orphaned sys/netgraph/ng_device.c cannot compile (52 error lines, removed cdevsw API) 4.0 KB view raw
fix.diff suggested-fix defense-in-depth: remove kmalloc/buffer/kfree(M_DEVBUF) dance, copy straight from mtod(m); git apply --check rc=0 1.2 KB view raw
VERDICT.md verdict full narrative: bug present in dead source text but unreachable; maintained netgraph7 has no analogous bug 8.5 KB ↓ raw
README.md readme summary + reproduce instructions 2.8 KB ↓ raw
README.md readme summary + reproduce instructions
↓ download raw

DF-2570 β€” ng_device_rcvdata kmalloc/mtod/kfree(M_DEVBUF) heap corruption

Claim (severity: High)

sys/netgraph/ng_device.c ng_device_rcvdata (lines 334-380) allocates a buffer via kmalloc(..., M_DEVBUF, ...) (line 363), immediately reassigns the local pointer to mtod(m, char *) (line 369) β€” leaking the kmalloc and now pointing into the mbuf zone β€” then kfree(buffer, M_DEVBUF) (line 377) frees m->m_data through the wrong zone (M_DEVBUF). Result: (1) memory leak of the kmalloc'd buffer per received mbuf; (2) M_DEVBUF slab freelist corruption plus a double-freeable mbuf when the mbuf is later freed through its own zone.

Verdict: NOT REPRODUCED β€” orphaned dead code (false positive on live axes)

The defect is genuinely present in the cited source text but the file is orphaned dead code, identical in status to sibling findings DF-2571 and DF-2572 (same file):

  • Not built: sys/netgraph/ng_device.c has no entry in sys/conf/files (the only ng_device entry, at conf/files:1699, is netgraph7/ng_device.c β€” a different file). Not in sys/config/X86_64_GENERIC.
  • Not in the running kernel: nm /boot/kernel/kernel.debug | grep -c ng_device = 0.
  • Not loadable: no ng_device.ko on disk; the orphaned source fails to compile against modern kernel headers (removed cdevsw/d_*_t/make_dev API) β€” 52 error lines, see module_build_failure.txt.
  • Maintained version has NO analogous bug: sys/netgraph7/ng_device.c ng_device_rcvdata (259-287) enqueues the mbuf whole via _IF_ENQUEUE; there is no kmalloc(...M_DEVBUF), no buffer = mtod, no kfree(...M_DEVBUF) anywhere in the function.
  • Root-only even if live: the netgraph graph feeding rcvdata and the /dev/ngdN device (mode 0600) both require root.

See VERDICT.md for the full trace and module_build_failure.txt for the compile proof.

How to reproduce (the reachability check, not a live trigger)

./build.sh     # builds the documentation-only trigger
./run.sh       # reports the dead-code status (nm count, conf/files, /dev/ngd*)

The trigger documents the in-kernel path it would take if the module were live (send mbuf data to an ng_device node's hook). It cannot exercise live code on the default kernel.

Files

  • trigger.c β€” documentation trigger (path it would take + dead-code status).
  • build.sh / run.sh β€” build/run the documentation trigger.
  • build.log / run.log β€” captured output.
  • env.txt β€” guest environment + live reachability evidence.
  • module_build_failure.txt β€” proof the orphaned source cannot compile.
  • fix.diff β€” defense-in-depth fix (remove kmalloc/buffer/kfree dance, copy straight from mtod(m)). Applies cleanly; cannot be built into a live kernel.
  • VERDICT.md β€” full narrative.
  • manifest.json β€” artifact catalog.
VERDICT.md verdict full narrative: bug present in dead source text but unreachable; maintained netgraph7 has no analogous bug
↓ download raw

DF-2570 β€” ng_device_rcvdata kmalloc/mtod/kfree(M_DEVBUF) heap corruption β€” VERDICT

Verdict: NOT REPRODUCED (unreachable dead code; impact claim is a false positive)

The heap-corruption anti-pattern the finding describes is genuinely present in the cited source text of sys/netgraph/ng_device.c:334-380, but that file is orphaned dead code that is not compiled into any shipping DragonFlyBSD kernel or module and cannot even compile against the current kernel headers. The finding's impact claim β€” "heap memory corruption / double-freeable mbuf" β€” is therefore a false positive on every live axis: the code is unreachable, and the maintained equivalent (sys/netgraph7/ng_device.c) operates on mbufs directly and has no analogous bug. This is the same dead-code conclusion the sibling findings DF-2571 and DF-2572 reached for the same file.


What the finding claims

sys/netgraph/ng_device.c ng_device_rcvdata (the netgraph data-receive callback) allocates a scratch buffer, immediately discards the pointer, then frees the mbuf's data through the wrong allocator:

/* ng_device_rcvdata β€” sys/netgraph/ng_device.c:334-380 */
339:    char *buffer;
...
363:    buffer = kmalloc(sizeof(char)*m->m_len, M_DEVBUF, M_NOWAIT | M_ZERO);  /* alloc M_DEVBUF */
364:    if(buffer == NULL) { ... return(-1); }
369:    buffer = mtod(m, char *);     /* LEAKS the kmalloc'd buffer; now == m->m_data (MBUF zone) */
371:    if( (connection->loc+m->m_len) < NGD_QUEUE_SIZE)
372:            memcpy(connection->readq+connection->loc, buffer, m->m_len);
...
377:    kfree(buffer, M_DEVBUF);      /* FREES m->m_data through WRONG zone (M_DEVBUF) */

Two real defects in the source text:

  1. Memory leak. The kmalloc'd buffer at line 363 is never freed β€” its pointer is overwritten at line 369 (buffer = mtod(m, char *)). One M_DEVBUF allocation (bucket sized to m->m_len) leaks per received mbuf.
  2. Wrong-zone free / heap corruption. kfree(buffer, M_DEVBUF) at line 377 operates on a pointer that is m->m_data, an interior pointer into the mbuf zone, not an M_DEVBUF allocation. Freeing it through M_DEVBUF corrupts the M_DEVBUF slab freelist, and when the mbuf is subsequently released through its own zone the same memory is freed twice β†’ double-freeable mbuf. A single received byte suffices to trigger it.

The bug is genuinely present and would be dangerous IF the code were compiled and reachable.

Why it does NOT reproduce β€” full trace

(1) The cited file is dead code β€” no build path

Build path Status for sys/netgraph/ng_device.c
sys/conf/files No entry. The only ng_device entry, at conf/files:1699, is netgraph7/ng_device.c optional netgraph7_device β€” a different file.
Kernel config sys/config/X86_64_GENERIC No ng_device / netgraph7_device option (grep returns nothing).
Compiled into running kernel No. nm /boot/kernel/kernel.debug \| grep -c ng_device = 0 (re-confirmed this run).
Loadable module on disk No. /boot/kernel/ has no ng_device.ko / netgraph7_device.ko.
/dev/ngd* device nodes None (ls /dev/ngd* β†’ No such file or directory).

(2) The cited file cannot even compile

Building sys/netgraph/ng_device.c as a kld module (mirroring the ng_echo/ng_socket pattern) fails hard β€” the entire struct cdevsw / d_*_t / cdevsw_add / make_dev character-device API it targets was removed from DragonFly years ago. Freshly reproduced for this finding (full output in module_build_failure.txt, 52 error lines, MAKE_RC non-zero):

ng_device.c:111:8: error: unknown type name 'd_close_t'
ng_device.c:112:8: error: unknown type name 'd_open_t'
ng_device.c:113:8: error: unknown type name 'd_read_t'
ng_device.c:114:8: error: unknown type name 'd_write_t'
ng_device.c:119:15: error: variable 'ngd_cdevsw' has initializer but incomplete type
ng_device.c:132:25: error: 'nommap' undeclared here
ng_device.c:152:4: error: implicit declaration of function 'cdevsw_add'
ng_device.c:286:27: error: implicit declaration of function 'make_dev'
ng_device.c:419:1: error: 'ngdopen' redeclared as different kind of symbol
...
cc1: all warnings being treated as errors
Stop.

The file is a relic of the pre-netgraph7 era (FreeBSD 1.1.2.1, 2002) and has not tracked the kernel API.

(3) The maintained version does NOT have the bug

sys/netgraph7/ng_device.c β€” the file actually referenced in conf/files (as optional netgraph7_device) β€” has a completely different architecture that eliminates the bug class. Its ng_device_rcvdata (lines 259-287) operates on the mbuf directly and never touches M_DEVBUF:

/* ng_device_rcvdata β€” sys/netgraph7/ng_device.c:259-287 */
priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
struct mbuf *m;
NGI_GET_M(item, m);
NG_FREE_ITEM(item);

IF_LOCK(&priv->readq);
if (_IF_QFULL(&priv->readq)) {
        _IF_DROP(&priv->readq);
        IF_UNLOCK(&priv->readq);
        NG_FREE_M(m);
        return (ENOBUFS);
}
_IF_ENQUEUE(&priv->readq, m);          /* mbuf enqueued whole; no kmalloc/copy */
IF_UNLOCK(&priv->readq);
...
return (0);

There is no kmalloc(...M_DEVBUF), no buffer = mtod(...), no kfree(...M_DEVBUF) anywhere in the maintained rcvdata (grep returns empty). The mbuf is handed intact to the readq and later drained/freed through the mbuf zone by m_freem in ngdread (netgraph7/ng_device.c:440). The maintained version is also not enabled in X86_64_GENERIC (the option netgraph7_device is absent), so even the fixed version is not in the default kernel.

(4) Even if reachable, the device is root-only

The netgraph graph that feeds ng_device_rcvdata (creating the node, hooking it up, pushing data onto the hook) requires root to configure, and the /dev/ngdN character device is created by make_dev(&ngd_cdevsw, unit, 0, 0, 0600, "ngd%d", unit) (sys/netgraph/ng_device.c:286-287). Mode 0600 means only root can open() it. So even on a hypothetical kernel where this code were live, the bug is a root→kernel issue, not an unprivileged→kernel escalation. Per the Phase-6 hard-blocker list, a write reachable only from an already-root context is game-over by definition — there is no privilege boundary to cross. (The maintained netgraph7/ng_device.c likewise creates the device mode 0600.)


Conclusion β€” which step-4 category

(d) Genuinely not reachable on this kernel (primary) + partial (a) false positive (the "heap memory corruption / double-freeable mbuf" impact claim is wrong on every live axis): - The vulnerable file is not in any build path (conf/files references only netgraph7/ng_device.c at :1699; absent from X86_64_GENERIC). - It cannot compile against the current kernel API (52 error lines, removed cdevsw/make_dev API). - It is not in the running kernel (nm count = 0) and no module exists on disk. - The maintained equivalent has no kmalloc(M_DEVBUF)/mtod/kfree(M_DEVBUF) in its rcvdata — the bug class is eliminated there. - The character device is mode 0600 (root-only) in both the dead and maintained versions, so even a live instance would be root→kernel, not unprivileged.

Because the cited code cannot run, there is no memory corruption to reproduce and therefore no Phase-6 escalation chain to develop and no Phase-8 patched-kernel build to validate (there is no live kernel image into which fix.diff can be built to exercise the path). The wrong-zone-free defect is a real code-quality / defense-in-depth issue in the orphaned source text, so fix.diff is provided: it removes the pointless kmalloc/buffer/kfree dance and copies straight from mtod(m, char *) into readq, matching the design already used in the fixed netgraph7/ng_device.c. The real resolution, however, is to delete the orphaned file (it is fully superseded by netgraph7/ng_device.c).

Fix validation status

not_testable β€” the cited file (sys/netgraph/ng_device.c) is not compiled into the default kernel and cannot be built as a module (the cdevsw/make_dev API it uses was removed). There is therefore no kernel image into which fix.diff can be built to exercise the path. fix.diff is verified to apply cleanly (git apply --check rc=0) and is a correct minimal change: it deletes the leaked kmalloc, the buffer = mtod reassignment, and the wrong-zone kfree(buffer, M_DEVBUF), copying directly from the mbuf instead β€” the same data-flow pattern already used in the fixed netgraph7/ng_device.c.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

not_applicable (not reproduced / false-positive on live axes): cited file sys/netgraph/ng_device.c is not compiled into the default kernel and cannot be built as a module (removed cdevsw/make_dev API), so no kernel image into which fix.diff can be built. fix.diff verified to apply cleanly (git apply --check rc=0) and is a correct minimal change matching the design of maintained netgraph7/ng_device.c; bug class already absent there.

not_applicable β€” no before/after kernel comparison possible: cited code is dead (nm ng_device count=0; conf/files has no sys/netgraph/ng_device.c entry; module compile fails with 52 errors). fix.diff only verified to apply cleanly (APPLY_CHECK_OK).
↓ fix.diffnot applicable (no patched kernel built β€” cited file is dead code that cannot be compiled into any kernel image)

Confirmed kernel references

Detail

Exploit chain

none β€” no memory corruption to reproduce: cited code is unreachable (no build path, not in kernel, cannot compile, maintained netgraph7 has no analogous bug). No Phase-6 escalation chain to develop, no Phase-8 build to validate.

Evidence (decisive lines)

run.log: nm ng_device symbol count = 0; conf/files ng_device entries = 1 (netgraph7); /dev/ngd* does NOT exist. env: NM_COUNT=0, conf/files:1699='netgraph7/ng_device.c optional netgraph7_device', X86_64_GENERIC='(none)', bug present in dead source at 363/369/377, maintained netgraph7='(no analogous bug)'. module_build_failure.txt: 52 error lines 'unknown type d_close_t'/'implicit make_dev'/'cc1 all warnings as errors'/'Stop.'

PoC changes

Created full evidence pack under findings/poc/DF-2570/: trigger.c (documentation trigger + dead-code status), build.sh/run.sh, VERDICT.md, README.md, env.txt, module_build_failure.txt (freshly re-proven 52-line compile failure), manifest.json, fix.diff (defense-in-depth).

Verified recommended fix

Defense-in-depth (for dead code): in sys/netgraph/ng_device.c ng_device_rcvdata, delete the pointless kmalloc(M_DEVBUF)/NULL-check, the leaked 'buffer = mtod(m, char )' reassignment, and the wrong-zone kfree(buffer, M_DEVBUF); copy straight from mtod(m, char ) into readq instead. Matches design already used in maintained sys/netgraph7/ng_device.c. git apply --check rc=0. Real resolution: delete orphaned file entirely.

Verdict

NOT REPRODUCED (unreachable dead code; impact claim is a false positive on live axes). The wrong-zone-free anti-pattern is genuinely present in the cited source text of sys/netgraph/ng_device.c:363,369,377 (kmalloc(M_DEVBUF) -> buffer = mtod(m) leaks it -> kfree(buffer, M_DEVBUF) frees mbuf data through the wrong zone), but that file is orphaned dead code with no entry in sys/conf/files (only netgraph7/ng_device.c at :1699), absent from X86_64_GENERIC, not in the running kernel (nm count=0), and it cannot compile against modern headers (52 error lines: removed cdevsw/d_*_t/make_dev API). The maintained sys/netgraph7/ng_device.c rcvdata (259-287) enqueues the mbuf whole via _IF_ENQUEUE and has NO kmalloc(M_DEVBUF)/buffer=mtod/kfree(M_DEVBUF). Even if live, /dev/ngdN is mode 0600 root-only. Same dead-code conclusion as siblings DF-2571/DF-2572 (identical file).