generic_reg_wait prototype in dm_services.h transposes (mask, shift) vs the definition in dc_helper.c (shift, mask) β latent API-contract trap in the DC register hub; harmless today only because REG_WAIT's FN() expansion happens to match the definition
Summary
Declaration and definition disagree on the (mask,shift) order - both u32 so arguments bind silently; the only call family (REG_WAIT/FN()) matches the definition so behavior is correct today, but any header-trusting caller transposes and polls garbage for the full budget. Fix the declaration to match. The hub otherwise clean: every *_SET_N/_UPDATE_N pair-count census found no n exceeding supplied triplets (no stack-garbage-into-MMIO; DF-4435 remains the only mismatch, drop-direction).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4999 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.9 KB | β raw | |
| VERDICT.md | β | 3.1 KB | β raw | |
| fix.diff | β | 685 B | view raw |
DF-4999 β dc_helper.c: generic_reg_wait prototype in dm_services.h declares (mask, shift) but the definition is (shift, mask) β latent API-contract trap for every DC consumer
Where
Declaration: sys/dev/drm/amd/display/dc/dm_services.h:152-156
Definition: sys/dev/drm/amd/display/dc/dc_helper.c:254-257
/* dm_services.h:152 */
unsigned int generic_reg_wait(const struct dc_context *ctx,
uint32_t addr, uint32_t mask, uint32_t shift, uint32_t condition_value, ...)
/* dc_helper.c:254 */
uint32_t generic_reg_wait(const struct dc_context *ctx,
uint32_t addr, uint32_t shift, uint32_t mask, uint32_t condition_value, ...)
What
The shared-header prototype swaps mask/shift relative to the actual
definition. Today this is harmless at the ABI level (both parameters
are uint32_t, so the compiler binds arguments positionally with no
diagnostic) and harmless at the behavior level because the only call
family, REG_WAIT() (inc/reg_helper.h), expands FN(reg,field) to
reg__field__SHIFT, reg__field__MASK β i.e. it passes shift first,
matching the definition, not the header. (There are no direct
generic_reg_wait() call sites outside reg_helper.h/dm_services.h.)
The trap: any future caller that reads the header (as one does) and writes
generic_reg_wait(ctx, addr, mask, shift, cond, delay, tries, __func__, __LINE__)
silently transposes the pair β get_reg_field_value_ex() (dm_services.h:112)
then computes (shift & reg_val) >> mask (garbage), the poll condition
never matches, and every such wait burns its full budget (in-tree budgets
reach 100 ms) and lands in the error path (dm_error + BREAK_TO_DEBUGGER).
The same mis-ordering applied to the varargs generic_reg_update_ex family
would be memory-unsafe (stack garbage merged into MMIO writes); for
generic_reg_wait it is functional-only.
The function's own comment block at dm_services.h:146-148 ("return number of poll before condition is met / return 0 if condition is not meet") further documents that the API is meant to be consumed directly.
Impact
No in-tree defect today (audited every call path); API-contract hazard / documentation defect in the single most-used helper hub of the DC subsystem. Info severity, hardening.
Pass-2 companion audit of the hub (negative results recorded in the finding entry)
generic_reg_update_ex/generic_indirect_reg_update_exvarargs: every direct*_REG_SET_N/UPDATE_Ncall site in the tree was pair-counted β all match (nnever exceeds the supplied triplets), so there is no stack-garbage-into-MMIO primitive; the only mismatch in-tree is DF-4435 (n smaller, writes silently dropped β filed pass 1, not re-reported).generic_reg_waitloop is bounded (time_out_num_tries+1iterations); all in-tree budgets are compile-time constants with products β€ 100 ms (largest seen: dcn10_optc.c:496/762, 1 Β΅s Γ 100000); no user-controlled argument ever reaches this file.
DF-4999 β VERDICT
Finding: prototype/definition parameter-order mismatch for
generic_reg_wait between dm_services.h:152-156 and dc_helper.c:254-257.
Status: untested (static; the audit guest has no DRM/DC device β /dev/dri absent, same as the DF-4435 verification)
Proof
- Declaration (header every DC translation unit includes):
c
/* sys/dev/drm/amd/display/dc/dm_services.h:152 */
unsigned int generic_reg_wait(const struct dc_context *ctx,
uint32_t addr, uint32_t mask, uint32_t shift, uint32_t condition_value,
unsigned int delay_between_poll_us, unsigned int time_out_num_tries,
const char *func_name, int line);
- Definition (this file):
c
/* sys/dev/drm/amd/display/dc/dc_helper.c:254 */
uint32_t generic_reg_wait(const struct dc_context *ctx,
uint32_t addr, uint32_t shift, uint32_t mask, uint32_t condition_value,
unsigned int delay_between_poll_us, unsigned int time_out_num_tries,
const char *func_name, int line)
-
Harmless today only because C binds arguments positionally over identical
uint32_ttypes and the sole call family is theREG_WAITmacro (inc/reg_helper.h:245-248), whoseFN()expansion orders shift, mask β matching the definition, contradicting the header. Direct-call census:rg generic_reg_wait\(oversys/dev/drm/amd/display/dcreturns only the definition, the header declaration, and theREG_WAITmacro β no other call sites. -
The trap for future callers is concrete: a header-believing caller transposes the pair;
get_reg_field_value_ex(reg_val, mask, shift)(dm_services.h:112-116) computes(mask & reg_val) >> shiftwith the roles swapped β condition never satisfied β full poll budget burned (in-tree budgets up to 100 ms, e.g. dcn10_optc.c:496) βdm_errortimeout +BREAK_TO_DEBUGGER()(dc_helper.c:288-293) on every call β and for the sibling varargs family (generic_reg_update_ex) an equivalent declaration/consumer disagreement would feed stack garbage into MMIO writes (va_arg walks past the supplied triplets), which is why the header contract matters even where the runtime effect today is nil. AMD's own code comment (dc_helper.c:223-225) acknowledges the varargs family is "prone to stack corruption type of bugs".
Fix
One-line header correction (fix.diff) making the declaration identical to
the definition (also unifying the return type to uint32_t). No behavior
change for existing callers.
Fix validation
patch --dry-run OK. No kernel build possible on the guest (DC requires
AMD display hardware; guest has no DRM device), but the edit is
declaration-only β it cannot change codegen of the existing macro callers.
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_testableDeclaration-only edit; cannot change codegen of existing macro callers (they already pass (shift, mask) positionally). patch --dry-run OK. No hardware to build/run DC on the guest.
findings/poc/DF-4999/fix.diff (patch -p1 --dry-run: OK)
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (side-by-side declaration/definition, call-site census, hazard analysis); fix.diff (one-hunk header correction, patch --dry-run APPLY-OK)
PoC changes
No PoC authored: no user-reachable surface; the defect is a header contract mismatch provable only statically.
Verified recommended fix
Fix the dm_services.h prototype to (addr, shift, mask, ...) and return type uint32_t, identical to the dc_helper.c definition
Verdict
Declaration/definition parameter-order mismatch, static certainty, no in-tree runtime defect. dm_services.h:152-156 declares generic_reg_wait(ctx, addr, mask, shift, condition_value, ...) while the definition dc_helper.c:254-257 is (ctx, addr, shift, mask, condition_value, ...). Because both parameters are uint32_t, arguments bind positionally with no compiler diagnostic; today the only call family, REG_WAIT (inc/reg_helper.h:245-248), expands FN(reg,field) to (shift, mask) - matching the DEFINITION - so behavior is correct (direct-call census over sys/dev/drm/amd/display/dc found no other call sites). The hazard: any caller trusting the header transposes the pair; get_reg_field_value_ex (dm_services.h:112) then extracts (shift & reg_val) >> mask - garbage - so the poll condition never matches, each wait burns its full budget (in-tree budgets reach 100ms, e.g. dcn10_optc.c:496) and trips dm_error/BREAK_TO_DEBUGGER (dc_helper.c:288-293); an equivalent mismatch on the sibling varargs family generic_reg_update_ex would be memory-unsafe (va_arg past supplied args feeds stack garbage into MMIO writes - the hazard class AMD itself notes at dc_helper.c:223-225). Untested: declaration-only change, no runtime surface without AMD DC hardware (guest /dev/dri absent, same as DF-4435 verification). Info severity hardening finding.
No comments yet.