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

amdgpu_atombios_lookup_voltage_object_v3: zero usSize strides forever; walk bounded only by an unvalidated usStructureSize (hang / OOB read at DPM init)

Summary

The voltage-object walk advances offset by the BIOS u16 usSize; usSize==0 on the first non-matching object makes offset never advance - infinite loop. The bound itself (usStructureSize) is an unvalidated BIOS u16, so a large claimed size walks up to ~64KB past the VoltageObjectInfo table inside the kmalloced BIOS image. Callers run at DPM init/driver bind. The radeon twin is in DF-3741; the adjacent get_voltage_table count clamp is correct - only this walk is unguarded. Floor-the-stride fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4155 Β· 4 files
FileTypeDescriptionSize
vbios_mutator.py trigger Illustrative trigger (hardware-gated) 1.4 KB view raw
VERDICT.md verdict Full source-trace analysis 2.5 KB ↓ raw
fix.diff suggested-fix Break the lookup walk when usSize < sizeof(ATOM_VOLTAGE_OBJECT_V3) (zero-stride) instead of adding it to offset. 960 B view raw
env.txt environment Guest uname + pciconf hardware-gate proof 980 B view raw
VERDICT.md verdict Full source-trace analysis
↓ download raw

DF-4155 β€” amdgpu_atombios_lookup_voltage_object_v3: zero usSize strides forever; walk bounded only by an unvalidated usStructureSize

File: sys/dev/drm/amd/amdgpu/amdgpu_atombios.c (pass 2)

Reproduced?

No β€” hardware-gated (no AMD GPU on the audit guest; see env.txt). Established at source level.

Source trace (amdgpu_atombios_lookup_voltage_object_v3, lines 1415-1430)

u32 size = le16_to_cpu(v3->sHeader.usStructureSize);       /* BIOS u16, unvalidated */
u32 offset = offsetof(ATOM_VOLTAGE_OBJECT_INFO_V3_1, asVoltageObj[0]);
while (offset < size) {
        ATOM_VOLTAGE_OBJECT_V3 *vo = (ATOM_VOLTAGE_OBJECT_V3 *)(start + offset);
        if (type/mode match) return vo;
        offset += le16_to_cpu(vo->asGpioVoltageObj.sHeader.usSize);  /* may be 0 */
}

Two defects:

  1. Zero-stride infinite loop: usSize == 0 on the first (non-matching) voltage object β‡’ offset never advances β‡’ the loop re-reads the same header forever β†’ hard hang. Callers: amdgpu_atombios_get_svi2_info (:1452), amdgpu_atombios_is_voltage_gpio (:1494), and amdgpu_atombios_get_voltage_table (:1533) β€” all invoked during amdgpu DPM init at driver bind with a crafted VBIOS. The radeon twin (atom_lookup_voltage_object_v3) is explicitly listed in DF-3741.

  2. Unvalidated bound: the loop runs to usStructureSize (up to 65535), never against the actual BIOS allocation length (atom_context has no bios_size β€” DF-4104 enabler). A large claimed size makes the walk read up to ~64 KB past the VoltageObjectInfo table inside the kmalloced image β†’ OOB kernel heap reads (headers, ucVoltageType/Mode comparisons) until a lucky match, the offset limit, or a fault.

Note the sibling amdgpu_atombios_get_voltage_table (:1539-1540) does correctly clamp ucGpioEntryNum > MAX_VOLTAGE_ENTRIES (32) β€” the DF-1574/ DF-4003 count family is bounded at this site; only the lookup walk above is defective.

Verdict

INCONCLUSIVE (hardware-gated / not reachable on the audit guest). Source-certain: deterministic hang via usSize == 0; OOB reads via unvalidated usStructureSize walk bound. Severity Medium (crafted-VBIOS DoS + OOB read at DPM init, hardware-gated).

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

Fix not testable: single-fix kernel build cannot exercise adv(4)/amdgpu paths without the respective hardware on the guest.

fix.diff authored post-verification from line-accurate source trace (VERDICT.md); env.txt documents absent hardware.
↓ fix.diffper-fix-DF-4155

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (zero-stride analysis + callers), fix.diff (obj_size floor), env.txt (guest has no AMD GPU).

PoC changes

Seed replaced with an honest hardware-gated trigger sketch/mutator; no runnable PoC exists for this defect class on the audit guest.

Verified recommended fix

Break the lookup walk when usSize < sizeof(ATOM_VOLTAGE_OBJECT_V3) (zero-stride) instead of adding it to offset.

Verdict

INCONCLUSIVE (hardware-gated). amdgpu_atombios_lookup_voltage_object_v3 (amdgpu_atombios.c:1415-1430): usSize==0 on a non-matching voltage object makes offset never advance -> infinite loop at DPM init (callers get_svi2_info :1452, is_voltage_gpio :1494, get_voltage_table :1533); the walk bound usStructureSize is itself an unvalidated BIOS u16, so a large claimed size walks OOB past the BIOS heap allocation. radeon twin named in DF-3741. Note get_voltage_table's ucGpioEntryNum IS correctly clamped (:1539) -- this lookup walk is the unguarded sibling.