From a57a051ba006cfa3b41a0532f484df759e008d47 Mon Sep 17 00:00:00 2001 From: Patrick Roy Date: Mon, 13 Mar 2023 11:01:19 +0000 Subject: [PATCH] Add missing bounds check to FamStructWrapper::deserialize MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An issue was discovered in the `Versionize::deserialize` implementation provided by the `versionize` crate for `vmm_sys_utils::fam::FamStructWrapper`, which can lead to out of bounds memory accesses. Objects of this type are used to model structures containing C-style flexible array members [1]. These structures contain a memory allocation that is prefixed by a header containing the size of the allocation. Due to treating the header and the memory allocation as two objects, `Versionize`'s data format stores the size of the allocation twice: once in the header and then again as its own metadata of the memory allocation. A serialized `FamStructWrapper` thus looks as follows: +------------------------------------------------------------+\ | header (containing length of flexible array member `len1`) |\ +------------------------------------------------------------+\ +---------------------------------------+-----------------------+ | length of flexible array member`len2` | array member contents | +---------------------------------------+-----------------------+ During deserialization, the library separately deserializes the header and the memory allocation. It allocates `len2` bytes of memory, and then prefixes it with the separately deserialized header. Since `len2` is an implementation detail of the `Versionize` implementation, it is forgotten about at the end of the deserialize `function`, and all subsequent operations on the `FamStructWrapper` assume the memory allocated to have size `len1`. If deserialization input was malformed such that `len1 != len2`, then this can lead to (safe) functions on ´FamStructWrapper` to read past the end of allocated memory (if `len1 > len2`). The issue was corrected by inserting a check that verifies that these two lengths are equal, and aborting deserialization otherwise. [1]: https://en.wikipedia.org/wiki/Flexible_array_member Signed-off-by: Patrick Roy --- src/primitives.rs | 12 ++++++++++++ tests/test.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/primitives.rs b/src/primitives.rs index a6ea4bf..f388b0d 100644 --- a/src/primitives.rs +++ b/src/primitives.rs @@ -369,6 +369,18 @@ where let entries: Vec<::Entry> = Vec::deserialize(reader, version_map, app_version) .map_err(|ref err| VersionizeError::Deserialize(format!("{:?}", err)))?; + + if header.len() != entries.len() { + let msg = format!( + "Mismatch between length of FAM specified in FamStruct header ({}) \ + and actual size of FAM ({})", + header.len(), + entries.len() + ); + + return Err(VersionizeError::Deserialize(msg)); + } + // Construct the object from the array items. // Header(T) fields will be initialized by Default trait impl. let mut object = FamStructWrapper::from_entries(&entries) diff --git a/tests/test.rs b/tests/test.rs index 20b48cd..75e3cd1 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1323,6 +1323,32 @@ impl Versionize for __IncompleteArrayField { type MessageFamStructWrapper = FamStructWrapper; type Message2FamStructWrapper = FamStructWrapper; +#[test] +fn test_deserialize_famstructwrapper_invalid_len() { + let mut vm = VersionMap::new(); + vm.new_version() + .set_type_version(Message::type_id(), 2) + .new_version() + .set_type_version(Message::type_id(), 3) + .new_version() + .set_type_version(Message::type_id(), 4); + + // Create FamStructWrapper with len 2 + let state = MessageFamStructWrapper::new(0).unwrap(); + let mut buffer = [0; 256]; + + state.serialize(&mut buffer.as_mut_slice(), &vm, 2).unwrap(); + + // the `len` field of the header is the first serialized field. + // Let's corrupt it by making it bigger than the actual number of serialized elements + buffer[0] = 255; + + assert_eq!( + MessageFamStructWrapper::deserialize(&mut buffer.as_slice(), &vm, 2).unwrap_err(), + VersionizeError::Deserialize("Mismatch between length of FAM specified in FamStruct header (255) and actual size of FAM (0)".to_string()) + ); +} + #[test] fn test_versionize_famstructwrapper() { let mut vm = VersionMap::new();