The KeeperProxy
contract includes sequencerUptimeFeed
checks to verify whether the sequencer on Arbitrum is operational. However, these checks are not implemented correctly. Since the protocol already performs some validation of the sequencerUptimeFeed
status, it should ensure all necessary checks are in place to avoid security risks.
According to the chainlink documentation, the sequencerUptimeFeed
can return a startedAt
value of 0 when queried during an "invalid round":
startedAt: This timestamp indicates when the sequencer changed status. This timestamp returns
0
if a round is invalid. When the sequencer comes back up after an outage, wait for theGRACE_PERIOD_TIME
to pass before accepting answers from the data feed. SubtractstartedAt
fromblock.timestamp
and revert the request if the result is less than theGRACE_PERIOD_TIME
.
An "invalid round" occurs when there is an issue updating the sequencer’s status, possibly due to network disruptions or insufficient oracle data. This is indicated by both startedAt = 0
and answer = 0
. Further clarification was provided by an official Chainlink engineer in the chainlink public discord.
Hello, @EricTee An "invalid round" means there was a problem updating the sequencer's status, possibly due to network issues or problems with data from oracles, and is shown by a
startedAt
time of 0. Normally, when a round starts,startedAt
is recorded, and the initial status (answer
) is set to0
. Later, both the answer and the time it was updated (updatedAt
) are set at the same time after getting enough data from oracles, making sure that answer only changes from0
when there's a confirmed update different from the start time. This process helps avoid mistakes in judging if the sequencer is available, which could cause security issues. Making surestartedAt
isn't0
is crucial for keeping the system secure and properly informed about the sequencer's status.
A key takeaway from Chainlink’s documentation and developer statement is:
Ensuring that startedAt is not 0 is crucial for maintaining security and accurately determining the sequencer's status.
The KeeperProxy::_validatePrice function contains an L2 sequencer check:
This check is flawed when executed during an "invalid round":
If startedAt = 0
, the arithmetic operation block.timestamp - startedAt
results in a value much greater than GRACE_PERIOD_TIME
.
For example, if block.timestamp = 1719739032
, then 1719739032 - 0 = 1719739032
, which is far greater than the required GRACE_PERIOD_TIME
(hardcoded as 3600).
As a result, the require(timeSinceUp > GRACE_PERIOD_TIME, "Grace period is not over");
condition never reverts, allowing function execution to proceed even if the sequencer uptime feed is in an invalid state.
This creates a security risk where validatePrice()
may incorrectly determine that the sequencer is operational when, in reality, the data is not properly updated.
The failure to properly validate sequencerUptimeFeed
status allows _validatePrice()
to succeed even when the sequencer uptime feed is in an invalid round.
Manual Review
Add a check that reverts if startedAt
is returned as 0.
startedAt is only 0 when contract is not initialized on Arbitrum, but it is already initialized on Arbitrum. startedAt is sufficient for the protocol, it does not need roundID. Current documentation of Chainlink does not have this sentence: “This timestamp returns `0` if a round is invalid.“
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.