amdgpu df_v3_6_funcs is an incomplete vtable: .enable_ecc_force_par_wr_rmw left NULL (latent NULL function-pointer dereference for any Vega20 caller)
Summary
6-of-7 initializer on the VEGA20 table; the sole dispatch is VEGA10-gated (which implements it in v1_7) so dead today - a widened ECC gate on MI50/Radeon VII yields a boot-time NULL-deref. Upstream-inherited. Note the v1_7 sibling does NOT carry the DF-2202 class here (the clamp v1_7 lacks IS present in v3_6). NULL-guard one-liner.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5213 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.1 KB | β raw | |
| VERDICT.md | β | 4.1 KB | β raw | |
| trigger.sh | β | 1.3 KB | view raw | |
| run.log | β | 1.1 KB | view raw | |
| fix.diff | β | 1.8 KB | view raw | |
| manifest.json | β | 923 B | view raw | |
| verdict.json | β | 3.6 KB | view raw |
DF-5213 β amdgpu df_v3_6_funcs incomplete vtable (NULL enable_ecc_force_par_wr_rmw)
What
struct amdgpu_df_funcs declares seven members
(sys/dev/drm/amd/amdgpu/amdgpu.h:771-783), including
enable_ecc_force_par_wr_rmw (amdgpu.h:781-782). df_v3_6_funcs
(sys/dev/drm/amd/amdgpu/df_v3_6.c:108-116) β assigned for
CHIP_VEGA20 in soc15.c:519-520 (device IDs incl. the consumer
Radeon VII 0x66AF, amdgpu_drv.c:903-909) β initializes only six.
C designated-initializer semantics zero the seventh:
df_v3_6_funcs.enable_ecc_force_par_wr_rmw == NULL.
The sole caller, gmc_v9_0_late_init
(sys/dev/drm/amd/amdgpu/gmc_v9_0.c:759), is today gated on
adev->asic_type == CHIP_VEGA10 (gmc_v9_0.c:753), which uses
df_v1_7_funcs β and df_v1_7 does implement the op
(df_v1_7.c:105,119). So the NULL slot is never dispatched today;
this is a latent hazard: any future Vega20-reachable caller (or a
widening of the VEGA10 asic gate on this repeatedly-touched ECC path)
turns it into a boot-time NULL function-pointer dereference (panic).
DragonFly's port matches upstream Linux here β the gap is inherited,
not introduced.
Info hardening; no demonstrated impact. The DF-2202 class (v1_7's
missing table-bounds guard) does not carry over: v3_6 masks the
register field to 4 bits (IntLvNumChan_MASK 0x3C, shift 2 β max 15)
and explicitly guards >= ARRAY_SIZE(df_v3_6_channel_number) (15
entries) at df_v3_6.c:67-68, clamping to index 0.
Build / Run
Nothing to build (no userspace trigger exists for an init-path vtable
gap). trigger.sh re-derives the proof on any host with kernel source
and probes for AMD hardware:
sh trigger.sh # see run.log for this guest's output
Expected: the grep/sed sections show the 6-of-7 initializer and the
VEGA10-gated sole caller; on this guest the hardware probe shows no AMD
PCI device at all (AMD_GREP_RC=1, VEGA20_GREP_RC=1), so nothing can
manifest at runtime β consistent with status=not_reproduced.
Fix
fix.diff β one NULL-guard at the sole call site
(gmc_v9_0.c:759), the drm convention for optional vtable ops.
DF-5213 β VERDICT
Status: not_reproduced (nothing can manifest β no AMD hardware on the guest, and the NULL slot has no live caller on any ASIC). Impact: none (latent NULL-deref panic if ever dispatched). Confidence in the code facts: certain.
The facts (source, line-accurate)
struct amdgpu_df_funcsdeclares.enable_ecc_force_par_wr_rmw(sys/dev/drm/amd/amdgpu/amdgpu.h:781-782).df_v3_6_funcs(sys/dev/drm/amd/amdgpu/df_v3_6.c:108-116) initializes 6 of the 7 members; C99 designated initializers zero the rest β.enable_ecc_force_par_wr_rmw == NULLin this table.df_v3_6_funcsis selected only forCHIP_VEGA20(soc15.c:519-520); Vega20 PCI IDs include the consumer Radeon VII (amdgpu_drv.c:903-909,0x66AF), anddf_v3_6.cis compiled (sys/dev/drm/amd/amdgpu/Makefile:80).- The only call site of the op is
gmc_v9_0_late_init(gmc_v9_0.c:759), gatedasic_type == CHIP_VEGA10 && !amdgpu_sriov_vf(gmc_v9_0.c:753) β VEGA10 selectsdf_v1_7_funcs(soc15.c:521-522), which implements the op (df_v1_7.c:105,119). β the NULL slot is never dispatched on any currently-supported ASIC. Verified exhaustive:rg enable_ecc_force_par_wr_rmwover all ofsys/dev/drm/amd/returns exactly the four lines above.
Conclusion: latent hazard only β a widened asic gate or any future
Vega20 caller converts it into a boot-time NULL function-pointer
dereference (panic). Inherited unchanged from upstream Linux (same
struct, same 6-of-7 initializer, same VEGA10-gated caller). Info-grade
hardening; the one-line NULL guard in fix.diff is behavior-preserving.
Runtime probe (this guest)
run.log: no vendor-0x1002 PCI device at all (AMD_GREP_RC=1), no
0x66a* Vega20 device (VEGA20_GREP_RC=1), no drm/amd kmods loaded
(KLD_GREP_RC=1). There is no runtime path to observe on this guest β
hence not_reproduced / impact none, per the honest-classification
table (expected marker absent, guest stays up).
Negative results (everything else hunted in df_v3_6.c, killed)
- DF-2202 class (table index OOB): does NOT carry over.
get_fb_channel_numbermasksIntLvNumChanto 4 bits (df_3_6_sh_mask.h:39,44β MASK0x3C, SHIFT2β max 15), the table has 15 entries (indices 0-14), and β unlike df_v1_7 β v3_6 guardsfb_channel_number >= ARRAY_SIZE(...)and clamps to 0 (df_v3_6.c:67-68). Signed/unsigned in the comparison is harmless (value is a masked u32, never negative). Killed. - MGCG enum vs field width:
MGCGMode_MASK 0xF(df_3_6_sh_mask.h:34);DF_V3_6_MGCG_ENABLE_15_CYCLE_DELAY = 13andDISABLE = 0(df_v3_6.h:29-36) both fit. Killed. - gmc/df interplay:
get_hbm_channel_numberfeeds onlygmc.vram_width = numchan * chansize(gmc_v9_0.c:812-813); a tripped clamp yields a wrong width (cosmetic), never memory unsafety. Killed. - vtable coverage (this finding): the other five ops v3_6 provides
(
init,enable_broadcast_mode,get_fb_channel_number,get_hbm_channel_number,update_medium_grain_clock_gating,get_clockgating_state) are all implemented; all externaldf_funcs->call sites (soc15.c:998,1051,gmc_v9_0.c:759,812) were enumerated and resolve to implemented members except the one filed here. Killed (except DF-5213). - No attacker-controlled input reaches this file at all: every path is register R/W during GPU init/clock-gating with values read back from the device's own config space. Killed.
Fix validation
Not performed at runtime (nothing observable; no AMD hardware). The
fix.diff NULL-guard is a pure conditional on an existing expression β
not compile-validated because the full drm/amdgpu module build is
disproportionate for an Info-grade latent hazard; patch -p1 --dry-run
verified against the pristine tree.
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_testableNo runtime behavior exists to A/B (slot never dispatched; guest has no AMD GPU). fix.diff is a pure NULL-guard on an existing expression, patch -p1 --dry-run clean against the pristine tree; full drm module build skipped as disproportionate for an Info-grade latent hazard.
fix.diff; run.log (AMD_GREP_RC=1 / VEGA20_GREP_RC=1 hardware absence)
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log (guest /usr/src grep showing the 6-of-7 initializer, the sole VEGA10-gated call site, the df_v1_7 implementation, and the no-AMD-hardware probe); VERDICT.md (line-accurate trace + five negative-result classes); fix.diff (one-line NULL guard, patch -p1 --dry-run clean); trigger.sh (re-derives everything on any DF host)
PoC changes
Authored fresh (no seed); trigger.sh is a source-trace + hardware-probe script because no userspace trigger exists for an init-path vtable gap.
Verified recommended fix
NULL-guard the sole indirect call: if (adev->df_funcs->enable_ecc_force_par_wr_rmw) adev->df_funcs->enable_ecc_force_par_wr_rmw(adev, false); at gmc_v9_0.c:759 (see fix.diff).
Verdict
Latent-hazard Info finding, code facts certain, nothing manifests. df_v3_6_funcs (sys/dev/drm/amd/amdgpu/df_v3_6.c:108-116, selected for CHIP_VEGA20 at soc15.c:519-520; compiled per amdgpu/Makefile:80) initializes 6 of the 7 members of struct amdgpu_df_funcs (amdgpu.h:771-783), leaving .enable_ecc_force_par_wr_rmw NULL by designated-initializer rules. The sole call site (gmc_v9_0.c:759) is gated on asic_type == CHIP_VEGA10 (gmc_v9_0.c:753), which uses df_v1_7_funcs where the op IS implemented (df_v1_7.c:105,119) - exhaustive grep confirms no other caller - so the slot is never dispatched today; a widened asic gate or future Vega20 caller converts it into a boot-time NULL function-pointer dereference (panic). Inherited unchanged from upstream Linux. Guest runtime probe: no vendor-0x1002 PCI device at all (run.log: AMD_GREP_RC=1, VEGA20_GREP_RC=1, no drm/amd kmods) - nothing observable, hence not_reproduced/impact none. All other classes hunted in df_v3_6.c are killed with line cites in VERDICT.md, notably: the DF-2202 table-index OOB class does NOT carry over (4-bit field mask 0x3C>>2 max 15 vs 15-entry table PLUS an explicit >= ARRAY_SIZE clamp at df_v3_6.c:67-68, which df_v1_7 lacked); MGCG enum values fit the 0xF field mask; get_hbm_channel_number feeds only vram_width (no memory-safety sink).
No comments yet.