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

SS-assignment walk bounded only by the table's own usStructureSize β€” up to 64KB OOB read past the ATOM table

Summary

The SS-assignment walk is bounded solely by the VBIOS-controlled usStructureSize header, never validated against the actual table/ROM extent - the size the data-table lookup returns is the SAME header field, so it cannot bound the walk either. A crafted oversized header walks past the BIOS image reading from adjacent kernel heap until an unmapped page faults or a coincidental match stops it. The fix must validate idx+size against the ROM image length in the atom layer - which this tree's atom_context cannot even express (no bios_size member). Bios-length plumbing fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4289 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.7 KB ↓ raw
VERDICT.md β€” 2.8 KB ↓ raw
fix.diff β€” 2.0 KB view raw
manifest.json β€” 795 B view raw
verdict.json β€” 2.8 KB view raw
env.txt β€” 962 B view raw

DF-4289 β€” SS-assignment walk bounded only by unvalidated VBIOS usStructureSize β†’ OOB read (≀64 KB past ATOM table)

Pass-2 finding for sys/dev/drm/amd/powerplay/hwmgr/ppatomctrl.c. Distinct from DF-1498 (ASIC_ProfilingInfo leakage arrays): this is the ASIC_InternalSS_Info walk in asic_internal_ss_get_ss_asignment() feeding atomctrl_get_engine_clock_spread_spectrum() / atomctrl_get_memory_clock_spread_spectrum().

The bug (ppatomctrl.c:1231-1243)

ssInfo = &table->asSpreadSpectrum[0];

while (((uint8_t *)ssInfo - (uint8_t *)table) <
        le16_to_cpu(table->sHeader.usStructureSize)) {
        if ((clockSource == ssInfo->ucClockIndication) && ...)
                break;
        ssInfo = (ATOM_ASIC_SS_ASSIGNMENT *)((uint8_t *)ssInfo +
                sizeof(ATOM_ASIC_SS_ASSIGNMENT));
}
  • table comes from smu_atom_get_data_table(..., &size, ...) (ppatomctrl.c:1204-1207). The atom layer returns a raw pointer into the BIOS image (bios + data_start) and its size/usStructureSize are VBIOS-controlled; amdgpu_atom_parse_data_header (sys/dev/drm/amd/amdgpu/atom.c:1384-1403) validates nothing.
  • The only loop bound is usStructureSize β€” the table's own header field β€” never checked against the actual allocated table size. A VBIOS with usStructureSize (USHORT) larger than the real table walks ATOM_ASIC_SS_ASSIGNMENT (10-byte) entries past the end of the BIOS image: up to 65535 bytes past the table base, dereferencing ucClockIndication and ulTargetClockRange each step (OOB read, CWE-125). On x86-64 kernel heap that is adjacent-kernel-memory read until an unmapped page or a coincidental match stops it; the values are then copied into ssEntry (speed_spectrum_percentage, speed_spectrum_rate) and used to program spread-spectrum PLL registers by the callers (ci/iceland/fiji/tonga/vegam smumgr).

Callers (attach-time, chip-specific)

atomctrl_get_engine_clock_spread_spectrum ← ci_smumgr.c:343, iceland_smumgr.c:843; atomctrl_get_memory_clock_spread_spectrum ← ci_smumgr.c:1084, iceland_smumgr.c:~1090 family β€” all during SMC table construction at driver attach.

Threat model

Crafted VBIOS on an AMD dGPU (same as DF-1498): boot/module-load OOB read of adjacent kernel heap (bounded 64 KB), potential panic on unmapped page, or silent programming of garbage SS parameters.

Reproduce

Not runnable on this guest (no AMD dGPU β€” env.txt). Trigger shape on hardware: ASIC_InternalSS_Info table whose header usStructureSize exceeds the table's actual size with no entry matching the requested clockSource. Expected: OOB read walk / eventual Fatal trap 12 on unmapped page during attach.

VERDICT.md
↓ download raw

DF-4289 VERDICT β€” SS-assignment OOB walk

Status: inconclusive (code-trace certain; guest has no AMD dGPU)

Trace

  • asic_internal_ss_get_ss_asignment() β€” ppatomctrl.c:1215-1273.
  • :1226-1229 table = asic_internal_ss_get_ss_table(hwmgr->adev); NULL-checked.
  • :1204-1207 the table is smu_atom_get_data_table(..., &size, &frev, &crev) β€” size is fetched but never used anywhere in the function.
  • :1231-1243 walk bound is exclusively le16_to_cpu(table->sHeader.usStructureSize) β€” a field inside the (attacker-supplied) table content, not the atom-layer table size.
  • Each iteration reads ssInfo->ucClockIndication (:1235) and ssInfo->ulTargetClockRange (:1236) β€” reads happen at the current offset before the bound is re-checked, so a usStructureSize larger than the real table immediately reads past it; stride sizeof(ATOM_ASIC_SS_ASSIGNMENT) (fixed), max excursion ≀ 0xFFFF bytes.
  • Atom layer (sys/dev/drm/amd/amdgpu/atom.c:1384-1403): amdgpu_atom_parse_data_header() performs no validation of idx, *size, or the bios buffer bounds; smu_atom_get_data_table (smu_helper.c:660-673) passes the raw pointer through.
  • Exit conditions: match on (clockSource, clockSpeed ≀ ulTargetClockRange) or bound reached. Non-matching crafted table β‡’ full 64 KB excursion.
  • Output ssEntry fields (:1246-1254): speed_spectrum_percentage, speed_spectrum_rate (raw VBIOS USHORT, and for table rev β‰₯ 2.2/3 divided by the constant 100 at :1253 β€” see DF-4290 for the div0 that this value feeds), speed_spectrum_mode. These are consumed by ci_smumgr.c:343-356 / :1084-1092 and equivalents in iceland/fiji/tonga/vegam smumgr to program CG_SPLL_SPREAD_SPECTRUM / MPLL_SS registers.

Bound check on the other dimension (verified safe)

The fixed-stride walk starts at offsetof(ATOM_ASIC_INTERNAL_SS_INFO, asSpreadSpectrum[0]) and the bound is checked before each iteration's reads, so for a correct usStructureSize there is no OOB; the defect is solely the unvalidated upper bound. No write occurs (pure read walk + copy-out into the caller's pp_atomctrl_internal_ss_info).

Impact

Boot/module-load OOB read of adjacent kernel heap (CWE-125), panic on unmapped page possible, or programming of garbage PLL/SS register values (hardware-stability impact). Crafted-VBIOS threat model β‡’ Medium, consistent with DF-1498. Not runnable on this guest (no AMD dGPU) β‡’ inconclusive status, trace-certain.

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 no→ patch + rebuild →patched clean

Baseline behavior not reproducible on this guest (no AMD dGPU) -> no fixed-kernel build possible. fix.diff authored from trace.

findings/poc/DF-4289/fix.diff (not applied to sys/; authored post-verification from line-accurate trace)
↓ fix.diffper-fix-DF-4289

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (trace incl. the size==usStructureSize identity); README.md; fix.diff (atom.c ROM-bounds validation + bios_size plumbing); env.txt

PoC changes

n/a β€” no AMD dGPU on guest; code-trace evidence pack

Verified recommended fix

Track BIOS image length in atom_context and reject data tables whose idx+size exceed it in amdgpu_atom_parse_data_header

Verdict

asic_internal_ss_get_ss_asignment() (ppatomctrl.c:1215-1273) bounds its ATOM_ASIC_SS_ASSIGNMENT walk solely by the table header's usStructureSize (ppatomctrl.c:1233-1234) β€” a VBIOS-controlled USHORT that is never validated against the actual data-table/ROM extent (amdgpu_atom_parse_data_header, sys/dev/drm/amd/amdgpu/atom.c:1384-1403, performs no validation and struct atom_context does not even track the BIOS image length). A crafted ASIC_InternalSS_Info table with an oversized usStructureSize and no matching ucClockIndication walks the fixed 10-byte stride past the end of the BIOS image, reading ucClockIndication/ulTargetClockRange from adjacent kernel heap for up to 65535 bytes (CWE-125); results are programmed into CG_SPLL/MPLL spread-spectrum registers by the ci/iceland/fiji/tonga/vegam smumgr callers at attach. Note: the size returned by smu_atom_get_data_table IS usStructureSize (same header field), so the existing fetch-and-ignore of size cannot bound the walk β€” the fix must live in the atom layer (validate idx+size <= ROM length), which is what fix.diff implements. Same crafted-VBIOS threat model and Medium band as DF-1498. NOT runnable on this guest (no AMD dGPU; no drm module loads β€” env.txt); trace-certain.