Skip to content

Commit

Permalink
Fix UTF-8 unsoundness in string::merge (#194)
Browse files Browse the repository at this point in the history
* Merge string value without as_mut_vec unsoundness

In case of an encoding error in string::merge, the appended string value
is left with broken UTF-8 in case of an error. The same can happen if
any of the Buf methods panics. This results in UB if the string value is used
after the error return or in unwind, respectively.

Change the implementation to truncate the string back to valid UTF-8
content in any code path that does not go through validation of the
newly appended bytes.

* Unit test for string::merge failure

Test that adding an invalid UTF-8 sequence results in an error,
and that the string is reverted to its state prior to the merge call.

* Safe reimplementation of string::merge

As suggested by Dan Burkert:
https://github.com/danburkert/prost/pull/194#discussion_r292096009

The existing string value is dropped in case of a merge failure,
but this is better than exposing an invalid value.
  • Loading branch information
mzabaluev authored and danburkert committed Jun 11, 2019
1 parent ab6af6e commit 5a25f27
Showing 1 changed file with 14 additions and 9 deletions.
23 changes: 14 additions & 9 deletions src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! Meant to be used only from `Message` implementations.

use std::cmp::min;
use std::str;
use std::mem;
use std::u32;
use std::usize;

Expand Down Expand Up @@ -728,14 +728,10 @@ pub mod string {
where
B: Buf,
{
unsafe {
// String::as_mut_vec is unsafe because it doesn't check that the bytes
// inserted into it the resulting vec are valid UTF-8. We check
// explicitly in order to ensure this is safe.
super::bytes::merge(wire_type, value.as_mut_vec(), buf)?;
str::from_utf8(value.as_bytes())
.map_err(|_| DecodeError::new("invalid string value: data is not UTF-8 encoded"))?;
}
let mut value_bytes = mem::replace(value, String::new()).into_bytes();
bytes::merge(wire_type, &mut value_bytes, buf)?;
*value = String::from_utf8(value_bytes)
.map_err(|_| DecodeError::new("invalid string value: data is not UTF-8 encoded"))?;
Ok(())
}

Expand Down Expand Up @@ -1307,6 +1303,15 @@ mod test {
}
}

#[test]
fn string_merge_failure() {
let mut s = String::new();
let mut buf = Cursor::new(b"\x80\x80");
let r = string::merge(WireType::LengthDelimited, &mut s, &mut buf);
r.expect_err("must be an error");
assert!(s.is_empty());
}

#[test]
fn varint() {
fn check(value: u64, encoded: &[u8]) {
Expand Down

0 comments on commit 5a25f27

Please sign in to comment.