DF-3093 / racestress.c
/* * 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); } |