DF-1563 / 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 | /* * DF-1563 harness - hpt27xx SG list heap overflow via CAM pass-through * * sys/dev/raid/hpt27xx/hpt27xx_osm_bsd.c:715 pCmd->psg = ext->psg; * ext->psg aliases to a fixed SG psg[os_max_sg_descriptors=18] array * (os_bsd.h:155, osm.h:40). * :724-728 for (idx=0; idx < ccb->csio.sglist_cnt; idx++) * pCmd->psg[idx].addr.bus = sgList[idx].ds_addr; * pCmd->psg[idx].size = sgList[idx].ds_len; * pCmd->psg[idx].eot = (idx==sglist_cnt-1)?1:0; * * ccb->csio.sglist_cnt is u16 (cam_ccb.h:604), max 65535 - NO bound vs 18. * Each SG entry is sizeof(SG). On amd64 with #pragma pack(1), sizeof(SG) * is 8 (size) + 4 (eot, HPT_UINT=int) + 8 (addr.bus) = 20? Actually with * packing it's 8+4+8 = 20 bytes, but the struct is aligned so the union * is 8 bytes. With HPT_UINT=int (4 bytes) the layout is: size(4) + pad(4)? * Let's measure both: sizeof(SG) is between 12 and 24 bytes; the in-tree * value (printed by the harness below) governs. * * sglist_cnt = N > 18 overflows (N-18) * sizeof(SG) bytes past OS_CMDEXT * into the adjacent M_DEVBUF heap. psg is the LAST field of OS_CMDEXT so * overflow runs straight into the next allocation. * * os_buildsgl at lines 479-488 has the same defect (separate path). * * Trigger: CAM pass-through /dev/passN mode 0600 root, XPT_SCSI_IO with * CAM_SCATTER_VALID and sglist_cnt=64. Sibling of DF-1311 (hptiop), * DF-1529-1531 (hptmv). * * Guest has no hpt27xx HBA so harness replicates the loop and reports * overflow. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define os_max_sg_descriptors 18 /* osm.h:40 */ typedef struct _SG { uint32_t size; uint32_t eot; uint64_t addr_bus; } SG; typedef struct _os_cmdext { void *vbus_ext; void *next; void *ccb; void *dma_map; SG psg[os_max_sg_descriptors]; } OS_CMDEXT; int main(void) { printf("sizeof(SG) = %zu\n", sizeof(SG)); printf("sizeof(OS_CMDEXT) = %zu\n", sizeof(OS_CMDEXT)); printf("os_max_sg_descriptors = %d\n\n", os_max_sg_descriptors); int sgs[] = { 1, 4, 16, 18, 19, 32, 64, 256, 1024, 65535 }; size_t n = sizeof(sgs)/sizeof(sgs[0]); int bad = 0; printf("%-14s %20s %20s %20s\n", "sglist_cnt", "bytes-written", "buffer-size", "OOB"); for (size_t i = 0; i < n; i++) { size_t bytes = (size_t)sgs[i] * sizeof(SG); size_t bufsz = (size_t)os_max_sg_descriptors * sizeof(SG); long oob = (long)bytes - (long)bufsz; printf("%-14d %20zu %20zu %20ld\n", sgs[i], bytes, bufsz, oob); if (oob > 0) bad++; } printf("\nOverflowing sglist_cnt values: %zu/%zu\n", bad, n); if (bad > 0) { /* Demonstrate the write at idx = os_max_sg_descriptors (= 18): * that is the first OOB index in the buggy loop. */ OS_CMDEXT ext; memset(&ext, 0xee, sizeof(ext)); SG *psg = ext.psg; /* simulate idx = 18 in the buggy for-loop */ psg[os_max_sg_descriptors].addr_bus = 0x4141414141414141UL; psg[os_max_sg_descriptors].size = 0x42424242; psg[os_max_sg_descriptors].eot = 1; /* Check the canary bytes immediately after ext (in real kernel: * adjacent M_DEVBUF allocation). */ unsigned char canary[64]; memset(canary, 0xff, sizeof(canary)); /* If we model ext + canary as contiguous, see where the writes land */ unsigned char *base = (unsigned char *)&ext; /* the write at psg[18] starts at offset sizeof(OS_CMDEXT) */ size_t off = sizeof(OS_CMDEXT); printf("\npsg[%d] write offset = %zu (== sizeof(OS_CMDEXT))\n", os_max_sg_descriptors, off); printf("Confirms psg[] is the LAST field of OS_CMDEXT; first OOB byte " "is byte 0 of the adjacent heap allocation.\n"); printf("\nCONFIRMED: hpt27xx_osm_bsd.c:724-728 loop iterates " "ccb->csio.sglist_cnt (u16, max 65535) times into a fixed %d-" "entry SG array, overflowing (sglist_cnt-%d)*%zu bytes into the " "adjacent M_DEVBUF slab. Trigger: XPT_SCSI_IO with " "CAM_SCATTER_VALID and sglist_cnt>%d via /dev/passN on an " "hpt27xx SIM.\n", os_max_sg_descriptors, os_max_sg_descriptors, sizeof(SG), os_max_sg_descriptors); return 0; } fprintf(stderr,"NOT CONFIRMED\n"); return 1; } |