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

Deserialize arrays: avoid leaking partially allocated memory when failing #825

Merged
merged 5 commits into from
Sep 16, 2024

Conversation

acerone85
Copy link
Contributor

@acerone85 acerone85 commented Sep 12, 2024

Closes #811

[Short description of the changes.]

  • Minor changes to the logic for deserializing arrays, using a [MaybeUninit; N] instead of MaybeUninit<[T]; N>.
  • Dropping initialized values when failing and the array has been partially initialized

Checklist

  • Breaking changes are clearly marked as such in the PR description and changelog
  • New behavior is reflected in tests
  • If performance characteristic of an instruction change, update gas costs as well or make a follow-up PR for that
  • The specification matches the implemented behavior (link update PR if changes are needed)

Before requesting review

  • I have reviewed the code myself
  • I have created follow-up issues caused by this PR and linked them here

After merging, notify other teams

[Add or remove entries as needed]

Comment on lines 463 to 465
let final_ptr = &mut uninit as *mut _ as *mut [T; N];
// SAFETY: All array elements have been initialized above.
let init = unsafe { uninit.assume_init() };
let init = unsafe { final_ptr.read() };
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is ugly, but mem::transmute cannot be used for arrays with generic length.

See rust-lang/rust#61956
(The solution advocated there also mentions calling mem::forget(uninit), but I don't think in this case is necessary and apparently it causes a compilation error.

@@ -430,23 +430,40 @@ impl<const N: usize, T: Deserialize> Deserialize for [T; N] {
} else {
// Spec doesn't say how to deserialize arrays with unaligned
// primitives(as `u16`, `u32`, `usize`), so unpad them.
let mut uninit = <MaybeUninit<[T; N]>>::uninit();
let mut uninit: [MaybeUninit<T>; N] =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I suggest this pattern instead:

// which do not require initialization.
let mut tmp: [MaybeUninit<T>; S] = unsafe { MaybeUninit::uninit().assume_init() };
let mut i = 0;
while i < c.len() {
match T::decompress_with(&c[i], ctx).await {
Ok(value) => {
// SAFETY: MaybeUninit can be safely overwritten.
tmp[i].write(value);
}
Err(e) => {
// Drop the already initialized elements, so we don't leak the memory
for initialized_item in tmp.iter_mut().take(i) {
// Safety: First i elements have been initialized successfully.
unsafe {
initialized_item.assume_init_drop();
}
}
return Err(e);
}
}
i += 1;
}
// SAFETY: Every element is initialized.
let result = tmp.map(|v| unsafe { v.assume_init() });
Ok(result)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 741007f

@@ -491,7 +505,7 @@ impl<'a> Input for &'a [u8] {

fn peek(&self, into: &mut [u8]) -> Result<(), Error> {
if into.len() > self.len() {
return Err(Error::BufferIsTooShort)
return Err(Error::BufferIsTooShort);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you revert all changes related to ; please?:)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This happens automatically when I run cargo +nightly fmt --all.
Is there anything I need to add to the rustfmt configuration?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (it also caught a trailing semicolon in another file, I will keep that change). See 81ebf29

@acerone85 acerone85 added this pull request to the merge queue Sep 16, 2024
Merged via the queue into master with commit 4b95333 Sep 16, 2024
39 checks passed
@acerone85 acerone85 deleted the 811_handle_errors_with_maybe_uninit branch September 16, 2024 09:47
@xgreenx xgreenx mentioned this pull request Sep 17, 2024
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 this pull request may close these issues.

Handle error in the cases with MaybeUninit
4 participants