Skip to content

Surprising behavior of Take with misbehaved inner reader #94981

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

Closed
197g opened this issue Mar 15, 2022 · 2 comments · Fixed by #95040
Closed

Surprising behavior of Take with misbehaved inner reader #94981

197g opened this issue Mar 15, 2022 · 2 comments · Fixed by #95040
Assignees
Labels
C-bug Category: This is a bug. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Comments

@197g
Copy link
Contributor

197g commented Mar 15, 2022

Nothing here is unsound, just surprising. I tried this code:

use std::io::Read;

struct LieLieLie(bool);

impl Read for LieLieLie {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        if core::mem::take(&mut self.0) {
            // Take my hand and let's end it all
            Ok(buf.len() + 1)
        } else {
            Ok(buf.len())
        }
    }
}

fn main() {
    let mut buffer = vec![0; 4];
    let mut reader = LieLieLie(true).take(4);
    // Primed the `Limit` by lying about the read size.
    let _ = reader.read(&mut buffer[..]);
    // Oops, limit is now u64::MAX.
    reader.read_to_end(&mut buffer);
}

I expected to see this happen: The wrapping into Take ensures that no more than the specified number are appended to the underlying vector.

Instead, this happened: read_to_end enters a very long loop, eventually fails to allocate and crashes.

Meta

rustc --version --verbose:

1.61.0-nightly (2022-03-14 285fa7ecd05dcbfdaf2f)

The standard library includes this code:

rust/library/std/src/io/mod.rs

Lines 2561 to 2562 in 83460d5

let n = self.inner.read(&mut buf[..max])?;
self.limit -= n as u64;

impl<T: Read> Read for Take<T> {
    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
// …
        let n = self.inner.read(&mut buf[..max])?;
        self.limit -= n as u64;

When the inner: T is misbehaved then n may end up larger than max, causing a wrapping subtraction. A remedy may be changing this to a saturating subtraction.

@197g 197g added the C-bug Category: This is a bug. label Mar 15, 2022
@197g
Copy link
Contributor Author

197g commented Mar 15, 2022

This is especially surprising because BufRead::consumes protects against this kind of misuse by the caller:

    fn consume(&mut self, amt: usize) {
        // Don't let callers reset the limit by passing an overlarge value
        let amt = cmp::min(amt as u64, self.limit) as usize;
        self.limit -= amt as u64;
        self.inner.consume(amt);
    }

@frank-king
Copy link
Contributor

@rustbot claim

@dtolnay dtolnay added the T-libs Relevant to the library team, which will review and decide on the PR/issue. label Mar 18, 2022
frank-king added a commit to frank-king/rust that referenced this issue May 29, 2022
@bors bors closed this as completed in 0ecbcbb Jul 25, 2022
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
C-bug Category: This is a bug. T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants