DF-3582 / trigger_df3582.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 | /* * DF-3582 trigger logic โ faithful extraction of the kernel write performed by * sys/dev/disk/isp/isp.c isp_parse_status() RQCS_RESET_OCCURRED handler: * * case RQCS_RESET_OCCURRED: * { * int chan; * isp_xs_prt(isp, xs, ISP_LOGWARN, "Bus Reset destroyed command"); * for (chan = 0; chan < isp->isp_nchan; chan++) { * FCPARAM(isp, chan)->sendmarker = 1; <-- THE BUG * } * ... * * On a SCSI card isp->isp_param is kmalloc(nchan * sizeof(sdparam)) bytes * (isp_pci.c:827-832), but FCPARAM() casts it to (fcparam *). sendmarker is * bit 3 of the first word of fcparam, so chan=1 writes at * isp_param + sizeof(fcparam) == isp_param + 21576 โ far outside the * 2*204 = 408 byte dual-bus allocation. * * This harness places a 408-byte buffer (exactly what a dual-bus SCSI card * gets) immediately before a PROT_NONE guard page and executes the identical * bitfield read-modify-write sequence. chan=0 succeeds; chan=1 faults at * +21576 โ proving the displacement arithmetic with the real struct * definitions taken from the kernel tree. */ #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <sys/mman.h> #include <signal.h> #include <unistd.h> #include <setjmp.h> typedef void *XS_T; typedef uint64_t XS_DMA_ADDR_T; typedef struct { uint64_t t; } NANOTIME_T; typedef struct { uint16_t param[16]; } mbreg_t; #define MAILBOX_STORAGE 16 struct isposinfo { char pad[128]; }; #define ISP_KERNEL 1 #include <dev/disk/isp/isp_stds.h> #include <dev/disk/isp/ispmbox.h> #include <dev/disk/isp/ispvar.h> static sigjmp_buf jb; static volatile sig_atomic_t got; static char faultbuf[64]; static void handler(int sig, siginfo_t *si, void *uc) { snprintf(faultbuf, sizeof (faultbuf), "SIGSEGV/%s", sig == SIGBUS ? "BUS" : "SEGV"); got = (sig_atomic_t)((uint8_t *)si->si_addr - (uint8_t *)0); siglongjmp(jb, 1); } int main(void) { size_t pagesz = 4096, allocsz = 2 * sizeof(sdparam); /* dual-bus SCSI */ uint8_t *base, *map; struct sigaction sa; int chan; printf("sizeof(sdparam) = %zu\n", sizeof(sdparam)); printf("sizeof(fcparam) = %zu\n", sizeof(fcparam)); printf("SCSI dual-bus isp_param allocation = %zu bytes (isp_pci.c:827-832)\n", allocsz); printf("FCPARAM(isp,1)->sendmarker write offset = +0x%zx (%zu) bytes\n", sizeof(fcparam), sizeof(fcparam)); printf("=> %zd bytes PAST the end of the allocation\n\n", (ssize_t)sizeof(fcparam) - (ssize_t)allocsz); /* * Map a large PROT_NONE reserve, then make exactly one page RW at its * start. The allocation stand-in lives in that page; ANY access beyond * it faults deterministically (like an unmapped/kmem_guard hole). */ size_t mapsz = 1 << 20; map = mmap(NULL, mapsz, PROT_NONE, MAP_ANON|MAP_PRIVATE, -1, 0); if (map == MAP_FAILED) { perror("mmap"); return (1); } if (mprotect(map, pagesz, PROT_READ|PROT_WRITE) != 0) { perror("mprotect"); return (1); } base = map; /* kmalloc(2 * sizeof(sdparam)) stand-in */ memset(base, 0, allocsz); memset(&sa, 0, sizeof (sa)); sa.sa_sigaction = handler; sa.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &sa, NULL); sigaction(SIGBUS, &sa, NULL); /* Exactly what the kernel loop does (isp.c:6109): * FCPARAM(isp, chan)->sendmarker = 1; * sendmarker is a 1-bit field in the first uint32_t of fcparam. */ for (chan = 0; chan < 2; chan++) { fcparam *fcp = &((fcparam *)(void *)base)[chan]; if (sigsetjmp(jb, 1) != 0) { printf("chan=%d: FAULT (%s) touching %p => offset %zu " "from allocation start (%zd past end of %zu-byte alloc)\n", chan, faultbuf, (void *)((uint8_t *)base + sizeof(fcparam) * (size_t)chan), sizeof(fcparam) * (size_t)chan, (ssize_t)(sizeof(fcparam) * (size_t)chan) - (ssize_t)allocsz, allocsz); return (0); } fcp->sendmarker = 1; /* the kernel's write */ printf("chan=%d: write at +%zu OK (in-allocation)\n", chan, sizeof(fcparam) * (size_t)chan); } printf("ERROR: no fault occurred\n"); return (1); } |