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

Legacy firmware size-check bypass in si_init_microcode() (err clobbered by later request_firmware success) -> heap OOB reads in constant-bounded legacy ucode loaders

Summary

Only the pfp 'Bogus length' failure aborts with goto out; me/ce/rlc/mc/smc set err=-EINVAL and fall through, every later successful request_firmware/radeon_ucode_validate assignment zeroes err, and the all-legacy final gate never re-checks, so si_init_microcode returns 0 with a wrong-size legacy firmware installed and new_fw=false. The legacy loaders are bounded by compile-time constants, not fw->datasize (SI_CE/SI_PM4_UCODE_SIZE 2144 dw, SI_RLC_UCODE_SIZE 2048 dw) -> heap overread of CONSTANT*4-datasize bytes (7552B for a 1KiB ME) streamed into CP/RLC MMIO. Runs at SI boot/resume regardless of the accel_working=false gate. Exact DF-3573 (cik.c) class; distinct from pass-1 DF-1130 (new-format headers). VERIFIED: statement-faithful harness on the guest 3/3 - 'err clobbered: -EINVAL -> 0', 'err=0 (ACCEPTED), me_fw=1024B', SIGSEGV at loader i=256 with 7552B OOB span; -DFIXED rejects cleanly. Root-installed/mis-packaged legacy radeonkmsfw module precondition.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3591 Β· 10 files
FileTypeDescriptionSize
fwsize_bypass_si.c β€” 11.0 KB view raw
setup_fw.sh β€” 922 B view raw
build.sh β€” 104 B view raw
run.sh β€” 162 B view raw
build.log β€” 692 B view raw
run.log β€” 1.8 KB view raw
run.fixed.log β€” 344 B view raw
env.txt β€” 399 B view raw
fix.diff β€” 1.2 KB view raw
VERDICT.md β€” 3.4 KB ↓ raw
VERDICT.md
↓ download raw

DF-3591 β€” VERDICT

Status: reproduced (logic-level, on-guest) / impact: defense-in-depth heap OOB read (root-supplied firmware precondition)

Why logic-level

The kernel path requires a Southern Islands GPU (Tahiti/Pitcairn/Verde/ Oland/Hainan) to attach; this audit guest is virtio-only (pciconf -l | grep -c 1002 β†’ 0, radeon.ko present in /boot/kernel but never attached β€” env.txt). The defect is nevertheless fully deterministic from source, so verification replicates the exact statements and data of si_init_microcode() (si.c:1660-1954) and the si_cp_load_microcode() legacy ME loop (si.c:3515-3538) in a userspace harness (fwsize_bypass_si.c), with the truncated firmware buffer placed at a page boundary followed by a PROT_NONE guard page so the first OOB dword read faults observably.

What was demonstrated (guest, 3/3 runs)

  1. Size-gate bypass: with an all-legacy TAHITI firmware set whose ME file is 1024 B instead of 8576 B, the me "Bogus length" block sets err = -EINVAL (si.c:1806) and falls through; the next block's successful legacy request_firmware (ce, si.c:1822) assigns 0 to err; rlc/mc/smc load cleanly; new_fw == 0 leaves err untouched (si.c:1927); si_init_microcode returns 0 with the undersized ME installed.
  2. OOB read: the legacy ME loader loop for (i = 0; i < SI_PM4_UCODE_SIZE; i++) be32_to_cpup(fw_data++) (si.c:3535-3536) faults at i=256 reading past the 1024-byte buffer β€” 7552 bytes of kernel-heap overread in the kernel equivalent (silent, kmalloc'd buffer).
  3. Fix validation: with the five goto out; insertions (fix.diff), the harness (and by statement-equivalence the kernel) rejects the truncated ME with err = -EINVAL, releases all six firmware blobs at out: (si.c:1936-1952), and never runs a loader. Exit 0.

Reachability (kernel)

  • Called at driver load and every resume: si_init β†’ si_startup β†’ si_cp_load_microcode (si.c:6724); si_irq_init β†’ si_rlc_resume (si.c:5979) β€” both before/independent of the CS-ioctl accel gate.
  • Trigger data: legacy-format radeonkmsfw_<CHIP>_{me,ce,rlc} firmware module with datasize β‰  constant. Guest ships both naming variants (radeonkmsfw_TAHITI_me.ko and radeonkmsfw_tahiti_me.ko in /boot/kernel), so the legacy fallback is a real configuration, not a theoretical one.
  • Attacker model: corrupted/mis-packaged firmware (root-installed) β€” supply-chain/defense-in-depth, same rating rationale as DF-3573 (Low).

Not a DFly port bug

Code inherited from Linux; the asymmetric goto out (pfp only) matches upstream radeon si.c byte-for-byte. Distinct finding from DF-1130 (new-format header fields) β€” this is the legacy path and the size gate itself.

Bottom line

Certain, by statement-faithful on-guest reproduction (3/3): the size gate for legacy me/ce/rlc/mc/smc firmware is bypassable via err-clobber, and the constant-bounded legacy loaders then overread the kernel heap by up to 7552 B (ME) / 7168 B (RLC, 8192-1024). Fix is five lines (fix.diff), kernel-equivalence demonstrated in the FIXED harness variant.

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

Fix verification

not_testable
baseline reproduced→ patch + rebuild →patched clean

Logic-level fix validation on-guest: FIXED harness variant (fix.diff applied as -DFIXED) rejects the truncated ME firmware with err=-EINVAL and releases all six firmware blobs at out:, so no constant-bounded loader ever runs; baseline acceptance + 7552-byte OOB span reproduce 3/3. A patched-kernel build could not be runtime-tested: no SI GPU on this guest (radeon.ko never attaches), so the loader path cannot execute in-kernel.

run.fixed.log (clean rejection, exit 0) vs run.log (acceptance + SIGSEGV at i=256, exit 42); fix.diff
↓ fix.diffper-fix-DF-3591

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log: '[err clobbered: -EINVAL -> 0 by ce request_firmware success]' + 'si_init_microcode -> err=0 (ACCEPTED), me_fw=1024B' + 'SIGSEGV ... iteration i=256 ... 7552 bytes past allocation'; run.fixed.log: 'err=-22 (rejected) ... no loader runs'; fix.diff: 5 goto out insertions

PoC changes

seed was a sketch; wrote full statement-faithful transcription of si.c:1660-1954 + legacy ME loader; fixed fw filename prefix (radeonkmsfw_*), made the loader read volatile (compiler was DCE-ing the proof read) and stdout unbuffered so the SIGSEGV handler report survives _exit()

Verified recommended fix

Add 'goto out;' after err=-EINVAL in the me/ce/rlc/mc/smc Bogus-length blocks of si_init_microcode() (pfp parity)

Verdict

Statement-faithful on-guest harness (3/3): si_init_microcode() accepts a truncated legacy ME firmware because the me/ce/rlc/mc/smc 'Bogus length' blocks set err=-EINVAL without goto out (only pfp has it, si.c:1782) and the next block's successful request_firmware clobbers err to 0; the all-legacy final gate (si.c:1927) never re-checks. The constant-bounded legacy ME loader (si.c:3535) then reads SI_PM4_UCODE_SIZE dwords from the undersized buffer - guard-page fault at the exact predicted dword (i=256), 7552 bytes past a 1024-byte buffer; si_rlc_resume (si.c:5892) has the same shape. Runtime kernel-path needs SI hardware (absent: virtio-only guest, 0 AMD PCI devices), firmware is root-installed -> defense-in-depth, same class/rating as DF-3573 (cik.c). Fixed variant (5x goto out) rejects with -EINVAL and releases all fw at out:.