Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Protect against OOB offset in variable list SSZ decoding

merged 16 commits into from Apr 20, 2020


Copy link

@paulhauner paulhauner commented Mar 31, 2020

Issue Addressed


Proposed Changes

As raised by @pventuzelo during fuzzing (thank you Patrick!!), SSZ decoding was failing to ensure that all offsets were in-bounds. This resulted in trying to allocation a billion-element Vec which caused a panic due to a failed memory allocation.

Ironically, I documented this bug (SSZ Offset Exploits #4) last year but failed to enforce it here. 🤦‍♂️

This PR:

  • Adds a fix for this bug.
  • Makes DecodeError more granular to be more certain that tests trigger certain code paths.
  • Add simple checks to stop VariableLists/FixedVectors from decoding lists that conflict with their variable/fixed sizes.
  • Only do a Vec::with_capacity for objects that specify a maximum length.
  • Fixes a bench that wasn't compiling (I needed it to check the performance impacts of not allocating)
  • Adds the pretty-ssz CLI command which allowed to me to parse the SSZ file from @pventuzelo. It should parse some SSZ then pretty-print it as YAML, but is useful for finding errors in SSZ decoding.
    • The command I used was lcli -s minimal pretty-ssz SignedBeaconBlock crash1_min.ssz

@paulhauner paulhauner added bug work-in-progress labels Mar 31, 2020
Copy link
Member Author

paulhauner commented Mar 31, 2020

This is a great catch by the fuzzer!

I had written a test that I thought covered this case:

<Vec<Vec<u16>>>::from_ssz_bytes(&[8, 0, 0, 0, 9, 0, 0, 0]),
Err(DecodeError::OutOfBoundsByte { i: 9 })

However, I was getting an Err here not because 9 is out-of-bounds, but instead because 9
is not a clean multiple of BYTES_PER_LENGTH_OFFSET:

// The fixed-length section must be a clean multiple of `BYTES_PER_LENGTH_OFFSET`.
if next_variable_byte != num_items * BYTES_PER_LENGTH_OFFSET {
return Err(DecodeError::InvalidByteLength {
len: next_variable_byte,
expected: num_items * BYTES_PER_LENGTH_OFFSET,

What we really needed to test this functionality was:

    <Vec<Vec<u16>>>::from_ssz_bytes(&[8, 0, 0, 0, 16, 0, 0, 0]),
    Err(DecodeError::OutOfBoundsByte { i: 16 })

I think there's a lesson to be learned about being specific with error types. If I had more specific DecodeError variants here then I would have noticed that we were getting the wrong error!

@paulhauner paulhauner marked this pull request as ready for review Apr 1, 2020
@paulhauner paulhauner added ready-for-review and removed work-in-progress labels Apr 1, 2020
lcli/src/ Outdated Show resolved Hide resolved
AgeManning added a commit that referenced this issue Apr 16, 2020
Copy link

@michaelsproul michaelsproul left a comment

Looks good. The updates appear to faithfully implement all the checks from the doc, while preserving existing behaviour.

@paulhauner paulhauner merged commit b374ead into master Apr 20, 2020
8 checks passed
@paulhauner paulhauner deleted the ssz-oob branch Apr 20, 2020
@michaelsproul michaelsproul added ready-to-squerge and removed ready-for-review labels Apr 20, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
bug security
None yet

Successfully merging this pull request may close these issues.

None yet

3 participants