github.com/anchore/syft@v1.38.2/syft/file/cataloger/executable/macho.go (about)

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