Although `ERC20Boost.transfer` and `ERC20Boost.transferFrom` functions try to prevent sender from transferring her or his gauge boost amount that is not free to receiver, such sender can still call `UtilityManager.forfeitBoost` and `bHermes.transfer` or `bHermes.transferFrom` functions to bypass such prevention #910
After calling the following ERC20Boost.attach function to attach a user's boost to a gauge, such user's getUserBoost[user] is updated to the corresponding balanceOf[user].toUint128(). Since getUserBoost[user] for such user becomes higher, the value returned by the ERC20Boost.freeGaugeBoost function below becomes lower.
function freeGaugeBoost(address user) public view returns (uint256) {
return balanceOf[user] - getUserBoost[user];
}
Because the following ERC20Boost.transfer and ERC20Boost.transferFrom functions call the ERC20Boost.notAttached modifier below, the previously mentioned user is only able to transfer up to the boost amount returned by the ERC20Boost.freeGaugeBoost function to the to address. This prevents such user from sending her or his gauge boost amount that is not free.
function transferFrom(address from, address to, uint256 amount)
public
override
notAttached(from, amount)
returns (bool)
{
return super.transferFrom(from, to, amount);
}
However, such user can call the following UtilityManager.forfeitBoost function to reduce her or his userClaimedBoost and then call the bHermes.transfer or bHermes.transferFrom function below to transfer a bHermes token amount, which equals such user's gauge boost amount that is not free, to the to address.
function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
uint256 userBalance = balanceOf[from];
if (
userBalance - userClaimedWeight[from] < amount || userBalance - userClaimedBoost[from] < amount
|| userBalance - userClaimedGovernance[from] < amount
) revert InsufficientUnderlying();
return super.transferFrom(from, to, amount);
}
Afterwards, the receiving user corresponding to the to address can call the following UtilityManager.claimBoost function to use the received bHermes token amount to claim the boost amount, which equals the sending user's gauge boost amount that is not free. As a result, although the ERC20Boost.transfer and ERC20Boost.transferFrom functions try to prevent the sending user from transferring her or his gauge boost amount that is not free to the receiving user, such sending user can still call the UtilityManager.forfeitBoost and bHermes.transfer or bHermes.transferFrom functions to bypass such prevention.
The following steps can occur for the described scenario.
Alice and Bob collude.
Alice has 1e18 bHermes and claimed 1e18 gauge boost.
All of Alice's 1e18 gauge boost are attached to a gauge.
Alice calls the UtilityManager.forfeitBoost function with the amount input being 1e18 to reduce her userClaimedBoost to 0.
Alice calls the bHermes.transfer function to transfer 1e18 bHermes to Bob.
Bob calls the UtilityManager.claimBoost function to claim 1e18 gauge boost using the received 1e18 bHermes even though Alice's 1e18 gauge boost are not free.
Tools Used
VSCode
Recommended Mitigation Steps
The UtilityManager.forfeitBoost function can be updated to only allow msg.sender to forfeit up to her or his gauge boost amount that is free. In other words, calling this function with the amount input being more than msg.sender's gauge boost amount that is free should revert.
Lines of code
https://github.com/code-423n4/2023-05-maia/blob/62f4f01a522dcbb4b9abfe2f6783bbb67c0da022/src/erc-20/ERC20Boost.sol#L116-L135 https://github.com/code-423n4/2023-05-maia/blob/62f4f01a522dcbb4b9abfe2f6783bbb67c0da022/src/erc-20/ERC20Boost.sol#L81-L83 https://github.com/code-423n4/2023-05-maia/blob/62f4f01a522dcbb4b9abfe2f6783bbb67c0da022/src/erc-20/ERC20Boost.sol#L312-L314 https://github.com/code-423n4/2023-05-maia/blob/62f4f01a522dcbb4b9abfe2f6783bbb67c0da022/src/erc-20/ERC20Boost.sol#L323-L330 https://github.com/code-423n4/2023-05-maia/blob/62f4f01a522dcbb4b9abfe2f6783bbb67c0da022/src/erc-20/ERC20Boost.sol#L341-L344 https://github.com/code-423n4/2023-05-maia/blob/53c7fe9d5e55754960eafe936b6cb592773d614c/src/hermes/UtilityManager.sol#L78-L84 https://github.com/code-423n4/2023-05-maia/blob/62f4f01a522dcbb4b9abfe2f6783bbb67c0da022/src/hermes/bHermes.sol#L140-L149 https://github.com/code-423n4/2023-05-maia/blob/62f4f01a522dcbb4b9abfe2f6783bbb67c0da022/src/hermes/bHermes.sol#L158-L167
Vulnerability details
Impact
After calling the following
ERC20Boost.attach
function to attach a user's boost to a gauge, such user'sgetUserBoost[user]
is updated to the correspondingbalanceOf[user].toUint128()
. SincegetUserBoost[user]
for such user becomes higher, the value returned by theERC20Boost.freeGaugeBoost
function below becomes lower.https://github.com/code-423n4/2023-05-maia/blob/62f4f01a522dcbb4b9abfe2f6783bbb67c0da022/src/erc-20/ERC20Boost.sol#L116-L135
https://github.com/code-423n4/2023-05-maia/blob/62f4f01a522dcbb4b9abfe2f6783bbb67c0da022/src/erc-20/ERC20Boost.sol#L81-L83
Because the following
ERC20Boost.transfer
andERC20Boost.transferFrom
functions call theERC20Boost.notAttached
modifier below, the previously mentioned user is only able to transfer up to the boost amount returned by theERC20Boost.freeGaugeBoost
function to theto
address. This prevents such user from sending her or his gauge boost amount that is not free.https://github.com/code-423n4/2023-05-maia/blob/62f4f01a522dcbb4b9abfe2f6783bbb67c0da022/src/erc-20/ERC20Boost.sol#L312-L314
https://github.com/code-423n4/2023-05-maia/blob/62f4f01a522dcbb4b9abfe2f6783bbb67c0da022/src/erc-20/ERC20Boost.sol#L323-L330
https://github.com/code-423n4/2023-05-maia/blob/62f4f01a522dcbb4b9abfe2f6783bbb67c0da022/src/erc-20/ERC20Boost.sol#L341-L344
However, such user can call the following
UtilityManager.forfeitBoost
function to reduce her or hisuserClaimedBoost
and then call thebHermes.transfer
orbHermes.transferFrom
function below to transfer a bHermes token amount, which equals such user's gauge boost amount that is not free, to theto
address.https://github.com/code-423n4/2023-05-maia/blob/53c7fe9d5e55754960eafe936b6cb592773d614c/src/hermes/UtilityManager.sol#L78-L84
https://github.com/code-423n4/2023-05-maia/blob/62f4f01a522dcbb4b9abfe2f6783bbb67c0da022/src/hermes/bHermes.sol#L140-L149
https://github.com/code-423n4/2023-05-maia/blob/62f4f01a522dcbb4b9abfe2f6783bbb67c0da022/src/hermes/bHermes.sol#L158-L167
Afterwards, the receiving user corresponding to the
to
address can call the followingUtilityManager.claimBoost
function to use the received bHermes token amount to claim the boost amount, which equals the sending user's gauge boost amount that is not free. As a result, although theERC20Boost.transfer
andERC20Boost.transferFrom
functions try to prevent the sending user from transferring her or his gauge boost amount that is not free to the receiving user, such sending user can still call theUtilityManager.forfeitBoost
andbHermes.transfer
orbHermes.transferFrom
functions to bypass such prevention.https://github.com/code-423n4/2023-05-maia/blob/53c7fe9d5e55754960eafe936b6cb592773d614c/src/hermes/UtilityManager.sol#L119-L125
https://github.com/code-423n4/2023-05-maia/blob/62f4f01a522dcbb4b9abfe2f6783bbb67c0da022/src/hermes/bHermes.sol#L77-L82
Proof of Concept
The following steps can occur for the described scenario.
UtilityManager.forfeitBoost
function with theamount
input being 1e18 to reduce heruserClaimedBoost
to 0.bHermes.transfer
function to transfer 1e18 bHermes to Bob.UtilityManager.claimBoost
function to claim 1e18 gauge boost using the received 1e18 bHermes even though Alice's 1e18 gauge boost are not free.Tools Used
VSCode
Recommended Mitigation Steps
The
UtilityManager.forfeitBoost
function can be updated to only allowmsg.sender
to forfeit up to her or his gauge boost amount that is free. In other words, calling this function with theamount
input being more thanmsg.sender
's gauge boost amount that is free should revert.Assessed type
Other