evergreen_hdmi_enable / evergreen_dp_enable consume lvds-typed enc_priv as radeon_encoder_atom_dig on the eDP dpms path: dig->afmt OOB read at +288 into the adjacent slab chunk β guard falls through β wild deref feeding six WREG32 MMIO sites + dig->afmt->enabled write-through (DF-4635/DF-4828 family, new sinks; corrects DF-4828's dpms-route dismissal)
Summary
The guards only check !dig/!dig->afmt but for LCD-carrying digital encoders enc_priv is the 280-byte lvds struct - dig->afmt at +288 reads the first 8 bytes of the ADJACENT 288-byte chunk; non-NULL garbage falls through, feeds wild MMIO offsets and a write-through on every dpms transition. NEW trigger vs the family: eDP panels - eDP yields ENCODER_MODE_DP so the dpms gate PASSES (disproving DF-4828's dismissal) and radeon_audio_detect assigns hdmi_funcs to the eDP encoder: every blank/unblank on a DCE4+ eDP laptop walks it, unpriv-triggered. KLD-PROVEN on the live allocator (OOB read returns the groomed pointer, guard falls through, offset collected, enabled flips 0->1->0 through it); fix compile-validated on full radeon.ko -Werror.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4853 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| evg_dpms_probe.c | β | 8.1 KB | view raw | |
| evg_offsets.c | β | 4.0 KB | view raw | |
| Makefile | β | 63 B | β download | |
| build.sh | β | 139 B | view raw | |
| build.log | β | 565 B | view raw | |
| run.log | β | 903 B | view raw | |
| fix.diff | β | 1.4 KB | view raw | |
| fix_build.log | β | 2.7 KB | view raw | |
| env.txt | β | 230 B | view raw | |
| README.md | β | 3.4 KB | β raw | |
| VERDICT.md | β | 5.5 KB | β raw | |
| verdict.json | β | 6.2 KB | view raw | |
| manifest.json | β | 1.2 KB | view raw |
DF-4853 β evergreen_hdmi_enable/evergreen_dp_enable consume lvds-typed enc_priv (DF-4635/DF-4828 family, new sinks, eDP-dpms trigger)
What
evergreen_hdmi_enable() (sys/dev/drm/radeon/evergreen_hdmi.c:429-461) and
evergreen_dp_enable() (:470-514) load radeon_encoder->enc_priv as
struct radeon_encoder_atom_dig * and guard only if (!dig || !dig->afmt)
(:431, :473). For LCD-carrying digital encoders β every eDP panel β
enc_priv is a 280-byte radeon_encoder_lvds (atombios_encoders.c:2862-2869,
UNIPHY*/KLDSCP_LVTMA/DDI/DVO1 with ATOM_DEVICE_LCD_SUPPORT, and TMDS1/LVTM1
at :2824-2836), so:
dig->afmtat +288 reads 8 bytes past the 280-byte object (280 rounds to a 288-byte slab chunk, zero slack) = the first 8 bytes of the ADJACENT kernel object;- the NULL-guard consumes that garbage; if non-NULL it falls through and
dig->afmt->offset(wild read) drives six WREG32/WREG32_AND/WREG32_OR sites (:438-455), anddig->afmt->enabled = enable(:458, :513) is a wild write through the OOB-read pointer on every dpms transition.
Trigger (why this is NOT covered by DF-4828's dismissal)
DF-4828's verdict dismissed the dpms path: "r600_hdmi_enable is only reachable via radeon_audio_dpms, which is gated on encoder_mode == HDMI || DP β the mis-typed LVDS-mode encoders never take it". That misses eDP:
- eDP panel encoder = INTERNAL_UNIPHY* + ATOM_DEVICE_LCD_SUPPORT β
registered
DRM_MODE_ENCODER_LVDS,enc_priv = radeon_encoder_lvds*(atombios_encoders.c:2862-2869); - eDP connector detect β
radeon_dp_detect(radeon_connectors.c:1764-68) βradeon_audio_detect(radeon_audio.c:444): connector_type == eDP (β DRM_MODE_CONNECTOR_DisplayPort) β else branch βradeon_encoder->audio = rdev->audio.hdmi_funcs(radeon_audio.c:471; DCE4+:.dpms = evergreen_hdmi_enable, radeon_audio.c:206); - any dpms transition:
radeon_atom_encoder_dpms(atombios_encoders.c:1836) βatombios_get_encoder_modeβ eDP connector βATOM_ENCODER_MODE_DP(atombios_encoders.c:824-829) β the gate at :1848-1851 passes (ENCODER_MODE_IS_DP) βradeon_audio_dpms(radeon_audio.c:777-783) βevergreen_hdmi_enablewith the mis-typed enc_priv.
So on any DCE4+ (evergreen/NI/SI) laptop with an eDP panel and audio
enabled, every screen blank/unblank/mode-set walks the corrupted path.
evergreen_dp_enable is the DP-sink twin (reachable if a DisplayPort-class
connector is served by an LCD-carrying encoder).
Sibling in another file (noted for the queue):
atombios_get_encoder_mode itself derefs enc_priv as atom_dig
(dig_enc->active_mst_links, atombios_encoders.c:741-744, +304 OOB read).
Reproduce (live-allocator harness; no radeon GPU on this guest)
cd /tmp/df4853 && cc -O -o evg_offsets evg_offsets.c && ./evg_offsets
make && kldload -v /tmp/df4853/evg_probe.ko && kldunload evg_probe
Expected (run.log): dig->afmt (+288) reads the groomed adjacent-chunk
pointer, the :431 guard FALLS THROUGH, dig->afmt->offset (0x5678) feeds
the MMIO sites, and dig->afmt->enabled flips 0β1β0 through the OOB-read
pointer; zeroed neighbor = silent return (field-hiding case).
Files
evg_offsets.c (geometry: lvds=280, atom_dig=312, afmt@+288),
evg_dpms_probe.c + Makefile (KLD harness), build.sh, run.log,
build.log, fix_build.log (patched radeon.ko, -Werror), fix.diff,
VERDICT.md, env.txt.
DF-4853 β VERDICT
Bottom line
Inconclusive end-to-end (the radeon driver path is hardware-gated on
this guest: no radeon GPU, no /dev/dri β QEMU std VGA only), the type
confusion is proven statically with exact lines and machine-verified
offsets, and its memory-corruption primitive is proven LIVE on the guest
kernel with the exact DragonFly heap geometry (280-byte
radeon_encoder_lvds in a 288-byte chunk, zero slack): a KLD replica of
evergreen_hdmi_enable's deref sequence read dig->afmt (+288) from the
adjacent kernel object, the !dig || !dig->afmt guard fell through on
the groomed non-NULL neighbor, dig->afmt->offset (0x5678) was collected
as the value the six WREG32 sites would program, and
dig->afmt->enabled = enable wrote through the OOB-read pointer
(observed 0β1β0 in a controlled fake afmt, both dpms directions).
Confidence: certain (offsets machine-verified by evg_offsets.c: lvds=280,
atom_dig=312, afmt@+288; family precedent DF-4635/DF-4828).
Why this is a NEW finding (not DF-4635, not DF-4828)
- DF-4635 covers the sinks in radeon_audio.c:262-480
(
radeon_audio_enable's walk deref,radeon_audio_detect's dig->pin). The sinks here are in evergreen_hdmi.c (:431/:438-455/:458 and :473/:483-510/:513) β dispatched into byradeon_audio_dpms. - DF-4828 covers the sinks in r600_hdmi.c (audio-work walk:
buffer_status_changed / update_audio_settings), and its verdict
explicitly dismissed the dpms route: "r600_hdmi_enable β¦ only reachable
via radeon_audio_dpms β¦ gated on encoder_mode == HDMI || DP β the
mis-typed LVDS-mode encoders never take it". That dismissal is wrong
for eDP: an eDP connector yields
ATOM_ENCODER_MODE_DP(atombios_encoders.c:824-829) while its encoder carries the lvds-typed enc_priv (atombios_encoders.c:2862-2869), so the gate at atombios_encoders.c:1848-1851 PASSES, andradeon_audio_detect(radeon_audio.c:461-472) assigns hdmi_funcs (= evergreen sinks on DCE4/5/6) to the eDP encoder because DRM_MODE_CONNECTOR_eDP β DRM_MODE_CONNECTOR_DisplayPort. On a DCE4+ eDP laptop, every dpms transition (screen blank/unblank, any modeset, DRM_IOCTL_MODE_DPMS by an unprivileged DRM client) hits the mis-typed deref. - DF-1911 is the missing-mutex race on the shared walk β unrelated root.
Live primitive proof (run.log)
evg_probe: 8192 objs of 280 bytes; usable chunk = 288 (want 288) evg_probe: adjacent pair A=0xfffff80117c95900 B=0xfffff80117c95a20 (B-A=288) evg_probe: victim B[0..8) groomed to 0xffffffff82600380 (fake afmt) evg_probe: OOB READ dig->afmt (+288) == 0xffffffff82600380 (non-NULL -> guard at evergreen_hdmi.c:431 FALLS THROUGH) evg_probe: fake_afmt before: enabled=0 offset=0x5678 evg_probe: after evergreen_hdmi_enable(true): enabled=1 <- WILD WRITE via OOB-read pointer (evergreen_hdmi.c:458) evg_probe: collected 2 distinct MMIO sites; dig->afmt->offset == 0x5678 would be added to HDMI_INFOFRAME_CONTROL0/AFMT_AUDIO_PACKET_CONTROL WREG32s (:438-455) evg_probe: after evergreen_hdmi_enable(false): enabled=0 (write-through both directions) evg_probe: victim B[0..8) zeroed -> OOB read NULL -> replica touched 0 MMIO site(s), no write (silent; hides the bug)
The harness replicates the exact deref sequence of evergreen_hdmi.c :429-:458 (verbatim comments map each line); WREG32 is factored out (the offset VALUE it would program is collected), since programming real MMIO on a GPU-less guest is impossible and meaningless.
Exploit chain (hardware-gated, not runnable here)
Groom the shared 288-byte slab size class so a chosen victim object follows
the lvds enc_priv allocation β eDP dpms transition β
evergreen_hdmi_enable β OOB read dig->afmt = victim[0..8) β if
non-NULL: wild read (->offset drives HDMI_INFOFRAME_CONTROL0 /
AFMT_AUDIO_PACKET_CONTROL MMIO offsets β attacker-influenced register
programming), and wild write dig->afmt->enabled = enable (byte at
victim[0..8)+0) on every dpms on AND off. NULL neighbor silently returns
(hides the bug), garbage neighbor = panic, groomed victim = controlled
write-where (through-pointer, address = victim[0..8)) β crash-to-UAF
escalation path, same analysis as DF-4635/DF-4828 (DF slab 32-byte
granularity makes the cross-object effect DragonFly-specific in severity;
upstream Linux code is byte-identical at the sinks but masked by
kmalloc-512 slack).
Fix
fix.diff: type-gate both sinks β enc_priv is radeon_encoder_lvds*
iff the encoder was registered DRM_MODE_ENCODER_LVDS
(atombios_encoders.c:2824-2836, :2862-2869), so
if (!dig || encoder->encoder_type == DRM_MODE_ENCODER_LVDS) return;
in evergreen_hdmi_enable and evergreen_dp_enable. Compile-validated:
patch applies clean (hunks at 428/485), full radeon.ko rebuild with
-Werror succeeds including the patched evergreen_hdmi.o (fix_build.log,
RC=0). Behavioral validation impossible on this guest (no radeon GPU β
radeon_audio_dpms never executes); the KLD primitive harness is
driver-path-independent by construction. Family precedent: the same gate
shape compile-validated for DF-4828.
PoC fixes vs seed
No seed existed (new pass-2 finding). One -Werror fix during the KLD build
(uninitialized mmio[0] in a kprintf).
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 applies clean (hunks at 428/485) and the whole radeon.ko module rebuilds with -Werror on the guest toolchain including the patched evergreen_hdmi.o (fix_build.log, RC=0). Behavioral validation impossible on this guest: no radeon GPU, so radeon_audio_dpms (the only route into both patched functions) never executes here; the KLD primitive harness is driver-path-independent by construction (it validates the geometry and the deref sequence, not the gate).
fix.diff; fix_build.log (patched evergreen_hdmi.c compiled -Werror, radeon.ko relinked RC=0)
Confirmed kernel references
- sys/dev/drm/radeon/evergreen_hdmi.c:429-461
- sys/dev/drm/radeon/evergreen_hdmi.c:470-514
- sys/dev/drm/radeon/radeon_audio.c:206
- sys/dev/drm/radeon/radeon_audio.c:461-472
- sys/dev/drm/radeon/radeon_audio.c:777-783
- sys/dev/drm/radeon/atombios_encoders.c:1848-1851
- sys/dev/drm/radeon/atombios_encoders.c:824-829
- sys/dev/drm/radeon/atombios_encoders.c:2862-2869
- sys/dev/drm/radeon/radeon_connectors.c:1764-1768
- sys/dev/drm/radeon/radeon_mode.h:427-445
- sys/kern/kern_slaballoc.c:638
Detail
Exploit chain
(hardware-gated, not runnable here) groom the 288-byte slab size class so a chosen victim follows the lvds enc_priv allocation -> eDP dpms transition (screen blank/unblank, modeset, DRM_IOCTL_MODE_DPMS by an unprivileged DRM client) -> evergreen_hdmi_enable -> OOB read dig->afmt = victim[0..8) -> non-NULL: wild read (->offset programs HDMI_INFOFRAME_CONTROL0/AFMT_AUDIO_PACKET_CONTROL MMIO) + wild write dig->afmt->enabled at victim[0..8)+0 on every dpms on AND off -> NULL neighbor silently returns, garbage neighbor panics, groomed victim = controlled through-pointer write -> crash-to-UAF escalation path; deterministic per dpms transition on affected laptops.
Evidence (decisive lines)
run.log (KLD: usable chunk=288, adjacent pair B-A=288, OOB READ dig->afmt(+288)==groomed ptr non-NULL, guard at evergreen_hdmi.c:431 falls through, afmt->offset 0x5678 collected for WREG32 sites, afmt->enabled written 0->1->0 through the OOB-read pointer (:458), NULL case silent); build.log + evg_offsets output (lvds=280 atom_dig=312 afmt@+288 afmt.enabled@+0 machine-verified); fix_build.log (patched radeon.ko rebuilds -Werror, RC=0); VERDICT.md full dispatch-chain citations incl. the DF-4828 dismissal correction
PoC changes
no seed; wrote evg_dpms_probe.c KLD (verbatim replica of evergreen_hdmi_enable's deref sequence on the live allocator with groomable adjacent victim) + evg_offsets.c (verbatim struct defs). One -Werror fix during build (uninitialized mmio[0]). fix.diff generated with real diff -u and compile-validated by a full radeon.ko rebuild (-Werror).
Verified recommended fix
Type-gate both sinks: early-return in evergreen_hdmi_enable/evergreen_dp_enable when (!dig || encoder->encoder_type == DRM_MODE_ENCODER_LVDS) -- enc_priv is radeon_encoder_lvds exactly for LVDS-registered encoders; see fix.diff
Verdict
Driver path hardware-gated on this guest (no radeon GPU, no /dev/dri), so evergreen_hdmi_enable/evergreen_dp_enable can never execute here; the type confusion is nonetheless proven statically with exact lines and machine-verified offsets, and the corruption PRIMITIVE is proven live on the guest kernel: evergreen_hdmi.c:429 loads radeon_encoder->enc_priv as a 312-byte radeon_encoder_atom_dig, but every LCD-carrying digital encoder (each eDP panel: UNIPHY*+ATOM_DEVICE_LCD_SUPPORT, atombios_encoders.c:2862-2869 and :2824-2836) carries a 280-byte radeon_encoder_lvds; the guard at evergreen_hdmi.c:431 reads dig->afmt (+288) OUT OF BOUNDS -- 8 bytes into the ADJACENT 288-byte slab chunk (zero slack) -- and consumes the garbage: non-NULL falls through, dig->afmt->offset feeds six WREG32 sites (:438-455) and dig->afmt->enabled = enable (:458; twin at :513) is a write through the OOB-read pointer. NEW vs DF-4635 (radeon_audio.c:262-480 sinks) and DF-4828 (r600_hdmi.c walk sinks): these are evergreen_hdmi.c's own dpms sinks, and DF-4828's dismissal of the dpms route is wrong for eDP -- an eDP connector yields ATOM_ENCODER_MODE_DP (atombios_encoders.c:824-829) so the radeon_audio_dpms gate at atombios_encoders.c:1848-1851 PASSES while the encoder carries the lvds-typed enc_priv, and radeon_audio_detect (radeon_audio.c:461-472) assigns hdmi_funcs (.dpms = evergreen_hdmi_enable, radeon_audio.c:206) to the eDP encoder; every screen blank/unblank on a DCE4+ eDP laptop walks the corrupted path. KLD replica on the live allocator: OOB read returned the groomed adjacent-chunk pointer, the :431 guard fell through, afmt->offset 0x5678 collected as the MMIO value, afmt->enabled flipped 0->1->0 through the garbage pointer (both dpms directions); zeroed neighbor = silent return (field-hiding case).
No comments yet.