vulnerability
Blog post
State divergence enables unauthorized access
Source: Trail of Bits Blog · Published · Last checked 3 hours ago · 3 min read
Publisher excerpt From Trail of Bits Blog
We found and reported a bug in Provenance Blockchain, a public proof-of-stake chain built on Cosmos SDK, that lets any user grant themselves admin control over marker accounts without holding a single token. Provenance covers a range of financial services, including on-chain tokenized loans, private equity tokens, bridged assets, and asset registries. Our bug affected 82 markers representing live financial assets on mainnet. We found the bug, which affects versions before 1.28.0, in March 2026, and reported it to Provenance on April 1. It was mitigated in PR #2627 (commit c81fd65), which shipped in v1.28.0 on May 1, 2026, and fixed in PR #2734, which shipped in v1.29.0 on June 8, 2026. What is a marker? The marker module is Provenance’s core primitive for fungible tokens. Chain participants can issue a new asset on Provenance by submitting a MsgAddMarkerRequest transaction; the chain creates a dedicated account for that asset, called a marker. Each marker is a special account type that controls: A denomination (e.g., uusd.trading, cusd.deposit, cguaranteedrateomni) An access control list governing who can mint, burn, withdraw, deposit, or administer the token A supply field recording the canonical token count An escrow balance (the marker account can hold any asset, not just its own denomination) Markers are either supply_fixed (the supply field is enforced as a hard cap) or non-fixed (the bank module is the source of truth; the supply field is informational). This distinction is central to the bug. The bug: An access check anyone can pass AddAccess is the Cosmos SDK message handler that processes requests to modify a marker’s access control list. It checks whether the caller is authorized using three conditions, any one of which is sufficient: The caller is the marker’s designated manager and the marker is in Finalized state. The caller already holds ACCESS_ADMIN on the marker. The caller controls 100% of the marker’s circulating supply. case types.StatusFinalized, types.StatusActive: if !(caller.Equals(m.GetManager()) && m.GetStatus() == types.StatusFinalized) && !m.AddressHasAccess(caller, types.Access_Admin) && !k.accountControlsAllSupply(ctx, caller, m) { return fmt.Errorf("%s is not authorized to make access list changes against finalized/active %s marker", caller, m.GetDenom()) } Figure 1: Authorization check in keeper.AddAccess (x/marker/keeper/marker.go#L94–L100) Condition 3 is implemented by accountControlsAllSupply: func (k Keeper) accountControlsAllSupply( ctx sdk.Context, caller sdk.AccAddress, m types.MarkerAccountI, ) bool { balance := k.bankKeeper.GetBalance(ctx, caller, m.GetDenom()) supply := m.GetSupply() // ← bug return supply.Equal(sdk.NewCoin(m.GetDenom(), balance.Amount)) } Figure 2: The vulnerable accountControlsAllSupply function (x/marker/keeper/marker.go#L866–L875) The m.GetSupply function reads the supply field stored directly on the marker struct. For non-fixed supply markers that were…
Excerpt only, shown with attribution. This content belongs to the original publisher and has not been independently verified by CyberSecurity.pk.
Keywords: rcevulnerabilityvulnerability newsTrail of Bits Blogcybersecurity newssecurity news Pakistan
Reference: Trail of Bits Blog — blog.trailofbits.com ↗

