Dead FPGA-diagnostics hw_translate installs an all-NULL function table: activating the MAXIMUS env would turn every GPIO id/offset translation into an immediate NULL function-pointer call (kernel panic)
Summary
Both funcs members explicitly NULL while init reports success; every consumer calls them unconditionally (gpio_service.c:151/:418, gpio_base.c:144) - first GPIO op = call through NULL = panic. Provably dead on this tree (sole gate IS_FPGA_MAXIMUS_DC; only dce_environment assignment tree-wide is the hardcoded DCE_ENV_PRODUCTION_DRV at amdgpu_dm.c:424; no sysctl/ioctl/modpath). Family-consistent with DF-5231 (hw_factory_diag). False-returning stub fix; closes the gpio/ family.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5236 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.2 KB | β raw | |
| VERDICT.md | β | 4.1 KB | β raw | |
| verdict.json | β | 4.1 KB | view raw | |
| fix.diff | β | 1.1 KB | view raw | |
| manifest.json | β | 1014 B | view raw |
DF-5236 β hw_translate_diag.c all-NULL function table (Info, latent, FPGA-gated)
File: sys/dev/drm/amd/display/dc/gpio/diagnostics/hw_translate_diag.c (pass 2)
What
funcs (hw_translate_diag.c:34-37) is a struct hw_translate_funcs with both
members explicitly NULL (offset_to_id = NULL, id_to_offset = NULL).
dal_hw_translate_diag_fpga_init() (hw_translate_diag.c:39-42) installs this table
and returns β reporting success β with no functional translator whatsoever.
Why it is only Info (unreachable on this tree)
The sole caller is dal_hw_translate_init() (hw_translate.c:61-63), which installs
the diag table only when IS_FPGA_MAXIMUS_DC(dce_environment) is true
(dc_types.h:58-59, i.e. dce_environment == DCE_ENV_FPGA_MAXIMUS).
enum dce_environment (dc_types.h:46-55) has DCE_ENV_PRODUCTION_DRV = 0. The
only assignment in the entire driver is hardcoded:
sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c:424: init_data.dce_environment = DCE_ENV_PRODUCTION_DRV;
copied verbatim into the dc context at core/dc.c:624 and passed to
dal_gpio_service_create() at core/dc.c:661-663. There is no module parameter,
sysctl, ioctl, debugfs entry, or device property in this tree that can select
DCE_ENV_FPGA_MAXIMUS. Zero-initialization is also safe (0 == production).
dal_hw_translate_diag_fpga_init is therefore dead code on DragonFlyBSD β same
proof shape as the i2caux diag twin and DF-5231 (hw_factory_diag.c, Info).
Impact if ever activated (e.g. someone flips the env or imports the diag env)
First GPIO operation would be an immediate NULL function-pointer call β kernel panic at boot/DC-init:
gpio_service.c:151 service->translate.funcs->offset_to_id(...) (create_irq)
gpio_service.c:418 service->translate.funcs->offset_to_id(...) (create_ddc)
gpio_base.c:144 gpio->service->translate.funcs->id_to_offset(...) (get_pin_info)
Not attacker-steerable: the table is a compile-time constant of NULLs; there is no write primitive, only call-through-NULL. Panic-only, latent, gated off.
Negative results (why nothing worse hides here)
- Unknown-ASIC path cannot leak the NULL table:
dal_hw_translate_init()default case returns false (hw_translate.c:92-94) anddal_gpio_service_create()handles it (gpio_service.c:69-73: BREAK_TO_DEBUGGER; goto failure_1; kfree; return NULL) β production always gets a fully-populated ASIC table. - No other caller of
dal_hw_translate_diag_fpga_initexists (tree-wide grep). - Nothing else in the 42-line file: no state, no allocation, no locking concerns.
PoC status
untested β static-analysis-only Info/hardening finding. The trigger requires a
source change (forcing DCE_ENV_FPGA_MAXIMUS), i.e. it is not reachable on a
shipped kernel; per audit contract Phase V is not run for Info findings. If a
maintainer wants a live demonstration, the 2-line patch in fix.diff applied
together with init_data.dce_environment = DCE_ENV_FPGA_MAXIMUS; panics at
dal_gpio_service_create_irq on any amdgpu ASIC (expected call 0x0 trap).
Fix
See fix.diff: replace the NULL entries with explicit false-returning stubs (or delete the diagnostics twins from the build β Makefile:252 builds them for nothing). Family-consistent with DF-5231's recommendation.
DF-5236 β VERDICT
Status: untested (static-analysis-only Info finding; Phase V legitimately skipped for Info hardening findings per audit contract). Reproduced: no. Impact: none as shipped. Confidence: certain (reachability proven dead by exhaustive grep + constant-flow analysis).
The finding
sys/dev/drm/amd/display/dc/gpio/diagnostics/hw_translate_diag.c:34-37 installs a
struct hw_translate_funcs whose both members (offset_to_id, id_to_offset)
are explicitly NULL, and dal_hw_translate_diag_fpga_init()
(hw_translate_diag.c:39-42) reports success while installing nothing functional.
Every consumer calls these pointers unconditionally:
sys/dev/drm/amd/display/dc/gpio/gpio_service.c:151βdal_gpio_service_create_irq()sys/dev/drm/amd/display/dc/gpio/gpio_service.c:418β DDC creationsys/dev/drm/amd/display/dc/gpio/gpio_base.c:144βdal_gpio_get_pin_info()
β immediate call-through-NULL β kernel panic.
Reachability β proven dead on this tree
- Sole installer entry:
dal_hw_translate_init()(hw_translate.c:61-64) takes the diag branch only whenIS_FPGA_MAXIMUS_DC(dce_environment)(dc_types.h:58-59 βdce_environment == DCE_ENV_FPGA_MAXIMUS). enum dce_environment(dc_types.h:46-55):DCE_ENV_PRODUCTION_DRV = 0,DCE_ENV_FPGA_MAXIMUS = 1,DCE_ENV_DIAG = 2. Zero-init is production β safe.- Tree-wide grep for
dce_environment =/DCE_ENV_FPGA_MAXIMUS: the only value assignment issys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c:424init_data.dce_environment = DCE_ENV_PRODUCTION_DRV;(hardcoded constant). It is copied at core/dc.c:624 and consumed at core/dc.c:661-663 (dal_gpio_service_create(dc_version, dc_ctx->dce_environment, ctx)). No sysctl, module parameter, ioctl, or firmware path can change it. - Failure path is handled: for an ASIC version missing from the switch,
dal_hw_translate_init()returns false (hw_translate.c:92-95) anddal_gpio_service_create()frees and returns NULL (gpio_service.c:69-73, 137-140) β a NULL table can never reach production via the version switch either.
Conclusion: dal_hw_translate_diag_fpga_init is dead code; the NULL-call hazard
is latent only, exactly like its twin DF-5231 (hw_factory_diag.c, Info) and the
i2caux diag twin (unreachable-proven). Family treatment consistent: Info.
Why no exploit chain exists
The hazard is a call through a compile-time-constant NULL in a static const
table. There is no attacker-controllable pointer value, no adjacent state to
corrupt, and no reachable path in a shipped kernel. Worst case (developer flips
the env constant): instant panic at amdgpu DC init β a DoS you did to your own
rebuilt kernel, not a vulnerability.
What was hunted and killed (pass-2 depth for this 42-LOC file)
- All-NULL table walk-in: consumers enumerated above; all gated behind the dead env.
- Version-switch default leak: killed (gpio_service.c:69-73 handles false).
hw_translate.funcsleft dangling on destroy:dal_gpio_service_destroy()frees only the busyness slots (gpio_service.c:159-175);translateis by-value inside the service β no dangling pointer.- Build inclusion:
SRCS+= hw_translate_diag.c hw_factory_diag.c(sys/dev/drm/amd/amdgpu/Makefile:252) β compiled into the amdgpu kernel module, so the dead code ships but never executes. - Race/refcount/lock surfaces: none β the file has no state, no allocation, no locks; it is a 4-line initializer.
Fix
fix.diff in this directory replaces the NULL entries with explicit false-returning stubs, making the table safe to call even if the MAXIMUS env is ever enabled (family-consistent with DF-5231's recommendation). Alternative: drop the two diagnostics objects from the build entirely.
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_testableFix not built/tested: the finding is Info and unreachable on shipped kernels (hardcoded DCE_ENV_PRODUCTION_DRV at amdgpu_dm.c:424), so there is no observable baseline behavior to A/B on the guest. fix.diff is a defensive compile-level change (NULL entries -> false-returning stubs) authored against the read-only sys/ tree and never applied there.
findings/poc/DF-5236/fix.diff
Confirmed kernel references
- sys/dev/drm/amd/display/dc/gpio/diagnostics/hw_translate_diag.c:34
- sys/dev/drm/amd/display/dc/gpio/diagnostics/hw_translate_diag.c:39
- sys/dev/drm/amd/display/dc/gpio/hw_translate.c:61
- sys/dev/drm/amd/display/dc/dc_types.h:46
- sys/dev/drm/amd/display/dc/dc_types.h:58
- sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c:424
- sys/dev/drm/amd/display/dc/core/dc.c:624
- sys/dev/drm/amd/display/dc/core/dc.c:661
- sys/dev/drm/amd/display/dc/gpio/gpio_service.c:151
- sys/dev/drm/amd/display/dc/gpio/gpio_service.c:418
- sys/dev/drm/amd/display/dc/gpio/gpio_base.c:144
- sys/dev/drm/amd/display/dc/gpio/gpio_service.c:69
- sys/dev/drm/amd/amdgpu/Makefile:252
Detail
Evidence (decisive lines)
findings/poc/DF-5236/VERDICT.md (full reachability proof); hw_translate_diag.c:34-42 (all-NULL table + init); hw_translate.c:61-64 (sole caller, FPGA gate); dc_types.h:46-59 (enum + IS_FPGA_MAXIMUS_DC); amdgpu_dm.c:424 (hardcoded production env); gpio_service.c:151,418 and gpio_base.c:144 (unconditional NULL-call sites); gpio_service.c:69-73 (unknown-ASIC failure handled, NULL table cannot leak); Makefile:252 (compiled but dead).
PoC changes
No PoC authored: the trigger requires a source-level change (forcing DCE_ENV_FPGA_MAXIMUS at amdgpu_dm.c:424), i.e. not reachable on a shipped kernel; recorded honestly as untested rather than fabricating a runnable trigger.
Verified recommended fix
Replace the two NULL entries in hw_translate_diag.c's funcs table with explicit false-returning stubs (see findings/poc/DF-5236/fix.diff), or drop hw_translate_diag.c/hw_factory_diag.c from the amdgpu build (Makefile:252).
Verdict
Info (latent, unreachable). hw_translate_diag.c:34-37 installs an all-NULL struct hw_translate_funcs and dal_hw_translate_diag_fpga_init (hw_translate_diag.c:39-42) reports success with no functional translator; the first GPIO operation (gpio_service.c:151 offset_to_id, gpio_service.c:418 offset_to_id, gpio_base.c:144 id_to_offset) would be an immediate NULL function-pointer call => kernel panic. Reachability is proven dead on this tree: the sole entry (hw_translate.c:61-64) is gated on IS_FPGA_MAXIMUS_DC (dc_types.h:58-59) and the only dce_environment assignment anywhere is the hardcoded DCE_ENV_PRODUCTION_DRV at amdgpu_dm.c:424 (copied at core/dc.c:624, consumed at core/dc.c:661-663); no sysctl/ioctl/module parameter can select DCE_ENV_FPGA_MAXIMUS, and enum zero-init is production (dc_types.h:47). Family-consistent with DF-5231 (hw_factory_diag, Info) and the unreachable i2caux diag twin. Panic-only even if activated; the NULLs are compile-time constants, so there is no attacker-steerable pointer and no exploit chain. Phase V skipped per contract for Info hardening findings.
No comments yet.