github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/file/cataloger/executable/macho.go (about)

     1  package executable
     2  
     3  import (
     4  	"debug/macho"
     5  
     6  	"github.com/anchore/syft/syft/file"
     7  	"github.com/anchore/syft/syft/internal/unionreader"
     8  )
     9  
    10  // source http://www.cilinder.be/docs/next/NeXTStep/3.3/nd/DevTools/14_MachO/MachO.htmld/index.html
    11  const (
    12  	machoNPExt uint8 = 0x10 /* N_PEXT: private external symbol bit */
    13  	machoNExt  uint8 = 0x01 /* N_EXT: external symbol bit, set for external symbols */
    14  	// > #define LC_REQ_DYLD 0x80000000
    15  	// > #define LC_MAIN (0x28|LC_REQ_DYLD) /* replacement for LC_UNIXTHREAD */
    16  	lcMain = 0x28 | 0x80000000
    17  )
    18  
    19  func findMachoFeatures(data *file.Executable, reader unionreader.UnionReader) error {
    20  	// TODO: support security features
    21  
    22  	// TODO: support multi-architecture binaries
    23  	f, err := macho.NewFile(reader)
    24  	if err != nil {
    25  		return err
    26  	}
    27  
    28  	libs, err := f.ImportedLibraries()
    29  	if err != nil {
    30  		return err
    31  	}
    32  
    33  	data.ImportedLibraries = libs
    34  	data.HasEntrypoint = machoHasEntrypoint(f)
    35  	data.HasExports = machoHasExports(f)
    36  
    37  	return nil
    38  }
    39  
    40  func machoHasEntrypoint(f *macho.File) bool {
    41  	// derived from struct entry_point_command found from which explicitly calls out LC_MAIN:
    42  	// https://opensource.apple.com/source/xnu/xnu-2050.18.24/EXTERNAL_HEADERS/mach-o/loader.h
    43  	// we need to look for both LC_MAIN and LC_UNIXTHREAD commands to determine if the file is an executable
    44  	//
    45  	// this is akin to:
    46  	//    otool -l ./path/to/bin | grep -A4 LC_MAIN
    47  	//    otool -l ./path/to/bin | grep -A4 LC_UNIXTHREAD
    48  	for _, l := range f.Loads {
    49  		data := l.Raw()
    50  		cmd := f.ByteOrder.Uint32(data)
    51  
    52  		if macho.LoadCmd(cmd) == macho.LoadCmdUnixThread || macho.LoadCmd(cmd) == lcMain {
    53  			return true
    54  		}
    55  	}
    56  	return false
    57  }
    58  
    59  func machoHasExports(f *macho.File) bool {
    60  	if f == nil || f.Symtab == nil {
    61  		return false
    62  	}
    63  	for _, sym := range f.Symtab.Syms {
    64  		// look for symbols that are:
    65  		//  - not private and are external
    66  		//  - do not have an N_TYPE value of N_UNDF (undefined symbol)
    67  		//
    68  		// here's the bit layout for the n_type field:
    69  		// 0000 0000
    70  		// ─┬─│ ─┬─│
    71  		//  │ │  │ └─ N_EXT (external symbol)
    72  		//  │ │  └─ N_TYPE (N_UNDF, N_ABS, N_SECT, N_PBUD, N_INDR)
    73  		//  │ └─ N_PEXT (private external symbol)
    74  		//  └─ N_STAB (debugging symbol)
    75  		//
    76  		isExternal := sym.Type&machoNExt == machoNExt
    77  		isPrivate := sym.Type&machoNPExt == machoNPExt
    78  		nTypeIsUndefined := sym.Type&0x0e == 0
    79  
    80  		if isExternal && !isPrivate {
    81  			if sym.Name == "_main" || sym.Name == "__mh_execute_header" {
    82  				// ...however there are some symbols that are not exported but are still important
    83  				// for debugging or as an entrypoint, so we need to explicitly check for them
    84  				continue
    85  			}
    86  			if nTypeIsUndefined {
    87  				continue
    88  			}
    89  			// we have a symbol that is not private and is external
    90  			// and is not undefined, so it is an export
    91  			return true
    92  		}
    93  	}
    94  	return false
    95  }