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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2571 Β· 10 files
FileTypeDescriptionSize
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
README.md readme summary + build/run
↓ download 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.

VERDICT.md verdict full reachability analysis: dead code, cannot compile, already-fixed in netgraph7
↓ download raw

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

not_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.
↓ fix.diffn/a β€” no kernel/module built (cited code is dead, cannot compile)

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.