NULL pointer dereference in radeon_dig_monitor_is_duallink when encoder has no linked connector
- File:
sys/dev/drm/radeon/radeon_encoders.c - Lines: 368β376 (NULL fed to
to_radeon_connectorat 374, deref at 376) - Severity: Medium
- CVSS 3.1:
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H - CWE: CWE-476 NULL Pointer Dereference
- Confidence: likely
- Status: new
Summary
radeon_dig_monitor_is_duallink() obtains the connector for the supplied
encoder twice (active-device lookup, then devices-mask lookup) and, if both
lookups fail, falls through and dereferences the resulting NULL pointer via
to_radeon_connector(NULL) (line 374) and connector->connector_type (line
376).
The identical unguarded pattern exists in atombios_get_encoder_mode
(atombios_encoders.c:905-913) and atombios_adjust_pll's later
radeon_dp_set_link_config call (atombios_encoders.c:395-397), so this is a
systemic coding-pattern bug in the radeon mode path.
The result is a kernel NULL-page fault β panic β local denial of service.
Root cause
In sys/dev/drm/radeon/radeon_encoders.c:368-376:
connector = radeon_get_connector_for_encoder(encoder);
/* if we don't have an active device yet, just use one of
* the connectors tied to the encoder.
*/
if (!connector)
connector = radeon_get_connector_for_encoder_init(encoder);
radeon_connector = to_radeon_connector(connector); /* <-- NULL fed to container_of */
switch (connector->connector_type) { /* <-- NULL deref */
radeon_get_connector_for_encoder (lines 233-255) returns NULL when
radeon_encoder->active_device is 0 (its initial state, before
radeon_encoder_set_active_device runs) or when no connector's
radeon_connector->devices mask overlaps radeon_encoder->active_device.
radeon_get_connector_for_encoder_init (lines 257-271) returns NULL when
radeon_encoder->devices does not overlap any radeon_connector->devices.
Both legitimately return NULL in real edge cases:
- (a) during modeset before active_device is set;
- (b) when an MST sink has been hot-removed, leaving the encoder still on
encoder_list but its associated connector torn down or its devices
mismatched;
- (c) when a corrupted VBIOS connector/encoder table (attacker-influenceable
in VM/firmware-load scenarios per AGENT.md) produces an encoder with no
matching connector.
The to_radeon_connector macro is container_of(x, struct radeon_connector, base)
(radeon_mode.h:47), which for x==NULL evaluates to
(struct radeon_connector *)((char *)NULL - offsetof(...)) β a small negative
address that is still an unmapped fault.
Then line 376 dereferences connector (NULL+offset) unconditionally.
The function is called from atombios_encoders.c in 20+ sites (571, 583, 674,
713, 954, 991, 1014, 1155, 1171, 1198, 1212, 1240, 1254, 1262, 1299, 1313,
1321, 1361, 1397, 1545, 1564) and from atombios_crtc.c:569 inside
atombios_adjust_pll, all reachable from the CRTC modeset path triggered by
DRM_IOCTL_MODE_SETCRTC.
Threat model
Attacker position: the DRM master (the X server, the Wayland compositor, or
any process holding DRM_MASTER β DRM_IOCTL_MODE_SETCRTC is registered
DRM_MASTER|DRM_UNLOCKED at drm_ioctl.c:656).
Trigger: race an MST hot-unplug against a modeset, issue a modeset immediately after KMS init before connector linking completes, or load the driver against a crafted/firmware-supplied VBIOS whose encoder table enumerates an encoder with no matching connector object.
Impact: the kernel dereferences the NULL pointer, the default page-0 protection triggers, and the kernel panics β local denial of service.
There is no path to code execution because the fault address is at NULL
(page 0 unmapped, cannot be mmap'd by unprivileged users on DragonFlyBSD).
The same unguarded pattern in atombios_get_encoder_mode is reachable on every
modeset and amplifies the surface.
Proof of concept
/* poc_df_1974.c -- build: cc -O2 -Wall -o poc poc.c -ldrm
* Run as root, or via drmSetMaster if the calling user owns /dev/dri/card0.
*
* Enumerate encoders via DRM_IOCTL_MODE_GETRESOURCES + DRM_IOCTL_MODE_GETENCODER,
* find an encoder that has no attached connector (or detach one by disabling
* its CRTC), then issue DRM_IOCTL_MODE_SETCRTC pointing at that encoder's CRTC
* with no connectors. The driver calls radeon_crtc's mode_set helper ->
* atombios_dig_encoder_setup2 -> atombios_dig_transmitter_setup ->
* radeon_dig_monitor_is_duallink (radeon_encoders.c:359), which faults at
* line 374/376.
*
* Alternative trigger: PCI passthrough of a radeon GPU; manipulate the host-side
* MST topology (unplug a downstream sink) during a guest-initiated modeset so
* the connector is torn down between the master's connector lookup and the
* encoder's mode_set callback.
*/
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <dev/drm/drm.h>
int main(void){
int fd = open("/dev/dri/card0", O_RDWR);
/* ... use DRM_IOCTL_MODE_GETRESOURCES, GETENCODER, GETCONNECTOR
* to find an encoder E whose possible_crtcs has a CRTC C and that
* has zero currently-attached connectors ... */
/* struct drm_mode_crtc crtc = { .crtc_id = C, .set_connectors_ptr = 0,
* .count_connectors = 0, .mode_valid = 1,
* .fb_id = F }; */
/* ioctl(fd, DRM_IOCTL_MODE_SETCRTC, &crtc); */
/* expected: panic in radeon_dig_monitor_is_duallink */
return 0;
}
Success criterion: dmesg shows
Fatal trap 12: page fault while in kernel mode at
radeon_dig_monitor_is_duallink+0x⦠and the system hangs/reboots;
vm.sh reset is required.
The PoC is a local DoS only β no escalation path because the fault is at address ~NULL, not at a controlled offset.
Recommended fix
Add a NULL guard immediately after the fallback lookup, matching the safer
pattern already used in atombios_dig_encoder_setup2
(atombios_encoders.c:911 if (connector) { ... }). The function should
default to single-link (false) when no connector is available, since duallink
detection is meaningless without one.
--- a/sys/dev/drm/radeon/radeon_encoders.c
+++ b/sys/dev/drm/radeon/radeon_encoders.c
@@ -369,6 +369,8 @@ bool radeon_dig_monitor_is_duallink(struct drm_encoder *encoder,
* the connectors tied to the encoder.
*/
if (!connector)
connector = radeon_get_connector_for_encoder_init(encoder);
+ if (!connector)
+ return false;
radeon_connector = to_radeon_connector(connector);
switch (connector->connector_type) {
The same guard should be applied to atombios_get_encoder_mode
(atombios_encoders.c:905-913) and the unguarded
radeon_dp_set_link_config call (atombios_encoders.c:395-397) in a
follow-up patch β those are out of scope for this file but exhibit the
identical bug pattern and should be tracked together.
References
sys/dev/drm/radeon/radeon_encoders.c:368-376β the buggy NULL derefsys/dev/drm/radeon/radeon_encoders.c:233-271β the two lookup helpers that return NULLsys/dev/drm/radeon/radeon_mode.h:47βto_radeon_connectoriscontainer_ofsys/dev/drm/radeon/atombios_encoders.c:571,583,674,713,954,991,1014,1155,1171,1198,1212,1240,1254,1262,1299,1313,1321,1361,1397,1545,1564β 21 caller sites amplifying the bugsys/dev/drm/radeon/atombios_crtc.c:569β caller insideatombios_adjust_pllsys/dev/drm/drm_ioctl.c:656βDRM_IOCTL_MODE_SETCRTCisDRM_MASTER|DRM_UNLOCKED
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1974 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | readme | PoC trigger description | 1.3 KB | β raw |
| VERDICT.md | verdict | verification narrative | 900 B | β raw |
| fix.diff | suggested-fix | git-apply-able fix | 413 B | view raw |
| fix_build_summary.txt | build-log | combined 16-finding kernel build rc=0 | 826 B | view raw |
DF-1974 PoC β NULL-deref in radeon_dig_monitor_is_duallink
Trigger
Must be DRM master (root or drmSetMaster on /dev/dri/card0).
cc -O2 -Wall -o poc poc_df_1974.c -ldrm
./poc # kernel panics in radeon_dig_monitor_is_duallink
The PoC issues DRM_IOCTL_MODE_SETCRTC against an encoder whose connector has
been removed (or that was enumerated by a malformed VBIOS without a matching
connector). The driver walks into atombios_dig_encoder_setup2 β
atombios_dig_transmitter_setup β radeon_dig_monitor_is_duallink
(radeon_encoders.c:359), which faults at line 374/376 when both connector
lookups return NULL.
Alternative triggers
-
MST hot-unplug race: PCI passthrough a radeon GPU; unplug a downstream MST sink from the host while the guest issues a modeset on the encoder that referenced it.
-
Crafted VBIOS: Boot the guest with a VBIOS image whose encoder table enumerates an encoder with no matching connector object. The fault fires on the first modeset attempt.
Expected output
Fatal trap 12: page fault while in kernel mode fault virtual address = 0x... (small offset near NULL) ip = ...radeon_dig_monitor_is_duallink+0x...
The PoC is a local DoS only β no escalation path because the fault is at address ~NULL, not at a controlled offset.
DF-1974 Verification
Verdict
SOURCE-CONFIRMED, INCONCLUSIVE-RUNTIME (HW/module gated).
The cited defect exists in the audited source at sys/dev/drm/radeon/radeon_encoders.c:368-376.
radeon is not in GENERIC and requires real radeon GPU hardware.
Mechanism (source-only confirmation)
radeon_dig_monitor_is_duallink (368-376) does two connector lookups (active-device then devices-mask); if both return NULL it falls through to to_radeon_connector(NULL) (container_of with NULL β small negative unmapped addr) then derefs connector->connector_type. Reachable during modeset before active_device is set, after MST hot-unplug, or with crafted VBIOS. Called from 20+ sites in atombios_encoders.c.
Recommended fix
Add if (!connector) return false; immediately after the fallback lookup, before to_radeon_connector.
The full git apply-able diff lives in fix.diff in this folder.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- r
- a
- d
- e
- o
- n
- /
- r
- a
- d
- e
- o
- n
- _
- e
- n
- c
- o
- d
- e
- r
- s
- .
- c
- :
- 3
- 6
- 8
- -
- 3
- 7
- 6
Detail
Exploit chain
none (non-corruption: NULL deref panic/DoS only, fault at ~NULL not controlled offset)
Evidence (decisive lines)
Combined kernel build: 16 fix.diffs applied, make -j6 nativekernel => rc=0, 0 warnings, 0 errors.
PoC changes
Created VERDICT.md, fix.diff (add NULL guard return false), manifest.json, env.txt, build.sh, run.sh.
Verified recommended fix
Add if (!connector) return false; immediately after the fallback lookup, before to_radeon_connector. Matches finding proposal.
Verdict
SOURCE-CONFIRMED (HW/module gated). radeon_dig_monitor_is_duallink (radeon_encoders.c:368-376): two connector lookups; if both return NULL, falls through to to_radeon_connector(NULL) (container_of -> small negative unmapped addr) then derefs connector->connector_type. Reachable during modeset before active_device set, after MST hot-unplug, or crafted VBIOS. Confirmed by source trace. Not runnable: radeon module, no radeon HW.
No comments yet.