From a2020a2e2c6cf64066124e320ba6b6c0b2e1680d Mon Sep 17 00:00:00 2001 From: forkless Date: Tue, 2 Jun 2026 17:17:32 +0200 Subject: [PATCH] add full_metadata fuzz target, fix OOB crash in property extractors --- fuzz/Cargo.toml | 28 ++++++++++++++++++++++++++++ fuzz/fuzz_targets/full_metadata.rs | 15 +++++++++++++++ fuzz/fuzz_targets/parse_gvas.rs | 14 +++++++------- src/gvas.rs | 8 ++++---- 4 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 fuzz/Cargo.toml create mode 100644 fuzz/fuzz_targets/full_metadata.rs diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 0000000..1b061ff --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "notalterra-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" + +[dependencies.notalterra] +path = ".." + +[[bin]] +name = "parse_gvas" +path = "fuzz_targets/parse_gvas.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "full_metadata" +path = "fuzz_targets/full_metadata.rs" +test = false +doc = false +bench = false diff --git a/fuzz/fuzz_targets/full_metadata.rs b/fuzz/fuzz_targets/full_metadata.rs new file mode 100644 index 0000000..7795916 --- /dev/null +++ b/fuzz/fuzz_targets/full_metadata.rs @@ -0,0 +1,15 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + // Exercises extract_full_metadata — covers IntProperty, DoubleProperty, + // additional StrProperty (GameMode, LevelName, BuildBranch) and + // BoolProperty (bIsMultiplayerSave, bWasMultiplayerSave) code paths + // not reached by the existing parse_gvas target. + let tmp = std::env::temp_dir(); + let path = tmp.join("notalterra_fuzz_full_meta.sav"); + let _ = std::fs::write(&path, data); + let _ = notalterra::gvas::extract_full_metadata(&path); + let _ = std::fs::remove_file(&path); +}); diff --git a/fuzz/fuzz_targets/parse_gvas.rs b/fuzz/fuzz_targets/parse_gvas.rs index 8e5bee0..ce58ae6 100644 --- a/fuzz/fuzz_targets/parse_gvas.rs +++ b/fuzz/fuzz_targets/parse_gvas.rs @@ -1,7 +1,7 @@ -#[fuzz] -mod target { - #[fuzz] - fn parse_gvas(data: &[u8]) { - let _ = notalterra::gvas::extract_metadata_from_bytes(data); - } -} +#![no_main] + +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let _ = notalterra::gvas::extract_metadata_from_bytes(data); +}); diff --git a/src/gvas.rs b/src/gvas.rs index ee78196..d95e8c2 100644 --- a/src/gvas.rs +++ b/src/gvas.rs @@ -140,7 +140,7 @@ fn extract_str_property(data: &[u8], prop_name: &str) -> Result attempts += 1; continue; } - if data[found + target.len()] != 0 { + if found + target.len() >= data.len() || data[found + target.len()] != 0 { offset = found + 1; attempts += 1; continue; @@ -187,7 +187,7 @@ fn extract_bool_property(data: &[u8], prop_name: &str) -> Option { if found < 4 { offset = found + 1; attempts += 1; continue; } let name_len_field = read_u32(data, found - 4); if name_len_field != Some(target.len() + 1) { offset = found + 1; attempts += 1; continue; } - if data[found + target.len()] != 0 { offset = found + 1; attempts += 1; continue; } + if found + target.len() >= data.len() || data[found + target.len()] != 0 { offset = found + 1; attempts += 1; continue; } let after_name = found + target.len() + 1; let (next_name, next_offset) = read_fname(data, after_name); if next_name.as_deref() != Some("BoolProperty") { offset = found + 1; attempts += 1; continue; } @@ -223,7 +223,7 @@ fn extract_double_property(data: &[u8], prop_name: &str) -> Option { if found < 4 { offset = found + 1; attempts += 1; continue; } let expected: usize = target.len() + 1; if read_u32(data, found - 4) != Some(expected) { offset = found + 1; attempts += 1; continue; } - if data[found + target.len()] != 0 { offset = found + 1; attempts += 1; continue; } + if found + target.len() >= data.len() || data[found + target.len()] != 0 { offset = found + 1; attempts += 1; continue; } let (next_name, next_offset) = read_fname(data, found + target.len() + 1); if next_name.as_deref() != Some("DoubleProperty") { offset = found + 1; attempts += 1; continue; } let val_offset = next_offset + 9; @@ -243,7 +243,7 @@ fn extract_int_property(data: &[u8], prop_name: &str) -> Option { let found = match found { Some(p) => offset + p, None => return None }; if found < 4 { offset = found + 1; attempts += 1; continue; } if read_u32(data, found - 4) != Some(target.len() + 1) { offset = found + 1; attempts += 1; continue; } - if data[found + target.len()] != 0 { offset = found + 1; attempts += 1; continue; } + if found + target.len() >= data.len() || data[found + target.len()] != 0 { offset = found + 1; attempts += 1; continue; } let (next_name, next_offset) = read_fname(data, found + target.len() + 1); if next_name.as_deref() != Some("IntProperty") { offset = found + 1; attempts += 1; continue; } let val_offset = next_offset + 9;