From a67662e48f8afc41e67237e281a0d83cd6ddb4be Mon Sep 17 00:00:00 2001 From: Ofek Shaked <32914127+oshaked1@users.noreply.github.com> Date: Sun, 12 May 2024 14:47:57 +0300 Subject: [PATCH] Add `set_fs_pwd` event (#3919) * Added set_fs_pwd event * Added test for set_fs_pwd * Run make fix-fmt * Added doc for set_fs_pwd event * Added test to PR workflow * Fix test for systems without bpf_probe_read_user_str The bpf_probe_read_user_str helper was added in kernel 5.5, and before that reads from user space were not guaranteed to work if the architecture doesn't support it. In practice, ARM systems couldn't read from user space before this helper was introduced. The test now doesn't expect the unresolved_path (which comes from a userspace read) to conatin anything if the helper doesn't exist. --- .github/workflows/pr.yaml | 1 + docs/docs/events/builtin/extra/set_fs_pwd.md | 34 +++++++ pkg/ebpf/c/common/arch.h | 2 + pkg/ebpf/c/tracee.bpf.c | 24 +++++ pkg/ebpf/c/types.h | 1 + pkg/ebpf/probes/probe_group.go | 1 + pkg/ebpf/probes/probes.go | 1 + pkg/events/core.go | 26 +++++ tests/e2e-inst-signatures/e2e-set_fs_pwd.go | 96 +++++++++++++++++++ tests/e2e-inst-signatures/export.go | 1 + .../e2e-inst-signatures/scripts/set_fs_pwd.sh | 14 +++ tests/e2e-inst-test.sh | 2 +- 12 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 docs/docs/events/builtin/extra/set_fs_pwd.md create mode 100644 tests/e2e-inst-signatures/e2e-set_fs_pwd.go create mode 100755 tests/e2e-inst-signatures/scripts/set_fs_pwd.sh diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 1a05e400babc..2c7aab7913d3 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -71,6 +71,7 @@ env: PROCTREE_DATA_SOURCE DNS_DATA_SOURCE WRITABLE_DATA_SOURCE + SET_FS_PWD jobs: # # DOC VERIFICATION diff --git a/docs/docs/events/builtin/extra/set_fs_pwd.md b/docs/docs/events/builtin/extra/set_fs_pwd.md new file mode 100644 index 000000000000..c3b3247d1ab1 --- /dev/null +++ b/docs/docs/events/builtin/extra/set_fs_pwd.md @@ -0,0 +1,34 @@ +# set_fs_pwd + +## Intro + +set_fs_pwd - An event capturing changes to the current working directory. + +## Description + +This event captures any changes to the current working directory (typically by using the `chdir` and `fchdir` syscalls). + +## Arguments + +* `unresolved_pathname`:`const char*`[K,TOCTOU,OPT] - unresolved, user-supplied path which the current working directory is being changed to (only relevant to directory changes using the `chdir` syscall). +* `resolved_pathname`:`const char*`[K] - the fully resolved filesystem path which the current working directory is being changed to. + +## Hooks + +### set_fs_pwd + +#### Type + +kprobe + +#### Purpose + +Catch changes to the current working directory. + +## Example Use Case + +## Issues + +## Related Events + +`chdir`, `fchdir` diff --git a/pkg/ebpf/c/common/arch.h b/pkg/ebpf/c/common/arch.h index 237814fabd00..41705841a655 100644 --- a/pkg/ebpf/c/common/arch.h +++ b/pkg/ebpf/c/common/arch.h @@ -111,6 +111,7 @@ statfunc struct pt_regs *get_task_pt_regs(struct task_struct *task) #define SYSCALL_FDATASYNC 75 #define SYSCALL_FTRUNCATE 77 #define SYSCALL_GETDENTS 78 + #define SYSCALL_CHDIR 80 #define SYSCALL_FCHDIR 81 #define SYSCALL_FCHMOD 91 #define SYSCALL_FCHOWN 93 @@ -221,6 +222,7 @@ statfunc struct pt_regs *get_task_pt_regs(struct task_struct *task) #define SYSCALL_FDATASYNC 83 #define SYSCALL_FTRUNCATE 46 #define SYSCALL_GETDENTS UNDEFINED_SYSCALL + #define SYSCALL_CHDIR 49 #define SYSCALL_FCHDIR 50 #define SYSCALL_FCHMOD 52 #define SYSCALL_FCHOWN 55 diff --git a/pkg/ebpf/c/tracee.bpf.c b/pkg/ebpf/c/tracee.bpf.c index ebbb18aec034..8b7eccaeb171 100644 --- a/pkg/ebpf/c/tracee.bpf.c +++ b/pkg/ebpf/c/tracee.bpf.c @@ -5072,6 +5072,30 @@ int BPF_KPROBE(trace_security_path_notify) return events_perf_submit(&p, 0); } +SEC("kprobe/set_fs_pwd") +int BPF_KPROBE(trace_set_fs_pwd) +{ + program_data_t p = {}; + if (!init_program_data(&p, ctx, SET_FS_PWD)) + return 0; + + if (!evaluate_scope_filters(&p)) + return 0; + + syscall_data_t *sys = &p.task_info->syscall_data; + + void *unresolved_path = NULL; + if (sys->id == SYSCALL_CHDIR) + unresolved_path = (void *) sys->args.args[0]; + + void *resolved_path = get_path_str((struct path *) PT_REGS_PARM2(ctx)); + + save_str_to_buf(&p.event->args_buf, unresolved_path, 0); + save_str_to_buf(&p.event->args_buf, resolved_path, 1); + + return events_perf_submit(&p, 0); +} + // clang-format off // Network Packets (works from ~5.2 and beyond) diff --git a/pkg/ebpf/c/types.h b/pkg/ebpf/c/types.h index 3945dfe15adc..d6b80c0535ef 100644 --- a/pkg/ebpf/c/types.h +++ b/pkg/ebpf/c/types.h @@ -122,6 +122,7 @@ enum event_id_e SECURITY_BPF_PROG, PROCESS_EXECUTION_FAILED, SECURITY_PATH_NOTIFY, + SET_FS_PWD, HIDDEN_KERNEL_MODULE_SEEKER, MODULE_LOAD, MODULE_FREE, diff --git a/pkg/ebpf/probes/probe_group.go b/pkg/ebpf/probes/probe_group.go index 93bb4e00c2d3..8ca1d1d49f20 100644 --- a/pkg/ebpf/probes/probe_group.go +++ b/pkg/ebpf/probes/probe_group.go @@ -222,6 +222,7 @@ func NewDefaultProbeGroup(module *bpf.Module, netEnabled bool, kSyms *helpers.Ke ExecBinprmRet: NewTraceProbe(KretProbe, "exec_binprm", "trace_ret_exec_binprm"), SecurityPathNotify: NewTraceProbe(KProbe, "security_path_notify", "trace_security_path_notify"), SecurityBprmCredsForExec: NewTraceProbe(KProbe, "security_bprm_creds_for_exec", "trace_security_bprm_creds_for_exec"), + SetFsPwd: NewTraceProbe(KProbe, "set_fs_pwd", "trace_set_fs_pwd"), TpProbeRegPrioMayExist: NewTraceProbe(KProbe, "tracepoint_probe_register_prio_may_exist", "trace_tracepoint_probe_register_prio_may_exist"), ModuleLoad: NewTraceProbe(RawTracepoint, "module:module_load", "tracepoint__module__module_load"), ModuleFree: NewTraceProbe(RawTracepoint, "module:module_free", "tracepoint__module__module_free"), diff --git a/pkg/ebpf/probes/probes.go b/pkg/ebpf/probes/probes.go index 67563e1f66ab..61317f20750c 100644 --- a/pkg/ebpf/probes/probes.go +++ b/pkg/ebpf/probes/probes.go @@ -129,6 +129,7 @@ const ( ExecBinprmRet SecurityPathNotify SecurityBprmCredsForExec + SetFsPwd HiddenKernelModuleSeeker TpProbeRegPrioMayExist HiddenKernelModuleVerifier diff --git a/pkg/events/core.go b/pkg/events/core.go index a417b6e96f62..7e0647935729 100644 --- a/pkg/events/core.go +++ b/pkg/events/core.go @@ -103,6 +103,7 @@ const ( SecurityBpfProg ProcessExecuteFailed SecurityPathNotify + SetFsPwd HiddenKernelModuleSeeker ModuleLoad ModuleFree @@ -13010,6 +13011,31 @@ var CoreEvents = map[ID]Definition{ {Type: "unsigned int", Name: "obj_type"}, }, }, + SetFsPwd: { + id: SetFsPwd, + id32Bit: Sys32Undefined, + name: "set_fs_pwd", + dependencies: Dependencies{ + probes: []Probe{ + {handle: probes.SetFsPwd, required: true}, + {handle: probes.SyscallEnter__Internal, required: true}, + }, + tailCalls: []TailCall{ + { + "sys_enter_init_tail", + "sys_enter_init", + []uint32{ + uint32(Chdir), + }, + }, + }, + }, + sets: []string{"syscalls"}, + params: []trace.ArgMeta{ + {Type: "const char*", Name: "unresolved_path"}, + {Type: "const char*", Name: "resolved_path"}, + }, + }, // // Begin of Signal Events (Control Plane) // diff --git a/tests/e2e-inst-signatures/e2e-set_fs_pwd.go b/tests/e2e-inst-signatures/e2e-set_fs_pwd.go new file mode 100644 index 000000000000..47e9abaff6db --- /dev/null +++ b/tests/e2e-inst-signatures/e2e-set_fs_pwd.go @@ -0,0 +1,96 @@ +package main + +import ( + "fmt" + "strings" + + libbfgo "github.com/aquasecurity/libbpfgo/helpers" + + "github.com/aquasecurity/tracee/signatures/helpers" + "github.com/aquasecurity/tracee/types/detect" + "github.com/aquasecurity/tracee/types/protocol" + "github.com/aquasecurity/tracee/types/trace" +) + +type e2eSetFsPwd struct { + cb detect.SignatureHandler + hasReadUser bool +} + +func (sig *e2eSetFsPwd) Init(ctx detect.SignatureContext) error { + sig.cb = ctx.Callback + + // Find if this system has the bpf_probe_read_user_str helper. + // If it doesn't we won't expect the unresolved path to contain anything + ksyms, err := libbfgo.NewKernelSymbolTable() + if err != nil { + return err + } + _, err = ksyms.GetSymbolByName("bpf_probe_read_user_str") + if err != nil { + sig.hasReadUser = false + } else { + sig.hasReadUser = true + } + + return nil +} + +func (sig *e2eSetFsPwd) GetMetadata() (detect.SignatureMetadata, error) { + return detect.SignatureMetadata{ + ID: "SET_FS_PWD", + EventName: "SET_FS_PWD", + Version: "0.1.0", + Name: "set_fs_pwd Test", + Description: "Instrumentation events E2E Tests: set_fs_pwd", + Tags: []string{"e2e", "instrumentation"}, + }, nil +} + +func (sig *e2eSetFsPwd) GetSelectedEvents() ([]detect.SignatureEventSelector, error) { + return []detect.SignatureEventSelector{ + {Source: "tracee", Name: "set_fs_pwd"}, + }, nil +} + +func (sig *e2eSetFsPwd) OnEvent(event protocol.Event) error { + eventObj, ok := event.Payload.(trace.Event) + if !ok { + return fmt.Errorf("failed to cast event's payload") + } + + switch eventObj.EventName { + case "set_fs_pwd": + unresolvedPath, err := helpers.GetTraceeStringArgumentByName(eventObj, "unresolved_path") + if sig.hasReadUser && err != nil { + return err + } + + resolvedPath, err := helpers.GetTraceeStringArgumentByName(eventObj, "resolved_path") + if err != nil { + return err + } + + // check expected values from test for detection + + if (sig.hasReadUser && !strings.HasSuffix(unresolvedPath, "/test_link")) || !strings.HasSuffix(resolvedPath, "/test_dir") { + return nil + } + + m, _ := sig.GetMetadata() + + sig.cb(&detect.Finding{ + SigMetadata: m, + Event: event, + Data: map[string]interface{}{}, + }) + } + + return nil +} + +func (sig *e2eSetFsPwd) OnSignal(s detect.Signal) error { + return nil +} + +func (sig *e2eSetFsPwd) Close() {} diff --git a/tests/e2e-inst-signatures/export.go b/tests/e2e-inst-signatures/export.go index f188ee2f5cd9..2002befcba88 100644 --- a/tests/e2e-inst-signatures/export.go +++ b/tests/e2e-inst-signatures/export.go @@ -20,6 +20,7 @@ var ExportedSignatures = []detect.Signature{ &e2eDnsDataSource{}, &e2eWritableDatasourceSig{}, &e2eSecurityPathNotify{}, + &e2eSetFsPwd{}, } var ExportedDataSources = []detect.DataSource{ diff --git a/tests/e2e-inst-signatures/scripts/set_fs_pwd.sh b/tests/e2e-inst-signatures/scripts/set_fs_pwd.sh new file mode 100755 index 000000000000..beeb127a36c9 --- /dev/null +++ b/tests/e2e-inst-signatures/scripts/set_fs_pwd.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +exit_err() { + echo -n "ERROR: " + echo "$@" + exit 1 +} + +mkdir test_dir || exit_err "failed creating dir" +ln -s test_dir test_link || exit_err "failed creating link" +cd test_link || exit_err "failed changing directory" +cd .. || exit_err "failed changing directory back" +rm test_link || exit_err "failed removing link" +rm -r test_dir || exit_err "failed removing dir" \ No newline at end of file diff --git a/tests/e2e-inst-test.sh b/tests/e2e-inst-test.sh index a7fdba31b7e6..6a81401a8abf 100755 --- a/tests/e2e-inst-test.sh +++ b/tests/e2e-inst-test.sh @@ -129,7 +129,7 @@ for TEST in $TESTS; do --output option:parse-arguments \ --log file:$SCRIPT_TMP_DIR/tracee-log-$$ \ --signatures-dir "$SIG_DIR" \ - --scope comm=echo,mv,ls,tracee,proctreetester,ping,ds_writer,fsnotify_tester,process_execute,tracee-ebpf,writev \ + --scope comm=echo,mv,ls,tracee,proctreetester,ping,ds_writer,fsnotify_tester,process_execute,tracee-ebpf,writev,set_fs_pwd.sh \ --dnscache enable \ --grpc-listen-addr unix:/tmp/tracee.sock \ --events "$TEST" &