Skip to content

Surprising behavior of Take with misbehaved inner reader #94981

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

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

Surprising behavior of Take with misbehaved inner reader #94981

HeroicKatora 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

@HeroicKatora
Copy link
Contributor

HeroicKatora 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.

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

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