Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attempt to run without syscall tracepoints, if possible. #175

Merged
merged 9 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions internal/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,6 @@ func (c *Controller) Start(ctx context.Context) error {
return fmt.Errorf("failed to probe eBPF syscall: %w", err)
}

if err := tracer.ProbeTracepoint(); err != nil {
return fmt.Errorf("failed to probe tracepoint: %w", err)
}

presentCores, err := numcpus.GetPresent()
if err != nil {
return fmt.Errorf("failed to read CPU file: %w", err)
Expand Down
10 changes: 0 additions & 10 deletions support/ebpf/interpreter_dispatcher.ebpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -243,13 +243,3 @@ char _license[] SEC("license") = "GPL";
// this number will be interpreted by the elf loader
// to set the current running kernel version
u32 _version SEC("version") = 0xFFFFFFFE;

// tracepoint__sys_enter_read serves as dummy tracepoint so we can check if tracepoints are
// enabled and we can make use of them.
// The argument that is passed to the tracepoint for the sys_enter_read hook is described in sysfs
// at /sys/kernel/debug/tracing/events/syscalls/sys_enter_read/format.
SEC("tracepoint/syscalls/sys_enter_read")
int tracepoint__sys_enter_read(void *ctx) {
printt("The read tracepoint was triggered");
return 0;
}
Binary file modified support/ebpf/tracer.ebpf.release.amd64
Binary file not shown.
Binary file modified support/ebpf/tracer.ebpf.release.arm64
Binary file not shown.
43 changes: 0 additions & 43 deletions support/ebpf_integration_test.go

This file was deleted.

24 changes: 11 additions & 13 deletions tracer/maccess.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,29 @@
package tracer // import "go.opentelemetry.io/ebpf-profiler/tracer"

import (
"errors"
"fmt"
"runtime"

cebpf "github.com/cilium/ebpf"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/ebpf-profiler/libpf"
"go.opentelemetry.io/ebpf-profiler/maccess"
)

// checkForMmaccessPatch validates if a Linux kernel function is patched by
// extracting the kernel code of the function and analyzing it.
func checkForMaccessPatch(coll *cebpf.CollectionSpec, maps map[string]*cebpf.Map,
kernelSymbols *libpf.SymbolMap) bool {
kernelSymbols *libpf.SymbolMap) error {
faultyFunc, err := kernelSymbols.LookupSymbol(
libpf.SymbolName("copy_from_user_nofault"))
if err != nil {
log.Warnf("Failed to look up Linux kernel symbol "+
return fmt.Errorf("failed to look up Linux kernel symbol "+
"'copy_from_user_nofault': %v", err)
return false
}

code, err := loadKernelCode(coll, maps, faultyFunc.Address)
if err != nil {
log.Warnf("Failed to load code for %s: %v", faultyFunc.Name, err)
return false
return fmt.Errorf("failed to load kernel code for %s: %v", faultyFunc.Name, err)
}

newCheckFunc, err := kernelSymbols.LookupSymbol(
Expand All @@ -43,20 +42,19 @@ func checkForMaccessPatch(coll *cebpf.CollectionSpec, maps map[string]*cebpf.Map
Address: 0,
}
} else {
log.Warnf("Failed to look up Linux kernel symbol 'nmi_uaccess_okay': %v",
err)

// Without the symbol information, we can not continue with checking the
// function and determine whether it got patched.
return false
return fmt.Errorf("failed to look up Linux kernel symbol 'nmi_uaccess_okay': %v", err)
}
}

patched, err := maccess.CopyFromUserNoFaultIsPatched(code, uint64(faultyFunc.Address),
uint64(newCheckFunc.Address))
if err != nil {
log.Warnf("Failed to check if %s is patched: %v", faultyFunc.Name, err)
return false
return fmt.Errorf("failed to check if %s is patched: %v", faultyFunc.Name, err)
}
if !patched {
return errors.New("kernel is not patched")
}
return patched
return nil
}
100 changes: 0 additions & 100 deletions tracer/probe_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,8 @@ import (
"bytes"
"errors"
"fmt"
"os"
"strings"

"go.opentelemetry.io/ebpf-profiler/rlimit"
"go.opentelemetry.io/ebpf-profiler/util"

"golang.org/x/sys/unix"

cebpf "github.com/cilium/ebpf"
"github.com/cilium/ebpf/asm"
log "github.com/sirupsen/logrus"
)

// ProbeBPFSyscall checks if the syscall EBPF is available on the system.
Expand All @@ -32,16 +23,6 @@ func ProbeBPFSyscall() error {
return nil
}

// getTracepointID returns the system specific tracepoint ID for a given tracepoint.
func getTracepointID(tracepoint string) (uint64, error) {
id, err := os.ReadFile("/sys/kernel/debug/tracing/events/syscalls/" + tracepoint + "/id")
if err != nil {
return 0, fmt.Errorf("failed to read tracepoint ID for %s: %v", tracepoint, err)
}
tid := util.DecToUint64(strings.TrimSpace(string(id)))
return tid, nil
}

// GetCurrentKernelVersion returns the major, minor and patch version of the kernel of the host
// from the utsname struct.
func GetCurrentKernelVersion() (major, minor, patch uint32, err error) {
Expand All @@ -52,84 +33,3 @@ func GetCurrentKernelVersion() (major, minor, patch uint32, err error) {
_, _ = fmt.Fscanf(bytes.NewReader(uname.Release[:]), "%d.%d.%d", &major, &minor, &patch)
return major, minor, patch, nil
}

// ProbeTracepoint checks if tracepoints are available on the system, so we can attach
// our eBPF code there.
func ProbeTracepoint() error {
ins := asm.Instructions{
// set exit code to 0
asm.Mov.Imm(asm.R0, 0),
asm.Return(),
}

// The check of the kernel version was removed with
// commit 6c4fc209fcf9d27efbaa48368773e4d2bfbd59aa. So kernel < 4.20
// need to set the kernel version to not be rejected by the verifier.
major, minor, patch, err := GetCurrentKernelVersion()
if err != nil {
return err
}
kernelVersion := util.VersionUint(major, minor, patch)
restoreRlimit, err := rlimit.MaximizeMemlock()
if err != nil {
return fmt.Errorf("failed to increase rlimit: %v", err)
}
defer restoreRlimit()

prog, err := cebpf.NewProgram(&cebpf.ProgramSpec{
Type: cebpf.TracePoint,
License: "GPL",
Instructions: ins,
KernelVersion: kernelVersion,
})
if err != nil {
return fmt.Errorf("failed to create tracepoint_probe: %v", err)
}
defer prog.Close()

var tid uint64
// sys_enter_mmap is the first tracepoint we have used
tid, err = getTracepointID("sys_enter_mmap")
if err != nil {
return fmt.Errorf("failed to get id for tracepoint: %v", err)
}

attr := unix.PerfEventAttr{
Type: unix.PERF_TYPE_TRACEPOINT,
Config: tid,
Sample_type: unix.PERF_SAMPLE_RAW,
Sample: 1,
Wakeup: 1,
}

pfd, err := unix.PerfEventOpen(&attr, -1, 0, -1, unix.PERF_FLAG_FD_CLOEXEC)
if err != nil {
return fmt.Errorf("unable to open perf events: %v", err)
}
defer func() {
if err = unix.Close(pfd); err != nil {
log.Fatalf("Failed to close tracepoint sys_enter_mmap probe: %v", err)
}
}()

if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(pfd),
unix.PERF_EVENT_IOC_ENABLE, 0); errno != 0 {
return fmt.Errorf("unable to set up perf events: %d", errno)
}

if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(pfd),
unix.PERF_EVENT_IOC_SET_BPF, uintptr(prog.FD())); errno != 0 {
return fmt.Errorf("unable to attach bpf program to perf event %d: %d", tid, errno)
}

// The test was successful, so disable the tracepoint and clean up.
// In kernel < 4.15 we can not attach multiple eBPF programs to the same tracepoint.
// This was changed in the kernel with commit e87c6bc3852b981e71c757be20771546ce9f76f3.
// So it is important not only to disable the tracepoint but also close its
// perf event file descriptor.
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(pfd),
unix.PERF_EVENT_IOC_DISABLE, 0); errno != 0 {
return fmt.Errorf("unable to disable perf events: %v", err)
}
return nil
}
5 changes: 0 additions & 5 deletions tracer/probe_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,6 @@ func ProbeBPFSyscall() error {
return fmt.Errorf("eBPF is not available on your system %s", runtime.GOOS)
}

// ProbeTracepoint checks if tracepoints are available on the system.
func ProbeTracepoint() error {
return fmt.Errorf("tracepoints are not available on your system %s", runtime.GOOS)
}

// GetCurrentKernelVersion returns an error for OS other than linux.
func GetCurrentKernelVersion() (_, _, _ uint32, err error) {
return 0, 0, 0, fmt.Errorf("kernel version detection is not supported on %s",
Expand Down
4 changes: 4 additions & 0 deletions tracer/systemconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ func executeSystemAnalysisBpfCode(progSpec *cebpf.ProgramSpec, maps map[string]*
func loadKernelCode(coll *cebpf.CollectionSpec, maps map[string]*cebpf.Map,
address libpf.SymbolValue) ([]byte, error) {
code, _, err := executeSystemAnalysisBpfCode(coll.Programs["read_kernel_memory"], maps, address)
if err != nil {
log.Warnf("Failed to load code: %v.\n"+
"Possible reasons include using a kernel without syscall tracepoints enabled.", err)
}
return code, err
}

Expand Down
14 changes: 9 additions & 5 deletions tracer/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -413,11 +413,15 @@ func initializeMapsAndPrograms(includeTracers types.IncludedTracers,
return nil, nil, fmt.Errorf("failed to get kernel version: %v", err)
}
if hasProbeReadBug(major, minor, patch) {
patched := checkForMaccessPatch(coll, ebpfMaps, kernelSymbols)
if !patched {
return nil, nil, fmt.Errorf("your kernel version %d.%d.%d is affected by a Linux "+
"kernel bug that can lead to system freezes, terminating host "+
"agent now to avoid triggering this bug", major, minor, patch)
if err = checkForMaccessPatch(coll, ebpfMaps, kernelSymbols); err != nil {
return nil, nil, fmt.Errorf("your kernel version %d.%d.%d may be "+
"affected by a Linux kernel bug that can lead to system "+
"freezes, terminating host agent now to avoid "+
"triggering this bug.\n"+
"If you are certain your kernel is not affected, "+
"you can override this check at your own risk "+
"with -no-kernel-version-check.\n"+
"Error: %v", major, minor, patch, err)
}
}
}
Expand Down