-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added support to read TLS traffic in go binaries (#234)
Co-authored-by: Ori Shavit <[email protected]>
- Loading branch information
1 parent
a10cd0b
commit cd423a9
Showing
21 changed files
with
1,388 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
FROM --platform=$TARGETPLATFORM golang:1.22.1 AS ebpf-buildenv | ||
|
||
WORKDIR /src | ||
COPY go.mod go.sum ./ | ||
|
||
RUN apt-get update | ||
RUN apt-get install -y clang llvm libelf-dev libbpf-dev linux-headers-generic | ||
RUN ln -sf /usr/include/$(uname -m)-linux-gnu/asm /usr/include/asm | ||
RUN go mod download | ||
|
||
COPY ebpf/ ./ebpf/ | ||
|
||
# Generate ebpf code | ||
ARG TARGETARCH | ||
RUN GOARCH=$TARGETARCH go generate -tags ebpf ./ebpf/... | ||
|
||
FROM quay.io/bpfman/bpfman AS bpfman | ||
COPY --from=ebpf-buildenv /src/ebpf/ /otterize/ebpf/ | ||
|
||
ENTRYPOINT ["./bpfman-rpc", "--timeout=0"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package bininfo | ||
|
||
import ( | ||
"debug/elf" | ||
"errors" | ||
) | ||
|
||
// ErrUnsupportedArch is returned when an architecture given as a parameter is not supported. | ||
var ErrUnsupportedArch = errors.New("got unsupported arch") | ||
|
||
// Arch only includes go architectures that we support in the ebpf code. | ||
type Arch string | ||
|
||
const ( | ||
ArchX86_64 Arch = "amd64" | ||
ArchARM64 Arch = "arm64" | ||
) | ||
|
||
// GetArchitecture returns supported Go architecture for the given ELF file. | ||
func GetArchitecture(elfFile *elf.File) (Arch, error) { | ||
switch elfFile.FileHeader.Machine { | ||
case elf.EM_X86_64: | ||
return ArchX86_64, nil | ||
case elf.EM_AARCH64: | ||
return ArchARM64, nil | ||
} | ||
|
||
return "", ErrUnsupportedArch | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package bininfo | ||
|
||
import ( | ||
"debug/buildinfo" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
) | ||
|
||
type SourceLanguage string | ||
|
||
const ( | ||
GoLang SourceLanguage = "golang" | ||
Python SourceLanguage = "python" | ||
Binary SourceLanguage = "binary" | ||
) | ||
|
||
func GetSourceLanguage(path string, f *os.File) SourceLanguage { | ||
if isGoBinary(f) { | ||
return GoLang | ||
} | ||
if isPythonCommand(path) { | ||
return Python | ||
|
||
} | ||
|
||
return Binary | ||
} | ||
|
||
func isGoBinary(f *os.File) bool { | ||
_, err := buildinfo.Read(f) | ||
return err == nil | ||
} | ||
|
||
func isPythonCommand(path string) bool { | ||
// Resolve the symlink to the actual executable path | ||
actualPath, err := os.Readlink(path) | ||
if err != nil { | ||
return false | ||
} | ||
|
||
// Check if the actual path is a python binary - path will be something along /usr/bin/python3 | ||
_, file := filepath.Split(actualPath) | ||
return strings.Contains(file, "python") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
// Some functionality was copied from the Datadog-agent repository. | ||
// Source: https://github.com/DataDog/datadog-agent | ||
// Original license applies: Apache License Version 2.0. | ||
|
||
package bintools | ||
|
||
import ( | ||
"debug/elf" | ||
"errors" | ||
"fmt" | ||
"github.com/otterize/network-mapper/src/bintools/bininfo" | ||
) | ||
|
||
// GetELFSymbolsByName retrieves ELF symbols by name and returns them in a map. | ||
func GetELFSymbolsByName(elfFile *elf.File) (map[string]elf.Symbol, error) { | ||
symbolMap := make(map[string]elf.Symbol) | ||
|
||
// Retrieve symbols from the .symtab section (static symbols) | ||
if symbols, err := elfFile.Symbols(); err == nil { | ||
for _, sym := range symbols { | ||
symbolMap[sym.Name] = sym | ||
} | ||
} | ||
|
||
// Retrieve symbols from the .dynsym section (dynamic symbols) | ||
if dynSymbols, err := elfFile.DynamicSymbols(); err == nil { | ||
for _, sym := range dynSymbols { | ||
symbolMap[sym.Name] = sym | ||
} | ||
} | ||
|
||
return symbolMap, nil | ||
} | ||
|
||
// SymbolToOffset returns the offset of the given symbol name in the given elf file. | ||
func SymbolToOffset(f *elf.File, symbol elf.Symbol) (uint32, error) { | ||
if f == nil { | ||
return 0, errors.New("got nil elf file") | ||
} | ||
|
||
var sectionsToSearchForSymbol []*elf.Section | ||
|
||
for i := range f.Sections { | ||
if f.Sections[i].Flags == elf.SHF_ALLOC+elf.SHF_EXECINSTR { | ||
sectionsToSearchForSymbol = append(sectionsToSearchForSymbol, f.Sections[i]) | ||
} | ||
} | ||
|
||
if len(sectionsToSearchForSymbol) == 0 { | ||
return 0, fmt.Errorf("symbol %q not found in file - no sections to search", symbol) | ||
} | ||
|
||
var executableSection *elf.Section | ||
|
||
// Find what section the symbol is in by checking the executable section's addr space. | ||
for m := range sectionsToSearchForSymbol { | ||
sectionStart := sectionsToSearchForSymbol[m].Addr | ||
sectionEnd := sectionStart + sectionsToSearchForSymbol[m].Size | ||
if symbol.Value >= sectionStart && symbol.Value < sectionEnd { | ||
executableSection = sectionsToSearchForSymbol[m] | ||
break | ||
} | ||
} | ||
|
||
if executableSection == nil { | ||
return 0, errors.New("could not find symbol in executable sections of binary") | ||
} | ||
|
||
// Calculate the Address of the symbol in the executable section | ||
return uint32(symbol.Value - executableSection.Addr + executableSection.Offset), nil | ||
} | ||
|
||
// FindReturnLocations returns the offsets of all the returns of the given func (sym) with the given offset. | ||
func FindReturnLocations(elfFile *elf.File, arch bininfo.Arch, sym elf.Symbol, functionOffset uint64) ([]uint64, error) { | ||
textSection := elfFile.Section(".text") | ||
if textSection == nil { | ||
return nil, fmt.Errorf("no %q section found in binary file", ".text") | ||
} | ||
|
||
switch arch { | ||
case bininfo.ArchX86_64: | ||
return ScanFunction(textSection, sym, functionOffset, FindX86_64ReturnInstructions) | ||
case bininfo.ArchARM64: | ||
return ScanFunction(textSection, sym, functionOffset, FindARM64ReturnInstructions) | ||
default: | ||
return nil, bininfo.ErrUnsupportedArch | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package gobin | ||
|
||
import ( | ||
"debug/buildinfo" | ||
"fmt" | ||
delve "github.com/go-delve/delve/pkg/goversion" | ||
"log" | ||
"os" | ||
) | ||
|
||
// FindGoVersion attempts to determine the Go version | ||
func FindGoVersion(f *os.File) (gover delve.GoVersion, raw string, err error) { | ||
info, err := buildinfo.Read(f) | ||
if err != nil { | ||
log.Fatalf("error reading build info: %v", err) | ||
} | ||
|
||
versionString := info.GoVersion | ||
gover, ok := delve.Parse(versionString) | ||
if !ok { | ||
return gover, "", fmt.Errorf("error parsing go version %s", versionString) | ||
} | ||
|
||
return gover, versionString, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package gobin | ||
|
||
import ( | ||
"github.com/otterize/network-mapper/src/bintools/bininfo" | ||
) | ||
|
||
// FunctionMetadata used to attach a uprobe to a function. | ||
type FunctionMetadata struct { | ||
EntryAddress uint64 | ||
ReturnAddresses []uint64 | ||
} | ||
|
||
type GoBinaryInfo struct { | ||
Arch bininfo.Arch | ||
GoVersion string | ||
Functions map[string]FunctionMetadata | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package bintools | ||
|
||
import ( | ||
"debug/elf" | ||
"fmt" | ||
delve "github.com/go-delve/delve/pkg/goversion" | ||
"github.com/otterize/network-mapper/src/bintools/bininfo" | ||
"github.com/otterize/network-mapper/src/bintools/gobin" | ||
"log" | ||
"os" | ||
) | ||
|
||
func ProcessGoBinary(binPath string, functions []string) (res *gobin.GoBinaryInfo, err error) { | ||
f, err := os.Open(binPath) | ||
if err != nil { | ||
log.Fatalf("could not open file %s, %w", binPath, err) | ||
} | ||
defer func(f *os.File) { | ||
cErr := f.Close() | ||
if cErr == nil { | ||
err = cErr | ||
} | ||
}(f) | ||
|
||
// Parse the ELF file | ||
elfFile, err := elf.NewFile(f) | ||
if err != nil { | ||
log.Fatalf("file %s could not be parsed as an ELF file: %w", binPath, err) | ||
} | ||
|
||
// Determine the architecture of the binary | ||
arch, err := bininfo.GetArchitecture(elfFile) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Determine the Go version | ||
goVersion, rawVersion, err := gobin.FindGoVersion(f) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// We only want to support Go versions 1.18 and above - where the ABI is register based | ||
if !goVersion.AfterOrEqual(delve.GoVersion{Major: 1, Minor: 18}) { | ||
return nil, fmt.Errorf("unsupported go version %s, only versions 1.18 and above are supported", rawVersion) | ||
} | ||
|
||
// Get symbols | ||
symbols, err := GetELFSymbolsByName(elfFile) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not get symbols from ELF file: %w", err) | ||
} | ||
|
||
functionMetadata := make(map[string]gobin.FunctionMetadata, len(functions)) | ||
|
||
for _, funcName := range functions { | ||
offset, err := SymbolToOffset(elfFile, symbols[funcName]) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not find location for function %q: %w", funcName, err) | ||
} | ||
|
||
// Find return locations | ||
var returnLocations []uint64 | ||
symbol, ok := symbols[funcName] | ||
if !ok { | ||
return nil, fmt.Errorf("could not find function %q in symbols", funcName) | ||
} | ||
|
||
locations, err := FindReturnLocations(elfFile, arch, symbol, uint64(offset)) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not find return locations for function %q: %w", funcName, err) | ||
} | ||
|
||
returnLocations = locations | ||
|
||
functionMetadata[funcName] = gobin.FunctionMetadata{ | ||
EntryAddress: uint64(offset), | ||
ReturnAddresses: returnLocations, | ||
} | ||
} | ||
|
||
return &gobin.GoBinaryInfo{ | ||
Arch: arch, | ||
GoVersion: rawVersion, | ||
Functions: functionMetadata, | ||
}, nil | ||
} |
Oops, something went wrong.