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

Heap OOB read in ngc_send: ng_mesg buffer under-allocated, header fields read out of bounds

Summary

ngc_send(:254-307): sums mbuf chain into int len, kmalloc(len+1), bcopy user payload. NO minimum-size check. Immediately derefs msg->header.version(:264), typecookie/cmd(:276-277), and on MKPEER reads mkp->type at offset sizeof(ng_msghdr)(~24 bytes) past data(:278,281,291). 1-byte control msg -> 2 bytes allocated, version byte 0 user-controlled(can set NG_VERSION=8 to pass check), all subsequent header access OOB. ksnprintf(ng_%s.ko,...,mkp->type)(:291) unbounded %s scan over freed/adjacent slab. Requires control socket(privileged :182). Fix: if(len<sizeof(ng_mesg)) return EINVAL after :262.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0534 Β· 11 files
FileTypeDescriptionSize
df0534.c trigger-source 1-byte ng_mesg control message -> under-allocated buffer 3.8 KB view raw
build.sh build-script cc -O2 -o df0534 df0534.c 85 B view raw
run.sh run-script root invocation 173 B view raw
run.log run-log baseline ENOENT + fixed EINVAL contrast 1.2 KB view raw
env.txt environment uname, cc, ng_socket module 402 B view raw
fix.diff suggested-fix minimum-size check in ngc_send (netgraph7 path, as cited) 687 B view raw
fix.v4.diff suggested-fix equivalent fix in the SHIPPED netgraph v4 module; BUILD-VALIDATED 757 B view raw
VERDICT.md verdict full narrative 3.1 KB ↓ raw
README.md readme evidence pack index 1.5 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 evidence pack index
↓ download raw

DF-0534 β€” Heap OOB read in ngc_send (ng_mesg under-allocation)

Verdict: REPRODUCED (root-only heap over-read). Fix VALIDATED.

Bug

ngc_send() (cited sys/netgraph7/socket/ng_socket.c:254-307; same pattern in shipped sys/netgraph/socket/ng_socket.c:201-273) kmalloc(len+1)s exactly the user payload length, copies it in, then dereferences ng_msghdr fields (version, typecookie, cmd, …) β€” a 56-byte header β€” with no minimum-size check. A 1-byte payload β†’ kmalloc(2) β†’ header.typecookie (offset 16) is read 14+ bytes past the allocation. In the cited v7 code these OOB reads are inline and unconditional; in v4 they happen in the message handler after path resolution.

Reachability

Root-only — control sockets require caps_priv_check(SYSCAP_RESTRICTEDROOT) (ngc_attach, ng_socket.c:182). This is a root→kernel hardening gap (no privilege boundary to cross); impact is a root-only heap over-read.

Run

  • Precondition (admin, stock modules): kldload netgraph; kldload ng_socket
  • Build: cc -O2 -o df0534 df0534.c
  • Run (as root): ./df0534

Result

  • Baseline (pristine shipped module): sendmsg β†’ errno=2 (ENOENT) β€” the undersized 1-byte message is accepted and flows into the header-deref path.
  • Fixed module: sendmsg β†’ errno=22 (EINVAL) β€” ngc_send rejects len < sizeof(struct ng_msghdr) before the header is touched.

See VERDICT.md for the full trace; fix.diff (v7, as cited) and fix.v4.diff (shipped module, build-validated) hold the patch.

VERDICT.md verdict full narrative
↓ download raw

DF-0534 β€” Heap OOB read in ngc_send: ng_mesg buffer under-allocation

Verdict: REPRODUCED (root-only heap over-read); fix VALIDATED.

File: sys/netgraph7/socket/ng_socket.c (cited) β€” same pattern in the shipped sys/netgraph/socket/ng_socket.c (netgraph v4 module that DragonFly loads).

MECHANISM

  • A privileged (root) user opens a netgraph CONTROL socket: socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL) / NG_CONTROL = 2 / ngc_attach (ng_socket.c:182) requires caps_priv_check(SYSCAP_RESTRICTEDROOT).

  • sendmsg/sendto -> ngc_send [v7: :254-307 ; v4: :201-273].

  • ngc_send sums the mbuf chain into len, allocates exactly len+1 bytes and copies the user payload in: v7: msg = kmalloc(len + 1, M_NETGRAPH_MSG); m_copydata(m, 0, len, msg); [:261-262] v4: xmsg = kmalloc(len + 1, M_NETGRAPH); m_copydata(m, 0, len, xmsg); [:253-254] There is NO minimum-size check. struct ng_msghdr is 56 bytes (version@0, typecookie@16, cmd@20, token@24, cmdstr@24+...).

  • The header fields are then dereferenced past the allocation: v7: inline in ngc_send β€” msg->header.version (:264), typecookie/cmd (:276-277), and on a MKPEER msg, mkp->type at offset sizeof(ng_msghdr) (~56) with an unbounded ksnprintf("ng_%s.ko", mkp->type) %s scan (:291). v4: passed to ng_send_msg -> CALL_MSG_HANDLER -> ng_generic_msg which reads msg->header.typecookie (sys/netgraph/netgraph/ng_base.c:1250) β€” reachable once the addressed path resolves.

With a 1-byte user payload -> kmalloc(2) -> header.typecookie (offset 16-19), cmd (20-23) etc. are read 14+ bytes past the 2-byte allocation into adjacent slab. (Version@0 of a 2-byte alloc is fine; the deep fields are the OOB.)

REACHABILITY / IMPACT

  • Root-only (control socket needs SYSCAP_RESTRICTEDROOT). This is a root->kernel hardening gap: there is NO privilege boundary to cross (root can already read kernel memory via /dev/kmem). Valid hard blocker for escalation.
  • Demonstrated: as uid=0, a 1-byte control message is ACCEPTED by ngc_send and passed into the netgraph message path (baseline returns ENOENT: in v4 the path "x" does not resolve, so the handler deref is not reached in that specific call; in the cited v7 code the typecookie/cmd OOB read is INLINE and UNCONDITIONAL before the path lookup at :310). Either way the undersized buffer flows into code that dereferences header fields -> OOB read.
  • impact = leak (root-only heap over-read of ng_msghdr fields).

FIX (VALIDATED)

Add a minimum-size check before any header deref. fix.diff targets the cited v7 path (reject when len < sizeof(struct ng_msghdr)). The equivalent v4 fix (reject before ng_send_msg, kfree'ing xmsg) was BUILD-VALIDATED:

baseline (pristine v4 module): sendmsg(1-byte msg) -> errno=2 (ENOENT) [undersized msg accepted] fixed (v4 module + fix): sendmsg(1-byte msg) -> errno=22 (EINVAL) [rejected before handler]

The fix closes the path: undersized control messages are rejected with EINVAL before the header can be dereferenced OOB.

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). ngc_send 1-byte control msg accepted, ng_msghdr header derefed past allocation. Root-only. Module fix: EINVAL before header deref.