You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository has been archived by the owner on Jan 12, 2025. It is now read-only.
In Solidity, when a function in one contract calls a function on another contract, the msg.sender in the second function call refers to the address of the contract that made the call, not the original external caller. More can be read here.
There are several instances in the protocol where the wrong address is expected to be msg.sender. The reasons is that it makes the incorrect assumption that msg.sender stays the same but in fact it changes to the contract that made the function call.
Vulnerability Detail
Here are the instances where an issues occurs with explanation:
Voter.sol and BribeRewarder.sol are coded so that they interact with each other. When a user votes using Voter.sol: vote() the following subsequent calls to functions happen: Voter.sol: vote() -> Voter.sol: _notifyBribes() -> BribeRewarder.sol: deposit() -> BribeRewarder.sol: _modify()
Let's look at a part of the code for _modify():
if (!IVoter(_caller).ownerOf(tokenId, msg.sender)) {
revertBribeRewarder__NotOwner();
}
It calls Voter.sol:ownerOf() to check if msg.sender is the owner of the tokenId. Here the wrong assumption is made that msg.sender still refers to the EOA that called vote(). That is not the case as msg.sender is now the Voter.sol contract itself.
Voter.sol: createFarms() is a function that can be called only by the owner. It calls MasterchefV2.sol: add():
function add(IERC20token, IMasterChefRewarder extraRewarder) externaloverride {
if (msg.sender!=address(_lbHooksManager)) _checkOwnerOrOperator();
...
}
Here we check that msg.sender is either _lbHooksManager, owner or operator. _lbHooksManager address will not be set to Voter.sol and we can see that from the constructor:
Owner and operator on the other hand are both EOAs. The same issue that occurs in 1 happens here. Msg.sender is expected to be either owner, operator or hooks manager but instead it will be the address of the Voter.sol contract.
BaseRewarder.sol expects a _caller to be set in the constructor:
// @param caller The address of the contract that will call the onModify function.constructor(addresscaller) {
_caller = caller;
MasterChefRewarder.sol inherits from BaseRewarder.sol and sets this caller during its own deployment:
//@param caller The address of the contract that will call the onModify function.constructor(addresscaller) BaseRewarder(caller) {}
As we can see from the comments this caller contract is expected to call MasterChefRewarder.sol: onModify(). Let's see what happens if it does:
We can see that MasterChefRewarder.sol: onModify() calls BaseRewarder.sol: onModify():
//@dev Called by the caller contract to update the rewards for a given account.function onModify(addressaccount, uint256pid, uint256oldBalance, uint256newBalance, uint256oldTotalSupply)
publicvirtualoverridereturns (uint256) {
if (msg.sender!= _caller) revertBaseRewarder__InvalidCaller();
}
As you can see it expects that msg.sender is the caller contract that called MasterChefRewarder.sol: onModify(). However that is not the case as the msg.sender is now the MasterChefRewarder.sol contract itself.
Impact
In all instances of this bug the transaction will simply revert. Because of this a lot of the functionality of the protocol will not work.
For 1: BribeRewarder.sol: _modify() is called on deposits and claims. Deposits happen when users call Voter.sol: vote(). The vote() function already checks if caller is the owner of the tokenId. However claim() does not. The simplest fix is to remove the check for the owner of the tokenId in _modify() and instead put it in claim():
0xboriskataa
High
Incorrect
msg.sender
expectedSummary
In Solidity, when a function in one contract calls a function on another contract, the
msg.sender
in the second function call refers to the address of the contract that made the call, not the original external caller. More can be read here.There are several instances in the protocol where the wrong address is expected to be
msg.sender
. The reasons is that it makes the incorrect assumption thatmsg.sender
stays the same but in fact it changes to the contract that made the function call.Vulnerability Detail
Here are the instances where an issues occurs with explanation:
Voter.sol
andBribeRewarder.sol
are coded so that they interact with each other. When a user votes usingVoter.sol: vote()
the following subsequent calls to functions happen:Voter.sol: vote()
->Voter.sol: _notifyBribes()
->BribeRewarder.sol: deposit()
->BribeRewarder.sol: _modify()
Let's look at a part of the code for
_modify()
:It calls
Voter.sol:ownerOf()
to check ifmsg.sender
is the owner of thetokenId
. Here the wrong assumption is made thatmsg.sender
still refers to the EOA that calledvote()
. That is not the case asmsg.sender
is now theVoter.sol
contract itself.Voter.sol: createFarms()
is a function that can be called only by the owner. It callsMasterchefV2.sol: add()
:Here we check that
msg.sender
is either_lbHooksManager
, owner or operator._lbHooksManager
address will not be set toVoter.sol
and we can see that from the constructor:Owner and operator on the other hand are both EOAs. The same issue that occurs in 1 happens here.
Msg.sender
is expected to be either owner, operator or hooks manager but instead it will be the address of theVoter.sol
contract.BaseRewarder.sol
expects a_caller
to be set in the constructor:MasterChefRewarder.sol
inherits fromBaseRewarder.sol
and sets this caller during its own deployment:As we can see from the comments this
caller
contract is expected to callMasterChefRewarder.sol: onModify()
. Let's see what happens if it does:We can see that
MasterChefRewarder.sol: onModify()
callsBaseRewarder.sol: onModify()
:As you can see it expects that
msg.sender
is thecaller
contract that calledMasterChefRewarder.sol: onModify()
. However that is not the case as themsg.sender
is now theMasterChefRewarder.sol
contract itself.Impact
In all instances of this bug the transaction will simply revert. Because of this a lot of the functionality of the protocol will not work.
Code Snippet
https://github.com/sherlock-audit/2024-06-magicsea/blob/main/magicsea-staking/src/rewarders/BribeRewarder.sol#L264
https://github.com/sherlock-audit/2024-06-magicsea/blob/main/magicsea-staking/src/MasterchefV2.sol#L368
https://github.com/sherlock-audit/2024-06-magicsea/blob/main/magicsea-staking/src/rewarders/BaseRewarder.sol#L239
Tool used
Manual Review
Recommendation
For 1:
BribeRewarder.sol: _modify()
is called on deposits and claims. Deposits happen when users callVoter.sol: vote()
. Thevote()
function already checks if caller is the owner of thetokenId
. Howeverclaim()
does not. The simplest fix is to remove the check for the owner of thetokenId
in_modify()
and instead put it inclaim()
:For 2: Expect the caller of
MasterchefV2.sol: add()
to be the voter contract:For 3:
Perhaps you can change the constructor of
MasterChefRewarder.sol
like so:Duplicate of #39
The text was updated successfully, but these errors were encountered: