#37462 [BC-Low] Invalid RLP decoding for single bytes
Submitted on Dec 5th 2024 at 10:45:39 UTC by @Franfran for Attackathon | Ethereum Protocol
Report ID: #37462
Report Type: Blockchain/DLT
Report severity: Low
Target: https://github.com/hyperledger/besu
Impacts:
(Specifications) A bug in specifications with no direct impact on client implementations
Description
Brief/Intro
The decodeOne
function in the RLP
class has a bug which makes it accept invalid inputs for an encoded value that has the same prefix as a single byte and could trigger consensus failures if the MPT contains some invalid state.
Vulnerability Details
There is a corectness bug in the implementation of the RLP.decodeOne
function.
Indeed, if we look at the Ethereum official documentation about the RLP format, it is stated that
By looking at the decodeOne
function code, we can see the case of the single byte as:
We can see that if the prefix is a single byte, the full encodedValue is returned while it is not checked that this value is indeed a single byte.
Let's write a test to confirm this behavior, which can be done by extending RLPTest.java
Impact Details
This function is called twice in the codebase.
https://github.com/hyperledger/besu/blob/e5c9f55f8b52d7a15d3b37db9beb076fb2f1c121/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/framing/Framer.java#L281
https://github.com/hyperledger/besu/blob/472357f118832ae4a0374a31bea71d33d2408259/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/accumulator/DiffBasedWorldStateUpdateAccumulator.java#L402 The only relevant one is 2. because in the case of 1., only a single byte can be passed to the
decodeOne
function. In the case of 2., if an invalid RLP value would be stored in the Merkle Patricia Trie (MPT) that would wrongly be decoded by this function such that it starts with aSINGLE_BYTE
and ends up with other bytes, while other clients with the correct implementation would disagree with the world view of Besu, triggering a consensus failure. Because of this assumption, the chosen vulnerability level was set toLOW
. Nonetheless, it is important to consider this report in order to fix the implementation since future code may rely on the assumption that any RLP-encoded value whose prefix is the single byte will error like other implementations. Let's for instance considergo-ethereum
, which has a different implementation of the decoding of an RLP value whose prefix is a single byte: https://github.com/ethereum/go-ethereum/blob/67a3b087951a3f3a8e341ae32b6ec18f3553e5cc/rlp/decode.go#L1056-L1058
Then, the prefix is of type Byte
and is validated correctly: https://github.com/ethereum/go-ethereum/blob/67a3b087951a3f3a8e341ae32b6ec18f3553e5cc/rlp/decode.go#L376-L381
Since it's going to return a decodeError
if the length of the slice is not 1.
References
https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp https://github.com/hyperledger/besu/blob/472357f118832ae4a0374a31bea71d33d2408259/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/RLP.java#L150-L152
Proof of Concept
Proof of Concept
As expected, this test fails because the function doesn't throw as it should.
Was this helpful?