diff --git a/crates/ssz/tests/encode_decode.rs b/crates/ssz/tests/encode_decode.rs index 610027a..ac5e65b 100644 --- a/crates/ssz/tests/encode_decode.rs +++ b/crates/ssz/tests/encode_decode.rs @@ -150,6 +150,31 @@ fn vec_of_vecs_empty() { assert_eq!(Vec::>::from_ssz_bytes(&encoded).unwrap(), val); } +// A zero first offset would imply 0 elements while the offset/scope boundary +// describes 1 element spanning the whole input; it must be rejected up front. +// See https://github.com/leanEthereum/leanSpec/issues/1175 +#[test] +fn vec_of_vecs_zero_first_offset() { + let bytes = [0x00, 0x00, 0x00, 0x00, 0xAA, 0xBB, 0xCC, 0xDD]; + assert_eq!( + Vec::>::from_ssz_bytes(&bytes), + Err(DecodeError::InvalidFirstOffset { + expected: 4, + got: 0 + }) + ); + + // Same with no trailing data: only an offset table pointing at itself. + let bytes = [0x00, 0x00, 0x00, 0x00]; + assert_eq!( + Vec::>::from_ssz_bytes(&bytes), + Err(DecodeError::InvalidFirstOffset { + expected: 4, + got: 0 + }) + ); +} + // ── ContainerEncoder / ContainerDecoder ── /// Simulates a container: { a: u32, b: Vec, c: u16 } diff --git a/fuzz/fuzz_targets/offset_adversarial.rs b/fuzz/fuzz_targets/offset_adversarial.rs index b2d6a76..25f65f8 100644 --- a/fuzz/fuzz_targets/offset_adversarial.rs +++ b/fuzz/fuzz_targets/offset_adversarial.rs @@ -34,6 +34,11 @@ struct FixedThenVar { data: Vec, } +#[derive(Debug, PartialEq, SszEncode, SszDecode)] +struct VarItemsField { + items: Vec>, +} + fuzz_target!(|data: &[u8]| { // Attempt decode of each struct — must never panic regardless of input. // If decode succeeds, roundtrip must hold. @@ -61,4 +66,18 @@ fuzz_target!(|data: &[u8]| { let decoded = FixedThenVar::from_ssz_bytes(&encoded).unwrap(); assert_eq!(decoded, v, "FixedThenVar roundtrip"); } + + // Lists of variable-length items have their own offset table, exercising + // decode_variable_length_items_with_max rather than ContainerDecoder. + if let Ok(v) = Vec::>::from_ssz_bytes(data) { + let encoded = v.to_ssz(); + let decoded = Vec::>::from_ssz_bytes(&encoded).unwrap(); + assert_eq!(decoded, v, "Vec> roundtrip"); + } + + if let Ok(v) = VarItemsField::from_ssz_bytes(data) { + let encoded = v.to_ssz(); + let decoded = VarItemsField::from_ssz_bytes(&encoded).unwrap(); + assert_eq!(decoded, v, "VarItemsField roundtrip"); + } });