Beacon Fuzz - Progress Update #7: Structural Differential Fuzzing
Beacon Fuzz - Update #07
Sigma Prime is leading the development and maintenance of beacon-fuzz, a differential fuzzing solution for Eth2 clients. This post is part of our series of status updates where we discuss current progress, interesting challenges encountered and direction for future work. See #00 and the repository's README for more context.
Summary
- Community Fuzzing Debrief
- Latest Bugs Identified
- Structural Differential Fuzzing with
beaconfuzz_v2
- Next Steps
Community Fuzzing Debrief
In our previous update, we announced our community fuzzing or "fuzzing at home" initiative, which allows anyone to contribute to the security of eth2 by running our eth2fuzz
tool from their laptops/desktops/cloud infrastructure, using Docker
.
We were very pleased with the response received from the community! Thanks to everyone who joined our fuzzing Discord channel and tried our Dockerised fuzzing framework.
Congratulations to @Butta, @cooganb, @Daft-Wullie and @MysticRyuujin for identifying 4 unique bugs!
We've realised that a lot of community members were attempting to run eth2fuzz
on Windows machines. While the dockerisation process was meant to ensure maximum interoperability between different operating systems, we realised that some of our settings were hardcoded for Linux support only. This was quickly fixed by the team and other improvements were added based on user feedback.
Latest Bugs Identified With ethfuzz
The community fuzzing initiative lead to the identification of the following bugs across three Eth2 implementations:
Lodestar
- Memory exhaustion/OOM when parsing invalid
ENR
string (refer to this issue for more details); AssertionError
inbcrypto
library (refer to this issue for more details);- Failed
val->IsArrayBufferView
assertion when parsing invalidENR
string (refer to this issue for more details).
Nimbus
- Overflow/underflow in
ProposerSlashing
processing (refer to this issue for more details).
Teku
- Lack of checks of validator indices in Attester Slashings (
ArrayIndexOutOfBoundsException
inAttesterSlashing
processing, refer to this issue for more details).
Differential Fuzzing
Over the last few weeks, we've been focussing our efforts on the differential fuzzing part of the project, i.e. beaconfuzz_v2
, in order to identify potential discrepancies between clients that could lead to consensus splits (forks) on the eth2 network. Our previous attempt at performing differential fuzzing across eth2 implementations was carried using a C++ based fuzzer, inherited from Guido Vranken's eth2.0-fuzzing and extended to support more eth2 implementations and target all state transition functions.
beaconfuzz_v2
has been written from scratch using Rust (the language we're most comfortable with at Sigma Prime) and the related Pull Request has now been merged into master
!
This allows us to perform structure-aware/grammar-based differential fuzzing on different eth2 clients. We currently support Lighthouse, Prysm and Nimbus. beaconfuzz_v2
has been developed in a modular approach, and we look forward to adding support for other eth2 implementations in the coming days.
beaconfuzz_v2
currently leverages two fuzzing engines: libfuzzer and honggfuzz.
Considering the complexity of this fuzzing framework (each fuzzer needs to instantiate goroutines for Prysm and their related garbage collectors), we're reaching a decent fuzzing speed as can be seen in the screenshots below (Lenovo laptop, 8x Intel i7-8550U CPU @ 1.80GHz, 16GB of RAM):
Honggfuzz exercising the process voluntary_exit functions on Lighthouse, Prysm and Nimbus
libFuzzer exercising the process deposit functions on Lighthouse, Prysm and Nimbus
Structural Awareness
As explained in a previous update, structural fuzzing, or grammar-based fuzzing, is a technique instructing fuzzing engines to generate inputs based on a particular set of rules. By default, coverage-guided mutation based fuzzers such as AFL or Honggfuzz do not provide any input grammar, which can result in inefficient fuzzing for complicated input types (such as the ones we use in eth2), where any traditional mutation (e.g. bit flipping, bit XORing) is exceedingly unlikely to produce a valid input (in our case invalid SSZ containers).
By leveraging the latest update to the libfuzzer-sys
and cargo_fuzz
crates, beaconfuzz_v2
defines fuzz targets that take well-formed instances of custom types by deriving and implementing the Arbitrary
trait, which allows us to create structured inputs from raw byte buffers. This has been incorporated into Beacon Fuzz via this Pull Request.
beaconfuzz_v2
provides two different types of fuzzers:
HonggFuzz
targets: Standard differential mutation fuzzing, using the standard mutation algorithms from HonggFuzz. Useful for detecting differences between clients when processing malformed/invalid consensus objects (e.g.fuzz_attestation
,fuzz_proposer_slashing
,fuzz_voluntary_exit
, etc.);libFuzzer
targets (targets names ending with-struct
): Structural differential fuzzing leveraging the work described above to produce valid consensus objects (e.g.fuzz_attestation-struct
,fuzz_proposer_slashing-struct
,fuzz_voluntary_exit-struct
, etc.).
Identifying Our First Discrepancy
While running our fuzzers, we've identified a discrepancy affecting Prysm when handling an invalid SignedVoluntaryExit
which looks like the following:
{
Message:
{
Epoch:
0,
ValidatorIndex:
0
},
Signature:
0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
}
Note: BLS signature verification is disabled on all targets to speed up fuzzing execution and exercise different code
While Lighthouse and Nimbus both rejected this consensus object when calling their process_exit
functions, Prysm seemed to accept it and produced a post-BeaconState
as a result of the state transition.
Our fuzzers are calling the ProcessVoluntaryExitsNoVerify
on Prysm, which we thought only disabled BLS signature verification. It turned out that this function also skips the spec checks on VoluntaryExit
s! Compare ProcessVoluntaryExitsNoVerify
with the function actually used in production (VerifyExit
) for details.
This allowed us to confirm that our differential fuzzing was indeed working! Thanks Nishant for quickly fixing this issue in this Pull Request.
First Consensus Bug Identified with beaconfuzz_v2
Our fuzzing effort lead to the identification of another discrepancy affecting Prysm on Attestation
processing.
Similarly to the VoluntaryExit
fuzzing, we are targeting the ProcessAttestationNoVerify
function when calling the Prysm FFI. This function is also missing spec checks, particularly verifying that the attesting indices are non-empty. This check is performed using the isValidAttestationIndices
function and appeared to be missing in Prysm's attestation batch verification (VerifyAttestations
in ProcessOperations
does not call isValidAttestationIndices
).
This bug, if exploited in the wild, could have caused a network split. Thanks to Protolambda for helping us debug this issue and kudos to the Prysmatic Labs team for providing a quick fix in this Pull Request.
Next Steps
Over the next few weeks, the Beacon Fuzz team will be looking into:
- Finalising the integration of Teku into
beaconfuzz_v2
- Running
beaconfuzz_v2
on a dedicated fuzzing infrastructure - Start attacking the P2P networking stack of Eth2 clients