sherlock-audit / 2024-08-winnables-raffles-judging

3 stars 1 forks source link

0x73696d616f - `CCIPClient` `whenHealthy` modifier will lead to stuck `ETH` due to DoSing claim and cancel #236

Closed sherlock-admin3 closed 3 weeks ago

sherlock-admin3 commented 1 month ago

0x73696d616f

Medium

CCIPClient whenHealthy modifier will lead to stuck ETH due to DoSing claim and cancel

Summary

CCIPClient has a whenHealthy modifier in the ccipSend() function, which means it can DoS _sendCCIPMessage() calls in WinnablesTicketManager. This would be particularly harmful in several scenarios:

  1. In case a raffle does not meet the minimum tickets threshold, it must be canceled. However, cancelling sets the status to CANCELED and allows users to claim refunds, but also sends a message to WinnablesPrizeManager to allow the admins to get their funds back. If the router is not healthy, it will revert. This procedure should be perfomed in a 2 step such that users can get their refunds right away, as they don't need to wait for the ccip router to work.
  2. Users buy tickets but the router is DoSed and WinnablesTicketManager::propagateRaffleWinner() reverts when calling _sendCCIPMessage(). This means that the protocol can never claim its ETH although the cross chain message was not required to be successful. A two step procedure would also fix this.

Scenario 1 breaks the specification in the readme

Participants in a raffle that got cancelled can always get refunded

Root Cause

The Chainlink Router has the whenHealthy modifier in ccipSend(), called in _sendCCIPMessage(), which DoSes the router as can be seen in the code linked above in lines 293-296.

WinnablesTicketManager does not deal with the notHealthy modifier.

Internal pre-conditions

None.

External pre-conditions

Chainlink pauses the Router.

Attack Path

The examples are given: A

  1. Users participate by calling WinnablesTicketManager::buyTickets().
  2. Not enough tickets were bought so the raffle should be canceled, but Chainlink DoSes the router.
  3. WinnablesTicketManager::cancelRaffle() calls the Chainlink router to send a message, but it reverts due to the modifier. Users can not get their refunds back until the Chainlink router is back up.

B

  1. Users participate by calling WinnablesTicketManager::buyTickets().
  2. Chainlink DoSes the router after the raffle ends, DoSing WinnablesTicketManager::propagateRaffleWinner().
  3. The protocol can not claim the locked ETH due to point 2 even though the cross chain message was not required.

Impact

In scenario A, users can not claim their refunds until the router is back up. In B, the protocol can not claim the ETH back even though it could be safely retrieved.

PoC

Check the mentioned Chainlink router links and the fact that the code never checks if the router is not healthy before calling _sendCCIPMessage().

Mitigation

The WinnablesTicketManager::cancelRaffle() and WinnablesTicketManager::propagateRaffleWinner() functions should be split into 2 separate steps, to always make sure users or the protocol can get their funds.

0xmahdirostami commented 1 month ago

the issue seems like a best practice advice, doesn't deserve medium severity.

Brivan-26 commented 1 month ago

@0xsimao Even tho I said I wouldn't step in here again, but I have to because all your comments and the validity of this issue is based on Chainlink being cursed

And finally the admin expects refunds to be always possible, which is not the case due to Chainlink being cursed.

This protocol is cross-chain, so any action on the tickets manager on the Avalench chain should notify the PrizeManager on the Ethereum destination chain. If Chainhainlink is cursed(supposing so, even if it never happened), it makes sense to stop all the actions that require cross-chain messages, even canceling the raffle because the cancel requires notifying the prizeManager to unlock the prize.

So, the admin expecting _refunds to be always possible_ and the contest README saying that *Participants in a raffle that got cancelled can always get refunded* is when the protocol is under normal behavior, it does not apply when cross-chain messaging is unsafe due to Chainlink being cursed.

Again, read the README word by word:

Participants in a raffle that got cancelled can always get refunded

The invariant is that users should always get refunded for raffles that GOT CANELLED. You can't and it is unsafe to cancel raffles if Chainlink is cursed. So, the invariant is NOT broken

The fix proposed diverges from the protocol's intended behavior. The fix suggests not to notify the prizeManager on the destination chain when canceling a raffle. This is incorrect, when canceling a raffle, the prizeManager on the destination chain should be notified to unlock the prizes. So, following your fix, if Chainlink is cursed, canceling the raffle will succeed on the source chain BUT the prizeManager will not be notified on the destination chain and the prize will not be unlocked. Something that is incorrect from the protocol's perspective

0xsimao commented 1 month ago

This protocol is cross-chain, so any action on the tickets manager on the Avalench chain should notify the PrizeManager on the Ethereum destination chain. If Chainhainlink is cursed(supposing so, even if it never happened), it makes sense to stop all the actions that require cross-chain messages, even canceling the raffle because the cancel requires notifying the prizeManager to unlock the prize.

What makes sense is what is in the readme, that is, users always get their refunds. The admin can claim it later, when Chainlink is no longer cursed.

So, the admin expecting refunds to be always possible and the contest README saying that Participants in a raffle that got cancelled can always get refunded is when the protocol is under normal behavior, it does not apply when cross-chain messaging is unsafe due to Chainlink being cursed.

Why would that be if it's possible to issue refunds safely when Chainlink is cursed? And the admin can get the funds after Chainlink is no longer cursed.

The invariant is that users should always get refunded for raffles that GOT CANELLED. You can't and it is unsafe to cancel raffles if Chainlink is cursed. So, the invariant is NOT broken

We have discussed this, the statement assumes that raffles can be cancelled. The reason for cancelling raffles is not reaching the minimum tickets. So whenever the minimum tickets are not reached, the raffle must be cancelled and users should get their refunds. Naturally we can not give refunds if the round can not even be cancelled. The main premise is broken, so the statement is also broken. Nothing is unsafe, with the fix, users get their refunds and the admin claims the prize later.

The fix proposed diverges from the protocol's intended behavior. The fix suggests not to notify the prizeManager on the destination chain when canceling a raffle. This is incorrect, when canceling a raffle, the prizeManager on the destination chain should be notified to unlock the prizes. So, following your fix, if Chainlink is cursed, canceling the raffle will succeed on the source chain BUT the prizeManager will not be notified on the destination chain and the prize will not be unlocked. Something that is incorrect from the protocol's perspective

False. With the fix, users always get their refunds and the admin is guaranteed to claim the prize later, when Chainlink is not cursed. The difference the fix makes is users can always get their refunds, the admin will get the prize at the same time it would as in the current code (has to wait for Chainlink to not be cursed). This does not deviate from the protocol's intention at all.

WangSecurity commented 1 month ago
  1. Indeed, there is data that chainlink can be cursed and pause the router and it happened in the past. But based on this data, all these situations were resolved within days.
  2. The fix doesn't affect the validity of the issue or the judging outcome.
  3. The README statement is not broken:

    Participants in a raffle that got cancelled can always get refunded

If, due to this issue, the raffle can be cancelled, the above statement is not broken. It's not about the raffle being cancelled under any conditions or certain conditions. After the Chainlink issue is resolved, the participants can easily get their refund. I don't see "always" as "at any point at a time", it's about getting the refund for any raffle that got cancelled and this is not broken, since in the end the user can still get their refunds.

This issue doesn't break the README statement and doesn't qualify for the DOS requirements to be considered a medium-severity issue. Planning to accept the escalation and invalidate the issue.

0xsimao commented 1 month ago

@WangSecurity the docs don't say that the issue will be resolved in less than 1 week.

And the temporal aspect of always is being ignored, could you explain why this is so? The readme is the source of truth and the word 'always' has a temporal meaning, not only certainty, so the issue is valid.

Definition of always

every time or all the time

So a few days where it is false does not match the definition of always.

neko-nyaa commented 1 month ago

Definition of time from the same dictionary

the part of existence that is measured in minutes, days, years, etc., or this process considered as a whole

So a few days where it is false does match the definition of always, as long as the process considered as a whole is still completed

Brivan-26 commented 1 month ago

@WangSecurity I wanna add one more thing to your point here:

If, due to this issue, the raffle can be cancelled, the above statement is not broken. It's not about the raffle being cancelled under any conditions or certain conditions.

This protocol is a cross-chain protocol. When cancelling the raffle on the source chain, the destination chain must be informed of such action to unlock the prize. So, even if the Chainlink router is cursed, it is not by protocol design to cancel the raffle on the source chain and only notify the destination chain when the router is live again, as this report suggests.

So, if the chainlink router is DoSed, all actions must stop and everything can be finalized after the router is live again. Participants will still be able to cancel the raffle and get refunded when the router is live, with no loss of funds, and no DoS of the protocol either. This protocol should not function if the CCIP router is down.

0xsimao commented 1 month ago

So, if the chainlink router is DoSed, all actions must stop and everything can be finalized after the router is live again.

This is false as per the readme, users should always get their refunds.

Brivan-26 commented 1 month ago

So, if the chainlink router is DoSed, all actions must stop and everything can be finalized after the router is live again.

This is false as per the readme, users should always get their refunds.

@0xsimao I’ve explained this point multiple times during our discussion, and it seems to be the key reason for the extended debate.

You are misinterpreting the contest README: Participants in a raffle that got cancelled can always get refunded. This invariant is stating that users should get refunded for CANCELLED raffles. Since raffle cancellation and winner propagation rely on cross-chain communication, if the CCIP router is subject to a DoS, the cancellation should not proceed. As a result, the statement in the README remains intact and unbroken because there is no cancelled raffle.

0xsimao commented 1 month ago

@Brivan-26

  1. cancel happens when the tickets don't reach the minimum threshold
  2. the tickets didn't reach the minimum threshold, so the round should be cancelled
  3. due to the modifier, the round can not be cancelled

The readme says

Participants in a raffle that got cancelled can always get refunded

which assumes rounds can be cancelled, which is not true due to the cursed modifier. The base assumption is broken, so the statement is also broken. The conceptual reason for the round being cancelled is there, which is the minimum tickets not being reached, so the round should be cancelled. If it is not cancelled users can not get their refunds.

Since raffle cancellation and winner propagation rely on cross-chain communication, if the CCIP router is subject to a DoS, the cancellation should not proceed. As a result, the statement in the README remains intact and unbroken because there is no cancelled raffle.

Again, the readme is clear and users can always be refunded. So this means the protocol should proceed for the users refunds, not that is should be halted.

WangSecurity commented 4 weeks ago

the docs don't say that the issue will be resolved in less than 1 week

I understand, but based on the historical evidence, these issues were resolved in less than a week.

As I've said previously, the README statement is not about users being able to get a refund at any point in time; it's about users being able to get the funds for any cancelled raffle they've participated in. I see that it may be confusing, but still, this invariant doesn't refer to "any point in time".

Hence, my decision remains the same as previously: the issue doesn't break README and doesn't qualify for DOS requirements. Hence, planning to accept the escalation and invalidate the issue.

AtanasDimulski commented 3 weeks ago

Hey @WangSecurity is there any reason why is this escalation not accepted yet, and the finding invalidated. It has been 2 days since your comment, and no one is disputing anything publicly?

WangSecurity commented 3 weeks ago

I was checking some things before resolving this.

WangSecurity commented 3 weeks ago

Result: Invalid Has duplicates

sherlock-admin4 commented 3 weeks ago

Escalations have been resolved successfully!

Escalation status: