Skip to content

Commit

Permalink
Fix fuzzer-found issue with serialising invalid UTF-8 byte strings
Browse files Browse the repository at this point in the history
  • Loading branch information
juntyr committed Aug 22, 2023
1 parent 869a540 commit 2814fed
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 20 deletions.
5 changes: 3 additions & 2 deletions src/ser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ impl<W: io::Write> Serializer<W> {
}

fn serialize_unescaped_or_raw_byte_str(&mut self, value: &[u8]) -> io::Result<()> {
if value.contains(&b'"') {
if value.contains(&b'"') || value.contains(&b'\\') {
let (_, num_consecutive_hashes) =
value.iter().fold((0, 0), |(count, max), c| match c {
b'#' => (count + 1, max.max(count + 1)),
Expand Down Expand Up @@ -681,7 +681,8 @@ impl<'a, W: io::Write> ser::Serializer for &'a mut Serializer<W> {
}

fn serialize_bytes(self, v: &[u8]) -> Result<()> {
if self.escape_strings() {
// We need to fall back to escaping if the byte string would be invalid UTF-8
if self.escape_strings() || std::str::from_utf8(v).is_err() {
self.serialize_escaped_byte_str(v)?;
} else {
self.serialize_unescaped_or_raw_byte_str(v)?;
Expand Down
72 changes: 72 additions & 0 deletions tests/438_rusty_byte_strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,75 @@ fn rusty_byte_string_roundtrip(bytes: &[u8], ron: &str, ron_raw: &str) {
let de_raw: bytes::Bytes = ron::from_str(&ser_raw).unwrap();
assert_eq!(de_raw, bytes);
}

#[test]
fn fuzzer_failures() {
assert_eq!(
ron::to_string(&bytes::Bytes::copy_from_slice(&[
123, 0, 0, 0, 0, 214, 214, 214, 214, 214
]))
.unwrap(),
r#"b"{\x00\x00\x00\x00\xd6\xd6\xd6\xd6\xd6""#
);
// Need to fall back to escaping so no invalid UTF-8 is produced
assert_eq!(
ron::ser::to_string_pretty(
&bytes::Bytes::copy_from_slice(&[123, 0, 0, 0, 0, 214, 214, 214, 214, 214]),
ron::ser::PrettyConfig::default().escape_strings(false)
)
.unwrap(),
r#"b"{\x00\x00\x00\x00\xd6\xd6\xd6\xd6\xd6""#
);

assert_eq!(
ron::to_string(&bytes::Bytes::copy_from_slice(&[123, 0, 0, 0, 0])).unwrap(),
r#"b"{\x00\x00\x00\x00""#
);
assert_eq!(
ron::ser::to_string_pretty(
&bytes::Bytes::copy_from_slice(&[123, 0, 0, 0, 0]),
ron::ser::PrettyConfig::default().escape_strings(false)
)
.unwrap(),
"b\"{\x00\x00\x00\x00\""
);
}

#[test]
fn serialize_backslash_byte_string() {
check_roundtrip('\\', r"'\\'", r"'\\'");
check_roundtrip(
bytes::Bytes::copy_from_slice(b"\\"),
r#"b"\\""#,
"br#\"\\\"#",
);
}

fn check_roundtrip<
T: PartialEq + std::fmt::Debug + serde::Serialize + serde::de::DeserializeOwned,
>(
val: T,
cmp: &str,
cmp_raw: &str,
) {
let ron = ron::to_string(&val).unwrap();
assert_eq!(ron, cmp);

let ron_escaped =
ron::ser::to_string_pretty(&val, ron::ser::PrettyConfig::default().escape_strings(true))
.unwrap();
assert_eq!(ron_escaped, cmp);

let ron_raw = ron::ser::to_string_pretty(
&val,
ron::ser::PrettyConfig::default().escape_strings(false),
)
.unwrap();
assert_eq!(ron_raw, cmp_raw);

let de = ron::from_str::<T>(&ron).unwrap();
assert_eq!(de, val);

let de_raw = ron::from_str::<T>(&ron_raw).unwrap();
assert_eq!(de_raw, val);
}
49 changes: 31 additions & 18 deletions tests/465_ser_backslash_string.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
#[test]
fn serialize_backslash_string() {
assert_eq!(ron::to_string(&'\\').unwrap(), r"'\\'");
assert_eq!(ron::to_string(&"\\").unwrap(), r#""\\""#);
assert_eq!(
ron::ser::to_string_pretty(
&"\\",
ron::ser::PrettyConfig::default().escape_strings(true)
)
.unwrap(),
r#""\\""#
);
assert_eq!(
ron::ser::to_string_pretty(
&"\\",
ron::ser::PrettyConfig::default().escape_strings(false)
)
.unwrap(),
"r#\"\\\"#"
);
check_roundtrip('\\', r"'\\'", r"'\\'");
check_roundtrip(String::from("\\"), r#""\\""#, "r#\"\\\"#");
}

fn check_roundtrip<
T: PartialEq + std::fmt::Debug + serde::Serialize + serde::de::DeserializeOwned,
>(
val: T,
cmp: &str,
cmp_raw: &str,
) {
let ron = ron::to_string(&val).unwrap();
assert_eq!(ron, cmp);

let ron_escaped =
ron::ser::to_string_pretty(&val, ron::ser::PrettyConfig::default().escape_strings(true))
.unwrap();
assert_eq!(ron_escaped, cmp);

let ron_raw = ron::ser::to_string_pretty(
&val,
ron::ser::PrettyConfig::default().escape_strings(false),
)
.unwrap();
assert_eq!(ron_raw, cmp_raw);

let de = ron::from_str::<T>(&ron).unwrap();
assert_eq!(de, val);

let de_raw = ron::from_str::<T>(&ron_raw).unwrap();
assert_eq!(de_raw, val);
}

0 comments on commit 2814fed

Please sign in to comment.