DF-1028 / poc.c
/* * DF-1028 โ OOB heap read in READ ELEMENT STATUS response parsing. * * Bug location: sys/bus/cam/scsi/scsi_ch.c:1118, 1159, 1176-1183 * * chgetelemstatus() drives a READ ELEMENT STATUS SCSI command and parses * the response. Two device-controlled fields drive the parser with no * validation: * * - desclen = scsi_2btoul(pg_hdr->edl); // line 1118 * - avail = scsi_2btoul(st_hdr->count); // line 1159 * * The kernel sizes its transfer buffer (line 1120-1122) from the * USER-supplied cesr->cesr_element_count and its user_data buffer (line * 1166-1168) from the DEVICE-supplied avail. copy_element_status() at * scsi_ch.c:983-1051 then unconditionally reads: * * desc->eaddr[2], flags1, reserved0, sense_code, sense_qual, * dt_scsi_flags, dt_scsi_addr, reserved1, flags2, ssea[2], * pvoltag (36 bytes), avoltag (36 bytes) * * โ about 84-88 fixed bytes per descriptor. Two distinct OOB reads: * * (a) desclen < 84: copy_element_status reads (84-desclen) bytes past * the per-descriptor slot of the data buffer. The data buffer * total size is sized only for desclen*cesr->cesr_element_count, * so even one short descriptor overruns the end of data. * * (b) avail > cesr->cesr_element_count: the for-loop at line 1176 * iterates `avail` times, walking the descriptor pointer past the * data allocation and copying the garbage bytes out to userspace * via copyout(user_data, ..., avail*sizeof(ces)). user_data was * sized for `avail` (so no heap overflow there), but it is filled * entirely with OOB-read heap contents. * * The leaked bytes are copied out to the (root-only) caller via * CHIOGSTATUS ioctl โ SYSCAP_RESTRICTEDROOT chopen(). * * Reachability on the audit guest: * - scsi_ch is part of the cam.ko module (loaded when CAM is in use, * which it is for the QEMU DVD-ROM). * - HOWEVER ch only registers a CAM peripheral for SCSI device type 8 * (changer) devices. The audit guest has only a QEMU DVD-ROM (type * 5 / CD-ROM), so no ch device node attaches and there is no path * to chgetelemstatus(). * - Bug is latent on this guest; reachable on any host that attaches * a SCSI changer (real or emulated) and runs CHIOGSTATUS against it. * * This harness documents the trigger and the fix. */ #include <stdio.h> int main(void) { printf("=== DF-1028 trigger documentation ===\n"); printf("\nVulnerable path (CHIOGSTATUS ioctl on /dev/ch0):\n"); printf(" chioctl -> chgetelemstatus (scsi_ch.c:1053)\n"); printf(" desclen = scsi_2btoul(pg_hdr->edl); // device-controlled\n"); printf(" avail = scsi_2btoul(st_hdr->count); // device-controlled\n"); printf(" for (i=0; i<avail; ++i) {\n"); printf(" copy_element_status(softc, pg_hdr->flags, desc, ces);\n"); printf(" // ^^^ reads ~84 fixed bytes from desc regardless of\n"); printf(" // desclen, walking past data allocation when\n"); printf(" // desclen<84 or avail>cesr->cesr_element_count\n"); printf(" desc = (char*)desc + desclen;\n"); printf(" }\n"); printf(" copyout(user_data, cesr->cesr_element_status,\n"); printf(" avail * sizeof(*user_data));\n"); printf("\nTrigger precondition: a SCSI changer device (or emulated\n"); printf(" changer presenting a malicious READ ELEMENT STATUS response)\n"); printf(" + a privileged user (root or CAP_RESTRICTEDROOT) issuing\n"); printf(" CHIOGSTATUS.\n"); printf("Effect: OOB heap read up to (avail-desclen/84)*84 bytes past\n"); printf(" data, copied out to userspace -> kernel heap leak.\n"); printf("\nStatus on this audit guest (only QEMU DVD-ROM, no SCSI\n"); printf(" changer, no /dev/ch0): NOT REACHABLE. Bug confirmed by code\n"); printf(" trace at sys/bus/cam/scsi/scsi_ch.c:1118, 1159, 1176-1183.\n"); printf(" Fix: clamp avail to cesr->cesr_element_count, reject\n"); printf(" desclen < sizeof(struct read_element_status_descriptor),\n"); printf(" and validate the residual descriptor pointer stays inside\n"); printf(" data. See fix.diff and VERDICT.md.\n"); return 0; } |