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

Deserialization of array can panic #834

Closed
ggwpez opened this issue Jun 16, 2024 · 3 comments · Fixed by #837
Closed

Deserialization of array can panic #834

ggwpez opened this issue Jun 16, 2024 · 3 comments · Fixed by #837

Comments

@ggwpez
Copy link
Contributor

ggwpez commented Jun 16, 2024

The deserialisation code of a static sized array uses unwrap instead of propagating the error:

let result = core::array::from_fn(|_| {
T::deserialize_with_mode(&mut reader, compress, Validate::No).unwrap()
});

This was possibly done since the try_from_fn function is experimental. However, it can cause a panic instead of an error return.

I would propose to use a vector with capacity. There is probably a way to make it a faster with unsafe, but thats not my speciality:

let mut result = Vec::<T>::with_capacity(N);
for i in 0 .. N {
    result.push(T::deserialize_with_mode(&mut reader, compress, validate)?);
}

let array: [T; N] = result.try_into().map_err(|_| SerializationError::InvalidData)?;
Ok(array)
@burdges
Copy link
Contributor

burdges commented Jun 16, 2024

Yeah, this is a serious bug. nice catch.

There are crates that do this, like arrayvec, but the unsafe solution looks like this:

impl<T: CanonicalDeserialize, const N: usize> CanonicalDeserialize for [T; N] {
    #[inline]
    fn deserialize_with_mode<R: Read>(
        mut reader: R,
        compress: Compress,
        validate: Validate,
    ) -> Result<Self, SerializationError> {
        let mut array = MaybeUninit::uninit_array::<N>();
        for i in 0..N {
            array[i].write(T::deserialize_with_mode(&mut reader, compress, Validate::No) ?);
        }
        if let Validate::Yes = validate {
            T::batch_check(result.iter())?
        }
        Ok(unsafe { MaybeUninit::array_assume_init(array) })
    }
}

What this unsafe is solving is the possibility that no sensible default initilization exists for T.

@Pratyush
Copy link
Member

We want to avoid allocation when possible, but the MaybeUninit solution looks reasonable to me. Would folks be interested in opening a PR?

@burdges
Copy link
Contributor

burdges commented Jun 19, 2024

We should use arrayvec instead of my code above because arrayvec avoids possible memory leaks in my code. Also arrayvec already gets used in ff, not really adding a dependency even.

impl<T: CanonicalDeserialize, const N: usize> CanonicalDeserialize for [T; N] {
    #[inline]
    fn deserialize_with_mode<R: Read>(
        mut reader: R,
        compress: Compress,
        validate: Validate,
    ) -> Result<Self, SerializationError> {
        let mut array = ArrayVec::<T, N>::new();
        for i in 0..N {
            array[i].push(T::deserialize_with_mode(&mut reader, compress, Validate::No) ?);
        }
        if let Validate::Yes = validate {
            T::batch_check(result.iter())?
        }
        Ok(array.into_inner())
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants