Skip to content

Fix uninitialized memory exposure in IO reads#756

Open
matteoldani wants to merge 1 commit into
servo:mainfrom
matteoldani:fix-uninit-read
Open

Fix uninitialized memory exposure in IO reads#756
matteoldani wants to merge 1 commit into
servo:mainfrom
matteoldani:fix-uninit-read

Conversation

@matteoldani

Copy link
Copy Markdown
Contributor

This PR addresses an API soundness bug where uninitialized heap memory could be exposed to safe io::Read implementations.

Currently, both ReadExt::read_to_tendril and TendrilSink::read_from expand their internal buffers via push_uninitialized() before passing the resulting slice to a generic io::Read trait object. As noted by an existing FIXME comment in the codebase:

  │  // FIXME: this exposes uninitialized bytes to a generic R type
  │  // this is fine for R=File which never reads these bytes,
  │  // but user-defined types might.

While standard readers like std::fs::File only write to the buffer, a poorly written or unconventional custom io::Read implementation might attempt to read from the provided slice. Because these tendril functions are safe to call, exposing
uninitialized memory this way technically violates Rust's strict memory safety guarantees and triggers Undefined Behavior under Miri.

Changes:

• Following the precedent of the standard library (e.g., Vec ), this PR explicitly zero-initializes the newly appended buffer slice using std::ptr::write_bytes before passing it to the generic reader.
• Using write_bytes avoids the UB of creating safe Rust references ( &mut [u8] ) to uninitialized memory. It also compiles down directly to a highly optimized LLVM memset intrinsic, ensuring there is no meaningful performance regression.

Resolves #752

@github-actions github-actions Bot added the V-non-breaking A non-breaking change label Jul 3, 2026
Comment thread tendril/src/tendril.rs Outdated
Comment on lines 1400 to 1404
unsafe {
buf.push_uninitialized(new_write_size);
let ptr = buf[len..].as_mut_ptr();
std::ptr::write_bytes(ptr, 0, new_write_size as usize);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Given that the buffer is not actually uninitialized anymore I think we should be able to get rid of the unsafe. This might require adding a new API, something akin to Vec::resize.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

True.

I'll push a change that introduces push_zeros(n) that imho aligns with the overall tendril approach of "push_<>" rather then the absolute resize.

Comment thread tendril/src/tendril.rs Outdated
Comment thread tendril/src/tendril.rs
///
/// This grows the tendril and initializes the new area with 0s.
#[inline]
pub fn push_zeros(&mut self, n: u32) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

FYI: @simonwuelker this is the new API

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Two things that I forgot during the first review:

  • We should only expose this for Tendril<fmt::Bytes, A>, not for all formats, as we might violate the internal invariants of the tendril otherwise.
  • Let's pass the byte that should be written as an argument, that way the method is actually useful in a more general context. Maybe call the method extend?

Apologies for not thinking of this earlier :/

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No worries, I will work on them tomorrow.

Regarding the API shape, I don't have a strong preference. As I mentioned in the previous comment I kept the "push" shape since it seemed more common in tendril API but happy to refactor it into an extend

@github-actions github-actions Bot added V-non-breaking A non-breaking change and removed V-non-breaking A non-breaking change labels Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

V-non-breaking A non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Zero-initialize buffers in read_to_tendril and TendrilSink::read_from

2 participants