DF-1017 / harness.c
/* * DF-1017 harness — READ_CAPACITY block_len=0 -> kernel divide-by-zero * * BUG: dasetgeom() at sys/bus/cam/scsi/scsi_da.c:2289 sets * dp->secsize = block_len; * with NO zero validation. block_len comes from * scsi_4btoul(rdcap->length) [device-controlled READ_CAPACITY response] * at dadone() :1831 / :1850 and dagetcapacity() :2228 / :2263. * * secsize is then used as a divisor at 7 sites with no zero guard: * dadump() :797-798 ap->a_offset / secsize, ap->a_length / secsize * dastart() TRIM :1341-1342 count = bp->b_bcount / secsize * lba = bio_offset / secsize * dastart() RW :1490 KKASSERT(bio_offset % secsize == 0) * :1500 bio_offset / secsize * :1501 b_bcount / secsize * * A malicious SCSI device (USB/iSCSI/FC) returning READ_CAPACITY with * length=0 causes secsize=0. The automatic partition probe * (disk_setdiskinfo -> disk_probe_slice -> dev_dstrategy -> dastart) * then divides by zero -> #DE trap -> kernel panic. * * This harness is a SOURCE-LEVEL proof, not a runtime exploit. It * demonstrates that secsize=0 causes integer division by zero in the * same arithmetic the kernel performs. The runtime trigger requires a * SCSI Direct Access (da) device, which this QEMU guest does not have * (only a DVD-ROM cd0 device is present; cd uses scsi_cd.c, not da). * * Build: cc -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> int main(void) { /* Simulate dasetgeom setting secsize from a malicious READ_CAPACITY */ uint32_t block_len = 0; /* malicious device returns length=0 */ uint32_t secsize = block_len; /* scsi_da.c:2289 — NO zero check */ /* Simulate the dastart RW path computing LBA and sector count */ uint64_t bio_offset = 0; /* partition probe starts at offset 0 */ uint32_t b_bcount = 512; /* typical read size */ printf("DF-1017: secsize=0 divide-by-zero demonstration\n"); printf(" block_len (from READ_CAPACITY): %u\n", block_len); printf(" secsize (set at dasetgeom:2289): %u\n", secsize); printf(" bio_offset: %lu\n", (unsigned long)bio_offset); printf(" b_bcount: %u\n", b_bcount); if (secsize == 0) { printf("\n BUG CONFIRMED: secsize=0 would trigger #DE at:\n"); printf(" dadump() :797 ap->a_offset / secsize => %s\n", "DIVIDE BY ZERO (#DE trap -> panic)"); printf(" dadump() :798 ap->a_length / secsize => DIVIDE BY ZERO\n"); printf(" dastart() :1341 bp->b_bcount / secsize => DIVIDE BY ZERO\n"); printf(" dastart() :1342 bio_offset / secsize => DIVIDE BY ZERO\n"); printf(" dastart() :1490 bio_offset %% secsize => DIVIDE BY ZERO\n"); printf(" dastart() :1500 bio_offset / secsize => DIVIDE BY ZERO\n"); printf(" dastart() :1501 b_bcount / secsize => DIVIDE BY ZERO\n"); printf("\n Kernel would panic with fatal trap 0 (#DE) in dastart/dadump.\n"); printf(" No user interaction needed: automatic partition probe triggers it.\n"); return 0; } /* This path is unreachable when block_len=0 but shown for completeness */ printf(" LBA = %lu, sectors = %u\n", (unsigned long)(bio_offset / secsize), b_bcount / secsize); return 1; } |