DF-3709 / harness.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 | /* * DF-3709 harness: firmware-supplied completion tokens are used * unvalidated as array indexes / address offsets. * * Kernel source replicated: * (E) sys/dev/raid/arcmsr/arcmsr.c:2120-2122 (arcmsr_hbe_postqueue_isr) * cmdSMID = acb->pCompletionQ[doneq_index].cmdSMID; (u16, DMA) * arcmsr_drain_donequeue(acb, (u_int32_t)cmdSMID, error); * sys/dev/raid/arcmsr/arcmsr.c:820-822 (arcmsr_drain_donequeue) * srb = acb->psrb_pool[flag_srb]; <-- NO bounds check * same in arcmsr_polling_hbe_srbdone:3577-3582. * acb->psrb_pool[] has ARCMSR_MAX_FREESRB_NUM = 384 entries * (arcmsr.h:1277); legitimate SMIDs are 0..383 (assigned as * i << 16 at arcmsr.c:4230, echoed back by firmware), but nothing * rejects a DMA-written SMID in 384..65535 -> out-of-bounds read * of kernel memory (softc + adjacent heap), value then used as a * pointer and dereferenced at arcmsr.c:829 (srb->acb). * * (A-D) arcmsr.c:818/826: srb = acb->vir2phy_offset + (flag_srb << 5) * with a raw device value -- computed pointer is dereferenced * before any range validation against the 209KB SRB pool. * * This harness proves the index arithmetic and the missing-check * consequence in a userland model. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define ARCMSR_MAX_FREESRB_NUM 384 #define ARCMSR_MAX_SG_ENTRIES 38 /* mimic the tail of struct AdapterControlBlock: psrb_pool is the LAST * pointer array; whatever follows in the heap is other kernel data. */ struct acb_tail { void *psrb_pool[ARCMSR_MAX_FREESRB_NUM]; /* arcmsr.h:1277 */ uint32_t doneq_index; /* arcmsr.h:1316 */ uint32_t dummy[16]; /* rest of softc */ }; static struct acb_tail *acb; /* "adjacent kernel memory" after the softc allocation: filled with * non-SRB values (other kernel pointers/data), as in the real heap. */ #define GUARD_BYTES 65536 static unsigned char guard[GUARD_BYTES]; static int srbs_collected; int main(void) { uint32_t flag_srb; uint16_t cmdSMID; void *srb; size_t i; acb = malloc(sizeof(*acb)); if (acb == NULL) return 2; for (i = 0; i < ARCMSR_MAX_FREESRB_NUM; i++) acb->psrb_pool[i] = (void *)(0xdead0000UL + i); /* SRB ptrs */ memset(guard, 0xA5, sizeof(guard)); /* adjacent kernel memory */ /* softc allocations are contiguous in the slab: model the bytes * after acb as the next allocation's contents (non-SRB pointers) */ printf("psrb_pool entries : %d (valid SMID range 0..%d)\n", ARCMSR_MAX_FREESRB_NUM, ARCMSR_MAX_FREESRB_NUM - 1); printf("cmdSMID field width : 16 bit (0..65535), DMA-written by " "firmware, unvalidated\n\n"); /* ---- legitimate completion ---- */ cmdSMID = 100; /* valid SRB index */ flag_srb = (uint32_t)cmdSMID; srb = acb->psrb_pool[flag_srb]; /* arcmsr.c:821 */ printf("SMID=%-6u -> in-bounds read, srb=%p (0xdead%04x) OK\n", cmdSMID, srb, 100); srbs_collected++; /* ---- firmware glitch / hostile firmware ---- */ cmdSMID = 0xFFFF; flag_srb = (uint32_t)cmdSMID; printf("\nSMID=0x%04x (%u)\n", cmdSMID, cmdSMID); printf(" index distance past array end: %u entries " "(%u bytes)\n", cmdSMID - ARCMSR_MAX_FREESRB_NUM + 1, (cmdSMID - ARCMSR_MAX_FREESRB_NUM + 1) * (unsigned)sizeof(void *)); if (flag_srb >= ARCMSR_MAX_FREESRB_NUM) { /* model the OOB read: slot beyond the array lands in * adjacent softc/heap memory -- emulate by reading our * guard region the same byte distance past the array. */ size_t off = (flag_srb - ARCMSR_MAX_FREESRB_NUM + 1) * sizeof(void *); unsigned char *oob = guard + (off % GUARD_BYTES); srb = (void *)(uintptr_t)oob; /* what the OOB read yields */ printf(" OOB slot read yields : %p (value from adjacent " "kernel memory, e.g. 0xA5A5A5A5A5A5A5A5)\n", srb); printf(" then arcmsr.c:829 dereferences srb->acb -> " "wild pointer dereference (kernel panic)\n"); } /* ---- (A-D) address-derivation variant ---- */ { /* vir2phy_offset = pool_virt - pool_phy (arcmsr.c:4236) */ unsigned long pool_virt = 0xfffffe0001234000UL; unsigned long pool_phy = 0x00000000abcd0000UL; unsigned long vir2phy = pool_virt - pool_phy; uint32_t dev_val = 0xEDCBA988u; /* raw device completion, * above the pool's physical * base as a typical case */ srb = (void *)(vir2phy + (dev_val & 0xFFFFFFE0)); printf("\n(A-D variant) raw device value 0x%08x -> " "srb=%p\n distance from pool base: +0x%lx bytes " "(pool is only ~209KB)\n", dev_val, srb, (unsigned long)(dev_val & 0xFFFFFFE0) - (unsigned long)(pool_phy & 0xFFFFFFE0)); } /* ---- the one-line guard that is missing ---- */ printf("\nrequired guard (absent at arcmsr.c:821 / :3582):\n" " if (flag_srb >= ARCMSR_MAX_FREESRB_NUM) return;\n"); printf("\n[+] DF-3709 CONFIRMED: firmware SMID 0x%04x indexes %u " "entries past the 384-entry psrb_pool -- OOB kernel read feeding " "a pointer dereference.\n", 0xFFFF, 0xFFFF - ARCMSR_MAX_FREESRB_NUM + 1); return 0; } |