diff --git a/event-processor/deploy/CS_RT_BIN_PERM_RAISE.wasm b/event-processor/deploy/CS_RT_BIN_PERM_RAISE.wasm index 6f5fdd4d..514b7005 100644 Binary files a/event-processor/deploy/CS_RT_BIN_PERM_RAISE.wasm and b/event-processor/deploy/CS_RT_BIN_PERM_RAISE.wasm differ diff --git a/event-processor/deploy/CS_RT_FIFO_FILE_CREATE.wasm b/event-processor/deploy/CS_RT_FIFO_FILE_CREATE.wasm new file mode 100644 index 00000000..87e65170 Binary files /dev/null and b/event-processor/deploy/CS_RT_FIFO_FILE_CREATE.wasm differ diff --git a/event-processor/deploy/CS_RT_HARDLINK_CREATE.wasm b/event-processor/deploy/CS_RT_HARDLINK_CREATE.wasm new file mode 100644 index 00000000..e8c48feb Binary files /dev/null and b/event-processor/deploy/CS_RT_HARDLINK_CREATE.wasm differ diff --git a/event-processor/detector/wasm/CS_RT_BIN_PERM_RAISE/main.go b/event-processor/detector/wasm/CS_RT_BIN_PERM_RAISE/main.go index 90f1f348..60332b17 100644 --- a/event-processor/detector/wasm/CS_RT_BIN_PERM_RAISE/main.go +++ b/event-processor/detector/wasm/CS_RT_BIN_PERM_RAISE/main.go @@ -4,6 +4,7 @@ package main import ( "context" + "fmt" "regexp" "github.com/gobwas/glob" @@ -13,9 +14,9 @@ import ( const ( ID = "CS_RT_BIN_PERM_RAISE" - Name = "Change of file access permissions" - Description = "The detector detects if the chmod command was started. The command changes file access permissions in the home, tmp, var, boot, media, mnt, dev, run, and sys directories by adding a permission to a specific user, group, or all users." - Version = 2 + Name = "Change of file access permisions" + Description = "The detector detects if permissions to execute files in the boot, dev, home, media, mnt, run, sys, tmp, and var directories were granted." + Version = 3 Author = "Runtime Radar Team" License = "Apache License 2.0" ) @@ -26,7 +27,8 @@ var ( // a particular event type, such as "PROCESS_EXEC", leave slice empty or use // wildcard "*". triggerCriteria = map[string][]string{ - "PROCESS_EXEC": {}, + "PROCESS_EXEC": {}, + "PROCESS_KPROBE": {"security_path_chmod"}, // Examples: // @@ -41,20 +43,20 @@ var ( var ( chmod = glob.MustCompile(`*/chmod`) execPerm = regexp.MustCompile(`^(?:[\-Rcfv\s]+)?(?:(?:[0-7]?[0-7][1357][0-7]\s|[0-7]?[1357][0-7][0-7]\s|[0-7]?[0-7][0-7][1357]\s)|(?:[ugoa]*[+=]x\s))`) - suspDirArgs = regexp.MustCompile(`^(?:[\-Rcfv\s]+)?(?:(?:[ugoa]*[-=+][rwxX])|(?:[0-7]{3,4}))(?:.*)\/?(?:home|tmp|var|boot|media|mnt|dev|run|sys)\/`) + suspDirArgs = regexp.MustCompile(`^(?:[\-Rcfv\s]+)?(?:(?:[ugoa]*[-=+][rwxX])|(?:[0-7]{3,4}))(?:.*)\/?(?:boot|dev|home|media|mnt|run|sys|tmp|var)\/`) relPath = regexp.MustCompile(`^(?:[\-Rcfv\s]+)?(?:(?:[ugoa]*[-=+][rwxX])|(?:[0-7]{3,4}))(?:.*)\s(?:[^\/])`) suspDirCwd = []glob.Glob{ - glob.MustCompile("/home*"), - glob.MustCompile("/tmp*"), - glob.MustCompile("/var*"), glob.MustCompile("/boot*"), + glob.MustCompile("/dev*"), + glob.MustCompile("/home*"), glob.MustCompile("/media*"), glob.MustCompile("/mnt*"), + glob.MustCompile("/run*"), glob.MustCompile("/srv*"), glob.MustCompile("/sys*"), - glob.MustCompile("/dev*"), - glob.MustCompile("/run*"), + glob.MustCompile("/tmp*"), + glob.MustCompile("/var*"), } ) @@ -152,7 +154,32 @@ func (d Detector) Detect(ctx context.Context, req *api.DetectReq) (*api.DetectRe case *tetragon.GetEventsResponse_ProcessExit: // Nothing here case *tetragon.GetEventsResponse_ProcessKprobe: - // Nothing here + kprobe := ev.ProcessKprobe + function := kprobe.GetFunctionName() + args := kprobe.GetArgs() + path := "" + + switch function { + case "security_path_chmod": + if len(args) < 2 { + return nil, fmt.Errorf("unexpected args len, got %d, want >= 2", len(args)) + } + + path = args[0].GetPathArg().GetPath() + default: + return resp, nil + } + + for _, dir := range suspDirCwd { + if dir.Match(path) { + resp.Severity = api.DetectResp_MEDIUM // <-- threat detected + + return resp, nil + } + } + + return resp, nil + case *tetragon.GetEventsResponse_ProcessTracepoint: // Nothing here } @@ -161,6 +188,9 @@ func (d Detector) Detect(ctx context.Context, req *api.DetectReq) (*api.DetectRe } /* Example event (JSON): + +PROCESS_EXEC + { "process_exec": { "process": { @@ -415,8 +445,314 @@ func (d Detector) Detect(ctx context.Context, req *api.DetectReq) (*api.DetectRe }, "ancestors": [] }, - "node_name": "ptcs-master-node", + "node_name": "experts-k8s-cs", "time": "2024-11-13T10:15:07.462452981Z", "aggregation_info": null } + +PROCESS_KPROBE + +{ + "process_kprobe": { + "process": { + "exec_id": "dnNoLWs4cy5hcHBzZWMtc3RhbmQucHRzZWN1cml0eS5jbG91ZDoxODEwOTkyMTM4Njc5NjQ3NjoyNzQ1MTE3", + "pid": 2745117, + "uid": 0, + "cwd": "/tmp", + "binary": "/usr/bin/chmod", + "arguments": "o+x reg-file", + "flags": "execve clone", + "start_time": "2025-12-10T19:03:39.717065187Z", + "auid": 4294967295, + "pod": { + "namespace": "default", + "name": "test-pod-debian", + "container": { + "id": "containerd://35ae39802175a6d328218c2d35d878bf0402b37a229ee28b2960161c567eca5d", + "name": "test-pod-debian", + "image": { + "id": "docker.io/library/debian@sha256:7e5bc0e499a8d50cb1e32287944a90b9ec8fd7d500673e75daff3f52882f5798", + "name": "docker.io/library/debian:12" + }, + "start_time": "2025-12-10T07:50:30Z", + "pid": 174, + "maybe_exec_probe": false, + "security_context": { + "privileged": false + } + }, + "pod_labels": {}, + "workload": "test-pod-debian", + "workload_kind": "Pod", + "pod_annotations": {} + }, + "docker": "35ae39802175a6d328218c2d35d878b", + "parent_exec_id": "dnNoLWs4cy5hcHBzZWMtc3RhbmQucHRzZWN1cml0eS5jbG91ZDoxODEwNzg5OTk2NDM0Nzg5MDoyNzAxOTgy", + "refcnt": 1, + "cap": { + "permitted": [ + "CAP_CHOWN", + "DAC_OVERRIDE", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW", + "CAP_SYS_CHROOT", + "CAP_MKNOD", + "CAP_AUDIT_WRITE", + "CAP_SETFCAP" + ], + "effective": [ + "CAP_CHOWN", + "DAC_OVERRIDE", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW", + "CAP_SYS_CHROOT", + "CAP_MKNOD", + "CAP_AUDIT_WRITE", + "CAP_SETFCAP" + ], + "inheritable": [] + }, + "ns": { + "uts": { + "inum": 4026533802, + "is_host": false + }, + "ipc": { + "inum": 4026533803, + "is_host": false + }, + "mnt": { + "inum": 4026533805, + "is_host": false + }, + "pid": { + "inum": 4026533806, + "is_host": false + }, + "pid_for_children": { + "inum": 4026533806, + "is_host": false + }, + "net": { + "inum": 4026533163, + "is_host": false + }, + "time": { + "inum": 4026531834, + "is_host": true + }, + "time_for_children": { + "inum": 4026531834, + "is_host": true + }, + "cgroup": { + "inum": 4026533807, + "is_host": false + }, + "user": { + "inum": 4026531837, + "is_host": true + } + }, + "tid": 2745117, + "process_credentials": { + "uid": 0, + "gid": 0, + "euid": 0, + "egid": 0, + "suid": 0, + "sgid": 0, + "fsuid": 0, + "fsgid": 0, + "securebits": [], + "caps": null, + "user_ns": null + }, + "binary_properties": null, + "user": null, + "in_init_tree": false + }, + "parent": { + "exec_id": "dnNoLWs4cy5hcHBzZWMtc3RhbmQucHRzZWN1cml0eS5jbG91ZDoxODEwNzg5OTk2NDM0Nzg5MDoyNzAxOTgy", + "pid": 2701982, + "uid": 0, + "cwd": "/", + "binary": "/usr/bin/bash", + "arguments": "", + "flags": "execve rootcwd", + "start_time": "2025-12-10T18:29:58.294617147Z", + "auid": 4294967295, + "pod": { + "namespace": "default", + "name": "test-pod-debian", + "container": { + "id": "containerd://35ae39802175a6d328218c2d35d878bf0402b37a229ee28b2960161c567eca5d", + "name": "test-pod-debian", + "image": { + "id": "docker.io/library/debian@sha256:7e5bc0e499a8d50cb1e32287944a90b9ec8fd7d500673e75daff3f52882f5798", + "name": "docker.io/library/debian:12" + }, + "start_time": "2025-12-10T07:50:30Z", + "pid": 82, + "maybe_exec_probe": false, + "security_context": { + "privileged": false + } + }, + "pod_labels": {}, + "workload": "test-pod-debian", + "workload_kind": "Pod", + "pod_annotations": {} + }, + "docker": "35ae39802175a6d328218c2d35d878b", + "parent_exec_id": "dnNoLWs4cy5hcHBzZWMtc3RhbmQucHRzZWN1cml0eS5jbG91ZDoxODEwNzg5OTk2MzIwNzExMDoyNzAxOTgy", + "refcnt": 0, + "cap": { + "permitted": [ + "CAP_CHOWN", + "DAC_OVERRIDE", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW", + "CAP_SYS_CHROOT", + "CAP_MKNOD", + "CAP_AUDIT_WRITE", + "CAP_SETFCAP" + ], + "effective": [ + "CAP_CHOWN", + "DAC_OVERRIDE", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW", + "CAP_SYS_CHROOT", + "CAP_MKNOD", + "CAP_AUDIT_WRITE", + "CAP_SETFCAP" + ], + "inheritable": [] + }, + "ns": { + "uts": { + "inum": 4026533802, + "is_host": false + }, + "ipc": { + "inum": 4026533803, + "is_host": false + }, + "mnt": { + "inum": 4026533805, + "is_host": false + }, + "pid": { + "inum": 4026533806, + "is_host": false + }, + "pid_for_children": { + "inum": 4026533806, + "is_host": false + }, + "net": { + "inum": 4026533163, + "is_host": false + }, + "time": { + "inum": 4026531834, + "is_host": true + }, + "time_for_children": { + "inum": 4026531834, + "is_host": true + }, + "cgroup": { + "inum": 4026533807, + "is_host": false + }, + "user": { + "inum": 4026531837, + "is_host": true + } + }, + "tid": 2701982, + "process_credentials": { + "uid": 0, + "gid": 0, + "euid": 0, + "egid": 0, + "suid": 0, + "sgid": 0, + "fsuid": 0, + "fsgid": 0, + "securebits": [], + "caps": null, + "user_ns": null + }, + "binary_properties": null, + "user": null, + "in_init_tree": false + }, + "function_name": "security_path_chmod", + "args": [ + { + "path_arg": { + "mount": "", + "path": "/tmp/reg-file", + "flags": "", + "permission": "-rw-r--rw-" + }, + "label": "file path" + }, + { + "uint_arg": 423, + "label": "file mode" + } + ], + "return": null, + "action": "KPROBE_ACTION_POST", + "kernel_stack_trace": [], + "policy_name": "permissions-manipulation", + "return_action": "KPROBE_ACTION_POST", + "message": "", + "tags": [], + "user_stack_trace": [], + "ancestors": [] + }, + "node_name": "experts-k8s-cs", + "time": "2025-12-10T19:03:39.717614198Z", + "aggregation_info": null, + "cluster_name": "", + "node_labels": { + "beta.kubernetes.io/arch": "amd64", + "beta.kubernetes.io/os": "linux", + "kubernetes.io/arch": "amd64", + "kubernetes.io/hostname": "experts-k8s-cs", + "kubernetes.io/os": "linux", + "node-role.kubernetes.io/control-plane": "", + "node.kubernetes.io/exclude-from-external-load-balancers": "" + } +} + */ diff --git a/event-processor/detector/wasm/CS_RT_FIFO_FILE_CREATE/main.go b/event-processor/detector/wasm/CS_RT_FIFO_FILE_CREATE/main.go new file mode 100644 index 00000000..5427599e --- /dev/null +++ b/event-processor/detector/wasm/CS_RT_FIFO_FILE_CREATE/main.go @@ -0,0 +1,440 @@ +//go:build tinygo.wasm + +package main + +import ( + "context" + "fmt" + + "github.com/gobwas/glob" + "github.com/runtime-radar/runtime-radar/event-processor/detector/api" + "github.com/runtime-radar/runtime-radar/event-processor/detector/api/tetragon" +) + +const ( + ID = "CS_RT_FIFO_FILE_CREATE" + Name = "Creation of named pipe file" + Description = "The detector detects signs that a named pipe file was created. Applications can use such a file to bypass existing audit policies when exchanging data; for example, to create a reverse shell." + Version = 1 + Author = "Runtime Radar Team" + License = "Apache License 2.0" +) + +var ( + // triggerCriteria sets Trigger Criteria as map of events types to corresponding functions + // which will be used by Detector. If function names are not applicable for + // a particular event type, such as "PROCESS_EXEC", leave slice empty or use + // wildcard "*". + triggerCriteria = map[string][]string{ + "PROCESS_KPROBE": {"security_path_mknod"}, + + // Examples: + // + // "PROCESS_KPROBE": {"security_file_permission", "security_mmap_file", "security_path_truncate"}, + // In order to process all possible functions leave right-hand part empty or use wildcard "*": + // "PROCESS_EXEC": {}, + // same as: + // "PROCESS_EXEC": {"*"}, + } +) + +var ( + suspDirs = []glob.Glob{ + glob.MustCompile("/boot/*"), + glob.MustCompile("/dev/*"), + glob.MustCompile("/home/*"), + glob.MustCompile("/media/*"), + glob.MustCompile("/mnt/*"), + glob.MustCompile("/run/*"), + glob.MustCompile("/srv/*"), + glob.MustCompile("/tmp/*"), + glob.MustCompile("/var/*"), + } +) + +// main is required for TinyGo to compile to Wasm. +func main() { + api.RegisterDetector(Detector{}) +} + +type Detector struct{} + +func (d Detector) Info(ctx context.Context, req *api.InfoReq) (*api.InfoResp, error) { + return &api.InfoResp{ + Id: ID, + Name: Name, + Description: Description, + Version: Version, + Author: Author, + License: License, + }, nil +} + +func (d Detector) TriggerCriteria(ctx context.Context, req *api.TriggerCriteriaReq) (*api.TriggerCriteriaResp, error) { + resp := &api.TriggerCriteriaResp{ + Criteria: make(map[string]*api.TriggerCriteriaResp_FuncNames, len(triggerCriteria)), + } + + for k, v := range triggerCriteria { + resp.Criteria[k] = &api.TriggerCriteriaResp_FuncNames{FuncNames: v} + } + + return resp, nil +} + +func (d Detector) Detect(ctx context.Context, req *api.DetectReq) (*api.DetectResp, error) { + // Detector info added to DetectResp because detector info is always correlated to response, thus + // to avoid +1 Wasm call on detect. + resp := &api.DetectResp{ + // Default response indicates that nothing detected (this is redundant and put here just for reference, + // as Severity == api.DetectResp_NONE == 0 when omitted (default zero value)). + Severity: api.DetectResp_NONE, + } + + event := req.GetEvent().GetEvent() + + switch ev := event.(type) { + case *tetragon.GetEventsResponse_ProcessExec: + // Nothing here + case *tetragon.GetEventsResponse_ProcessExit: + // Nothing here + case *tetragon.GetEventsResponse_ProcessKprobe: + kprobe := ev.ProcessKprobe + function := kprobe.GetFunctionName() + args := kprobe.GetArgs() + path := "" + + switch function { + case "security_path_mknod": + if len(args) < 2 { + return nil, fmt.Errorf("unexpected args len, got %d, want >= 2", len(args)) + } + + path = args[0].GetPathArg().GetPath() + default: + return resp, nil + } + + for _, dir := range suspDirs { + if dir.Match(path) { + resp.Severity = api.DetectResp_HIGH // <-- threat detected + + return resp, nil + } + } + + return resp, nil + + case *tetragon.GetEventsResponse_ProcessTracepoint: + // Nothing here + } + + return resp, nil +} + +/* Example event (JSON): + +{ + "process_kprobe": { + "process": { + "exec_id": "dnNoLWs4cy5hcHBzZWMtc3RhbmQucHRzZWN1cml0eS5jbG91ZDoxODA3MTQ5NjA3OTAyMjM5MDoxOTE5OTAw", + "pid": 1919900, + "uid": 0, + "cwd": "/tmp", + "binary": "/usr/bin/mkfifo", + "arguments": "fifo-test", + "flags": "execve clone", + "start_time": "2025-12-10T08:23:14.409290982Z", + "auid": 4294967295, + "pod": { + "namespace": "default", + "name": "test-pod-debian", + "container": { + "id": "containerd://35ae39802175a6d328218c2d35d878bf0402b37a229ee28b2960161c567eca5d", + "name": "test-pod-debian", + "image": { + "id": "docker.io/library/debian@sha256:7e5bc0e499a8d50cb1e32287944a90b9ec8fd7d500673e75daff3f52882f5798", + "name": "docker.io/library/debian:12" + }, + "start_time": "2025-12-10T07:50:30Z", + "pid": 54, + "maybe_exec_probe": false, + "security_context": { + "privileged": false + } + }, + "pod_labels": {}, + "workload": "test-pod-debian", + "workload_kind": "Pod", + "pod_annotations": {} + }, + "docker": "35ae39802175a6d328218c2d35d878b", + "parent_exec_id": "dnNoLWs4cy5hcHBzZWMtc3RhbmQucHRzZWN1cml0eS5jbG91ZDoxODA3MTQ2MjM2MjUzMTYzMToxOTE5MjAw", + "refcnt": 1, + "cap": { + "permitted": [ + "CAP_CHOWN", + "DAC_OVERRIDE", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW", + "CAP_SYS_CHROOT", + "CAP_MKNOD", + "CAP_AUDIT_WRITE", + "CAP_SETFCAP" + ], + "effective": [ + "CAP_CHOWN", + "DAC_OVERRIDE", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW", + "CAP_SYS_CHROOT", + "CAP_MKNOD", + "CAP_AUDIT_WRITE", + "CAP_SETFCAP" + ], + "inheritable": [] + }, + "ns": { + "uts": { + "inum": 4026533802, + "is_host": false + }, + "ipc": { + "inum": 4026533803, + "is_host": false + }, + "mnt": { + "inum": 4026533805, + "is_host": false + }, + "pid": { + "inum": 4026533806, + "is_host": false + }, + "pid_for_children": { + "inum": 4026533806, + "is_host": false + }, + "net": { + "inum": 4026533163, + "is_host": false + }, + "time": { + "inum": 4026531834, + "is_host": true + }, + "time_for_children": { + "inum": 4026531834, + "is_host": true + }, + "cgroup": { + "inum": 4026533807, + "is_host": false + }, + "user": { + "inum": 4026531837, + "is_host": true + } + }, + "tid": 1919900, + "process_credentials": { + "uid": 0, + "gid": 0, + "euid": 0, + "egid": 0, + "suid": 0, + "sgid": 0, + "fsuid": 0, + "fsgid": 0, + "securebits": [], + "caps": null, + "user_ns": null + }, + "binary_properties": null, + "user": null, + "in_init_tree": false + }, + "parent": { + "exec_id": "dnNoLWs4cy5hcHBzZWMtc3RhbmQucHRzZWN1cml0eS5jbG91ZDoxODA3MTQ2MjM2MjUzMTYzMToxOTE5MjAw", + "pid": 1919200, + "uid": 0, + "cwd": "/", + "binary": "/usr/bin/bash", + "arguments": "", + "flags": "execve rootcwd", + "start_time": "2025-12-10T08:22:40.692800397Z", + "auid": 4294967295, + "pod": { + "namespace": "default", + "name": "test-pod-debian", + "container": { + "id": "containerd://35ae39802175a6d328218c2d35d878bf0402b37a229ee28b2960161c567eca5d", + "name": "test-pod-debian", + "image": { + "id": "docker.io/library/debian@sha256:7e5bc0e499a8d50cb1e32287944a90b9ec8fd7d500673e75daff3f52882f5798", + "name": "docker.io/library/debian:12" + }, + "start_time": "2025-12-10T07:50:30Z", + "pid": 45, + "maybe_exec_probe": false, + "security_context": { + "privileged": false + } + }, + "pod_labels": {}, + "workload": "test-pod-debian", + "workload_kind": "Pod", + "pod_annotations": {} + }, + "docker": "35ae39802175a6d328218c2d35d878b", + "parent_exec_id": "dnNoLWs4cy5hcHBzZWMtc3RhbmQucHRzZWN1cml0eS5jbG91ZDoxODA3MTQ2MjM2MTU0OTAwNjoxOTE5MjAw", + "refcnt": 0, + "cap": { + "permitted": [ + "CAP_CHOWN", + "DAC_OVERRIDE", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW", + "CAP_SYS_CHROOT", + "CAP_MKNOD", + "CAP_AUDIT_WRITE", + "CAP_SETFCAP" + ], + "effective": [ + "CAP_CHOWN", + "DAC_OVERRIDE", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW", + "CAP_SYS_CHROOT", + "CAP_MKNOD", + "CAP_AUDIT_WRITE", + "CAP_SETFCAP" + ], + "inheritable": [] + }, + "ns": { + "uts": { + "inum": 4026533802, + "is_host": false + }, + "ipc": { + "inum": 4026533803, + "is_host": false + }, + "mnt": { + "inum": 4026533805, + "is_host": false + }, + "pid": { + "inum": 4026533806, + "is_host": false + }, + "pid_for_children": { + "inum": 4026533806, + "is_host": false + }, + "net": { + "inum": 4026533163, + "is_host": false + }, + "time": { + "inum": 4026531834, + "is_host": true + }, + "time_for_children": { + "inum": 4026531834, + "is_host": true + }, + "cgroup": { + "inum": 4026533807, + "is_host": false + }, + "user": { + "inum": 4026531837, + "is_host": true + } + }, + "tid": 1919200, + "process_credentials": { + "uid": 0, + "gid": 0, + "euid": 0, + "egid": 0, + "suid": 0, + "sgid": 0, + "fsuid": 0, + "fsgid": 0, + "securebits": [], + "caps": null, + "user_ns": null + }, + "binary_properties": null, + "user": null, + "in_init_tree": false + }, + "function_name": "security_path_mknod", + "args": [ + { + "path_arg": { + "mount": "", + "path": "/tmp/fifo-test", + "flags": "", + "permission": "---------" + }, + "label": "new file path" + }, + { + "uint_arg": 4534, + "label": "new file mode" + } + ], + "return": null, + "action": "KPROBE_ACTION_POST", + "kernel_stack_trace": [], + "policy_name": "io-streams", + "return_action": "KPROBE_ACTION_POST", + "message": "", + "tags": [], + "user_stack_trace": [], + "ancestors": [] + }, + "node_name": "experts-k8s-cs", + "time": "2025-12-10T08:23:14.410150687Z", + "aggregation_info": null, + "cluster_name": "", + "node_labels": { + "beta.kubernetes.io/arch": "amd64", + "beta.kubernetes.io/os": "linux", + "kubernetes.io/arch": "amd64", + "kubernetes.io/hostname": "experts-k8s-cs", + "kubernetes.io/os": "linux", + "node-role.kubernetes.io/control-plane": "", + "node.kubernetes.io/exclude-from-external-load-balancers": "" + } +} + +*/ diff --git a/event-processor/detector/wasm/CS_RT_HARDLINK_CREATE/main.go b/event-processor/detector/wasm/CS_RT_HARDLINK_CREATE/main.go new file mode 100644 index 00000000..2c0d37ca --- /dev/null +++ b/event-processor/detector/wasm/CS_RT_HARDLINK_CREATE/main.go @@ -0,0 +1,418 @@ +//go:build tinygo.wasm + +package main + +import ( + "context" + + "github.com/runtime-radar/runtime-radar/event-processor/detector/api" + "github.com/runtime-radar/runtime-radar/event-processor/detector/api/tetragon" +) + +const ( + ID = "CS_RT_HARDLINK_CREATE" + Name = "Creation of hard links to sensitive system files" + Description = "The detector detects if hard links to sensitive system files were created, which may indicate an attacker's attempt to bypass the existing file monitoring rules." + Version = 1 + Author = "Runtime Radar Team" + License = "Apache License 2.0" +) + +var ( + // triggerCriteria sets Trigger Criteria as map of events types to corresponding functions + // which will be used by Detector. If function names are not applicable for + // a particular event type, such as "PROCESS_EXEC", leave slice empty or use + // wildcard "*". + triggerCriteria = map[string][]string{ + "PROCESS_KPROBE": {"security_path_link"}, + + // Examples: + // + // "PROCESS_KPROBE": {"security_file_permission", "security_mmap_file", "security_path_truncate"}, + // In order to process all possible functions leave right-hand part empty or use wildcard "*": + // "PROCESS_EXEC": {}, + // same as: + // "PROCESS_EXEC": {"*"}, + } +) + +// main is required for TinyGo to compile to Wasm. +func main() { + api.RegisterDetector(Detector{}) +} + +type Detector struct{} + +func (d Detector) Info(ctx context.Context, req *api.InfoReq) (*api.InfoResp, error) { + return &api.InfoResp{ + Id: ID, + Name: Name, + Description: Description, + Version: Version, + Author: Author, + License: License, + }, nil +} + +func (d Detector) TriggerCriteria(ctx context.Context, req *api.TriggerCriteriaReq) (*api.TriggerCriteriaResp, error) { + resp := &api.TriggerCriteriaResp{ + Criteria: make(map[string]*api.TriggerCriteriaResp_FuncNames, len(triggerCriteria)), + } + + for k, v := range triggerCriteria { + resp.Criteria[k] = &api.TriggerCriteriaResp_FuncNames{FuncNames: v} + } + + return resp, nil +} + +func (d Detector) Detect(ctx context.Context, req *api.DetectReq) (*api.DetectResp, error) { + // Detector info added to DetectResp because detector info is always correlated to response, thus + // to avoid +1 Wasm call on detect. + resp := &api.DetectResp{ + // Default response indicates that nothing detected (this is redundant and put here just for reference, + // as Severity == api.DetectResp_NONE == 0 when omitted (default zero value)). + Severity: api.DetectResp_NONE, + } + + event := req.GetEvent().GetEvent() + + switch ev := event.(type) { + case *tetragon.GetEventsResponse_ProcessExec: + // Nothing here + case *tetragon.GetEventsResponse_ProcessExit: + // Nothing here + case *tetragon.GetEventsResponse_ProcessKprobe: + kprobe := ev.ProcessKprobe + function := kprobe.GetFunctionName() + + switch function { + case "security_path_link": + resp.Severity = api.DetectResp_HIGH // <-- threat detected + + return resp, nil + default: + return resp, nil + } + + return resp, nil + + case *tetragon.GetEventsResponse_ProcessTracepoint: + // Nothing here + } + + return resp, nil +} + +/* Example event (JSON): +{ + "process_kprobe": { + "process": { + "exec_id": "cHRjcy1tYXN0ZXItbm9kZTo3NzM0MzkzMDM4MDk5MzMyOjUyMDAzOA==", + "pid": 520038, + "uid": 0, + "cwd": "/", + "binary": "/usr/bin/ln", + "arguments": "/etc/shadow /tmp/token", + "flags": "execve rootcwd clone", + "start_time": "2025-12-10T07:55:01.513635226Z", + "auid": 4294967295, + "pod": { + "namespace": "default", + "name": "test-pod-debian", + "container": { + "id": "containerd://30af0bcafcdec812b8ed60f86b3ccdbd54da68e48152bedd79e4aa1751acdcb4", + "name": "test-pod-debian", + "image": { + "id": "docker.io/library/debian@sha256:2bc5c236e9b262645a323e9088dfa3bb1ecb16cc75811daf40a23a824d665be9", + "name": "docker.io/library/debian:12.2-slim" + }, + "start_time": "2025-09-11T19:30:01Z", + "pid": 144, + "maybe_exec_probe": false, + "security_context": { + "privileged": false + } + }, + "pod_labels": {}, + "workload": "test-pod-debian", + "workload_kind": "Pod", + "pod_annotations": {} + }, + "docker": "30af0bcafcdec812b8ed60f86b3ccdb", + "parent_exec_id": "cHRjcy1tYXN0ZXItbm9kZTo3NzMwOTU4MTU3OTcwMTY4OjMzMDIyNw==", + "refcnt": 1, + "cap": { + "permitted": [ + "CAP_CHOWN", + "DAC_OVERRIDE", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW", + "CAP_SYS_CHROOT", + "CAP_MKNOD", + "CAP_AUDIT_WRITE", + "CAP_SETFCAP" + ], + "effective": [ + "CAP_CHOWN", + "DAC_OVERRIDE", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW", + "CAP_SYS_CHROOT", + "CAP_MKNOD", + "CAP_AUDIT_WRITE", + "CAP_SETFCAP" + ], + "inheritable": [] + }, + "ns": { + "uts": { + "inum": 4026534078, + "is_host": false + }, + "ipc": { + "inum": 4026534079, + "is_host": false + }, + "mnt": { + "inum": 4026534081, + "is_host": false + }, + "pid": { + "inum": 4026534082, + "is_host": false + }, + "pid_for_children": { + "inum": 4026534082, + "is_host": false + }, + "net": { + "inum": 4026534018, + "is_host": false + }, + "time": { + "inum": 4026531834, + "is_host": true + }, + "time_for_children": { + "inum": 4026531834, + "is_host": true + }, + "cgroup": { + "inum": 4026534083, + "is_host": false + }, + "user": { + "inum": 4026531837, + "is_host": true + } + }, + "tid": 520038, + "process_credentials": { + "uid": 0, + "gid": 0, + "euid": 0, + "egid": 0, + "suid": 0, + "sgid": 0, + "fsuid": 0, + "fsgid": 0, + "securebits": [], + "caps": null, + "user_ns": null + }, + "binary_properties": null, + "user": null, + "in_init_tree": false + }, + "parent": { + "exec_id": "cHRjcy1tYXN0ZXItbm9kZTo3NzMwOTU4MTU3OTcwMTY4OjMzMDIyNw==", + "pid": 330227, + "uid": 0, + "cwd": "/", + "binary": "/usr/bin/bash", + "arguments": "", + "flags": "execve rootcwd", + "start_time": "2025-12-10T06:57:46.633506777Z", + "auid": 4294967295, + "pod": { + "namespace": "default", + "name": "test-pod-debian", + "container": { + "id": "containerd://30af0bcafcdec812b8ed60f86b3ccdbd54da68e48152bedd79e4aa1751acdcb4", + "name": "test-pod-debian", + "image": { + "id": "docker.io/library/debian@sha256:2bc5c236e9b262645a323e9088dfa3bb1ecb16cc75811daf40a23a824d665be9", + "name": "docker.io/library/debian:12.2-slim" + }, + "start_time": "2025-09-11T19:30:01Z", + "pid": 104, + "maybe_exec_probe": false, + "security_context": { + "privileged": false + } + }, + "pod_labels": {}, + "workload": "test-pod-debian", + "workload_kind": "Pod", + "pod_annotations": {} + }, + "docker": "30af0bcafcdec812b8ed60f86b3ccdb", + "parent_exec_id": "cHRjcy1tYXN0ZXItbm9kZTo3NzMwOTU4MTUzMTU4MzQ1OjMzMDIyNw==", + "refcnt": 0, + "cap": { + "permitted": [ + "CAP_CHOWN", + "DAC_OVERRIDE", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW", + "CAP_SYS_CHROOT", + "CAP_MKNOD", + "CAP_AUDIT_WRITE", + "CAP_SETFCAP" + ], + "effective": [ + "CAP_CHOWN", + "DAC_OVERRIDE", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW", + "CAP_SYS_CHROOT", + "CAP_MKNOD", + "CAP_AUDIT_WRITE", + "CAP_SETFCAP" + ], + "inheritable": [] + }, + "ns": { + "uts": { + "inum": 4026534078, + "is_host": false + }, + "ipc": { + "inum": 4026534079, + "is_host": false + }, + "mnt": { + "inum": 4026534081, + "is_host": false + }, + "pid": { + "inum": 4026534082, + "is_host": false + }, + "pid_for_children": { + "inum": 4026534082, + "is_host": false + }, + "net": { + "inum": 4026534018, + "is_host": false + }, + "time": { + "inum": 4026531834, + "is_host": true + }, + "time_for_children": { + "inum": 4026531834, + "is_host": true + }, + "cgroup": { + "inum": 4026534083, + "is_host": false + }, + "user": { + "inum": 4026531837, + "is_host": true + } + }, + "tid": 330227, + "process_credentials": { + "uid": 0, + "gid": 0, + "euid": 0, + "egid": 0, + "suid": 0, + "sgid": 0, + "fsuid": 0, + "fsgid": 0, + "securebits": [], + "caps": null, + "user_ns": null + }, + "binary_properties": null, + "user": null, + "in_init_tree": false + }, + "function_name": "security_path_link", + "args": [ + { + "path_arg": { + "mount": "", + "path": "/etc/shadow", + "flags": "", + "permission": "-rw-r-----" + }, + "label": "existing file" + }, + { + "path_arg": { + "mount": "", + "path": "/tmp/token", + "flags": "", + "permission": "---------" + }, + "label": "new hard link" + } + ], + "return": { + "int_arg": 0, + "label": "" + }, + "action": "KPROBE_ACTION_POST", + "kernel_stack_trace": [], + "policy_name": "file-monitoring", + "return_action": "KPROBE_ACTION_POST", + "message": "", + "tags": [], + "user_stack_trace": [], + "ancestors": [] + }, + "node_name": "experts-k8s-cs", + "time": "2025-12-10T07:55:01.526266305Z", + "aggregation_info": null, + "cluster_name": "", + "node_labels": { + "beta.kubernetes.io/arch": "amd64", + "beta.kubernetes.io/os": "linux", + "kubernetes.io/arch": "amd64", + "kubernetes.io/hostname": "experts-k8s-cs", + "kubernetes.io/os": "linux", + "node-role.kubernetes.io/control-plane": "", + "node.kubernetes.io/exclude-from-external-load-balancers": "" + } +} +*/ diff --git a/runtime-monitor/pkg/model/config.go b/runtime-monitor/pkg/model/config.go index 2d2a8e68..77e55f08 100644 --- a/runtime-monitor/pkg/model/config.go +++ b/runtime-monitor/pkg/model/config.go @@ -18,8 +18,8 @@ var ( //go:embed tracingpolicy/connect.yaml connect string - //go:embed tracingpolicy/process-credentials.yaml - processCredentials string + //go:embed tracingpolicy/permissions.yaml + permissions string //go:embed tracingpolicy/file-monitoring.yaml fileMonitoring string @@ -36,8 +36,8 @@ var ( //go:embed tracingpolicy/listen-socket.yaml listenSocket string - //go:embed tracingpolicy/dup.yaml - dup string + //go:embed tracingpolicy/io-streams.yaml + ioStreams string //go:embed tracingpolicy/io-uring.yml ioUring string @@ -55,10 +55,10 @@ var ( Yaml: connect, Enabled: false, }, - "process-credentials": { - Name: "Privilege escalation", - Description: "This source tracks the commit_creds function allowing detection of privilege escalation, including superuser (root) privileges.", - Yaml: processCredentials, + "permissions": { + Name: "Actions with access permissions for files and processes", + Description: "The source tracks calls of the Linux kernel function commit_creds(), which could indicate attempts to elevate process privileges, including obtaining superuser (root) permissions. In addition, the source tracks calls of the LSM function security_path_chmod() with a set of permissions that includes execution permissions", + Yaml: permissions, Enabled: false, }, "file-monitoring": { @@ -91,10 +91,10 @@ var ( Yaml: listenSocket, Enabled: false, }, - "dup": { - Name: "Copying file descriptors", - Description: "This source monitors calls to functions that perform file descriptor copying. Currently, it tracks the copying of the standard input (Stdin) file descriptor, which may indicate an attempt to build a pipe required for the operation of various hacking tools.", - Yaml: dup, + "io-streams": { + Name: "Actions with standard I/O streams", + Description: "The source tracks calls of the Linux kernel function do_dup2(), which copies the standard input file descriptor (STDIN) as well as creation of a named pipe file (S_IFIFO) via the LSM function security_path_mknod(). Such actions often indicate that an attacker is attempting to start a reverse shell, a hidden communication channel, or another attack tool.", + Yaml: ioStreams, Enabled: false, }, "io-uring": { diff --git a/runtime-monitor/pkg/model/tracingpolicy/dup.yaml b/runtime-monitor/pkg/model/tracingpolicy/dup.yaml deleted file mode 100644 index 824efbbc..00000000 --- a/runtime-monitor/pkg/model/tracingpolicy/dup.yaml +++ /dev/null @@ -1,40 +0,0 @@ -apiVersion: cilium.io/v1alpha1 -kind: TracingPolicy -metadata: - name: "dup" -spec: - kprobes: - # SYNOPSIS - # int do_dup2(struct files_struct *files, struct file *file, unsigned fd, unsigned flags) - # @files: files related to the new fd (fd that will become a copy) - # @file: file related to the old fd (fd being copied) - # @fd: file descriptor which needs to be copied - # @flags: flags which tweak function behaviour - # - # DESCRIPTION - # This function creates a copy of the file descriptor (fd). - # - # RETURN VALUE - # On success, this function returns the new descriptor. If there is an error, the error code is returned. - # - # SYSCALLS - # dup2(), dup3() - # - - call: "do_dup2" - syscall: false - return: true - args: - - index: 1 - type: "file" - - index: 2 - type: "int" - label: "fd" - returnArg: - index: 0 - type: "int" - selectors: - - matchArgs: - - index: 2 - operator: "Equal" - values: - - "0" # stdin diff --git a/runtime-monitor/pkg/model/tracingpolicy/file-monitoring.yaml b/runtime-monitor/pkg/model/tracingpolicy/file-monitoring.yaml index ea26cfa2..15b4d8ac 100644 --- a/runtime-monitor/pkg/model/tracingpolicy/file-monitoring.yaml +++ b/runtime-monitor/pkg/model/tracingpolicy/file-monitoring.yaml @@ -4,314 +4,386 @@ metadata: name: "file-monitoring" spec: kprobes: - # SYNOPSIS - # int security_file_permission(struct file *file, int mask) - # @file: file - # @mask: requested permissions - # - # DESCRIPTION - # Check file permissions before accessing an open file. This hook is called - # by various operations that read or write files. - # - # RETURN VALUE - # Returns 0 if permission is granted. (https://elixir.bootlin.com/linux/v6.8-rc7/source/security/security.c#L2666) - # - # READ SYSCALLS - # read(), readv(), preadv(), preadv2(), pread64(), - # ioctl(), sendfile(), sendfile64(), copy_file_range(), - # old_readdir(), getdents(), getdents64() - # - # WRITE/MODIFY SYSCALLS - # write(), writev(), pwritev(), pwritev2(), pwrite64() - # ioctl(), splice(), madvise(), process_madvise(), fallocate() - # - - call: "security_file_permission" - syscall: false - return: true - args: - - index: 0 - type: "file" - - index: 1 - type: "int" - returnArg: - index: 0 - type: "int" - selectors: - # READ - - matchNamespaces: - - namespace: Pid - operator: NotIn - values: - - "host_ns" - matchArgs: - - index: 0 - operator: "Prefix" - values: - - "/boot/" - - "/root/.ssh" - - "/etc/shadow" - - "/etc/profile" - - "/etc/sudoers" - - "/etc/pam." - - "/etc/bashrc" - - "/etc/csh.cshrc" - - "/etc/csh.login" - - "/etc/kubernetes/pki/" - - "/etc/security/pwquality.conf" - - "/run/secrets/" - - "/proc/" - - index: 1 - operator: "Equal" - values: - - "4" # MAY_READ - - matchNamespaces: - - namespace: Pid - operator: NotIn - values: - - "host_ns" - matchArgs: - - index: 0 - operator: "Postfix" - values: - - ".bashrc" - - ".bash_profile" - - ".bash_login" - - ".bash_logout" - - ".cshrc" - - ".cshdirs" - - ".profile" - - ".login" - - ".logout" - - ".history" - - "-release" - - "environ" - - index: 1 - operator: "Equal" - values: - - "4" # MAY_READ - - matchNamespaces: - - namespace: Pid - operator: NotIn - values: - - "host_ns" - # WRITE/MODIFY - matchArgs: - - index: 0 - operator: "Prefix" - values: - - "/etc/" - - "/boot/" - - "/lib" - - "/bin/" - - "/sbin/" - - "/usr/lib" - - "/usr/local/lib" - - "/usr/local/sbin/" - - "/usr/local/bin/" - - "/usr/bin/" - - "/usr/sbin/" - - "/root/" - - "/home/" - - index: 1 - operator: "Equal" - values: - - "2" # MAY_WRITE - - matchNamespaces: - - namespace: Pid - operator: NotIn - values: - - "host_ns" - matchArgs: # CVE-2022-0492 - - index: 0 - operator: "Postfix" - values: - - "/notify_on_release" - - "/release_agent" - - index: 1 - operator: "Equal" - values: - - "2" # MAY_WRITE - - # SYNOPSIS - # int security_mmap_file(struct file *file, unsigned long prot, unsigned long flags) - # @file: file - # @prot: desired memory protection of the mapping (bitwise OR) - # - PROT_READ: memory pages with mapped @file may be read - # - PROT_WRITE: memory pages with mapped @file may be written - # @flags: mapping visibility to other processes (bitwise OR) - # - # DESCRIPTION - # Check permissions for a mmap operation. - # - # RETURN VALUE - # Returns 0 if permission is granted. (https://elixir.bootlin.com/linux/v6.8-rc7/source/security/security.c#L2792) - # - # SYSCALLS - # shmat(), ipc(), mmap_pgoff(), old_mmap(), mmap() - # - - call: "security_mmap_file" - syscall: false - return: true - args: - - index: 0 - type: "file" - - index: 1 - type: "uint32" - - index: 2 - type: "uint32" - returnArg: - index: 0 - type: "int" - selectors: - # READ - - matchNamespaces: - - namespace: Pid - operator: NotIn - values: - - "host_ns" - matchArgs: - - index: 0 - operator: "Prefix" - values: - - "/boot/" - - "/root/.ssh" - - "/etc/shadow" - - "/etc/profile" - - "/etc/sudoers" - - "/etc/pam." - - "/etc/bashrc" - - "/etc/csh.cshrc" - - "/etc/csh.login" - - "/etc/kubernetes/pki/" - - "/etc/security/pwquality.conf" - - "/run/secrets/" - - "/proc/" - - "/tmp/nginx/client-body/" - - index: 1 - operator: "Mask" - values: - - "1" # PROT_READ - - matchNamespaces: - - namespace: Pid - operator: NotIn - values: - - "host_ns" - matchArgs: - - index: 0 - operator: "Postfix" - values: - - ".bashrc" - - ".bash_profile" - - ".bash_login" - - ".bash_logout" - - ".cshrc" - - ".cshdirs" - - ".profile" - - ".login" - - ".logout" - - ".history" - - "-release" - - "environ" - - index: 1 - operator: "Mask" - values: - - "1" # PROT_READ - # WRITE/MODIFY - - matchNamespaces: - - namespace: Pid - operator: NotIn - values: - - "host_ns" - matchArgs: - - index: 0 - operator: "Prefix" - values: - - "/etc/" - - "/boot/" - - "/lib" - - "/bin/" - - "/sbin/" - - "/usr/lib" - - "/usr/local/lib" - - "/usr/local/sbin/" - - "/usr/local/bin/" - - "/usr/bin/" - - "/usr/sbin/" - - "/root/" - - "/home/" - - index: 1 - operator: "Mask" - values: - - "2" # PROT_WRITE - - matchNamespaces: - - namespace: Pid - operator: NotIn - values: - - "host_ns" - matchArgs: # CVE-2022-0492 - - index: 0 - operator: "Postfix" - values: - - "/notify_on_release" - - "/release_agent" - - index: 1 - operator: "Mask" - values: - - "2" # PROT_WRITE - # SYNOPSIS - # int security_path_truncate(const struct path *path) - # @path: file - # - # DESCRIPTION - # Check permission before truncating the file indicated by path. - # - # RETURN VALUE - # Returns 0 if permission is granted. (https://elixir.bootlin.com/linux/v6.8-rc7/source/security/security.c#L1928) - # - # SYSCALLS - # truncate() - # - - call: "security_path_truncate" - syscall: false - return: true - args: - - index: 0 - type: "path" - returnArg: - index: 0 - type: "int" - selectors: - # MODIFY - - matchNamespaces: - - namespace: Pid - operator: NotIn - values: - - "host_ns" - matchArgs: - - index: 0 - operator: "Prefix" - values: - - "/etc/" - - "/boot/" - - "/lib" - - "/bin/" - - "/sbin/" - - "/usr/lib" - - "/usr/local/lib" - - "/usr/local/sbin/" - - "/usr/local/bin/" - - "/usr/bin/" - - "/usr/sbin/" - - "/root/" - - "/home/" - - matchNamespaces: - - namespace: Pid - operator: NotIn - values: - - "host_ns" - matchArgs: # CVE-2022-0492 - - index: 0 - operator: "Postfix" - values: - - "/notify_on_release" - - "/release_agent" + # SYNOPSIS + # int security_file_permission(struct file *file, int mask) + # @file: file + # @mask: requested permissions + # + # DESCRIPTION + # Check file permissions before accessing an open file. This hook is called + # by various operations that read or write files. + # + # RETURN VALUE + # Returns 0 if permission is granted. (https://elixir.bootlin.com/linux/v6.8-rc7/source/security/security.c#L2666) + # + # READ SYSCALLS + # read(), readv(), preadv(), preadv2(), pread64(), + # ioctl(), sendfile(), sendfile64(), copy_file_range(), + # old_readdir(), getdents(), getdents64() + # + # WRITE/MODIFY SYSCALLS + # write(), writev(), pwritev(), pwritev2(), pwrite64() + # ioctl(), splice(), madvise(), process_madvise(), fallocate() + # + - call: "security_file_permission" + syscall: false + return: true + args: + - index: 0 + type: "file" + - index: 1 + type: "int" + returnArg: + index: 0 + type: "int" + selectors: + # READ + - matchNamespaces: + - namespace: Pid + operator: NotIn + values: + - "host_ns" + matchArgs: + - index: 0 + operator: "Prefix" + values: + - "/boot/" + - "/root/.ssh" + - "/etc/shadow" + - "/etc/profile" + - "/etc/sudoers" + - "/etc/pam." + - "/etc/bashrc" + - "/etc/csh.cshrc" + - "/etc/csh.login" + - "/etc/kubernetes/pki/" + - "/etc/security/pwquality.conf" + - "/run/secrets/" + - "/proc/" + - index: 1 + operator: "Equal" + values: + - "4" # MAY_READ + - matchNamespaces: + - namespace: Pid + operator: NotIn + values: + - "host_ns" + matchArgs: + - index: 0 + operator: "Postfix" + values: + - ".bashrc" + - ".bash_profile" + - ".bash_login" + - ".bash_logout" + - ".cshrc" + - ".cshdirs" + - ".profile" + - ".login" + - ".logout" + - ".history" + - "-release" + - "environ" + - index: 1 + operator: "Equal" + values: + - "4" # MAY_READ + - matchNamespaces: + - namespace: Pid + operator: NotIn + values: + - "host_ns" + # WRITE/MODIFY + matchArgs: + - index: 0 + operator: "Prefix" + values: + - "/etc/" + - "/boot/" + - "/lib" + - "/bin/" + - "/sbin/" + - "/usr/lib" + - "/usr/local/lib" + - "/usr/local/sbin/" + - "/usr/local/bin/" + - "/usr/bin/" + - "/usr/sbin/" + - "/root/" + - "/home/" + - index: 1 + operator: "Equal" + values: + - "2" # MAY_WRITE + - matchNamespaces: + - namespace: Pid + operator: NotIn + values: + - "host_ns" + matchArgs: # CVE-2022-0492 + - index: 0 + operator: "Postfix" + values: + - "/notify_on_release" + - "/release_agent" + - index: 1 + operator: "Equal" + values: + - "2" # MAY_WRITE + + # SYNOPSIS + # int security_mmap_file(struct file *file, unsigned long prot, unsigned long flags) + # @file: file + # @prot: desired memory protection of the mapping (bitwise OR) + # - PROT_READ: memory pages with mapped @file may be read + # - PROT_WRITE: memory pages with mapped @file may be written + # @flags: mapping visibility to other processes (bitwise OR) + # + # DESCRIPTION + # Check permissions for a mmap operation. + # + # RETURN VALUE + # Returns 0 if permission is granted. (https://elixir.bootlin.com/linux/v6.8-rc7/source/security/security.c#L2792) + # + # SYSCALLS + # shmat(), ipc(), mmap_pgoff(), old_mmap(), mmap() + # + - call: "security_mmap_file" + syscall: false + return: true + args: + - index: 0 + type: "file" + - index: 1 + type: "uint32" + - index: 2 + type: "uint32" + returnArg: + index: 0 + type: "int" + selectors: + # READ + - matchNamespaces: + - namespace: Pid + operator: NotIn + values: + - "host_ns" + matchArgs: + - index: 0 + operator: "Prefix" + values: + - "/boot/" + - "/root/.ssh" + - "/etc/shadow" + - "/etc/profile" + - "/etc/sudoers" + - "/etc/pam." + - "/etc/bashrc" + - "/etc/csh.cshrc" + - "/etc/csh.login" + - "/etc/kubernetes/pki/" + - "/etc/security/pwquality.conf" + - "/run/secrets/" + - "/proc/" + - "/tmp/nginx/client-body/" + - index: 1 + operator: "Mask" + values: + - "1" # PROT_READ + - matchNamespaces: + - namespace: Pid + operator: NotIn + values: + - "host_ns" + matchArgs: + - index: 0 + operator: "Postfix" + values: + - ".bashrc" + - ".bash_profile" + - ".bash_login" + - ".bash_logout" + - ".cshrc" + - ".cshdirs" + - ".profile" + - ".login" + - ".logout" + - ".history" + - "-release" + - "environ" + - index: 1 + operator: "Mask" + values: + - "1" # PROT_READ + # WRITE/MODIFY + - matchNamespaces: + - namespace: Pid + operator: NotIn + values: + - "host_ns" + matchArgs: + - index: 0 + operator: "Prefix" + values: + - "/etc/" + - "/boot/" + - "/lib" + - "/bin/" + - "/sbin/" + - "/usr/lib" + - "/usr/local/lib" + - "/usr/local/sbin/" + - "/usr/local/bin/" + - "/usr/bin/" + - "/usr/sbin/" + - "/root/" + - "/home/" + - index: 1 + operator: "Mask" + values: + - "2" # PROT_WRITE + - matchNamespaces: + - namespace: Pid + operator: NotIn + values: + - "host_ns" + matchArgs: # CVE-2022-0492 + - index: 0 + operator: "Postfix" + values: + - "/notify_on_release" + - "/release_agent" + - index: 1 + operator: "Mask" + values: + - "2" # PROT_WRITE + # SYNOPSIS + # int security_path_truncate(const struct path *path) + # @path: file + # + # DESCRIPTION + # Check permission before truncating the file indicated by path. + # + # RETURN VALUE + # Returns 0 if permission is granted. (https://elixir.bootlin.com/linux/v6.8-rc7/source/security/security.c#L1928) + # + # SYSCALLS + # truncate() + # + - call: "security_path_truncate" + syscall: false + return: true + args: + - index: 0 + type: "path" + returnArg: + index: 0 + type: "int" + selectors: + # MODIFY + - matchNamespaces: + - namespace: Pid + operator: NotIn + values: + - "host_ns" + matchArgs: + - index: 0 + operator: "Prefix" + values: + - "/etc/" + - "/boot/" + - "/lib" + - "/bin/" + - "/sbin/" + - "/usr/lib" + - "/usr/local/lib" + - "/usr/local/sbin/" + - "/usr/local/bin/" + - "/usr/bin/" + - "/usr/sbin/" + - "/root/" + - "/home/" + - matchNamespaces: + - namespace: Pid + operator: NotIn + values: + - "host_ns" + matchArgs: # CVE-2022-0492 + - index: 0 + operator: "Postfix" + values: + - "/notify_on_release" + - "/release_agent" + # SYNOPSIS + # int security_path_link(struct dentry *old_dentry, const struct path *new_dir, struct dentry *new_dentry) + # @old_dentry: existing file + # @new_dir: new parent directory + # @new_dentry: new hard link + # + # DESCRIPTION + # Check if creating a hard link is allowed. + # + # RETURN VALUE + # Returns 0 if permission is granted. (https://elixir.bootlin.com/linux/v6.16-rc5/source/security/security.c#L1987) + # + # SYSCALLS + # link, linkat + # + - call: "security_path_link" + syscall: false + return: true + args: + - index: 0 + type: "dentry" + label: "existing file" + - index: 2 + type: "dentry" + label: "new hard link" + returnArg: + index: 0 + type: "int" + selectors: + - matchNamespaces: + - namespace: Pid + operator: NotIn + values: + - "host_ns" + matchArgs: + - index: 0 + operator: "Prefix" + values: + - "/etc/" + - "/boot/" + - "/root/" + - "/run/secrets/" + - "/lib/" + - "/bin/" + - "/sbin/" + - "/usr/lib/" + - "/usr/local/lib/" + - "/usr/local/sbin/" + - "/usr/local/bin/" + - "/usr/bin/" + - "/usr/sbin/" + - "/home/" + - matchNamespaces: + - namespace: Pid + operator: NotIn + values: + - "host_ns" + matchArgs: + - index: 0 + operator: "Postfix" + values: + - ".bashrc" + - ".bash_profile" + - ".bash_login" + - ".bash_logout" + - ".cshrc" + - ".cshdirs" + - ".profile" + - ".login" + - ".logout" + - ".history" + - "-release" diff --git a/runtime-monitor/pkg/model/tracingpolicy/io-streams.yaml b/runtime-monitor/pkg/model/tracingpolicy/io-streams.yaml new file mode 100644 index 00000000..341b58f3 --- /dev/null +++ b/runtime-monitor/pkg/model/tracingpolicy/io-streams.yaml @@ -0,0 +1,72 @@ +apiVersion: cilium.io/v1alpha1 +kind: TracingPolicy +metadata: + name: "io-streams" +spec: + kprobes: + # SYNOPSIS + # int do_dup2(struct files_struct *files, struct file *file, unsigned fd, unsigned flags) + # @files: files related to the new fd (fd that will become a copy) + # @file: file related to the old fd (fd being copied) + # @fd: file descriptor which needs to be copied + # @flags: flags for tweaking function behavior + # + # DESCRIPTION + # This function creates a copy of the file descriptor (fd). + # + # RETURN VALUE + # On success, this function returns the new descriptor. If there is an error, the error code is returned. + # + # SYSCALLS + # dup2(), dup3() + # + - call: "do_dup2" + syscall: false + return: true + args: + - index: 1 + type: "file" + - index: 2 + type: "int" + label: "fd" + returnArg: + index: 0 + type: "int" + selectors: + - matchArgs: + - index: 2 + operator: "Equal" + values: + - "0" # stdin + # SYNOPSIS + # int security_path_mknod(const struct path *dir, struct dentry *dentry, umode_t mode, unsigned int dev) + # @dir: parent directory path + # @dentry: new file path + # @umode_t: new file mode + # @dev: device number + # + # DESCRIPTION + # Check permissions when creating a file (regular and special ones). + # + # RETURN VALUE + # Returns 0 if permission is granted + # + # SYSCALLS + # mknod(), mknodat() + # + - call: "security_path_mknod" + syscall: false + return: false + args: + - index: 1 + type: "dentry" + label: "new file path" + - index: 2 + type: "uint32" # WA for broken uint16 argument type in actual releases + label: "new file mode" + selectors: + - matchArgs: + - index: 2 + operator: "Mask" + values: + - "010000" # S_IFIFO file type mask in octal form diff --git a/runtime-monitor/pkg/model/tracingpolicy/permissions.yaml b/runtime-monitor/pkg/model/tracingpolicy/permissions.yaml new file mode 100644 index 00000000..6433d350 --- /dev/null +++ b/runtime-monitor/pkg/model/tracingpolicy/permissions.yaml @@ -0,0 +1,71 @@ +apiVersion: cilium.io/v1alpha1 +kind: TracingPolicy +metadata: + name: "permissions" +spec: + kprobes: + # SYNOPSIS + # int commit_creds(struct cred *new) + # @new: The credentials to be assigned + # + # DESCRIPTION + # Install a new set of credentials to the current task, using RCU to replace + # the old set. Both the objective and the subjective credentials pointers are + # updated. This function may not be called if the subjective credentials are + # in an overridden state. + # + # This function eats the caller's reference to the new credentials. + # + # RETURN VALUE + # Always returns 0 thus allowing this function to be tail-called at the end of, say, sys_setgid(). + # + - call: "commit_creds" + syscall: false + return: false + args: + - index: 0 # The new credentials to apply + type: "cred" + label: "the new credentials to apply" + selectors: + - matchNamespaces: + - namespace: Pid + operator: NotIn + values: + - "host_ns" + matchActions: + - action: Post + rateLimit: "1m" + # SYNOPSIS + # int security_path_chmod(const struct path *path, umode_t mode) - Check if changing the file's mode is allowed + # @path: file path + # @mode: file mode + # + # DESCRIPTION + # Check for permission to change a mode of the file @path. The new mode is + # specified in @mode which is a bitmask of constants from + # . + # + # RETURN VALUE + # Returns 0 if permission is granted. + # + - call: "security_path_chmod" + syscall: false + return: false + args: + - index: 0 + type: "path" + label: "file path" + - index: 1 + type: "uint32" # WA for broken uint16 in Tetragon + label: "new file mode" + selectors: + - matchNamespaces: + - namespace: Pid + operator: NotIn + values: + - "host_ns" + matchArgs: + - index: 1 + operator: "Mask" + values: + - "0111" # equivalent of symbolic --x--x--x diff --git a/runtime-monitor/pkg/model/tracingpolicy/process-credentials.yaml b/runtime-monitor/pkg/model/tracingpolicy/process-credentials.yaml deleted file mode 100644 index 7abca156..00000000 --- a/runtime-monitor/pkg/model/tracingpolicy/process-credentials.yaml +++ /dev/null @@ -1,35 +0,0 @@ -apiVersion: cilium.io/v1alpha1 -kind: TracingPolicy -metadata: - name: "process-credentials" -spec: - kprobes: - # SYNOPSIS - # int commit_creds(struct cred *new) - # @new: The credentials to be assigned - # - # DESCRIPTION - # Install a new set of credentials to the current task, using RCU to replace - # the old set. Both the objective and the subjective credentials pointers are - # updated. This function may not be called if the subjective credentials are - # in an overridden state. - # - # This function eats the caller's reference to the new credentials. - # - # RETURN VALUE - # Always returns 0 thus allowing this function to be tail-called at the end of, say, sys_setgid(). - # - - call: "commit_creds" - syscall: false - args: - - index: 0 # The new credentials to apply - type: "cred" - selectors: - - matchNamespaces: - - namespace: Pid - operator: NotIn - values: - - "host_ns" - matchActions: - - action: Post - rateLimit: "1m"