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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0536 Β· 12 files
FileTypeDescriptionSize
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
README.md readme this evidence pack index
↓ download 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) β€” the strncpy over-read fires before the path lookup. Bug exercised.
  • Fixed module: connect β†’ errno=22 (EINVAL) β€” ng_connect_data rejects 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).

VERDICT.md verdict full narrative: mechanism, primitive, fix validation
↓ download raw

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 nam to 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

fixed

validated

see evidence pack
↓ fix.diffn/a (module-level fix validated)

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.