Kernel stack overflow via user-controlled VLA in ngdread() and ngdwrite()
Summary
ngdread and ngdwrite declare stack VLA whose size taken directly from user-supplied uio->uio_resid (size_t). Kernel thread stack only 16KB. read()/write() of more than few KB causes stack pointer overrun into guard page. char buffer[uio->uio_resid+1] at :509 and char buffer[uio->uio_resid] at :562. No upper bound before VLA expands stack frame. LWKT_THREAD_STACK=16384 bytes. int len=uio->uio_resid at :563 narrows 64-bit size_t to int. /dev/ngdN mode 0600 root:root.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2571 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trigger.c | trigger-source | minimal PoC: open /dev/ngd0, large read/write to overflow VLA | 1.9 KB | view raw |
| build.sh | build-script | cc -o trigger trigger.c | 157 B | view raw |
| run.sh | run-script | checks for /dev/ngd0, runs trigger if present | 1.3 KB | view raw |
| fix.diff | suggested-fix | VLA -> kmalloc + bounds check (defense-in-depth for orphaned file) | 2.6 KB | view raw |
| VERDICT.md | verdict | full reachability analysis: dead code, cannot compile, already-fixed in netgraph7 | 5.5 KB | β raw |
| README.md | readme | summary + build/run | 1.5 KB | β raw |
| module_build_failure.txt | build-log | proof the orphaned source fails to compile (removed cdevsw API) | 1.4 KB | view raw |
| env.txt | environment | uname, cc, kldstat, conf/files, GENERIC option check | 539 B | view raw |
| build.log | build-log | trigger build output (success) | 65 B | view raw |
| run.log | run-log | trigger run output (/dev/ngd0 does not exist on default kernel) | 385 B | view raw |
DF-2571 β ng_device stack VLA overflow
Summary
Verdict: NOT REPRODUCED β the cited vulnerable file (sys/netgraph/ng_device.c)
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
maintained equivalent (sys/netgraph7/ng_device.c) already eliminated the VLA
bug. The finding's "unprivileged user" impact claim is a false positive.
See VERDICT.md for the full line-by-line reachability trace.
Files
| File | Description |
|---|---|
trigger.c |
Minimal PoC: opens /dev/ngd0, does a large read()/write() to overflow the VLA |
build.sh |
Builds trigger (cc -o trigger trigger.c) |
run.sh |
Runs the trigger (checks for /dev/ngd0 first) |
fix.diff |
git-apply-able defense-in-depth fix: VLA β kmalloc + bounds check |
VERDICT.md |
Full analysis: why the bug is real in source but unreachable |
module_build_failure.txt |
Proof the orphaned source cannot compile |
env.txt |
Guest environment (uname, cc, kldstat, conf/files) |
build.log / run.log |
Build and run output |
manifest.json |
Machine-readable catalog |
Build & Run
./build.sh # cc -o trigger trigger.c
./run.sh # checks for /dev/ngd0; on default kernel: "does NOT exist"
Expected result on default kernel
/dev/ngd0 does not exist; ng_device is not loaded; no effect. The orphaned
source cannot be compiled as a module (removed cdevsw API). See VERDICT.md.
DF-2571 β ng_device stack VLA overflow β VERDICT
Verdict: NOT REPRODUCED (unreachable dead code; impact claim is a false positive)
The VLA anti-pattern the finding describes is genuinely present in the cited
source text, but the cited 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 β "controllable by
an unprivileged user" β is therefore a false positive. The maintained
equivalent (sys/netgraph7/ng_device.c) already eliminated the bug.
What the finding claims
sys/netgraph/ng_device.c ngdread/ngdwrite declare a stack VLA whose size
comes directly from user-supplied uio->uio_resid:
/* ngdread β sys/netgraph/ng_device.c:509 */
char buffer[uio->uio_resid+1];
/* ngdwrite β sys/netgraph/ng_device.c:562 */
char buffer[uio->uio_resid];
uio->uio_resid is a size_t taken from the count argument to read()/write().
The DragonFly lwkt thread stack is 16 KB (UPAGES(4) * PAGE_SIZE(4096) =
LWKT_THREAD_STACK = 16384, sys/cpu/x86_64/include/param.h:126,
sys/sys/thread.h:472). A read/write of more than ~16 KB would make the VLA
overrun the stack into the guard page β kernel panic (double fault). The VLA
pattern is real 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 for netgraph7/ng_device.c β a different file.) |
Module Makefile (sys/netgraph/Makefile) |
No device SUBDIR. There is no sys/netgraph/device/ directory. |
Kernel config (sys/config/X86_64_GENERIC) |
No netgraph7_device / ng_device option. |
| Compiled into running kernel | No. nm /boot/kernel/kernel.debug \| grep ng_device = empty. |
| Loadable module on disk | No. kldload ng_device β "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:
ng_device.c:111: error: unknown type name 'd_close_t' ng_device.c:112: error: unknown type name 'd_open_t' ng_device.c:113: error: unknown type name 'd_read_t' ng_device.c:114: error: unknown type name 'd_write_t' ng_device.c:119: error: variable 'ngd_cdevsw' has initializer but incomplete type ng_device.c:132: error: 'nommap' undeclared here ng_device.c:133: error: 'nostrategy' undeclared here ng_device.c:286: warning: implicit declaration of function 'make_dev' ng_device.c:419: error: 'ngdopen' redeclared as different kind of symbol ng_device.c:506: error: 'ngdread' redeclared as different kind of symbol ... Stop.
(Full output in module_build_failure.txt.) 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 is already fixed
sys/netgraph7/ng_device.c β the file actually referenced in conf/files
(as optional netgraph7_device) β uses the modern struct cdev/dev_ops API
and already eliminated both VLAs:
/* ngdread β sys/netgraph7/ng_device.c:432-433 β uses mbufs, no VLA */
while (m && uio->uio_resid > 0 && error == 0) {
len = MIN(uio->uio_resid, m->m_len);
error = uiomove(mtod(m, void *), len, uio);
/* ngdwrite β sys/netgraph7/ng_device.c:463-464 β bounds check, no VLA */
if (uio->uio_resid > IP_MAXPACKET)
return (EIO);
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
/dev/ngdN is created by make_dev(&ngd_cdevsw, unit, UID=0, GID=0, MODE=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.
Conclusion β which step-4 category
(d) Genuinely not reachable on this kernel (primary) + partial (a) false positive (the "unprivileged user" impact claim is wrong on every axis): - The vulnerable file is not in any build path. - It cannot compile against the current kernel API. - The maintained equivalent is already fixed. - The character device is mode 0600 (root-only).
The VLA is a real code-quality / defense-in-depth defect in the orphaned source
text, so fix.diff is provided (replaces both VLAs with kmalloc + size
bounds). Because the code is not part of any buildable kernel, the fix cannot be
built/validated on the guest β fix_status: not_testable.
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 (removed cdevsw API).
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 VLAβkmalloc+bounds transformation matching the pattern
already used in the fixed netgraph7/ng_device.c.
Fix verification
not_testablenot_testable: the cited file sys/netgraph/ng_device.c is orphaned dead code that cannot be compiled into any kernel or module (entire cdevsw/d_*_t/make_dev API removed β 19 compile errors unrelated to the VLA). No kernel image into which fix.diff can be built. fix.diff applies cleanly (git apply --check rc=0) and is a correct VLA->kmalloc+bounds transformation matching the already-fixed netgraph7 pattern, but cannot be build-validated because the surrounding file does not compile.
baseline: cannot reproduce β cited code dead/unreachable (not in kernel, not loadable, /dev/ngd0 absent). module build attempt: 19 compile errors (removed cdevsw API). fix.diff: git apply --check rc=0. Maintained netgraph7/ng_device.c:463 already implements the bound.
Confirmed kernel references
Detail
Exploit chain
none β not a memory-corruption primitive on this kernel. Cited code is unreachable dead code (not compiled, cannot compile, device root-only).
Evidence (decisive lines)
module build failure (orphaned source cannot compile): ng_device.c:111 error unknown type 'd_close_t' / :119 incomplete cdevsw type / :132 'nommap' undeclared / :506 'ngdread' redeclared / Stop. trigger run as maxx: /dev/ngd0 does NOT exist, RUN_EXIT=0. kldstat: only kernel+ehci+xhci, no ng_device. conf/files:1699: netgraph7/ng_device.c optional netgraph7_device (NOT the cited file). netgraph7 ngdwrite:463 already bounds uio_resid>IP_MAXPACKET.
PoC changes
Authored trigger.c (minimal PoC: open /dev/ngd0 + large read/write), build.sh, run.sh, VERDICT.md, README.md, manifest.json, fix.diff. Dir was empty.
Verified recommended fix
Defense-in-depth fix.diff replaces both VLAs in sys/netgraph/ng_device.c with kmalloc + size bounds (ngdread caps uio_resid to NGD_QUEUE_SIZE, ngdwrite caps to IP_MAXPACKET), matching the already-fixed netgraph7 pattern. BUT the real recommendation is to DELETE the orphaned file (it cannot compile). fix.diff applies cleanly (git apply --check rc=0).
Verdict
NOT REPRODUCED (unreachable dead code). The VLA anti-pattern is genuinely present β sys/netgraph/ng_device.c:509 (char buffer[uio->uio_resid+1] in ngdread) and :562 (char buffer[uio->uio_resid] in ngdwrite), sized by user-controlled size_t on a 16KB stack. BUT the cited file is orphaned dead code with NO build path: no entry in sys/conf/files (the only ng_device entry, conf/files:1699, is for netgraph7/ng_device.c β a DIFFERENT file), no module directory, not in X86_64_GENERIC. Building it as a kld FAILS HARD β the entire struct cdevsw/d_*_t/cdevsw_add/make_dev character-device API it targets was removed (19 compile errors). The maintained sys/netgraph7/ng_device.c ALREADY eliminated both VLAs (ngdread:432 uses mbufs len=MIN(uio_resid,m_len); ngdwrite:463 bounds uio_resid>IP_MAXPACKET). Even if reachable, /dev/ngdN is make_dev mode 0600 root-only, so the trigger requires root. The 'unprivileged user' impact claim is a false positive on every axis.
No comments yet.