diff --git a/cutile-rs/cutile-ir/src/bytecode/decoder.rs b/cutile-rs/cutile-ir/src/bytecode/decoder.rs index c9d43640f4..84f53496a9 100644 --- a/cutile-rs/cutile-ir/src/bytecode/decoder.rs +++ b/cutile-rs/cutile-ir/src/bytecode/decoder.rs @@ -178,7 +178,7 @@ impl<'a> Reader<'a> { } fn read_bytes(&mut self, n: usize) -> Result<&'a [u8]> { - if self.pos + n > self.data.len() { + if n > self.remaining() { return Err(err("unexpected end of data")); } let slice = &self.data[self.pos..self.pos + n]; @@ -191,6 +191,10 @@ impl<'a> Reader<'a> { let mut shift: u32 = 0; loop { let b = self.read_byte()?; + // The tenth byte can contribute only bit 63 and cannot continue. + if shift == 63 && b > 1 { + return Err(err("varint overflow")); + } result |= ((b & 0x7F) as u64) << shift; if b & 0x80 == 0 { break; @@ -884,6 +888,17 @@ mod tests { assert!(decode_bytecode(&data).is_err()); } + #[test] + fn varint_u64_boundaries() { + for value in [0, 127, 128, 1 << 63, u64::MAX] { + let mut writer = EncodingWriter::new(); + writer.write_varint(value); + let mut reader = Reader::new(writer.as_bytes()); + assert_eq!(reader.read_varint().unwrap(), value); + assert_eq!(reader.remaining(), 0); + } + } + #[test] fn roundtrip_string_section() { // Build a bytecode with just a string section. diff --git a/cutile-rs/cutile-ir/tests/decode_rejection.rs b/cutile-rs/cutile-ir/tests/decode_rejection.rs index fe54c2bd25..580c1d9690 100644 --- a/cutile-rs/cutile-ir/tests/decode_rejection.rs +++ b/cutile-rs/cutile-ir/tests/decode_rejection.rs @@ -10,6 +10,7 @@ //! on valid minimal modules. use cutile_ir::builder::{append_op, build_single_block_region, OpBuilder}; +use cutile_ir::bytecode::encoding::EncodingWriter; use cutile_ir::bytecode::{BytecodeVersion, Opcode, Section, MAGIC}; use cutile_ir::ir::*; use cutile_ir::{decode_bytecode, write_bytecode}; @@ -124,6 +125,44 @@ fn reject_invalid_section_marker() { ); } +#[test] +fn reject_section_length_overflow() { + let mut buf = minimal_bytecode(); + buf.pop(); + buf.push(Section::String as u8); + let mut length = EncodingWriter::new(); + length.write_varint(usize::MAX as u64); + buf.extend_from_slice(length.as_bytes()); + buf.push(Section::EndOfBytecode as u8); + + assert!( + decode_bytecode(&buf).is_err(), + "an oversized section length should return an error" + ); +} + +#[test] +fn reject_section_length_varint_overflow() { + // The tenth byte has only one payload bit available in a u64 varint. + // Without an overflow check, each of these lengths wraps to 1 and the + // empty string table below appears to be a valid section. + for last_byte in [0x02, 0x04, 0x7e] { + let mut buf = minimal_bytecode(); + buf.pop(); + buf.push(Section::String as u8); + buf.push(0x81); + buf.extend_from_slice(&[0x80; 8]); + buf.push(last_byte); + buf.push(0); // Empty string table. + buf.push(Section::EndOfBytecode as u8); + + assert!( + decode_bytecode(&buf).is_err(), + "an overflowing varint ending in {last_byte:#04x} should be rejected" + ); + } +} + #[test] fn reject_truncated_type_section() { // Build a valid module so we get correct bytecode, then find and truncate