NULL-pointer dereference of radeon_encoder->enc_priv in dce6_audio_get_pin() encoder walk (dce6_afmt.c:104-105)
Summary
The pin walk derefs dig->pin for every digital encoder without checking enc_priv - which is NULL-able for those exact ids (missing/unparsable LVDS_Info or kzalloc failure). One NULL system-wide panics the next audio-capable display detect; audio enabled by default on DCE6+ (SI/CIK). Sibling unguarded derefs at radeon_audio.c:483/266-267; same code verbatim upstream. NULL-page read only - panic, not exploitable. Hardware-gated on guest. NULL-guard fix applies cleanly (dce6_afmt + radeon_audio siblings).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5028 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.7 KB | β raw | |
| VERDICT.md | β | 4.8 KB | β raw | |
| env.txt | β | 1023 B | view raw | |
| fix.diff | β | 612 B | view raw | |
| manifest.json | β | 1.0 KB | view raw | |
| verdict.json | β | 3.7 KB | view raw |
DF-5028 β NULL-pointer dereference of radeon_encoder->enc_priv in dce6_audio_get_pin() (dce6_afmt.c)
What this is
sys/dev/drm/radeon/dce6_afmt.c:104-105 dereferences dig->pin for every
digital encoder on the device's encoder list without first checking that
dig = radeon_encoder->enc_priv is non-NULL:
list_for_each_entry(encoder, &rdev->ddev->mode_config.encoder_list, head) {
if (radeon_encoder_is_digital(encoder)) { /* id-only test */
radeon_encoder = to_radeon_encoder(encoder);
dig = radeon_encoder->enc_priv;
if (dig->pin == pin) /* NULL deref */
pin_count++;
}
}
Why enc_priv can be NULL for a digital encoder
radeon_encoder_is_digital() (sys/dev/drm/radeon/radeon_encoders.c:423-445)
filters only by encoder_id. But for several of those very ids,
radeon_add_atom_encoder() assigns enc_priv from functions that can return
NULL:
atombios_encoders.c:2838and:2872β forINTERNAL_LVDS,INTERNAL_TMDS1,INTERNAL_KLDSCP_TMDS1,INTERNAL_LVTM1,INTERNAL_UNIPHY*withATOM_DEVICE_LCD_SUPPORT,enc_priv = radeon_atombios_get_lvds_info().radeon_atombios.c:1630-1640: when the VBIOS has no parsableLVDS_Infotable (atom_parse_data_header()fails) the function returns its still-NULLlvds; it also returns NULL whenkzallocfails (radeon_atombios.c:1639).atombios_encoders.c:2842/2876/2880βenc_priv = radeon_atombios_set_dig_info()returns NULL onkzallocfailure (atombios_encoders.c:2760-2763).
So an encoder whose id satisfies radeon_encoder_is_digital() can carry a NULL
enc_priv either from a missing/malformed VBIOS LVDS table or from a failed
allocation during encoder creation.
Reachability
Audio is enabled by default (radeon_audio = -1, sys/dev/drm/radeon/radeon_drv.c:180;
gate is radeon_audio != 0, radeon_connectors.c:1766). On every connector
(hot-)detect:
radeon_dp_detect / radeon_dvi_detect (radeon_connectors.c:1767, :1439)
-> radeon_audio_detect() (radeon_audio.c:444)
dig = radeon_encoder->enc_priv; ... dig->pin <- same unguarded deref at :483
-> radeon_audio_get_pin() (radeon_audio.c:425)
-> dce6_audio_get_pin() (dce6_afmt.c:86)
walks all encoders -> dig->pin (dce6_afmt.c:105)
The walk at dce6_afmt.c:101 touches every digital encoder on the system, not
just the one being detected, so a NULL enc_priv on any DCE6-era digital
encoder panics the machine during an audio-capable display detect. The sibling
unprotected derefs live in radeon_audio.c:483 (dig->pin in
radeon_audio_detect) and radeon_audio.c:267 (dig->pin in
radeon_audio_enable's identical walk) β same bug family, other file.
Impact
Kernel NULL-page read at a small fixed struct offset -> kernel panic. Not
exploitable beyond DoS (no controlled data at the dereferenced address; page 0
is unmapped). Trigger requires SI/CIK (DCE6+) radeon hardware with audio (the
default) and a VBIOS/an allocation state that leaves a digital encoder's
enc_priv NULL β hence Low severity. The same code exists verbatim in upstream
Linux drivers/gpu/drm/radeon/dce6_afmt.c.
Why no runtime PoC on this guest
The audit guest is QEMU/KVM with a Bochs std-VGA device
(vgapci0: chip=0x11111234, see env.txt). There is no AMD GPU, so
dce6_afmt.c never executes: radeon_audio_init is only reached on radeon
DCE6+ ASICs. Reproduction would need SI/CIK hardware (or GPU passthrough) plus
a doctored VBIOS (or forced allocation failure) β not possible here. The
finding is a source-level proof; see VERDICT.md.
DF-5028 β verdict narrative
Mode: pass-2 audit of sys/dev/drm/radeon/dce6_afmt.c (GLM 5.3)
Result: not reproduced at runtime (hardware-gated); source-level proof
The bug is a missing NULL check on radeon_encoder->enc_priv before
dig->pin in the encoder-list walk inside dce6_audio_get_pin()
(sys/dev/drm/radeon/dce6_afmt.c:104-105).
Proof chain (all path:line)
dce6_afmt.c:101-107β the walk dereferencesdig->pinfor every encoder for whichradeon_encoder_is_digital()is true. No NULL guard.radeon_encoders.c:423-445βradeon_encoder_is_digital()tests onlyradeon_encoder->encoder_id; nothing guaranteesenc_priv != NULL.atombios_encoders.c:2838, 2872β for digital encoder ids withATOM_DEVICE_LCD_SUPPORT,enc_priv = radeon_atombios_get_lvds_info().radeon_atombios.c:1626-1640βget_lvds_info()returns NULL whenatom_parse_data_header(..., LVDS_Info, ...)fails (no/invalid VBIOS table) and whenkzallocfails; caller does not check.atombios_encoders.c:2757-2766βset_dig_info()(the other digitalenc_privsource, lines 2842/2876/2880) returns NULL onkzallocfailure.- Reachable by default:
radeon_audio = -1(radeon_drv.c:180), audio gate isradeon_audio != 0(radeon_connectors.c:1766); detect paths callradeon_audio_detect()(radeon_connectors.c:1439, :1767) ->radeon_audio_get_pin()(radeon_audio.c:425) ->dce6_audio_get_pin()(dce6_afmt.c:86) on any audio-capable DCE6+ system. - Note the sibling unguarded derefs at
radeon_audio.c:483(detect) andradeon_audio.c:266-267(radeon_audio_enablewalk) β the same event would usually crash there first for the calling encoder; the dce6_afmt.c walk additionally crashes on any other digital encoder with NULLenc_privon the system.
Why the crash is a panic, not an exploit primitive
struct radeon_encoder_atom_dig is ~230 bytes; pin sits at a small fixed
offset, so the access is a read from the zero page (unmapped on amd64 with
vm.pmap.pg_ps_enabled-style protections; NULL page is never mappable by
userland on DragonFly). No attacker-controlled data is read or written. Impact
ceiling: kernel panic (DoS) on machines with anomalous VBIOS or under extreme
memory pressure. Not a memcorrupt primitive.
Verification attempt on the audit guest
uname -a: DragonFly 6.5-DEVELOPMENT X86_64_GENERIC (see env.txt).pciconf -l: only display device isvgapci0 ... chip=0x11111234(Bochs QEMU std VGA). No ATI/AMD GPU ->radeondriver never attaches ->dce6_afmt.ccode paths cannot execute on this guest.- Status therefore
not_reproduced(impactnonedemonstrated at runtime); the finding stands on the source-level chain above, which is deterministic.
Fix
fix.diff (this directory) adds the NULL guard in dce6_afmt.c:105 and the
primary sibling in radeon_audio.c:459 (radeon_audio_detect). A third
identical walk in radeon_audio.c:266 (radeon_audio_enable) should get the
same guard. Fix cannot be boot-validated without AMD hardware ->
fix_status: not_testable.
Other classes hunted in dce6_afmt.c and killed (pass-2 negative result)
- ELD/SAD walk bounds:
sads[]/sad_countcome fromdrm_edid_to_sad()(drm_edid.c:4034) which allocates exactlycount = dbl/3entries and returns that samecount; callerradeon_audio_write_sad_regs(radeon_audio.c:375-381) rejects<= 0.dce6_afmt_write_sad_regs'sfor (j = 0; j < sad_count; j++)(dce6_afmt.c:246) is therefore in-bounds by construction. sadb[0](dce6_afmt.c:181, :208) is guarded byif (sad_count);drm_edid_to_speaker_allocationreturns count==0 with sadb==NULL or a 3-byte kmemdup with count==3 β never a short non-NULL buffer.- pin-array bounds:
num_pins <= 7 == RADEON_MAX_AFMT_BLOCKS(radeon_audio.c:316-328, radeon_mode.h:52, radeon.h:1769) β walks at dce6_afmt.c:75 and :96 are in-bounds. - div-by-zero:
radeon_audio_decode_dfs_div()returns 0 for any out-of-range WDIVIDER field anddce6_afmt.c:326guardsif (div);clock*100 <= 60,000,000cannot overflow u32. - register write indexes: all endpoint writes go through
dce6_endpoint_rreg/wregunderrdev->end_idx_lock(dce6_afmt.c:49-52, :60-67) with offsets from the fixedpin_offsets[]table β no attacker-controlled index. - latency fields (dce6_afmt.c:144-156):
connector->latency_present[]/video_latency[]/audio_latency[]are fixed-size fields written by the EDID CEA parser, always in-bounds here.
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 adds the NULL guards (dce6_afmt.c:105, radeon_audio.c detect) but cannot be boot-validated on this guest: no AMD GPU means neither baseline nor patched kernel can execute the DCE6 audio path. Fix is trivially correct by inspection.
fix.diff (applies cleanly to pristine sys/, patch --dry-run verified); no runtime validation possible - see env.txt.
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md 'Proof chain' (7 steps, each path:line); env.txt documents the guest's display hardware (vgapci0 chip=0x11111234, Bochs std VGA) and stock X86_64_GENERIC lacking radeon hardware - the cited path cannot execute on this guest.
PoC changes
No runnable trigger possible on this guest (no AMD GPU; dce6_afmt executes only on DCE6+ ASICs). Verification was environment-level: confirmed the guest cannot execute the path; the finding stands on the deterministic source chain.
Verified recommended fix
NULL-check enc_priv before dereferencing dig->pin in dce6_audio_get_pin() (and the radeon_audio.c:483/:267 siblings): see fix.diff.
Verdict
dce6_audio_get_pin() (sys/dev/drm/radeon/dce6_afmt.c:104-105) dereferences dig->pin after dig = radeon_encoder->enc_priv without a NULL check for every encoder that radeon_encoder_is_digital() accepts; that predicate (radeon_encoders.c:423-445) tests only encoder_id, while radeon_add_atom_encoder() assigns enc_priv from radeon_atombios_get_lvds_info() (returns NULL when the VBIOS LVDS_Info table is missing/unparsable or kzalloc fails - radeon_atombios.c:1630-1640) and from radeon_atombios_set_dig_info() (returns NULL on kzalloc failure - atombios_encoders.c:2760-2763) for exactly those digital ids. Reachable by default (radeon_audio=-1, radeon_drv.c:180; gate radeon_audio != 0, radeon_connectors.c:1766) on any display detect via radeon_audio_detect (radeon_connectors.c:1439/:1767) -> radeon_audio_get_pin (radeon_audio.c:425) -> dce6_audio_get_pin; the walk crashes on ANY digital encoder system-wide with NULL enc_priv, and the sibling unguarded derefs are radeon_audio.c:483 and radeon_audio.c:267. Impact ceiling is a kernel NULL-page read = panic (DoS), not a corruption primitive. Not reproduced at runtime because the audit guest has no AMD GPU (Bochs VGA 1234:1111, env.txt): dce6 code only executes on SI/CIK ASICs; source-level proof chain is deterministic, hence confidence likely, severity Low (hardware+firmware anomaly or OOM precondition).
No comments yet.