β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-1028

OOB heap read in READ ELEMENT STATUS response parsing (device-controlled desclen/avail/flags)

Summary

chgetelemstatus at scsi_ch.c:1118 desclen=scsi_2btoul(pg_hdr->edl) device-controlled. :1159 avail=scsi_2btoul(st_hdr->count) device-controlled. copy_element_status :983-1051 reads ~84 bytes per desc unconditionally (desc->eaddr/flags1/sense_code/sense_qual/dt_scsi_flags/dt_scsi_addr + pvoltag[36] + avoltag[36]). desclen<84 -> reads (84-desclen) bytes past buffer. avail>cesr_element_count -> loop runs past data allocation. user_data sized for avail but data sized for cesr_count -> heap OOB read into user_data -> copyout to user. Root-only (SYSCAP_RESTRICTEDROOT chopen). Malicious SCSI changer device. Fix: clamp avail to cesr_count, check desclen>=sizeof(desc), validate resid.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1028 Β· 11 files
FileTypeDescriptionSize
poc.c trigger-source documentation harness (no live trigger possible on guest) 4.1 KB view raw
build.sh build-script cc -O -pipe -Wall -o poc poc.c 140 B view raw
run.sh run-script ./poc 63 B view raw
build.log build-log harness compile output, full 13 B view raw
run.log run-log harness run output 1.3 KB view raw
fix.diff suggested-fix reject desclen<sizeof(descriptor), clamp avail to cesr_element_count 1.3 KB view raw
fix_build.log build-log cam.ko module compile with fix applied, full output (clean, -Werror) 19.5 KB view raw
env.txt environment uname, cc, kldstat, pciconf, modules 1.7 KB view raw
VERDICT.md verdict narrative analysis 4.5 KB ↓ raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
VERDICT.md verdict narrative analysis
↓ download raw

DF-1028 β€” VERDICT

Verdict

NOT REPRODUCED (live) β€” bug CONFIRMED via code trace, fix.diff compiles cleanly.

The OOB heap read in READ ELEMENT STATUS parsing is real and the cited line numbers are exact, but it cannot be exercised on the audit guest because the only SCSI device on the guest is a QEMU DVD-ROM (CD -ROM, SCSI device type 5) and the ch peripheral only attaches to SCSI device type 8 (medium changer). There is no /dev/ch0 and therefore no code path to chgetelemstatus(). The bug is latent on this guest and live on any host that has a (real or emulated) SCSI medium changer against which a privileged user issues CHIOGSTATUS.

Mechanism (code trace)

chgetelemstatus() (scsi_ch.c:1053-1200) drives a SCSI READ ELEMENT STATUS command and parses the response. Three device-controlled quantities drive the parser with no validation:

  • sys/bus/cam/scsi/scsi_ch.c:1118 c desclen = scsi_2btoul(pg_hdr->edl); The per-descriptor length, taken from the device's response page header. Attacker controls this.

  • sys/bus/cam/scsi/scsi_ch.c:1159 c avail = scsi_2btoul(st_hdr->count); The number of available elements, taken from the device's response status header. Attacker controls this.

  • The loop at scsi_ch.c:1176-1183 c for (i = 0; i < avail; ++i) { ... copy_element_status(softc, pg_hdr->flags, desc, ces); desc = (struct read_element_status_descriptor *) ((uintptr_t)desc + desclen); } iterates avail times and reads ~84 fixed bytes per descriptor regardless of desclen.

copy_element_status() (scsi_ch.c:983-1051) unconditionally dereferences desc->eaddr, flags1, reserved0, sense_code, sense_qual, dt_scsi_flags, dt_scsi_addr, reserved1, flags2, ssea, pvoltag (36 bytes), avoltag (36 bytes) β€” about 84 bytes total (verified from struct read_element_status_descriptor at scsi_ch.h:208-252). Two distinct OOB reads follow:

  • desclen < 84: each call to copy_element_status reads (84 - desclen) bytes past the per-descriptor slot. data is sized for desclen * cesr->cesr_element_count bytes, so even one short descriptor overruns the end of data.

  • avail > cesr->cesr_element_count: the loop walks desc past the data allocation, copying the OOB-read bytes into user_data (which is sized for avail, so no overflow there). The leaked kernel heap bytes are then copyout to the (privileged) user via scsi_ch.c:1186-1188.

The ch driver is part of the cam.ko module, which IS loaded on the audit guest (the QEMU DVD-ROM uses CAM via natapicam). But the ch peripheral only attaches when CAM detects a SCSI device type 8.

Why it does not reproduce on this guest

Audit-guest fact Evidence
Only SCSI device is a QEMU DVD-ROM (type 5, CD-ROM) camcontrol devlist shows only QEMU DVD-ROM at scbus1 target 0 lun 0
No /dev/ch0 device node exists ls /dev/ch* returns nothing
ch peripheral never registers because no medium-changer device is enumerated ch only matches T_CHANGER (type 8) at peripheral registration

There is no code path on the audit guest that reaches chgetelemstatus().

Exploit chain

none β€” pure OOB heap read (CWE-125). The ch driver's chopen is gated by SYSCAP_RESTRICTEDROOT so this is a privileged-user-only read; on a host with a malicious SCSI changer the leak ceiling is "kernel heap bytes the size of the (avail-count) gap past data, re-routed to userspace". Not exploitable as corruption.

Fix

fix.diff: 1. Rejects desclen < sizeof(struct read_element_status_descriptor) (84 bytes) with EIO and a diagnostic print, preventing the per-slot OOB read. 2. Clamps avail to cesr->cesr_element_count, preventing the loop from walking past data.

Both checks land before the user_data kmalloc and the descriptor loop, so they close both OOB paths.

Fix validation

  • fix.diff applies cleanly to /usr/src/sys/bus/cam/scsi/scsi_ch.c with patch -p1 (1 hunk succeeded).
  • The patched cam.ko module (which contains scsi_ch.o) compiles cleanly with -Werror (see fix_build.log).
  • Not live-tested (no SCSI changer device to attach).

fix_status: not_testable (no live trigger available).

PoC changes

The finding folder was empty; this run authored: - poc.c β€” documentation harness - fix.diff β€” the verified fix - build.sh, run.sh, build.log, run.log, env.txt, fix_build.log, manifest.json, VERDICT.md

Fix verification

not_testable

compile validated

module/kernel build rc=0 -Werror

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. chgetelemstatus desclen/avail no validation -> OOB read. No SCSI changer. Fix compiles.