@@ -114,8 +114,8 @@ pub fn is_thin_arm64_macho_header(data: &[u8]) -> bool {
114114 header. is_little_endian ( )
115115 && header. cputype ( LE ) == macho:: CPU_TYPE_ARM64
116116 && matches ! (
117- header. cpusubtype( LE ) ,
118- macho:: CPU_SUBTYPE_ARM64_ALL | macho:: CPU_SUBTYPE_ARM64_V8
117+ header. cpusubtype( LE ) & !macho :: CPU_SUBTYPE_MASK ,
118+ macho:: CPU_SUBTYPE_ARM64_ALL | macho:: CPU_SUBTYPE_ARM64_V8 | macho :: CPU_SUBTYPE_ARM64E
119119 )
120120}
121121
@@ -215,13 +215,15 @@ impl MachoParsedFile {
215215 if !header. is_little_endian ( )
216216 || header. cputype ( LE ) != macho:: CPU_TYPE_ARM64
217217 || !matches ! (
218- header. cpusubtype( LE ) ,
219- macho:: CPU_SUBTYPE_ARM64_ALL | macho:: CPU_SUBTYPE_ARM64_V8
218+ header. cpusubtype( LE ) & !macho:: CPU_SUBTYPE_MASK ,
219+ macho:: CPU_SUBTYPE_ARM64_ALL
220+ | macho:: CPU_SUBTYPE_ARM64_V8
221+ | macho:: CPU_SUBTYPE_ARM64E
220222 )
221223 || header. filetype ( LE ) != macho:: MH_EXECUTE
222224 {
223225 return Err ( Unsupported (
224- "unexpected file type or architecture (requires arm64, not arm64e )" ,
226+ "unexpected file type or architecture (requires arm64/arm64e, executable only )" ,
225227 ) ) ;
226228 }
227229 if header. flags ( LE ) & ( macho:: MH_DYLDLINK | macho:: MH_DYLIB_IN_CACHE ) != 0 {
@@ -376,17 +378,14 @@ impl MachoParsedFile {
376378 }
377379}
378380
379- /// Return the container-relative range of the unique arm64 (not arm64e) slice.
381+ /// Return the container-relative range of the selected arm64 or arm64e slice.
380382pub fn arm64_slice_range ( data : & [ u8 ] ) -> Result < Range < usize > , MachoLoaderError > {
381383 let slice = arm64_slice ( data) ?;
382384 let offset = slice. as_ptr ( ) . addr ( ) - data. as_ptr ( ) . addr ( ) ;
383385 Ok ( offset..offset + slice. len ( ) )
384386}
385387
386- /// Select the unique arm64 (not arm64e) slice, or return thin input unchanged.
387- ///
388- /// Pass the returned slice to the parser and rewriter: their file offsets are
389- /// slice-relative. Malformed universal headers are rejected, not treated as thin.
388+ /// Select an arm64 or arm64e slice, or return thin input unchanged.
390389pub fn arm64_slice ( data : & [ u8 ] ) -> Result < & [ u8 ] , MachoLoaderError > {
391390 use MachoLoaderError :: { Invalid , Unsupported } ;
392391 const HEADER_SIZE : usize = size_of :: < macho:: FatHeader > ( ) ;
@@ -442,7 +441,10 @@ fn select_arm64<'a, A: object::read::macho::FatArch>(
442441 table_end : usize ,
443442) -> Result < & ' a [ u8 ] , MachoLoaderError > {
444443 use MachoLoaderError :: { Invalid , Unsupported } ;
445- let mut selected = None ;
444+ let mut arm64 = None ;
445+ let mut arm64e = None ;
446+ let mut ambiguous_arm64 = false ;
447+ let mut ambiguous_arm64e = false ;
446448 for arch in arches {
447449 let offset = usize:: try_from ( arch. offset ( ) . into ( ) ) . map_err ( |_| Invalid ( "fat offset" ) ) ?;
448450 let size = usize:: try_from ( arch. size ( ) . into ( ) ) . map_err ( |_| Invalid ( "fat size" ) ) ?;
@@ -453,19 +455,26 @@ fn select_arm64<'a, A: object::read::macho::FatArch>(
453455 if offset < table_end || size == 0 || !offset. is_multiple_of ( align) || end > data. len ( ) {
454456 return Err ( Invalid ( "fat slice bounds/alignment" ) ) ;
455457 }
456- if arch. cputype ( ) == macho:: CPU_TYPE_ARM64
457- && matches ! (
458- arch. cpusubtype( ) ,
459- macho:: CPU_SUBTYPE_ARM64_ALL | macho:: CPU_SUBTYPE_ARM64_V8
460- )
461- {
462- if selected. is_some ( ) {
463- return Err ( Invalid ( "ambiguous arm64 slices" ) ) ;
458+ if arch. cputype ( ) != macho:: CPU_TYPE_ARM64 {
459+ continue ;
460+ }
461+ let slice = & data[ offset..end] ;
462+ match arch. cpusubtype ( ) & !macho:: CPU_SUBTYPE_MASK {
463+ macho:: CPU_SUBTYPE_ARM64_ALL | macho:: CPU_SUBTYPE_ARM64_V8 => {
464+ ambiguous_arm64 |= arm64. replace ( slice) . is_some ( ) ;
465+ }
466+ macho:: CPU_SUBTYPE_ARM64E => {
467+ ambiguous_arm64e |= arm64e. replace ( slice) . is_some ( ) ;
464468 }
465- selected = Some ( & data [ offset..end ] ) ;
469+ _ => { }
466470 }
467471 }
468- selected. ok_or ( Unsupported ( "universal binary has no supported arm64 slice" ) )
472+ if ambiguous_arm64 || ( arm64. is_none ( ) && ambiguous_arm64e) {
473+ return Err ( Invalid ( "ambiguous arm64 slices" ) ) ;
474+ }
475+ arm64
476+ . or ( arm64e)
477+ . ok_or ( Unsupported ( "universal binary has no supported arm64 slice" ) )
469478}
470479
471480#[ cfg( test) ]
@@ -549,6 +558,69 @@ mod tests {
549558 data
550559 }
551560
561+ fn image_with_subtype ( subtype : u32 ) -> Vec < u8 > {
562+ let mut data = image ( ) ;
563+ put32 ( & mut data, offset_of ! ( Header , cpusubtype) , subtype) ;
564+ data
565+ }
566+
567+ fn fat32 ( subtypes : & [ u32 ] ) -> Vec < u8 > {
568+ use object:: { endian:: U32 , pod:: bytes_of} ;
569+
570+ let header = macho:: FatHeader {
571+ magic : U32 :: new ( BE , macho:: FAT_MAGIC ) ,
572+ nfat_arch : U32 :: new ( BE , u32:: try_from ( subtypes. len ( ) ) . unwrap ( ) ) ,
573+ } ;
574+ let mut fat = bytes_of ( & header) . to_vec ( ) ;
575+ for ( index, subtype) in subtypes. iter ( ) . copied ( ) . enumerate ( ) {
576+ let offset = ( index + 1 ) * PAGE_SIZE ;
577+ let arch = macho:: FatArch32 {
578+ cputype : U32 :: new ( BE , macho:: CPU_TYPE_ARM64 ) ,
579+ cpusubtype : U32 :: new ( BE , subtype) ,
580+ offset : U32 :: new ( BE , u32:: try_from ( offset) . unwrap ( ) ) ,
581+ size : U32 :: new ( BE , u32:: try_from ( PAGE_SIZE ) . unwrap ( ) ) ,
582+ align : U32 :: new ( BE , PAGE_SIZE . ilog2 ( ) ) ,
583+ } ;
584+ fat. extend_from_slice ( bytes_of ( & arch) ) ;
585+ }
586+ for subtype in subtypes {
587+ fat. resize ( fat. len ( ) . next_multiple_of ( PAGE_SIZE ) , 0 ) ;
588+ fat. extend_from_slice ( & image_with_subtype ( * subtype) ) ;
589+ }
590+ fat
591+ }
592+
593+ #[ test]
594+ fn mixed_arm64_fat_binaries_prefer_plain_arm64 ( ) {
595+ for subtypes in [
596+ [ macho:: CPU_SUBTYPE_ARM64E , macho:: CPU_SUBTYPE_ARM64_ALL ] ,
597+ [ macho:: CPU_SUBTYPE_ARM64_V8 , macho:: CPU_SUBTYPE_ARM64E ] ,
598+ ] {
599+ let fat = fat32 ( & subtypes) ;
600+ let plain_index = subtypes
601+ . iter ( )
602+ . position ( |subtype| * subtype != macho:: CPU_SUBTYPE_ARM64E )
603+ . unwrap ( ) ;
604+ let start = ( plain_index + 1 ) * PAGE_SIZE ;
605+ assert_eq ! ( arm64_slice_range( & fat) . unwrap( ) , start..start + PAGE_SIZE ) ;
606+ }
607+
608+ assert ! ( matches!(
609+ arm64_slice( & fat32( & [
610+ macho:: CPU_SUBTYPE_ARM64_ALL ,
611+ macho:: CPU_SUBTYPE_ARM64_V8 ,
612+ ] ) ) ,
613+ Err ( MachoLoaderError :: Invalid ( "ambiguous arm64 slices" ) )
614+ ) ) ;
615+ assert ! ( matches!(
616+ arm64_slice( & fat32( & [
617+ macho:: CPU_SUBTYPE_ARM64E ,
618+ macho:: CPU_SUBTYPE_ARM64E ,
619+ ] ) ) ,
620+ Err ( MachoLoaderError :: Invalid ( "ambiguous arm64 slices" ) )
621+ ) ) ;
622+ }
623+
552624 #[ test]
553625 fn universal_slices_are_bounded_and_architecture_specific ( ) {
554626 use object:: {
@@ -623,7 +695,7 @@ mod tests {
623695 }
624696 for ( offset, value) in [
625697 ( COUNT , u32:: MAX ) , // table multiplication / bounds
626- ( HEADER_SIZE + subtype, macho :: CPU_SUBTYPE_ARM64E ) ,
698+ ( HEADER_SIZE + subtype, u32 :: MAX ) ,
627699 ( HEADER_SIZE + offset_field, u32:: MAX ) , // slice past EOF
628700 ( HEADER_SIZE + alignment, usize:: BITS ) , // alignment shift overflow
629701 ] {
0 commit comments