mirror of
https://github.com/anchore/syft.git
synced 2026-11-02 23:46:35 +01:00
Detect ELF security features (#2443)
* add detection of ELF security features Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix linting Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * update json schema with file executable data Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * update expected fixure when no tty present Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * more detailed differ Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * use json differ Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix tests Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * remove json schema addition Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * regenerate json schema Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix mimtype set ref Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
package executable
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
)
|
||||
|
||||
func findELFSecurityFeatures(reader unionreader.UnionReader) (*file.ELFSecurityFeatures, error) {
|
||||
f, err := elf.NewFile(reader)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
features := file.ELFSecurityFeatures{
|
||||
SymbolTableStripped: isElfSymbolTableStripped(f),
|
||||
StackCanary: checkElfStackCanary(f),
|
||||
NoExecutable: checkElfNXProtection(f),
|
||||
RelocationReadOnly: checkElfRelROProtection(f),
|
||||
PositionIndependentExecutable: isELFPIE(f),
|
||||
DynamicSharedObject: isELFDSO(f),
|
||||
LlvmSafeStack: checkLLVMSafeStack(f),
|
||||
LlvmControlFlowIntegrity: checkLLVMControlFlowIntegrity(f),
|
||||
ClangFortifySource: checkClangFortifySource(f),
|
||||
}
|
||||
|
||||
return &features, nil
|
||||
}
|
||||
|
||||
func isElfSymbolTableStripped(file *elf.File) bool {
|
||||
return file.Section(".symtab") == nil
|
||||
}
|
||||
|
||||
func checkElfStackCanary(file *elf.File) *bool {
|
||||
return hasAnyDynamicSymbols(file, "__stack_chk_fail", "__stack_chk_guard")
|
||||
}
|
||||
|
||||
func hasAnyDynamicSymbols(file *elf.File, symbolNames ...string) *bool {
|
||||
dynSyms, err := file.DynamicSymbols()
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Warn("unable to read dynamic symbols from elf file")
|
||||
return nil
|
||||
}
|
||||
|
||||
nameSet := strset.New(symbolNames...)
|
||||
|
||||
for _, sym := range dynSyms {
|
||||
if nameSet.Has(sym.Name) {
|
||||
return boolRef(true)
|
||||
}
|
||||
}
|
||||
return boolRef(false)
|
||||
}
|
||||
|
||||
func boolRef(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
func checkElfNXProtection(file *elf.File) bool {
|
||||
// find the program headers until you find the GNU_STACK segment
|
||||
for _, prog := range file.Progs {
|
||||
if prog.Type == elf.PT_GNU_STACK {
|
||||
// check if the GNU_STACK segment is executable
|
||||
return prog.Flags&elf.PF_X == 0
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkElfRelROProtection(f *elf.File) file.RelocationReadOnly {
|
||||
// background on relro https://www.redhat.com/en/blog/hardening-elf-binaries-using-relocation-read-only-relro
|
||||
hasRelro := false
|
||||
hasBindNow := hasBindNowDynTagOrFlag(f)
|
||||
|
||||
for _, prog := range f.Progs {
|
||||
if prog.Type == elf.PT_GNU_RELRO {
|
||||
hasRelro = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case hasRelro && hasBindNow:
|
||||
return file.RelocationReadOnlyFull
|
||||
case hasRelro:
|
||||
return file.RelocationReadOnlyPartial
|
||||
default:
|
||||
return file.RelocationReadOnlyNone
|
||||
}
|
||||
}
|
||||
|
||||
func hasBindNowDynTagOrFlag(f *elf.File) bool {
|
||||
if hasElfDynTag(f, elf.DT_BIND_NOW) {
|
||||
// support older binaries...
|
||||
return true
|
||||
}
|
||||
|
||||
// "DT_BIND_NOW ... use has been superseded by the DF_BIND_NOW flag"
|
||||
// source: https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html
|
||||
return hasElfDynFlag(f, elf.DF_BIND_NOW)
|
||||
}
|
||||
|
||||
func hasElfDynFlag(f *elf.File, flag elf.DynFlag) bool {
|
||||
vals, err := f.DynValue(elf.DT_FLAGS)
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Warn("unable to read DT_FLAGS from elf file")
|
||||
return false
|
||||
}
|
||||
for _, val := range vals {
|
||||
if val&uint64(flag) != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasElfDynFlag1(f *elf.File, flag elf.DynFlag1) bool {
|
||||
vals, err := f.DynValue(elf.DT_FLAGS_1)
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Warn("unable to read DT_FLAGS_1 from elf file")
|
||||
return false
|
||||
}
|
||||
for _, val := range vals {
|
||||
if val&uint64(flag) != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasElfDynTag(f *elf.File, tag elf.DynTag) bool {
|
||||
// source https://github.com/golang/go/blob/9b4b3e5acca2dabe107fa2c3ed963097d78a4562/src/cmd/cgo/internal/testshared/shared_test.go#L280
|
||||
|
||||
ds := f.SectionByType(elf.SHT_DYNAMIC)
|
||||
if ds == nil {
|
||||
return false
|
||||
}
|
||||
d, err := ds.Data()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for len(d) > 0 {
|
||||
var t elf.DynTag
|
||||
switch f.Class {
|
||||
case elf.ELFCLASS32:
|
||||
t = elf.DynTag(f.ByteOrder.Uint32(d[0:4]))
|
||||
d = d[8:]
|
||||
case elf.ELFCLASS64:
|
||||
t = elf.DynTag(f.ByteOrder.Uint64(d[0:8]))
|
||||
d = d[16:]
|
||||
}
|
||||
if t == tag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isELFPIE(f *elf.File) bool {
|
||||
// being a shared object is not sufficient to be a PIE, the explicit flag must be set also
|
||||
return isELFDSO(f) && hasElfDynFlag1(f, elf.DF_1_PIE)
|
||||
}
|
||||
|
||||
func isELFDSO(f *elf.File) bool {
|
||||
return f.Type == elf.ET_DYN
|
||||
}
|
||||
|
||||
func checkLLVMSafeStack(file *elf.File) *bool {
|
||||
// looking for the presence of https://github.com/microsoft/compiler-rt/blob/30b3b8cb5c9a0854f2f40f187c6f6773561a35f2/lib/safestack/safestack.cc#L207
|
||||
return hasAnyDynamicSymbols(file, "__safestack_init")
|
||||
}
|
||||
|
||||
func checkLLVMControlFlowIntegrity(file *elf.File) *bool {
|
||||
// look for any symbols that are functions and end with ".cfi"
|
||||
dynSyms, err := file.Symbols()
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Trace("unable to read symbols from elf file")
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, sym := range dynSyms {
|
||||
if isFunction(sym) && strings.HasSuffix(sym.Name, ".cfi") {
|
||||
return boolRef(true)
|
||||
}
|
||||
}
|
||||
return boolRef(false)
|
||||
}
|
||||
|
||||
func isFunction(sym elf.Symbol) bool {
|
||||
return elf.ST_TYPE(sym.Info) == elf.STT_FUNC
|
||||
}
|
||||
|
||||
var fortifyPattern = regexp.MustCompile(`__\w+_chk@.+`)
|
||||
|
||||
func checkClangFortifySource(file *elf.File) *bool {
|
||||
dynSyms, err := file.Symbols()
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Trace("unable to read symbols from elf file")
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, sym := range dynSyms {
|
||||
if isFunction(sym) && fortifyPattern.MatchString(sym.Name) {
|
||||
return boolRef(true)
|
||||
}
|
||||
}
|
||||
return boolRef(false)
|
||||
}
|
||||
Reference in New Issue
Block a user