BIOS-count-driven OOB pointer write past rdev->i2c_bus[16] in radeon_atombios_i2c_init
Summary
num_indices = (BIOS u16 size - 4)/28 (up to 2340 with size=0xFFFF, unvalidated by atom_parse_data_header) drives 'rdev->i2c_bus[i] = radeon_i2c_create(...)' into the fixed array rdev->i2c_bus[RADEON_MAX_I2C_BUS=16]. i2c.valid is BIOS-gated (usClkMaskRegisterIndex != 0), so every OOB store is attacker-controlled: heap pointers overwrite the following radeon_device fields (debugfs[32], vm_manager, gpu_clock_mutex, atif/atcs). HARNESS on the guest: GPIO_I2C_Info size 4092 -> 146 indices -> 130 OOB pointer writes (1040 B) past i2c_bus[16]; worst case 2324 writes (18.5 KB). Crafted VBIOS on any ATOM radeon card (radeon.ko ships in default installs): kernel-heap corruption inside struct radeon_device at attach -> crash/DoS minimum, adjacent-structure corruption exploitable further. One-line cap fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3739 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.0 KB | β raw | |
| VERDICT.md | β | 2.8 KB | β raw | |
| build.log | β | 0 B | β download | |
| build.sh | β | 65 B | view raw | |
| env.txt | β | 375 B | view raw | |
| harness_i2c.c | β | 4.8 KB | view raw | |
| run.log | β | 479 B | view raw | |
| run.sh | β | 131 B | view raw | |
| README.md | β | 2.0 KB | β raw | |
| VERDICT.md | β | 2.8 KB | β raw | |
| manifest.json | β | 970 B | view raw | |
| verdict.json | β | 3.2 KB | view raw | |
| fix.diff | β | 563 B | view raw |
DF-3739 β BIOS-count-driven OOB pointer write past rdev->i2c_bus[16] in radeon_atombios_i2c_init
Severity: High Β· CWE: CWE-787 Out-of-bounds Write Β· File: sys/dev/drm/radeon/radeon_atombios.c:172-187
What this is
Source-certain heap-corruption write in the radeon ATOM I2C init path. atom_parse_data_header() (atom.c:1366-1385) returns the GPIO_I2C_Info table's self-declared u16 size with NO validation; radeon_atombios_i2c_init() derives num_indices=(size-4)/28 (up to 2340 with size=0xFFFF) and stores radeon_i2c_create() heap pointers into rdev->i2c_bus[i] (radeon.h:2440) whose capacity is RADEON_MAX_I2C_BUS=16 (radeon_mode.h:96). i2c.valid is BIOS-controlled (usClkMaskRegisterIndex!=0, :113-116), so every OOB store is attacker-gated. Entries 16..num_indices-1 overwrite the following radeon_device fields (debugfs[32], debugfs_count, vm_manager, gpu_clock_mutex, atif/atcs, ... radeon.h:2441+). Harness on guest: size=4092 -> 146 entries -> 130 OOB pointer writes (1040 B); worst case 0xFFFF -> 2324 OOB writes (18.5 KB). Not executable in-kernel on this guest (no AMD GPU); module present in /boot/kernel/radeon.ko.
Reproduce
./build.sh # cc -O1 -g -Wall -o <bin> harness_i2c.c (DragonFly guest, cc 8.3) ./run.sh # expected: num_indices=146 vs i2c_bus[16]; 130 OOB pointer writes (1040 bytes); worst case 2324 (18592 bytes); 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
cap the loop at RADEON_MAX_I2C_BUS β see fix.diff (authored after verification; never applied to sys/).
DF-3739 β BIOS-count-driven OOB pointer write past rdev->i2c_bus[16] in radeon_atombios_i2c_init
Severity: High Β· CWE: CWE-787 Out-of-bounds Write Β· File: sys/dev/drm/radeon/radeon_atombios.c:172-187
What this is
Source-certain heap-corruption write in the radeon ATOM I2C init path. atom_parse_data_header() (atom.c:1366-1385) returns the GPIO_I2C_Info table's self-declared u16 size with NO validation; radeon_atombios_i2c_init() derives num_indices=(size-4)/28 (up to 2340 with size=0xFFFF) and stores radeon_i2c_create() heap pointers into rdev->i2c_bus[i] (radeon.h:2440) whose capacity is RADEON_MAX_I2C_BUS=16 (radeon_mode.h:96). i2c.valid is BIOS-controlled (usClkMaskRegisterIndex!=0, :113-116), so every OOB store is attacker-gated. Entries 16..num_indices-1 overwrite the following radeon_device fields (debugfs[32], debugfs_count, vm_manager, gpu_clock_mutex, atif/atcs, ... radeon.h:2441+). Harness on guest: size=4092 -> 146 entries -> 130 OOB pointer writes (1040 B); worst case 0xFFFF -> 2324 OOB writes (18.5 KB). Not executable in-kernel on this guest (no AMD GPU); module present in /boot/kernel/radeon.ko.
Reproduce
./build.sh # cc -O1 -g -Wall -o <bin> harness_i2c.c (DragonFly guest, cc 8.3) ./run.sh # expected: num_indices=146 vs i2c_bus[16]; 130 OOB pointer writes (1040 bytes); worst case 2324 (18592 bytes); 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
cap the loop at RADEON_MAX_I2C_BUS β see fix.diff (authored after verification; never applied to sys/).
VERDICT β DF-3739
Status: inconclusive (source-certain; harness-reproduced arithmetic; kernel path unreachable on this guest) reproduced: 0 (in-kernel) β harness demonstration: YES Impact on real hardware: High β CWE-787 Out-of-bounds Write
Finding
Source-certain heap-corruption write in the radeon ATOM I2C init path. atom_parse_data_header() (atom.c:1366-1385) returns the GPIO_I2C_Info table's self-declared u16 size with NO validation; radeon_atombios_i2c_init() derives num_indices=(size-4)/28 (up to 2340 with size=0xFFFF) and stores radeon_i2c_create() heap pointers into rdev->i2c_bus[i] (radeon.h:2440) whose capacity is RADEON_MAX_I2C_BUS=16 (radeon_mode.h:96). i2c.valid is BIOS-controlled (usClkMaskRegisterIndex!=0, :113-116), so every OOB store is attacker-gated. Entries 16..num_indices-1 overwrite the following radeon_device fields (debugfs[32], debugfs_count, vm_manager, gpu_clock_mutex, atif/atcs, ... radeon.h:2441+). Harness on guest: size=4092 -> 146 entries -> 130 OOB pointer writes (1040 B); worst case 0xFFFF -> 2324 OOB writes (18.5 KB). Not executable in-kernel on this guest (no AMD GPU); module present in /boot/kernel/radeon.ko.
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_i2c.c).
Harness result
Command: ./run.sh (after ./build.sh) on the DragonFly guest Expected/observed: num_indices=146 vs i2c_bus[16]; 130 OOB pointer writes (1040 bytes); worst case 2324 (18592 bytes); exit_code=0 Full logs: build.log, run.log.
Impact / exploit chain
crafted VBIOS GPIO_I2C_Info table with size>=4+17*28=480 -> i2c valid entries -> heap pointer corruption inside struct radeon_device at driver attach (boot or kldload radeon) -> crash/DoS, potential further exploitation via corrupted adjacent structures (locks, vm_manager)
Fix
cap the loop at RADEON_MAX_I2C_BUS β fix.diff.
Honest notes
- DF-3739 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 β DF-3739
Status: inconclusive (source-certain; harness-reproduced arithmetic; kernel path unreachable on this guest) reproduced: 0 (in-kernel) β harness demonstration: YES Impact on real hardware: High β CWE-787 Out-of-bounds Write
Finding
Source-certain heap-corruption write in the radeon ATOM I2C init path. atom_parse_data_header() (atom.c:1366-1385) returns the GPIO_I2C_Info table's self-declared u16 size with NO validation; radeon_atombios_i2c_init() derives num_indices=(size-4)/28 (up to 2340 with size=0xFFFF) and stores radeon_i2c_create() heap pointers into rdev->i2c_bus[i] (radeon.h:2440) whose capacity is RADEON_MAX_I2C_BUS=16 (radeon_mode.h:96). i2c.valid is BIOS-controlled (usClkMaskRegisterIndex!=0, :113-116), so every OOB store is attacker-gated. Entries 16..num_indices-1 overwrite the following radeon_device fields (debugfs[32], debugfs_count, vm_manager, gpu_clock_mutex, atif/atcs, ... radeon.h:2441+). Harness on guest: size=4092 -> 146 entries -> 130 OOB pointer writes (1040 B); worst case 0xFFFF -> 2324 OOB writes (18.5 KB). Not executable in-kernel on this guest (no AMD GPU); module present in /boot/kernel/radeon.ko.
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_i2c.c).
Harness result
Command: ./run.sh (after ./build.sh) on the DragonFly guest Expected/observed: num_indices=146 vs i2c_bus[16]; 130 OOB pointer writes (1040 bytes); worst case 2324 (18592 bytes); exit_code=0 Full logs: build.log, run.log.
Impact / exploit chain
crafted VBIOS GPIO_I2C_Info table with size>=4+17*28=480 -> i2c valid entries -> heap pointer corruption inside struct radeon_device at driver attach (boot or kldload radeon) -> crash/DoS, potential further exploitation via corrupted adjacent structures (locks, vm_manager)
Fix
cap the loop at RADEON_MAX_I2C_BUS β fix.diff.
Honest notes
- DF-3739 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_testablefix.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)
Confirmed kernel references
Detail
Exploit chain
crafted VBIOS GPIO_I2C_Info table with size>=4+17*28=480 -> i2c valid entries -> heap pointer corruption inside struct radeon_device at driver attach (boot or kldload radeon) -> crash/DoS, potential further exploitation via corrupted adjacent structures (locks, vm_manager)
Evidence (decisive lines)
harness_i2c.c + build.log + run.log; VERDICT.md; kernel refs sys/dev/drm/radeon/radeon_atombios.c:172-187
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
cap the loop at RADEON_MAX_I2C_BUS
Verdict
Source-certain heap-corruption write in the radeon ATOM I2C init path. atom_parse_data_header() (atom.c:1366-1385) returns the GPIO_I2C_Info table's self-declared u16 size with NO validation; radeon_atombios_i2c_init() derives num_indices=(size-4)/28 (up to 2340 with size=0xFFFF) and stores radeon_i2c_create() heap pointers into rdev->i2c_bus[i] (radeon.h:2440) whose capacity is RADEON_MAX_I2C_BUS=16 (radeon_mode.h:96). i2c.valid is BIOS-controlled (usClkMaskRegisterIndex!=0, :113-116), so every OOB store is attacker-gated. Entries 16..num_indices-1 overwrite the following radeon_device fields (debugfs[32], debugfs_count, vm_manager, gpu_clock_mutex, atif/atcs, ... radeon.h:2441+). Harness on guest: size=4092 -> 146 entries -> 130 OOB pointer writes (1040 B); worst case 0xFFFF -> 2324 OOB writes (18.5 KB). Not executable in-kernel on this guest (no AMD GPU); module present in /boot/kernel/radeon.ko.
No comments yet.