Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # 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
# for free to join this conversation on GitHub. Already have an account? # 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