github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/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/syft/file" 12 "github.com/anchore/syft/syft/internal/unionreader" 13 ) 14 15 func findELFFeatures(data *file.Executable, reader unionreader.UnionReader) error { 16 f, err := elf.NewFile(reader) 17 if err != nil { 18 return err 19 } 20 21 libs, err := f.ImportedLibraries() 22 if err != nil { 23 // TODO: known-unknowns 24 log.WithFields("error", err).Trace("unable to read imported libraries from elf file") 25 libs = nil 26 } 27 28 if libs == nil { 29 libs = []string{} 30 } 31 32 data.ImportedLibraries = libs 33 data.ELFSecurityFeatures = findELFSecurityFeatures(f) 34 data.HasEntrypoint = elfHasEntrypoint(f) 35 data.HasExports = elfHasExports(f) 36 37 return nil 38 } 39 40 func findELFSecurityFeatures(f *elf.File) *file.ELFSecurityFeatures { 41 return &file.ELFSecurityFeatures{ 42 SymbolTableStripped: isElfSymbolTableStripped(f), 43 StackCanary: checkElfStackCanary(f), 44 NoExecutable: checkElfNXProtection(f), 45 RelocationReadOnly: checkElfRelROProtection(f), 46 PositionIndependentExecutable: isELFPIE(f), 47 DynamicSharedObject: isELFDSO(f), 48 LlvmSafeStack: checkLLVMSafeStack(f), 49 LlvmControlFlowIntegrity: checkLLVMControlFlowIntegrity(f), 50 ClangFortifySource: checkClangFortifySource(f), 51 } 52 } 53 54 func isElfSymbolTableStripped(file *elf.File) bool { 55 return file.Section(".symtab") == nil 56 } 57 58 func checkElfStackCanary(file *elf.File) *bool { 59 return hasAnyDynamicSymbols(file, "__stack_chk_fail", "__stack_chk_guard") 60 } 61 62 func hasAnyDynamicSymbols(file *elf.File, symbolNames ...string) *bool { 63 dynSyms, err := file.DynamicSymbols() 64 if err != nil { 65 // TODO: known-unknowns 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 // TODO: known-unknowns 133 log.WithFields("error", err).Trace("unable to read DT_FLAGS from elf file") 134 return false 135 } 136 for _, val := range vals { 137 if val&uint64(flag) != 0 { 138 return true 139 } 140 } 141 return false 142 } 143 144 func hasElfDynFlag1(f *elf.File, flag elf.DynFlag1) bool { 145 vals, err := f.DynValue(elf.DT_FLAGS_1) 146 if err != nil { 147 // TODO: known-unknowns 148 log.WithFields("error", err).Trace("unable to read DT_FLAGS_1 from elf file") 149 return false 150 } 151 for _, val := range vals { 152 if val&uint64(flag) != 0 { 153 return true 154 } 155 } 156 return false 157 } 158 159 func hasElfDynTag(f *elf.File, tag elf.DynTag) bool { 160 // source https://github.com/golang/go/blob/9b4b3e5acca2dabe107fa2c3ed963097d78a4562/src/cmd/cgo/internal/testshared/shared_test.go#L280 161 162 ds := f.SectionByType(elf.SHT_DYNAMIC) 163 if ds == nil { 164 return false 165 } 166 d, err := ds.Data() 167 if err != nil { 168 return false 169 } 170 171 for len(d) > 0 { 172 var t elf.DynTag 173 switch f.Class { 174 case elf.ELFCLASS32: 175 t = elf.DynTag(f.ByteOrder.Uint32(d[0:4])) 176 d = d[8:] 177 case elf.ELFCLASS64: 178 t = elf.DynTag(f.ByteOrder.Uint64(d[0:8])) 179 d = d[16:] 180 } 181 if t == tag { 182 return true 183 } 184 } 185 return false 186 } 187 188 func isELFPIE(f *elf.File) bool { 189 // being a shared object is not sufficient to be a PIE, the explicit flag must be set also 190 return isELFDSO(f) && hasElfDynFlag1(f, elf.DF_1_PIE) 191 } 192 193 func isELFDSO(f *elf.File) bool { 194 return f.Type == elf.ET_DYN 195 } 196 197 func checkLLVMSafeStack(file *elf.File) *bool { 198 // looking for the presence of https://github.com/microsoft/compiler-rt/blob/30b3b8cb5c9a0854f2f40f187c6f6773561a35f2/lib/safestack/safestack.cc#L207 199 return hasAnyDynamicSymbols(file, "__safestack_init") 200 } 201 202 func checkLLVMControlFlowIntegrity(file *elf.File) *bool { 203 // look for any symbols that are functions and end with ".cfi" 204 dynSyms, err := file.Symbols() 205 if err != nil { 206 // TODO: known-unknowns 207 log.WithFields("error", err).Trace("unable to read symbols from elf file") 208 return nil 209 } 210 211 for _, sym := range dynSyms { 212 if isFunction(sym) && strings.HasSuffix(sym.Name, ".cfi") { 213 return boolRef(true) 214 } 215 } 216 return boolRef(false) 217 } 218 219 func isFunction(sym elf.Symbol) bool { 220 return elf.ST_TYPE(sym.Info) == elf.STT_FUNC 221 } 222 223 var fortifyPattern = regexp.MustCompile(`__\w+_chk@.+`) 224 225 func checkClangFortifySource(file *elf.File) *bool { 226 dynSyms, err := file.Symbols() 227 if err != nil { 228 // TODO: known-unknowns 229 log.WithFields("error", err).Trace("unable to read symbols from elf file") 230 return nil 231 } 232 233 for _, sym := range dynSyms { 234 if isFunction(sym) && fortifyPattern.MatchString(sym.Name) { 235 return boolRef(true) 236 } 237 } 238 return boolRef(false) 239 } 240 241 func elfHasEntrypoint(f *elf.File) bool { 242 // this is akin to 243 // readelf -h ./path/to/bin | grep "Entry point address" 244 return f.Entry > 0 245 } 246 247 func elfHasExports(f *elf.File) bool { 248 // this is akin to: 249 // nm -D --defined-only ./path/to/bin | grep ' T \| W \| B ' 250 // where: 251 // T - symbol in the text section 252 // W - weak symbol that might be overwritten 253 // B - variable located in the uninitialized data section 254 // really anything that is not marked with 'U' (undefined) is considered an export. 255 symbols, err := f.DynamicSymbols() 256 if err != nil { 257 // TODO: known-unknowns? 258 return false 259 } 260 261 for _, s := range symbols { 262 // check if the section is SHN_UNDEF, which is the "U" output type from "nm -D" meaning that the symbol 263 // is undefined, meaning it is not an export. Any entry that is not undefined is considered an export. 264 if s.Section != elf.SHN_UNDEF { 265 return true 266 } 267 } 268 269 return false 270 }