DF-4244 / dpt_df4244_walk.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 | /* * DF-4244 harness: arithmetic proof of the OOB walk in dpt_detect_cache() * sys/dev/raid/dpt/dpt_scsi.c:648-669 using the DPT_HCP_* macros * verbatim from sys/dev/raid/dpt/dpt.h:341-344. * * The controller is asked for a 512-byte log page (cp_datalen = htonl(512), * dpt_scsi.c:618) DMA'd into `buff` (&dccb[1], inside the init sg page). * The walk bound, however, is taken from the *device-fed* page header: * * bytes = DPT_HCP_LENGTH(buff); // dpt_scsi.c:648 * ... * while (DPT_HCP_CODE(param) != 6) { * param = DPT_HCP_NEXT(param); // reads param[3] * if (param < buff || param >= &buff[bytes]) * return; // bound = device-fed bytes * } * * DPT_HCP_LENGTH(page) = ntohs(*(int16_t *)&page[2]) + 4 -> up to 65539, * NOT the 512 bytes actually transferred. A hostile/faulty controller * that reports a large page length steers `param` up to ~64 KB past the * 512-byte DMA buffer; every step reads param[0..1] (code) and param[3] * (NEXT length) from kernel memory the device never wrote. * * This program replays the exact loop over a model of the kernel's init * sg page (4096 bytes, buff = first 512), counts reads that land outside * the 512 bytes the HBA DMA'd, and reports the farthest offset reached. * * Build: cc -O2 -o dpt_df4244_walk dpt_df4244_walk.c * Run: ./dpt_df4244_walk * Success criterion: walk proceeds past buff+512 while the * `param >= &buff[bytes]` check still passes; reads up to bytes(=65539) * are classified OOB w.r.t. the DMA transfer size. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> /* ---- verbatim from dpt.h:341-344 ---- */ #define DPT_HCP_LENGTH(page) (ntohs(*(int16_t *)(void *)(&page[2]))+4) #define DPT_HCP_FIRST(page) (&page[4]) #define DPT_HCP_NEXT(param) (¶m[3 + param[3] + 1]) #define DPT_HCP_CODE(param) (ntohs(*(int16_t *)(void *)param)) /* ------------------------------------- */ static uint16_t ntohs_(uint16_t v) { return __builtin_bswap16(v); } #define ntohs ntohs_ int main(void) { const unsigned DMA_LEN = 512; /* cp_datalen, dpt_scsi.c:618 */ const unsigned PAGE = 4096; /* bus_dmamem_alloc'd sg page */ unsigned char *kern = calloc(1, 65536 + 4096); /* "kernel memory" model */ unsigned char *buff = kern; /* buff = &dccb[1] within page */ int bytes, steps = 0; long oob_reads = 0, same_page_reads = 0, far_reads = 0; unsigned char *param, *maxp = buff; /* Hostile controller fills the 512-byte DMA buffer: */ buff[0] = 0x33; /* page code 0x33 (sanity check passes) */ buff[2] = 0xFF; buff[3] = 0xFF; /* page length 0xFFFF -> bytes = 65539 */ *(uint16_t *)&buff[4] = ntohs_(1); /* first param code = 1 (passes !=1) */ buff[8] = 0x04; /* param[4] & 0x4: cache possible */ buff[7] = 0; /* param[3]=0 -> NEXT step = 4 bytes */ bytes = DPT_HCP_LENGTH(buff); printf("DF-4244 dpt_detect_cache() walk model\n"); printf("device reports page length bytes=%d; actual DMA transfer=%u; page=%u\n", bytes, DMA_LEN, PAGE); /* dpt_scsi.c:651-658 checks */ if (DPT_HCP_CODE(DPT_HCP_FIRST(buff)) != 1) { printf("layout chk failed\n"); return 1; } /* dpt_scsi.c:663-669: the exact loop */ param = DPT_HCP_FIRST(buff); while (DPT_HCP_CODE(param) != 6) { param = DPT_HCP_NEXT(param); if ((param < buff) || (param >= &buff[bytes])) break; /* the only bound: device-fed bytes */ steps++; if (param > maxp) maxp = param; /* classify the reads this iteration will do next (code + next len) */ if (param >= buff + DMA_LEN) { oob_reads++; if (param < buff + PAGE) same_page_reads++; else far_reads++; } if (steps > 100000) break; /* safety for the model */ } printf("walk stopped after %d accepted steps at buff+%td (max)\n", steps, maxp - buff); printf("reads OUTSIDE the %u-byte DMA buffer : %ld iterations\n", DMA_LEN, oob_reads); printf(" of which past the whole 4096B sg page: %ld iterations " "(arbitrary adjacent kernel memory)\n", far_reads); printf("bound used by driver : &buff[bytes] = buff+%d\n", bytes); printf("bound that is safe : &buff[%u] (the DMA'd length)\n", DMA_LEN); if (oob_reads > 0 && bytes > (int)DMA_LEN) { printf("\nPROVEN: with a device-fed length of %d, the walk accepts\n" "pointers up to buff+%d while only %u bytes were DMA'd ->\n" "kernel OOB read of up to %d bytes past the transfer buffer.\n", bytes, bytes, DMA_LEN, bytes - DMA_LEN); return 0; } printf("\nNOT PROVEN\n"); return 1; } |