Lack of _loan
validation
Description
There is a lack of validation on the _loan
object.
The
ExternalLongStrategy._rebalanceExternally
function calls_getLoan
in order to retrieve the_loan
associated with the specifiedtokenId
and verify that themsg.sender
is the creator of the loan.
function _getLoan(uint256 tokenId) internal virtual view returns(LibStorage.Loan storage _loan) {
_loan = s.loans[tokenId];
if(tokenId != uint256(keccak256(abi.encode(msg.sender, address(this), _loan.id)))) {
revert Forbidden();
}
}
However, there is currently no check in place to ensure that the _loan
exists and that the _loan.id
is not a zero value.
Therefore, to bypass the check that the msg.sender
is the loan's creator, a caller can pass the tokenId
equal to uint256(keccak256(abi.encode(msg.sender, address(this), _loan.id))
where _loan.id
equals zero.
This would cause the function to return an empty _loan
object.
There is not a check that the
_loan
associated withtokenId
exists inside theExternalLiquidationStrategy._liquidateExternally
function.
function _liquidateExternally(uint256 tokenId, uint128[] calldata amounts, uint256 lpTokens, address to, bytes calldata data) external override lock virtual returns(uint256 loanLiquidity, uint256[] memory refund) {
uint128[] memory tokensHeld;
uint256 writeDownAmt;
uint256 collateral;
LibStorage.Loan storage _loan = s.loans[tokenId];
(loanLiquidity, collateral, tokensHeld, writeDownAmt) = getLoanLiquidityAndCollateral(_loan, s.cfmm);
...
}
As a result, the empty _loan
object can be used inside this function.
Impact
Calling these functions with an empty _loan
object will result in a reversion with an out-of-bounds error when trying to iterate over the empty tokensHeld
array stored within the _loan
object. However, even though this error provides a measure of protection, it is still crucial to perform explicit verification of data to prevent unexpected behavior during function execution.
Recommendations
Add a check that the _loan
associated with tokenId
is not empty.
function _getLoan(uint256 tokenId) internal virtual view returns(LibStorage.Loan storage _loan) {
_loan = s.loans[tokenId];
+ require(_loan.id != 0)
if(tokenId != uint256(keccak256(abi.encode(msg.sender, address(this), _loan.id)))) {
revert Forbidden();
}
}
function _liquidateExternally(uint256 tokenId, uint128[] calldata amounts, uint256 lpTokens, address to, bytes calldata data) external override lock virtual returns(uint256 loanLiquidity, uint256[] memory refund) {
uint128[] memory tokensHeld;
uint256 writeDownAmt;
uint256 collateral;
LibStorage.Loan storage _loan = s.loans[tokenId];
+ require(_loan.id != 0)
(loanLiquidity, collateral, tokensHeld, writeDownAmt) = getLoanLiquidityAndCollateral(_loan, s.cfmm);
...
}
Remediation
GammaSwap acknowledged this finding and implemented a fix in commit ec1a429e↗.