#38828 [BC-Low] Decode RLP of Legacy Transaction Allows Tailing Bytes

Submitted on Jan 14th 2025 at 19:33:55 UTC by @CertiK for Attackathon | Ethereum Protocol

  • Report ID: #38828

  • Report Type: Blockchain/DLT

  • Report severity: Low

  • Target: https://github.com/ledgerwatch/erigon

  • Impacts:

    • (Specifications) A bug in specifications with no direct impact on client implementations

Description

Brief/Intro

RLP serialization is widely used in the Ethereum clients, which provides a standard format for the data transfer between nodes.

In Erigon (https://github.com/erigontech/erigon/ ), the decode RLP of legacy tx misses check the tailing bytes, which allows extra tailing bytes during the RLP decoding. This could possibly be a consensus issue as other Ethereum clients reject such legacy transactions.

Vulnerability Details

Affected Codebase: https://github.com/erigontech/erigon/tree/v2.61.0

The function DecodeRLPTransaction() is intended to decode the RLP encoding of transactions:

https://github.com/erigontech/erigon/blob/v2.61.0/core/types/transaction.go#L116

For non-legacy transactions, it invokes UnmarshalTransactionFromBinary()that handles the tailing bytes with the check s.Remaining() != 0:

https://github.com/erigontech/erigon/blob/v2.61.0/core/types/transaction.go#L178C1-L211C2

However, if it’s the legacy transaction, it calls tx.DecodeRLP(s) to directly decode the legacy transaction, in which the tailing bytes is unchecked. This oversight would allow tailing bytes to sneak into the legacy transaction when calling DecodeRLPTransaction() to decode the legacy transaction.

https://github.com/erigontech/erigon/blob/v2.61.0/core/types/legacy_tx.go#L291C1-L340C2

Impact Details

The function DecodeRLPTransaction() has been widely utilized in the Erigon codebase to decode the transactions from block, block body and user’s submitted raw transaction. Since tailing bytes of a transaction is disallowed in other Ethereum clients, it could introduce consensus issues with this flawed decoding function.

References

  • https://github.com/erigontech/erigon/tree/v2.61.0

Proof of Concept

Proof of Concept

For simplicity, we provide the following unit test to compare two transaction decoding functions, DecodeRLPTransaction() vs UnmarshalTransactionFromBinary().

The encoded legacy transaction is []byte{248, 99, 128, 2, 1, 148, 9, 94, 123, 174, 166, 166, 199, 196, 194, 223, 235, 151, 126, 250, 195, 38, 175, 85, 45, 135, 128, 134, 97, 98, 99, 100, 101, 102, 37, 160, 142, 183, 96, 239, 234, 152, 124, 9, 98, 209, 245, 242, 175, 209, 122, 221, 51, 54, 23, 237, 233, 142, 83, 7, 50, 17, 119, 99, 15, 34, 57, 125, 160, 21, 141, 44, 195, 195, 220, 247, 64, 91, 79, 6, 37, 93, 226, 96, 69, 227, 240, 8, 168, 169, 112, 118, 124, 14, 250, 73, 19, 190, 7, 104, 57, 1} that contains the last taling bytes 0x01.

  1. Run the following unit test:

  1. The test results show that the decoding of this legacy transaction with tailing bytes succeeds with DecodeRLPTransaction() but fails with UnmarshalTransactionFromBinary().

Was this helpful?