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)
PoC verification
Evidence pack
findings/poc/DF-5207 Β· 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | file | 5.6 KB | β raw | |
| verdict.json | file | 3.4 KB | view 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_testableConfirmed kernel references
- sys/dev/drm/amd/display/dc/virtual/virtual_link_encoder.c:86
- sys/dev/drm/amd/display/dc/virtual/virtual_link_encoder.c:101
- sys/dev/drm/amd/display/dc/inc/hw/link_encoder.h:116
- sys/dev/drm/amd/display/dc/dce110/dce110_hw_sequencer.c:1579
- sys/dev/drm/amd/display/dc/dce/dce_dmcu.c:167
- sys/dev/drm/amd/display/dc/core/dc_link.c:1988
- sys/dev/drm/amd/display/dc/core/dc_link_dp.c:2492
- sys/dev/drm/amd/display/dc/core/dc.c:169
- sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c:395
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).
No comments yet.