Tadle

Tadle
DeFiFoundry
27,750 USDC
View results
Submission Details
Severity: high
Valid

Unrestricted Access to Approve Function

Summary

The approve function in the CapitalPool contract can be called by anyone, not just the intended TokenManager contract. This unrestricted access could lead to core functionality failures and potential malicious exploitation.

Vulnerability Details

The root cause of the vulnerability is that the approve function lacks a proper access control mechanism, allowing any external account to call it.

  • Found in src/core/CapitalPool.sol at Line 24

@>: dev noted that "only can be called by token manager" but approve can actually be called by anyone

/**
* @dev Approve token for token manager
@> * @notice only can be called by token manager
* @param tokenAddr address of token
*/
24:@> function approve(address tokenAddr) external {
25: address tokenManager = tadleFactory.relatedContracts(
...
39: }

Let us walk through the issue with the following scenario:

  1. Alice, who is a legitimate user of the TokenManager, triggers a token transfer that requires approval from the CapitalPool.

  2. Bob, a malicious actor, identifies this and frontruns Alice's transaction by calling the approve function with a malicious tokenAddr.

  3. As a result, Bob's malicious contract gets approved to transfer tokens from the CapitalPool, leading to potential undefined behavior, or worse, he can DOS a transfer of tokens like USDT, which has strict approval rules requiring existing allowances to be zero before setting a new allowance.

USDT's implementation:

@>: USDT requires user's allowedbalance to be 0 before proceeds to update the allowance. By frontruning the permissionless CapitalPool:approve, it's posible for BOB to DOS Alice's transaction.

function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {
// To change the approve amount you first have to reduce the addresses`
// allowance to zero by calling `approve(_spender, 0)` if it is not
// already 0 to mitigate the race condition described here:
// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
@> require(!((_value != 0) && (allowed[msg.sender][_spender] != 0)));
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
}

Impact

This vulnerability is significant as aside from breaking Dev's intended core functionality, malicious actor can also DOS a token transfer process, especially with tokens like USDT that have specific allowance requirements.

Tools Used

Manual Review

Recommendations

Add a check to ensure that only the TokenManager contract can call the approve function:

require(msg.sender == tokenManager, "Only Token Manager contract allowed");

Updates

Lead Judging Commences

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

finding-TokenManager-withdraw-userTokenBalanceMap-not-reset

Valid critical severity finding, the lack of clearance of the `userTokenBalanceMap` mapping allows complete draining of the CapitalPool contract. Note: This would require the approval issues highlighted in other issues to be fixed first (i.e. wrong approval address within `_transfer` and lack of approvals within `_safe_transfer_from` during ERC20 withdrawals)

Support

FAQs

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