Unprivileged kernel heap over-read via non-NUL-terminated sg_data in ng_connect_data
Summary
ng_connect_data(:752-778): casts nam to sockaddr_ng, passes sap->sg_data to ng_address_path() as C string(:775) WITHOUT verifying NUL-termination within sa_len bytes. ng_bind(:824) checks sg_data[sg_len-3]!=\0 but connect omits this check. Data sockets require NO privilege (ngd_attach:387 no caps_priv_check). Unprivileged user calls connect(2) on AF_NETGRAPH SOCK_DGRAM with sockaddr sg_len bytes no NUL. Syscall layer allocates exactly sa_len bytes M_SONAME no M_ZERO. ng_address_path strlcpy/strcmp scans past allocation into adjacent slab. Crosses page boundary -> panic DoS. Minor info side-channel. Fix: replicate ng_bind validation sg_len>=3 sg_data[sg_len-3]==\0.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0536 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df0536.c | trigger-source | unprivileged AF_NETGRAPH data-socket connect with non-NUL-terminated sockaddr | 4.9 KB | view raw |
| build.sh | build-script | cc -O2 -o df0536 df0536.c | 100 B | view raw |
| run.sh | run-script | unprivileged invocation | 160 B | view raw |
| run.log | run-log | baseline run (pristine v4 module): connect ENXIO, OOB read fired | 1.3 KB | view raw |
| fix_run.log | run-log | fixed-module run: connect EINVAL, OOB path closed | 706 B | view raw |
| env.txt | environment | uname, cc, ng_socket module, netgraph abi=2 | 402 B | view raw |
| fix.diff | suggested-fix | NUL-termination check in ng_connect_data (netgraph7 path, as cited) | 846 B | view raw |
| fix.v4.diff | suggested-fix | equivalent fix in the SHIPPED netgraph v4 module; BUILD-VALIDATED | 739 B | view raw |
| VERDICT.md | verdict | full narrative: mechanism, primitive, fix validation | 3.6 KB | β raw |
| README.md | readme | this evidence pack index | 1.6 KB | β 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 |
DF-0536 β Unprivileged kernel heap over-read via non-NUL-terminated sockaddr_ng
Verdict: REPRODUCED (unprivileged heap over-read). Fix VALIDATED.
Bug
ng_connect_data() (cited sys/netgraph7/socket/ng_socket.c:752-778; same bug
in the shipped sys/netgraph/socket/ng_socket.c:644-673) passes sap->sg_data
to the path resolver without the NUL-termination check that ng_bind()
performs. The resolver does strncpy(fullpath, address, NG_PATHSIZ-1) which
scans up to ~511 bytes past the sa_len-byte M_SONAME allocation β a kernel
heap over-read.
Data sockets require no privilege (ngd_attach has no caps_priv_check),
so an unprivileged user can trigger this with connect(2).
Run
- Precondition (admin, stock modules):
kldload netgraph; kldload ng_socket - Build:
cc -O2 -o df0536 df0536.c - Run (as unpriv user):
./df0536
Result
- Baseline (pristine shipped module):
connectβerrno=6 (ENXIO)β thestrncpyover-read fires before the path lookup. Bug exercised. - Fixed module:
connectβerrno=22 (EINVAL)βng_connect_datarejects the non-NUL-terminated sockaddr before calling the resolver. OOB path closed.
Impact
Read-only primitive β heap info leak (no write). A bounded 511-byte read into
contiguous slab pages does not reliably panic, so realistic impact is a silent
over-read / minor info side-channel, not a reliable DoS. No uid=0 escalation
(read-only primitive is a valid hard blocker for an escalation chain).
See VERDICT.md for the full line-cited trace and fix.diff / fix.v4.diff
for the patch (the v4 variant was build-validated on this guest).
DF-0536 β Unprivileged kernel heap over-read via non-NUL-terminated sockaddr_ng in ng_connect_data
Verdict: REPRODUCED (unprivileged heap over-read); fix VALIDATED.
File: sys/netgraph7/socket/ng_socket.c (cited) β and the SAME bug exists in the actually-shipped sys/netgraph/socket/ng_socket.c (netgraph "v4"), which is the module DragonFly loads by default (/boot/kernel/ng_socket.ko, abi=2).
MECHANISM
-
An unprivileged user opens a netgraph DATA socket: socket(AF_NETGRAPH, SOCK_DGRAM, NG_DATA) / NG_DATA = 1 / ngd_attach (ng_socket.c) has NO caps_priv_check β only the CONTROL socket (ngc_attach, protocol=NG_CONTROL=2) requires SYSCAP_RESTRICTEDROOT.
-
connect(2) on that data socket -> ngd_connect -> ng_connect_data(nam, pcbp) [v7: ng_socket.c:752-778 ; v4: ng_socket.c:644-673].
-
ng_connect_data casts
namto sockaddr_ng and passes sap->sg_data straight to the path resolver: v7: ng_address_path(NULL, item, sap->sg_data, 0) [:775] v4: ng_path2node(NULL, sap->sg_data, &farnode, NULL) [:658] WITHOUT verifying that sg_data is NUL-terminated within sa_len bytes. ng_bind DOES check this (v7 :822-824 / v4 :718:sg_len < 3 || sg_data[sg_len-3]!='\0'); ng_connect_data omits the check. -
The resolver copies the address into a stack buffer with an unbounded string op: ng_path2noderef / ng_path2node: strncpy(fullpath, address, sizeof(fullpath) - 1); / NG_PATHSIZ-1 = 511 / (sys/netgraph7/netgraph/ng_base.c:1706 ; sys/netgraph/netgraph/ng_base.c:1093) The syscall layer (getsockaddr, kern/uipc_syscalls.c:1523) allocates EXACTLY sa_len bytes (M_SONAME, no M_ZERO), so a short non-NUL-terminated sg_data makes strncpy scan up to ~508 bytes past the allocation into adjacent kernel heap.
PRIMITIVE / IMPACT
- Class: kernel heap OVER-READ (read-only), unprivileged.
- Demonstrated: as uid=1001 (maxx, not in wheel), connect() reaches the strncpy OOB read (returns ENXIO "Device not configured" because the leaked-into path does not name a real node, but the read already happened). An 8000-iteration spray did not panic (kernel heap is contiguous; the 511-byte bounded read stays inside mapped slab pages), so the realistic, demonstrable impact is a SILENT heap over-read / minor info side-channel, not a reliable DoS on this layout.
- NO escalation to uid=0: the primitive is read-only (strncpy writes only into
the 512-byte on-stack
fullpath, bounded). Read-only primitive = valid hard blocker for an escalation chain (Phase 6). Impact ceiling = heap info leak.
PRECONDITION (realistic)
ng_socket is a STOCK loadable module shipped with the default install
(/boot/kernel/ng_socket.ko). It is not compiled into X86_64_GENERIC, so the
admin enables the subsystem with kldload netgraph; kldload ng_socket β a
realistic, default-shipped subsystem enablement (analogous to loading ipfw or
nfsserver), NOT a custom module. The TRIGGER (connect) is fully unprivileged.
This is an acceptable documented precondition, not a circular one.
FIX (VALIDATED)
Mirror ng_bind()'s NUL-termination check in ng_connect_data, before the resolver call. Authoritative fix.diff targets the cited netgraph7 path; the SAME fix was applied to the shipped v4 module and BUILD-VALIDATED on this guest:
baseline (pristine v4 module): connect -> errno=6 (ENXIO) [strncpy OOB fired] fixed (v4 module + fix): connect -> errno=22 (EINVAL) [check rejects first]
The fix closes the OOB path (the kernel returns EINVAL before ng_path2node runs).
Fix verification
fixedvalidated
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
REPRODUCED (live unpriv). ng_connect_data non-NUL sg_data -> strncpy OOB heap read. Unprivileged data socket. Module fix: NUL check.
No comments yet.