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

Heap buffer overflow in read_to_end_with_reservation() #80894

Closed
Qwaz opened this issue Jan 11, 2021 · 3 comments · Fixed by #80895
Closed

Heap buffer overflow in read_to_end_with_reservation() #80894

Qwaz opened this issue Jan 11, 2021 · 3 comments · Fixed by #80895
Labels
A-io Area: `std::io`, `std::fs`, `std::net` and `std::path` C-bug Category: This is a bug. I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness P-critical Critical priority T-libs Relevant to the library team, which will review and decide on the PR/issue. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@Qwaz
Copy link
Contributor

Qwaz commented Jan 11, 2021

fn read_to_end_with_reservation<R, F>(
r: &mut R,
buf: &mut Vec<u8>,
mut reservation_size: F,
) -> Result<usize>
where
R: Read + ?Sized,
F: FnMut(&R) -> usize,
{
let start_len = buf.len();
let mut g = Guard { len: buf.len(), buf };
let ret;
loop {
if g.len == g.buf.len() {
unsafe {
// FIXME(danielhenrymantilla): #42788
//
// - This creates a (mut) reference to a slice of
// _uninitialized_ integers, which is **undefined behavior**
//
// - Only the standard library gets to soundly "ignore" this,
// based on its privileged knowledge of unstable rustc
// internals;
g.buf.reserve(reservation_size(r));
let capacity = g.buf.capacity();
g.buf.set_len(capacity);
r.initializer().initialize(&mut g.buf[g.len..]);
}
}
match r.read(&mut g.buf[g.len..]) {
Ok(0) => {
ret = Ok(g.len - start_len);
break;
}
Ok(n) => g.len += n,
Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
Err(e) => {
ret = Err(e);
break;
}
}
}
ret
}

At line 393, the guard object's .len field is incremented by the value returned from a read implementation. If a questionable Read returns a value larger than the buffer size, it will take that value and set the length of the vector over the boundary.

This bug is reachable from Read::read_to_end() and Read::read_to_string().

Here is a playground link that demonstrates the bug. It segfaults with double free or corruption (out).

@Qwaz Qwaz added the C-bug Category: This is a bug. label Jan 11, 2021
@sfackler
Copy link
Member

This is definitely broken - nice find.

A simple fix would be to just add an assert!(self.len <= self.buf.len()); in the drop impl:

fn drop(&mut self) {
unsafe {
self.buf.set_len(self.len);
}
}

You can still get weird behavior in read_to_end_with_reservation, but it should be memory safe and it seems best to avoid a bunch of extra logic in the main loop.

@sfackler sfackler added I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. labels Jan 11, 2021
@rustbot rustbot added the I-prioritize Issue: Indicates that prioritization has been requested for this issue. label Jan 11, 2021
@sfackler
Copy link
Member

It looks like the bug was introduced in ecbb896.

@camelid camelid added T-libs Relevant to the library team, which will review and decide on the PR/issue. A-io Area: `std::io`, `std::fs`, `std::net` and `std::path` labels Jan 11, 2021
@JohnTitor
Copy link
Member

Assigning P-critical as discussed as part of the Prioritization Working Group procedure and removing I-prioritize.

@JohnTitor JohnTitor added P-critical Critical priority and removed I-prioritize Issue: Indicates that prioritization has been requested for this issue. labels Jan 12, 2021
Qwaz added a commit to Qwaz/advisory-db that referenced this issue Jan 13, 2021
m-ou-se added a commit to m-ou-se/rust that referenced this issue Jan 14, 2021
Fix handling of malicious Readers in read_to_end

A malicious `Read` impl could return overly large values from `read`, which would result in the guard's drop impl setting the buffer's length to greater than its capacity! ~~To fix this, the drop impl now uses the safe `truncate` function instead of `set_len` which ensures that this will not happen. The result of calling the function will be nonsensical, but that's fine given the contract violation of the `Read` impl.~~

~~The `Guard` type is also used by `append_to_string` which does not pass untrusted values into the length field, so I've copied the guard type into each function and only modified the one used by `read_to_end`. We could just keep a single one and modify it, but it seems a bit cleaner to keep the guard code close to the functions and related specifically to them.~~

To fix this, we now assert that the returned length is not larger than the buffer passed to the method.

For reference, this bug has been present for ~2.5 years since 1.20: rust-lang@ecbb896.

Closes rust-lang#80894.
@bors bors closed this as completed in ce48709 Jan 14, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-io Area: `std::io`, `std::fs`, `std::net` and `std::path` C-bug Category: This is a bug. I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness P-critical Critical priority T-libs Relevant to the library team, which will review and decide on the PR/issue. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants