Platform: Code4rena
Start Date: 05/01/2023
Pot Size: $90,500 USDC
Total HM: 55
Participants: 103
Period: 14 days
Judge: Picodes
Total Solo HM: 18
Id: 202
League: ETH
Rank: 25/103
Findings: 3
Award: $557.15
🌟 Selected for report: 2
🚀 Solo Findings: 0
🌟 Selected for report: csanuragjain
Also found by: 7siech, KIntern_NA, Koolex, bin2chen, cergyk, evan, obront, unforgiven
135.5273 USDC - $135.53
https://github.com/code-423n4/2023-01-astaria/blob/main/src/VaultImplementation.sol#L239
An unapproved, non-owner of collateral can still take loan for the owner/operator of collateral even when owner did not needed any loan. This is happening due to incorrect checks as shown in POC. This leads to unintended loan and associated fees for users
commitToLien
function by User X. Params used by User X are as below:collateralId = params.tokenContract.computeId(params.tokenId) = 1 CT.ownerOf(1) = User Y CT.getApproved(1) = User Z CT.isApprovedForAll(User Y, User X) = false receiver = User Y
_requestLienAndIssuePayout
which then calls _validateCommitment
function for signature verification_validateCommitment
functionfunction _validateCommitment( IAstariaRouter.Commitment calldata params, address receiver ) internal view { uint256 collateralId = params.tokenContract.computeId(params.tokenId); ERC721 CT = ERC721(address(COLLATERAL_TOKEN())); address holder = CT.ownerOf(collateralId); address operator = CT.getApproved(collateralId); if ( msg.sender != holder && receiver != holder && receiver != operator && !CT.isApprovedForAll(holder, msg.sender) ) { revert InvalidRequest(InvalidRequestReason.NO_AUTHORITY); } VIData storage s = _loadVISlot(); address recovered = ecrecover( keccak256( _encodeStrategyData( s, params.lienRequest.strategy, params.lienRequest.merkle.root ) ), params.lienRequest.v, params.lienRequest.r, params.lienRequest.s ); if ( (recovered != owner() && recovered != s.delegate) || recovered == address(0) ) { revert IVaultImplementation.InvalidRequest( InvalidRequestReason.INVALID_SIGNATURE ); } }
a. User X is not owner of passed collateral b. User X is not approved for this collateral c. User X is not approved for all of User Y token
uint256 collateralId = params.tokenContract.computeId(params.tokenId); ERC721 CT = ERC721(address(COLLATERAL_TOKEN())); address holder = CT.ownerOf(collateralId); address operator = CT.getApproved(collateralId); if ( msg.sender != holder && receiver != holder && receiver != operator && !CT.isApprovedForAll(holder, msg.sender) ) { revert InvalidRequest(InvalidRequestReason.NO_AUTHORITY); }
if ( msg.sender != holder && // true since User X is not the owner receiver != holder && // false since attacker passed receiver as User Y which is owner of collateral, thus failing this if condition receiver != operator && !CT.isApprovedForAll(holder, msg.sender) ) { revert InvalidRequest(InvalidRequestReason.NO_AUTHORITY); }
Revise the condition as shown below:
if ( msg.sender != holder && msg.sender != operator && !CT.isApprovedForAll(holder, msg.sender) ) { revert InvalidRequest(InvalidRequestReason.NO_AUTHORITY); } if ( receiver != holder && receiver != operator ) { revert InvalidRequest(InvalidRequestReason.NO_AUTHORITY); }
#0 - c4-judge
2023-01-24T22:48:00Z
Picodes marked the issue as duplicate of #565
#1 - c4-judge
2023-02-15T07:07:39Z
Picodes marked the issue as satisfactory
#2 - c4-judge
2023-02-15T07:08:23Z
Picodes marked the issue as selected for report
#3 - c4-judge
2023-02-15T07:18:02Z
Picodes changed the severity to QA (Quality Assurance)
#4 - c4-judge
2023-02-15T07:22:02Z
This previously downgraded issue has been upgraded by Picodes
🌟 Selected for report: rbserver
Also found by: 0Kage, 0xcm, bin2chen, caventa, csanuragjain, synackrst, unforgiven
39.0944 USDC - $39.09
The condition verifying that deposited amount is greater than minDepositAmount()
is incorrect. Instead of checking assets > minDepositAmount()
, the function incorrectly checks shares > minDepositAmount()
. This could either allow :
minDepositAmount()
is 100deposit
function with amount 1000function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) { // Check for rounding error since we round down in previewDeposit. require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES"); require(shares > minDepositAmount(), "VALUE_TOO_SMALL"); // Need to transfer before minting or ERC777s could reenter. ERC20(asset()).safeTransferFrom(msg.sender, address(this), assets); _mint(receiver, shares); emit Deposit(msg.sender, receiver, assets, shares); afterDeposit(assets, shares); }
lets say this resulted in 5 shares from previewDeposit(assets)
The deposit fails since 5<minDepositAmount()=100
But this is incorrect since user deposit amount was 1000 which is 10x more than min amount
Revise the deposit function as below (this is done correctly in mint function)
function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) { ... require(assets > minDepositAmount(), "VALUE_TOO_SMALL"); ... }
#0 - c4-judge
2023-01-26T20:08:23Z
Picodes marked the issue as duplicate of #486
#1 - c4-judge
2023-02-21T22:15:00Z
Picodes marked the issue as satisfactory
🌟 Selected for report: csanuragjain
Also found by: 0xbepresent
382.5279 USDC - $382.53
https://github.com/code-423n4/2023-01-astaria/blob/main/src/Vault.sol#L65
It is possible to make a deposit even when _loadVISlot().isShutdown
is true. This check is done in whenNotPaused
modifier and is already done for Public Vault but is missing for Private Vault, allowing unexpected deposits
function deposit(uint256 amount, address receiver) public virtual returns (uint256) { VIData storage s = _loadVISlot(); require(s.allowList[msg.sender] && receiver == owner()); ERC20(asset()).safeTransferFrom(msg.sender, address(this), amount); return amount; }
whenNotPaused
modifier in deposit which confirm that shutdown has not been called yetmodifier whenNotPaused() { if (ROUTER().paused()) { revert InvalidRequest(InvalidRequestReason.PAUSED); } if (_loadVISlot().isShutdown) { revert InvalidRequest(InvalidRequestReason.SHUTDOWN); } _; }
Revise the deposit function as below:
function deposit(uint256 amount, address receiver) public virtual whenNotPaused returns (uint256) { VIData storage s = _loadVISlot(); require(s.allowList[msg.sender] && receiver == owner()); ERC20(asset()).safeTransferFrom(msg.sender, address(this), amount); return amount; }
#0 - Picodes
2023-01-26T20:07:35Z
It makes sense though considering it's a private vault so only the owner can deposit
#1 - c4-sponsor
2023-02-01T23:16:29Z
SantiagoGregory marked the issue as sponsor confirmed
#2 - c4-judge
2023-02-23T08:24:52Z
Picodes marked the issue as satisfactory
#3 - c4-judge
2023-02-24T09:32:24Z
Picodes marked the issue as selected for report
#4 - c4-judge
2023-02-24T09:32:30Z
Picodes marked the issue as primary issue