Arbitrary calldata execution in xReserve via forwarding hook leads to token drain from reserve/gateway wallet
Summary:
A forwarding hook in xReserve allows arbitrary calldata to be executed against TokenMessenger / TokenMessengerV2 when processing a withdrawal attestation. Because xReserve sets unlimited allowances for TokenMessenger (so it can operate), an attacker who can supply a valid attestation (or if an attestation signer is compromised / a malicious signer is added) can craft forwarding calldata that triggers a token pull (e.g., depositForBurn/drain pattern) and cause tokens to be transferred out of the reserve/gateway wallet.
Steps To Reproduce:
- Prepare environment: deploy or fork a chain with
xReserve,GatewayWallet, andTokenMessengerinstances; ensurexReservesupports a token and sets unlimited allowances forTokenMessenger(this is the default behavior inTokenSupport). - Seed funds: deposit tokens into the reserve/gateway wallet so there are funds available for withdrawal.
- Craft malicious forwarding calldata that calls a TokenMessenger function that performs
transferFrom(msg.sender, to, amount)(e.g.,depositForBurnor a mockdrainfunction). Example calldata:abi.encodeWithSelector(TokenMessenger.depositForBurn.selector, amount, destDomain, recipientBytes32, tokenAddress). - Build an attestation (or use the PoC attestation signer in the test harness) whose
TransferSpeccontains hook data withforwardingContractpointing toTokenMessengerandforwardingCalldataset to the payload from step 3. Also setdestinationCallerso the reserve is a valid caller per validation (the PoC tests handle this). - Call
reserve.withdraw(encodedAttestations, signature)from an attacker-controlled account supplying the crafted attestation and signature. - Observe:
xReserveexecutes the forwarding viaAddress.functionCall(forwardingContract, forwardingCalldata)andTokenMessengerwilltransferFromfrom the reserve/local minter, moving tokens to the attacker/recipient.
Supporting Material/References:
Attestation is not easily forged — signature verification is via ECDSA and must come from an address in the attestationSigners set (lib/…/Mints.sol::_verifyAttestationSignature). However, the exploit remains real when one of the conditions/prerequisites is met:
- Compromise of one of the attestation signers' private keys (key leak, phish, or HSM/multisig compromise),
- Owner/gov adds malicious or misconfigured signers (e.g. owner adds signers without review),
- Replay/misconfiguration in the attestation/allowlist process allows attackers to issue valid attestations.
Relevant source locations:
src/modules/x-reserve/Withdrawal.sol—_validateAndProcessHookData/_processForwarding(executesAddress.functionCallon the forwarded calldata without selector whitelist)src/modules/x-reserve/TokenSupport.sol—_setUnlimitedAllowances(xReserve gives unlimited allowance totokenMessenger/tokenMessengerV2)lib/evm-cctp-contracts/src/TokenMessenger.solandlib/evm-cctp-contracts/src/v2/BaseTokenMessenger.sol—_depositForBurn/_depositAndBurninvoketransferFrom(msg.sender, ...)(the token pull occurs here)lib/evm-gateway-contracts/src/modules/minter/Mints.sol—_verifyAttestationSignatureandisAttestationSigner(attestation signature verification and signer allowlist)
A possible question is, what if the Wallet gateway is empty? That means the attacker won't gain any profit. just need to monitor or the "attacker" can create their own balance,
Flash loan amplification Attacker can borrow → deposit → drain → repay .
The point is that what you have to outwit is the SIGNER PART
buyme coffee: 0x136e744b1a2a2816b98556de69c636dd35e65306