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)
PoC verification
Evidence pack
findings/poc/DF-3957 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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).
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
-
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 whenatom_parse_data_header(... LVDS_Info ...)fails (missing/short table) and when itskzalloc(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 withenc_priv == NULL. -
Sinks (all verified unguarded; list in README). The most decisive: -
atombios_dig_transmitter_setup2:1074/:1085 loadsdig->dig_encoderat NULL+0 β called at BOOT for every UNIPHY-family encoder byradeon_atom_encoder_init(:2314). -radeon_atom_encoder_dpms_dig:1744 writesdig->panel_modeat 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 readsdig->dig_encoderthen 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). -
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 getencoder_id = 0(NONE) soradeon_encoder_is_digital()is false for them β no type confusion throughenc_priv(struct radeon_encoder_mst);sysctl_backlight_handler(:197-218) is registered only after theenc_privNULL 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_testablefix.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
Confirmed kernel references
- sys/dev/drm/radeon/atombios_encoders.c:742
- sys/dev/drm/radeon/atombios_encoders.c:1085
- sys/dev/drm/radeon/atombios_encoders.c:1744
- sys/dev/drm/radeon/atombios_encoders.c:2006
- sys/dev/drm/radeon/atombios_encoders.c:2270
- sys/dev/drm/radeon/atombios_encoders.c:2661
- sys/dev/drm/radeon/atombios_encoders.c:2872
- sys/dev/drm/radeon/radeon_atombios.c:1617
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
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.
No comments yet.