DF-0987 / cdcheckmedia_harness.c
/* * DF-0987 — userspace harness replicating the TOC-parsing logic of * cdcheckmedia() in sys/bus/cam/scsi/scsi_cd.c. * * The kernel computes num_entries = (ending_track - starting_track) + 2 with * NO upper bound, then issues a READ_TOC DMA of toclen+4 bytes into * &softc->toc, where: * struct cd_tocdata { struct ioc_toc_header header; // 4 bytes * struct cd_toc_entry entries[100]; }; // 800 bytes * so sizeof(cd_tocdata) == 804. A malicious SCSI CD returning * ending_track=255, starting_track=0 yields num_entries=257, toclen=2056, and * DMA writes 2060 bytes into the 804-byte buffer -> 1256-byte heap overflow, * plus entries[256] OOB write. * * The harness feeds that malicious header through the exact arithmetic and * shows the overflow. Compile again with -DFIXED for the patched behavior. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define NENTRIES 100 #define TOCENT_SZ 8 /* sizeof(struct cd_toc_entry) */ #define HDR_SZ 4 /* sizeof(struct ioc_toc_header) */ #define TOCDATA_SZ (HDR_SZ + NENTRIES*TOCENT_SZ) /* 804 */ struct ioc_toc_header { uint8_t len[2], starting_track, ending_track; }; int main(void) { /* model the softc->toc region + adjacent softc memory */ uint8_t region[TOCDATA_SZ + 1024]; uint8_t *softc = region; memset(region, 0x00, sizeof(region)); memset(region + TOCDATA_SZ, 0xCC, sizeof(region) - TOCDATA_SZ); /* canary */ /* malicious READ_TOC header returned by the device */ struct ioc_toc_header *toch = (struct ioc_toc_header *)softc; toch->starting_track = 0; toch->ending_track = 255; int num_entries = (toch->ending_track - toch->starting_track) + 2; printf("starting_track=%u ending_track=%u -> num_entries=%d (cap=%d)\n", toch->starting_track, toch->ending_track, num_entries, NENTRIES); #ifdef FIXED if (num_entries > NENTRIES) { printf("FIX: num_entries=%d > %d -> bailout (overflow prevented)\n", num_entries, NENTRIES); return 0; } #endif if (num_entries <= 0) { printf("num_entries<=0 bailout\n"); return 0; } int toclen = num_entries * TOCENT_SZ; /* 257*8 = 2056 */ int dma_len = toclen + HDR_SZ; /* 2060 */ printf("toclen=%d DMA length into &softc->toc = %d (buffer = %d)\n", toclen, dma_len, TOCDATA_SZ); /* simulate the device DMA fill (kernel trusts the length) */ if (dma_len <= (int)sizeof(region)) { memset(softc, 0xAB, dma_len); /* corrupts canary tail */ } else { printf("(DMA length exceeds harness region; clamped for demo)\n"); memset(softc, 0xAB, sizeof(region)); } /* the kernel then does entries[num_entries-1] = entries[256] OOB write */ int oob_index = num_entries - 1; /* 256 */ printf("subsequent entries[%d] write (cap=%d) -> OOB by %d entries\n", oob_index, NENTRIES, oob_index - (NENTRIES-1)); long overflow_bytes = (long)dma_len - TOCDATA_SZ; int canary_hits = 0; for (int i = TOCDATA_SZ; i < (int)sizeof(region); i++) if (softc[i] != 0xCC) canary_hits++; printf("overflow past softc->toc: %ld bytes (clobbered %d tail bytes)\n", overflow_bytes, canary_hits); if (overflow_bytes > 0) printf("RESULT: OVERFLOW CONFIRMED — %ld-byte heap overflow into " "softc / M_DEVBUF\n", overflow_bytes); else printf("RESULT: NO OVERFLOW\n"); return (overflow_bytes > 0) ? 1 : 0; } |