From b0adcb9c989eb70bf8ab1505e5ffd063bb3ab9a4 Mon Sep 17 00:00:00 2001 From: Bryce Kahle Date: Tue, 12 Nov 2024 15:28:51 -0800 Subject: [PATCH] Prevents panics during ELF parsing from crashing process (#30725) --- .github/CODEOWNERS | 1 + .golangci.yml | 2 + .../corechecks/servicediscovery/apm/detect.go | 4 +- .../diconfig/binary_inspection.go | 7 +- .../diconfig/binary_inspection_test.go | 5 +- .../diconfig/config_manager.go | 6 +- pkg/dynamicinstrumentation/diconfig/dwarf.go | 8 +- .../proctracker/proctracker.go | 11 +- pkg/ebpf/uprobes/inspector.go | 10 +- pkg/ebpf/verifier/elf.go | 31 +--- pkg/gpu/cuda/cubin.go | 15 +- pkg/gpu/cuda/fatbin.go | 7 +- pkg/gpu/cuda/symbols.go | 5 +- .../internal/detectors/go_detector.go | 6 +- pkg/network/go/asmscan/scan.go | 10 +- pkg/network/go/bininspect/dwarf.go | 14 +- pkg/network/go/bininspect/newproc.go | 10 +- pkg/network/go/bininspect/pclntab.go | 21 ++- pkg/network/go/bininspect/pclntab_test.go | 16 ++- pkg/network/go/bininspect/symbol_filter.go | 8 +- pkg/network/go/bininspect/symbols.go | 52 ++++--- pkg/network/go/bininspect/symbols_test.go | 6 +- pkg/network/go/bininspect/types.go | 7 +- pkg/network/go/bininspect/utils.go | 25 ++-- pkg/network/go/binversion/buildinfo.go | 8 +- .../go/goid/internal/generate_goid_lut.go | 4 +- .../gotls/lookup/internal/generate_luts.go | 4 +- pkg/network/protocols/tls/nodejs/nodejs.go | 2 + pkg/network/usm/ebpf_gotls_helpers.go | 6 +- pkg/network/usm/ebpf_ssl.go | 6 +- .../probe/constantfetch/runtime_compiled.go | 8 +- pkg/security/ptracer/utils.go | 4 +- pkg/util/safeelf/elf.go | 136 ++++++++++++++++++ pkg/util/safeelf/types.go | 44 ++++++ .../test-json-review/testowners.go | 2 +- 35 files changed, 354 insertions(+), 157 deletions(-) create mode 100644 pkg/util/safeelf/elf.go create mode 100644 pkg/util/safeelf/types.go diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7b2acde0f3863..b091f3938b790 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -444,6 +444,7 @@ /pkg/util/ecs/ @DataDog/container-integrations /pkg/util/funcs/ @DataDog/ebpf-platform /pkg/util/kernel/ @DataDog/ebpf-platform +/pkg/util/safeelf/ @DataDog/ebpf-platform /pkg/util/ktime @DataDog/agent-security /pkg/util/kubernetes/ @DataDog/container-integrations @DataDog/container-platform @DataDog/container-app /pkg/util/podman/ @DataDog/container-integrations diff --git a/.golangci.yml b/.golangci.yml index 5e6c781919a56..0358a505b0118 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -605,6 +605,8 @@ linters-settings: desc: "Not really forbidden to use, but it is usually imported by mistake instead of github.com/stretchr/testify/assert" - pkg: "github.com/tj/assert" desc: "Not really forbidden to use, but it is usually imported by mistake instead of github.com/stretchr/testify/assert, and confusing since it actually has the behavior of github.com/stretchr/testify/require" + - pkg: "debug/elf" + desc: "prefer pkg/util/safeelf to prevent panics during parsing" errcheck: exclude-functions: diff --git a/pkg/collector/corechecks/servicediscovery/apm/detect.go b/pkg/collector/corechecks/servicediscovery/apm/detect.go index f90a16448e48c..f342f955e2274 100644 --- a/pkg/collector/corechecks/servicediscovery/apm/detect.go +++ b/pkg/collector/corechecks/servicediscovery/apm/detect.go @@ -10,7 +10,6 @@ package apm import ( "bufio" - "debug/elf" "io" "io/fs" "os" @@ -24,6 +23,7 @@ import ( "github.com/DataDog/datadog-agent/pkg/network/go/bininspect" "github.com/DataDog/datadog-agent/pkg/util/kernel" "github.com/DataDog/datadog-agent/pkg/util/log" + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) // Instrumentation represents the state of APM instrumentation for a service. @@ -100,7 +100,7 @@ const ( func goDetector(ctx usm.DetectionContext) Instrumentation { exePath := kernel.HostProc(strconv.Itoa(ctx.Pid), "exe") - elfFile, err := elf.Open(exePath) + elfFile, err := safeelf.Open(exePath) if err != nil { log.Debugf("Unable to open exe %s: %v", exePath, err) return None diff --git a/pkg/dynamicinstrumentation/diconfig/binary_inspection.go b/pkg/dynamicinstrumentation/diconfig/binary_inspection.go index c712a1e3dbe56..02885a2b6772a 100644 --- a/pkg/dynamicinstrumentation/diconfig/binary_inspection.go +++ b/pkg/dynamicinstrumentation/diconfig/binary_inspection.go @@ -8,14 +8,13 @@ package diconfig import ( - "debug/elf" "fmt" "reflect" - "github.com/DataDog/datadog-agent/pkg/util/log" - "github.com/DataDog/datadog-agent/pkg/dynamicinstrumentation/ditypes" "github.com/DataDog/datadog-agent/pkg/network/go/bininspect" + "github.com/DataDog/datadog-agent/pkg/util/log" + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) // inspectGoBinaries goes through each service and populates information about the binary @@ -54,7 +53,7 @@ func AnalyzeBinary(procInfo *ditypes.ProcessInfo) error { procInfo.TypeMap = typeMap - elfFile, err := elf.Open(procInfo.BinaryPath) + elfFile, err := safeelf.Open(procInfo.BinaryPath) if err != nil { return fmt.Errorf("could not open elf file %w", err) } diff --git a/pkg/dynamicinstrumentation/diconfig/binary_inspection_test.go b/pkg/dynamicinstrumentation/diconfig/binary_inspection_test.go index 3de712a7a515f..32d31e49c2188 100644 --- a/pkg/dynamicinstrumentation/diconfig/binary_inspection_test.go +++ b/pkg/dynamicinstrumentation/diconfig/binary_inspection_test.go @@ -8,7 +8,6 @@ package diconfig import ( - "debug/elf" "fmt" "os" "path/filepath" @@ -18,6 +17,8 @@ import ( "github.com/DataDog/datadog-agent/pkg/dynamicinstrumentation/testutil" "github.com/DataDog/datadog-agent/pkg/network/go/bininspect" + "github.com/DataDog/datadog-agent/pkg/util/safeelf" + "github.com/kr/pretty" ) @@ -40,7 +41,7 @@ func TestBinaryInspection(t *testing.T) { t.Error(err) } - f, err := elf.Open(binPath) + f, err := safeelf.Open(binPath) if err != nil { t.Error(err) } diff --git a/pkg/dynamicinstrumentation/diconfig/config_manager.go b/pkg/dynamicinstrumentation/diconfig/config_manager.go index e4ba457d8b992..b939b8827d6cb 100644 --- a/pkg/dynamicinstrumentation/diconfig/config_manager.go +++ b/pkg/dynamicinstrumentation/diconfig/config_manager.go @@ -13,7 +13,8 @@ import ( "encoding/json" "fmt" - "github.com/DataDog/datadog-agent/pkg/util/log" + "github.com/cilium/ebpf/ringbuf" + "github.com/google/uuid" "github.com/DataDog/datadog-agent/pkg/dynamicinstrumentation/codegen" "github.com/DataDog/datadog-agent/pkg/dynamicinstrumentation/diagnostics" @@ -22,8 +23,7 @@ import ( "github.com/DataDog/datadog-agent/pkg/dynamicinstrumentation/eventparser" "github.com/DataDog/datadog-agent/pkg/dynamicinstrumentation/proctracker" "github.com/DataDog/datadog-agent/pkg/dynamicinstrumentation/ratelimiter" - "github.com/cilium/ebpf/ringbuf" - "github.com/google/uuid" + "github.com/DataDog/datadog-agent/pkg/util/log" ) type rcConfig struct { diff --git a/pkg/dynamicinstrumentation/diconfig/dwarf.go b/pkg/dynamicinstrumentation/diconfig/dwarf.go index fbf403cf17526..85b9ba2ba4176 100644 --- a/pkg/dynamicinstrumentation/diconfig/dwarf.go +++ b/pkg/dynamicinstrumentation/diconfig/dwarf.go @@ -10,16 +10,16 @@ package diconfig import ( "cmp" "debug/dwarf" - "debug/elf" "fmt" "io" "reflect" "slices" - "github.com/DataDog/datadog-agent/pkg/util/log" + "github.com/go-delve/delve/pkg/dwarf/godwarf" "github.com/DataDog/datadog-agent/pkg/dynamicinstrumentation/ditypes" - "github.com/go-delve/delve/pkg/dwarf/godwarf" + "github.com/DataDog/datadog-agent/pkg/util/log" + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) func getTypeMap(dwarfData *dwarf.Data, targetFunctions map[string]bool) (*ditypes.TypeMap, error) { @@ -184,7 +184,7 @@ func loadDWARF(binaryPath string) (*dwarf.Data, error) { if dwarfData, ok := dwarfMap[binaryPath]; ok { return dwarfData, nil } - elfFile, err := elf.Open(binaryPath) + elfFile, err := safeelf.Open(binaryPath) if err != nil { return nil, fmt.Errorf("couldn't open elf binary: %w", err) } diff --git a/pkg/dynamicinstrumentation/proctracker/proctracker.go b/pkg/dynamicinstrumentation/proctracker/proctracker.go index f03d86c17efaa..fd5c6f750488c 100644 --- a/pkg/dynamicinstrumentation/proctracker/proctracker.go +++ b/pkg/dynamicinstrumentation/proctracker/proctracker.go @@ -10,7 +10,6 @@ package proctracker import ( - "debug/elf" "errors" "os" "path/filepath" @@ -19,19 +18,19 @@ import ( "sync" "syscall" - "github.com/DataDog/datadog-agent/pkg/util/log" - - "github.com/DataDog/datadog-agent/pkg/dynamicinstrumentation/ditypes" "github.com/cilium/ebpf" "github.com/cilium/ebpf/link" + "golang.org/x/sys/unix" + "github.com/DataDog/datadog-agent/pkg/dynamicinstrumentation/ditypes" "github.com/DataDog/datadog-agent/pkg/network/go/bininspect" "github.com/DataDog/datadog-agent/pkg/network/go/binversion" "github.com/DataDog/datadog-agent/pkg/process/monitor" "github.com/DataDog/datadog-agent/pkg/security/secl/model" "github.com/DataDog/datadog-agent/pkg/security/utils" "github.com/DataDog/datadog-agent/pkg/util/kernel" - "golang.org/x/sys/unix" + "github.com/DataDog/datadog-agent/pkg/util/log" + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) type processTrackerCallback func(ditypes.DIProcs) @@ -134,7 +133,7 @@ func (pt *ProcessTracker) inspectBinary(exePath string, pid uint32) { } defer f.Close() - elfFile, err := elf.NewFile(f) + elfFile, err := safeelf.NewFile(f) if err != nil { log.Infof("file %s could not be parsed as an ELF file: %s", binPath, err) return diff --git a/pkg/ebpf/uprobes/inspector.go b/pkg/ebpf/uprobes/inspector.go index f26cb58f6c135..2edc9d806cdd9 100644 --- a/pkg/ebpf/uprobes/inspector.go +++ b/pkg/ebpf/uprobes/inspector.go @@ -8,7 +8,6 @@ package uprobes import ( - "debug/elf" "errors" "fmt" "runtime" @@ -18,6 +17,7 @@ import ( "github.com/DataDog/datadog-agent/pkg/network/go/bininspect" "github.com/DataDog/datadog-agent/pkg/network/usm/utils" "github.com/DataDog/datadog-agent/pkg/util/common" + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) // BinaryInspector implementors are responsible for extracting the metadata required to attach from a binary. @@ -54,7 +54,7 @@ var _ BinaryInspector = &NativeBinaryInspector{} // Inspect extracts the metadata required to attach to a binary from the ELF file at the given path. func (p *NativeBinaryInspector) Inspect(fpath utils.FilePath, requests []SymbolRequest) (map[string]bininspect.FunctionMetadata, error) { path := fpath.HostPath - elfFile, err := elf.Open(path) + elfFile, err := safeelf.Open(path) if err != nil { return nil, err } @@ -102,7 +102,7 @@ func (p *NativeBinaryInspector) Inspect(fpath utils.FilePath, requests []SymbolR symbolMapBestEffort, _ := bininspect.GetAllSymbolsInSetByName(elfFile, bestEffortSymbols) funcMap := make(map[string]bininspect.FunctionMetadata, len(symbolMap)+len(symbolMapBestEffort)) - for _, symMap := range []map[string]elf.Symbol{symbolMap, symbolMapBestEffort} { + for _, symMap := range []map[string]safeelf.Symbol{symbolMap, symbolMapBestEffort} { for symbolName, symbol := range symMap { m, err := p.symbolToFuncMetadata(elfFile, symbol) if err != nil { @@ -115,8 +115,8 @@ func (p *NativeBinaryInspector) Inspect(fpath utils.FilePath, requests []SymbolR return funcMap, nil } -func (*NativeBinaryInspector) symbolToFuncMetadata(elfFile *elf.File, sym elf.Symbol) (*bininspect.FunctionMetadata, error) { - manager.SanitizeUprobeAddresses(elfFile, []elf.Symbol{sym}) +func (*NativeBinaryInspector) symbolToFuncMetadata(elfFile *safeelf.File, sym safeelf.Symbol) (*bininspect.FunctionMetadata, error) { + manager.SanitizeUprobeAddresses(elfFile.File, []safeelf.Symbol{sym}) offset, err := bininspect.SymbolToOffset(elfFile, sym) if err != nil { return nil, err diff --git a/pkg/ebpf/verifier/elf.go b/pkg/ebpf/verifier/elf.go index 3f1210852fd05..95291d348a7d7 100644 --- a/pkg/ebpf/verifier/elf.go +++ b/pkg/ebpf/verifier/elf.go @@ -40,7 +40,6 @@ package verifier import ( "debug/dwarf" - "debug/elf" "errors" "fmt" "io" @@ -48,6 +47,8 @@ import ( "runtime" "github.com/cilium/ebpf" + + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) // getLineReader gets the line reader for a DWARF data object, searching in the compilation unit entry @@ -133,7 +134,7 @@ func buildProgStartMap(dwarfData *dwarf.Data, symToSeq map[string]int) (map[prog // buildSymbolToSequenceMap builds a map that links each symbol to the sequence index it belongs to. // The address in the DWARF debug_line section is relative to the start of each sequence, but the symbol information // doesn't explicitly say which sequence it belongs to. This function builds that map. -func buildSymbolToSequenceMap(elfFile *elf.File) (map[string]int, error) { +func buildSymbolToSequenceMap(elfFile *safeelf.File) (map[string]int, error) { symbols, err := elfFile.Symbols() if err != nil { return nil, fmt.Errorf("failed to read symbols from ELF file: %w", err) @@ -143,7 +144,7 @@ func buildSymbolToSequenceMap(elfFile *elf.File) (map[string]int, error) { sectIndexToSeqIndex := make(map[int]int) idx := 0 for i, sect := range elfFile.Sections { - if sect.Flags&elf.SHF_EXECINSTR != 0 && sect.Size > 0 { + if sect.Flags&safeelf.SHF_EXECINSTR != 0 && sect.Size > 0 { sectIndexToSeqIndex[i] = idx idx++ } @@ -160,32 +161,12 @@ func buildSymbolToSequenceMap(elfFile *elf.File) (map[string]int, error) { return symToSeq, nil } -// openSafeELFFile opens an ELF file and recovers from panics that might happen when reading it. -func openSafeELFFile(path string) (safe *elf.File, err error) { - defer func() { - r := recover() - if r == nil { - return - } - - safe = nil - err = fmt.Errorf("reading ELF file panicked: %s", r) - }() - - file, err := elf.Open(path) - if err != nil { - return nil, err - } - - return file, nil -} - // getSourceMap builds the source map for an eBPF program. It returns two maps, one that // for each program function maps the instruction offset to the source line information, and // another that for each section maps the functions that belong to it. func getSourceMap(file string, spec *ebpf.CollectionSpec) (map[string]map[int]*SourceLine, map[string][]string, error) { // Open the ELF file - elfFile, err := openSafeELFFile(file) + elfFile, err := safeelf.Open(file) if err != nil { return nil, nil, fmt.Errorf("cannot open ELF file %s: %w", file, err) } @@ -195,7 +176,7 @@ func getSourceMap(file string, spec *ebpf.CollectionSpec) (map[string]map[int]*S // files because of missing support for relocations. However, we don't need them here as we're // not necessary for line info, so we can skip them. The DWARF library will skip that processing // if we set manually the type of the file to ET_EXEC. - elfFile.Type = elf.ET_EXEC + elfFile.Type = safeelf.ET_EXEC dwarfData, err := elfFile.DWARF() if err != nil { return nil, nil, fmt.Errorf("cannot read DWARF data for %s: %w", file, err) diff --git a/pkg/gpu/cuda/cubin.go b/pkg/gpu/cuda/cubin.go index 3b89316324f95..bf91065f10d74 100644 --- a/pkg/gpu/cuda/cubin.go +++ b/pkg/gpu/cuda/cubin.go @@ -10,12 +10,13 @@ package cuda import ( "bytes" - "debug/elf" "encoding/binary" "fmt" "io" "regexp" "strings" + + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) // CubinKernelKey is the key to identify a kernel in a fatbin @@ -126,7 +127,7 @@ type nvInfoItem struct { Attr nvInfoAttr } -type sectionParserFunc func(*elf.Section, string) error +type sectionParserFunc func(*safeelf.Section, string) error // cubinParser is a helper struct to parse the cubin ELF sections type cubinParser struct { @@ -167,7 +168,7 @@ func (cp *cubinParser) parseCubinElf(data []byte) error { } data[elfVersionOffset] = 1 - cubinElf, err := elf.NewFile(bytes.NewReader(data)) + cubinElf, err := safeelf.NewFile(bytes.NewReader(data)) if err != nil { return fmt.Errorf("failed to parse cubin ELF: %w", err) } @@ -203,7 +204,7 @@ type nvInfoParsedItem struct { value []byte } -func (cp *cubinParser) parseNvInfoSection(sect *elf.Section, kernelName string) error { +func (cp *cubinParser) parseNvInfoSection(sect *safeelf.Section, kernelName string) error { items := make(map[nvInfoAttr]nvInfoParsedItem) buffer := sect.Open() @@ -253,7 +254,7 @@ func (cp *cubinParser) parseNvInfoSection(sect *elf.Section, kernelName string) return nil } -func (cp *cubinParser) parseTextSection(sect *elf.Section, kernelName string) error { +func (cp *cubinParser) parseTextSection(sect *safeelf.Section, kernelName string) error { if kernelName == "" { return nil } @@ -265,7 +266,7 @@ func (cp *cubinParser) parseTextSection(sect *elf.Section, kernelName string) er return nil } -func (cp *cubinParser) parseSharedMemSection(sect *elf.Section, kernelName string) error { +func (cp *cubinParser) parseSharedMemSection(sect *safeelf.Section, kernelName string) error { if kernelName == "" { return nil } @@ -278,7 +279,7 @@ func (cp *cubinParser) parseSharedMemSection(sect *elf.Section, kernelName strin var constantSectNameRegex = regexp.MustCompile(`\.nv\.constant\d\.(.*)`) -func (cp *cubinParser) parseConstantMemSection(sect *elf.Section, _ string) error { +func (cp *cubinParser) parseConstantMemSection(sect *safeelf.Section, _ string) error { // Constant memory sections are named .nv.constantX.Y where X is the constant memory index and Y is the name // so we have to do some custom parsing match := constantSectNameRegex.FindStringSubmatch(sect.Name) diff --git a/pkg/gpu/cuda/fatbin.go b/pkg/gpu/cuda/fatbin.go index 09ea20e05cd27..5000defcd3934 100644 --- a/pkg/gpu/cuda/fatbin.go +++ b/pkg/gpu/cuda/fatbin.go @@ -17,13 +17,14 @@ package cuda import ( - "debug/elf" "encoding/binary" "fmt" "io" "unsafe" "github.com/pierrec/lz4/v4" + + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) type fatbinDataKind uint16 @@ -104,7 +105,7 @@ func (fbd *fatbinData) validate() error { // ParseFatbinFromELFFilePath opens the given path and parses the resulting ELF for CUDA kernels func ParseFatbinFromELFFilePath(path string) (*Fatbin, error) { - elfFile, err := elf.Open(path) + elfFile, err := safeelf.Open(path) if err != nil { return nil, fmt.Errorf("failed to open ELF file %s: %w", path, err) } @@ -119,7 +120,7 @@ func getBufferOffset(buf io.Seeker) int64 { } // ParseFatbinFromELFFile parses the fatbin sections of the given ELF file and returns the information found in it -func ParseFatbinFromELFFile(elfFile *elf.File) (*Fatbin, error) { +func ParseFatbinFromELFFile(elfFile *safeelf.File) (*Fatbin, error) { fatbin := &Fatbin{ Kernels: make(map[CubinKernelKey]*CubinKernel), } diff --git a/pkg/gpu/cuda/symbols.go b/pkg/gpu/cuda/symbols.go index ea10b549dc1a9..650d6ca7add1e 100644 --- a/pkg/gpu/cuda/symbols.go +++ b/pkg/gpu/cuda/symbols.go @@ -6,8 +6,9 @@ package cuda import ( - "debug/elf" "fmt" + + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) // Symbols holds all necessary data from a CUDA executable for @@ -21,7 +22,7 @@ type Symbols struct { // GetSymbols reads an ELF file from the given path and return the parsed CUDA data func GetSymbols(path string) (*Symbols, error) { - elfFile, err := elf.Open(path) + elfFile, err := safeelf.Open(path) if err != nil { return nil, fmt.Errorf("error opening ELF file %s: %w", path, err) } diff --git a/pkg/languagedetection/internal/detectors/go_detector.go b/pkg/languagedetection/internal/detectors/go_detector.go index 4f8d7e86b73eb..ae9260bbeff8f 100644 --- a/pkg/languagedetection/internal/detectors/go_detector.go +++ b/pkg/languagedetection/internal/detectors/go_detector.go @@ -8,7 +8,6 @@ package detectors import ( - "debug/elf" "fmt" "path" "strconv" @@ -17,6 +16,7 @@ import ( "github.com/DataDog/datadog-agent/pkg/languagedetection/languagemodels" "github.com/DataDog/datadog-agent/pkg/network/go/binversion" "github.com/DataDog/datadog-agent/pkg/util/kernel" + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) //nolint:revive // TODO(PROC) Fix revive linter @@ -32,12 +32,12 @@ func NewGoDetector() GoDetector { // DetectLanguage allows for detecting if a process is a go process, and its version. // Note that currently the goDetector only returns non-retriable errors since in all cases we will not be able to detect the language. // Scenarios in which we can return an error: -// - Program exits early, and we fail to call `elf.Open`. Note that in the future it may be possible to lock the directory using a system call. +// - Program exits early, and we fail to call `safeelf.Open`. Note that in the future it may be possible to lock the directory using a system call. // - Program is not a go binary, or has build tags stripped out. In this case we return a `dderrors.NotFound`. func (d GoDetector) DetectLanguage(process languagemodels.Process) (languagemodels.Language, error) { exePath := d.getHostProc(process.GetPid()) - bin, err := elf.Open(exePath) + bin, err := safeelf.Open(exePath) if err != nil { return languagemodels.Language{}, fmt.Errorf("open: %v", err) } diff --git a/pkg/network/go/asmscan/scan.go b/pkg/network/go/asmscan/scan.go index 94ae7ad09919d..5c55ba13905a8 100644 --- a/pkg/network/go/asmscan/scan.go +++ b/pkg/network/go/asmscan/scan.go @@ -7,11 +7,13 @@ package asmscan import ( - "debug/elf" + "errors" "fmt" "golang.org/x/arch/arm64/arm64asm" "golang.org/x/arch/x86/x86asm" + + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) // ScanFunction finds the program counter (PC) positions of machine code instructions @@ -37,7 +39,11 @@ import ( // - https://github.com/iovisor/bcc/issues/1320#issuecomment-407927542 // (which describes how this approach works as a workaround) // - https://github.com/golang/go/issues/22008 -func ScanFunction(textSection *elf.Section, sym elf.Symbol, functionOffset uint64, scanInstructions func(data []byte) ([]uint64, error)) ([]uint64, error) { +func ScanFunction(textSection *safeelf.Section, sym safeelf.Symbol, functionOffset uint64, scanInstructions func(data []byte) ([]uint64, error)) ([]uint64, error) { + if textSection.ReaderAt == nil { + return nil, errors.New("text section is not available in random-access form") + } + // Determine the offset in the section that the function starts at lowPC := sym.Value highPC := lowPC + sym.Size diff --git a/pkg/network/go/bininspect/dwarf.go b/pkg/network/go/bininspect/dwarf.go index 3723606a14754..745e89bfd6c12 100644 --- a/pkg/network/go/bininspect/dwarf.go +++ b/pkg/network/go/bininspect/dwarf.go @@ -9,11 +9,13 @@ package bininspect import ( "debug/dwarf" - "debug/elf" "errors" "fmt" + "github.com/DataDog/datadog-agent/pkg/network/go/dwarfutils" "github.com/DataDog/datadog-agent/pkg/network/go/dwarfutils/locexpr" + "github.com/DataDog/datadog-agent/pkg/util/safeelf" + "github.com/go-delve/delve/pkg/dwarf/godwarf" "github.com/go-delve/delve/pkg/dwarf/loclist" ) @@ -28,7 +30,7 @@ type dwarfInspector struct { // It also returns some additional relevant metadata about the given file. // It is using the DWARF debug data to obtain information, and therefore should be run on elf files that contain debug // data, like our test binaries. -func InspectWithDWARF(elfFile *elf.File, functions []string, structFields []FieldIdentifier) (*Result, error) { +func InspectWithDWARF(elfFile *safeelf.File, functions []string, structFields []FieldIdentifier) (*Result, error) { if elfFile == nil { return nil, ErrNilElf } @@ -281,7 +283,7 @@ func (d dwarfInspector) findStructOffsets(structFields []FieldIdentifier) (map[F // starting at offset, for address pc. // Adapted from github.com/go-delve/delve/pkg/proc.(*BinaryInfo).loclistEntry func (d dwarfInspector) getLoclistEntry(offset int64, pc uint64) (*loclist.Entry, error) { - debugInfoBytes, err := godwarf.GetDebugSectionElf(d.elf.file, "info") + debugInfoBytes, err := godwarf.GetDebugSectionElf(d.elf.file.File, "info") if err != nil { return nil, err } @@ -291,11 +293,11 @@ func (d dwarfInspector) getLoclistEntry(offset int64, pc uint64) (*loclist.Entry return nil, err } - debugLocBytes, _ := godwarf.GetDebugSectionElf(d.elf.file, "loc") + debugLocBytes, _ := godwarf.GetDebugSectionElf(d.elf.file.File, "loc") loclist2 := loclist.NewDwarf2Reader(debugLocBytes, int(d.elf.arch.PointerSize())) - debugLoclistBytes, _ := godwarf.GetDebugSectionElf(d.elf.file, "loclists") + debugLoclistBytes, _ := godwarf.GetDebugSectionElf(d.elf.file.File, "loclists") loclist5 := loclist.NewDwarf5Reader(debugLoclistBytes) - debugAddrBytes, _ := godwarf.GetDebugSectionElf(d.elf.file, "addr") + debugAddrBytes, _ := godwarf.GetDebugSectionElf(d.elf.file.File, "addr") debugAddrSection := godwarf.ParseAddr(debugAddrBytes) var base uint64 diff --git a/pkg/network/go/bininspect/newproc.go b/pkg/network/go/bininspect/newproc.go index 9af8349e854ca..a6400f56525b1 100644 --- a/pkg/network/go/bininspect/newproc.go +++ b/pkg/network/go/bininspect/newproc.go @@ -8,23 +8,23 @@ package bininspect import ( - "debug/elf" "errors" "fmt" "github.com/DataDog/datadog-agent/pkg/network/go/goid" "github.com/DataDog/datadog-agent/pkg/network/go/goversion" "github.com/DataDog/datadog-agent/pkg/util/common" + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) type newProcessBinaryInspector struct { elf elfMetadata - symbols map[string]elf.Symbol + symbols map[string]safeelf.Symbol goVersion goversion.GoVersion } // InspectNewProcessBinary process the given elf File, and returns the offsets of the given functions and structs. -func InspectNewProcessBinary(elfFile *elf.File, functions map[string]FunctionConfiguration, structs map[FieldIdentifier]StructLookupFunction) (*Result, error) { +func InspectNewProcessBinary(elfFile *safeelf.File, functions map[string]FunctionConfiguration, structs map[FieldIdentifier]StructLookupFunction) (*Result, error) { if elfFile == nil { return nil, errors.New("got nil elf file") } @@ -152,9 +152,9 @@ func (i *newProcessBinaryInspector) getRuntimeGAddrTLSOffset() (uint64, error) { // - On ARM64 (but really, any architecture other than i386 and 86x64) the // offset is calculated using runtime.tls_g and the formula is different. - var tls *elf.Prog + var tls *safeelf.Prog for _, prog := range i.elf.file.Progs { - if prog.Type == elf.PT_TLS { + if prog.Type == safeelf.PT_TLS { tls = prog break } diff --git a/pkg/network/go/bininspect/pclntab.go b/pkg/network/go/bininspect/pclntab.go index 3a898de9de102..98228f470bbdc 100644 --- a/pkg/network/go/bininspect/pclntab.go +++ b/pkg/network/go/bininspect/pclntab.go @@ -9,11 +9,12 @@ package bininspect import ( "bytes" - "debug/elf" "encoding/binary" "errors" "fmt" "io" + + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) const ( @@ -51,12 +52,15 @@ var ( // This is used to lazy read from the pclntab section, as the pclntab is large and we don't want to read it all at once, // or store it in memory. type sectionAccess struct { - section *elf.Section + section *safeelf.Section baseOffset int64 } // ReadAt reads len(p) bytes from the section starting at the given offset. func (s *sectionAccess) ReadAt(outBuffer []byte, offset int64) (int, error) { + if s.section.ReaderAt == nil { + return 0, errors.New("section not available in random-access form") + } return s.section.ReadAt(outBuffer, s.baseOffset+offset) } @@ -64,7 +68,7 @@ func (s *sectionAccess) ReadAt(outBuffer []byte, offset int64) (int, error) { // Similar to LineTable struct in https://github.com/golang/go/blob/6a861010be9eed02d5285509cbaf3fb26d2c5041/src/debug/gosym/pclntab.go#L43 type pclntanSymbolParser struct { // section is the pclntab section. - section *elf.Section + section *safeelf.Section // symbolFilter is the filter for the symbols. symbolFilter symbolFilter @@ -93,7 +97,7 @@ type pclntanSymbolParser struct { } // GetPCLNTABSymbolParser returns the matching symbols from the pclntab section. -func GetPCLNTABSymbolParser(f *elf.File, symbolFilter symbolFilter) (map[string]*elf.Symbol, error) { +func GetPCLNTABSymbolParser(f *safeelf.File, symbolFilter symbolFilter) (map[string]*safeelf.Symbol, error) { section := f.Section(pclntabSectionName) if section == nil { return nil, ErrMissingPCLNTABSection @@ -118,6 +122,9 @@ func GetPCLNTABSymbolParser(f *elf.File, symbolFilter symbolFilter) (map[string] // parsePclntab parses the pclntab, setting the version and verifying the header. // Based on parsePclnTab in https://github.com/golang/go/blob/6a861010be9eed02d5285509cbaf3fb26d2c5041/src/debug/gosym/pclntab.go#L194 func (p *pclntanSymbolParser) parsePclntab() error { + if p.section.ReaderAt == nil { + return errors.New("section not available in random-access form") + } p.cachedVersion = ver11 pclntabHeader := make([]byte, 8) @@ -219,9 +226,9 @@ func getFuncTableFieldSize(version version, ptrSize int) int { // getSymbols returns the symbols from the pclntab section that match the symbol filter. // based on https://github.com/golang/go/blob/6a861010be9eed02d5285509cbaf3fb26d2c5041/src/debug/gosym/pclntab.go#L300-L329 -func (p *pclntanSymbolParser) getSymbols() (map[string]*elf.Symbol, error) { +func (p *pclntanSymbolParser) getSymbols() (map[string]*safeelf.Symbol, error) { numWanted := p.symbolFilter.getNumWanted() - symbols := make(map[string]*elf.Symbol, numWanted) + symbols := make(map[string]*safeelf.Symbol, numWanted) data := sectionAccess{section: p.section} for currentIdx := uint32(0); currentIdx < p.funcTableSize; currentIdx++ { // based on https://github.com/golang/go/blob/6a861010be9eed02d5285509cbaf3fb26d2c5041/src/debug/gosym/pclntab.go#L315 @@ -237,7 +244,7 @@ func (p *pclntanSymbolParser) getSymbols() (map[string]*elf.Symbol, error) { if funcName == "" { continue } - symbols[funcName] = &elf.Symbol{ + symbols[funcName] = &safeelf.Symbol{ Name: funcName, } if len(symbols) == numWanted { diff --git a/pkg/network/go/bininspect/pclntab_test.go b/pkg/network/go/bininspect/pclntab_test.go index ce47439733ef4..a37edc9908799 100644 --- a/pkg/network/go/bininspect/pclntab_test.go +++ b/pkg/network/go/bininspect/pclntab_test.go @@ -8,33 +8,35 @@ package bininspect import ( - "debug/elf" - "github.com/DataDog/datadog-agent/pkg/util/common" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "os" "strconv" "strings" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/DataDog/datadog-agent/pkg/util/common" + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) const ( // Info is composed of the type and binding of the symbol. Type is the lower 4 bits and binding is the upper 4 bits. // We are only interested in functions, which binding STB_GLOBAL (1) and type STT_FUNC (2). // Hence, we are interested in symbols with Info 18. - infoFunction = byte(elf.STB_GLOBAL)<<4 | byte(elf.STT_FUNC) + infoFunction = byte(safeelf.STB_GLOBAL)<<4 | byte(safeelf.STT_FUNC) ) // TestGetPCLNTABSymbolParser tests the GetPCLNTABSymbolParser function with strings set symbol filter. // We are looking to find all symbols of the current process executable and check if they are found in the PCLNTAB. func TestGetPCLNTABSymbolParser(t *testing.T) { currentPid := os.Getpid() - f, err := elf.Open("/proc/" + strconv.Itoa(currentPid) + "/exe") + f, err := safeelf.Open("/proc/" + strconv.Itoa(currentPid) + "/exe") require.NoError(t, err) symbolSet := make(common.StringSet) staticSymbols, _ := f.Symbols() dynamicSymbols, _ := f.DynamicSymbols() - for _, symbols := range [][]elf.Symbol{staticSymbols, dynamicSymbols} { + for _, symbols := range [][]safeelf.Symbol{staticSymbols, dynamicSymbols} { for _, sym := range symbols { if sym.Info != infoFunction { continue diff --git a/pkg/network/go/bininspect/symbol_filter.go b/pkg/network/go/bininspect/symbol_filter.go index c0a296b43dd86..d96d6fe6bf703 100644 --- a/pkg/network/go/bininspect/symbol_filter.go +++ b/pkg/network/go/bininspect/symbol_filter.go @@ -8,10 +8,10 @@ package bininspect import ( - "debug/elf" "strings" "github.com/DataDog/datadog-agent/pkg/util/common" + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) // symbolFilter is an interface for filtering symbols read from ELF files. @@ -24,7 +24,7 @@ type symbolFilter interface { want(symbol string) bool // findMissing returns the list of symbol names which the filter wanted but were not found in the // symbol map. This is only used for error messages. - findMissing(map[string]elf.Symbol) []string + findMissing(map[string]safeelf.Symbol) []string } // stringSetSymbolFilter is a symbol filter which finds all the symbols in a @@ -58,7 +58,7 @@ func (f stringSetSymbolFilter) want(symbol string) bool { } // findMissing gets the list of symbols which were missing. Only used for error prints. -func (f stringSetSymbolFilter) findMissing(symbolByName map[string]elf.Symbol) []string { +func (f stringSetSymbolFilter) findMissing(symbolByName map[string]safeelf.Symbol) []string { missingSymbols := make([]string, 0, max(0, len(f.symbolSet)-len(symbolByName))) for symbolName := range f.symbolSet { if _, ok := symbolByName[symbolName]; !ok { @@ -97,6 +97,6 @@ func (f prefixSymbolFilter) want(symbol string) bool { // findMissing gets the list of symbols which were missing. Only used for error // prints. Since we only know we were looking for a prefix, return that. -func (f prefixSymbolFilter) findMissing(_ map[string]elf.Symbol) []string { +func (f prefixSymbolFilter) findMissing(_ map[string]safeelf.Symbol) []string { return []string{f.prefix} } diff --git a/pkg/network/go/bininspect/symbols.go b/pkg/network/go/bininspect/symbols.go index 910bc37d3ff30..32af710f74046 100644 --- a/pkg/network/go/bininspect/symbols.go +++ b/pkg/network/go/bininspect/symbols.go @@ -8,7 +8,6 @@ package bininspect import ( - "debug/elf" "encoding/binary" "errors" "fmt" @@ -19,6 +18,7 @@ import ( "github.com/DataDog/datadog-agent/pkg/util/common" "github.com/DataDog/datadog-agent/pkg/util/log" + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) const ( @@ -32,6 +32,9 @@ const ( // redundant allocations. We get it as a parameter and not putting it as a global, to be thread safe among concurrent // and parallel calls. func getSymbolNameByEntry(sectionReader io.ReaderAt, startPos, minLength int, preAllocatedBuf []byte) int { + if sectionReader == nil { + return -1 + } readBytes, err := sectionReader.ReadAt(preAllocatedBuf, int64(startPos)) if err != nil && err != io.EOF { return -1 @@ -89,35 +92,35 @@ func getSymbolLengthBoundaries(set common.StringSet) (int, int) { // fillSymbol reads the symbol entry from the symbol section with the first 4 bytes of the name entry (which // we read using readSymbolEntryInStringTable). -func fillSymbol(symbol *elf.Symbol, byteOrder binary.ByteOrder, symbolName string, allocatedBufferForRead []byte, is64Bit bool) { +func fillSymbol(symbol *safeelf.Symbol, byteOrder binary.ByteOrder, symbolName string, allocatedBufferForRead []byte, is64Bit bool) { symbol.Name = symbolName if is64Bit { infoAndOther := byteOrder.Uint16(allocatedBufferForRead[0:2]) symbol.Info = uint8(infoAndOther >> 8) symbol.Other = uint8(infoAndOther) - symbol.Section = elf.SectionIndex(byteOrder.Uint16(allocatedBufferForRead[2:4])) + symbol.Section = safeelf.SectionIndex(byteOrder.Uint16(allocatedBufferForRead[2:4])) symbol.Value = byteOrder.Uint64(allocatedBufferForRead[4:12]) symbol.Size = byteOrder.Uint64(allocatedBufferForRead[12:20]) } else { infoAndOther := byteOrder.Uint16(allocatedBufferForRead[8:10]) symbol.Info = uint8(infoAndOther >> 8) symbol.Other = uint8(infoAndOther) - symbol.Section = elf.SectionIndex(byteOrder.Uint16(allocatedBufferForRead[10:12])) + symbol.Section = safeelf.SectionIndex(byteOrder.Uint16(allocatedBufferForRead[10:12])) symbol.Value = uint64(byteOrder.Uint32(allocatedBufferForRead[0:4])) symbol.Size = uint64(byteOrder.Uint32(allocatedBufferForRead[4:8])) } } // getSymbolsUnified extracts the given symbol list from the binary. -func getSymbolsUnified(f *elf.File, typ elf.SectionType, filter symbolFilter, is64Bit bool) ([]elf.Symbol, error) { - symbolSize := elf.Sym32Size +func getSymbolsUnified(f *safeelf.File, typ safeelf.SectionType, filter symbolFilter, is64Bit bool) ([]safeelf.Symbol, error) { + symbolSize := safeelf.Sym32Size if is64Bit { - symbolSize = elf.Sym64Size + symbolSize = safeelf.Sym64Size } // Getting the relevant symbol section. symbolSection := f.SectionByType(typ) if symbolSection == nil { - return nil, elf.ErrNoSymbols + return nil, safeelf.ErrNoSymbols } // Checking the symbol section size is aligned to a multiplication of symbolSize. @@ -129,11 +132,14 @@ func getSymbolsUnified(f *elf.File, typ elf.SectionType, filter symbolFilter, is if symbolSection.Link <= 0 || symbolSection.Link >= uint32(len(f.Sections)) { return nil, errors.New("section has invalid string table link") } + if symbolSection.ReaderAt == nil { + return nil, errors.New("symbol section not available in random-access form") + } numWanted := filter.getNumWanted() // Allocating entries for all wanted symbols. - symbols := make([]elf.Symbol, 0, numWanted) + symbols := make([]safeelf.Symbol, 0, numWanted) // Extracting the min and max symbol length. minSymbolNameSize, maxSymbolNameSize := filter.getMinMaxLength() // Pre-allocating a buffer to read the symbol string into. @@ -183,7 +189,7 @@ func getSymbolsUnified(f *elf.File, typ elf.SectionType, filter symbolFilter, is continue } - var symbol elf.Symbol + var symbol safeelf.Symbol // Complete the symbol reading. // The symbol is composed of 4 bytes representing the symbol name in the string table, and rest is the fields // of the symbols. So here we skip the first 4 bytes of the symbol, as we already processed it. @@ -199,12 +205,12 @@ func getSymbolsUnified(f *elf.File, typ elf.SectionType, filter symbolFilter, is return symbols, nil } -func getSymbols(f *elf.File, typ elf.SectionType, filter symbolFilter) ([]elf.Symbol, error) { +func getSymbols(f *safeelf.File, typ safeelf.SectionType, filter symbolFilter) ([]safeelf.Symbol, error) { switch f.Class { - case elf.ELFCLASS64: + case safeelf.ELFCLASS64: return getSymbolsUnified(f, typ, filter, true) - case elf.ELFCLASS32: + case safeelf.ELFCLASS32: return getSymbolsUnified(f, typ, filter, false) } @@ -214,31 +220,31 @@ func getSymbols(f *elf.File, typ elf.SectionType, filter symbolFilter) ([]elf.Sy // GetAllSymbolsByName returns all filtered symbols in the given elf file, // mapped by the symbol names. In case of a missing symbol, an error is // returned. -func GetAllSymbolsByName(elfFile *elf.File, filter symbolFilter) (map[string]elf.Symbol, error) { - regularSymbols, regularSymbolsErr := getSymbols(elfFile, elf.SHT_SYMTAB, filter) +func GetAllSymbolsByName(elfFile *safeelf.File, filter symbolFilter) (map[string]safeelf.Symbol, error) { + regularSymbols, regularSymbolsErr := getSymbols(elfFile, safeelf.SHT_SYMTAB, filter) if regularSymbolsErr != nil && log.ShouldLog(seelog.TraceLvl) { log.Tracef("Failed getting regular symbols of elf file: %s", regularSymbolsErr) } - var dynamicSymbols []elf.Symbol + var dynamicSymbols []safeelf.Symbol var dynamicSymbolsErr error numWanted := filter.getNumWanted() if len(regularSymbols) != numWanted { - dynamicSymbols, dynamicSymbolsErr = getSymbols(elfFile, elf.SHT_DYNSYM, filter) + dynamicSymbols, dynamicSymbolsErr = getSymbols(elfFile, safeelf.SHT_DYNSYM, filter) if dynamicSymbolsErr != nil && log.ShouldLog(seelog.TraceLvl) { log.Tracef("Failed getting dynamic symbols of elf file: %s", dynamicSymbolsErr) } } // Only if we failed getting both regular and dynamic symbols - then we abort. - if regularSymbolsErr == elf.ErrNoSymbols && dynamicSymbolsErr == elf.ErrNoSymbols { - return nil, elf.ErrNoSymbols + if regularSymbolsErr == safeelf.ErrNoSymbols && dynamicSymbolsErr == safeelf.ErrNoSymbols { + return nil, safeelf.ErrNoSymbols } if regularSymbolsErr != nil && dynamicSymbolsErr != nil { return nil, fmt.Errorf("could not open symbol sections to resolve symbol offset: %v, %v", regularSymbolsErr, dynamicSymbolsErr) } - symbolByName := make(map[string]elf.Symbol, len(regularSymbols)+len(dynamicSymbols)) + symbolByName := make(map[string]safeelf.Symbol, len(regularSymbols)+len(dynamicSymbols)) for _, regularSymbol := range regularSymbols { symbolByName[regularSymbol.Name] = regularSymbol @@ -259,14 +265,14 @@ func GetAllSymbolsByName(elfFile *elf.File, filter symbolFilter) (map[string]elf // GetAllSymbolsInSetByName returns all symbols (from the symbolSet) in the // given elf file, mapped by the symbol names. In case of a missing symbol, an // error is returned. -func GetAllSymbolsInSetByName(elfFile *elf.File, symbolSet common.StringSet) (map[string]elf.Symbol, error) { +func GetAllSymbolsInSetByName(elfFile *safeelf.File, symbolSet common.StringSet) (map[string]safeelf.Symbol, error) { filter := newStringSetSymbolFilter(symbolSet) return GetAllSymbolsByName(elfFile, filter) } // GetAnySymbolWithPrefix returns any one symbol with the given prefix and the // specified maximum length from the ELF file. -func GetAnySymbolWithPrefix(elfFile *elf.File, prefix string, maxLength int) (*elf.Symbol, error) { +func GetAnySymbolWithPrefix(elfFile *safeelf.File, prefix string, maxLength int) (*safeelf.Symbol, error) { filter := newPrefixSymbolFilter(prefix, maxLength) symbols, err := GetAllSymbolsByName(elfFile, filter) if err != nil { @@ -284,7 +290,7 @@ func GetAnySymbolWithPrefix(elfFile *elf.File, prefix string, maxLength int) (*e // GetAnySymbolWithPrefixPCLNTAB returns any one symbol with the given prefix and the // specified maximum length from the pclntab section in ELF file. -func GetAnySymbolWithPrefixPCLNTAB(elfFile *elf.File, prefix string, maxLength int) (*elf.Symbol, error) { +func GetAnySymbolWithPrefixPCLNTAB(elfFile *safeelf.File, prefix string, maxLength int) (*safeelf.Symbol, error) { symbols, err := GetPCLNTABSymbolParser(elfFile, newPrefixSymbolFilter(prefix, maxLength)) if err != nil { return nil, err diff --git a/pkg/network/go/bininspect/symbols_test.go b/pkg/network/go/bininspect/symbols_test.go index 8d2cd3ce8aae2..3926e11c98e1a 100644 --- a/pkg/network/go/bininspect/symbols_test.go +++ b/pkg/network/go/bininspect/symbols_test.go @@ -8,7 +8,6 @@ package bininspect import ( - "debug/elf" "path/filepath" "testing" @@ -17,9 +16,10 @@ import ( "github.com/DataDog/datadog-agent/pkg/network/protocols/http/testutil" "github.com/DataDog/datadog-agent/pkg/util/common" + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) -func openTestElf(t *testing.T) *elf.File { +func openTestElf(t *testing.T) *safeelf.File { curDir, err := testutil.CurDir() require.NoError(t, err) @@ -27,7 +27,7 @@ func openTestElf(t *testing.T) *elf.File { // always. lib := filepath.Join(curDir, "..", "..", "usm", "testdata", "site-packages", "ddtrace", "libssl.so.arm64") - elfFile, err := elf.Open(lib) + elfFile, err := safeelf.Open(lib) require.NoError(t, err) return elfFile diff --git a/pkg/network/go/bininspect/types.go b/pkg/network/go/bininspect/types.go index 309c06377e2f5..c83be49b4e410 100644 --- a/pkg/network/go/bininspect/types.go +++ b/pkg/network/go/bininspect/types.go @@ -9,12 +9,13 @@ package bininspect import ( - "debug/elf" "errors" "reflect" - "github.com/DataDog/datadog-agent/pkg/network/go/goversion" delve "github.com/go-delve/delve/pkg/goversion" + + "github.com/DataDog/datadog-agent/pkg/network/go/goversion" + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) const ( @@ -63,7 +64,7 @@ var StructOffsetLimitListenerConnNetConn = FieldIdentifier{ } type elfMetadata struct { - file *elf.File + file *safeelf.File arch GoArch } diff --git a/pkg/network/go/bininspect/utils.go b/pkg/network/go/bininspect/utils.go index 0f331c8d7265a..83aafa6cfedc4 100644 --- a/pkg/network/go/bininspect/utils.go +++ b/pkg/network/go/bininspect/utils.go @@ -9,24 +9,25 @@ package bininspect import ( "debug/dwarf" - "debug/elf" "errors" "fmt" "strings" + "github.com/go-delve/delve/pkg/goversion" + "github.com/DataDog/datadog-agent/pkg/network/go/asmscan" "github.com/DataDog/datadog-agent/pkg/network/go/binversion" ddversion "github.com/DataDog/datadog-agent/pkg/network/go/goversion" - "github.com/go-delve/delve/pkg/goversion" + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) // GetArchitecture returns the `runtime.GOARCH`-compatible names of the architecture. // Only returns a value for supported architectures. -func GetArchitecture(elfFile *elf.File) (GoArch, error) { +func GetArchitecture(elfFile *safeelf.File) (GoArch, error) { switch elfFile.FileHeader.Machine { - case elf.EM_X86_64: + case safeelf.EM_X86_64: return GoArchX86_64, nil - case elf.EM_AARCH64: + case safeelf.EM_AARCH64: return GoArchARM64, nil } @@ -36,7 +37,7 @@ func GetArchitecture(elfFile *elf.File) (GoArch, error) { // HasDwarfInfo attempts to parse the DWARF data and look for any records. // If it cannot be parsed or if there are no DWARF info records, // then it assumes that the binary has been stripped. -func HasDwarfInfo(elfFile *elf.File) (*dwarf.Data, bool) { +func HasDwarfInfo(elfFile *safeelf.File) (*dwarf.Data, bool) { dwarfData, err := elfFile.DWARF() if err != nil { return nil, false @@ -55,7 +56,7 @@ func HasDwarfInfo(elfFile *elf.File) (*dwarf.Data, bool) { // The implementation is available in src/cmd/go/internal/version/version.go: // https://cs.opensource.google/go/go/+/refs/tags/go1.17.2:src/cmd/go/internal/version/version.go // The main logic was pulled out to a sub-package, `binversion` -func FindGoVersion(elfFile *elf.File) (ddversion.GoVersion, error) { +func FindGoVersion(elfFile *safeelf.File) (ddversion.GoVersion, error) { version, err := binversion.ReadElfBuildInfo(elfFile) if err != nil { return ddversion.GoVersion{}, fmt.Errorf("could not get Go toolchain version from ELF binary file: %w", err) @@ -90,7 +91,7 @@ func FindABI(version ddversion.GoVersion, arch GoArch) (GoABI, error) { // - https://github.com/go-delve/delve/pull/2704/files#diff-fb7b7a020e32bf8bf477c052ac2d2857e7e587478be6039aebc7135c658417b2R769 // - https://github.com/go-delve/delve/blob/75bbbbb60cecda0d65c63de7ae8cb8b8412d6fc3/pkg/proc/breakpoints.go#L86-L95 // - https://github.com/go-delve/delve/blob/75bbbbb60cecda0d65c63de7ae8cb8b8412d6fc3/pkg/proc/breakpoints.go#L374 -func FindReturnLocations(elfFile *elf.File, sym elf.Symbol, functionOffset uint64) ([]uint64, error) { +func FindReturnLocations(elfFile *safeelf.File, sym safeelf.Symbol, functionOffset uint64) ([]uint64, error) { arch, err := GetArchitecture(elfFile) if err != nil { return nil, err @@ -112,15 +113,15 @@ func FindReturnLocations(elfFile *elf.File, sym elf.Symbol, functionOffset uint6 } // SymbolToOffset returns the offset of the given symbol name in the given elf file. -func SymbolToOffset(f *elf.File, symbol elf.Symbol) (uint32, error) { +func SymbolToOffset(f *safeelf.File, symbol safeelf.Symbol) (uint32, error) { if f == nil { return 0, errors.New("got nil elf file") } - var sectionsToSearchForSymbol []*elf.Section + var sectionsToSearchForSymbol []*safeelf.Section for i := range f.Sections { - if f.Sections[i].Flags == elf.SHF_ALLOC+elf.SHF_EXECINSTR { + if f.Sections[i].Flags == safeelf.SHF_ALLOC+safeelf.SHF_EXECINSTR { sectionsToSearchForSymbol = append(sectionsToSearchForSymbol, f.Sections[i]) } } @@ -129,7 +130,7 @@ func SymbolToOffset(f *elf.File, symbol elf.Symbol) (uint32, error) { return 0, fmt.Errorf("symbol %q not found in file - no sections to search", symbol) } - var executableSection *elf.Section + var executableSection *safeelf.Section // Find what section the symbol is in by checking the executable section's // addr space. diff --git a/pkg/network/go/binversion/buildinfo.go b/pkg/network/go/binversion/buildinfo.go index 8914693bd62e1..e95fd862ffec5 100644 --- a/pkg/network/go/binversion/buildinfo.go +++ b/pkg/network/go/binversion/buildinfo.go @@ -29,11 +29,11 @@ package binversion import ( "bytes" - "debug/elf" "encoding/binary" "errors" "io" + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ddsync "github.com/DataDog/datadog-agent/pkg/util/sync" ) @@ -77,7 +77,7 @@ type exe interface { // ReadElfBuildInfo extracts the Go toolchain version and module information // strings from a Go binary. On success, vers should be non-empty. mod // is empty if the binary was not built with modules enabled. -func ReadElfBuildInfo(elfFile *elf.File) (vers string, err error) { +func ReadElfBuildInfo(elfFile *safeelf.File) (vers string, err error) { x := &elfExe{f: elfFile} // Read the first 64kB of dataAddr to find the build info blob. @@ -176,7 +176,7 @@ func readString(x exe, ptrSize int, readPtr func([]byte) uint64, addr uint64) st // elfExe is the ELF implementation of the exe interface. type elfExe struct { - f *elf.File + f *safeelf.File } func (x *elfExe) ReadData(addr, size uint64) ([]byte, error) { @@ -225,7 +225,7 @@ func (x *elfExe) DataStart() uint64 { } } for _, p := range x.f.Progs { - if p.Type == elf.PT_LOAD && p.Flags&(elf.PF_X|elf.PF_W) == elf.PF_W { + if p.Type == safeelf.PT_LOAD && p.Flags&(safeelf.PF_X|safeelf.PF_W) == safeelf.PF_W { return p.Vaddr } } diff --git a/pkg/network/go/goid/internal/generate_goid_lut.go b/pkg/network/go/goid/internal/generate_goid_lut.go index d21533c7f0566..0d7145d03d411 100644 --- a/pkg/network/go/goid/internal/generate_goid_lut.go +++ b/pkg/network/go/goid/internal/generate_goid_lut.go @@ -9,7 +9,6 @@ package main import ( "context" - "debug/elf" "flag" "fmt" "log" @@ -21,6 +20,7 @@ import ( "github.com/DataDog/datadog-agent/pkg/network/go/dwarfutils" "github.com/DataDog/datadog-agent/pkg/network/go/goversion" "github.com/DataDog/datadog-agent/pkg/network/go/lutgen" + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) var ( @@ -134,7 +134,7 @@ func inspectBinary(binary lutgen.Binary) (interface{}, error) { return 0, err } defer file.Close() - elfFile, err := elf.NewFile(file) + elfFile, err := safeelf.NewFile(file) if err != nil { return 0, err } diff --git a/pkg/network/protocols/http/gotls/lookup/internal/generate_luts.go b/pkg/network/protocols/http/gotls/lookup/internal/generate_luts.go index b2596f9de3e49..bfe620de890b6 100644 --- a/pkg/network/protocols/http/gotls/lookup/internal/generate_luts.go +++ b/pkg/network/protocols/http/gotls/lookup/internal/generate_luts.go @@ -9,7 +9,6 @@ package main import ( "context" - "debug/elf" _ "embed" "flag" "fmt" @@ -23,6 +22,7 @@ import ( "github.com/DataDog/datadog-agent/pkg/network/go/bininspect" "github.com/DataDog/datadog-agent/pkg/network/go/goversion" "github.com/DataDog/datadog-agent/pkg/network/go/lutgen" + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) var ( @@ -255,7 +255,7 @@ func inspectBinary(binary lutgen.Binary) (interface{}, error) { } defer file.Close() - elfFile, err := elf.NewFile(file) + elfFile, err := safeelf.NewFile(file) if err != nil { return bininspect.Result{}, err } diff --git a/pkg/network/protocols/tls/nodejs/nodejs.go b/pkg/network/protocols/tls/nodejs/nodejs.go index 5d2e225829131..09ee5ecebf497 100644 --- a/pkg/network/protocols/tls/nodejs/nodejs.go +++ b/pkg/network/protocols/tls/nodejs/nodejs.go @@ -3,6 +3,8 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2016-present Datadog, Inc. +//go:build test + // Package nodejs provides helpers to run nodejs HTTPs server. package nodejs diff --git a/pkg/network/usm/ebpf_gotls_helpers.go b/pkg/network/usm/ebpf_gotls_helpers.go index 0ed45396b9024..5aa18b83fb2cf 100644 --- a/pkg/network/usm/ebpf_gotls_helpers.go +++ b/pkg/network/usm/ebpf_gotls_helpers.go @@ -8,7 +8,6 @@ package usm import ( - "debug/elf" "errors" "fmt" "os" @@ -26,6 +25,7 @@ import ( libtelemetry "github.com/DataDog/datadog-agent/pkg/network/protocols/telemetry" "github.com/DataDog/datadog-agent/pkg/network/usm/utils" "github.com/DataDog/datadog-agent/pkg/util/log" + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) var paramLookupFunctions = map[string]bininspect.ParameterLookupFunction{ @@ -74,7 +74,7 @@ func (p *goTLSBinaryInspector) Inspect(fpath utils.FilePath, requests []uprobes. } defer f.Close() - elfFile, err := elf.NewFile(f) + elfFile, err := safeelf.NewFile(f) if err != nil { return nil, fmt.Errorf("file %s could not be parsed as an ELF file: %w", path, err) } @@ -94,7 +94,7 @@ func (p *goTLSBinaryInspector) Inspect(fpath utils.FilePath, requests []uprobes. inspectionResult, err := bininspect.InspectNewProcessBinary(elfFile, functionsConfig, p.structFieldsLookupFunctions) if err != nil { - if errors.Is(err, elf.ErrNoSymbols) { + if errors.Is(err, safeelf.ErrNoSymbols) { p.binNoSymbolsMetric.Add(1) } return nil, fmt.Errorf("error extracting inspection data from %s: %w", path, err) diff --git a/pkg/network/usm/ebpf_ssl.go b/pkg/network/usm/ebpf_ssl.go index aed0ffac49216..6873ff4818e93 100644 --- a/pkg/network/usm/ebpf_ssl.go +++ b/pkg/network/usm/ebpf_ssl.go @@ -9,7 +9,6 @@ package usm import ( "bytes" - "debug/elf" "fmt" "io" "os" @@ -37,6 +36,7 @@ import ( "github.com/DataDog/datadog-agent/pkg/util/common" "github.com/DataDog/datadog-agent/pkg/util/kernel" "github.com/DataDog/datadog-agent/pkg/util/log" + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ddsync "github.com/DataDog/datadog-agent/pkg/util/sync" ) @@ -617,7 +617,7 @@ func addHooks(m *manager.Manager, procRoot string, probes []manager.ProbesSelect uid := getUID(fpath.ID) - elfFile, err := elf.Open(fpath.HostPath) + elfFile, err := safeelf.Open(fpath.HostPath) if err != nil { return err } @@ -697,7 +697,7 @@ func addHooks(m *manager.Manager, procRoot string, probes []manager.ProbesSelect continue } } - manager.SanitizeUprobeAddresses(elfFile, []elf.Symbol{sym}) + manager.SanitizeUprobeAddresses(elfFile.File, []safeelf.Symbol{sym}) offset, err := bininspect.SymbolToOffset(elfFile, sym) if err != nil { return err diff --git a/pkg/security/probe/constantfetch/runtime_compiled.go b/pkg/security/probe/constantfetch/runtime_compiled.go index cb004bf72897b..c847ab821e9a4 100644 --- a/pkg/security/probe/constantfetch/runtime_compiled.go +++ b/pkg/security/probe/constantfetch/runtime_compiled.go @@ -10,7 +10,7 @@ package constantfetch import ( "bytes" - "debug/elf" + "errors" "fmt" "sort" "text/template" @@ -20,6 +20,7 @@ import ( "github.com/DataDog/datadog-agent/pkg/ebpf" "github.com/DataDog/datadog-agent/pkg/ebpf/bytecode/runtime" "github.com/DataDog/datadog-agent/pkg/security/seclog" + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) type rcSymbolPair struct { @@ -121,7 +122,7 @@ func (cf *RuntimeCompilationConstantFetcher) FinishAndGetResults() (map[string]u return nil, err } - f, err := elf.NewFile(elfFile) + f, err := safeelf.NewFile(elfFile) if err != nil { return nil, err } @@ -136,6 +137,9 @@ func (cf *RuntimeCompilationConstantFetcher) FinishAndGetResults() (map[string]u } section := f.Sections[sym.Section] + if section.ReaderAt == nil { + return nil, errors.New("section not available in random-access form") + } buf := make([]byte, sym.Size) if _, err := section.ReadAt(buf, int64(sym.Value)); err != nil { return nil, fmt.Errorf("unable to read section at %d: %s", int64(sym.Value), err) diff --git a/pkg/security/ptracer/utils.go b/pkg/security/ptracer/utils.go index f313d02415800..7edfc091c612b 100644 --- a/pkg/security/ptracer/utils.go +++ b/pkg/security/ptracer/utils.go @@ -11,7 +11,6 @@ package ptracer import ( "bufio" "bytes" - "debug/elf" "errors" "fmt" "io" @@ -31,6 +30,7 @@ import ( "github.com/DataDog/datadog-agent/pkg/security/proto/ebpfless" "github.com/DataDog/datadog-agent/pkg/security/secl/containerutils" "github.com/DataDog/datadog-agent/pkg/security/secl/model" + "github.com/DataDog/datadog-agent/pkg/util/safeelf" ) // Funcs mainly copied from github.com/DataDog/datadog-agent/pkg/security/utils/cgroup.go @@ -459,7 +459,7 @@ func microsecsToNanosecs(secs uint64) uint64 { } func getModuleName(reader io.ReaderAt) (string, error) { - elf, err := elf.NewFile(reader) + elf, err := safeelf.NewFile(reader) if err != nil { return "", err } diff --git a/pkg/util/safeelf/elf.go b/pkg/util/safeelf/elf.go new file mode 100644 index 0000000000000..0b94bbb956046 --- /dev/null +++ b/pkg/util/safeelf/elf.go @@ -0,0 +1,136 @@ +// This file is licensed under the MIT License. +// +// Copyright (c) 2017 Nathan Sweet +// Copyright (c) 2018, 2019 Cloudflare +// Copyright (c) 2019 Authors of Cilium +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// Package safeelf provides safe (from panics) wrappers around ELF parsing +package safeelf + +import ( + "debug/dwarf" + "debug/elf" //nolint:depguard + "fmt" + "io" +) + +// File is a safe wrapper around *elf.File that handles any panics in parsing +type File struct { + *elf.File +} + +// NewFile reads an ELF safely. +// +// Any panic during parsing is turned into an error. This is necessary since +// there are a bunch of unfixed bugs in debug/elf. +// +// https://github.com/golang/go/issues?q=is%3Aissue+is%3Aopen+debug%2Felf+in%3Atitle +func NewFile(r io.ReaderAt) (safe *File, err error) { + defer func() { + r := recover() + if r == nil { + return + } + + safe = nil + err = fmt.Errorf("reading ELF file panicked: %s", r) + }() + + file, err := elf.NewFile(r) + if err != nil { + return nil, err + } + + return &File{file}, nil +} + +// Open reads an ELF from a file. +// +// It works like NewFile, with the exception that safe.Close will +// close the underlying file. +func Open(path string) (safe *File, err error) { + defer func() { + r := recover() + if r == nil { + return + } + + safe = nil + err = fmt.Errorf("reading ELF file panicked: %s", r) + }() + + file, err := elf.Open(path) + if err != nil { + return nil, err + } + + return &File{file}, nil +} + +// Symbols is the safe version of elf.File.Symbols. +func (se *File) Symbols() (syms []Symbol, err error) { + defer func() { + r := recover() + if r == nil { + return + } + + syms = nil + err = fmt.Errorf("reading ELF symbols panicked: %s", r) + }() + + syms, err = se.File.Symbols() + return +} + +// DynamicSymbols is the safe version of elf.File.DynamicSymbols. +func (se *File) DynamicSymbols() (syms []Symbol, err error) { + defer func() { + r := recover() + if r == nil { + return + } + + syms = nil + err = fmt.Errorf("reading ELF dynamic symbols panicked: %s", r) + }() + + syms, err = se.File.DynamicSymbols() + return +} + +// DWARF is the safe version of elf.File.DWARF. +func (se *File) DWARF() (d *dwarf.Data, err error) { + defer func() { + r := recover() + if r == nil { + return + } + + d = nil + err = fmt.Errorf("reading ELF DWARF panicked: %s", r) + }() + + d, err = se.File.DWARF() + return +} diff --git a/pkg/util/safeelf/types.go b/pkg/util/safeelf/types.go new file mode 100644 index 0000000000000..dab4baca56bb5 --- /dev/null +++ b/pkg/util/safeelf/types.go @@ -0,0 +1,44 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024-present Datadog, Inc. + +//nolint:revive +package safeelf + +import "debug/elf" //nolint:depguard + +type Prog = elf.Prog +type Symbol = elf.Symbol +type Section = elf.Section +type SectionType = elf.SectionType +type SectionIndex = elf.SectionIndex + +var ErrNoSymbols = elf.ErrNoSymbols + +const SHF_ALLOC = elf.SHF_ALLOC +const SHF_EXECINSTR = elf.SHF_EXECINSTR + +const SHT_SYMTAB = elf.SHT_SYMTAB +const SHT_DYNSYM = elf.SHT_DYNSYM + +const ET_EXEC = elf.ET_EXEC +const ET_DYN = elf.ET_DYN + +const PT_LOAD = elf.PT_LOAD +const PT_TLS = elf.PT_TLS + +const EM_X86_64 = elf.EM_X86_64 +const EM_AARCH64 = elf.EM_AARCH64 + +const Sym32Size = elf.Sym32Size +const Sym64Size = elf.Sym64Size + +const ELFCLASS32 = elf.ELFCLASS32 +const ELFCLASS64 = elf.ELFCLASS64 + +const PF_X = elf.PF_X +const PF_W = elf.PF_W + +const STB_GLOBAL = elf.STB_GLOBAL +const STT_FUNC = elf.STT_FUNC diff --git a/test/new-e2e/system-probe/test-json-review/testowners.go b/test/new-e2e/system-probe/test-json-review/testowners.go index 5677efe029ce0..0ec0165d55488 100644 --- a/test/new-e2e/system-probe/test-json-review/testowners.go +++ b/test/new-e2e/system-probe/test-json-review/testowners.go @@ -8,7 +8,7 @@ package main import ( - "debug/elf" + "debug/elf" //nolint:depguard "debug/gosym" "fmt" "io"