kubeshark/tap/tlstapper/tls_tapper.go
M. Mert Yıldıran 52c9251c00
Add ABI0 support to Go crypto/tls eBPF tracer (#1169)
* Determine the Go ABI and get `goid` offset from DWARF

* Add `ABI` enum and morph the function according to the detected ABI

* Pass `goid` offset to an eBPF map to retrieve it in eBPF context

* Add `vmlinux.h` and implement `get_goid_from_thread_local_storage`

* Fix BPF verifier errors

* Update the comments

* Add `go_abi_0.h` and implement `ABI0` specific reads for `arm64`

* Upgrade `github.com/cilium/ebpf` to `v0.9.0`

* Add a comment

* Add macros for x86 specific parts

* Update `x86.o`

* Fix the map key type

* Add `user_pt_regs`

* Update arm64 object file

* Fix the version detection logic

* Add `getGStructOffset` method

* Define `goid_offsets`, `goid_offsets_map` structs and pass the offsets correctly

* Fix the `net.TCPConn` and buffer addresses for `ABI0`

* Remove comment

* Fix the issues for arm64 build

* Update x86.o

* Revert "Fix the issues for arm64 build"

This reverts commit 48b041b1b6.

* Revert `user_pt_regs`

* Add `vmlinux` directory

* Fix the `build.sh` and `Dockerfile`

* Add vmlinux_arm64.h

* Disable `get_goid_from_thread_local_storage` on ARM64 with a macro

* Update x86.o

* Update arm64.o

* x86

* arm64

* Fix the cross-compilation issue from x86 to arm64

* Fix the same thing for x86

* Use `BPF_CORE_READ` macro instead of `bpf_ringbuf_reserve` to support kernel versions older than 5.8

Also;
Add legacy version of thread_struct: thread_struct___v46
Build an additional object file for the kernel versions older than or equal to 4.6 and load them accordingly.
Add github.com/moby/moby

* Make #define directives more definitive

* Select the x86 and arm64 versions of `vmlinux.h` using macros

* Put `goid` offsets into the map before installing `uprobe`(s)

* arm64

* #run_acceptance_tests

* Remove a forgotten `fmt.Printf`

* Log the detected Linux kernel version
2022-07-05 14:35:30 +03:00

246 lines
6.1 KiB
Go

package tlstapper
import (
"strconv"
"sync"
"github.com/cilium/ebpf/rlimit"
"github.com/go-errors/errors"
"github.com/moby/moby/pkg/parsers/kernel"
"github.com/up9inc/mizu/logger"
"github.com/up9inc/mizu/tap/api"
)
const GlobalTapPid = 0
// TODO: cilium/ebpf does not support .kconfig Therefore; for now, we build object files per kernel version.
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go@v0.9.0 -target $BPF_TARGET -cflags $BPF_CFLAGS -type tls_chunk -type goid_offsets tlsTapper bpf/tls_tapper.c
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go@v0.9.0 -target $BPF_TARGET -cflags "${BPF_CFLAGS} -DKERNEL_BEFORE_4_6" -type tls_chunk -type goid_offsets tlsTapper46 bpf/tls_tapper.c
type TlsTapper struct {
bpfObjects tlsTapperObjects
syscallHooks syscallHooks
sslHooksStructs []sslHooks
goHooksStructs []goHooks
poller *tlsPoller
bpfLogger *bpfLogger
registeredPids sync.Map
}
func (t *TlsTapper) Init(chunksBufferSize int, logBufferSize int, procfs string, extension *api.Extension) error {
logger.Log.Infof("Initializing tls tapper (chunksSize: %d) (logSize: %d)", chunksBufferSize, logBufferSize)
var err error
err = setupRLimit()
if err != nil {
return err
}
var kernelVersion *kernel.VersionInfo
kernelVersion, err = kernel.GetKernelVersion()
if err != nil {
return err
}
logger.Log.Infof("Detected Linux kernel version: %s", kernelVersion)
t.bpfObjects = tlsTapperObjects{}
// TODO: cilium/ebpf does not support .kconfig Therefore; for now, we load object files according to kernel version.
if kernel.CompareKernelVersion(*kernelVersion, kernel.VersionInfo{Kernel: 4, Major: 6, Minor: 0}) < 1 {
if err := loadTlsTapper46Objects(&t.bpfObjects, nil); err != nil {
return errors.Wrap(err, 0)
}
} else {
if err := loadTlsTapperObjects(&t.bpfObjects, nil); err != nil {
return errors.Wrap(err, 0)
}
}
t.syscallHooks = syscallHooks{}
if err := t.syscallHooks.installSyscallHooks(&t.bpfObjects); err != nil {
return err
}
t.sslHooksStructs = make([]sslHooks, 0)
t.bpfLogger = newBpfLogger()
if err := t.bpfLogger.init(&t.bpfObjects, logBufferSize); err != nil {
return err
}
t.poller, err = newTlsPoller(t, extension, procfs)
if err != nil {
return err
}
return t.poller.init(&t.bpfObjects, chunksBufferSize)
}
func (t *TlsTapper) Poll(emitter api.Emitter, options *api.TrafficFilteringOptions, streamsMap api.TcpStreamMap) {
t.poller.poll(emitter, options, streamsMap)
}
func (t *TlsTapper) PollForLogging() {
t.bpfLogger.poll()
}
func (t *TlsTapper) GlobalSSLLibTap(sslLibrary string) error {
return t.tapSSLLibPid(GlobalTapPid, sslLibrary, api.UnknownNamespace)
}
func (t *TlsTapper) GlobalGoTap(procfs string, pid string) error {
_pid, err := strconv.Atoi(pid)
if err != nil {
return err
}
return t.tapGoPid(procfs, uint32(_pid), api.UnknownNamespace)
}
func (t *TlsTapper) AddSSLLibPid(procfs string, pid uint32, namespace string) error {
sslLibrary, err := findSsllib(procfs, pid)
if err != nil {
logger.Log.Infof("PID skipped no libssl.so found (pid: %d) %v", pid, err)
return nil // hide the error on purpose, it's OK for a process to not use libssl.so
}
return t.tapSSLLibPid(pid, sslLibrary, namespace)
}
func (t *TlsTapper) AddGoPid(procfs string, pid uint32, namespace string) error {
return t.tapGoPid(procfs, pid, namespace)
}
func (t *TlsTapper) RemovePid(pid uint32) error {
logger.Log.Infof("Removing PID (pid: %v)", pid)
pids := t.bpfObjects.tlsTapperMaps.PidsMap
if err := pids.Delete(pid); err != nil {
return errors.Wrap(err, 0)
}
return nil
}
func (t *TlsTapper) ClearPids() {
t.poller.clearPids()
t.registeredPids.Range(func(key, v interface{}) bool {
pid := key.(uint32)
if pid == GlobalTapPid {
return true
}
if err := t.RemovePid(pid); err != nil {
LogError(err)
}
t.registeredPids.Delete(key)
return true
})
}
func (t *TlsTapper) Close() []error {
returnValue := make([]error, 0)
if err := t.bpfObjects.Close(); err != nil {
returnValue = append(returnValue, err)
}
returnValue = append(returnValue, t.syscallHooks.close()...)
for _, sslHooks := range t.sslHooksStructs {
returnValue = append(returnValue, sslHooks.close()...)
}
for _, goHooks := range t.goHooksStructs {
returnValue = append(returnValue, goHooks.close()...)
}
if err := t.bpfLogger.close(); err != nil {
returnValue = append(returnValue, err)
}
if err := t.poller.close(); err != nil {
returnValue = append(returnValue, err)
}
return returnValue
}
func setupRLimit() error {
err := rlimit.RemoveMemlock()
if err != nil {
return errors.Wrap(err, 0)
}
return nil
}
func (t *TlsTapper) tapSSLLibPid(pid uint32, sslLibrary string, namespace string) error {
newSsl := sslHooks{}
if err := newSsl.installUprobes(&t.bpfObjects, sslLibrary); err != nil {
return err
}
logger.Log.Infof("Tapping TLS (pid: %v) (sslLibrary: %v)", pid, sslLibrary)
t.sslHooksStructs = append(t.sslHooksStructs, newSsl)
t.poller.addPid(pid, namespace)
pids := t.bpfObjects.tlsTapperMaps.PidsMap
if err := pids.Put(pid, uint32(1)); err != nil {
return errors.Wrap(err, 0)
}
t.registeredPids.Store(pid, true)
return nil
}
func (t *TlsTapper) tapGoPid(procfs string, pid uint32, namespace string) error {
exePath, err := findLibraryByPid(procfs, pid, "")
if err != nil {
return err
}
hooks := goHooks{}
if err := hooks.installUprobes(&t.bpfObjects, exePath); err != nil {
logger.Log.Infof("PID skipped not a Go binary or symbol table is stripped (pid: %v) %v", pid, exePath)
return nil // hide the error on purpose, its OK for a process to be not a Go binary or stripped Go binary
}
logger.Log.Infof("Tapping TLS (pid: %v) (Go: %v)", pid, exePath)
t.goHooksStructs = append(t.goHooksStructs, hooks)
t.poller.addPid(pid, namespace)
pids := t.bpfObjects.tlsTapperMaps.PidsMap
if err := pids.Put(pid, uint32(1)); err != nil {
return errors.Wrap(err, 0)
}
t.registeredPids.Store(pid, true)
return nil
}
func LogError(err error) {
var e *errors.Error
if errors.As(err, &e) {
logger.Log.Errorf("Error: %v", e.ErrorStack())
} else {
logger.Log.Errorf("Error: %v", err)
}
}