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

Unvalidated VBIOS container offsets in r600_parse_extended_power_table: usFanTableOffset and usExtendendedHeaderOffset dereferenced with no zero/bounds check β€” offset 0 aliases the PowerPlayInfo header as the extended header; out-of-range offsets read past the kmalloc'd BIOS image (sibling of DF-1566: offset provenance, not counts)

Summary

All six sub-table offsets inside the extended header are checked for non-zero, but the two container offsets are not checked at all: the fan-table pointer accepts any u16 offset past the end of the kmalloc'd BIOS buffer, and ext_hdr is dereferenced with no validation - offset 0 reads the PowerPlayInfo header as the extended header, turning all six downstream table offsets into garbage pointing outside small ROMs. No ROM size is plumbed anywhere in the radeon driver (the shared root of the atom.c DF-4337 radeon-side assessment). Plumb bios_size + validate fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4372 Β· 5 files
FileTypeDescriptionSize
README.md β€” 2.8 KB ↓ raw
VERDICT.md β€” 3.2 KB ↓ raw
fix.diff β€” 5.3 KB view raw
manifest.json β€” 1.1 KB view raw
verdict.json β€” 2.3 KB view raw

DF-4372 β€” r600_parse_extended_power_table dereferences two VBIOS offsets

without any validation (fan table offset, extended-header offset)

  • File: sys/dev/drm/radeon/r600_dpm.c (:891-918 fan table, :1068-1073 ext header + every downstream ext-table condition)
  • Severity: Low (sibling of DF-1566 β€” distinct missing checks: offset provenance rather than count/index bounds; same hostile-VBIOS threat)
  • Class: CWE-125 / CWE-20 (unvalidated firmware offset β†’ OOB heap reads and pointer confusion of the entire extended-table parse).

The bug

Every sub-table inside the extended header checks its own offset field (usVCETableOffset, usUVDTableOffset, …) for non-zero, but the two container offsets are used with no check at all:

  1. Fan table β€” r600_dpm.c:893-895: fan_info = bios + data_offset + usFanTableOffset and immediately dereferenced (ucTHyst, usTMin, …, and for ucFanTableFormat >= 2 the fan2/fan3 unions at :903-915). usFanTableOffset is a raw u16 from the ROM: 0 is pre-checked, but any value up to 65535 is accepted even when data_offset + offset (+ record size) lies beyond the kmalloc'd BIOS image (radeon_bios.c:68/98/153/269/717, ≀ 256 KiB) β†’ OOB heap read; with ucFanTableFormat itself read out of bounds the walk can chain further.

  2. Extended header β€” r600_dpm.c:1070-1073: ext_hdr = bios + data_offset + usExtendendedHeaderOffset is dereferenced (ext_hdr->usSize) with no zero check and no bound. A crafted table with usExtendendedHeaderOffset == 0 aliases the PowerPlayInfo table header itself as the extended header: usSize and all six sub-table offsets then come from unrelated header fields, and each subsequent ext_hdr->usXTableOffset (used at :1074-1293 for VCE/UVD/SAMU/PPM/ACP/ PowerTune) can point anywhere within a 64 KiB window of bios β€” far outside the actual allocation on small ROMs. The downstream count/index walks are DF-1566's known territory; the pointer provenance itself is unchecked here.

Trigger

Crafted VBIOS on a radeon DPM-family card (modified ROM dump, hostile option ROM, or ACPI VFCT image β€” radeon_bios.c:717 copies the VFCT image into the same kmalloc buffer). Boot β†’ radeon attach β†’ radeon_pm_init β†’ <asic>_dpm_init β†’ r600_parse_extended_power_table.

Fix

fix.diff plumbs the real ROM size (rdev->bios_size, set at every allocation site in radeon_bios.c) into the parser and (a) rejects a fan table offset that runs past the image, (b) computes ext_hdr only when usExtendendedHeaderOffset != 0 and data_offset + ext_off + sizeof(ATOM_PPLIB_EXTENDEDHEADER) <= bios_size, gating all six downstream conditions on the result (ext_hdr_ok). Verified patch -p1 --dry-run β†’ APPLIES_CLEAN on scratch copies (radeon.h, radeon_bios.c, r600_dpm.c).

VERDICT.md
↓ download raw

DF-4372 VERDICT β€” unchecked fan/ext-header offsets in r600_parse_extended_power_table

Bottom line

Certain by static proof; PoC phase not executed (Low severity, and the audit VM has no radeon GPU / no /dev/dri). Two container offsets from the VBIOS are dereferenced with no zero/bounds validation, distinct from the count/index walks already covered by DF-1566.

Detail

  • r600_dpm.c:893-895 β€” fan_info derived from usFanTableOffset (u16) and dereferenced at :896-915, including the versioned union reads (fan2.usTMax at :904, fan3.ucFanControlMode/usFanPWMMax/ usFanOutputSensitivity at :909-914) whose format byte (ucFanTableFormat) itself comes from the same unvalidated location.
  • r600_dpm.c:1070-1073 β€” ext_hdr derived from usExtendendedHeaderOffset with no check whatsoever: offset 0 reads the PowerPlayInfo header as the extended header (in-bounds but wrong object β€” all derived sub-table offsets become header garbage); an offset beyond the image reads off the end of the ≀256 KiB rdev->bios kmalloc buffer. Six downstream blocks (:1074-1293) then chase the garbage offsets; their internal count/index behavior is DF-1566 (not re-reported).
  • atom_parse_data_header() (atom.c) already returns an unbounded data_offset (noted in DF-1566); the container-offset checks added by this finding's fix close the remaining provenance holes inside this parser.

Threat model / impact

Requires control of the GPU VBIOS (physically modified card, option-ROM injection, or ACPI VFCT image at radeon_bios.c:717). Impact: OOB kernel heap reads (values copied into rdev->pm.dpm.fan.* and dyn_state tables, potentially observable via hwmon/debugfs on some setups) and mis-programmed fan/voltage behavior. Consistent with DF-1566's Medium rating for the stronger count-walk variant; the offset-provenance half is narrower β†’ Low.

Verification on the audit VM

Not attempted at runtime: no radeon hardware (QEMU virtio VGA only), no /dev/dri, VBIOS not attacker-controllable in this VM. Status untested, impact none on this guest; the code defect itself is certain from inspection.

Fix validation

fix.diff (3 files: radeon.h adds bios_size; radeon_bios.c records it at all five allocation sites :68/:98/:153/:269/:717; r600_dpm.c validates both offsets and gates the six ext-table conditions on ext_hdr_ok). Verified patch -p1 --dry-run β†’ APPLIES_CLEAN on scratch copies. Runtime validation impossible on this VM β†’ fix_status: not_testable.

kernel_refs

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.diff authored post-verification (radeon.h + radeon_bios.c + r600_dpm.c); applies cleanly via patch -p1 --dry-run on scratch copies; runtime validation impossible on this VM (no radeon hardware)

findings/poc/DF-4372/fix.diff; dry-run output 'checking file ... radeon.h / radeon_bios.c / r600_dpm.c / DF4372_APPLIES_CLEAN'
↓ fix.diffper-fix-DF-4372

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md; README.md; fix.diff (3-file) verified APPLIES_CLEAN

PoC changes

none (no runnable PoC possible without radeon hardware and a crafted VBIOS)

Verified recommended fix

Plumb rdev->bios_size from the allocation sites and validate usFanTableOffset / usExtendendedHeaderOffset (non-zero, within image) before any dereference; gate all six ext-table blocks on the validated header (see fix.diff)

Verdict

PoC phase not executed for this Low-severity finding (per audit contract PoC is optional below Medium; additionally the audit VM has no radeon GPU, no /dev/dri, and the VBIOS is not attacker-controllable in a QEMU virtio-gpu VM). Defect is certain from inspection: r600_parse_extended_power_table dereferences usFanTableOffset (r600_dpm.c:893-895, versioned union reads :903-915) and usExtendendedHeaderOffset (r600_dpm.c:1070-1073) with no zero/bounds validation; offset 0 aliases the PowerPlayInfo header as the extended header and out-of-range offsets read past the <=256KiB kmalloc'd BIOS image. Sibling of DF-1566 (which covers the count/index walks); this covers the container-offset provenance.