Skip to content

Commit

Permalink
[USMON-1385] usm: sharedlibraries: Use fexit (DataDog#31717)
Browse files Browse the repository at this point in the history
  • Loading branch information
guyarb authored Jan 2, 2025
1 parent f054ec9 commit 6a79fcb
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 76 deletions.
19 changes: 19 additions & 0 deletions pkg/ebpf/c/bpf_bypass.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,23 @@ static __always_inline typeof(name(0)) ____##name(struct pt_regs *ctx, ##args)
#define BPF_BYPASSABLE_UPROBE(name, args...) BPF_BYPASSABLE_KPROBE(name, ##args)
#define BPF_BYPASSABLE_URETPROBE(name, args...) BPF_BYPASSABLE_KRETPROBE(name, ##args)

/* BPF_BYPASSABLE_PROG is identical to BPF_PROG (bpf_tracing.h), but with a stub (CHECK_BPF_PROGRAM_BYPASSED)
* that checks if the program is bypassed. This is useful for testings, as we want to dynamically control
* the execution of the program.
*/
#define BPF_BYPASSABLE_PROG(name, args...) \
name(unsigned long long *ctx); \
static __always_inline typeof(name(0)) \
____##name(unsigned long long *ctx, ##args); \
typeof(name(0)) name(unsigned long long *ctx) \
{ \
CHECK_BPF_PROGRAM_BYPASSED() \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
return ____##name(___bpf_ctx_cast(args)); \
_Pragma("GCC diagnostic pop") \
} \
static __always_inline typeof(name(0)) \
____##name(unsigned long long *ctx, ##args)

#endif
81 changes: 51 additions & 30 deletions pkg/network/ebpf/c/shared-libraries/probes.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,42 +18,35 @@ static __always_inline void fill_path_safe(lib_path_t *path, const char *path_ar
}
}

static __always_inline void do_sys_open_helper_enter(const char *filename) {
lib_path_t path = { 0 };
if (bpf_probe_read_user_with_telemetry(path.buf, sizeof(path.buf), filename) >= 0) {
static __always_inline bool fill_lib_path(lib_path_t *path, const char *path_argument) {
path->pid = GET_USER_MODE_PID(bpf_get_current_pid_tgid());
if (bpf_probe_read_user_with_telemetry(path->buf, sizeof(path->buf), path_argument) >= 0) {
// Find the null character and clean up the garbage following it
#pragma unroll
for (int i = 0; i < LIB_PATH_MAX_SIZE; i++) {
if (path.buf[i] == 0) {
path.len = i;
if (path->buf[i] == 0) {
path->len = i;
break;
}
}
} else {
fill_path_safe(&path, filename);
}

// Bail out if the path size is larger than our buffer
if (!path.len) {
return;
fill_path_safe(path, path_argument);
}

u64 pid_tgid = bpf_get_current_pid_tgid();
path.pid = GET_USER_MODE_PID(pid_tgid);
bpf_map_update_with_telemetry(open_at_args, &pid_tgid, &path, BPF_ANY);
return;
return path->len > 0;
}

static __always_inline void do_sys_open_helper_exit(exit_sys_ctx *args) {
u64 pid_tgid = bpf_get_current_pid_tgid();

// If file couldn't be opened, bail out
if (args->ret < 0) {
goto cleanup;
static __always_inline void do_sys_open_helper_enter(const char *filename) {
lib_path_t path = { 0 };
if (fill_lib_path(&path, filename)) {
u64 pid_tgid = bpf_get_current_pid_tgid();
bpf_map_update_with_telemetry(open_at_args, &pid_tgid, &path, BPF_ANY);
}
return;
}

lib_path_t *path = bpf_map_lookup_elem(&open_at_args, &pid_tgid);
if (path == NULL) {
static __always_inline void push_event_if_relevant(void *ctx, lib_path_t *path, long return_code) {
if (return_code < 0) {
return;
}

Expand All @@ -79,25 +72,33 @@ static __always_inline void do_sys_open_helper_exit(exit_sys_ctx *args) {
}
}
if (!is_shared_library) {
goto cleanup;
return;
}

u64 crypto_libset_enabled = 0;
LOAD_CONSTANT("crypto_libset_enabled", crypto_libset_enabled);

if (crypto_libset_enabled && (match6chars(0, 'l', 'i', 'b', 's', 's', 'l') || match6chars(0, 'c', 'r', 'y', 'p', 't', 'o') || match6chars(0, 'g', 'n', 'u', 't', 'l', 's'))) {
bpf_perf_event_output((void *)args, &crypto_shared_libraries, BPF_F_CURRENT_CPU, path, sizeof(lib_path_t));
goto cleanup;
bpf_perf_event_output(ctx, &crypto_shared_libraries, BPF_F_CURRENT_CPU, path, sizeof(lib_path_t));
return;
}

u64 gpu_libset_enabled = 0;
LOAD_CONSTANT("gpu_libset_enabled", gpu_libset_enabled);

if (gpu_libset_enabled && (match6chars(0, 'c', 'u', 'd', 'a', 'r', 't'))) {
bpf_perf_event_output((void *)args, &gpu_shared_libraries, BPF_F_CURRENT_CPU, path, sizeof(lib_path_t));
bpf_perf_event_output(ctx, &gpu_shared_libraries, BPF_F_CURRENT_CPU, path, sizeof(lib_path_t));
}
}

static __always_inline void do_sys_open_helper_exit(exit_sys_ctx *args) {
u64 pid_tgid = bpf_get_current_pid_tgid();
lib_path_t *path = bpf_map_lookup_elem(&open_at_args, &pid_tgid);
if (path == NULL) {
return;
}

cleanup:
push_event_if_relevant(args, path, args->ret);
bpf_map_delete_elem(&open_at_args, &pid_tgid);
return;
}
Expand Down Expand Up @@ -153,8 +154,15 @@ int tracepoint__syscalls__sys_exit_openat(exit_sys_ctx *args) {
SEC("tracepoint/syscalls/sys_enter_openat2")
int tracepoint__syscalls__sys_enter_openat2(enter_sys_openat2_ctx *args) {
CHECK_BPF_PROGRAM_BYPASSED()
// Unlike the other variants, openat2(2) has the flags embedded inside the
// how argument; we don't bother trying to accessing it for now.

if (args->how != NULL) {
__u64 flags = 0;
bpf_probe_read_user(&flags, sizeof(flags), &args->how->flags);
if (should_ignore_flags(flags)) {
return 0;
}
}

do_sys_open_helper_enter(args->filename);
return 0;
}
Expand All @@ -166,4 +174,17 @@ int tracepoint__syscalls__sys_exit_openat2(exit_sys_ctx *args) {
return 0;
}

SEC("fexit/do_sys_openat2")
int BPF_BYPASSABLE_PROG(do_sys_openat2_exit, int dirfd, const char *pathname, openat2_open_how *how, long ret) {
if (how != NULL && should_ignore_flags(how->flags)) {
return 0;
}

lib_path_t path = { 0 };
if (fill_lib_path(&path, pathname)) {
push_event_if_relevant(ctx, &path, ret);
}
return 0;
}

#endif
8 changes: 7 additions & 1 deletion pkg/network/ebpf/c/shared-libraries/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ typedef struct {
int mode;
} enter_sys_openat_ctx;

typedef struct {
__u64 flags;
__u64 mode;
__u64 resolve;
} openat2_open_how;

typedef struct {
unsigned short common_type;
unsigned char common_flags;
Expand All @@ -46,7 +52,7 @@ typedef struct {

int dfd;
const char* filename;
void *how;
openat2_open_how *how;
size_t usize;
} enter_sys_openat2_ctx;

Expand Down
87 changes: 71 additions & 16 deletions pkg/network/usm/sharedlibraries/ebpf.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ import (
"strings"
"sync"

"github.com/cilium/ebpf"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/features"
"github.com/cilium/ebpf/link"

manager "github.com/DataDog/ebpf-manager"

ddebpf "github.com/DataDog/datadog-agent/pkg/ebpf"
Expand Down Expand Up @@ -105,6 +110,11 @@ type EbpfProgram struct {
// otherwise used to check if the program needs to be stopped and re-started
// when adding new libsets
isInitialized bool

// enabledProbes is a list of the probes that are enabled for the current system.
enabledProbes []manager.ProbeIdentificationPair
// disabledProbes is a list of the probes that are disabled for the current system.
disabledProbes []manager.ProbeIdentificationPair
}

// IsSupported returns true if the shared libraries monitoring is supported on the current system.
Expand Down Expand Up @@ -196,8 +206,8 @@ func (e *EbpfProgram) setupManagerAndPerfHandlers() {
handler.perfHandler = perfHandler
}

probeIDs := getSysOpenHooksIdentifiers()
for _, identifier := range probeIDs {
e.initializeProbes()
for _, identifier := range e.enabledProbes {
mgr.Probes = append(mgr.Probes,
&manager.Probe{
ProbeIdentificationPair: identifier,
Expand Down Expand Up @@ -480,13 +490,16 @@ func (e *EbpfProgram) stopImpl() {
func (e *EbpfProgram) init(buf bytecode.AssetReader, options manager.Options) error {
options.RemoveRlimit = true

for _, probe := range e.Probes {
for _, probe := range e.enabledProbes {
options.ActivatedProbes = append(options.ActivatedProbes,
&manager.ProbeSelector{
ProbeIdentificationPair: probe.ProbeIdentificationPair,
ProbeIdentificationPair: probe,
},
)
}
for _, probe := range e.disabledProbes {
options.ExcludedFunctions = append(options.ExcludedFunctions, probe.EBPFFuncName)
}

var enabledMsgs []string
for libset := range LibsetToLibSuffixes {
Expand Down Expand Up @@ -537,43 +550,85 @@ func (e *EbpfProgram) initPrebuilt() error {
func sysOpenAt2Supported() bool {
missing, err := ddebpf.VerifyKernelFuncs("do_sys_openat2")

if err == nil && len(missing) == 0 {
return true
return err == nil && len(missing) == 0
}

// fexitSupported checks if fexit type of probe is supported on the current host.
// It does this by creating a dummy program that attaches to the given function name, and returns true if it succeeds.
// Method was adapted from the CWS code.
func fexitSupported(funcName string) bool {
if features.HaveProgramType(ebpf.Tracing) != nil {
return false
}

kversion, err := kernel.HostVersion()
spec := &ebpf.ProgramSpec{
Type: ebpf.Tracing,
AttachType: ebpf.AttachTraceFExit,
AttachTo: funcName,
Instructions: asm.Instructions{
asm.LoadImm(asm.R0, 0, asm.DWord),
asm.Return(),
},
}
prog, err := ebpf.NewProgramWithOptions(spec, ebpf.ProgramOptions{
LogDisabled: true,
})
if err != nil {
return false
}
defer prog.Close()

l, err := link.AttachTracing(link.TracingOptions{
Program: prog,
})
if err != nil {
log.Error("could not determine the current kernel version. fallback to do_sys_open")
return false
}
defer l.Close()

return kversion >= kernel.VersionCode(5, 6, 0)
return true
}

// getSysOpenHooksIdentifiers returns the enter and exit tracepoints for supported open*
// system calls.
func getSysOpenHooksIdentifiers() []manager.ProbeIdentificationPair {
// initializedProbes initializes the probes that are enabled for the current system
func (e *EbpfProgram) initializeProbes() {
openat2Supported := sysOpenAt2Supported()
isFexitSupported := fexitSupported("do_sys_openat2")

// Tracing represents fentry/fexit probes.
tracingProbes := []manager.ProbeIdentificationPair{
{
EBPFFuncName: fmt.Sprintf("do_sys_%s_exit", openat2SysCall),
UID: probeUID,
},
}

openatProbes := []string{openatSysCall}
if sysOpenAt2Supported() {
if openat2Supported {
openatProbes = append(openatProbes, openat2SysCall)
}
// amd64 has open(2), arm64 doesn't
if runtime.GOARCH == "amd64" {
openatProbes = append(openatProbes, openSysCall)
}

res := make([]manager.ProbeIdentificationPair, 0, len(traceTypes)*len(openatProbes))
// tp stands for tracepoints, which is the older format of the probes.
tpProbes := make([]manager.ProbeIdentificationPair, 0, len(traceTypes)*len(openatProbes))
for _, probe := range openatProbes {
for _, traceType := range traceTypes {
res = append(res, manager.ProbeIdentificationPair{
tpProbes = append(tpProbes, manager.ProbeIdentificationPair{
EBPFFuncName: fmt.Sprintf("tracepoint__syscalls__sys_%s_%s", traceType, probe),
UID: probeUID,
})
}
}

return res
if isFexitSupported && openat2Supported {
e.enabledProbes = tracingProbes
e.disabledProbes = tpProbes
} else {
e.enabledProbes = tpProbes
e.disabledProbes = tracingProbes
}
}

func getAssetName(module string, debug bool) string {
Expand Down
Loading

0 comments on commit 6a79fcb

Please sign in to comment.