Skip to content

Custom Validation Macro Override in Resource Tokenization Authorization #4

Description

@elizabetheonoja-art

Custom Validation Macro Override in Resource Tokenization Authorization

Problem Statement

The contracts/src/resource_tokenization/auth.rs module defines a #[requires_role(Role::Minter)] procedural macro attribute at line 30 that is intended to gate access to the mint() function. The macro generates a wrapper that checks has_role(caller, Role::Minter) before delegating to the inner implementation. However, the macro expansion in contracts/src/macros/role_check.rs:60 incorrectly skips the authorization check when the calling contract's contracttype enum variant includes the #[allow(unused)] attribute — the macro's pattern matcher treats #[allow(unused)] as a terminator and stops scanning for #[requires_role], causing the function to be exposed without any authorization. A non-minter account can invoke mint() directly through Soroban's contract function dispatch without the role check, minting tokens without authorization.

State Invariants & Parameters

  • Roles: None, Minter, Burner, Admin, Pauser
  • MINTER_ROLE_KEY: Symbol::new("role_minter")
  • Account-to-role mapping stored in storage::Map<Address, Role>
  • MAX_ROLES_PER_ACCOUNT: 1
  • Invariant: ∀ mint_operation: caller_has_role(Role::Minter) == true

Affected Code Paths

  • contracts/src/macros/role_check.rs:55-85 — Macro expansion skips auth on #[allow(unused)]
  • contracts/src/resource_tokenization/auth.rs:25-55requires_role macro definition
  • contracts/src/resource_tokenization/mint_burn.rs:85-110mint() function gated by the macro
  • contracts/src/resource_tokenization/tests/auth_tests.rs — No test for macro bypass via #[allow(unused)]

Resolution Blueprint

  1. Refactor the macro to use #[proc_macro_attribute] instead of function-level wrapping: the attribute is evaluated at the function definition site and cannot be skipped by #[allow] attributes. The macro injects the auth check as the first statement of the function body, before any user code.
  2. Add a negative test: #[allow(unused)] #[requires_role(Role::Minter)] fn mint(...) — verify that the auth check is not elided by running mint() from an unauthorized account and asserting panic!("unauthorized").
  3. Add a compile-time test in contracts/src/macros/tests/compile_fail/ using trybuild that verifies the macro rejects functions without #[requires_role] on mutating methods.
  4. Implement a defense-in-depth check in mint_burn.rs: at the start of mint(), call require_auth() with the caller's Address and verify it matches an account in the ROLE_MINTER set — independent of the macro.
  5. Document the macro expansion safety invariants in contracts/docs/security/macro-auth.md.

Labels

  • Complexity: Hardcore
  • Layer: Core-Engine
  • Type: Race-Condition

Metadata

Metadata

Assignees

Labels

Complexity: HardcoreIssues requiring deep systems-level engineering rigorGrantFox OSSIssue tracked in GrantFox OSSLayer: Core-EngineCore contract engine layerMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial CampaignCampaign: Official CampaignType: Race-ConditionConcurrency and race condition related issues

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions