Submitted on Tue Jul 23 2024 15:39:34 GMT-0400 (Atlantic Standard Time) by @A2Security for Boost | Folks Finance
Report ID: #33568
Report type: Smart Contract
Report severity: Medium
Target: https://testnet.snowtrace.io/address/0x2cAa1315bd676FbecABFC3195000c642f503f1C9
Impacts:
- Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield
- Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol)
The BridgeRouter
system contains a vulnerability in its loan creation process. This flaw allows malicious actors to front-run legitimate loan creation attempts, causing financial losses to users and disrupting the normal operation of the cross-chain lending platform. The issue stems from the ability to create loans with arbitrary IDs
combined with the speed disparity between hub-to-hub and spoke-to-hub transactions.
- The vulnerability lies in the loan creation process, specifically in the interaction between spoke chains and the hub chain. The core issue stems from the ability of users to choose arbitrary
loanId
when creating loans, combined with the speed difference between hub-to-hub and spoke-to-hub transactions.
Here's a detailed breakdown of the vulnerability:
-
Loan Creation Process:
- Users can initiate loan creation from spoke chains using the
createLoanAndDeposit
function in SpokeToken.sol.
function createLoanAndDeposit( Messages.MessageParams memory params, bytes32 accountId, bytes32 loanId, uint256 amount, uint16 loanTypeId, bytes32 loanName ) external payable nonReentrant { _doOperation( params, Messages.Action.CreateLoanAndDeposit, accountId, amount, abi.encodePacked(loanId, poolId, amount, loanTypeId, loanName) ); }
- This function allows users to specify any
loanId
they want. - The
createLoan
function in SpokeCommon.sol on the hub chain also allows arbitraryloanId
selection.function createLoan( Messages.MessageParams memory params, bytes32 accountId, bytes32 loanId, uint16 loanTypeId, bytes32 loanName ) external payable nonReentrant { _doOperation(params, Messages.Action.CreateLoan, accountId, abi.encodePacked(loanId, loanTypeId, loanName)); }
- Users can initiate loan creation from spoke chains using the
-
Transaction Speed Disparity:
- Hub-to-hub transactions are processed in a single transaction, as seen with
HubAdapter.sol
.
function sendMessage(Messages.MessageToSend calldata message) external payable override onlyBridgeRouter { // ... (other code) IBridgeRouter bridgeRouter = msg.sender == address(bridgeRouterSpoke) ? bridgeRouterHub : bridgeRouterSpoke; bridgeRouter.receiveMessage{ value: msg.value }(messageReceived); // ... (other code) }
- Spoke-to-hub transactions require cross-chain messaging, making them significantly slower.
- Hub-to-hub transactions are processed in a single transaction, as seen with
-
Exploitation Mechanism:
- A malicious user observes a victim's
createLoanAndDeposit
transaction on a spoke chain. - The attacker quickly calls
createLoan
from SpokeCommon.sol on the hub chain using the sameloanId
. - Due to the speed advantage, the attacker's transaction is processed first.
- A malicious user observes a victim's
-
Victim Transaction Failure:
- When the victim's transaction reaches the hub, it calls
createUserLoan
thandeposit
in LoanManager.sol . - This function includes a check:
function createUserLoan(bytes32 loanId, bytes32 accountId, uint16 loanTypeId, bytes32 loanName) external override onlyRole(HUB_ROLE) nonReentrant { // ... (other checks) if (isUserLoanActive(loanId)) revert UserLoanAlreadyCreated(loanId); // ... (rest }
- Since the
loanId
is already active due to the attacker's transaction, the victim's transaction reverts.
- When the victim's transaction reaches the hub, it calls
-
Financial Implications:
- The victim loses crosschain fees for the failed transaction.
- tokens that were transferred as part of
createLoanAndDeposit
, may be lost for ever.
-
Reversal Complications:
- The victim can attempt to reverse the transaction to not lose his deposited funds, however this also incurs additional costs:
- Fees for the initial cross-chain message (spoke to hub)
- Fees for the reversal cross-chain message (hub to spoke)
- Gas costs for executing both actions
- If
returnAdapterId
orreturnGasLimit
weren't specified, reversal will fail, and cause permanent loss of deposited funds.
- The victim can attempt to reverse the transaction to not lose his deposited funds, however this also incurs additional costs:
- This vulnerability exploits the system's cross-chain architecture and the lack of
loanId
reservation or verification mechanisms. It allows malicious actors to block legitimate loan creations and cause significant financial losses to users, undermining the security and reliability of the entire loan system.
- This vulnerability enables attackers to front-run loan creation transactions, causing immediate financial losses to users through failed transactions and reversal costs.
- More critically, if users fail to specify the
returnIdAdapter
or returngas limit
, their transactions become non-reversible, resulting in permanent loss of deposited funds. This flaw not only disrupts the platform's core loan creation functionality but also introduces a severe risk of irretrievable asset loss. - The combination of transaction failures, unexpected costs, and the possibility of permanent fund loss poses an existential threat to the platform's operational integrity make this vulnerability high severity.
Here's a step-by-step scenario demonstrating the vulnerability:
- Victim initiates a
createLoanAndDeposit
transaction on a spoke chain.
// On Spoke Chain
spokeToken.createLoanAndDeposit(params, accountId, loanId, amount, loanTypeId, loanName);
- Attacker observes this transaction and quickly submits a
createLoan
transaction directly to thespokeCommon
contract on the hub chain.
// On Hub Chain
spokeCommon.createLoan(accountId, loanId, loanTypeId, loanName);
- Due to faster hub-to-hub communication, the attacker's transaction is processed first:
- In
LoanManager.sol
, thecreateUserLoan
function successfully creates the loan for the attacker.
- In
// In LoanManager.sol
function createUserLoan(bytes32 loanId, bytes32 accountId, uint16 loanTypeId, bytes32 loanName) {
if (isUserLoanActive(loanId)) revert UserLoanAlreadyCreated(loanId);
// Loan created successfully for the attacker
}
-
When the victim's transaction reaches the hub, it fails:
- In
LoanManager.sol
, thecreateUserLoan
function reverts because the loanId is already active.
// In LoanManager.sol function createUserLoan(bytes32 loanId, bytes32 accountId, uint16 loanTypeId, bytes32 loanName) { if (isUserLoanActive(loanId)) revert UserLoanAlreadyCreated(loanId); // Reverts here because loanId is already active }
- In
-
The victim's transaction is reverted, but they've already paid for:
- Gas fees for the initial transaction
- Cross-chain messaging fees
-
To recover, the victim must initiate a reversal on the Hub Chain.
This reversal incurs additional costs:
- Gas fees for the reversal transaction
- Cross-chain messaging fees for returning funds
This PoC demonstrates how an attacker can cause financial losses to a victim by front-running their loan creation, exploiting the speed difference between hub-to-hub and spoke-to-hub transactions.