DF-4283 / midi_kq_model.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 203 204 205 206 207 208 209 210 211 212 213 214 215 | /* * midi_kq_model.c -- userland harness modeling the DragonFly kernel path * * devfs kqueue detach -> midi_filter_detach() [sys/dev/sound/midi/midi.c:932-941] * -> knote_remove() [sys/kern/kern_event.c:1902-1909] * -> SLIST_REMOVE() [sys/sys/queue.h:208-219] * * midi.c's single detach callback removes the knote from BOTH the read and * the write klist, but midi_kqfilter() (midi.c:901-930) inserted it into * exactly ONE (rkq for EVFILT_READ, wkq for EVFILT_WRITE). knote_remove() * uses SLIST_REMOVE, which walks the list to find the element and has NO * membership guard: if the element is absent, `curelm` walks off the end, * becomes NULL, and `SLIST_NEXT(curelm, field)` dereferences NULL. * In the kernel this is a page fault on the kernel unmapped low page => * panic. In this model it is a SIGSEGV near address 0. * * The queue macros below are verbatim copies (modulo the _DIAGNOSTIC-only * QMD_SAVELINK/TRASHIT no-ops, which the kernel build does not enable) of * sys/sys/queue.h:191-219, and knote_remove() is a verbatim copy of * sys/kern/kern_event.c:1902-1909 with only the LWKT pool-token * acquisition/release omitted (an SMP exclusivity primitive with no * bearing on the single-threaded list semantics). * * Modes: * empty - buggy detach; other list EMPTY (immediate NULL deref) * peer - buggy detach; other list holds another knote (walk, then NULL) * fixed - split detach callbacks (as in fix.diff); clean removal * control - SLIST_REMOVE on a list that DOES contain the knote (sanity) * * Exit codes: 0 = clean, 99 = SIGSEGV (model of kernel panic). */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <ucontext.h> /* ---------- sys/sys/queue.h:191-219 (verbatim) ---------- */ #define SLIST_HEAD(name, type) struct name { struct type *slh_first; } #define SLIST_ENTRY(type) struct { struct type *sle_next; } #define SLIST_FIRST(head) ((head)->slh_first) #define SLIST_EMPTY(head) ((head)->slh_first == NULL) #define SLIST_INSERT_HEAD(head, elm, field) do { \ SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \ SLIST_FIRST((head)) = (elm); \ } while (0) #define SLIST_NEXT(elm, field) ((elm)->field.sle_next) #define SLIST_REMOVE_HEAD(head, field) do { \ SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \ } while (0) #define SLIST_REMOVE(head, elm, type, field) do { \ if (SLIST_FIRST((head)) == (elm)) { \ SLIST_REMOVE_HEAD((head), field); \ } \ else { \ struct type *curelm = SLIST_FIRST((head)); \ while (SLIST_NEXT(curelm, field) != (elm)) \ curelm = SLIST_NEXT(curelm, field); \ SLIST_NEXT(curelm, field) = \ SLIST_NEXT(SLIST_NEXT(curelm, field), field); \ } \ } while (0) /* ---------- kern_event.c knote/klist (minimal) ---------- */ #define EVFILT_READ (-1) #define EVFILT_WRITE (-2) #define KN_PROCESSING 0x0002 /* knote_acquire() sets this before detach */ struct knote { SLIST_ENTRY(knote) kn_next; /* on source klist */ void *kn_hook; /* midi.c stores `m' here */ int kn_filter; int kn_status; }; SLIST_HEAD(klist, knote); /* kern/kern_event.c:1902-1909 (verbatim minus pool token) */ static void knote_remove(struct klist *klist, struct knote *kn) { /* lwkt_getpooltoken(klist); */ if (!(kn->kn_status & KN_PROCESSING)) { fprintf(stderr, "KKASSERT(kn->kn_status & KN_PROCESSING) " "would fire first -- still a panic\n"); abort(); } SLIST_REMOVE(klist, kn, knote, kn_next); /* lwkt_relpooltoken(klist); */ } /* ---------- snd_midi (kqueue members only) ---------- */ struct snd_midi { struct klist rkq; /* midi.c:106: struct kqinfo rkq */ struct klist wkq; /* midi.c:106: struct kqinfo wkq */ }; /* ---------- midi.c:901-930 midi_kqfilter (modeled) ---------- */ static void midi_kqfilter(struct snd_midi *m, struct knote *kn, int filter, struct klist **klistp) { switch (filter) { case EVFILT_READ: kn->kn_hook = (void *)m; *klistp = &m->rkq; break; case EVFILT_WRITE: kn->kn_hook = (void *)m; *klistp = &m->wkq; break; default: abort(); } kn->kn_status |= KN_PROCESSING; /* knote_acquire() equivalent */ SLIST_INSERT_HEAD(*klistp, kn, kn_next); } /* ---------- midi.c:932-941 midi_filter_detach (VERBATIM) ---------- */ static void midi_filter_detach(struct knote *kn) { struct snd_midi *m = (struct snd_midi *)kn->kn_hook; struct klist *rklist = &m->rkq; struct klist *wklist = &m->wkq; knote_remove(rklist, kn); knote_remove(wklist, kn); } /* ---------- fixed version (fix.diff): split detach callbacks ---------- */ static void midi_filter_detach_read(struct knote *kn) { struct snd_midi *m = (struct snd_midi *)kn->kn_hook; knote_remove(&m->rkq, kn); } static void midi_filter_detach_write(struct knote *kn) { struct snd_midi *m = (struct snd_midi *)kn->kn_hook; knote_remove(&m->wkq, kn); } static const char *g_mode; static void sigsegv(int sig, siginfo_t *si, void *uc) { fprintf(stderr, "[%s] SIGSEGV at faulting address %p " "(kernel equivalent: page fault on unmapped low page => panic)\n", g_mode, si->si_addr); exit(99); } int main(int argc, char **argv) { struct snd_midi m; struct knote kn_r, kn_peer; struct klist *owner; struct sigaction sa; g_mode = argc > 1 ? argv[1] : "empty"; memset(&m, 0, sizeof(m)); memset(&kn_r, 0, sizeof(kn_r)); memset(&kn_peer, 0, sizeof(kn_peer)); memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = sigsegv; sa.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &sa, NULL); /* * Exactly what an unprivileged user triggers with: * fd = open("/dev/midi0.1", O_RDWR); (mode 0666, midi.c:364-366) * kq = kqueue(); * kevent(kq, {fd, EVFILT_READ, EV_ADD}, 1, NULL, 0, NULL); * kevent(kq, {fd, EVFILT_READ, EV_DELETE}, ...) or close(kq)/exit; * kern_event.c:706-708 / 1365-1368 -> knote_detach_and_drop() -> * midi_filter_detach(). */ midi_kqfilter(&m, &kn_r, EVFILT_READ, &owner); printf("[%s] knote inserted on rkq only (midi.c:911-916); " "wkq empty=%d\n", g_mode, SLIST_EMPTY(&m.wkq)); if (strcmp(g_mode, "peer") == 0) { SLIST_INSERT_HEAD(&m.wkq, &kn_peer, kn_next); kn_peer.kn_status |= KN_PROCESSING; printf("[%s] peer knote inserted on wkq (different knote!)\n", g_mode); } if (strcmp(g_mode, "empty") == 0 || strcmp(g_mode, "peer") == 0) { printf("[%s] calling buggy midi_filter_detach()...\n", g_mode); fflush(stdout); midi_filter_detach(&kn_r); /* walks off wkq => NULL deref */ printf("[%s] NOT REACHED: detach returned\n", g_mode); return (1); } else if (strcmp(g_mode, "fixed") == 0) { printf("[%s] calling fixed detach_read()...\n", g_mode); fflush(stdout); midi_filter_detach_read(&kn_r); printf("[%s] clean: knote removed from its owning list, " "no foreign-list walk\n", g_mode); return (0); } else if (strcmp(g_mode, "control") == 0) { /* sanity: SLIST_REMOVE behaves when membership holds */ midi_filter_detach_read(&kn_r); printf("[%s] control ok\n", g_mode); return (0); } fprintf(stderr, "usage: %s empty|peer|fixed|control\n", argv[0]); return (2); } |