@@ -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}
0 commit comments