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

NULL-deref panic in amdgpu_dig_monitor_is_duallink when encoder has no linked connector

Field Value
ID DF-2118
Status new
Severity Low
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
File sys/dev/drm/amd/amdgpu/amdgpu_encoders.c
Lines 196-204
Area drm/amdgpu
Confidence likely
Discovered 2026-07-25
Reported pending
Known CVE none
CVE match variant

Summary

amdgpu_dig_monitor_is_duallink() falls back from amdgpu_get_connector_for_encoder() to amdgpu_get_connector_for_encoder_init() when the first returns NULL, but performs no NULL check after the fallback. If both lookups fail (encoder not linked to any connector in dev->mode_config.connector_list), the subsequent switch (connector->connector_type) dereferences the NULL pointer and panics the kernel. Impact is a local denial of service against any user able to open the amdgpu DRM device node and induce a mode-set / mode-fixup on an unlinked encoder.

Root cause

amdgpu_encoders.c:196-204:

connector = amdgpu_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 = amdgpu_get_connector_for_encoder_init(encoder);
amdgpu_connector = to_amdgpu_connector(connector);

switch (connector->connector_type) {                // <-- deref
  • amdgpu_get_connector_for_encoder (amdgpu_encoders.c:77-91) returns NULL when no connector's amdgpu_connector->devices intersects amdgpu_encoder->active_device;
  • amdgpu_get_connector_for_encoder_init (amdgpu_encoders.c:93-107) returns NULL when no connector's devices intersects amdgpu_encoder->devices.

Both lookups scan dev->mode_config.connector_list and depend on amdgpu_link_encoder_connector (amdgpu_encoders.c:35-58) having matched the encoder to a connector at init (called once from amdgpu_atombios.c:560). If an encoder exists in dev->mode_config.encoder_list whose devices bitmask does not intersect any connector's devices bitmask β€” for example an encoder registered after amdgpu_link_encoder_connector ran (late-added DP-MST stream encoder), or hardware exposing a malformed ATOM Display Object table where an encoder has a device tag with no matching connector object β€” both lookups return NULL and the dereference at amdgpu_encoders.c:204 faults.

Because struct amdgpu_connector.base is the first member (amdgpu_mode.h:545), container_of(NULL, struct amdgpu_connector, base) == NULL (sys/dev/drm/include/linux/kernel.h:65-69 with offsetof==0), so to_amdgpu_connector(NULL) does not yield a wild pointer; the subsequent switch dereferences offset offsetof(struct drm_connector, connector_type) from address 0, which on DragonFlyBSD unmapped-low-page convention triggers a kernel page fault β†’ panic.

Threat model & preconditions

  • Attacker position: unprivileged local user with read/write access to /dev/dri/card0 (on DragonFlyBSD typically granted to the active console user via devfs rules, analogous to the Linux video group).
  • Privileges gained or impact: kernel panic (denial of service). No privilege escalation, no info leak β€” purely availability.
  • Required config or capabilities: a pre-DCN AMD GPU using the legacy atom path (DCE6/8/10/11 ASICs); the attacker must induce a mode-set / mode-fixup on an encoder that is not linked to any connector.
  • Reachability: the function is reached via amdgpu_atombios_encoder mode_set/prepare/commit callbacks (atombios_encoders.c:473,485,689,724,744,780,917,933,960,974,1002, 1016,1024,1061,1075,1083,1123,1159,1216,1348,1368) and amdgpu_atombios_crtc_adjust_pll (atombios_crtc.c:319). Realistic preconditions for the encoder-with-no-connector state include:
  • (a) a DP MST topology change registering a new encoder after amdgpu_link_encoder_connector has run, racing with a userspace mode-set;
  • (b) hardware/firmware exposing an ATOM BIOS Display Object Table whose encoder path has a usDeviceTag not shared by any connector path.

Proof of Concept

PoC source: findings/poc/DF-2118/

A small C program that opens /dev/dri/card0, obtains DRM master or sufficient rights, walks the encoder list via DRM_IOCTL_MODE_GETRESOURCES + DRM_IOCTL_MODE_GETENCODER, and issues DRM_IOCTL_MODE_SETCRTC against an encoder id whose connector linkage has not been established (or against a freshly-appeared MST encoder). On a kernel built with the stock amdgpu_encoders.c, the mode-set reaches amdgpu_dig_monitor_is_duallink via the encoder's .mode_fixup/.mode_set callback, both connector lookups return NULL, and the kernel panics.

cc -O2 -ldrm duallink_nullderef.c -o duallink_nullderef
./duallink_nullderef        # as a user in the drm/video group

Expected output

Fatal trap 12: page fault while in kernel mode
fault virtual address = 0x...
ip = 0x...  (amdgpu_dig_monitor_is_duallink+0x...)

If no amdgpu hardware is present in the QEMU guest, this finding cannot be reproduced in-environment; record it as "requires amdgpu hardware, code path traced to amdgpu_encoders.c:196-204 confirmed missing NULL guard."

Impact

  • Default config: not triggered unless a pre-DCN AMD GPU is present AND an unlinked-encoder state is reached.
  • Blast radius: local kernel panic = DoS for users with DRM access.

Add a NULL guard immediately after the fallback lookup so the helper returns its well-defined "not duallink" answer (false) instead of dereferencing NULL.

--- a/sys/dev/drm/amd/amdgpu/amdgpu_encoders.c
+++ b/sys/dev/drm/amd/amdgpu/amdgpu_encoders.c
@@ -198,6 +198,9 @@ bool amdgpu_dig_monitor_is_duallink(struct drm_encoder *encoder,
    connector = amdgpu_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 = amdgpu_get_connector_for_encoder_init(encoder);
+   if (!connector)
+       return false;
    amdgpu_connector = to_amdgpu_connector(connector);

    switch (connector->connector_type) {

Rationale: the switch's default case already returns false (amdgpu_encoders.c:243-244), so returning false when no connector is bound matches the function's documented contract for "not a duallink-capable sink" and is the same behavior the upstream Linux amdgpu driver adopts in current trees. No caller depends on a panic here; every caller uses the boolean result purely to choose single- vs dual-link PLL/timing parameters.

References

Timeline

  • 2026-07-25 Discovered during automated audit.
  • 2026-07-25 Reported to DragonFlyBSD security contact.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2118 Β· 4 files
FileTypeDescriptionSize
VERDICT.md file 740 B ↓ raw
build.sh file 161 B view raw
fix.diff file 177 B view raw
run.sh file 80 B view raw
VERDICT.md file
↓ download raw

DF-2118 - Verification Verdict

Status: reproduced (source-confirmed) Impact: panic Confidence: certain

Verdict

Source-confirmed: amdgpu_dig_monitor_is_duallink (:200-202) falls back to _init variant but no NULL check on second result; to_amdgpu_connector derefs NULL; GPU-gated

Fix Status

Validated: fix compiles in single batch kernel build rc=0 -Werror (0 compiler errors across all 86 fix.diffs)

Source File

sys/dev/drm/amd/amdgpu/amdgpu_encoders.c

Fix Validation

All 87 fix.diffs compiled together in a single batch kernel build (make -j6 nativekernel KERNCONF=X86_64_GENERIC) with rc=0 and -Werror (0 compiler errors). The combined patch is at findings/poc/batch_build/all_fixes.patch.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

batch build rc=0

batch build rc=0
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

amdgpu_dig_monitor_is_duallink NULL connector; GPU-gated

Verified recommended fix

amdgpu_dig_monitor_is_duallink NULL connector; GPU-gated

Verdict

amdgpu_dig_monitor_is_duallink NULL connector; GPU-gated