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

NULL amdgpu_encoder / enc_priv dereference family after best_encoder() in the two scaling-mode property handlers and amdgpu_connector_get_monitor_bpc (radeon DF-4033 twin)

Summary

Three sites assume best_encoder never returns NULL and enc_priv is always allocated: the get_monitor_bpc eDP/LVDS branch (best_encoder returns NULL on an empty possible-encoder list; enc_priv initialized NULL and only filled if the dig/lcd info kzallocs succeed) - both dereferenced unchecked (the same file guards enc_priv elsewhere); and the two scaling-mode property handlers via DRM_MASTER-gated SETPROPERTY. An encoder-less connector arises when the VBIOS device bits match no encoder. to_amdgpu_encoder(NULL) = container_of(NULL) -> deterministic NULL-page panic. NULL-guard fix at all three sites.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4201 Β· 11 files
FileTypeDescriptionSize
README.md β€” 2.9 KB ↓ raw
VERDICT.md β€” 3.0 KB ↓ raw
trigger_df4201.c β€” 4.5 KB view raw
build.sh β€” 87 B view raw
run.sh β€” 89 B view raw
build.log β€” 9 B view raw
run.log β€” 108 B view raw
env.txt β€” 244 B view raw
fix.diff β€” 1.7 KB view raw
manifest.json β€” 1.0 KB view raw
verdict.json β€” 3.7 KB view raw

DF-4201 β€” NULL amdgpu_encoder dereference family after best_encoder() (radeon DF-4033 twin)

File: sys/dev/drm/amd/amdgpu/amdgpu_connectors.c:139-141, 583-594, 792-803 (pass 2) Severity: Low (VBIOS-misconfig-gated NULL deref panic; DRM_MASTER-gated ioctls / modeset path) Status: not reproduced at runtime β€” hardware-gated; source-level certainty.

Sites

  1. amdgpu_connector_get_monitor_bpc() β€” eDP/LVDS branch when display_info.bpc == 0 (no EDID / EDID without bpc): c struct drm_encoder *encoder = connector_funcs->best_encoder(connector); /* :139 may be NULL */ struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); /* :140 NULL->container */ struct amdgpu_encoder_atom_dig *dig = amdgpu_encoder->enc_priv; /* :141 */ if (dig->lcd_misc & ...) /* :143 deref */ Two NULLs in the chain: best_encoder() (NULL on an empty possible-encoder list β€” amdgpu_connector_best_single_encoder :346-357, amdgpu_connector_dvi_encoder :1122-1148 both return NULL), and enc_priv itself, which dce_v*_0_encoder_add initializes to NULL (dce_v10_0.c:3494) and only replaces if amdgpu_atombios_encoder_get_lcd_info()/get_dig_info() kzallocs succeed (dce_v10_0.c:3520/3524/3528). The same file knows to guard enc_priv β€” see the coherent-mode handler at :484 β€” but not here. Reached from every modeset (atombios_crtc.c:773, atombios_dp.c:261, dce_v{8,10,11}_0.c).

  2. amdgpu_connector_set_property() β€” scaling-mode branch: c } else { amdgpu_encoder = to_amdgpu_encoder(connector_funcs->best_encoder(connector)); /* :584 */ } ... if (amdgpu_encoder->rmx_type == rmx_type) /* :594 NULL deref */ Reached via DRM_IOCTL_MODE_SETPROPERTY / DRM_IOCTL_MODE_OBJ_SETPROPERTY (both DRM_MASTER-gated β€” drm_ioctl.c:667/679): the display session owner.

  3. amdgpu_connector_set_lcd_property() β€” identical pattern, deref at :803. Same ioctls (this is the eDP/LVDS .set_property).

Reach

An empty (or NULL-producing) possible-encoder list requires the VBIOS to describe a connector whose device bits no encoder claims β€” amdgpu_link_encoder_connector() (amdgpu_encoders.c:45-56) then attaches nothing. Atom-table parser quirk territory; upstream Linux carries the identical unguarded code.

Evidence

  • trigger_df4201.c β€” raw-ioctl scaling-mode write over every connector property (becomes DRM master, enumerates connectors, writes DRM_MODE_SCALE_CENTER). Builds and runs on the guest; guest has no AMD GPU (env.txt), so the amdgpu handlers are unreachable there.
  • fix.diff β€” NULL guards at all three sites (applies cleanly to a copy of the real file; cumulative with DF-4200/4202 diffs).

Fix

See fix.diff (guard best_encoder() result at :584/:793; guard both encoder and enc_priv in the monitor-bpc branch).

VERDICT.md
↓ download raw

DF-4201 VERDICT β€” NULL encoder deref family (DF-4033 amdgpu twin)

Verdict: not reproduced at runtime (hardware-gated); source-level certainty: certain. Impact if exercised: kernel NULL-deref panic (DoS) from the display-session owner.

Proof of the NULL sources

  • amdgpu_connector_best_single_encoder() (amdgpu_connectors.c:346-357) returns NULL when drm_connector_for_each_possible_encoder yields nothing; amdgpu_connector_dvi_encoder() (:1122-1148) returns NULL the same way (its type-filter loop can miss, then the fallback "pick the first one" loop also finds none).
  • Possible encoders are attached only by amdgpu_link_encoder_connector() (amdgpu_encoders.c:45-56): a connector whose VBIOS device bits match no encoder ends with an empty list. The connector is still created and registered (amdgpu_connector_add, amdgpu_connectors.c:1499-1900 β€” encoder linkage happens later and is never checked).
  • enc_priv NULL: dce_v8_0.c:3382, dce_v10_0.c:3494, dce_v11_0.c:3626 all start with amdgpu_encoder->enc_priv = NULL and only fill it if the atom-parser kzallocs succeed (amdgpu_atombios_encoder_get_lcd_info / amdgpu_atombios_encoder_get_dig_info). The coherent-mode handler in this very file guards it (if (!amdgpu_encoder->enc_priv) return 0; amdgpu_connectors.c:484-485) β€” the monitor-bpc path at :141 does not.

Consumers of the NULL

  • amdgpu_connector_get_monitor_bpc() :139-143 β€” called on every modeset from atombios_crtc.c:773 (crtc prepare), atombios_dp.c:261 (link bpp), dce_v8_0.c:444 / dce_v10_0.c:504 / dce_v11_0.c:530.
  • amdgpu_connector_set_property() :583-594 β€” scaling-mode property write (DRM_MASTER-gated ioctls, drm_ioctl.c:667/679).
  • amdgpu_connector_set_lcd_property() :792-803 β€” the eDP/LVDS .set_property for the same property (same ioctls).

to_amdgpu_encoder(NULL) = container_of(NULL, ...) (amdgpu_mode.h:58); base is the first member, so the :594/:803 rmx_type reads fault at a small fixed offset β€” deterministic NULL-page panic.

Runtime attempt

trigger_df4201.c compiled and ran on the audit guest (cc 8.3); the guest has no DRM device at all (/dev/dri absent, no AMD/ATI PCI β€” env.txt), so the amdgpu connector functions cannot execute there. Triggering on real hardware needs an amdgpu ASIC whose VBIOS describes an encoder-less connector (or an encoder whose dig/lcd info allocation failed at probe) β€” same VBIOS-quirk gating as the radeon twin DF-4033, which this finding mirrors site-for-site.

Fix validation

Not possible on this guest (no AMD GPU). fix.diff verified to apply cleanly to a pristine copy of the real file and is consistent with the in-file guard patterns (:479-485, :1083).

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 applies cleanly to a pristine copy of the real amdgpu_connectors.c (verified, cumulative with DF-4200/4202 diffs), consistent with the in-file guard pattern at :484; kernel-build validation not possible on this guest (no AMD GPU).

fix.diff; local patch --dry-run + applied-copy verification noted in VERDICT.md
↓ fix.diffper-fix-DF-4201

Confirmed kernel references

Detail

Exploit chain

display-session owner (DRM master) writes a connector's scaling-mode property on an encoder-less amdgpu connector (VBIOS misconfiguration), or any modeset on an eDP/LVDS panel with bpc-less EDID and failed dig/lcd-info allocation -> NULL deref panic

Evidence (decisive lines)

VERDICT.md (NULL-source proof at both levels: encoder list and enc_priv allocation); trigger_df4201.c + run.log (guest run, hardware-gated); fix.diff (three guards); env.txt

PoC changes

trigger written from scratch (raw ioctls, self-contained struct defs, no libdrm); __u32/__u64 typedefs added after first guest compile

Verified recommended fix

NULL-check best_encoder() results at the two scaling-mode sites and both encoder and enc_priv in the monitor-bpc branch (see fix.diff).

Verdict

DF-4033 radeon twin in amdgpu: three sites dereference the result of best_encoder()/enc_priv without NULL checks β€” amdgpu_connector_get_monitor_bpc:139-143 (best_encoder NULL from amdgpu_connector_best_single_encoder :346-357 / dvi_encoder :1122-1148 on an empty possible-encoder list, plus enc_priv NULL because dce_v*_0_encoder_add initializes it to NULL and only fills it if get_lcd_info/get_dig_info kzallocs succeed, dce_v10_0.c:3494/3520), amdgpu_connector_set_property:583-594 and amdgpu_connector_set_lcd_property:792-803 (scaling-mode branch, deref rmx_type/native_mode). Possible encoders are attached only by amdgpu_link_encoder_connector (amdgpu_encoders.c:45-56) β€” a VBIOS connector whose device bits match no encoder stays encoder-less while remaining registered. The monitor-bpc path fires on every modeset (atombios_crtc.c:773); the property paths via DRM_MASTER-gated MODE_SETPROPERTY/OBJ_SETPROPERTY (drm_ioctl.c:667/679). to_amdgpu_encoder(NULL) deref = deterministic NULL-page panic. The same file guards enc_priv at :484 β€” the omission is provable in-source. Not reproduced at runtime: guest has no AMD GPU (env.txt); trigger built and ran on the guest. Identical unguarded code in upstream Linux.