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)
PoC verification
Evidence pack
findings/poc/DF-2570 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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.chas no entry insys/conf/files(the onlyng_deviceentry, atconf/files:1699, isnetgraph7/ng_device.cβ a different file). Not insys/config/X86_64_GENERIC. - Not in the running kernel:
nm /boot/kernel/kernel.debug | grep -c ng_device= 0. - Not loadable: no
ng_device.koon disk; the orphaned source fails to compile against modern kernel headers (removedcdevsw/d_*_t/make_devAPI) β 52 error lines, seemodule_build_failure.txt. - Maintained version has NO analogous bug:
sys/netgraph7/ng_device.cng_device_rcvdata(259-287) enqueues the mbuf whole via_IF_ENQUEUE; there is nokmalloc(...M_DEVBUF), nobuffer = mtod, nokfree(...M_DEVBUF)anywhere in the function. - Root-only even if live: the netgraph graph feeding
rcvdataand the/dev/ngdNdevice (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.
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:
- Memory leak. The
kmalloc'd buffer at line 363 is never freed β its pointer is overwritten at line 369 (buffer = mtod(m, char *)). OneM_DEVBUFallocation (bucket sized tom->m_len) leaks per received mbuf. - Wrong-zone free / heap corruption.
kfree(buffer, M_DEVBUF)at line 377 operates on a pointer that ism->m_data, an interior pointer into the mbuf zone, not anM_DEVBUFallocation. Freeing it throughM_DEVBUFcorrupts theM_DEVBUFslab 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_testablenot_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).
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).
No comments yet.