Skip to content

Surprising behavior of Take with misbehaved inner reader #94981

Closed
@197g

Description

@197g

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.

Metadata

Metadata

Assignees

Labels

C-bugCategory: This is a bug.T-libsRelevant to the library team, which will review and decide on the PR/issue.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions