diff --git a/crates/ssz/src/decode.rs b/crates/ssz/src/decode.rs index 6986f4d..8c5070f 100644 --- a/crates/ssz/src/decode.rs +++ b/crates/ssz/src/decode.rs @@ -262,6 +262,9 @@ fn decode_byte_array_vec(bytes: &[u8]) -> Result, D } let count = bytes.len() / N; let mut result = Vec::<[u8; N]>::with_capacity(count); + // SAFETY: The length check guarantees `count * N == bytes.len()`, and + // `with_capacity(count)` allocates at least `count * N` bytes, so the + // copy and `set_len` stay in bounds. unsafe { core::ptr::copy_nonoverlapping(bytes.as_ptr(), result.as_mut_ptr() as *mut u8, bytes.len()); result.set_len(count); diff --git a/crates/ssz/src/encode.rs b/crates/ssz/src/encode.rs index 6feea40..0fdb917 100644 --- a/crates/ssz/src/encode.rs +++ b/crates/ssz/src/encode.rs @@ -43,11 +43,23 @@ pub trait SszEncode { } } +/// Reinterpret `&[T]` as `&[u8]` and append to `buf`. +/// +/// # Safety +/// +/// `T` must not have padding bytes. +/// +/// # Correctness +/// +/// `T` must have a little-endian in-memory layout matching SSZ wire +/// format, unless `T` is endianness-independent (e.g. `[u8; N]`). #[cfg(feature = "alloc")] -fn append_fixed_slice_as_bytes(items: &[T], buf: &mut Vec) { - let byte_slice = unsafe { - core::slice::from_raw_parts(items.as_ptr().cast::(), core::mem::size_of_val(items)) - }; +unsafe fn append_fixed_slice_as_bytes(items: &[T], buf: &mut Vec) { + // Padding bytes in a valid `T` may be uninitialized, and a `&[u8]` + // covering uninitialized bytes is UB (a reference must point to + // valid values, and uninitialized `u8` is invalid). + let byte_slice = + core::slice::from_raw_parts(items.as_ptr().cast::(), core::mem::size_of_val(items)); buf.extend_from_slice(byte_slice); } @@ -95,7 +107,8 @@ impl SszEncode for u8 { fn ssz_append_fixed_slice(items: &[Self], buf: &mut Vec) { #[cfg(target_endian = "little")] { - append_fixed_slice_as_bytes(items, buf); + // SAFETY: primitive integers have no padding. + unsafe { append_fixed_slice_as_bytes(items, buf) }; } #[cfg(not(target_endian = "little"))] { @@ -128,7 +141,8 @@ impl SszEncode for u16 { fn ssz_append_fixed_slice(items: &[Self], buf: &mut Vec) { #[cfg(target_endian = "little")] { - append_fixed_slice_as_bytes(items, buf); + // SAFETY: primitive integers have no padding. + unsafe { append_fixed_slice_as_bytes(items, buf) }; } #[cfg(not(target_endian = "little"))] { @@ -161,7 +175,8 @@ impl SszEncode for u32 { fn ssz_append_fixed_slice(items: &[Self], buf: &mut Vec) { #[cfg(target_endian = "little")] { - append_fixed_slice_as_bytes(items, buf); + // SAFETY: primitive integers have no padding. + unsafe { append_fixed_slice_as_bytes(items, buf) }; } #[cfg(not(target_endian = "little"))] { @@ -194,7 +209,8 @@ impl SszEncode for u64 { fn ssz_append_fixed_slice(items: &[Self], buf: &mut Vec) { #[cfg(target_endian = "little")] { - append_fixed_slice_as_bytes(items, buf); + // SAFETY: primitive integers have no padding. + unsafe { append_fixed_slice_as_bytes(items, buf) }; } #[cfg(not(target_endian = "little"))] { @@ -227,7 +243,8 @@ impl SszEncode for u128 { fn ssz_append_fixed_slice(items: &[Self], buf: &mut Vec) { #[cfg(target_endian = "little")] { - append_fixed_slice_as_bytes(items, buf); + // SAFETY: primitive integers have no padding. + unsafe { append_fixed_slice_as_bytes(items, buf) }; } #[cfg(not(target_endian = "little"))] { @@ -260,7 +277,8 @@ impl SszEncode for [u8; N] { } #[cfg(feature = "alloc")] fn ssz_append_fixed_slice(items: &[Self], buf: &mut Vec) { - append_fixed_slice_as_bytes(items, buf); + // SAFETY: [u8; N] has no padding. + unsafe { append_fixed_slice_as_bytes(items, buf) }; } }