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

OOB heap read via unchecked blk_desc_len in MODE SENSE parsing (chdone/chgetparams)

Summary

find_mode_page_6 at scsi_all.h:1418 page_start=&mode_header[1]+mode_header->blk_desc_len. blk_desc_len u_int8_t offset 3 fully attacker-controlled. chdone :546-547 ea=find_mode_page_6(mode_header); reads 16 bytes from ea. chgetparams :1409-1419 reads ea fields + :1474-1484 reads cap fields. mode_buffer 32 bytes; blk_desc_len=0xFF -> page pointer mode_buffer+259 -> ~240+ bytes OOB kernel heap read into softc sc_firsts/sc_counts (exposed via CHIOGPARAMS) or panic on page boundary. Auto-triggered on device probe (chstart->chdone). Fix: validate blk_desc_len against buffer size before find_mode_page_6.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1029 Β· 11 files
FileTypeDescriptionSize
poc.c trigger-source documentation harness (no live trigger possible on guest) 3.3 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 add ch_mode_header_sane() helper, validate before each find_mode_page_6 call 2.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.3 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-1029 β€” VERDICT

Verdict

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

The OOB heap read via unchecked blk_desc_len is real and the cited line numbers are exact, but it cannot be exercised on the audit guest for the same reason as DF-1028: there is no SCSI medium-changer device, so no ch peripheral attaches and the vulnerable code paths (chstart β†’ chdone, and chgetparams) never run. The bug is latent on this guest and live on any host that attaches a SCSI changer; unlike DF-1028, the trigger is automatic at probe time β€” no userland cooperation is required.

Mechanism (code trace)

find_mode_page_6() at sys/bus/cam/scsi/scsi_all.h:1418-1427:

static __inline void *
find_mode_page_6(struct scsi_mode_header_6 *mode_header)
{
    void *page_start;
    page_start = (void *)((u_int8_t *)&mode_header[1] +
                          mode_header->blk_desc_len);
    return(page_start);
}

blk_desc_len is a u_int8_t at offset 3 of struct scsi_mode_header_6 (scsi_all.h:956-962) β€” i.e. a byte the device fully controls in its MODE SENSE response. The function adds that byte to &mode_header[1] (= mode_header + 4) with NO bounds check, so a blk_desc_len == 0xFF (255) makes page_start point 259 bytes into a buffer that may be far smaller.

chstart at sys/bus/cam/scsi/scsi_ch.c:485-519 allocates the probe mode buffer with:

mode_buffer_len = sizeof(struct scsi_mode_header_6) +   // 4
                  sizeof(struct scsi_mode_blk_desc)  +   // 8
                  sizeof(struct page_element_address_assignment);  // 20
// total = 32 bytes

If the device lies and returns blk_desc_len == 0xFF, find_mode_page_6(mode_header) returns mode_buffer + 4 + 255 = mode_buffer + 259 β€” 227 bytes past the 32-byte allocation.

Three call sites then dereference that OOB pointer:

Some of the resulting softc fields are then exposed to userspace via CHIOGPARAMS (slot counts). This is an automatic OOB read at device-probe time β€” no userland action needed beyond attaching the device.

Why it does not reproduce on this guest

Same as DF-1028: no SCSI device type 8 (medium changer) is present, so no ch peripheral attaches and chstart/chdone/chgetparams never run. The cam.ko module is loaded (QEMU DVD-ROM uses CAM), but the ch driver only matches T_CHANGER.

Exploit chain

none β€” pure OOB heap read (CWE-125) at probe time. Read size is up to ~16 bytes per call site, fed into softc state that is partially readable by CHIOGPARAMS (root-only). On a malicious-changer scenario, the read ceiling is bounded by the slab bucket / page adjacency of the 32-byte probe mode_buffer.

Fix

fix.diff adds a small static __inline int ch_mode_header_sane() helper that requires mode_header->blk_desc_len <= buflen - sizeof(*mode_header), and calls it at all three call sites before find_mode_page_6(). On a bogus value the driver logs a diagnostic and bails (probe announcement becomes empty; chgetparams returns EIO). This is a minimal, root-cause fix that matches the finding's "validate blk_desc_len against buffer size before find_mode_page_6" recommendation.

Fix validation

  • fix.diff applies cleanly to /usr/src/sys/bus/cam/scsi/scsi_ch.c with patch -p1 (4 hunks all succeeded; 2 with a 23-line offset because the helper was inserted above the first call site β€” patch auto-applied the offset).
  • 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. find_mode_page_6 blk_desc_len no bounds -> OOB read at probe. No SCSI changer. Fix compiles.