Skip to content

Potential Undefined Behavior Reported by Miri in BytesReader #272

Description

@yilin0518

Hi!

We are a team of researchers studying memory safety in Rust. As part of our ongoing research, we tested quick-protobuf (version: 0.8.1) and found that the following code snippet is reported as undefined behavior by Miri:

Minimal Problematic Snippet

#![feature(allocator_api)]
extern crate alloc;
use quick_protobuf::*;
fn main() {
	let v24 = [121u8, 103u8, 246u8];
	let v25 = Box::new(v24);
	let v26 = &v25[..];
	let mut v27 = reader::BytesReader::from_bytes(v26);
	let v38: &'_ mut reader::BytesReader = &mut v27;
	let v39 = [206u8, 109u8, 153u8];
	let v40 = Box::new(v39);
	let v41 = &v40[..];
	let _ = reader::BytesReader::read_string(v38, v41);
	let v48: &'_ mut reader::BytesReader = &mut v27;
	let v49 = [109u8, 180u8, 69u8];
	let v50 = Box::new(v49);
	let v51 = &v50[..];
	let _ = reader::BytesReader::read_packed_fixed::<u8>(v48, v51);
}

Miri Error Excerpt

error: Undefined Behavior: `assume` called with `false`
   --> /home/rose/projects/lifesonar-tests/quick-protobuf-0.8.1/src/reader.rs:456:17
	|
456 |                 bytes.get_unchecked(self.start) as *const u8 as *const M,
	|                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
	= note: inside `quick_protobuf::BytesReader::read_packed_fixed::<u8>` at /home/rose/projects/lifesonar-tests/quick-protobuf-0.8.1/src/reader.rs:456:17: 456:48
note: inside `main`
   --> src/main.rs:18:15

Root-Cause Hypothesis

After analyzing the Miri report and the source code, we assume the UB is rooted in:

  • Suspect location: src/reader.rs:394-402 and src/reader.rs:448-456
  • Invariant being broken: BytesReader cursor/bounds invariants are not restored when read_len returns early on error.
  • Causality chain: crafted length-delimited input -> read_len sets self.end = self.start + len -> closure errors and ? returns before rollback -> later read_packed_fixed trusts stale logical bounds -> get_unchecked/from_raw_parts executes with invalid assumptions -> UB.

Human-Readable Reproduction

use quick_protobuf::BytesReader;

fn main() {
	let original_bytes = [121u8, 103, 246];
	let mut reader = BytesReader::from_bytes(&original_bytes);

	let _ = reader.read_string(&[206u8, 109, 153]);
	let _ = reader.read_packed_fixed::<u8>(&[109u8, 180, 69]);
}

Miri Output of Human-Readable Reproduction

Command used:

MIRIFLAGS="-Zmiri-ignore-leaks -Zmiri-tree-borrows" RUSTFLAGS=-Awarnings RUST_BACKTRACE=1 cargo miri run

Result:

error: Undefined Behavior: `assume` called with `false`
   --> /home/rose/projects/lifesonar-tests/quick-protobuf-0.8.1/src/reader.rs:456:17
	|
456 |                 bytes.get_unchecked(self.start) as *const u8 as *const M,
	|                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
	= note: inside `quick_protobuf::BytesReader::read_packed_fixed::<u8>` at /home/rose/projects/lifesonar-tests/quick-protobuf-0.8.1/src/reader.rs:456:17: 456:48
note: inside `main`
   --> src/main.rs:8:13

We don't know whether this is a truly undefined behaviour or just because of our misuse, or this is a API design flaw which results in the creation of this test case.

Possible Fix

  • Make read_len restore start/end on both success and error paths (for example with a guard that always runs on scope exit).
  • Before from_raw_parts, prove self.start <= bytes.len() and self.start + len <= bytes.len() using checked arithmetic.
  • Keep unsafe operations behind a single checked helper to prevent state leakage from earlier fallible paths.

We would appreciate it if you could take a look and confirm whether this behavior indicates a real issue, or if it is a false positive / expected limitation of Miri.

Thank you very much for your time and for maintaining this great project!

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions