NULL-deref: 'rev = RBIOS8(offset)' executed before 'if (offset)' in radeon_combios_external_tmds_setup() IGP branch β panic when rdev->bios == NULL
Summary
The IGP branch reads the table revision before testing whether the table offset is nonzero: offset comes from combios_get_table_offset() which returns 0 when rdev->bios == NULL, and r100_init() deliberately continues after radeon_get_bios() failure on pre-AVIVO chips (only AVIVO aborts), so bios==NULL is a live state on exactly the RS100-RS480 IGP boards this branch serves. RBIOS8(0) then reads bios[0] with bios==NULL - kernel page fault, panic at the first external-TMDS modeset. The premature read is dead code (re-read inside the guard); present verbatim upstream. HARNESS: segfault exit 139 on the replicated order. IGP board with unreadable ROM + external-TMDS from the connector-table path. One-line delete fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3854 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | replicates the exact check-order deref with bios==NULL; dies with SIGSEGV | 2.5 KB | view raw |
| build.sh | build-script | cc -O2 -Wall -o harness harness.c | 65 B | view raw |
| run.sh | run-script | ./harness (crash-tolerant wrapper for the SIGFPE/SIGSEGV harnesses) | 89 B | view raw |
| build.log | build-log | full compiler output (guest) | 65 B | view raw |
| run.log | run-log | full runtime output (guest) - decisive | 549 B | view raw |
| fix.diff | suggested-fix | NULL-deref: RBIOS8(offset) executed before if (offset) in radeon_combios_external_tmds_setup IGP branch - git/patch-applyable vs sys/ | 304 B | view raw |
| fix_apply.log | apply-log | patch --dry-run -p1 -d /usr/src on the guest: clean apply | 314 B | view raw |
| VERDICT.md | verdict | full narrative with path:line trace | 3.1 KB | β raw |
| README.md | readme | build/run/expected + hardware reproduction notes | 1.6 KB | β raw |
| env.txt | environment | guest uname + toolchain + 'dmesg radeon lines: 0' | 210 B | view raw |
| verdict.json | verdict-json | machine verdict (persist_poc.py input) | 3.2 KB | view raw |
DF-3854 β rev = RBIOS8(offset) executed before if (offset) β NULL-deref panic
Verdict (one line)
CONFIRMED REAL by source trace; harness replicates the exact deref order and
dies with SIGSEGV. Kernel-side panic NOT observable on the audit guest (no
radeon GPU, no IGP board) β inconclusive.
Build / run
./build.sh # cc -O2 -Wall -o harness harness.c ./run.sh # runs the harness, records the SIGSEGV
Expected output
== DF-3854: IGP board, ROM unreadable -> rdev->bios == NULL == r100_init: pre-AVIVO, radeon_get_bios() failed, driver continues ... combios_get_table_offset(TMDS_POWER_ON) = 0x0000 (NULL bios -> 0, radeon_combios.c:131) executing radeon_combios.c:2890: rev = RBIOS8(offset); i.e. bios[0] with bios == (nil) ... harness exit=139 (139=128+11 SIGSEGV => in-kernel: page fault on NULL -> panic)
Preconditions (all three source-cited)
rdev->bios == NULL:r100.c:4056-4059deliberately continues afterradeon_get_bios()failure on pre-AVIVO ASICs.rdev->flags & RADEON_IS_IGP(RS100/RS200/RS300/RS400/RS480).- An external-TMDS encoder reaches mode set: forced connector table
(root module param
radeon.connector_table=selecting CT_POWERBOOK_EXTERNAL / CT_MINI_EXTERNAL / CT_MAC_X800 / CT_MAC_G5_9600 β the no-BIOS fallback path β radeon_combios.c:1447-1528) or a PPC Mac auto-table; thenradeon_legacy_encoders.c:985callsradeon_combios_external_tmds_setup().
Low severity: needs a dead ROM on an IGP board plus a root-forced (or Mac-specific) connector table; impact is a NULL-page kernel panic (boot/mode set DoS), not corruption.
DF-3854 β VERDICT
Status: inconclusive (deref order proven by trace + harness SIGSEGV; kernel run hardware-gated, no radeon GPU on audit guest)
Full narrative
The bug
radeon_combios_external_tmds_setup() IGP branch
(sys/dev/drm/radeon/radeon_combios.c:2888-2891):
if (rdev->flags & RADEON_IS_IGP) {
offset = combios_get_table_offset(dev, COMBIOS_TMDS_POWER_ON_TABLE);
rev = RBIOS8(offset); /* <-- deref BEFORE the check */
if (offset) {
rev = RBIOS8(offset);
combios_get_table_offset() returns 0 when rdev->bios == NULL
(radeon_combios.c:131). RBIOS8(0) is rdev->bios[0] (radeon.h:2686).
So when the BIOS is absent the function reads address 0 before testing the
offset it just got. The read at :2890 is dead code (re-read at :2892 inside
the guard) β a pure check-order slip, present verbatim upstream
(Linux v5.10 radeon_combios.c:2900) β inherited, not a port regression.
Why bios == NULL is a live state on exactly these chips
r100_init() (sys/dev/drm/radeon/r100.c:4056-4059):
if (!radeon_get_bios(rdev)) {
if (ASIC_IS_AVIVO(rdev)) /* only AVIVO aborts */
return -EINVAL;
}
... radeon_combios_init(rdev); /* combios-era chips continue with bios==NULL */
RS100βRS480 IGP chipsets are pre-AVIVO, combios-only. A board whose ROM
cannot be read (BAR unavailable, shadow disabled, VFCT absent β occurs on
real hardware) leaves rdev->bios == NULL and the driver loads in
table-fallback mode.
Reaching the encoder
With no BIOS, connector info comes from
radeon_get_legacy_connector_info_from_table() (radeon_combios.c:1447+),
selected by the radeon.connector_table= module parameter or the PPC
PowerMac tables (:1455-1520). CT_POWERBOOK_EXTERNAL / CT_MINI_EXTERNAL /
CT_MAC_X800 / CT_MAC_G5_9600 create DFP2 (external TMDS) encoders. The first
mode set on such an encoder runs radeon_legacy_tmds_ext_mode_set() β
radeon_combios_external_tmds_setup() (radeon_legacy_encoders.c:985) β
NULL deref β kernel page fault at address 0 β panic.
Impact ceiling: local boot/mode-set denial of service on misconfigured IGP hardware; requires root-set module param on x86 (or a PowerMac with a dead ROM). Severity Low.
Fix
fix.diff removes the premature read (one line). Apply-verified
(fix_apply.log); runtime fix validation not possible (no hardware):
fix_status = not_testable.
kernel_refs
- sys/dev/drm/radeon/radeon_combios.c:2888-2891 (bug)
- sys/dev/drm/radeon/radeon_combios.c:124-132 (offset 0 on NULL bios)
- sys/dev/drm/radeon/radeon.h:2686 (RBIOS8)
- sys/dev/drm/radeon/r100.c:4056-4059 (bios==NULL continues, pre-AVIVO)
- sys/dev/drm/radeon/radeon_legacy_encoders.c:985 (caller)
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_testableone-line fix.diff applies cleanly to pristine /usr/src (fix_apply.log); runtime fix validation impossible on this guest (no hardware)
fix.diff; fix_apply.log
Confirmed kernel references
Detail
Exploit chain
root sets radeon.connector_table to an external-TMDS table (or PPC Mac auto-detect) on an IGP board whose ROM read failed -> driver loads with bios==NULL (pre-AVIVO does not abort) -> table-fallback connector info creates DFP2/ext-TMDS encoder -> first mode set -> radeon_combios_external_tmds_setup -> RBIOS8(0) on NULL -> panic (boot/mode-set DoS)
Evidence (decisive lines)
run.log decisive lines: 'combios_get_table_offset(TMDS_POWER_ON) = 0x0000 (NULL bios -> 0 ...)' then 'Segmentation fault (core dumped)', exit=139; VERDICT.md full path:line trace; fix_apply.log: one-line fix applies to pristine /usr/src (hunk at 2887)
PoC changes
harness written from scratch (no seed); exact deref order of the kernel function replicated
Verified recommended fix
Delete the premature rev = RBIOS8(offset) before if (offset) (fix.diff, one line)
Verdict
Source-certain NULL dereference by check order: radeon_combios_external_tmds_setup() IGP branch executes rev = RBIOS8(offset) BEFORE testing if (offset) (radeon_combios.c:2889-2891). combios_get_table_offset() returns 0 when rdev->bios == NULL (radeon_combios.c:131) and r100_init() deliberately continues after radeon_get_bios() failure on pre-AVIVO chips (r100.c:4056-4059), so bios==NULL is a live state on exactly the RS100-RS480 IGP boards this branch serves; RBIOS8(0) then reads rdev->bios[0] with bios==NULL -> kernel page fault at address 0 -> panic at the first external-TMDS modeset (radeon_legacy_encoders.c:985). Harness replicates the deref order and dies with SIGSEGV (run.log: 'Segmentation fault (core dumped)', exit 139). Kernel-side panic not observable on the virtio-only audit guest (0 radeon devices). Precondition chain keeps severity Low: IGP board + unreadable ROM + ext-TMDS encoder (forced connector table via root module param, or PPC Mac auto-table). The dead read is present verbatim upstream (Linux v5.10:2900) - inherited, not a port regression.
No comments yet.