DF-4237 / sili_ncq_pool_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 216 217 218 219 220 221 222 223 224 225 226 227 | /* * sili_ncq_pool_model.c - DF-4237 evidence model * * Userspace model of the DragonFly sili(4) CAM ccb-pool accounting that * proves sili_ata_get_xfer() can return NULL while sili_cam.c dereferences * the result without a check: * * - sili_cam.c:289-291 IDENTIFY probe: xa = sili_ata_get_xfer(ap, atx); * xa->complete = ...; <- NULL deref * - sili_cam.c:371-372 NCQ renegotiation loop: * xa = sili_ata_get_xfer(ap, atx); * if (xa->tag < at->at_ncqdepth) <- NULL deref * * Kernel facts encoded (see VERDICT.md for path:line citations): * - sc_ncmds = SILI_MAX_CMDS = 31 slots, tags 0..30 (sili.h:706) * - slot 1 is ap_err_ccb, never on the free list (sili.c:265-268) * - free TAILQ initialised in tag order: [0,2,3,...,30] (sili.c:268) * - sili_get_ccb: pop head, returns NULL when empty (sili.c:2003-2018) * - sili_put_ccb: push tail (sili.c:2021-2030) * - CAM devq opening = sc_ncmds - 1 = 30 (sili_cam.c:101-104) * - NCQ renegotiation loop runs sc_ncmds iterations and * permanently HOLDS every slot whose tag >= at_ncqdepth (sili_cam.c:370-376) * - at_ncqdepth = (device qdepth & 0x1F) + 1, clamped to sc_ncmds * (sili_cam.c:365-368) * - a softreset success sets ap_probe = NEED_IDENT *without* revoking * in-flight ccbs (sili.c:845-849) * - XPT_PATH_INQ(wildcard)/XPT_ENG_EXEC/port-thread probes run with no * devq credit (sili_cam.c:846-850,878-881; sili.c:511-512) * * Scenarios: * A: XPT_SCSI_IO-triggered reprobe, 29 in-flight, device re-reports * qdepth=1 (ncqdepth=2) -> loop drains pool -> NULL deref at :371-372 * B: port-thread / PATH_INQ probe, 30 in-flight -> IDENTIFY get_xfer NULL * -> deref at :289-290 * C: control - boot-time probe, no in-flight I/O -> no NULL (bug needs * the concurrency precondition). */ #include <stdio.h> #include <stdlib.h> #include <string.h> #define SILI_MAX_CMDS 31 /* sili.h:706 */ #define DEVQ_OPENING (SILI_MAX_CMDS - 1) /* sili_cam.c:101-104 */ struct pool { int free[SILI_MAX_CMDS]; /* TAILQ emulation: head=idx 0 */ int nfree; int inflight[SILI_MAX_CMDS]; /* tag -> held by CAM I/O */ int held[SILI_MAX_CMDS]; /* tag -> held by NCQ loop */ }; static struct pool P; static void pool_init(void) { int i; memset(&P, 0, sizeof(P)); /* sili.c:265-268: slot 1 reserved as ap_err_ccb, others INSERT_TAIL * in ascending tag order */ for (i = 0; i < SILI_MAX_CMDS; ++i) { if (i == 1) continue; P.free[P.nfree++] = i; } } /* sili_get_ccb (sili.c:2003-2018): pop head, NULL when empty */ static int get_xfer(void) { int tag, i; if (P.nfree == 0) return -1; /* NULL */ tag = P.free[0]; for (i = 1; i < P.nfree; ++i) P.free[i-1] = P.free[i]; P.nfree--; return tag; } /* sili_put_ccb (sili.c:2021-2030): push tail */ static void put_xfer(int tag) { P.free[P.nfree++] = tag; } static void mark_inflight(int tag) { P.inflight[tag] = 1; } /* count = how many of the first 'n' free-list slots CAM I/O grabbed */ static void cam_io(int n) { int i, tag; for (i = 0; i < n; ++i) { tag = get_xfer(); if (tag < 0) { printf(" cam_io: get_xfer NULL (pool exhausted)\n"); exit(1); } mark_inflight(tag); } } /* * sili_cam.c:289-321 - the IDENTIFY step of sili_cam_probe(). * Returns the xa (tag) or emulates the kernel NULL deref. */ static int probe_identify(int check_null) { int tag; tag = get_xfer(); if (tag < 0) { if (!check_null) { /* kernel behaviour: xa->complete = sili_ata_dummy_done * on a NULL xa ==> page fault on the direct map of * NULL+offsetof(struct ata_xfer, complete) */ printf(" PANIC: sili_cam.c:290 xa->complete written " "through NULL xa (probe with exhausted pool)\n"); exit(2); } printf(" (patched) probe deferred, no free ccb\n"); return -1; } put_xfer(tag); /* sili_cam.c:321 */ return tag; } /* * sili_cam.c:365-381 - NCQ renegotiation loop. * device_qdepth: value a (crafted) device reports in identify word 75. * check_null: 0 = model shipped kernel (deref), 1 = model patched guard. */ static int ncq_renegotiate(int device_qdepth, int check_null) { int at_ncqdepth = (device_qdepth & 0x1F) + 1; /* sili_cam.c:365 */ int i, tag, held = 0; if (at_ncqdepth > SILI_MAX_CMDS) at_ncqdepth = SILI_MAX_CMDS; /* sili_cam.c:367-368 */ if (at_ncqdepth <= 1) return 0; /* loop gated off */ for (i = 0; i < SILI_MAX_CMDS; ++i) { /* sili_cam.c:370 */ tag = get_xfer(); if (tag < 0) { if (!check_null) { /* kernel behaviour: xa->tag read through * NULL xa at sili_cam.c:372 */ printf(" PANIC: sili_cam.c:372 xa->tag read " "through NULL xa after loop held %d " "slots (iteration %d/%d)\n", held, i + 1, SILI_MAX_CMDS); exit(3); } printf(" (patched) loop stopped at iteration %d, " "pool exhausted (held=%d)\n", i + 1, held); return held; } if (tag < at_ncqdepth) { /* sili_cam.c:372 */ put_xfer(tag); /* cycles forever */ } else { P.held[tag] = 1; /* held forever */ held++; } } return held; } static int nfree(void) { return P.nfree; } int main(int argc, char **argv) { /* * argv[1] == "guard": model the proposed fix (NULL checks), which * turns the panics into deferrals. Default: shipped kernel. */ int guard = (argc > 1 && strcmp(argv[1], "guard") == 0); int deref = guard; /* pass guard flag as check_null: 0=kernel derefs, 1=guarded */ printf("DF-4237 model: sili(4) ccb pool, sc_ncmds=%d, devq opening=%d, " "slot 1 reserved, mode=%s\n", SILI_MAX_CMDS, DEVQ_OPENING, guard ? "patched (NULL-guarded)" : "shipped kernel"); printf("\nScenario C (control): boot-time probe, no in-flight I/O\n"); pool_init(); printf(" free=%d [", nfree()); probe_identify(deref); ncq_renegotiate(31 /*device reports 31*/, deref); printf("%d tags cycling, no NULL - probe succeeds on idle port]\n", nfree()); printf(" => bug NOT reachable without concurrent I/O (negative " "control)\n"); printf("\nScenario A: XPT_SCSI_IO reprobe (sili_cam.c:944-945), " "29 in-flight, device re-reports qdepth=1\n"); pool_init(); cam_io(29); printf(" free=%d after 29 in-flight; old ncqdepth=31 so max_tags=30\n", nfree()); probe_identify(deref); /* takes last free slot, puts it back */ ncq_renegotiate(1, deref); /* ncqdepth=2: only tag 0 cycles */ if (guard) printf(" no panic in guarded mode\n"); printf("\nScenario B: port-thread / XPT_PATH_INQ(wildcard) probe with " "saturated queue (30 in-flight)\n"); pool_init(); cam_io(30); printf(" free=%d - probe caller holds no devq credit\n", nfree()); probe_identify(deref); /* NULL -> deref (or guarded defer) */ if (guard) printf(" no panic in guarded mode\n"); return 0; } |