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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3854 Β· 11 files
FileTypeDescriptionSize
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
README.md readme build/run/expected + hardware reproduction notes
↓ download 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)

  1. rdev->bios == NULL: r100.c:4056-4059 deliberately continues after radeon_get_bios() failure on pre-AVIVO ASICs.
  2. rdev->flags & RADEON_IS_IGP (RS100/RS200/RS300/RS400/RS480).
  3. 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; then radeon_legacy_encoders.c:985 calls radeon_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.

VERDICT.md verdict full narrative with path:line trace
↓ download raw

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

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

one-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
↓ fix.diffper-fix-DF-3854

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.