DeFiFoundrySolidity
16,653 OP
View results
Submission Details
Severity: medium
Invalid

Cross-Chain Access Control Bypass in `claimAndSwap` Functions

Summary and Impact

The claimAndSwap function, present across multiple chain-specific strategy implementations (StrategyOp.sol, StrategyMainnet.sol, and StrategyArb.sol), relies on the onlyKeepers modifier to enforce access control. However, the code does not enforce chain-specific restrictions on keeper calls. This omission allows a keeper with privileges on one chain to exploit opportunities intended for keepers on another chain. By monitoring conditions across different chains and front-running legitimate keeper operations, a malicious actor can claim profits not intended for them.

This violates the protocol’s intended invariant that keepers must only operate under controlled, chain-specific conditions. The vulnerability impacts the economic assumptions, enabling profit extraction and disruption of the expected keeper ecosystem. If unaddressed, it can diminish trust, reduce protocol efficiency, and grant unauthorized profits, harming the system’s integrity and stakeholders' interests.


Vulnerability Details

Root Cause: The core issue lies in the claimAndSwap function’s trust model. While it ensures that only keepers can invoke it, it does not guarantee that a keeper is authorized for that specific chain. Without a chain-specific identity check, an attacker who controls or impersonates a keeper on a cheaper chain can front-run profitable claimAndSwap calls intended for another chain.

Affected Code Snippet (from StrategyOp.sol and similarly in StrategyMainnet.sol and StrategyArb.sol):

function claimAndSwap(
uint256 _amountClaim,
uint256 _minOut,
IVeloRouter.route[] calldata _path
) external onlyKeepers {
transmuter.claim(_amountClaim, address(this));
uint256 balBefore = asset.balanceOf(address(this));
_swapUnderlyingToAsset(_amountClaim, _minOut, _path);
uint256 balAfter = asset.balanceOf(address(this));
require((balAfter - balBefore) >= _minOut, "Slippage too high");
transmuter.deposit(asset.balanceOf(address(this)), address(this));
}

This snippet shows the onlyKeepers modifier as the sole check. The logic does not differentiate keepers by deployment or chain, enabling cross-chain exploitation.

Test Code Snippet (Demonstration of Exploit):

function testCrossChainKeeperExploit() public {
// Assume `maliciousKeeper` was assigned keeper privileges on Chain A
// On Chain B, a legitimate keeper is about to call `claimAndSwap`
// The malicious keeper can front-run that call on Chain A environment.
IVeloRouter.route[] memory path = new IVeloRouter.route[]();
path[0] = IVeloRouter.route({from: address(underlying), to: address(asset), stable: false, factory: address(0)});
vm.startPrank(maliciousKeeper);
uint256 amountClaim = 10;
uint256 minOut = 11;
// Malicious keeper executes claimAndSwap first, stealing the profitable opportunity
strategy.claimAndSwap(amountClaim, minOut, path);
vm.stopPrank();
}

Step-by-Step Logic of the Exploit:

  1. Malicious keeper gains or reuses keeper permissions on a cheaper chain.

  2. Monitors the network for profitable claimAndSwap conditions originally intended for another chain’s deployment.

  3. When detected, malicious keeper calls claimAndSwap on the chain they control before the legitimate keeper can act on their designated chain.

  4. Profits that were supposed to be captured by the rightful keeper on the correct chain are now taken by the malicious actor.

Invariants Violated:

  • Keeper roles are assumed to be chain-specific. This vulnerability breaks that assumption by allowing a single keeper identity or address to operate outside its intended domain.

  • Economic integrity of cross-chain strategies is compromised, as profits meant for legitimate keepers are siphoned off by unauthorized actors.


Tools Used

  • Manual Review

  • Foundry


Recommendations

To mitigate this vulnerability, enforce stricter keeper validation:

Chain-Specific Keeper Whitelists:
Maintain distinct whitelists per chain deployment. Each chain’s contract should have its own verified list of keepers, preventing a keeper on one chain from acting on another.

Updates

Appeal created

inallhonesty Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
inallhonesty Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.