diff --git a/Makefile b/Makefile index 715af62e6697..549e94619a34 100644 --- a/Makefile +++ b/Makefile @@ -627,6 +627,35 @@ clean-signatures: # other commands # +# evt + +EVT_SRC_DIRS = ./cmd/evt/ +EVT_SRC = $(shell find $(EVT_SRC_DIRS) \ + -type f \ + -name '*.go' \ + ! -name '*_test.go' \ + ) + +.PHONY: evt +evt: $(OUTPUT_DIR)/evt + +$(OUTPUT_DIR)/evt: \ + $(EVT_SRC) \ + $(OUTPUT_DIR)/tracee \ + | .eval_goenv \ + .checkver_$(CMD_GO) \ +# + $(GO_ENV_EBPF) $(CMD_GO) build \ + -ldflags="$(GO_DEBUG_FLAG) \ + " \ + -v -o $@ \ + ./cmd/evt + +.PHONY: clean-evt +clean-evt: +# + $(CMD_RM) -rf $(OUTPUT_DIR)/evt + # tracee-bench TRACEE_BENCH_SRC_DIRS = ./cmd/tracee-bench/ diff --git a/cmd/evt/cmd/root.go b/cmd/evt/cmd/root.go new file mode 100644 index 000000000000..010285239bdf --- /dev/null +++ b/cmd/evt/cmd/root.go @@ -0,0 +1,38 @@ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" + + "github.com/aquasecurity/tracee/cmd/evt/cmd/stress" + "github.com/aquasecurity/tracee/cmd/evt/cmd/trigger" +) + +func init() { + rootCmd.AddCommand(stress.Cmd()) + rootCmd.AddCommand(trigger.Cmd()) +} + +var ( + rootCmd = &cobra.Command{ + Use: "evt", + Short: "An event stress testing tool", + Long: "evt is a simple testing tool that generates events to stress the system", + } +) + +func initRootCmd() error { + rootCmd.SetOutput(os.Stdout) + rootCmd.SetErr(os.Stderr) + + return nil +} + +func Execute() error { + if err := initRootCmd(); err != nil { + return err + } + + return rootCmd.Execute() +} diff --git a/cmd/evt/cmd/stress/stress.go b/cmd/evt/cmd/stress/stress.go new file mode 100644 index 000000000000..781012d37946 --- /dev/null +++ b/cmd/evt/cmd/stress/stress.go @@ -0,0 +1,655 @@ +package stress + +import ( + "context" + "fmt" + "os" + "os/exec" + "os/signal" + "path/filepath" + "strconv" + "strings" + "sync" + "syscall" + "time" + + "github.com/dustin/go-humanize" + "github.com/spf13/cobra" + "golang.org/x/exp/slices" + "gopkg.in/yaml.v2" + + "github.com/aquasecurity/tracee/pkg/cmd/flags" + "github.com/aquasecurity/tracee/pkg/logger" + "github.com/aquasecurity/tracee/pkg/policy/v1beta1" +) + +func init() { + stressCmd.Flags().StringArrayP( + "policy", + "p", + []string{}, + "\t\t\tPath to a policy or directory with policies to stress", + ) + stressCmd.Flags().StringSliceP( + "event", + "e", + []string{}, + "...\t\t\tSelect events to stress", + ) + + stressMode := os.Getenv("EVT_STRESS_MODE") + if stressMode == "" { + stressMode = fmt.Sprintf("ops=%d", defaultStressOps) + } + stressCmd.Flags().StringVarP( + &stressMode, + "mode", + "m", + stressMode, + "\tStress mode", + ) + + stressThreads := defaultStressThreads + stressThreadsS := os.Getenv("EVT_STRESS_THREADS") + if stressThreadsS != "" { + v, err := strconv.ParseUint(stressThreadsS, 10, 8) + if err != nil { + fmt.Printf("Error parsing EVT_STRESS_THREADS: %v\n", err) + os.Exit(1) + } + + stressThreads = uint8(v) + } + stressCmd.Flags().Uint8P( + "threads", + "t", + stressThreads, + "...\t\t\tNumber of threads to stress for each trigger", + ) +} + +type StressMode int32 + +const ( + StressModeTime StressMode = iota + StressModeOps +) + +const ( + maxStressTime = 3 * time.Hour + defaultStressTime = 10 * time.Minute + defaultStressOps = uint32(10_000_000) + defaultStressThreads = uint8(1) +) + +var ( + stressCmd = &cobra.Command{ + Use: "stress", + Aliases: []string{"s"}, + Short: "Stress the system with events", + RunE: stressRun, + SilenceErrors: true, + SilenceUsage: true, + } +) + +type StressConfig struct { + Mode StressModeConfig + Selected *SelectedToStress +} + +type StressModeConfig struct { + Selected StressMode + Ops uint32 + Threads uint8 + Time time.Duration +} + +func parseStressModeFlag(modeFlag string) (StressModeConfig, error) { + parts := strings.Split(modeFlag, "=") + mode := "" + value := "" + if len(parts) == 0 || len(parts) > 2 { + goto invalid_mode + } + + mode = parts[0] + if mode != "time" && mode != "ops" { + goto invalid_mode + } + + if len(parts) == 1 { + switch mode { + case "time": + return StressModeConfig{ + Selected: StressModeTime, + Time: defaultStressTime, + }, nil + + case "ops": + return StressModeConfig{ + Selected: StressModeOps, + Ops: defaultStressOps, + }, nil + } + } + + value = parts[1] + switch mode { + case "time": + t, err := time.ParseDuration(value) + if err != nil { + return StressModeConfig{}, fmt.Errorf("invalid stress time: %s", value) + } + return StressModeConfig{ + Selected: StressModeTime, + Time: t, + }, nil + + case "ops": + ops, err := strconv.ParseUint(value, 10, 32) + if err != nil { + return StressModeConfig{}, fmt.Errorf("invalid stress ops: %s", value) + } + return StressModeConfig{ + Selected: StressModeOps, + Ops: uint32(ops), + }, nil + } + +invalid_mode: + return StressModeConfig{}, fmt.Errorf("invalid stress mode: %s", modeFlag) +} + +func getStressConfig(cmd *cobra.Command) (*StressConfig, error) { + modeFlag := cmd.Flag("mode").Value.String() + modeConfig, err := parseStressModeFlag(modeFlag) + if err != nil { + return nil, err + } + + modeConfig.Threads, err = cmd.Flags().GetUint8("threads") + if err != nil { + return nil, err + } + + return &StressConfig{ + Mode: modeConfig, + }, nil +} + +const coolDownTime = 10 * time.Second + +func coolDownCtx(ctx context.Context, msg string, coolDownTime time.Duration) { + fmt.Printf("%s: waiting %v for cool down...\n", msg, coolDownTime) + + select { + case <-time.After(coolDownTime): + return + case <-ctx.Done(): + return + } +} + +func coolDown(msg string, coolDownTime time.Duration) { + fmt.Printf("%s: waiting %v for cool down...\n", msg, coolDownTime) + time.Sleep(coolDownTime) +} + +func stressRun(cmd *cobra.Command, args []string) error { + logger.Init(logger.NewDefaultLoggingConfig()) + + cfg, err := getStressConfig(cmd) + if err != nil { + return err + } + + err = setSelectedToStress(cmd, cfg) + if err != nil { + return err + } + + fmt.Printf("Events selected to stress: %v\n", cfg.Selected.EventsNames) + + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer cancel() + + var timeLimit time.Duration + if cfg.Mode.Selected == StressModeOps { + timeLimit = maxStressTime + fmt.Printf("Stressing the system with %v ops per event and time limit of %v\n", humanize.Comma(int64(cfg.Mode.Ops)), timeLimit) + fmt.Println("Ops mode triggers events based on the ops of the event, until it reaches the event ops amount or the time limit") + } else { + timeLimit = cfg.Mode.Time + fmt.Printf("Stressing the system without ops limit for %v\n", timeLimit) + fmt.Println("Time mode triggers events disregarding the ops, until it reaches the time limit") + } + + ctx, cancelTimeout := context.WithTimeoutCause( + ctx, + timeLimit, + fmt.Errorf("stress time is up after %v", timeLimit), + ) + defer cancelTimeout() + + wg := &sync.WaitGroup{} + triggerPids := make([]int, 0, len(cfg.Selected.EventsNames)*int(cfg.Mode.Threads)) + triggerComms := make([]string, 0, len(cfg.Selected.EventsNames)*int(cfg.Mode.Threads)) + + for _, evt := range cfg.Selected.EventsNames { + for i := uint8(0); i < cfg.Mode.Threads; i++ { + triggerPid, err := triggerEvent(ctx, wg, evt, cfg.Mode.Ops) + if err != nil { + return err + } + + fmt.Printf("Started trigger %d for event %s with pid %d\n", i+1, evt, triggerPid) + triggerPids = append(triggerPids, triggerPid) + // limit the comm length to 15 printable characters + comm := fmt.Sprintf("%s.sh", evt) + comm = comm[:min(len(comm), 15)] + triggerComms = append(triggerComms, comm) + } + } + + traceeStatus := RunTracee(ctx, wg, cfg.Selected, os.Getpid(), triggerComms, triggerPids) + + // block until receiving tracee status + err = <-traceeStatus + if err != nil { + return err + } + + coolDownCtx(ctx, "tracee started", coolDownTime) + + // signal all triggers to start + for _, pid := range triggerPids { + err = syscall.Kill(pid, syscall.SIGUSR1) + if err != nil { + return fmt.Errorf("sending SIGUSR1 to trigger %d: %w", pid, err) + } + } + // err = syscall.Kill(-os.Getpid(), syscall.SIGUSR1) + // if err != nil { + // return fmt.Errorf("sending SIGUSR1 to all triggers: %w", err) + // } + + if cfg.Mode.Selected == StressModeOps { + wg.Add(1) + go func(cancel context.CancelFunc) { + logger.Debugw("checkTriggersLiveness goroutine started") + defer logger.Debugw("checkTriggersLiveness goroutine finished") + + defer wg.Done() + + for { + select { + case <-time.After(1 * time.Second): + allDone := true + for _, pid := range triggerPids { + if syscall.Kill(pid, 0) == nil { + allDone = false + break + } + } + + if allDone { + coolDown("triggers finished", coolDownTime) + cancel() + return + } + } + } + }(cancel) + } + + // block until tracee is finished or context is done + for { + select { + case <-ctx.Done(): + for _, pid := range triggerPids { + err = syscall.Kill(pid, syscall.SIGTERM) + if err != nil { + return fmt.Errorf("sending SIGTERM to trigger %d: %w", pid, err) + } + } + goto cleanup + case err := <-traceeStatus: + if err != nil { + fmt.Println(err) + } + goto cleanup + } + } + +cleanup: + // drain closed channels + for err := range traceeStatus { + if err != nil { + fmt.Println(err) + } + } + + wg.Wait() + + return nil +} + +func triggerEvent(ctx context.Context, wg *sync.WaitGroup, event string, ops uint32) (int, error) { + var err error + triggerPid := 0 + + exeCmd := exec.CommandContext(ctx, "./dist/evt", "trigger", "-e", event, "-o", fmt.Sprintf("%d", ops)) + exeCmd.SysProcAttr = &syscall.SysProcAttr{ + // Setpgid: true, + // Pgid: os.Getpid(), + } + exeCmd.Stdin = nil + exeCmd.Stdout = os.Stdout + exeCmd.Stderr = os.Stderr + + err = exeCmd.Start() + if err != nil { + err = fmt.Errorf("starting trigger: %w", err) + return triggerPid, err + } + + triggerPid = exeCmd.Process.Pid + + wg.Add(1) + go func() { + logger.Debugw("waitForTrigger goroutine started") + defer logger.Debugw("waitForTrigger goroutine finished") + + defer wg.Done() + + waitErr := exeCmd.Wait() + if waitErr != nil { + fmt.Errorf("waiting for trigger: %w", waitErr) + } + }() + + return triggerPid, err +} + +func RunTracee( + ctx context.Context, + wg *sync.WaitGroup, + selected *SelectedToStress, + treePid int, + filterOutCommScope []string, + filterOutPidScope []int, +) <-chan error { + errCh := make(chan error, 1) + + wg.Add(1) + go func() { + logger.Debugw("runTracee goroutine started") + defer logger.Debugw("runTracee goroutine finished") + + defer wg.Done() + defer close(errCh) + + var args []string + if selected.Origin == "policy" { + // update the policy files with the filter out scope + updatePolicyFiles(selected.PolicyFiles, treePid, filterOutCommScope, filterOutPidScope) + + // save all updated policies to a temporary directory + const policiesTmpDir = "/tmp/evt-policies" + + err := writePolicyFiles(policiesTmpDir, selected.PolicyFiles) + if err != nil { + errCh <- fmt.Errorf("writing policy files: %w", err) + } + + args = append(args, "-p", policiesTmpDir) + } else { + args = append(args, "-s", getFilterOutCommScope(filepath.Base(os.Args[0]))) + for _, outComm := range filterOutCommScope { + args = append(args, "-s", getFilterOutCommScope(outComm)) + } + + selfPid := os.Getpid() + args = append(args, "-s", getFilterOutPidScope(selfPid)) + for _, outPid := range filterOutPidScope { + args = append(args, "-s", getFilterOutPidScope(outPid)) + } + + args = append(args, "-s", fmt.Sprintf("tree=%d", treePid)) + + for _, evt := range selected.EventsFlags { + args = append(args, "-e", evt) + } + } + + args = append(args, "--metrics", "--pprof", "--pyroscope") + args = append(args, "-o", "none") + + fmt.Println("Running tracee with args:", args) + + cmd := exec.CommandContext(ctx, "./dist/tracee", args...) + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + } + cmd.Cancel = func() error { + errCh <- fmt.Errorf("tracee being stopped: %w", context.Cause(ctx)) + return cmd.Process.Signal(syscall.SIGTERM) + } + cmd.Stdin = nil + + logFile, err := os.Create(fmt.Sprintf("tracee-%s.log", time.Now().Format("20060102-150405"))) + if err != nil { + errCh <- fmt.Errorf("creating log file: %w", err) + return + } + defer logFile.Close() + + cmd.Stdout = logFile + cmd.Stderr = logFile + + err = cmd.Start() + if err != nil { + errCh <- fmt.Errorf("starting tracee: %w", err) + return + } + + cmdWaitChan := waitForCommand(cmd) + select { + case err = <-cmdWaitChan: + if err == nil { + errCh <- fmt.Errorf("tracee finished with no error right after starting") + return + } + + // tracee finished with error + errCh <- fmt.Errorf("tracee finished: %w", err) + case <-time.After(1 * time.Second): + // give tracee some time to start + } + if err != nil { + return + } + + // tracee started, unblock the caller + errCh <- nil + + // wait for tracee to finish + err = <-cmdWaitChan + if err != nil { + errCh <- fmt.Errorf("tracee finished: %w", err) + } + }() + + return errCh +} + +func updatePolicyFiles( + policyFiles []v1beta1.PolicyFile, + treePid int, + filterOutCommScope []string, + filterOutPidScope []int, +) { + for i := range policyFiles { + p := &policyFiles[i] + p.Spec.Scope = append(p.Spec.Scope, getFilterTreeScope(treePid)) + + selfCommScope := getFilterOutCommScope(filepath.Base(os.Args[0])) + if idx := slices.Index(p.Spec.Scope, "global"); idx != -1 { + p.Spec.Scope[idx] = selfCommScope + } else { + p.Spec.Scope = append(p.Spec.Scope, selfCommScope) + } + p.Spec.Scope = append(p.Spec.Scope, getFilterOutPidScope(os.Getpid())) + + for _, outComm := range filterOutCommScope { + p.Spec.Scope = append(p.Spec.Scope, getFilterOutCommScope(outComm)) + } + for _, outPid := range filterOutPidScope { + p.Spec.Scope = append(p.Spec.Scope, getFilterOutPidScope(outPid)) + } + } +} + +func writePolicyFiles(policiesTmpDir string, policyFiles []v1beta1.PolicyFile) error { + err := os.RemoveAll(policiesTmpDir) + if err != nil { + return fmt.Errorf("removing policies tmp dir: %w", err) + } + err = os.MkdirAll(policiesTmpDir, 0755) + if err != nil { + return fmt.Errorf("creating policies tmp dir: %w", err) + } + + for _, policyFile := range policyFiles { + policyYaml, err := yaml.Marshal(policyFile) + if err != nil { + return fmt.Errorf("marshaling policy to yaml: %w", err) + } + + policyFilePath := fmt.Sprintf("%s/%s.yaml", policiesTmpDir, policyFile.Metadata.Name) + err = os.WriteFile(policyFilePath, policyYaml, 0755) + if err != nil { + return fmt.Errorf("writing policy to file: %w", err) + } + } + + return nil +} + +func waitForCommand(cmd *exec.Cmd) <-chan error { + done := make(chan error) + + go func() { + done <- cmd.Wait() + close(done) + }() + + return done +} + +type SelectedToStress struct { + Origin string + EventsNames []string + EventsFlags []string + PolicyFlags []string + PolicyFiles []v1beta1.PolicyFile +} + +func setSelectedToStress(cmd *cobra.Command, stressConfig *StressConfig) error { + policyFlags, err := cmd.Flags().GetStringArray("policy") + if err != nil { + return err + } + eventFlags, err := cmd.Flags().GetStringSlice("event") + if err != nil { + return err + } + + if len(policyFlags) == 0 && len(eventFlags) == 0 { + return fmt.Errorf("no policies or events provided") + } + if len(policyFlags) > 0 && len(eventFlags) > 0 { + return fmt.Errorf("policy and event flags cannot be used together") + } + + var events []string + var policyFiles []v1beta1.PolicyFile + origin := "" + if len(policyFlags) > 0 { + origin = "policy" + events, policyFiles, err = getEventsAndPoliciesFromPolicyFiles(policyFlags) + if err != nil { + return err + } + } else { + origin = "event" + events, err = getEventsFromEventFlags(eventFlags) + if err != nil { + return err + } + } + + slices.Sort(events) + events = slices.Compact(events) + + // set the selected events + stressConfig.Selected = &SelectedToStress{ + Origin: origin, + EventsNames: events, + EventsFlags: eventFlags, + PolicyFlags: policyFlags, + PolicyFiles: policyFiles, + } + + return nil +} + +func getFilterOutCommScope(comm string) string { + return fmt.Sprintf("comm!=%s", comm) +} + +func getFilterOutPidScope(pid int) string { + return fmt.Sprintf("pid!=%d", pid) +} + +func getFilterTreeScope(treePid int) string { + return fmt.Sprintf("tree=%d", treePid) +} + +func getEventsAndPoliciesFromPolicyFiles(policyFlags []string) ([]string, []v1beta1.PolicyFile, error) { + policyInterfaceSlice, err := v1beta1.PoliciesFromPaths(policyFlags) + if err != nil { + return nil, nil, err + } + + policyFiles := make([]v1beta1.PolicyFile, 0, len(policyInterfaceSlice)) + for i := range policyInterfaceSlice { + p, ok := policyInterfaceSlice[i].(v1beta1.PolicyFile) + if !ok { + return nil, nil, fmt.Errorf("policy file is not a v1beta1.PolicyFile") + } + + policyFiles = append(policyFiles, p) + } + + _, policyEventsMap, err := flags.PrepareFilterMapsFromPolicies(policyInterfaceSlice) + if err != nil { + return nil, nil, err + } + + return policyEventsMap.GetSelectedEvents(), policyFiles, nil +} + +func getEventsFromEventFlags(eventFlags []string) ([]string, error) { + policyEventsMap, err := flags.PrepareEventMapFromFlags(eventFlags) + if err != nil { + return nil, err + } + + return policyEventsMap.GetSelectedEvents(), nil +} + +func Cmd() *cobra.Command { + return stressCmd +} diff --git a/cmd/evt/cmd/trigger/trigger.go b/cmd/evt/cmd/trigger/trigger.go new file mode 100644 index 000000000000..334c8b409bd0 --- /dev/null +++ b/cmd/evt/cmd/trigger/trigger.go @@ -0,0 +1,139 @@ +package trigger + +import ( + "context" + "fmt" + "os" + "os/exec" + "os/signal" + "syscall" + "time" + + "github.com/spf13/cobra" +) + +func init() { + triggerCmd.Flags().StringP( + "event", + "e", + "", + "...\t\t\tSelect event to stress", + ) + if err := triggerCmd.MarkFlagRequired("event"); err != nil { + fmt.Printf("Error setting required flag: %v\n", err) + os.Exit(1) + } + + triggerCmd.Flags().Uint32P( + "ops", + "o", + defaultStressOps, + "...\t\t\tNumber of operations to perform", + ) + + triggerCmd.Flags().DurationP( + "sleep", + "s", + defaultTriggerSleep, + "...\t\t\tSleep time between operations", + ) +} + +const ( + maxTriggerTime = 1 * time.Hour + defaultTriggerSleep = 10 * time.Nanosecond + defaultStressOps = uint32(1_000_000) +) + +var ( + triggerCmd = &cobra.Command{ + Use: "trigger", + Aliases: []string{"t"}, + Short: "Trigger events to stress the system", + RunE: triggerRun, + SilenceErrors: true, + SilenceUsage: true, + } +) + +type TriggerConfig struct { + Event string + Ops uint32 + Sleep time.Duration +} + +func getTriggerConfig(cmd *cobra.Command) (*TriggerConfig, error) { + event, err := cmd.Flags().GetString("event") + if err != nil { + return nil, err + } + + ops, err := cmd.Flags().GetUint32("ops") + if err != nil { + return nil, err + } + + sleep, err := cmd.Flags().GetDuration("sleep") + if err != nil { + return nil, err + } + + return &TriggerConfig{ + Event: event, + Ops: ops, + Sleep: sleep, + }, nil +} + +func triggerRun(cmd *cobra.Command, args []string) error { + cfg, err := getTriggerConfig(cmd) + if err != nil { + return err + } + + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer cancel() + + _, cancelTimeout := context.WithTimeoutCause( + ctx, + maxTriggerTime, + fmt.Errorf("[trigger:%d] time for event %s is up after %v", os.Getegid(), cfg.Event, maxTriggerTime), + ) + defer cancelTimeout() + + startChan := make(chan os.Signal, 1) + signal.Notify(startChan, syscall.SIGUSR1) + fmt.Printf("[trigger:%d:%s] Waiting for start signal\n", os.Getpid(), cfg.Event) + + select { + case <-ctx.Done(): + fmt.Printf("[trigger:%d:%s] Stopping triggering: %v\n", os.Getpid(), cfg.Event, ctx.Err()) + return ctx.Err() + case <-startChan: + fmt.Printf("[trigger:%d:%s] Starting triggering %d ops with %v sleep time\n", os.Getpid(), cfg.Event, cfg.Ops, cfg.Sleep) + } + + for i := uint32(0); i < cfg.Ops; i++ { + select { + case <-ctx.Done(): + fmt.Printf("[trigger:%d:%s] Stopping triggering: %v\n", os.Getpid(), cfg.Event, ctx.Err()) + return ctx.Err() + case <-time.After(cfg.Sleep): + // continue + } + + exeCmd := exec.CommandContext(ctx, fmt.Sprintf("./cmd/evt/cmd/trigger/triggers/%s.sh", cfg.Event)) + err := exeCmd.Run() + if err != nil { + return fmt.Errorf("[trigger:%d:%s] failed to run command: %w", os.Getpid(), cfg.Event, err) + } + } + + fmt.Printf("[trigger:%d:%s] Finished triggering %d ops\n", os.Getpid(), cfg.Event, cfg.Ops) + + return nil +} + +func Cmd() *cobra.Command { + return triggerCmd +} diff --git a/cmd/evt/cmd/trigger/triggers/arch_prctl.sh b/cmd/evt/cmd/trigger/triggers/arch_prctl.sh new file mode 120000 index 000000000000..f00670e47d78 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/arch_prctl.sh @@ -0,0 +1 @@ +common/true.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/bpf_attach.sh b/cmd/evt/cmd/trigger/triggers/bpf_attach.sh new file mode 120000 index 000000000000..8bb8ef0f5c5b --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/bpf_attach.sh @@ -0,0 +1 @@ +common/bpftrace.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/commit_creds.sh b/cmd/evt/cmd/trigger/triggers/commit_creds.sh new file mode 120000 index 000000000000..a93f46bb9bb4 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/commit_creds.sh @@ -0,0 +1 @@ +common/sudo.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/common/bpftrace.sh b/cmd/evt/cmd/trigger/triggers/common/bpftrace.sh new file mode 100755 index 000000000000..74f08e27dc5a --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/common/bpftrace.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +# common + +# security_file_open 60 +# shared_object_loaded 44 +# sched_process_exec 2 +# arch_prctl 2 +# security_bpf_prog 4 +# kallsyms_lookup_name 2 +# kprobe_attach 1 +# bpf_attach 1 +# sched_process_exit 2 + +bpftrace -e 'kprobe:__do_sys_vfork { }' & +bpftrace_pid=$! +sleep 3 +kill -KILL $bpftrace_pid diff --git a/cmd/evt/cmd/trigger/triggers/common/docker.sh b/cmd/evt/cmd/trigger/triggers/common/docker.sh new file mode 100755 index 000000000000..58c5b76aa709 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/common/docker.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +# common + + +sh -c 'docker run --rm -it ubuntu /bin/bash' diff --git a/cmd/evt/cmd/trigger/triggers/common/mktemp-ln-rm.sh b/cmd/evt/cmd/trigger/triggers/common/mktemp-ln-rm.sh new file mode 100755 index 000000000000..a45e66116c74 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/common/mktemp-ln-rm.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +# common + +# sched_process_exec 5 +# security_file_open 17 +# shared_object_loaded 5 +# arch_prctl 5 +# security_inode_unlink 3 +# security_inode_symlink 1 +# sched_process_exit 5 + +file=$(mktemp /tmp/fileXXXXXX) +link1=$(mktemp /tmp/link1XXXXXX) + +rm -f "$link1" + +ln -s "$file" "$link1" +rm "$file" "$link1" diff --git a/cmd/evt/cmd/trigger/triggers/common/ping.sh b/cmd/evt/cmd/trigger/triggers/common/ping.sh new file mode 100755 index 000000000000..b33f2f2b83e5 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/common/ping.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +# common + +# sched_process_exec 1 +# security_file_open 8 +# shared_object_loaded 4 +# arch_prctl 1 +# security_socket_create 3 +# security_socket_connect 1 +# sched_process_exit 1 + +ping 0.0.0.0 -c 1 diff --git a/cmd/evt/cmd/trigger/triggers/common/self-comm.sh b/cmd/evt/cmd/trigger/triggers/common/self-comm.sh new file mode 100755 index 000000000000..8a7f245ca1f6 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/common/self-comm.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +# common + +# magic_write 2 +# security_file_open 1 +# do_truncate 1 +# sched_process_exit 1 + +echo "fake-comm" > /proc/self/comm # trigger magic-write by fake-comm +echo "fake-comm" > /proc/self/comm # trigger do_truncate by fake-comm diff --git a/cmd/evt/cmd/trigger/triggers/common/sudo.sh b/cmd/evt/cmd/trigger/triggers/common/sudo.sh new file mode 100755 index 000000000000..8f45c5b51e76 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/common/sudo.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# common + +# sched_process_exec 3 +# security_file_open 113 +# shared_object_loaded 40 +# arch_prctl 3 +# security_socket_create 19 +# commit_creds 4 +# sched_process_fork 3 +# sched_process_exit 3 +# socket_dup 2 + +sudo echo sudo >/dev/null diff --git a/cmd/evt/cmd/trigger/triggers/common/timeout-nc.sh b/cmd/evt/cmd/trigger/triggers/common/timeout-nc.sh new file mode 100755 index 000000000000..fdd1575f8246 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/common/timeout-nc.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +# sched_process_exec 2 +# security_file_open 11 +# shared_object_loaded 2 +# arch_prctl 2 +# security_file_open 12 +# sched_process_fork 1 +# process_execute_failed 5 +# security_socket_create 1 +# security_socket_bind 1 +# sched_process_exit 2 + +timeout 0.1 nc -l -p 8888 diff --git a/cmd/evt/cmd/trigger/triggers/common/true.sh b/cmd/evt/cmd/trigger/triggers/common/true.sh new file mode 100755 index 000000000000..d14c3a84219a --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/common/true.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +# common + +# sched_process_exec 1 +# security_file_open 2 +# shared_object_loaded 1 +# arch_prctl 1 +# sched_process_exit 1 + +/bin/true # full path to avoid shell built-in diff --git a/cmd/evt/cmd/trigger/triggers/common/unshare-mkdir.sh b/cmd/evt/cmd/trigger/triggers/common/unshare-mkdir.sh new file mode 100755 index 000000000000..66f11745db9d --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/common/unshare-mkdir.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +# common + +# sched_process_exec 2 +# security_file_open 13 +# shared_object_loaded 2 +# arch_prctl 2 +# debugfs_create_dir 1 +# debugfs_create_file 2 +# security_socket_create 15 +# device_add 1 +# switch_task_ns 1 +# sched_process_fork 1 +# magic_write 3 +# security_sb_mount 1 +# process_execute_failed 4 +# sched_process_exit 2 + +unshare --mount --pid --net --ipc --uts --user --fork --map-root-user sh & +sleep 1 # wait for the unshare to complete and exit +exit 0 diff --git a/cmd/evt/cmd/trigger/triggers/debugfs_create_dir.sh b/cmd/evt/cmd/trigger/triggers/debugfs_create_dir.sh new file mode 120000 index 000000000000..5126d17dc5a3 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/debugfs_create_dir.sh @@ -0,0 +1 @@ +common/unshare-mkdir.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/debugfs_create_file.sh b/cmd/evt/cmd/trigger/triggers/debugfs_create_file.sh new file mode 120000 index 000000000000..5126d17dc5a3 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/debugfs_create_file.sh @@ -0,0 +1 @@ +common/unshare-mkdir.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/device_add.sh b/cmd/evt/cmd/trigger/triggers/device_add.sh new file mode 120000 index 000000000000..5126d17dc5a3 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/device_add.sh @@ -0,0 +1 @@ +common/unshare-mkdir.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/do_truncate.sh b/cmd/evt/cmd/trigger/triggers/do_truncate.sh new file mode 120000 index 000000000000..b7d3fd2c787b --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/do_truncate.sh @@ -0,0 +1 @@ +common/self-comm.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/kallsyms_lookup_name.sh b/cmd/evt/cmd/trigger/triggers/kallsyms_lookup_name.sh new file mode 120000 index 000000000000..8bb8ef0f5c5b --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/kallsyms_lookup_name.sh @@ -0,0 +1 @@ +common/bpftrace.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/kprobe_attach.sh b/cmd/evt/cmd/trigger/triggers/kprobe_attach.sh new file mode 120000 index 000000000000..8bb8ef0f5c5b --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/kprobe_attach.sh @@ -0,0 +1 @@ +common/bpftrace.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/list b/cmd/evt/cmd/trigger/triggers/list new file mode 100644 index 000000000000..fe99f18fba3a --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/list @@ -0,0 +1,19 @@ +arch_prctl,bpf_attach,call_usermodehelper,commit_creds,container_create,container_remove,debugfs_create_dir,debugfs_create_file,device_add,dirty_pipe_splice,do_truncate,existing_container,hidden_kernel_module,hooked_syscall,init_namespaces,kallsyms_lookup_name,kprobe_attach,magic_write,mem_prot_alert,module_load,print_mem_dump,proc_create,process_execute_failed,process_vm_writev,ptrace,register_chrdev,sched_process_exec,sched_process_exit,sched_process_fork,security_bpf_prog,security_file_open,security_inode_rename,security_inode_symlink,security_inode_unlink,security_path_notify,security_sb_mount,security_socket_bind,security_socket_connect,security_socket_create,set_fs_pwd,shared_object_loaded,socket_dup,switch_task_ns,symbols_loaded + + + +call_usermodehelper +dirty_pipe_splice +mem_prot_alert +process_vm_writev +register_chrdev +set_fs_pwd + + +pid 1: +container_create +container_remove + +dockerd: +security_inode_rename + diff --git a/cmd/evt/cmd/trigger/triggers/magic_write.sh b/cmd/evt/cmd/trigger/triggers/magic_write.sh new file mode 120000 index 000000000000..b7d3fd2c787b --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/magic_write.sh @@ -0,0 +1 @@ +common/self-comm.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/process_execute_failed.sh b/cmd/evt/cmd/trigger/triggers/process_execute_failed.sh new file mode 120000 index 000000000000..0f387e997ad6 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/process_execute_failed.sh @@ -0,0 +1 @@ +common/timeout-nc.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/ptrace.sh b/cmd/evt/cmd/trigger/triggers/ptrace.sh new file mode 100755 index 000000000000..f432a71d05b2 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/ptrace.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +# ptrace + +# sched_process_exec 2 +# security_file_open 14 +# shared_object_loaded 6 +# arch_prctl 2 +# sched_process_fork 2 +# ptrace 287 +# sched_process_exit 4 + +strace /bin/true # full path to avoid shell built-in diff --git a/cmd/evt/cmd/trigger/triggers/sched_process_exec.sh b/cmd/evt/cmd/trigger/triggers/sched_process_exec.sh new file mode 120000 index 000000000000..f00670e47d78 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/sched_process_exec.sh @@ -0,0 +1 @@ +common/true.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/sched_process_exit.sh b/cmd/evt/cmd/trigger/triggers/sched_process_exit.sh new file mode 120000 index 000000000000..f00670e47d78 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/sched_process_exit.sh @@ -0,0 +1 @@ +common/true.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/sched_process_fork.sh b/cmd/evt/cmd/trigger/triggers/sched_process_fork.sh new file mode 120000 index 000000000000..0f387e997ad6 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/sched_process_fork.sh @@ -0,0 +1 @@ +common/timeout-nc.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/security_bpf_prog.sh b/cmd/evt/cmd/trigger/triggers/security_bpf_prog.sh new file mode 100755 index 000000000000..b1d850b47849 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/security_bpf_prog.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +# security_bpf_prog + +# sched_process_exec 1 +# arch_prctl 2 +# security_bpf_prog 487 +# security_file_open 3 +# sched_process_exit 1 + +bpftool prog dump xlated name trace_execute_finished diff --git a/cmd/evt/cmd/trigger/triggers/security_file_open.sh b/cmd/evt/cmd/trigger/triggers/security_file_open.sh new file mode 120000 index 000000000000..f00670e47d78 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/security_file_open.sh @@ -0,0 +1 @@ +common/true.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/security_inode_symlink.sh b/cmd/evt/cmd/trigger/triggers/security_inode_symlink.sh new file mode 120000 index 000000000000..ac2bb20b000e --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/security_inode_symlink.sh @@ -0,0 +1 @@ +common/mktemp-ln-rm.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/security_inode_unlink.sh b/cmd/evt/cmd/trigger/triggers/security_inode_unlink.sh new file mode 120000 index 000000000000..ac2bb20b000e --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/security_inode_unlink.sh @@ -0,0 +1 @@ +common/mktemp-ln-rm.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/security_path_notify.sh b/cmd/evt/cmd/trigger/triggers/security_path_notify.sh new file mode 100755 index 000000000000..f29d07d52ce1 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/security_path_notify.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +# sched_process_exec 1 +# security_file_open 6 +# shared_object_loaded 5 +# arch_prctl 1 +# security_path_notify 1 +# sched_process_exit 1 + +inotifywait -m /tmp -t 1 diff --git a/cmd/evt/cmd/trigger/triggers/security_sb_mount.sh b/cmd/evt/cmd/trigger/triggers/security_sb_mount.sh new file mode 120000 index 000000000000..5126d17dc5a3 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/security_sb_mount.sh @@ -0,0 +1 @@ +common/unshare-mkdir.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/security_socket_bind.sh b/cmd/evt/cmd/trigger/triggers/security_socket_bind.sh new file mode 120000 index 000000000000..0f387e997ad6 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/security_socket_bind.sh @@ -0,0 +1 @@ +common/timeout-nc.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/security_socket_connect.sh b/cmd/evt/cmd/trigger/triggers/security_socket_connect.sh new file mode 120000 index 000000000000..603cfb870512 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/security_socket_connect.sh @@ -0,0 +1 @@ +common/ping.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/security_socket_create.sh b/cmd/evt/cmd/trigger/triggers/security_socket_create.sh new file mode 120000 index 000000000000..603cfb870512 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/security_socket_create.sh @@ -0,0 +1 @@ +common/ping.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/shared_object_loaded.sh b/cmd/evt/cmd/trigger/triggers/shared_object_loaded.sh new file mode 120000 index 000000000000..f00670e47d78 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/shared_object_loaded.sh @@ -0,0 +1 @@ +common/true.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/socked_dup.sh b/cmd/evt/cmd/trigger/triggers/socked_dup.sh new file mode 120000 index 000000000000..a93f46bb9bb4 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/socked_dup.sh @@ -0,0 +1 @@ +common/sudo.sh \ No newline at end of file diff --git a/cmd/evt/cmd/trigger/triggers/switch_task_ns.sh b/cmd/evt/cmd/trigger/triggers/switch_task_ns.sh new file mode 120000 index 000000000000..5126d17dc5a3 --- /dev/null +++ b/cmd/evt/cmd/trigger/triggers/switch_task_ns.sh @@ -0,0 +1 @@ +common/unshare-mkdir.sh \ No newline at end of file diff --git a/cmd/evt/main.go b/cmd/evt/main.go new file mode 100644 index 000000000000..7fb60c83ea2b --- /dev/null +++ b/cmd/evt/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" + "os" + + "github.com/aquasecurity/tracee/cmd/evt/cmd" +) + +func main() { + err := cmd.Execute() + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + os.Exit(0) +} diff --git a/go.mod b/go.mod index a09d2ce2a423..1cdbe58f4376 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/aquasecurity/tracee/types v0.0.0-20241008181102-d40bc1f81863 github.com/containerd/containerd v1.7.21 github.com/docker/docker v26.1.5+incompatible + github.com/dustin/go-humanize v1.0.1 github.com/golang/protobuf v1.5.4 github.com/google/gopacket v1.1.19 github.com/grafana/pyroscope-go v1.1.1 diff --git a/pkg/cmd/flags/event.go b/pkg/cmd/flags/event.go index b1343c24c481..ebb89585b103 100644 --- a/pkg/cmd/flags/event.go +++ b/pkg/cmd/flags/event.go @@ -28,6 +28,23 @@ type eventFlag struct { filter string } +func (pe PolicyEventMap) GetSelectedEvents() []string { + evtMap := make(map[string]struct{}) + + for _, events := range pe { + for _, event := range events.eventFlags { + evtMap[event.eventName] = struct{}{} + } + } + + evts := make([]string, 0, len(evtMap)) + for evt := range evtMap { + evts = append(evts, evt) + } + + return evts +} + func PrepareEventMapFromFlags(eventsArr []string) (PolicyEventMap, error) { // parse and store events flags var evtFlags []eventFlag