Boost _ Folks Finance 33568 - [Smart Contract - Medium] Front-running vulnerability in cross-chain loan creation process could lead in funds loss for users

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)

Description

Brief/Intro

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.

Vulnerability Details

  • 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:

  1. 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 arbitrary loanId 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));
         }
  2. 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.

  3. 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 same loanId.

    • Due to the speed advantage, the attacker's transaction is processed first.

  4. Victim Transaction Failure:

    • When the victim's transaction reaches the hub, it calls createUserLoan than deposit 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.

  5. Financial Implications:

    • The victim loses crosschain fees for the failed transaction.

    • tokens that were transferred as part of createLoanAndDeposit, may be lost for ever.

  6. 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 or returnGasLimit weren't specified, reversal will fail, and cause permanent loss of deposited funds.

  • 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.

Impact Details

  • 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 return gas 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.

Proof of concept

Proof of Concept

Here's a step-by-step scenario demonstrating the vulnerability:

  1. Victim initiates a createLoanAndDeposit transaction on a spoke chain.

// On Spoke Chain
spokeToken.createLoanAndDeposit(params, accountId, loanId, amount, loanTypeId, loanName);
  1. Attacker observes this transaction and quickly submits a createLoan transaction directly to the spokeCommon contract on the hub chain.

// On Hub Chain
spokeCommon.createLoan(accountId, loanId, loanTypeId, loanName);
  1. Due to faster hub-to-hub communication, the attacker's transaction is processed first:

    • In LoanManager.sol, the createUserLoan function successfully creates the loan for the attacker.

 // 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
 }
  1. When the victim's transaction reaches the hub, it fails:

    • In LoanManager.sol, the createUserLoan 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
    }
  2. The victim's transaction is reverted, but they've already paid for:

    • Gas fees for the initial transaction

    • Cross-chain messaging fees

  3. 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.

Last updated