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

virtual_lnk_enc_funcs omits 6 of 18 link_encoder_funcs ops (NULL) with 4 unguarded call sites in DC core β€” latent NULL function-pointer call if virtual link encoders are ever instantiated

Summary

6 NULL ops, 4 unguarded call sites (is_dig_enabled, 2 PSR ops, enable_lvds_output) - all confined to eDP/LVDS today and the instantiation path is doubly dead (num_virtual_links==0 tree-wide). The DF-5199 twin. Complete-the-vtable fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5207 Β· 2 files
FileTypeDescriptionSize
VERDICT.md file 5.6 KB ↓ raw
verdict.json file 3.4 KB view raw
VERDICT.md file
↓ download raw

DF-5207 β€” virtual_lnk_enc_funcs omits 6 of 18 ops (NULL) β€” latent NULL fp-call

File: sys/dev/drm/amd/display/dc/virtual/virtual_link_encoder.c:86-101 Sibling of DF-5199 (virtual_stream_encoder.c, 7 NULL stream-encoder ops).

Structural proof

struct link_encoder_funcs (inc/hw/link_encoder.h:116-156) declares 18 ops. virtual_lnk_enc_funcs initializes 12. NULL:

op unguarded call sites why unreachable today
enable_lvds_output dc_link.c:1988 (enable_link_lvds) dispatched only for SIGNAL_TYPE_LVDS streams (dc_link.c:2016-2018); virtual links carry SIGNAL_TYPE_VIRTUAL (dc.c:154) whose enable_link() case is a pure no-op (dc_link.c:2019-2021)
psr_program_dp_dphy_fast_training dce_dmcu.c:167, dce_dmcu.c:586 PSR setup paths, eDP links only
psr_program_secondary_packet dce_dmcu.c:215, dce_dmcu.c:634 same
enable_hpd β€” (NULL-guarded dc_link_dp.c:2492) guarded
disable_hpd β€” (NULL-guarded via enable_hpd proxy check dc_link_dp.c:2500) guarded
is_dig_enabled dce110_hw_sequencer.c:1579 (dce110_enable_accelerated_mode) invoked on get_link_for_edp() result, which filters connector_signal == SIGNAL_TYPE_EDP (dce110_hw_sequencer.c:1520-1531) β€” a virtual link is never returned

Deadness proof (why "latent")

The only instantiation of a virtual link encoder is the num_virtual_links loop in create_links() (dc.c:133-172; kzalloc + virtual_link_encoder_construct at dc.c:161/169). struct dc_init_data init_data is memset to 0 in amdgpu_dm_init() (amdgpu_dm.c:395) and no code anywhere in the tree ever assigns num_virtual_links β€” verified tree-wide. So the loop body is dead code on every real system; the file is compiled (amdgpu/Makefile:297) but the encoder is never constructed at runtime. The audit guest additionally has no AMD GPU.

Consequence if the family is ever wired up (num_virtual_links > 0) and one of the four unguarded sites ever runs with a virtual encoder: NULL function-pointer call β†’ kernel panic (local DoS; a controllable dispatch primitive in the worst case). Same class/severity as the accepted DF-5199 (Info, CWE-476).

Construct/destroy pairing (checked, correct)

virtual_link_encoder_construct() does not allocate; it only fills an already-kzalloc'd struct link_encoder (dc.c:161) β€” all fields it touches are assigned, the kzalloc zeroes the rest, so no uninitialized-field read. virtual_link_encoder_destroy() (virtual_link_encoder.c:79-83) kfree's the pointer and NULLs it, matching the one-shot funcs->destroy(&link->link_enc) in dc_link.c:87 / dc_link.c:1249. No double-free/UAF path.

Also noted (latent, not filed): create_links() bounds only connectors_num against ENUM_ID_COUNT (dc.c:105-112) but writes dc->links[MAX_PIPES*2] (dc.h:287) connectors_num + num_virtual_links times β€” harmless only because num_virtual_links is hardcoded 0. And the virtual_link_encoder_construct() return value is ignored at dc.c:169 (always true today, benign).

Fix

Add no-op stubs for the six missing ops:

--- a/sys/dev/drm/amd/display/dc/virtual/virtual_link_encoder.c
+++ b/sys/dev/drm/amd/display/dc/virtual/virtual_link_encoder.c
@@ static void virtual_link_encoder_connect_dig_be_to_fe(
    struct link_encoder *enc,
    enum engine_id engine,
    bool connect) {}

+static void virtual_link_encoder_enable_lvds_output(
+   struct link_encoder *enc,
+   enum clock_source_id clock_source,
+   uint32_t pixel_clock) {}
+
+static void virtual_link_encoder_psr_program_dp_dphy_fast_training(
+   struct link_encoder *enc,
+   bool exit_link_training_required) {}
+
+static void virtual_link_encoder_psr_program_secondary_packet(
+   struct link_encoder *enc,
+   unsigned int sdp_transmit_line_num_deadline) {}
+
+static void virtual_link_encoder_enable_hpd(struct link_encoder *enc) {}
+
+static void virtual_link_encoder_disable_hpd(struct link_encoder *enc) {}
+
+static bool virtual_link_encoder_is_dig_enabled(struct link_encoder *enc)
+{
+   return true;
+}
+
 static void virtual_link_encoder_destroy(struct link_encoder **enc)
 {
    kfree(*enc);
@@ static const struct link_encoder_funcs virtual_lnk_enc_funcs = {
    .enable_dp_mst_output = virtual_link_encoder_enable_dp_mst_output,
+   .enable_lvds_output = virtual_link_encoder_enable_lvds_output,
    .disable_output = virtual_link_encoder_disable_output,
    .dp_set_lane_settings = virtual_link_encoder_dp_set_lane_settings,
    .dp_set_phy_pattern = virtual_link_encoder_dp_set_phy_pattern,
    .update_mst_stream_allocation_table =
        virtual_link_encoder_update_mst_stream_allocation_table,
+   .psr_program_dp_dphy_fast_training =
+       virtual_link_encoder_psr_program_dp_dphy_fast_training,
+   .psr_program_secondary_packet =
+       virtual_link_encoder_psr_program_secondary_packet,
    .connect_dig_be_to_fe = virtual_link_encoder_connect_dig_be_to_fe,
+   .enable_hpd = virtual_link_encoder_enable_hpd,
+   .disable_hpd = virtual_link_encoder_disable_hpd,
+   .is_dig_enabled = virtual_link_encoder_is_dig_enabled,
    .destroy = virtual_link_encoder_destroy
 };

Why not runtime-verified

Info/latent finding on a dead path (no AMD GPU on the guest; num_virtual_links == 0 tree-wide). Phase V skipped per contract; verdict.json records status: untested, attempts: 0.

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.

(No fix.diff for this item: documented recommendation only - see Fix above.)

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean
per-fix-DF-5207

Confirmed kernel references

Detail

Evidence (decisive lines)

static analysis: inc/hw/link_encoder.h:116-156 (18 ops) vs virtual_link_encoder.c:86-101 (12 initialized -> 6 NULL); unguarded call sites dce110_hw_sequencer.c:1579, dce_dmcu.c:167/215/586/634, dc_link.c:1988; guarded ones dc_link_dp.c:2492/2500; instantiation dead path dc.c:133-172 + amdgpu_dm.c:395 (memset, zero setters tree-wide); DF-5199 twin (virtual_stream_encoder.c, 7 NULL ops).

Verified recommended fix

populate the 6 missing ops with no-op stubs (mirror dce_virtual pattern)

Verdict

untested (dead path: num_virtual_links is memset to 0 at amdgpu_dm.c:395 and no code in the tree ever sets it, so create_links' virtual loop (dc.c:133-172) never instantiates a virtual link encoder on any real system; additionally the guest has no AMD GPU). Static proof is complete: virtual_lnk_enc_funcs (virtual_link_encoder.c:86-101) populates only 12 of the 18 ops in struct link_encoder_funcs (inc/hw/link_encoder.h:116-156); enable_lvds_output, psr_program_dp_dphy_fast_training, psr_program_secondary_packet, enable_hpd, disable_hpd and is_dig_enabled are NULL. Four of the six have UNGUARDED call sites in DC core: dce110_hw_sequencer.c:1579 (is_dig_enabled), dce_dmcu.c:167 and dce_dmcu.c:586 (psr_program_dp_dphy_fast_training), dce_dmcu.c:215 and dce_dmcu.c:634 (psr_program_secondary_packet), dc_link.c:1988 (enable_lvds_output). Today those sites only ever run with real DCE/DCN encoders: get_link_for_edp() filters SIGNAL_TYPE_EDP (dce110_hw_sequencer.c:1520-1531), PSR setup runs only on eDP links, enable_link_lvds() only for SIGNAL_TYPE_LVDS streams, and the SIGNAL_TYPE_VIRTUAL case in enable_link() is a pure no-op (dc_link.c:2019-2021) touching no encoder op. The two ops a virtual link could plausibly reach (dc_link_dp_enable_hpd/disable_hpd) are already NULL-guarded (dc_link_dp.c:2492/2500). So the gap is latent: the moment virtual links are wired up (num_virtual_links>0) and any of those paths ever sees a virtual encoder, the call is a NULL function-pointer dereference -> kernel panic (local DoS at best, controllable-call primitive at worst).