Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Unsound BufWriter copy_to specialization with the unstable read_buf feature #93305

Copy link
Copy link

Description

@paolobarbolini
Issue body actions

Code

#![feature(read_buf)]

use std::io::{self, BufWriter, Read, ReadBuf};

struct BadReader;

impl Read for BadReader {
    fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
        unreachable!()
    }

    fn read_buf(&mut self, read_buf: &mut ReadBuf<'_>) -> io::Result<()> {
        let vec = vec![0; 2048];

        let mut buf = ReadBuf::new(vec.leak());
        buf.append(&[123; 2048]);
        *read_buf = buf;

        Ok(())
    }
}

fn main() {
    let mut buf = Vec::new();

    {
        let mut buf_ = BufWriter::with_capacity(1024, &mut buf);

        let mut input = BadReader.take(4096);
        io::copy(&mut input, &mut buf_).unwrap();
    }

    // read the memory to trigger miri to realize that the memory is uninitialized
    println!("buf[0]: {}", buf[0]);
}

Playground link: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=483f0b4f2f881519f459c3cd273a50f4 (run miri on it)

Unsound implementation

fn copy_to<R: Read + ?Sized>(reader: &mut R, writer: &mut Self) -> Result<u64> {
if writer.capacity() < DEFAULT_BUF_SIZE {
return stack_buffer_copy(reader, writer);
}
let mut len = 0;
let mut init = 0;
loop {
let buf = writer.buffer_mut();
let mut read_buf = ReadBuf::uninit(buf.spare_capacity_mut());
// SAFETY: init is either 0 or the initialized_len of the previous iteration
unsafe {
read_buf.assume_init(init);
}
if read_buf.capacity() >= DEFAULT_BUF_SIZE {
match reader.read_buf(&mut read_buf) {
Ok(()) => {
let bytes_read = read_buf.filled_len();
if bytes_read == 0 {
return Ok(len);
}
init = read_buf.initialized_len() - bytes_read;
// SAFETY: ReadBuf guarantees all of its filled bytes are init
unsafe { buf.set_len(buf.len() + bytes_read) };
len += bytes_read as u64;
// Read again if the buffer still has enough capacity, as BufWriter itself would do
// This will occur if the reader returns short reads
continue;
}
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
writer.flush_buf()?;
}
}

(we're basically tricking it into calling set_len with a length higher than what has actually been filled)

Documentation

Should this be considered a possible footgun when implementing something that calls read_buf? Should it be documented?

Reactions are currently unavailable

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-bugCategory: This is a bug.Category: This is a bug.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    Morty Proxy This is a proxified and sanitized view of the page, visit original site.