github.com/anchore/syft@v1.38.2/syft/file/cataloger/executable/elf.go (about) 1 package executable 2 3 import ( 4 "debug/elf" 5 "regexp" 6 "strings" 7 8 "github.com/scylladb/go-set/strset" 9 10 "github.com/anchore/syft/internal/log" 11 "github.com/anchore/syft/internal/unknown" 12 "github.com/anchore/syft/syft/file" 13 "github.com/anchore/syft/syft/internal/unionreader" 14 ) 15 16 func findELFFeatures(data *file.Executable, reader unionreader.UnionReader) error { 17 f, err := elf.NewFile(reader) 18 if err != nil { 19 return err 20 } 21 22 libs, err := f.ImportedLibraries() 23 if err != nil { 24 log.WithFields("error", err).Trace("unable to read imported libraries from elf file") 25 err = unknown.Joinf(err, "unable to read imported libraries from elf file: %w", err) 26 libs = nil 27 } 28 29 if libs == nil { 30 libs = []string{} 31 } 32 33 data.ImportedLibraries = libs 34 data.ELFSecurityFeatures = findELFSecurityFeatures(f) 35 data.HasEntrypoint = elfHasEntrypoint(f) 36 data.HasExports = elfHasExports(f) 37 38 return err 39 } 40 41 func findELFSecurityFeatures(f *elf.File) *file.ELFSecurityFeatures { 42 return &file.ELFSecurityFeatures{ 43 SymbolTableStripped: isElfSymbolTableStripped(f), 44 StackCanary: checkElfStackCanary(f), 45 NoExecutable: checkElfNXProtection(f), 46 RelocationReadOnly: checkElfRelROProtection(f), 47 PositionIndependentExecutable: isELFPIE(f), 48 DynamicSharedObject: isELFDSO(f), 49 LlvmSafeStack: checkLLVMSafeStack(f), 50 LlvmControlFlowIntegrity: checkLLVMControlFlowIntegrity(f), 51 ClangFortifySource: checkClangFortifySource(f), 52 } 53 } 54 55 func isElfSymbolTableStripped(file *elf.File) bool { 56 return file.Section(".symtab") == nil 57 } 58 59 func checkElfStackCanary(file *elf.File) *bool { 60 return hasAnyDynamicSymbols(file, "__stack_chk_fail", "__stack_chk_guard") 61 } 62 63 func hasAnyDynamicSymbols(file *elf.File, symbolNames ...string) *bool { 64 dynSyms, err := file.DynamicSymbols() 65 if err != nil { 66 log.WithFields("error", err).Trace("unable to read dynamic symbols from elf file") 67 return nil 68 } 69 70 nameSet := strset.New(symbolNames...) 71 72 for _, sym := range dynSyms { 73 if nameSet.Has(sym.Name) { 74 return boolRef(true) 75 } 76 } 77 return boolRef(false) 78 } 79 80 func boolRef(b bool) *bool { 81 return &b 82 } 83 84 func checkElfNXProtection(file *elf.File) bool { 85 // find the program headers until you find the GNU_STACK segment 86 for _, prog := range file.Progs { 87 if prog.Type == elf.PT_GNU_STACK { 88 // check if the GNU_STACK segment is executable 89 return prog.Flags&elf.PF_X == 0 90 } 91 } 92 93 return false 94 } 95 96 func checkElfRelROProtection(f *elf.File) file.RelocationReadOnly { 97 // background on relro https://www.redhat.com/en/blog/hardening-elf-binaries-using-relocation-read-only-relro 98 hasRelro := false 99 hasBindNow := hasBindNowDynTagOrFlag(f) 100 101 for _, prog := range f.Progs { 102 if prog.Type == elf.PT_GNU_RELRO { 103 hasRelro = true 104 break 105 } 106 } 107 108 switch { 109 case hasRelro && hasBindNow: 110 return file.RelocationReadOnlyFull 111 case hasRelro: 112 return file.RelocationReadOnlyPartial 113 default: 114 return file.RelocationReadOnlyNone 115 } 116 } 117 118 func hasBindNowDynTagOrFlag(f *elf.File) bool { 119 if hasElfDynTag(f, elf.DT_BIND_NOW) { 120 // support older binaries... 121 return true 122 } 123 124 // "DT_BIND_NOW ... use has been superseded by the DF_BIND_NOW flag" 125 // source: https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html 126 return hasElfDynFlag(f, elf.DF_BIND_NOW) 127 } 128 129 func hasElfDynFlag(f *elf.File, flag elf.DynFlag) bool { 130 vals, err := f.DynValue(elf.DT_FLAGS) 131 if err != nil { 132 log.WithFields("error", err).Trace("unable to read DT_FLAGS from elf file") 133 return false 134 } 135 for _, val := range vals { 136 if val&uint64(flag) != 0 { 137 return true 138 } 139 } 140 return false 141 } 142 143 func hasElfDynFlag1(f *elf.File, flag elf.DynFlag1) bool { 144 vals, err := f.DynValue(elf.DT_FLAGS_1) 145 if err != nil { 146 log.WithFields("error", err).Trace("unable to read DT_FLAGS_1 from elf file") 147 return false 148 } 149 for _, val := range vals { 150 if val&uint64(flag) != 0 { 151 return true 152 } 153 } 154 return false 155 } 156 157 func hasElfDynTag(f *elf.File, tag elf.DynTag) bool { 158 // source https://github.com/golang/go/blob/9b4b3e5acca2dabe107fa2c3ed963097d78a4562/src/cmd/cgo/internal/testshared/shared_test.go#L280 159 160 ds := f.SectionByType(elf.SHT_DYNAMIC) 161 if ds == nil { 162 return false 163 } 164 d, err := ds.Data() 165 if err != nil { 166 return false 167 } 168 169 for len(d) > 0 { 170 var t elf.DynTag 171 switch f.Class { 172 case elf.ELFCLASS32: 173 t = elf.DynTag(f.ByteOrder.Uint32(d[0:4])) 174 d = d[8:] 175 case elf.ELFCLASS64: 176 t = elf.DynTag(f.ByteOrder.Uint64(d[0:8])) 177 d = d[16:] 178 } 179 if t == tag { 180 return true 181 } 182 } 183 return false 184 } 185 186 func isELFPIE(f *elf.File) bool { 187 // being a shared object is not sufficient to be a PIE, the explicit flag must be set also 188 return isELFDSO(f) && hasElfDynFlag1(f, elf.DF_1_PIE) 189 } 190 191 func isELFDSO(f *elf.File) bool { 192 return f.Type == elf.ET_DYN 193 } 194 195 func checkLLVMSafeStack(file *elf.File) *bool { 196 // looking for the presence of https://github.com/microsoft/compiler-rt/blob/30b3b8cb5c9a0854f2f40f187c6f6773561a35f2/lib/safestack/safestack.cc#L207 197 return hasAnyDynamicSymbols(file, "__safestack_init") 198 } 199 200 func checkLLVMControlFlowIntegrity(file *elf.File) *bool { 201 // look for any symbols that are functions and end with ".cfi" 202 dynSyms, err := file.Symbols() 203 if err != nil { 204 log.WithFields("error", err).Trace("unable to read symbols from elf file") 205 return nil 206 } 207 208 for _, sym := range dynSyms { 209 if isFunction(sym) && strings.HasSuffix(sym.Name, ".cfi") { 210 return boolRef(true) 211 } 212 } 213 return boolRef(false) 214 } 215 216 func isFunction(sym elf.Symbol) bool { 217 return elf.ST_TYPE(sym.Info) == elf.STT_FUNC 218 } 219 220 var fortifyPattern = regexp.MustCompile(`__\w+_chk@.+`) 221 222 func checkClangFortifySource(file *elf.File) *bool { 223 dynSyms, err := file.Symbols() 224 if err != nil { 225 log.WithFields("error", err).Trace("unable to read symbols from elf file") 226 return nil 227 } 228 229 for _, sym := range dynSyms { 230 if isFunction(sym) && fortifyPattern.MatchString(sym.Name) { 231 return boolRef(true) 232 } 233 } 234 return boolRef(false) 235 } 236 237 func elfHasEntrypoint(f *elf.File) bool { 238 // this is akin to 239 // readelf -h ./path/to/bin | grep "Entry point address" 240 return f.Entry > 0 241 } 242 243 func elfHasExports(f *elf.File) bool { 244 // this is akin to: 245 // nm -D --defined-only ./path/to/bin | grep ' T \| W \| B ' 246 // where: 247 // T - symbol in the text section 248 // W - weak symbol that might be overwritten 249 // B - variable located in the uninitialized data section 250 // really anything that is not marked with 'U' (undefined) is considered an export. 251 symbols, err := f.DynamicSymbols() 252 if err != nil { 253 log.WithFields("error", err).Trace("unable to get ELF dynamic symbols") 254 return false 255 } 256 257 for _, s := range symbols { 258 // check if the section is SHN_UNDEF, which is the "U" output type from "nm -D" meaning that the symbol 259 // is undefined, meaning it is not an export. Any entry that is not undefined is considered an export. 260 if s.Section != elf.SHN_UNDEF { 261 return true 262 } 263 } 264 265 return false 266 }