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

VoltageObjectInfo walkers trust self-declared sizes β€” OOB heap reads, v1 stride bug, and ~130 KB unbounded leakage-array indices

Summary

atom_lookup_voltage_object_v1/v2/v3 walk bounded only by the table's own u16 usStructureSize (0xFFF8 walks ~16k objects over a 64-byte table, far past the image); the returned OOB objects are dereferenced by radeon_atom_get_max/min_voltage, get_voltage_step, get_svi2_info. v1 also advances ucNumOfVoltageEntries BYTES instead of x sizeof(VOLTAGE_LUT_ENTRY=4), mis-parsing every subsequent object. radeon_atom_get_leakage_vddc_based_on_leakage_params checks only the fixed header size then indexes vddc_buf[j*ucElbVDDC_Num+i] with two BIOS u8 counts - element 65024, ~130 KB past the array base. HARNESS: 16290 OOB reads past image end; stride bug 7 vs 16 bytes; leakage index 129538 B past array base. Crafted VoltageObjectInfo/ASIC_ProfilingInfo: OOB heap reads at DPM init -> crash + mis-parsed voltage objects (wrong voltages programmed).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3746 Β· 13 files
FileTypeDescriptionSize
README.md β€” 2.2 KB ↓ raw
VERDICT.md β€” 2.7 KB ↓ raw
build.log β€” 0 B ↓ download
build.sh β€” 65 B view raw
env.txt β€” 375 B view raw
harness_voltobj.c β€” 3.8 KB view raw
run.log β€” 579 B view raw
run.sh β€” 108 B view raw
README.md β€” 2.2 KB ↓ raw
VERDICT.md β€” 2.7 KB ↓ raw
manifest.json β€” 1.1 KB view raw
verdict.json β€” 3.1 KB view raw
fix.diff β€” 939 B view raw

DF-3746 β€” VoltageObjectInfo walkers trust self-declared sizes β€” OOB heap reads, v1 stride bug, and unbounded leakage-array indices

Severity: Medium Β· CWE: CWE-125 Out-of-bounds Read Β· File: sys/dev/drm/radeon/radeon_atombios.c:3382-3431, 3237-3280

What this is

The voltage-object family reads past the VBIOS image on self-declared data: (1) atom_lookup_voltage_object_v1/v2/v3 (:3382-3431) walk bounded only by the table's own usStructureSize (u16) β€” size=0xFFF8 walks ~16k objects over a 64-byte table and past the image (harness: 16290 OOB reads); returned OOB objects are then dereferenced by radeon_atom_get_max/min_voltage, get_voltage_step, get_svi2_info. (2) v1 advance bug: offset += offsetof(...)+ucNumOfVoltageEntries BYTES while entries are VOLTAGE_LUT_ENTRY=4 bytes (:3393-3394) β€” v2 correctly multiplies by sizeof (:3410-3411) β€” so v1 mis-parses every subsequent object. (3) radeon_atom_get_leakage_vddc_based_on_leakage_params checks only size<sizeof(header) (:3237) then indexes vddc_buf[j*ucElbVDDC_Num+i] with two BIOS u8 counts β€” up to element 65024 (~130 KB past the array base, far past any VBIOS image). Kernel path unreachable on this guest.

Reproduce

./build.sh    # cc -O1 -g -Wall -o <bin> harness_voltobj.c   (DragonFly guest, cc 8.3)
./run.sh      # expected: v1 walk with self-declared size 0xFFF8 over a 64-byte table: 16290 reads past image end; stride bug: 7 vs 16 bytes for 3 entries; leakage index 64769 -> 129538 B past array; exit_code=0

The harness replicates the kernel parse arithmetic VERBATIM (cited path:line) against a synthetic crafted-VBIOS buffer with canary-guarded destinations.

Guest / environment

Guest has NO AMD/radeon GPU (virtio-only); /boot/kernel/radeon.ko exists (code shipped in the default install) but cannot attach, so the kernel code path is unreachable at runtime on this guest. Evidence is source-level certainty (path:line traced) plus a verbatim-arithmetic userland harness compiled and executed on the guest (build.log/run.log). See env.txt (uname, cc, kldstat, /boot/kernel/radeon.ko presence).

Fix

bound walks by the image length (bios_size plumbing) and fix the v1 stride β€” see fix.diff (authored after verification; never applied to sys/).

DF-3746 β€” VoltageObjectInfo walkers trust self-declared sizes β€” OOB heap reads, v1 stride bug, and unbounded leakage-array indices

Severity: Medium Β· CWE: CWE-125 Out-of-bounds Read Β· File: sys/dev/drm/radeon/radeon_atombios.c:3382-3431, 3237-3280

What this is

The voltage-object family reads past the VBIOS image on self-declared data: (1) atom_lookup_voltage_object_v1/v2/v3 (:3382-3431) walk bounded only by the table's own usStructureSize (u16) β€” size=0xFFF8 walks ~16k objects over a 64-byte table and past the image (harness: 16290 OOB reads); returned OOB objects are then dereferenced by radeon_atom_get_max/min_voltage, get_voltage_step, get_svi2_info. (2) v1 advance bug: offset += offsetof(...)+ucNumOfVoltageEntries BYTES while entries are VOLTAGE_LUT_ENTRY=4 bytes (:3393-3394) β€” v2 correctly multiplies by sizeof (:3410-3411) β€” so v1 mis-parses every subsequent object. (3) radeon_atom_get_leakage_vddc_based_on_leakage_params checks only size<sizeof(header) (:3237) then indexes vddc_buf[j*ucElbVDDC_Num+i] with two BIOS u8 counts β€” up to element 65024 (~130 KB past the array base, far past any VBIOS image). Kernel path unreachable on this guest.

Reproduce

./build.sh    # cc -O1 -g -Wall -o <bin> harness_voltobj.c   (DragonFly guest, cc 8.3)
./run.sh      # expected: v1 walk with self-declared size 0xFFF8 over a 64-byte table: 16290 reads past image end; stride bug: 7 vs 16 bytes for 3 entries; leakage index 64769 -> 129538 B past array; exit_code=0

The harness replicates the kernel parse arithmetic VERBATIM (cited path:line) against a synthetic crafted-VBIOS buffer with canary-guarded destinations.

Guest / environment

Guest has NO AMD/radeon GPU (virtio-only); /boot/kernel/radeon.ko exists (code shipped in the default install) but cannot attach, so the kernel code path is unreachable at runtime on this guest. Evidence is source-level certainty (path:line traced) plus a verbatim-arithmetic userland harness compiled and executed on the guest (build.log/run.log). See env.txt (uname, cc, kldstat, /boot/kernel/radeon.ko presence).

Fix

bound walks by the image length (bios_size plumbing) and fix the v1 stride β€” see fix.diff (authored after verification; never applied to sys/).

VERDICT.md
↓ download raw

VERDICT β€” DF-3746

Status: inconclusive (source-certain; harness-reproduced arithmetic; kernel path unreachable on this guest) reproduced: 0 (in-kernel) β€” harness demonstration: YES Impact on real hardware: Medium β€” CWE-125 Out-of-bounds Read

Finding

The voltage-object family reads past the VBIOS image on self-declared data: (1) atom_lookup_voltage_object_v1/v2/v3 (:3382-3431) walk bounded only by the table's own usStructureSize (u16) β€” size=0xFFF8 walks ~16k objects over a 64-byte table and past the image (harness: 16290 OOB reads); returned OOB objects are then dereferenced by radeon_atom_get_max/min_voltage, get_voltage_step, get_svi2_info. (2) v1 advance bug: offset += offsetof(...)+ucNumOfVoltageEntries BYTES while entries are VOLTAGE_LUT_ENTRY=4 bytes (:3393-3394) β€” v2 correctly multiplies by sizeof (:3410-3411) β€” so v1 mis-parses every subsequent object. (3) radeon_atom_get_leakage_vddc_based_on_leakage_params checks only size<sizeof(header) (:3237) then indexes vddc_buf[j*ucElbVDDC_Num+i] with two BIOS u8 counts β€” up to element 65024 (~130 KB past the array base, far past any VBIOS image). Kernel path unreachable on this guest.

Why not reproduced in-kernel

The QEMU guest is virtio-only (no AMD GPU): pciconf -l shows no 1002 device; kldstat shows radeon not loaded. /boot/kernel/radeon.ko exists (the vulnerable code ships in the default install) but cannot attach, so radeon_atombios.c parse paths never execute here. All conclusions are source-level (path:line above) plus the on-guest harness (harness_voltobj.c).

Harness result

Command: ./run.sh (after ./build.sh) Expected/observed: v1 walk with self-declared size 0xFFF8 over a 64-byte table: 16290 reads past image end; stride bug: 7 vs 16 bytes for 3 entries; leakage index 64769 -> 129538 B past array; exit_code=0 Full logs: build.log, run.log.

Impact / exploit chain

crafted VoltageObjectInfo/ASIC_ProfilingInfo tables -> OOB heap reads at DPM init -> wrong voltage objects used (mis-programmed voltages) and/or crash

Fix

bound walks by the image length (bios_size plumbing) and fix the v1 stride β€” fix.diff.

Honest notes

  • DF-3746 does NOT re-report pass-1 findings DF-1198/1199/1200 (power-table state_index mismatch, asic_ss_info underflow, power-table indices) β€” different functions/tables/fields.
  • fix validation not possible on this guest (no hardware); fix.diff is source-authored only.

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.

VERDICT.md
↓ download raw

VERDICT β€” DF-3746

Status: inconclusive (source-certain; harness-reproduced arithmetic; kernel path unreachable on this guest) reproduced: 0 (in-kernel) β€” harness demonstration: YES Impact on real hardware: Medium β€” CWE-125 Out-of-bounds Read

Finding

The voltage-object family reads past the VBIOS image on self-declared data: (1) atom_lookup_voltage_object_v1/v2/v3 (:3382-3431) walk bounded only by the table's own usStructureSize (u16) β€” size=0xFFF8 walks ~16k objects over a 64-byte table and past the image (harness: 16290 OOB reads); returned OOB objects are then dereferenced by radeon_atom_get_max/min_voltage, get_voltage_step, get_svi2_info. (2) v1 advance bug: offset += offsetof(...)+ucNumOfVoltageEntries BYTES while entries are VOLTAGE_LUT_ENTRY=4 bytes (:3393-3394) β€” v2 correctly multiplies by sizeof (:3410-3411) β€” so v1 mis-parses every subsequent object. (3) radeon_atom_get_leakage_vddc_based_on_leakage_params checks only size<sizeof(header) (:3237) then indexes vddc_buf[j*ucElbVDDC_Num+i] with two BIOS u8 counts β€” up to element 65024 (~130 KB past the array base, far past any VBIOS image). Kernel path unreachable on this guest.

Why not reproduced in-kernel

The QEMU guest is virtio-only (no AMD GPU): pciconf -l shows no 1002 device; kldstat shows radeon not loaded. /boot/kernel/radeon.ko exists (the vulnerable code ships in the default install) but cannot attach, so radeon_atombios.c parse paths never execute here. All conclusions are source-level (path:line above) plus the on-guest harness (harness_voltobj.c).

Harness result

Command: ./run.sh (after ./build.sh) Expected/observed: v1 walk with self-declared size 0xFFF8 over a 64-byte table: 16290 reads past image end; stride bug: 7 vs 16 bytes for 3 entries; leakage index 64769 -> 129538 B past array; exit_code=0 Full logs: build.log, run.log.

Impact / exploit chain

crafted VoltageObjectInfo/ASIC_ProfilingInfo tables -> OOB heap reads at DPM init -> wrong voltage objects used (mis-programmed voltages) and/or crash

Fix

bound walks by the image length (bios_size plumbing) and fix the v1 stride β€” fix.diff.

Honest notes

  • DF-3746 does NOT re-report pass-1 findings DF-1198/1199/1200 (power-table state_index mismatch, asic_ss_info underflow, power-table indices) β€” different functions/tables/fields.
  • fix validation not possible on this guest (no hardware); fix.diff is source-authored only.

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 after source-level verification; cannot be validated on this guest (no AMD GPU; radeon.ko cannot attach). Compiling the module or booting a patched kernel would not exercise the path without hardware.

fix.diff (unified, against read-only sys/ tree; never applied)
↓ fix.diffper-fix-DF-3746

Confirmed kernel references

Detail

Exploit chain

crafted VoltageObjectInfo/ASIC_ProfilingInfo tables -> OOB heap reads at DPM init -> wrong voltage objects used (mis-programmed voltages) and/or crash

Evidence (decisive lines)

harness_voltobj.c + build.log + run.log; VERDICT.md; kernel refs sys/dev/drm/radeon/radeon_atombios.c:3382-3397

PoC changes

harness written from scratch (no seed PoC): verbatim kernel arithmetic replica with canary-guarded destination model, built and executed on the DragonFly guest (cc 8.3); fixed nested-comment, stddef/stdint includes and volatile divisors during bring-up

Verified recommended fix

bound walks by the image length (bios_size plumbing) and fix the v1 stride

Verdict

The voltage-object family reads past the VBIOS image on self-declared data: (1) atom_lookup_voltage_object_v1/v2/v3 (:3382-3431) walk bounded only by the table's own usStructureSize (u16) β€” size=0xFFF8 walks ~16k objects over a 64-byte table and past the image (harness: 16290 OOB reads); returned OOB objects are then dereferenced by radeon_atom_get_max/min_voltage, get_voltage_step, get_svi2_info. (2) v1 advance bug: offset += offsetof(...)+ucNumOfVoltageEntries BYTES while entries are VOLTAGE_LUT_ENTRY=4 bytes (:3393-3394) β€” v2 correctly multiplies by sizeof (:3410-3411) β€” so v1 mis-parses every subsequent object. (3) radeon_atom_get_leakage_vddc_based_on_leakage_params checks only size<sizeof(header) (:3237) then indexes vddc_buf[j*ucElbVDDC_Num+i] with two BIOS u8 counts β€” up to element 65024 (~130 KB past the array base, far past any VBIOS image). Kernel path unreachable on this guest.