Skip to content

Unsoundness in BufReader with a broken inner Read impl #120603

Description

@conradludgate

I tried this code:

(source: @davidzeng0 from the Community Discord server)
https://play.rust-lang.org/?version=stable&mode=release&edition=2021&gist=198b86fbcfb8144e6fbb39d037f95f4e

use std::io::{Read, BufReader, Result};

pub struct MalformedRead {}

impl Read for MalformedRead {
    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
        Ok(buf.len() * 20)
    }
}

fn main() {
    let mut reader = BufReader::new(MalformedRead {});
    let mut buf = [0u8; 1024];
    
    for _ in 0..8 {
        let read = reader.read(&mut buf).unwrap();
        dbg!(read);
    }

    reader.read(&mut buf).unwrap();
    dbg!(buf);
}

I expected to see this happen: panic, out of bounds read

Instead, this happened:

In debug: SEGMENTATION VIOLATION
In release: Heap buffer overflow
In miri: Undefined Behavior: out-of-bounds pointer use: alloc893 has size 8192, so pointer to 163840 bytes starting at offset 0 is out-of-bounds

Meta

Stable (1.75.0) and Nightly (1.77.0-nightly 2024-02-01 bf3c6c5) have the issue.

Issue

BufReader uses read_buf to read from the Reader, using the BorrowedBuf's len as the filled value.

reader.read_buf(buf.unfilled())?;
self.pos = 0;
self.filled = buf.len();
self.initialized = buf.init_len();

The default_read_buf function calls advance(n) with the dodgy read amount.

let n = read(cursor.ensure_init().init_mut())?;
unsafe {
// SAFETY: we initialised using `ensure_init` so there is no uninit data to advance to.
cursor.advance(n);
}

BufReader uses the filled value as the upper bound of the read buffer.

unsafe { MaybeUninit::slice_assume_init_ref(self.buf.get_unchecked(self.pos..self.filled)) }

Activity

  1. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Feb 3, 2024
  2. added
    I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
    T-libsRelevant to the library team, which will review and decide on the PR/issue.
    A-ioArea: `std::io` (note: `std::fs`, `std::net`, `std::path` are A-filesystem, A-net, A-path)
    and removed
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Feb 3, 2024
  3. added
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Feb 3, 2024
  4. conradludgate commented on Feb 3, 2024

    @conradludgate
    ContributorAuthor

    It's unclear whether

    • default_read_buf should be fixed to check that n is in bounds
    • BufReader should be fixed to check that the buf.len() is in bounds

    Reading the current safety comments, it appears that default_read_buf is in the wrong:

    Safety
    The caller must ensure that the first n bytes of the cursor have been properly initialised.

    this is violated as only 8192 bytes are intiialised.

    I would personally suggest that default_read_buf be updated to add a bounds check.

  5. conradludgate commented on Feb 3, 2024

    @conradludgate
    ContributorAuthor

    @rustbot claim

  6. added 3 commits that reference this issue on Feb 3, 2024
    a27e45a
    e502cbf
    c168f17
  7. added a commit that references this issue on Feb 5, 2024
    d1110a4
  8. added a commit that references this issue on Feb 5, 2024
    d8e9ddc
  9. added a commit that references this issue on Feb 5, 2024
    8c0b4f6
  10. added a commit that references this issue on Feb 5, 2024
    5c906ab
  11. removed
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Feb 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

A-ioArea: `std::io` (note: `std::fs`, `std::net`, `std::path` are A-filesystem, A-net, A-path)C-bugCategory: This is a bug.I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessT-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