Skip to content

Commit c5dc0ec

Browse files
authored
pe: handle unpadded resource values in .NET assemblies compiled with Mono, fix #500 (#501)
* fix: Handle unpadded resource values in .NET assemblies compiled with Mono * added testcase for mono unpadded resource stringsimproved calculation of correct offset to deal with over/underflows
1 parent 6a19a59 commit c5dc0ec

File tree

2 files changed

+87
-9
lines changed

2 files changed

+87
-9
lines changed

src/pe/resource.rs

Lines changed: 87 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -792,15 +792,36 @@ impl<'a> ResourceString<'a> {
792792
let key = bytes.gread_with::<Utf16String>(offset, scroll::LE)?;
793793
*offset = utils::align_up(*offset, RESOURCE_STRING_FIELD_ALIGNMENT);
794794

795-
let real_value_len = utils::align_up(
796-
if r#type == 1 {
797-
value_len as usize * SIZE_OF_WCHAR
798-
} else {
799-
value_len as usize
800-
},
801-
4,
802-
);
803-
let value = bytes.pread_with::<&[u8]>(*offset, real_value_len as usize)?;
795+
let unaligned_value_len = if r#type == 1 {
796+
value_len.checked_mul(SIZE_OF_WCHAR as u16).ok_or_else(|| {
797+
error::Error::Malformed(format!(
798+
"ResourceString value_len overflow: {} * {}",
799+
value_len, SIZE_OF_WCHAR
800+
))
801+
})? as usize
802+
} else {
803+
value_len as usize
804+
};
805+
806+
let bytes_remaining = bytes.len().saturating_sub(*offset);
807+
if unaligned_value_len > bytes_remaining {
808+
return Err(error::Error::Malformed(format!(
809+
"ResourceString value_len ({}) exceeds available bytes ({})",
810+
unaligned_value_len, bytes_remaining
811+
))
812+
.into());
813+
}
814+
815+
// Only align if there are bytes remaining after the value
816+
// Some resource compilers (like Mono) don't pad the last value to 4-byte alignment
817+
let aligned_value_len = utils::align_up(unaligned_value_len, 4);
818+
let actual_read_len = if aligned_value_len <= bytes_remaining {
819+
aligned_value_len
820+
} else {
821+
unaligned_value_len
822+
};
823+
824+
let value = bytes.pread_with::<&[u8]>(*offset, actual_read_len)?;
804825
*offset += value.len();
805826

806827
Ok(Some(Self {
@@ -1937,4 +1958,61 @@ mod tests {
19371958
fn malformed_resource_tree() {
19381959
let _ = crate::pe::PE::parse(MALFORMED_RESOURCE_TREE).unwrap();
19391960
}
1961+
1962+
#[test]
1963+
fn test_parse_dotnet_dll_resources() {
1964+
// Test parsing .NET DLL System.Xml.XDocument.dll compiled with Mono 4.8
1965+
// This DLL has resource structures without padding on the last value, which
1966+
// previously caused out-of-bounds read attempts
1967+
const MONO_DOTNET_DLL: &[u8] = include_bytes!(concat!(
1968+
env!("CARGO_MANIFEST_DIR"),
1969+
"/tests/bins/pe/System.Xml.XDocument.dll"
1970+
));
1971+
1972+
let pe =
1973+
crate::pe::PE::parse(MONO_DOTNET_DLL).expect("Failed to parse Mono-compiled .NET DLL");
1974+
1975+
let res_data = pe
1976+
.resource_data
1977+
.as_ref()
1978+
.expect("Resource data should be present");
1979+
1980+
let ver_info = res_data
1981+
.version_info
1982+
.as_ref()
1983+
.expect("Version info should be present");
1984+
1985+
let fixed = ver_info
1986+
.fixed_info
1987+
.as_ref()
1988+
.expect("Fixed info should be present");
1989+
1990+
let file_ver = fixed.file_version();
1991+
assert_eq!(file_ver.major, 0, "File version major");
1992+
assert_eq!(file_ver.minor, 0, "File version minor");
1993+
assert_eq!(file_ver.build, 0, "File version build");
1994+
assert_eq!(file_ver.revision, 0, "File version revision");
1995+
1996+
let product_ver = fixed.product_version();
1997+
assert_eq!(product_ver.major, 4, "Product version major (Mono 4.x)");
1998+
assert_eq!(product_ver.minor, 8, "Product version minor");
1999+
assert_eq!(product_ver.build, 3761, "Product version build");
2000+
assert_eq!(product_ver.revision, 0, "Product version revision");
2001+
2002+
assert_eq!(
2003+
ver_info.string_info.legal_copyright(),
2004+
Some("(c) Various Mono authors".to_string()),
2005+
"Copyright should match Mono authors"
2006+
);
2007+
assert_eq!(
2008+
ver_info.string_info.product_name(),
2009+
Some("Mono Common Language Infrastructure".to_string()),
2010+
"Product name should match Mono CLI"
2011+
);
2012+
assert_eq!(
2013+
ver_info.string_info.file_description(),
2014+
Some("System.Xml.XDocument".to_string()),
2015+
"File description should match assembly name"
2016+
);
2017+
}
19402018
}
5.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)