โฌข DragonFlyBSD Kernel Audit
DF-0716 / strdup_test.c
โ† back to finding โ†“ download raw
/*
 * DF-0716 โ€” kernel module test harness for smb_strdupin.
 *
 * smb_strdupin (sys/netproto/smb/smb_subr.c:113-131) is only reachable via
 * the SMBIOC_T2RQ ioctl, which requires a connected SMB share (SMBIOC_OPENSHARE
 * โ†’ smb_smb_treeconnect โ†’ live SMB server).  No SMB server is available on the
 * audit guest.  This module creates /dev/strdup_test (root-only 0600) that
 * calls smb_strdupin directly, allowing the userspace driver (strdup_race.c)
 * to race the user buffer and characterize the ignored-copyin-return defect.
 *
 * This is a TEST HARNESS, not an exploit โ€” the finding is root-only (device
 * 0700 + kldload) and there is no escalation (TOCTOU info leak of stale slab
 * contents to an SMB server, not uid0).
 *
 * Build:  see Makefile (bsd.kmod.mk)
 * Load:   kldload ./strdup_test.ko   (requires smbfs.ko already loaded)
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/uio.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/types.h>
#include <sys/ioccom.h>
#include <sys/fcntl.h>

/* Exported by smbfs.ko (global symbols, resolved at kldload time) */
extern char *smb_strdupin(char *s, int maxlen);
extern void  smb_strfree(char *s);

struct strdup_test_args {
	char	*user_ptr;	/* user buffer to pass to smb_strdupin */
	int	 maxlen;	/* maxlen argument */
	char	*result_buf;	/* where to copy the result bytes */
	int	 result_buflen;	/* size of result_buf (== expected len) */
	int	 result_is_null;	/* 1 = smb_strdupin returned NULL */
};

#define STRDUP_TEST_IOCTL	_IOWR('S', 1, struct strdup_test_args)

static d_open_t	strdup_test_open;
static d_close_t	strdup_test_close;
static d_ioctl_t	strdup_test_ioctl;

static struct dev_ops strdup_test_ops = {
	{ "strdup_test", 0, 0, NULL, 0, 0 },
	.d_open =	strdup_test_open,
	.d_close =	strdup_test_close,
	.d_ioctl =	strdup_test_ioctl,
};

static int
strdup_test_open(struct dev_open_args *ap)
{
	return 0;
}

static int
strdup_test_close(struct dev_close_args *ap)
{
	return 0;
}

static int
strdup_test_ioctl(struct dev_ioctl_args *ap)
{
	struct strdup_test_args *args;
	char *result;

	if (ap->a_cmd != STRDUP_TEST_IOCTL)
		return ENOTTY;

	args = (struct strdup_test_args *)ap->a_data;

	/* Call smb_strdupin โ€” the function under test */
	result = smb_strdupin(args->user_ptr, args->maxlen);

	if (result == NULL) {
		args->result_is_null = 1;
		return 0;
	}

	args->result_is_null = 0;

	/*
	 * Copy result_buflen bytes back to userspace.  The caller sets
	 * result_buflen == maxlen, and the user string is crafted to be
	 * exactly maxlen-1 chars + NUL, so the kmalloc allocation is exactly
	 * maxlen bytes.  Reading maxlen bytes is safe (within the allocation).
	 */
	copyout(result, args->result_buf, args->result_buflen);

	smb_strfree(result);
	return 0;
}

static int
strdup_test_load(module_t mod, int cmd, void *arg)
{
	static cdev_t dev = NULL;
	int error = 0;

	switch (cmd) {
	case MOD_LOAD:
		dev = make_dev(&strdup_test_ops, 0, UID_ROOT, GID_WHEEL,
			       0600, "strdup_test");
		kprintf("strdup_test: loaded (/dev/strdup_test)\n");
		break;
	case MOD_UNLOAD:
		if (dev != NULL)
			destroy_dev(dev);
		dev = NULL;
		kprintf("strdup_test: unloaded\n");
		break;
	default:
		error = EINVAL;
		break;
	}
	return error;
}

DEV_MODULE(strdup_test, strdup_test_load, NULL);