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

dc_link_dp_set_test_pattern NULL-pointer dereference on inactive pipe (pipes[i].stream->sink unchecked) plus wrong-object blank (pipes[0] instead of matched pipe_ctx) β€” sink-triggerable panic with no active stream

Summary

The scan 'if (pipes[i].stream->sink->link == link)' has no NULL check; pipe_ctx[].stream is NULL for every inactive pipe, and the function is reachable from a sink-initiated automated test with NO stream on the tested link. First NULL hole => panic. Second defect: :2653 blanks pipes[0]'s stream_enc instead of the matched pipe_ctx. Independent of lane_count. NULL-check + correct-object fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3988 Β· 9 files
FileTypeDescriptionSize
trigger.c trigger hardware-precondition model / protocol replica 2.9 KB view raw
README.md readme build/run/expected 895 B ↓ raw
VERDICT.md verdict full path:line trace 3.6 KB ↓ raw
build.sh script exact build commands 65 B view raw
run.sh script exact run commands 89 B view raw
build.log log full untrimmed build output 298 B view raw
run.log log full untrimmed runtime output 457 B view raw
env.txt env guest hardware/src-md5 proof 1.1 KB view raw
fix.diff fix git-apply-able unified diff 1.1 KB view raw
README.md readme build/run/expected
↓ download raw

DF-3988 β€” dc_link_dp_set_test_pattern dereferences pipes[i].stream->sink without a NULL check (dc_link_dp.c:2606): pipe_ctx[].stream is NULL for every inactive pipe, and sink-initiated automated-test requests arrive without any active stream on the tested link β†’ NULL-pointer deref panic. Also :2653 blanks pipes[0]'s stream_enc instead of the matched pipe_ctx (wrong object; second NULL deref when pipe0 is an inactive hole).

BUILD cc -O2 -Wall -Wextra -o trigger trigger.c

RUN ./trigger (model of the :2605-2610 scan: pipes[0]=NULL hole, match at pipes[1] β†’ NULL DEREF on i=0)

On the audit guest: builds and runs clean (run.log). Hardware-gated: guest has bochs VGA only, zero 0x1002 PCI devices, amdgpu.ko not loaded.

EXPECTED "i=0 pipes[0].stream=0x0 -> ...->sink->link: NULL DEREF (panic)" and the :2653 wrong-object note; TRIGGER_RC=0.

VERDICT.md verdict full path:line trace
↓ download raw

DF-3988 VERDICT β€” source-level certain, runtime hardware-gated (inconclusive)

Root cause (path:line)

dc_link_dp_set_test_pattern β€” dc_link_dp.c:2588-2740:

2595: struct pipe_ctx *pipes = link->dc->current_state->res_ctx.pipe_ctx;
2596: struct pipe_ctx *pipe_ctx = &pipes[0];
2605: for (i = 0; i < MAX_PIPES; i++) {
2606:         if (pipes[i].stream->sink->link == link) {   /* no NULL check */
2607:                 pipe_ctx = &pipes[i];
2608:                 break;
2609:         }
2610: }

pipe_ctx[].stream is NULL for every inactive pipe: after boot with no display commit, for all pipes during DPMS-off/idle, and for holes left in the array after unplug/re-plug re-arrangements (e.g. stream on pipe1 while pipe0 is free). The function is reachable from a sink-initiated automated test with NO stream on the tested link:

HPD short pulse β†’ dc_link_handle_hpd_rx_irq :2045-2054 (AUTOMATED_TEST bit) β†’ handle_automated_test :1990 (LINK_TEST_PATTRN) β†’ dp_test_send_link_test_pattern :1957 / or :1994 (PHY_TEST_PATTERN) β†’ dp_test_send_phy_test_pattern :1909 β†’ dc_link_dp_set_test_pattern.

A malicious sink/dongle (or a compliance test box hitting during a no-commit window, or any tested link whose pipe sits after an inactive hole) reaches :2606 with pipes[0].stream == NULL β†’ NULL->sink β†’ kernel panic.

Second defect in the same function, :2653:

pipes->stream_res.stream_enc->funcs->dp_blank(pipe_ctx->stream_res.stream_enc);

This uses pipes (i.e. &pipes[0]) β€” NOT the matched pipe_ctx β€” for the object deref (wrong pipe: blanks pipe 0's stream encoder regardless of which pipe carries the tested link), and dereferences its stream_enc without a NULL check (inactive pipe 0 β†’ second NULL deref), while passing pipe_ctx's (possibly different) stream_enc as the argument. Discovered while auditing the automated-test surface for pass 2; both defects are in the sink-reachable test-pattern path.

Distinguishing from DF-1319/DF-3987

DF-1319 = lane_count stack overflow in LT helpers. DF-3987 = poisoned cur_link_settings indexed past lane arrays. This finding is a NULL-deref / wrong-object deref on the pipe array β€” independent of lane_count, reachable even with byte-perfect DPCD data (a compliant test appliance triggering a link test pattern with no active stream is enough).

Verification on the guest (honest)

  • trigger.c models the exact scan shape (NULL hole at [0], match at [1]): builds and runs on the guest (run.log, RC=0, NULL-DEREF reported at i=0).
  • Real chain hardware-gated: zero 0x1002 PCI devices, amdgpu.ko not loaded (env.txt). Status inconclusive per family convention.
  • Guest src md5 matches audited tree (env.txt).

Threat model / impact

Malicious DP sink/dongle (physical) OR benign compliance-test traffic at an unlucky instant (no active stream / pipe hole) β†’ deterministic NULL-deref panic of the kernel thread handling the HPD work. Local DoS; no info leak, no write primitive. Severity Medium (matches DF-1319 family threat model, lower impact class).

Fix

fix.diff: NULL-check pipes[i].stream != NULL && pipes[i].stream->sink != NULL in the scan; blank the matched pipe_ctx with a stream_enc NULL check. git-apply clean; not runtime-validable on this guest.

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 adds pipes[i].stream != NULL && pipes[i].stream->sink != NULL to the scan and switches the blank to the matched pipe_ctx with a stream_enc NULL check. Cannot be runtime-validated: no AMD GPU on the guest. Mechanical NULL-guard on an already-compiled path; git-apply clean.

fix.diff; hardware absence proof in env.txt
↓ fix.diffper-fix-DF-3988

Confirmed kernel references

Detail

Exploit chain

On real hardware: DP sink/test appliance sends AUTOMATED_TEST short HPD with LINK_TEST_PATTERN or PHY_TEST_PATTERN request while no stream is committed on the tested link (or the link's pipe sits behind a NULL hole) -> pipes[0].stream == NULL -> NULL pointer dereference panic in the HPD work thread. DoS only; no write primitive, no leak.

Evidence (decisive lines)

run.log (model: pipes[0]=NULL hole, match at pipes[1] -> NULL DEREF at i=0; RC=0), env.txt (NO_AMD_GPU / amdgpu.ko unloaded / guest-src md5 match), VERDICT.md (path:line trace incl. reachability without any stream), trigger.c, fix.diff (NULL checks + matched-pipe blank, git-apply clean)

PoC changes

No seed PoC. Authored trigger.c as an annotated hardware-precondition + scan-shape model; honest hardware-absent verification per family convention.

Verified recommended fix

NULL-check pipes[i].stream/sink in the scan loop and blank the matched pipe_ctx (not pipes[0]) with a stream_enc NULL check (see fix.diff).

Verdict

Source-level certain, runtime hardware-gated (inconclusive per family convention). dc_link_dp_set_test_pattern (dc_link_dp.c:2588) scans current_state->res_ctx.pipe_ctx with if (pipes[i].stream->sink->link == link) (:2605-2610) with NO NULL check on pipes[i].stream. pipe_ctx[].stream is NULL for every inactive pipe - after boot with no display commit, during DPMS-off, and in holes left after unplug re-arrangements - and the function is reachable from a sink-initiated automated test with NO active stream on the tested link: dc_link_handle_hpd_rx_irq:2045 (AUTOMATED_TEST) -> handle_automated_test:1990/:1994 -> dp_test_send_link_test_pattern:1957 / dp_test_send_phy_test_pattern:1909 -> dc_link_dp_set_test_pattern. First NULL hole at i=0 -> NULL->sink deref -> panic. Second defect in the same function: :2653 blanks pipes[0]'s stream_enc (pipes->stream_res.stream_enc->funcs->dp_blank(pipe_ctx->stream_res.stream_enc)) instead of the MATCHED pipe_ctx - wrong-object access and another NULL deref when pipe0 is an inactive hole. Independent of lane_count: even byte-perfect DPCD data (a compliant test appliance during a no-commit window, or any tested link whose pipe sits after an inactive hole) triggers it. Runtime: guest has bochs VGA only, zero 0x1002 devices, amdgpu.ko not loaded (env.txt); trigger.c model of the scan builds and runs on the guest (run.log RC=0, NULL DEREF reported at i=0). Guest src md5 028326495b845611c38ec70dcd655bf3 == audited tree.