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 }