Fix uninitialized memory exposure in IO reads#756
Conversation
| 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); | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
9bdd320 to
c23026f
Compare
| /// | ||
| /// This grows the tendril and initializes the new area with 0s. | ||
| #[inline] | ||
| pub fn push_zeros(&mut self, n: u32) { |
There was a problem hiding this comment.
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 :/
There was a problem hiding this comment.
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
This PR addresses an API soundness bug where uninitialized heap memory could be exposed to safe
io::Readimplementations.Currently, both
ReadExt::read_to_tendrilandTendrilSink::read_fromexpand their internal buffers viapush_uninitialized()before passing the resulting slice to a genericio::Readtrait object. As noted by an existing FIXME comment in the codebase: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 usingstd::ptr::write_bytesbefore passing it to the generic reader.• Using
write_bytesavoids the UB of creating safe Rust references (&mut [u8]) to uninitialized memory. It also compiles down directly to a highly optimized LLVMmemsetintrinsic, ensuring there is no meaningful performance regression.Resolves #752