โฌข DragonFlyBSD Kernel Audit
โ† dashboard
DF-0010

Uninitialized struct cmsgcred leaks kernel stack via synthesized SCM_CREDS (SO_PASSCRED)

Field Value
ID DF-0010
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N
CWE CWE-908 Use of Uninitialized Resource; CWE-200 Exposure of Sensitive Information
File sys/kern/uipc_usrreq.c
Lines 683, 695-697, 1734-1744
Area kern
Confidence certain
Discovered 2026-06-29
Reported pending

Summary

In uipc_send()'s AF_UNIX SOCK_DGRAM path, when the receiver has SO_PASSCRED set and the sender did not include an SCM_CREDS control message, the kernel synthesizes one from an uninitialized on-stack struct cmsgcred cred. sbcreatecontrol() copies sizeof(cred) bytes of that stack garbage into the mbuf, then unp_internalize() (SCM_CREDS branch) only fills cmcred_pid/uid/euid/gid/ngroups and groups[0..ngroups-1]. The tail groups[ngroups..CMGROUP_MAX-1] (up to 15*4 = 60 bytes) retain whatever was on the kernel stack and are delivered verbatim to the receiver via recvmsg().

Root cause

sys/kern/uipc_usrreq.c:683 declares struct cmsgcred cred; with no initializer. sys/kern/uipc_usrreq.c:695-696 calls sbcreatecontrol(&cred, sizeof(cred), SCM_CREDS, SOL_SOCKET), which (uipc_sockbuf.c) copies the full sizeof(cred) bytes โ€” including the uninitialized tail โ€” into the control mbuf. unp_internalize then, in the SCM_CREDS branch (uipc_usrreq.c:1734-1744), fills only:

cmcred->cmcred_pid    = p->p_pid;
cmcred->cmcred_uid    = ... cr_ruid;
cmcred->cmcred_gid    = ... cr_rgid;
cmcred->cmcred_euid   = ... cr_uid;
cmcred->cmcred_ngroups = MIN(...cr_ngroups, CMGROUP_MAX);
for (i = 0; i < cmcred->cmcred_ngroups; i++)
    cmcred->cmcred_groups[i] = ... cr_groups[i];

It never writes groups[ngroups..CMGROUP_MAX-1]. CMGROUP_MAX == 16 (sys/sys/socket.h:422) and struct cmsgcred.cmcred_groups[16] (sys/sys/socket.h:437), so up to (16 - ngroups) * 4 bytes of kernel stack are delivered to the receiver.

Threat model & preconditions

  • Attacker position: any local unprivileged user.
  • Privileges gained or impact: information disclosure. Each message leaks up to ~60 bytes of kernel stack whose contents depend on prior frames (pointer fragments, etc.). A samplable KASLR / stack-residue oracle that lowers the bar for exploiting a separate kernel bug. Not a direct LPE.
  • Required config or capabilities: none; default kernel.
  • Reachability: socketpair(AF_UNIX, SOCK_DGRAM), setsockopt(SO_PASSCRED) on one end, send plain data from the other (no SCM_CREDS), recvmsg on the marked end. Fully self-reachable (sender == receiver).

Proof of concept

PoC source: findings/poc/DF-0010/leak_cmsgcred.c

Build & run

cc -o leak_cmsgcred findings/poc/DF-0010/leak_cmsgcred.c
./leak_cmsgcred        # as a non-root user

Expected output

sample 0: pid=... uid=... ... ngroups=1
  UNFILLED groups [1..15] = { 0x<..> 0x<..> ... }
result: LEAK CONFIRMED (kernel-stack bytes in unfilled groups)

Impact

Low-impact kernel-memory info leak, samplable in a tight loop. Useful as a KASLR/heap-grooming oracle ingredient. Rated Low.

Zero the synthesized cred:

--- a/sys/kern/uipc_usrreq.c
+++ b/sys/kern/uipc_usrreq.c
@@ -680,7 +680,7 @@
        if (so2->so_options & SO_PASSCRED) {
            struct mbuf **mp;
            struct cmsghdr *cm;
-           struct cmsgcred cred;
+           struct cmsgcred cred = {};
            struct mbuf *ncon;

References

Timeline

  • 2026-06-29 Discovered during automated file-by-file audit of sys/kern/uipc_usrreq.c.
  • pending Reported to DragonFlyBSD security contact.

PoC verification

Evidence pack

findings/poc/DF-0010 ยท 10 files
FileTypeDescriptionSize
leak_cmsgcred.c trigger-source minimal AF_UNIX SOCK_DGRAM SO_PASSCRED uninit-leak trigger 3.1 KB view raw
README.md readme build/run/expected + root-cause 1.4 KB โ†“ raw
VERDICT.md verdict full REPRODUCED narrative: trigger->primitive->effect with path:line 4.5 KB โ†“ raw
build.sh repro-script cc -o leak_cmsgcred leak_cmsgcred.c 122 B view raw
run.sh repro-script ./leak_cmsgcred 226 B view raw
build.log build-log final successful build, full output 13 B view raw
run.log run-log decisive single run, full output 1.2 KB view raw
leak_sample.txt leak-sample 3 stress runs showing varying leaked kernel-stack bytes incl. KASLR pointers 3.7 KB view raw
env.txt environment uname, cc version, kern.ipc sysctls 307 B view raw
fix.diff suggested-fix git-apply-able: bzero(&cred,...) at uipc_usrreq.c:683 to zero the synthesized cmsgcred 316 B view raw
README.md readme build/run/expected + root-cause
โ†“ download raw

DF-0010 โ€” PoC

leak_cmsgcred.c โ€” unprivileged leak of kernel-stack bytes via the uninitialized struct cmsgcred synthesized by SO_PASSCRED.

The issue

uipc_send() (sys/kern/uipc_usrreq.c:680-700), AF_UNIX SOCK_DGRAM, receiver has SO_PASSCRED and sender sent no SCM_CREDS:

struct cmsgcred cred;                 /* :683  uninitialized               */
...
ncon = sbcreatecontrol(&cred, sizeof(cred), SCM_CREDS, SOL_SOCKET); /* :695 */
unp_internalize(ncon, msg->send.nm_td);

sbcreatecontrol copies sizeof(cred) bytes of stack garbage into the mbuf; unp_internalize SCM_CREDS (uipc_usrreq.c:1734-1744) only fills cmcred_pid/uid/euid/gid/ngroups and groups[0..ngroups-1]. The tail groups[ngroups..CMGROUP_MAX-1] (up to 15*4 = 60 bytes) retain the leaked kernel-stack bytes and are delivered to the receiver via recvmsg.

Build

cc -o leak_cmsgcred findings/poc/DF-0010/leak_cmsgcred.c

Run

As an unprivileged user:

./leak_cmsgcred

Expected output (bug present)

sample 0: pid=... uid=... ... ngroups=1
  filled groups [0..0] = { <gid> }
  UNFILLED groups [1..15] = { 0x<..> 0x<..> ... }     <-- leaked kernel stack
result: LEAK CONFIRMED (kernel-stack bytes in unfilled groups)

The unfilled group words vary across runs and are kernel-stack residue (pointer fragments, etc.) โ€” a samplable KASLR/stack-residue oracle. On a fixed kernel they read as zero.

VERDICT.md verdict full REPRODUCED narrative: trigger->primitive->effect with path:line
โ†“ download raw

DF-0010 โ€” VERDICT

Verdict: REPRODUCED (uninitialized-kernel-stack info leak via SO_PASSCRED synthesis). Impact: info leak, up to ~60 bytes of kernel stack per call including canonical kernel-virtual pointers (KASLR/stack-residue oracle). Confidence: certain.

Mechanism (trigger โ†’ primitive โ†’ effect)

  1. Trigger (unprivileged, self-reachable). An AF_UNIX SOCK_DGRAM socketpair; the receiver end sets SO_PASSCRED; the peer sends a plain datagram with no SCM_CREDS control message. send() โ†’ sosend โ†’ uipc_send.

  2. Synthesis from an uninitialized on-stack struct. In uipc_send, sys/kern/uipc_usrreq.c:683 declares c struct cmsgcred cred; /* NO initializer */ inside the if (so2->so_options & SO_PASSCRED) block (:680). When the loop at :687-693 finds no pre-existing SCM_CREDS cmsg (ncon == NULL), the kernel synthesizes one at :694-699: c ncon = sbcreatecontrol(&cred, sizeof(cred), SCM_CREDS, SOL_SOCKET); unp_internalize(ncon, msg->send.nm_td); *mp = ncon;

  3. Primitive: full-struct copy of stack garbage. sbcreatecontrol (sys/kern/uipc_sockbuf.c:585-604) does memcpy(CMSG_DATA(cp), p, size) with size = sizeof(struct cmsgcred) = 84 (:598) โ€” it copies all 84 bytes of the uninitialized on-stack cred into the control mbuf.

  4. Partial fill leaves the tail unwritten. unp_internalize's SCM_CREDS branch (sys/kern/uipc_usrreq.c:1734-1744) only writes: - cmcred_pid, cmcred_uid, cmcred_gid, cmcred_euid, cmcred_ngroups (:1736-1741) - cmcred_groups[0..ngroups-1] (:1742-1743)

It never writes groups[ngroups..CMGROUP_MAX-1] (nor the 2-byte pad after cmcred_ngroups). struct cmsgcred (sys/sys/socket.h:431-438) is __pid_t + uid_t*3 + short + pad + gid_t[CMGROUP_MAX] with CMGROUP_MAX = 16 (sys/sys/socket.h:422), so up to (16 - ngroups)*4 + 2 bytes of kernel stack are delivered verbatim to the receiver via recvmsg.

  1. Effect: kernel-stack residue leaked to peer. For maxx (ngroups=1), groups[1..15] (60 bytes) plus 2 pad bytes are leaked. Across runs the leaked words vary (different stack residue) and contain canonical kernel-virtual pointers such as 0xfffff8006687a5a0, 0xfffff800ab9d5688, 0xfffff800aba73688 โ€” directly defeating KASLR and providing a samplable stack-residue / heap-grooming oracle for a separate kernel bug. No integrity/availability impact (pure info leak).

Evidence (decisive)

leak_sample.txt holds three independent runs (PIDs 838 / 933 / 938 โ€ฆ on different boots). Representative (PID 838, run 1):

sample 0: pid=838 uid=1001 euid=1001 gid=1001 ngroups=1
  filled groups [0..0] = { 1001 }
  UNFILLED groups [1..15] = { 0x00000000 0x4060d100 0xfffff800 0xaba73688
    0xfffff800 0x80656a1e 0xffffffff 0x00000005 0x00000000 0x4060d100
    0xfffff800 0xaba736a8 0xfffff800 0x00000000 0x00000000 }

The 0xfffff800........ words are canonical kernel-virtual addresses (the kernel text/data range). They differ across PIDs/runs, proving genuine uninitialized-memory content rather than cosmetic output. result: LEAK CONFIRMED on every run; exit code 0. run.log is the decisive single run.

PoC changes

The seeded leak_cmsgcred.c was already correct and compiled/run unchanged (no source edits were needed). It builds clean with cc -o leak_cmsgcred leak_cmsgcred.c and reproduces on the first run.

Exploit chain

Not a memory-corruption class โ€” pure info leak. No further primitive is derivable beyond the samplable ~60-byte kernel-stack oracle (useful as a KASLR/heap-grooming ingredient for a separate bug). Ceiling impact = Low info disclosure, matching the finding's rating.

fix.diff (git-apply-able, git apply --check verified) inserts bzero(&cred, sizeof(cred)); immediately after the declaration block at sys/kern/uipc_usrreq.c:683-684, zeroing all 84 bytes (including the pad and the unfilled groups[ngroups..15]) before sbcreatecontrol copies them. This matches the finding markdown's ## Recommended fix intent (zero the synthesized cred) and additionally covers the 2-byte inter-field padding that = {} initialization would not guarantee to clear. Alternative defense-in-depth would be to zero the whole cmcred inside unp_internalize before the partial fill at :1734-1744, but the declaration-site fix is the minimal targeted change at the root location the finding cites.

Confirmed kernel references

Detail

Exploit chain

none -- pure info-leak class (no memory corruption). Ceiling impact = samplable ~60-byte/call kernel-stack oracle (KASLR/heap-grooming ingredient for a separate bug); no integrity/availability impact, no further primitive derivable.

Evidence (decisive lines)

sample 0: pid=853 uid=1001 euid=1001 gid=1001 ngroups=1
  filled groups [0..0] = { 1001 }
  UNFILLED groups [1..15] = { 0x00000000 0x405a1080 0xfffff800 0x668787a0 0xfffff800 0x00000148 0x00000000 0x00000005 0x00000000 0x405a1080 0xfffff800 0xab9496a8 0xfffff800 0x00000000 0x00000000 }
... sample 2: ... 0xfffff800 0x806bd635 0xffffffff ... 0xab4e6c88 0xfffff800 ...
result: LEAK CONFIRMED (kernel-stack bytes in unfilled groups)
RUN_EXIT=0   (0xfffff800........ = canonical kernel-virtual ptrs; vary across runs)

PoC changes

The seeded leak_cmsgcred.c was already correct -- no source edits needed; it built clean and reproduced on the first run. Added build.sh/run.sh repro scripts, captured build.log/run.log/leak_sample.txt (3 stress runs)/env.txt, wrote VERDICT.md + manifest.json, and authored fix.diff (bzero(&cred,...) at uipc_usrreq.c:683).

Verified recommended fix

fix.diff (git-apply-able, git apply --check verified) inserts bzero(&cred, sizeof(cred)); right after the declaration block at sys/kern/uipc_usrreq.c:683-684, zeroing all 84 bytes (including the inter-field pad and the unfilled groups[ngroups..15]) before sbcreatecontrol copies them. Matches the finding markdown's ## Recommended fix intent (zero the synthesized cred) and additionally covers the 2-byte padding that = {} would not guarantee to clear. Full diff in findings/poc/DF-0010/fix.diff.

Verdict

REPRODUCED. The bug is real: uipc_send (sys/kern/uipc_usrreq.c:683) declares struct cmsgcred cred; with no initializer in the SOCK_DGRAM SO_PASSCRED synthesis block; sbcreatecontrol (uipc_sockbuf.c:598) memcpy's all 84 bytes of that stack garbage into the control mbuf; unp_internalize's SCM_CREDS branch (uipc_usrreq.c:1734-1744) only fills cmcred_pid/uid/gid/euid/ngroups and groups[0..ngroups-1], leaving the 2-byte pad after ngroups and groups[ngroups..CMGROUP_MAX-1] (CMGROUP_MAX=16, sys/sys/socket.h:422) uninitialized. Confirmed empirically: an unprivileged socketpair(AF_UNIX,SOCK_DGRAM) + SO_PASSCRED + plain send() leaks ~60 bytes of kernel stack to the receiver, including canonical kernel-virtual pointers (0xfffff800668787a0, 0xfffff800ab9496a8, ...) that vary across PIDs/runs (838/853/933/938) -- a genuine uninitialized-memory info leak / KASLR+stack-residue oracle, not cosmetic output. Full 3-run variance proof in findings/poc/DF-0010/leak_sample.txt.