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

Unguarded enc_priv derefs (NULL-page read AND write) for LCD-supporting digital encoders when radeon_atombios_get_lvds_info returns NULL

Summary

radeon_add_atom_encoder assigns enc_priv from get_lvds_info, which returns NULL when the LVDS_Info DATA table is missing/short or on kzalloc failure. Nine hot paths then dereference enc_priv with no NULL check, including NULL-page WRITES: dig->panel_mode (first DPMS ON) and dig->dig_encoder = -1 (encoder disable). Contrast the guards that do exist elsewhere in the file. VERIFIED: children executing the verbatim NULL+0 read/write and NULL+12 write die SIGSEGV as predicted. Crafted VBIOS with eDP/LVDS connector but no LVDS_Info -> boot panic. Upstream-inherited. Guard-at-nine-sinks fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3957 Β· 11 files
FileTypeDescriptionSize
README.md β€” 2.4 KB ↓ raw
VERDICT.md β€” 3.7 KB ↓ raw
trigger_df3957.c β€” 4.6 KB view raw
build.sh β€” 92 B view raw
run.sh β€” 116 B view raw
build.log β€” 89 B view raw
run.log β€” 2.0 KB view raw
env.txt β€” 725 B view raw
fix.diff β€” 2.8 KB view raw
manifest.json β€” 1.9 KB view raw
verdict.json β€” 3.9 KB view raw

DF-3957 β€” radeon atombios_encoders.c unguarded enc_priv derefs (NULL-page read and write)

File: sys/dev/drm/radeon/atombios_encoders.c (pass 2, GLM 5.3; pass 1: no findings) Severity: Low Β· CWE: CWE-476 Β· Confidence: likely Status: inconclusive on guest (no AMD GPU); offset-arithmetic simulation harness verified.

What

radeon_add_atom_encoder() assigns enc_priv = radeon_atombios_get_lvds_info() for LCD-supporting encoders (:2834-2838 LVDS family, :2868-2872 UNIPHY family). That function (radeon_atombios.c:1617-1755) returns NULL when the VBIOS LVDS_Info DATA table is missing (atom_parse_data_header fails) or on kzalloc failure β€” while the connector table still advertised ATOM_DEVICE_LCD_SUPPORT. Several hot paths then dereference enc_priv without a NULL check (contrast the guards that DO exist at :100, :240, :629, :2551):

Site Function Access
:742-744 atombios_get_encoder_mode read active_mst_links (NULL+8)
:1085 atombios_dig_transmitter_setup2 read dig_encoder (NULL+0) β€” boot, via radeon_atom_encoder_init :2314
:1695-97 radeon_atom_encoder_dpms_avivo read backlight_level (arg evaluation)
:1744 radeon_atom_encoder_dpms_dig write panel_mode (NULL+12)
:1783 radeon_atom_encoder_dpms_dig read backlight_level
:1796 radeon_atom_encoder_dpms_dig read active_mst_links
:2006-07 atombios_set_encoder_crtc_source read dig_encoder
:2270-72 radeon_atom_pick_dig_encoder read dig_encoder (any digital encoder)
:2661-63 radeon_atom_encoder_disable read + write dig_encoder = -1 (NULL+0)

All faults land in the NULL page β†’ kernel page fault β†’ panic at boot/mode-set. Upstream Linux radeon carries the identical unguarded paths (inherited defect); trigger is hardware+VBIOS-gated (eDP/LVDS connector in the BIOS connector table but no/short LVDS_Info table β€” seen in the wild on odd laptop BIOSes β€” or boot-time OOM).

Reproduce

cd findings/poc/DF-3957
./build.sh          # cc -O2 -Wall -o trigger_df3957 trigger_df3957.c
./run.sh

Expected (crash mode): both children SIGSEGV sig=11 β€” child1 the dig->dig_encoder read-then-write--1 (:2661-2663), child2 the dig->panel_mode write (:1744); RESULT: NULL-PAGE FAULTS AS PREDICTED (rc=0).

Fix

NULL-guard the sinks, matching the guards already present elsewhere in the file (fix.diff).

VERDICT.md
↓ download raw

DF-3957 VERDICT

Verdict: INCONCLUSIVE at kernel level (no AMD GPU) β€” source-certain NULL enc_priv dereference family including two NULL-page WRITES; the verbatim NULL+offset read/write expressions fault in the userland simulation exactly as predicted. Confidence: likely (NULL requires missing LVDS_Info table or kzalloc failure β€” both real-world-observed conditions, neither forceable without hardware).

Trace

  1. enc_priv can be NULL for a digital encoder. radeon_add_atom_encoder (atombios_encoders.c:2829-2883): every LCD-supporting digital encoder takes enc_priv = radeon_atombios_get_lvds_info(...) (:2838, :2872). That function (radeon_atombios.c:1617-1755) returns NULL both when atom_parse_data_header(... LVDS_Info ...) fails (missing/short table) and when its kzalloc(sizeof(struct radeon_encoder_atom_dig)) fails β€” the return is never checked at the assignment sites. An eDP/LVDS connector in the BIOS connector table with a missing LVDS_Info DATA table therefore yields a live digital encoder with enc_priv == NULL.

  2. Sinks (all verified unguarded; list in README). The most decisive: - atombios_dig_transmitter_setup2 :1074/:1085 loads dig->dig_encoder at NULL+0 β€” called at BOOT for every UNIPHY-family encoder by radeon_atom_encoder_init (:2314). - radeon_atom_encoder_dpms_dig :1744 writes dig->panel_mode at NULL+12 on the first DPMS ON of an LCD-supporting UNIPHY encoder (DCE4.1+, i.e. every KV/BT APU). - radeon_atom_encoder_disable :2661-2663 reads dig->dig_encoder then writes βˆ’1 to NULL+0 on encoder teardown. Neighboring code proves the invariant is not trusted elsewhere: NULL checks exist at :100 (set_backlight_level), :240 (backlight_init), :629 (digital_setup), :2551 (prepare) β€” the file is simply inconsistent, and upstream Linux radeon is identical (inherited).

  3. Non-issues ruled out while auditing this family (pass-2 depth): afmt[dig->dig_encoder] (:2557) is in-bounds β€” RADEON_MAX_AFMT_BLOCKS=7 (radeon_mode.h:52) and dig_encoder ≀ 6; MST fake encoders get encoder_id = 0 (NONE) so radeon_encoder_is_digital() is false for them β€” no type confusion through enc_priv (struct radeon_encoder_mst); sysctl_backlight_handler (:197-218) is registered only after the enc_priv NULL check at :240, so it adds no new NULL path (its lifetime vs. encoder destruction is a detach-race hardening note only).

Harness

Replicates the leading struct radeon_encoder_atom_dig fields; child1 executes the verbatim read-then-write(-1) of dig->dig_encoder (:2661-2663), child2 the dig->panel_mode = ... write (:1744) β€” both SIGSEGV sig=11 at NULL+0 / NULL+12. Analysis mode prints all four offsets (0, 8, 12).

Why Low

NULL-page read/write at fixed small offsets β†’ panic (DoS); no control of the written value beyond a constant βˆ’1; trigger requires radeon hardware with a BIOS whose connector table promises a panel the LVDS_Info table doesn't describe (or boot OOM). No unprivileged-local or remote vector. Pass 1 filed nothing on this file; this is the depth delta of pass 2.

Fix validation

fix.diff adds the missing NULL guards at all nine sinks (same pattern the file already uses at :629/:2551). Not built on the guest (no AMD GPU): fix_status: not_testable. Trivially sufficient by inspection.

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 against the read-only sys/ tree; not built - no AMD hardware on the guest. Guards remove all nine sinks by construction.

findings/poc/DF-3957/fix.diff
↓ fix.diffper-fix-DF-3957

Confirmed kernel references

Detail

Exploit chain

radeon GPU with BIOS connector table advertising an eDP/LVDS panel but a missing/short LVDS_Info table (or boot-time kzalloc failure) -> LCD-supporting digital encoder with enc_priv==NULL -> first transmitter init / DPMS ON / encoder disable -> NULL-page read/write -> kernel page fault -> panic (DoS). Written value is the constant -1; no control, no uid0 path.

Evidence (decisive lines)

run.log: ':742 dig_enc->active_mst_links READ at NULL+8', ':1085 ... NULL+0', ':1744 dig->panel_mode = ... WRITE at NULL+12', ':2663 dig->dig_encoder = -1 WRITE at NULL+0', 'child1 (disable :2661-2663): SIGNALLED sig=11', 'child2 (dpms_dig :1744): SIGNALLED sig=11', 'RESULT: NULL-PAGE FAULTS AS PREDICTED (rc=0)'; VERDICT.md; fix.diff

PoC changes

No seed (pass-2 self-authored). Two fix cycles: missing ; none behavioral.

Verified recommended fix

NULL-guard enc_priv at the nine sinks, mirroring the file's existing guards at :100/:240/:629/:2551 (fix.diff).

Verdict

Pass-2 depth delta on a pass-1-clean file (radeon twin of amdgpu DF-1380-era robustness gaps, different bug). radeon_add_atom_encoder (:2838/:2872) assigns enc_priv from radeon_atombios_get_lvds_info (radeon_atombios.c:1617-1755), which returns NULL when the VBIOS LVDS_Info DATA table is missing/short or on kzalloc failure - unchecked at both assignment sites. Nine sinks dereference it without a NULL guard (contrast existing guards at :100/:240/:629/:2551): reads at :742-744, :1085 (BOOT via radeon_atom_encoder_init :2314), :1695-97, :1783, :1796, :2006-07, :2270-72, :2661; NULL-page WRITES at :1744 (dig->panel_mode, first DPMS ON, DCE4.1+) and :2663 (dig->dig_encoder = -1 on disable). All fault in the NULL page -> panic (DoS); no unprivileged local or remote vector; upstream Linux radeon carries the identical code (inherited). Harness executes the verbatim NULL+0 read/write-1 and NULL+12 write in children: both SIGSEGV sig=11. Runtime-inconclusive: no AMD GPU on guest.