ngc_send does not validate ng_mesg length or arglen causing heap OOB read (FreeBSD CVE-2008-5736 never ported)
Summary
ng_socket.c:248-254 ngc_send linearizes user mbuf into xmsg=kmalloc(len+1) casts to struct ng_mesg* with NO check len>=sizeof(ng_mesg)(52) and NO check arglen<=len-sizeof(ng_mesg). :257 ng_send_msg passes to node. When path empty ng_path2node treats as here loops back. :737 ship_msg msglen=sizeof(ng_mesg)+msg->header.arglen trusts arglen. :738 m_devget(msg,msglen,...) reads msglen bytes from small buffer. arglen=20428 with 52-byte send = 20KB heap OOB read leaked to userspace via recvfrom. arglen=65535 crosses page = panic. FreeBSD fixed in svn r184036 (2008) as CVE-2008-5736 NEVER PORTED to DragonFly. Requires root control socket (SYSCAP_RESTRICTEDROOT). Impact: kernel heap info leak + DoS panic.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0860 · 19 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df0860.c | trigger-source | open ng control socket, lie header.arglen, recvfrom the OOB heap read | 6.6 KB | view raw |
| build.sh | build-script | cc -O2 -o df0860 df0860.c | 175 B | view raw |
| run.sh | run-script | kldload ng_socket + ./df0860 <leak_bytes> (root) | 612 B | view raw |
| README.md | readme | human-facing build/run/expected + impact | 1.9 KB | ↓ raw |
| VERDICT.md | verdict | full mechanism walkthrough + fix validation | 7.9 KB | ↓ raw |
| fix.diff | suggested-fix | validate header.arglen <= len-sizeof(ng_mesg) in ngc_send (CVE-2008-5736 port) | 1.1 KB | view raw |
| run.log | run-log | baseline leak #1 (arglen=128): 128-byte heap leak | 774 B | view raw |
| run.2.log | run-log | baseline leak #2 (arglen=128) | 774 B | view raw |
| run.3.log | run-log | baseline leak #3 (arglen=64) | 544 B | view raw |
| baseline_run.log | run-log | post-reset reconfirm of leak on unpatched #0 | 839 B | view raw |
| run_bigarglen.log | run-log | arglen=60000 -> 60KB OOB read (no panic on this slab layout) | 413 B | view raw |
| run_maxx_gate.log | run-log | EPERM as unprivileged maxx (privilege gate proof) | 206 B | view raw |
| fix_build.log | build-log | single-fix nativekernel build (rc=0) | 282 B | view raw |
| fix_run.log | run-log | patched kernel+module re-run: sendto EINVAL, no leak | 418 B | view raw |
| leak_sample.txt | leak-sample | leaked-byte summary across runs | 697 B | view raw |
| env.txt | environment | uname, cc version, module/sysctl state | 256 B | view raw |
| env_uname.txt | environment | kern.version of baseline | 59 B | view 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-0860 — ngc_send missing ng_mesg arglen validation -> heap OOB read
Root-triggerable kernel heap out-of-bounds read / information leak in the
netgraph control-socket send path (sys/netgraph/socket/ng_socket.c). This is
the DragonFlyBSD instance of FreeBSD CVE-2008-5736 (svn r184036, 2008),
which was never ported.
Build
cc -O2 -o df0860 df0860.c # compiles unprivileged
Run (ROOT only)
The trigger requires a netgraph control socket, which is gated by
caps_priv_check(SYSCAP_RESTRICTEDROOT) (ng_socket.c:172) — i.e. euid 0.
The netgraph socket module must also be loaded.
# as root: kldload ng_socket ./df0860 128 # leaks 128 bytes of heap past the 52-byte header ./df0860 64 # leaks 64 bytes LEAK_BYTES=2048 ./df0860 # or via env
As an unprivileged user the socket(2) call returns EPERM (or
EPROTONOSUPPORT if the module is not loaded) — see run_maxx_gate.log.
Expected
On the buggy kernel (6.5-DEVELOPMENT #0, root, module loaded):
sendto returned 52 recvfrom returned 180 bytes LEAK: kernel returned 128 bytes past our 52-byte header (arglen lied as 128): 0000: 00 00 00 00 01 00 00 00 0f 00 00 00 c0 43 16 4f 0010: 00 f8 ff ff d0 43 16 4f 00 f8 ff ff 72 63 6e 67 ... ^ kernel pointers + heap strings
On the fixed kernel (root): sendto failed: errno=22 (Invalid argument) —
the lying arglen is rejected before reaching ship_msg. Honest messages
(arglen matching actual data) still succeed.
Impact
Kernel heap information leak (canonical kernel pointers + residual allocation
contents disclosed to a root process) and, for large arglen, a
layout-dependent DoS panic when the OOB read crosses an unmapped page.
Not an unprivileged escalation — the trigger is root-only.
See VERDICT.md for the full mechanism walkthrough and fix.diff for the
validated one-line validation fix.
DF-0860 — ngc_send missing ng_mesg arglen validation -> heap OOB read
CVE reference: FreeBSD CVE-2008-5736 (svn r184036, 2008), never ported to DragonFlyBSD.
Severity: Medium (CVSS AV:L/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:L).
CWE: CWE-125 (OOB Read), CWE-200 (Information Exposure).
Verdict
REPRODUCED — root-triggerable kernel heap out-of-bounds read / information
leak via the netgraph control-socket send path, plus a layout-dependent DoS
panic when arglen is large. A targeted single-line validation fix closes it
(VALIDATED by building and booting a single-fix ng_socket.ko).
Privilege gate (why this is root-only, not unpriv→root)
Creating a netgraph control socket is gated by:
/* sys/netgraph/socket/ng_socket.c:172 */
if (caps_priv_check(ai->p_ucred,
SYSCAP_RESTRICTEDROOT | __SYSCAP_NULLCRED) != 0) {
error = EPERM;
}
SYSCAP_RESTRICTEDROOT (sys/sys/caps.h:132) requires cr_uid == 0
(sys/kern/kern_caps.c:330: "Uid must be 0 unless NOROOTTEST..."). The
audit's unprivileged user maxx (uid 1001, not in wheel) confirmed this
empirically:
$ id # uid=1001(maxx) gid=1001(maxx) groups=1001(maxx) $ socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL) socket(AF_NETGRAPH,SOCK_DGRAM,NG_CONTROL) failed: errno=1 (Operation not permitted) GATE: EPERM — netgraph control socket requires SYSCAP_RESTRICTEDROOT (euid 0).
(Module not loaded → EPROTONOSUPPORT; loaded → EPERM. Either way maxx is
blocked.) This is a valid Phase-6 hard blocker: the primitive is reachable
only from an already-root credential, so there is no privilege boundary to
cross — root→kernel is game-over by definition. There is therefore no
unpriv→root escalation chain to develop; the honest impact is a root→kernel
heap info-leak (and a DoS panic).
Mechanism (trigger → primitive → effect)
-
Root opens a netgraph control socket:
socket(PF_NETGRAPH, SOCK_DGRAM, NG_CONTROL). This creates a netgraphsocketnode and wiresngc_usrreqs.pru_send = ngc_send(ng_socket.c:944,955). -
Root sends a short datagram (only the 52-byte
ng_mesgheader, no data payload) but lies in the header:header.arglen = NwithN > 0. Destination address is".". -
ngc_send(ng_socket.c:201) linearizes the user mbuf chain:c /* ng_socket.c:248-254 */ for (len = 0, m0 = m; m0 != NULL; m0 = m0->m_next) len += m0->m_len; /* len = 52 */ xmsg = kmalloc(len + 1, M_NETGRAPH, M_WAITOK); /* 53-byte alloc */ m_copydata(m, 0, len, xmsg); /* copies 52 bytes */No check thatlen >= sizeof(struct ng_mesg)(52), and no check that((struct ng_mesg *)xmsg)->header.arglen <= len - sizeof(struct ng_mesg). -
ng_send_msg(node, (struct ng_mesg *)xmsg, path=".", &resp)(ng_socket.c:257) →ng_path2node(here, ".", &dest, ...)resolves.to the same node (empty segment →continue;ng_base.c:~1130), sodest == here. -
CALL_MSG_HANDLERdispatches to the socket node'sngs_rcvmsg(ng_socket.c:788). Because we settypecookieto neitherNGM_GENERIC_COOKIEnorNGM_SOCKET_COOKIE, it falls through theif (typecookie == NGM_SOCKET_COOKIE)branch and reaches:c /* ng_socket.c:836 */ error = ship_msg(pcbp, msg, addr); /* msg == our undersized xmsg */ -
ship_msg(ng_socket.c:729) trustsheader.arglen:c /* ng_socket.c:737-738 */ msglen = sizeof(struct ng_mesg) + msg->header.arglen; /* 52 + N */ mdata = m_devget(msg, msglen, 0, NULL); /* copies 52+N bytes */m_devgetreads52 + Nbytes starting at the 53-bytexmsgallocation. TheNbytes past offset 52 are whatever kernel heap bytes live past the allocation — an out-of-bounds read. The copied bytes are appended to the socket'sso_rcv(ssb_appendaddr,ng_socket.c:751). -
Root
recvfrom()s the queued mbuf and reads52 + Nbytes — the trailingNbeing leaked kernel heap.kfree(msg)atng_socket.c:742then frees the original buffer.
Effect: kernel heap information leak. Demonstrated leak of 128 / 64 / 60000
bytes past the header. Leaked bytes contain canonical kernel pointers
(0xfffff800_4f1643c0 etc.) and residual allocation strings
(rcng_automountd, disabled, /usr). For very large arglen the read
crosses into an unmapped page → kernel page fault → DoS panic (layout-dependent;
on this guest the slab neighbourhood happened to be a large mapped run, so a
60000-byte read completed without faulting, but the leak still occurred).
Exploit chain
Not applicable — this is an information leak / OOB read, and the trigger
is root-only (control-socket privilege gate, ng_socket.c:172). The
primitive is read-only (no attacker-controlled write through this path), and
root→kernel is game-over regardless. Per the Phase-6 hard-blocker rules, there
is no privilege boundary to cross and no escalation chain to develop. The
realistic impact ceiling is: a root process (or a confused-deputy root service
that proxies untrusted netgraph control messages) can disclose kernel heap
memory (defeating KASLR — though KASLR is off on this guest anyway — and
leaking adjacent slab contents) and, depending on slab layout, panic the
kernel.
PoC
df0860.c — a self-contained C harness that opens the control socket, sends a
52-byte header with a lying arglen, and recvfroms the leaked bytes. Run as
root with kldload ng_socket first:
cc -O2 -o df0860 df0860.c # unprivileged build OK # as root: kldload ng_socket ./df0860 128 # leaks 128 bytes of heap past the header
Expected on the buggy kernel (root):
sendto returned 52 recvfrom returned 180 bytes LEAK: kernel returned 128 bytes past our 52-byte header (arglen lied as 128): leaked heap bytes (128 bytes): 0000: 00 00 00 00 01 00 00 00 0f 00 00 00 c0 43 16 4f 0010: 00 f8 ff ff d0 43 16 4f 00 f8 ff ff 72 63 6e 67 ...
Expected on the fixed kernel (root):
sendto failed: errno=22 (Invalid argument)
Recommended fix (validated)
Add a length/arglen sanity check in ngc_send immediately after m_copydata
and before ng_send_msg. This mirrors FreeBSD svn r184036 (the CVE-2008-5736
fix). See fix.diff — it rejects the datagram with EINVAL when either the
header does not fit (len < sizeof(struct ng_mesg)) or the embedded
header.arglen claims more payload than was actually supplied
(arglen > len - sizeof(struct ng_mesg)). On the rejection path xmsg is
freed locally (normally ng_send_msg/its handler frees it, so the early-out
must free it itself).
Build/validation: Applied the diff to in-guest /usr/src, rebuilt with
make -j6 nativekernel KERNCONF=X86_64_GENERIC (rc=0), installed the rebuilt
ng_socket.ko to /boot/kernel/ (the bug lives in the loadable module, not
the base kernel — nativekernel rebuilds it but you must copy the new
.ko), rebooted, and re-ran the identical PoC. Result on #1 fixed kernel +
module: sendto failed: errno=22 (Invalid argument) — leak gone. Honest
messages (arglen matching actual data) still succeed (no regression).
Files
| file | desc |
|---|---|
df0860.c |
trigger harness (open control socket, lie arglen, recvfrom leak) |
build.sh / run.sh |
exact build & run commands |
fix.diff |
git-apply-able validation fix (validated) |
run.log, run.2.log, run.3.log |
baseline leak runs (unpatched #0) |
baseline_run.log |
post-reset reconfirm of the leak on #0 |
run_bigarglen.log |
arglen=60000 leak (60KB OOB read; no panic on this layout) |
run_maxx_gate.log |
EPERM as unprivileged maxx (privilege gate proof) |
fix_build.log |
single-fix kernel build log (rc=0) |
fix_run.log |
patched-kernel re-run: EINVAL, no leak |
leak_sample.txt |
leaked-byte summary across runs |
env.txt |
guest environment |
Fix verification
fixedVALIDATED the fix. The PoC leaked 128 bytes of heap on the unpatched 6.5-DEVELOPMENT #0 baseline (recvfrom returned 180 bytes, trailing 128 = leaked heap incl kernel pointers). On the single-fix kernel #1 (nativekernel rc=0) with the rebuilt ng_socket.ko installed, the SAME ./df0860 128 invocation is now rejected: sendto failed: errno=22 (Invalid argument) -- no recvfrom, no leak. Honest messages (arglen matching actual data, e.g. arglen=16/len=68 and arglen=0/len=52) still succeed -> no regression. NOTE: ng_socket.c compiles into the loadable module ng_socket.ko, NOT the base kernel, so nativekernel rebuilds it but you MUST also copy /usr/obj/.../ng_socket.ko over /boot/kernel/ng_socket.ko and reboot (copying only kernel.stripped leaves the old vulnerable module in place -- caught and corrected during validation).
BEFORE (unpatched #0, baseline_run.log): sending 52-byte datagram with header.arglen=128 -> path "." sendto returned 52 recvfrom returned 180 bytes LEAK: kernel returned 128 bytes past our 52-byte header AFTER (patched #1 + rebuilt ng_socket.ko, fix_run.log): sending 52-byte datagram with header.arglen=128 -> path "." sendto failed: errno=22 (Invalid argument) <- leak GONE (honest arglen=16/len=68 still accepted: sendto=68; no regression)
Confirmed kernel references
- sys/netgraph/socket/ng_socket.c:172
- sys/netgraph/socket/ng_socket.c:253
- sys/netgraph/socket/ng_socket.c:254
- sys/netgraph/socket/ng_socket.c:257
- sys/netgraph/socket/ng_socket.c:737
- sys/netgraph/socket/ng_socket.c:738
- sys/netgraph/socket/ng_socket.c:836
- sys/netgraph/netgraph/ng_base.c:1208
- sys/kern/kern_caps.c:330
- sys/sys/caps.h:132
Detail
Exploit chain
Not applicable / blocked by a VALID Phase-6 hard blocker. The bug is an OOB READ (no attacker-controlled write through this path), and -- critically -- the trigger is reachable ONLY from an already-root credential: opening a netgraph CONTROL socket requires caps_priv_check(p_ucred, SYSCAP_RESTRICTEDROOT) at ng_socket.c:172, which (sys/kern/kern_caps.c:330) requires cr_uid==0. The audit's unprivileged user maxx (uid 1001) confirmed the gate empirically: socket(AF_NETGRAPH,SOCK_DGRAM,NG_CONTROL) returns EPERM (run_maxx_gate.log). There is therefore no privilege boundary to cross (root->kernel is game-over by definition) and no escalation chain to develop. Realistic impact ceiling: a root process (or a confused-deputy root service proxying netgraph control messages) can disclose kernel heap memory and, depending on slab layout, panic the kernel (arglen=60000 completed a 60KB OOB read without faulting on this guest's layout; arglen=65535 would panic on a tighter layout). No exploit-chain file written because the primitive is read-only and root-gated; df0860.c is both the trigger and the impact demonstration.
Evidence (decisive lines)
BASELINE (unpatched #0, root, ng_socket loaded), ./df0860 128: sending 52-byte datagram with header.arglen=128 -> path "." sendto returned 52 recvfrom returned 180 bytes LEAK: kernel returned 128 bytes past our 52-byte header (arglen lied as 128): 0000: 00 00 00 00 01 00 00 00 0f 00 00 00 c0 43 16 4f 0010: 00 f8 ff ff d0 43 16 4f 00 f8 ff ff 72 63 6e 67 ... (non-zero bytes in leak: 84 / 128; ASCII rcng_automountd/disabled//usr) PRIVILEGE GATE (unprivileged maxx, module loaded): socket(AF_NETGRAPH,SOCK_DGRAM,NG_CONTROL) failed: errno=1 (Operation not permitted) GATE: EPERM - netgraph control socket requires SYSCAP_RESTRICTEDROOT (euid 0).
PoC changes
No PoC scaffolding existed on disk (only the DB row), so I authored the full evidence pack from scratch: df0860.c (control-socket harness that sends a 52-byte ng_mesg header with a lying arglen and recvfroms the leaked heap bytes), build.sh, run.sh (kldload ng_socket + run), VERDICT.md (full mechanism walkthrough), README.md, fix.diff (validated one-line arglen check), manifest.json, plus the captured run/fix logs and env.txt.
Verified recommended fix
In ngc_send, immediately after m_copydata(m,0,len,xmsg) and before ng_send_msg, reject the datagram with EINVAL when len < sizeof(struct ng_mesg) OR ((struct ng_mesg*)xmsg)->header.arglen > len - sizeof(struct ng_mesg); free xmsg on that error path (ng_send_msg normally frees it). This is the DragonFly port of FreeBSD svn r184036 (CVE-2008-5736). Supersedes the finding proposal (the finding described the bug accurately but no concrete diff existed; this is the authoritatively-validated fix). Full git-apply-able diff in findings/poc/DF-0860/fix.diff.
Verdict
REPRODUCED. The bug is real: ngc_send (sys/netgraph/socket/ng_socket.c:201) linearizes the user datagram into xmsg=kmalloc(len+1) (line 253) and casts it to (struct ng_mesg*) WITHOUT checking len>=sizeof(ng_mesg) (52) or header.arglen<=len-sizeof(ng_mesg). When the destination path is '.' ng_path2node loops to the same node (ng_base.c:~1130), and ngs_rcvmsg (ng_socket.c:788) -- for a typecookie that is neither NGM_GENERIC_COOKIE nor NGM_SOCKET_COOKIE -- calls ship_msg(pcbp, msg=xmsg, addr) (ng_socket.c:836). ship_msg trusts header.arglen: msglen=sizeof(ng_mesg)+arglen (line 737), m_devget(msg,msglen,...) (line 738) reads 52+arglen bytes from the undersized buffer -> heap OOB read queued on so_rcv and returned to userspace via recvfrom. Confirmed as root: sendto(52-byte header with arglen=128) -> recvfrom returns 180 bytes, the trailing 128 are leaked kernel heap containing canonical pointers (0xfffff800_4f1643c0) and residual strings (rcng_automountd, disabled, /usr). This is the never-ported FreeBSD CVE-2008-5736 (svn r184036).
No comments yet.