https://github.com/Cyfrin/2024-08-fjord/blob/main/src/FjordStaking.sol#L561
FjordStaking::_unstakeVested
is an internal function, which can be called via unstakeVested
and onStreamCanceled
function.
unstakeVested
function allows users to unstake their whole NFT, while onStreamCanceled
is called when the stream creator cancels the stream and the function either unstakes the NFT or removes the staked amount for the NFT.
But when onStreamCanceled
function calls _unstakeVested
, where _unstakeVested
function will unstake the amount for the NFT and also updates the staked amount on FjordPoints
contract via FjordPoints::onUnstaked
.
But instead of passing the sablier NFT owner it passes msg.sender
i.e., Sablier contract to the onUnstaked
function and as Sablier contract has 0 staked amount on fjord points, it will result in a revert which thus reverts the onStreamCanceled
function but Sablier contract will ignore this revert.
Thus the stream was cancelled but staked data remains unupdated on FjordStaking
contract and the sablier NFT owner will be able to receive rewards on the whole value including the value which was supposed to be unstaked.
The vulnerability is present in the FjordStaking::_unstakeVested
where it passes msg.sender
as user for which unstaking is done to onUnstaked
function instead of streamOwner
.
Here, msg.sender
will not be the sablier NFT owner (stream owner) in all cases.
For the case when _unstakeVested
is called by onStreamCanceled
function, then msg.sender
is going to be the Sablier contract and not the stream owner as a result of which it tries to reduce the staked amount data of Sablier contract on FjordPoints
contract, thus leading to revert.
This revert would thus cause the onStreamCanceled
function to revert and all the updations related to unstaking of amount of user will be undone, but the stream creator cancelling of the stream would be successful as the revert caused by onStreamCanceled
is ignored by Sablier contract.
When stream is cancelled by creator, the NFT holder's will have no ownership of the unstreamed amount. But as onStreamCanceled
faced a revert, the unstreamed amount is still in the accounting of the FjordStaking
and FjordPoints
contract.
Thus, user will be able to receive rewards on the amount which they do not own as it is still in their staked amount.
Manual Review
In FjordStaking::_unstakeVested
, instead of passing msg.sender
to onUnstaked
function, pass streamOwner
Indeed the `points.onUnstaked` should use the streamOwner instead of msg.sender as an input parameter. Impact: high - The vested stakers who got their streams canceled will keep on receiving rewards (points included) for the previously staked stream. Likelihood: low - whenever a Sablier stream sender decides to `cancel()` a recipient's stream
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.