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)
PoC verification
Evidence pack
findings/poc/DF-3591 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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)
- 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 legacyrequest_firmware(ce, si.c:1822) assigns 0 toerr; rlc/mc/smc load cleanly;new_fw == 0leaves err untouched (si.c:1927);si_init_microcodereturns 0 with the undersized ME installed. - 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). - Fix validation: with the five
goto out;insertions (fix.diff), the harness (and by statement-equivalence the kernel) rejects the truncated ME witherr = -EINVAL, releases all six firmware blobs atout:(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_testableLogic-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
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:.
No comments yet.