-
Notifications
You must be signed in to change notification settings - Fork 10
obront - Batcher frames are incorrectly decoded leading to consensus split #279
Comments
Comment from Optimism Description: A malicious or buggy batcher could submit a frame that the op-node would accept that a spec compliant node would not due to a bug in our parsing code. Reason: This would cause a consensus failure between different clients. Action: We need to fix this code. |
Escalate for 250 USDC This submission concerns a specific consensus failure due to parsing of batcher frames. It has been wrongly duped to #53, which concerns a contract level issue with posted output roots for withdrawals. Optimism has acknowledged the validity of the issue and this is certainly just a misclick, as the issues are entirely unrelated. |
You've created a valid escalation for 250 USDC! To remove the escalation from consideration: Delete your comment. You may delete or edit your escalation comment anytime before the 48-hour escalation window closes. After that, the escalation becomes final. |
Escalation accepted and resolving incorrect duplication state |
This issue's escalations have been accepted! Contestants' payouts and scores will be updated according to the changes made on this issue. |
obront
medium
Batcher frames are incorrectly decoded leading to consensus split
Summary
There is an error in the implementation of how frames are decoded, which will allow invalid frames to be accepted. This can allow a malicious sequencer to cause a consensus split between op-node implementations and the (correct) reference implementation.
Vulnerability Detail
Optimism implements a highly efficient derivation scheme based on compression of L2 data that is sent to an L1 address. The spec clearly defines how this works. Channels are split into frames, each one encoded as defined here. Frames will be aggregated by the derivation driver.
Docs state that:
Specifically:
Clearly,
is_last
is mandatory as per the specs. However, if we look at the code it will accept a frame even ifis_last
is not supplied.Decoding of the frame is done in
frame.go
, in theUnmarshalBinary
function. After reading the frame data, only the last byte remains.If the
ByteReader
object is empty, reading the next byte will return anEOF
and the error clause is skipped. The result ofReadByte
when an error occurs isundefined
, however in all Go setups we've testedisLastByte
is zero. This means it setsf.IsLast = false
and returns theEOF
.Back in
ParseFrames
which callsUnmarshalBinary
, theEOF
is ignored and the frame is accepted:So, it is demonstrated that an invalid frame is accepted by the Optimism implementation, provided the frame is the last one in the frames buffer. The impact is that a malicious sequencer can cause a consensus split between correct implementations and the reference implementation. It has been defined by the rules as Medium severity:
All that is needed is to send in different frame packages two frames of a channel, omit the
is_last
byte in the first frame and make sure it is the last frame in the package.Impact
Malicious sequencer can easily cause a consensus split by taking advantage of the incorrect frame reading logic in op-node.
Code Snippet
https://github.com/ethereum-optimism/optimism/blob/407f97b9d13448b766624995ec824d3059d4d4f6/op-node/rollup/derive/frame.go#L93
Tool used
Manual Review
Recommendation
In
UnmarshalBinary
, return a non-EOF error whenis_last
byte does not exist.The text was updated successfully, but these errors were encountered: