/*
 * 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;
}
