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  }