From f4bc8222c5e4bdc57633c908bd14ca6fe2e224ce Mon Sep 17 00:00:00 2001 From: Lorenzo Susini Date: Tue, 24 Mar 2026 15:58:58 +0100 Subject: [PATCH 1/2] CWS: Add is_session_leader field to process events Add a new boolean field `is_session_leader` to CWS process events that indicates whether a process is a session leader (PID == SID). The session ID is read from the kernel via: task->signal->pids[PIDTYPE_SID]->numbers[0].nr and compared against the process tgid. Two new kernel offset constants are introduced (task_struct_signal_offset, signal_struct_pids_offset) resolved via BTF at runtime. The field is exposed in SECL as process.is_session_leader and propagates to all process-related event types (exec, exit, signal, ptrace, etc.). Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/cloud-workload-security/backend_linux.md | 18 + .../backend_linux.schema.json | 8 + .../linux_expressions.md | 23 ++ docs/cloud-workload-security/secl_linux.json | 95 +++++ .../c/include/constants/offsets/process.h | 11 + pkg/security/ebpf/c/include/helpers/process.h | 4 + pkg/security/ebpf/c/include/hooks/exec.h | 24 +- .../ebpf/c/include/structs/events_context.h | 2 + pkg/security/ebpf/c/include/structs/process.h | 2 + .../probe/constantfetch/constant_names.go | 4 + pkg/security/probe/constantfetch/fallback.go | 16 + pkg/security/probe/probe_ebpf.go | 2 + pkg/security/secl/model/accessors_unix.go | 330 ++++++++++++++++++ .../secl/model/event_deep_copy_unix.go | 1 + pkg/security/secl/model/marshallers_linux.go | 10 +- pkg/security/secl/model/model_unix.go | 3 +- .../secl/model/unmarshallers_linux.go | 14 +- pkg/security/serializers/deserializers.go | 3 +- .../serializers_base_linux_easyjson.go | 11 + pkg/security/serializers/serializers_linux.go | 8 +- .../serializers/serializers_linux_easyjson.go | 11 + pkg/security/tests/process_test.go | 51 +++ 22 files changed, 639 insertions(+), 12 deletions(-) diff --git a/docs/cloud-workload-security/backend_linux.md b/docs/cloud-workload-security/backend_linux.md index 2a77bca407bd49..f1cc95cc70e5eb 100644 --- a/docs/cloud-workload-security/backend_linux.md +++ b/docs/cloud-workload-security/backend_linux.md @@ -1453,6 +1453,10 @@ Workload Protection events for Linux systems have the following JSON schema: "type": "boolean", "description": "Indicates whether the process is a kworker" }, + "is_session_leader": { + "type": "boolean", + "description": "Indicates whether the process is a session leader" + }, "is_exec": { "type": "boolean", "description": "Indicates whether the process entry is from a new binary execution" @@ -1634,6 +1638,10 @@ Workload Protection events for Linux systems have the following JSON schema: "type": "boolean", "description": "Indicates whether the process is a kworker" }, + "is_session_leader": { + "type": "boolean", + "description": "Indicates whether the process is a session leader" + }, "is_exec": { "type": "boolean", "description": "Indicates whether the process entry is from a new binary execution" @@ -4609,6 +4617,10 @@ Workload Protection events for Linux systems have the following JSON schema: "type": "boolean", "description": "Indicates whether the process is a kworker" }, + "is_session_leader": { + "type": "boolean", + "description": "Indicates whether the process is a session leader" + }, "is_exec": { "type": "boolean", "description": "Indicates whether the process entry is from a new binary execution" @@ -4691,6 +4703,7 @@ Workload Protection events for Linux systems have the following JSON schema: | `envs_truncated` | Indicator of environments variable truncation | | `is_thread` | Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program) | | `is_kworker` | Indicates whether the process is a kworker | +| `is_session_leader` | Indicates whether the process is a session leader | | `is_exec` | Indicates whether the process entry is from a new binary execution | | `is_exec_child` | Indicates whether the process is an exec following another exec | | `is_parent_missing` | Indicates whether the direct parent is missing | @@ -4847,6 +4860,10 @@ Workload Protection events for Linux systems have the following JSON schema: "type": "boolean", "description": "Indicates whether the process is a kworker" }, + "is_session_leader": { + "type": "boolean", + "description": "Indicates whether the process is a session leader" + }, "is_exec": { "type": "boolean", "description": "Indicates whether the process entry is from a new binary execution" @@ -4944,6 +4961,7 @@ Workload Protection events for Linux systems have the following JSON schema: | `envs_truncated` | Indicator of environments variable truncation | | `is_thread` | Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program) | | `is_kworker` | Indicates whether the process is a kworker | +| `is_session_leader` | Indicates whether the process is a session leader | | `is_exec` | Indicates whether the process entry is from a new binary execution | | `is_exec_child` | Indicates whether the process is an exec following another exec | | `is_parent_missing` | Indicates whether the direct parent is missing | diff --git a/docs/cloud-workload-security/backend_linux.schema.json b/docs/cloud-workload-security/backend_linux.schema.json index 0499bcfece3692..f40339ae775db1 100644 --- a/docs/cloud-workload-security/backend_linux.schema.json +++ b/docs/cloud-workload-security/backend_linux.schema.json @@ -1442,6 +1442,10 @@ "type": "boolean", "description": "Indicates whether the process is a kworker" }, + "is_session_leader": { + "type": "boolean", + "description": "Indicates whether the process is a session leader" + }, "is_exec": { "type": "boolean", "description": "Indicates whether the process entry is from a new binary execution" @@ -1623,6 +1627,10 @@ "type": "boolean", "description": "Indicates whether the process is a kworker" }, + "is_session_leader": { + "type": "boolean", + "description": "Indicates whether the process is a session leader" + }, "is_exec": { "type": "boolean", "description": "Indicates whether the process entry is from a new binary execution" diff --git a/docs/cloud-workload-security/linux_expressions.md b/docs/cloud-workload-security/linux_expressions.md index e84629740217a3..e3c892bf6f6bfa 100644 --- a/docs/cloud-workload-security/linux_expressions.md +++ b/docs/cloud-workload-security/linux_expressions.md @@ -278,6 +278,7 @@ The *file.rights* attribute can now be used in addition to *file.mode*. *file.mo | [`process.ancestors.interpreter.file.user`](#common-filefields-user-doc) | User of the file's owner | | [`process.ancestors.is_exec`](#common-process-is_exec-doc) | Indicates whether the process entry is from a new binary execution | | [`process.ancestors.is_kworker`](#common-pidcontext-is_kworker-doc) | Indicates whether the process is a kworker | +| [`process.ancestors.is_session_leader`](#common-pidcontext-is_session_leader-doc) | Indicates whether the process is a session leader | | [`process.ancestors.is_thread`](#common-process-is_thread-doc) | Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program) | | [`process.ancestors.length`](#common-string-length-doc) | Length of the corresponding element | | [`process.ancestors.mntns`](#common-pidcontext-mntns-doc) | MNTNS ID of the process | @@ -389,6 +390,7 @@ The *file.rights* attribute can now be used in addition to *file.mode*. *file.mo | [`process.interpreter.file.user`](#common-filefields-user-doc) | User of the file's owner | | [`process.is_exec`](#common-process-is_exec-doc) | Indicates whether the process entry is from a new binary execution | | [`process.is_kworker`](#common-pidcontext-is_kworker-doc) | Indicates whether the process is a kworker | +| [`process.is_session_leader`](#common-pidcontext-is_session_leader-doc) | Indicates whether the process is a session leader | | [`process.is_thread`](#common-process-is_thread-doc) | Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program) | | [`process.mntns`](#common-pidcontext-mntns-doc) | MNTNS ID of the process | | [`process.netns`](#common-pidcontext-netns-doc) | NetNS ID of the process | @@ -481,6 +483,7 @@ The *file.rights* attribute can now be used in addition to *file.mode*. *file.mo | [`process.parent.interpreter.file.user`](#common-filefields-user-doc) | User of the file's owner | | [`process.parent.is_exec`](#common-process-is_exec-doc) | Indicates whether the process entry is from a new binary execution | | [`process.parent.is_kworker`](#common-pidcontext-is_kworker-doc) | Indicates whether the process is a kworker | +| [`process.parent.is_session_leader`](#common-pidcontext-is_session_leader-doc) | Indicates whether the process is a session leader | | [`process.parent.is_thread`](#common-process-is_thread-doc) | Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program) | | [`process.parent.mntns`](#common-pidcontext-mntns-doc) | MNTNS ID of the process | | [`process.parent.netns`](#common-pidcontext-netns-doc) | NetNS ID of the process | @@ -887,6 +890,7 @@ A process was executed (does not trigger on fork syscalls). | [`exec.interpreter.file.user`](#common-filefields-user-doc) | User of the file's owner | | [`exec.is_exec`](#common-process-is_exec-doc) | Indicates whether the process entry is from a new binary execution | | [`exec.is_kworker`](#common-pidcontext-is_kworker-doc) | Indicates whether the process is a kworker | +| [`exec.is_session_leader`](#common-pidcontext-is_session_leader-doc) | Indicates whether the process is a session leader | | [`exec.is_thread`](#common-process-is_thread-doc) | Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program) | | [`exec.mntns`](#common-pidcontext-mntns-doc) | MNTNS ID of the process | | [`exec.netns`](#common-pidcontext-netns-doc) | NetNS ID of the process | @@ -1007,6 +1011,7 @@ A process was terminated | [`exit.interpreter.file.user`](#common-filefields-user-doc) | User of the file's owner | | [`exit.is_exec`](#common-process-is_exec-doc) | Indicates whether the process entry is from a new binary execution | | [`exit.is_kworker`](#common-pidcontext-is_kworker-doc) | Indicates whether the process is a kworker | +| [`exit.is_session_leader`](#common-pidcontext-is_session_leader-doc) | Indicates whether the process is a session leader | | [`exit.is_thread`](#common-process-is_thread-doc) | Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program) | | [`exit.mntns`](#common-pidcontext-mntns-doc) | MNTNS ID of the process | | [`exit.netns`](#common-pidcontext-netns-doc) | NetNS ID of the process | @@ -1471,6 +1476,7 @@ A ptrace command was executed | [`ptrace.tracee.ancestors.interpreter.file.user`](#common-filefields-user-doc) | User of the file's owner | | [`ptrace.tracee.ancestors.is_exec`](#common-process-is_exec-doc) | Indicates whether the process entry is from a new binary execution | | [`ptrace.tracee.ancestors.is_kworker`](#common-pidcontext-is_kworker-doc) | Indicates whether the process is a kworker | +| [`ptrace.tracee.ancestors.is_session_leader`](#common-pidcontext-is_session_leader-doc) | Indicates whether the process is a session leader | | [`ptrace.tracee.ancestors.is_thread`](#common-process-is_thread-doc) | Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program) | | [`ptrace.tracee.ancestors.length`](#common-string-length-doc) | Length of the corresponding element | | [`ptrace.tracee.ancestors.mntns`](#common-pidcontext-mntns-doc) | MNTNS ID of the process | @@ -1582,6 +1588,7 @@ A ptrace command was executed | [`ptrace.tracee.interpreter.file.user`](#common-filefields-user-doc) | User of the file's owner | | [`ptrace.tracee.is_exec`](#common-process-is_exec-doc) | Indicates whether the process entry is from a new binary execution | | [`ptrace.tracee.is_kworker`](#common-pidcontext-is_kworker-doc) | Indicates whether the process is a kworker | +| [`ptrace.tracee.is_session_leader`](#common-pidcontext-is_session_leader-doc) | Indicates whether the process is a session leader | | [`ptrace.tracee.is_thread`](#common-process-is_thread-doc) | Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program) | | [`ptrace.tracee.mntns`](#common-pidcontext-mntns-doc) | MNTNS ID of the process | | [`ptrace.tracee.netns`](#common-pidcontext-netns-doc) | NetNS ID of the process | @@ -1674,6 +1681,7 @@ A ptrace command was executed | [`ptrace.tracee.parent.interpreter.file.user`](#common-filefields-user-doc) | User of the file's owner | | [`ptrace.tracee.parent.is_exec`](#common-process-is_exec-doc) | Indicates whether the process entry is from a new binary execution | | [`ptrace.tracee.parent.is_kworker`](#common-pidcontext-is_kworker-doc) | Indicates whether the process is a kworker | +| [`ptrace.tracee.parent.is_session_leader`](#common-pidcontext-is_session_leader-doc) | Indicates whether the process is a session leader | | [`ptrace.tracee.parent.is_thread`](#common-process-is_thread-doc) | Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program) | | [`ptrace.tracee.parent.mntns`](#common-pidcontext-mntns-doc) | MNTNS ID of the process | | [`ptrace.tracee.parent.netns`](#common-pidcontext-netns-doc) | NetNS ID of the process | @@ -1974,6 +1982,7 @@ A setrlimit command was executed | [`setrlimit.target.ancestors.interpreter.file.user`](#common-filefields-user-doc) | User of the file's owner | | [`setrlimit.target.ancestors.is_exec`](#common-process-is_exec-doc) | Indicates whether the process entry is from a new binary execution | | [`setrlimit.target.ancestors.is_kworker`](#common-pidcontext-is_kworker-doc) | Indicates whether the process is a kworker | +| [`setrlimit.target.ancestors.is_session_leader`](#common-pidcontext-is_session_leader-doc) | Indicates whether the process is a session leader | | [`setrlimit.target.ancestors.is_thread`](#common-process-is_thread-doc) | Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program) | | [`setrlimit.target.ancestors.length`](#common-string-length-doc) | Length of the corresponding element | | [`setrlimit.target.ancestors.mntns`](#common-pidcontext-mntns-doc) | MNTNS ID of the process | @@ -2085,6 +2094,7 @@ A setrlimit command was executed | [`setrlimit.target.interpreter.file.user`](#common-filefields-user-doc) | User of the file's owner | | [`setrlimit.target.is_exec`](#common-process-is_exec-doc) | Indicates whether the process entry is from a new binary execution | | [`setrlimit.target.is_kworker`](#common-pidcontext-is_kworker-doc) | Indicates whether the process is a kworker | +| [`setrlimit.target.is_session_leader`](#common-pidcontext-is_session_leader-doc) | Indicates whether the process is a session leader | | [`setrlimit.target.is_thread`](#common-process-is_thread-doc) | Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program) | | [`setrlimit.target.mntns`](#common-pidcontext-mntns-doc) | MNTNS ID of the process | | [`setrlimit.target.netns`](#common-pidcontext-netns-doc) | NetNS ID of the process | @@ -2177,6 +2187,7 @@ A setrlimit command was executed | [`setrlimit.target.parent.interpreter.file.user`](#common-filefields-user-doc) | User of the file's owner | | [`setrlimit.target.parent.is_exec`](#common-process-is_exec-doc) | Indicates whether the process entry is from a new binary execution | | [`setrlimit.target.parent.is_kworker`](#common-pidcontext-is_kworker-doc) | Indicates whether the process is a kworker | +| [`setrlimit.target.parent.is_session_leader`](#common-pidcontext-is_session_leader-doc) | Indicates whether the process is a session leader | | [`setrlimit.target.parent.is_thread`](#common-process-is_thread-doc) | Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program) | | [`setrlimit.target.parent.mntns`](#common-pidcontext-mntns-doc) | MNTNS ID of the process | | [`setrlimit.target.parent.netns`](#common-pidcontext-netns-doc) | NetNS ID of the process | @@ -2382,6 +2393,7 @@ A signal was sent | [`signal.target.ancestors.interpreter.file.user`](#common-filefields-user-doc) | User of the file's owner | | [`signal.target.ancestors.is_exec`](#common-process-is_exec-doc) | Indicates whether the process entry is from a new binary execution | | [`signal.target.ancestors.is_kworker`](#common-pidcontext-is_kworker-doc) | Indicates whether the process is a kworker | +| [`signal.target.ancestors.is_session_leader`](#common-pidcontext-is_session_leader-doc) | Indicates whether the process is a session leader | | [`signal.target.ancestors.is_thread`](#common-process-is_thread-doc) | Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program) | | [`signal.target.ancestors.length`](#common-string-length-doc) | Length of the corresponding element | | [`signal.target.ancestors.mntns`](#common-pidcontext-mntns-doc) | MNTNS ID of the process | @@ -2493,6 +2505,7 @@ A signal was sent | [`signal.target.interpreter.file.user`](#common-filefields-user-doc) | User of the file's owner | | [`signal.target.is_exec`](#common-process-is_exec-doc) | Indicates whether the process entry is from a new binary execution | | [`signal.target.is_kworker`](#common-pidcontext-is_kworker-doc) | Indicates whether the process is a kworker | +| [`signal.target.is_session_leader`](#common-pidcontext-is_session_leader-doc) | Indicates whether the process is a session leader | | [`signal.target.is_thread`](#common-process-is_thread-doc) | Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program) | | [`signal.target.mntns`](#common-pidcontext-mntns-doc) | MNTNS ID of the process | | [`signal.target.netns`](#common-pidcontext-netns-doc) | NetNS ID of the process | @@ -2585,6 +2598,7 @@ A signal was sent | [`signal.target.parent.interpreter.file.user`](#common-filefields-user-doc) | User of the file's owner | | [`signal.target.parent.is_exec`](#common-process-is_exec-doc) | Indicates whether the process entry is from a new binary execution | | [`signal.target.parent.is_kworker`](#common-pidcontext-is_kworker-doc) | Indicates whether the process is a kworker | +| [`signal.target.parent.is_session_leader`](#common-pidcontext-is_session_leader-doc) | Indicates whether the process is a session leader | | [`signal.target.parent.is_thread`](#common-process-is_thread-doc) | Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program) | | [`signal.target.parent.mntns`](#common-pidcontext-mntns-doc) | MNTNS ID of the process | | [`signal.target.parent.netns`](#common-pidcontext-netns-doc) | NetNS ID of the process | @@ -3245,6 +3259,15 @@ Definition: Whether the IP address belongs to a public network `accept.addr` `bind.addr` `connect.addr` `network.destination` `network.source` `network_flow_monitor.flows.destination` `network_flow_monitor.flows.source` `packet.destination` `packet.source` +### `*.is_session_leader` {#common-pidcontext-is_session_leader-doc} +Type: bool + +Definition: Indicates whether the process is a session leader + +`*.is_session_leader` has 14 possible prefixes: +`exec` `exit` `process` `process.ancestors` `process.parent` `ptrace.tracee` `ptrace.tracee.ancestors` `ptrace.tracee.parent` `setrlimit.target` `setrlimit.target.ancestors` `setrlimit.target.parent` `signal.target` `signal.target.ancestors` `signal.target.parent` + + ### `*.is_thread` {#common-process-is_thread-doc} Type: bool diff --git a/docs/cloud-workload-security/secl_linux.json b/docs/cloud-workload-security/secl_linux.json index 929150ef74b185..0ef3aea5e468a8 100644 --- a/docs/cloud-workload-security/secl_linux.json +++ b/docs/cloud-workload-security/secl_linux.json @@ -497,6 +497,11 @@ "definition": "Indicates whether the process is a kworker", "property_doc_link": "common-pidcontext-is_kworker-doc" }, + { + "name": "process.ancestors.is_session_leader", + "definition": "Indicates whether the process is a session leader", + "property_doc_link": "common-pidcontext-is_session_leader-doc" + }, { "name": "process.ancestors.is_thread", "definition": "Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program)", @@ -1052,6 +1057,11 @@ "definition": "Indicates whether the process is a kworker", "property_doc_link": "common-pidcontext-is_kworker-doc" }, + { + "name": "process.is_session_leader", + "definition": "Indicates whether the process is a session leader", + "property_doc_link": "common-pidcontext-is_session_leader-doc" + }, { "name": "process.is_thread", "definition": "Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program)", @@ -1512,6 +1522,11 @@ "definition": "Indicates whether the process is a kworker", "property_doc_link": "common-pidcontext-is_kworker-doc" }, + { + "name": "process.parent.is_session_leader", + "definition": "Indicates whether the process is a session leader", + "property_doc_link": "common-pidcontext-is_session_leader-doc" + }, { "name": "process.parent.is_thread", "definition": "Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program)", @@ -3210,6 +3225,11 @@ "definition": "Indicates whether the process is a kworker", "property_doc_link": "common-pidcontext-is_kworker-doc" }, + { + "name": "exec.is_session_leader", + "definition": "Indicates whether the process is a session leader", + "property_doc_link": "common-pidcontext-is_session_leader-doc" + }, { "name": "exec.is_thread", "definition": "Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program)", @@ -3784,6 +3804,11 @@ "definition": "Indicates whether the process is a kworker", "property_doc_link": "common-pidcontext-is_kworker-doc" }, + { + "name": "exit.is_session_leader", + "definition": "Indicates whether the process is a session leader", + "property_doc_link": "common-pidcontext-is_session_leader-doc" + }, { "name": "exit.is_thread", "definition": "Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program)", @@ -5782,6 +5807,11 @@ "definition": "Indicates whether the process is a kworker", "property_doc_link": "common-pidcontext-is_kworker-doc" }, + { + "name": "ptrace.tracee.ancestors.is_session_leader", + "definition": "Indicates whether the process is a session leader", + "property_doc_link": "common-pidcontext-is_session_leader-doc" + }, { "name": "ptrace.tracee.ancestors.is_thread", "definition": "Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program)", @@ -6337,6 +6367,11 @@ "definition": "Indicates whether the process is a kworker", "property_doc_link": "common-pidcontext-is_kworker-doc" }, + { + "name": "ptrace.tracee.is_session_leader", + "definition": "Indicates whether the process is a session leader", + "property_doc_link": "common-pidcontext-is_session_leader-doc" + }, { "name": "ptrace.tracee.is_thread", "definition": "Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program)", @@ -6797,6 +6832,11 @@ "definition": "Indicates whether the process is a kworker", "property_doc_link": "common-pidcontext-is_kworker-doc" }, + { + "name": "ptrace.tracee.parent.is_session_leader", + "definition": "Indicates whether the process is a session leader", + "property_doc_link": "common-pidcontext-is_session_leader-doc" + }, { "name": "ptrace.tracee.parent.is_thread", "definition": "Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program)", @@ -8141,6 +8181,11 @@ "definition": "Indicates whether the process is a kworker", "property_doc_link": "common-pidcontext-is_kworker-doc" }, + { + "name": "setrlimit.target.ancestors.is_session_leader", + "definition": "Indicates whether the process is a session leader", + "property_doc_link": "common-pidcontext-is_session_leader-doc" + }, { "name": "setrlimit.target.ancestors.is_thread", "definition": "Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program)", @@ -8696,6 +8741,11 @@ "definition": "Indicates whether the process is a kworker", "property_doc_link": "common-pidcontext-is_kworker-doc" }, + { + "name": "setrlimit.target.is_session_leader", + "definition": "Indicates whether the process is a session leader", + "property_doc_link": "common-pidcontext-is_session_leader-doc" + }, { "name": "setrlimit.target.is_thread", "definition": "Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program)", @@ -9156,6 +9206,11 @@ "definition": "Indicates whether the process is a kworker", "property_doc_link": "common-pidcontext-is_kworker-doc" }, + { + "name": "setrlimit.target.parent.is_session_leader", + "definition": "Indicates whether the process is a session leader", + "property_doc_link": "common-pidcontext-is_session_leader-doc" + }, { "name": "setrlimit.target.parent.is_thread", "definition": "Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program)", @@ -10077,6 +10132,11 @@ "definition": "Indicates whether the process is a kworker", "property_doc_link": "common-pidcontext-is_kworker-doc" }, + { + "name": "signal.target.ancestors.is_session_leader", + "definition": "Indicates whether the process is a session leader", + "property_doc_link": "common-pidcontext-is_session_leader-doc" + }, { "name": "signal.target.ancestors.is_thread", "definition": "Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program)", @@ -10632,6 +10692,11 @@ "definition": "Indicates whether the process is a kworker", "property_doc_link": "common-pidcontext-is_kworker-doc" }, + { + "name": "signal.target.is_session_leader", + "definition": "Indicates whether the process is a session leader", + "property_doc_link": "common-pidcontext-is_session_leader-doc" + }, { "name": "signal.target.is_thread", "definition": "Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program)", @@ -11092,6 +11157,11 @@ "definition": "Indicates whether the process is a kworker", "property_doc_link": "common-pidcontext-is_kworker-doc" }, + { + "name": "signal.target.parent.is_session_leader", + "definition": "Indicates whether the process is a session leader", + "property_doc_link": "common-pidcontext-is_session_leader-doc" + }, { "name": "signal.target.parent.is_thread", "definition": "Indicates whether the process is considered a thread (that is, a child process that hasn't executed another program)", @@ -13258,6 +13328,31 @@ "constants_link": "", "examples": [] }, + { + "name": "*.is_session_leader", + "link": "common-pidcontext-is_session_leader-doc", + "type": "bool", + "definition": "Indicates whether the process is a session leader", + "prefixes": [ + "exec", + "exit", + "process", + "process.ancestors", + "process.parent", + "ptrace.tracee", + "ptrace.tracee.ancestors", + "ptrace.tracee.parent", + "setrlimit.target", + "setrlimit.target.ancestors", + "setrlimit.target.parent", + "signal.target", + "signal.target.ancestors", + "signal.target.parent" + ], + "constants": "", + "constants_link": "", + "examples": [] + }, { "name": "*.is_thread", "link": "common-process-is_thread-doc", diff --git a/pkg/security/ebpf/c/include/constants/offsets/process.h b/pkg/security/ebpf/c/include/constants/offsets/process.h index f94eb022610c06..4bcec9447eab52 100644 --- a/pkg/security/ebpf/c/include/constants/offsets/process.h +++ b/pkg/security/ebpf/c/include/constants/offsets/process.h @@ -51,4 +51,15 @@ u64 __attribute__((always_inline)) get_task_struct_pid_offset() { return task_struct_pid_offset; } +u64 __attribute__((always_inline)) get_task_struct_signal_offset() { + u64 offset; + LOAD_CONSTANT("task_struct_signal_offset", offset); + return offset; +} + +u64 __attribute__((always_inline)) get_signal_struct_pids_offset() { + u64 offset; + LOAD_CONSTANT("signal_struct_pids_offset", offset); + return offset; +} #endif diff --git a/pkg/security/ebpf/c/include/helpers/process.h b/pkg/security/ebpf/c/include/helpers/process.h index 53cb731968cf8c..32462d50c5f0d2 100644 --- a/pkg/security/ebpf/c/include/helpers/process.h +++ b/pkg/security/ebpf/c/include/helpers/process.h @@ -41,6 +41,7 @@ void __attribute__((always_inline)) copy_pid_cache_except_exit_ts(struct pid_cac dst->user_session_id = src->user_session_id; dst->fork_timestamp = src->fork_timestamp; dst->fork_flags = src->fork_flags; + dst->is_session_leader = src->is_session_leader; dst->credentials = src->credentials; } @@ -115,6 +116,9 @@ static struct proc_cache_t *__attribute__((always_inline)) fill_process_context_ // copy user session id data->user_session_id = pid_entry->user_session_id; + // copy session leader status from cache + data->is_session_leader = pid_entry->is_session_leader; + struct proc_cache_t *pc = get_proc_from_cookie(pid_entry->cookie); if (pc) { data->inode = pc->entry.executable.path_key.ino; diff --git a/pkg/security/ebpf/c/include/hooks/exec.h b/pkg/security/ebpf/c/include/hooks/exec.h index 57b232fd76fad2..82dbee977dfecb 100644 --- a/pkg/security/ebpf/c/include/hooks/exec.h +++ b/pkg/security/ebpf/c/include/hooks/exec.h @@ -808,7 +808,9 @@ int __attribute__((always_inline)) send_exec_event(ctx_t *ctx) { // update pid <-> cookie mapping if (fork_entry) { fork_entry->cookie = cookie; - } else { + } + + if (!fork_entry) { struct pid_cache_t new_pid_entry = { .cookie = cookie, }; @@ -820,6 +822,26 @@ int __attribute__((always_inline)) send_exec_event(ctx_t *ctx) { } } + // compute session leader status: pid == sid (task->signal->pids[PIDTYPE_SID]->numbers[0].nr) + { + u64 signal_offset = get_task_struct_signal_offset(); + u64 pids_offset = get_signal_struct_pids_offset(); + if (signal_offset != 0 && pids_offset != 0) { + struct task_struct *cur_task = (struct task_struct *)bpf_get_current_task(); + void *signal_ptr = NULL; + bpf_probe_read_kernel(&signal_ptr, sizeof(signal_ptr), (void *)cur_task + signal_offset); + if (signal_ptr) { + struct pid *sid_pid = NULL; + bpf_probe_read_kernel(&sid_pid, sizeof(sid_pid), signal_ptr + pids_offset + 3 * sizeof(void *)); + if (sid_pid) { + u32 sid = 0; + bpf_probe_read_kernel(&sid, sizeof(sid), (void *)sid_pid + get_pid_numbers_offset()); + fork_entry->is_session_leader = (sid != 0 && sid == tgid) ? 1 : 0; + } + } + } + } + struct process_event_t *event = new_process_event(0); if (event == NULL) { return 0; diff --git a/pkg/security/ebpf/c/include/structs/events_context.h b/pkg/security/ebpf/c/include/structs/events_context.h index f4a39354d5ab95..8ee85b2ece3c54 100644 --- a/pkg/security/ebpf/c/include/structs/events_context.h +++ b/pkg/security/ebpf/c/include/structs/events_context.h @@ -33,6 +33,8 @@ struct process_context_t { u32 ppid; u64 inode; u64 user_session_id; + u32 is_session_leader; + u32 padding_session; }; struct ktimeval { diff --git a/pkg/security/ebpf/c/include/structs/process.h b/pkg/security/ebpf/c/include/structs/process.h index 1339f80ef5038a..36cea1924f5090 100644 --- a/pkg/security/ebpf/c/include/structs/process.h +++ b/pkg/security/ebpf/c/include/structs/process.h @@ -37,6 +37,8 @@ struct pid_cache_t { u64 exit_timestamp; u64 user_session_id; u64 fork_flags; + u32 is_session_leader; + u32 padding_session; struct credentials_t credentials; }; diff --git a/pkg/security/probe/constantfetch/constant_names.go b/pkg/security/probe/constantfetch/constant_names.go index 756aab4ac4cfe2..295ff8942d99af 100644 --- a/pkg/security/probe/constantfetch/constant_names.go +++ b/pkg/security/probe/constantfetch/constant_names.go @@ -92,6 +92,10 @@ const ( OffsetNameTaskStructRealParent = "task_struct_real_parent_offset" OffsetNameTaskStructTGID = "task_struct_tgid_offset" + // session leader detection + OffsetNameTaskStructSignal = "task_struct_signal_offset" + OffsetNameSignalStructPIDs = "signal_struct_pids_offset" + // splice event OffsetNamePipeInodeInfoStructBufs = "pipe_inode_info_bufs_offset" OffsetNamePipeInodeInfoStructNrbufs = "pipe_inode_info_nrbufs_offset" // kernels < 5.5 diff --git a/pkg/security/probe/constantfetch/fallback.go b/pkg/security/probe/constantfetch/fallback.go index ea524aa884314f..11052fc289c15b 100644 --- a/pkg/security/probe/constantfetch/fallback.go +++ b/pkg/security/probe/constantfetch/fallback.go @@ -88,6 +88,8 @@ func computeCallbacksTable() map[string]func(*kernel.Version) uint64 { SizeOfInode: getSizeOfStructInode, OffsetNameSuperBlockStructSMagic: getSuperBlockMagicOffset, OffsetNameSignalStructStructTTY: getSignalTTYOffset, + OffsetNameTaskStructSignal: getTaskStructSignalOffset, + OffsetNameSignalStructPIDs: getSignalStructPIDsOffset, OffsetNameTTYStructStructName: getTTYNameOffset, OffsetNameCredStructUID: getCredsUIDOffset, OffsetNameCredStructCapInheritable: getCredCapInheritableOffset, @@ -1115,3 +1117,17 @@ func getTaskStructTGIDOffset(kv *kernel.Version) uint64 { return ErrorSentinel } } + +// getTaskStructSignalOffset returns the offset of the signal field in task_struct +// This is the primary path through BTF; the fallback returns ErrorSentinel +// to signal that the offset could not be determined. +func getTaskStructSignalOffset(_ *kernel.Version) uint64 { + return ErrorSentinel +} + +// getSignalStructPIDsOffset returns the offset of the pids array in signal_struct +// This is the primary path through BTF; the fallback returns ErrorSentinel +// to signal that the offset could not be determined. +func getSignalStructPIDsOffset(_ *kernel.Version) uint64 { + return ErrorSentinel +} diff --git a/pkg/security/probe/probe_ebpf.go b/pkg/security/probe/probe_ebpf.go index 387e49d603b7ef..1be8c6c0c52f68 100644 --- a/pkg/security/probe/probe_ebpf.go +++ b/pkg/security/probe/probe_ebpf.go @@ -3239,6 +3239,8 @@ func AppendProbeRequestsToFetcher(constantFetcher constantfetch.ConstantFetcher, appendOffsetofRequest(constantFetcher, constantfetch.OffsetNameSuperBlockStructSMagic, "struct super_block", "s_magic") appendOffsetofRequest(constantFetcher, constantfetch.OffsetNameDentryStructDSB, "struct dentry", "d_sb") appendOffsetofRequest(constantFetcher, constantfetch.OffsetNameSignalStructStructTTY, "struct signal_struct", "tty") + appendOffsetofRequest(constantFetcher, constantfetch.OffsetNameTaskStructSignal, "struct task_struct", "signal") + appendOffsetofRequest(constantFetcher, constantfetch.OffsetNameSignalStructPIDs, "struct signal_struct", "pids") appendOffsetofRequest(constantFetcher, constantfetch.OffsetNameTTYStructStructName, "struct tty_struct", "name") appendOffsetofRequest(constantFetcher, constantfetch.OffsetNameCredStructUID, "struct cred", "uid") appendOffsetofRequest(constantFetcher, constantfetch.OffsetNameCredStructCapInheritable, "struct cred", "cap_inheritable") diff --git a/pkg/security/secl/model/accessors_unix.go b/pkg/security/secl/model/accessors_unix.go index bd6812f48cd92a..eca342765c943e 100644 --- a/pkg/security/secl/model/accessors_unix.go +++ b/pkg/security/secl/model/accessors_unix.go @@ -3334,6 +3334,17 @@ func (_ *Model) GetEvaluator(field eval.Field, regID eval.RegisterID, offset int Weight: eval.FunctionWeight, Offset: offset, }, nil + case "exec.is_session_leader": + return &eval.BoolEvaluator{ + EvalFnc: func(ctx *eval.Context) bool { + ctx.AppendResolvedField(field) + ev := ctx.Event.(*Event) + return ev.Exec.Process.PIDContext.IsSessionLeader + }, + Field: field, + Weight: eval.FunctionWeight, + Offset: offset, + }, nil case "exec.is_thread": return &eval.BoolEvaluator{ EvalFnc: func(ctx *eval.Context) bool { @@ -4737,6 +4748,17 @@ func (_ *Model) GetEvaluator(field eval.Field, regID eval.RegisterID, offset int Weight: eval.FunctionWeight, Offset: offset, }, nil + case "exit.is_session_leader": + return &eval.BoolEvaluator{ + EvalFnc: func(ctx *eval.Context) bool { + ctx.AppendResolvedField(field) + ev := ctx.Event.(*Event) + return ev.Exit.Process.PIDContext.IsSessionLeader + }, + Field: field, + Weight: eval.FunctionWeight, + Offset: offset, + }, nil case "exit.is_thread": return &eval.BoolEvaluator{ EvalFnc: func(ctx *eval.Context) bool { @@ -10843,6 +10865,33 @@ func (_ *Model) GetEvaluator(field eval.Field, regID eval.RegisterID, offset int Weight: eval.IteratorWeight, Offset: offset, }, nil + case "process.ancestors.is_session_leader": + return &eval.BoolArrayEvaluator{ + EvalFnc: func(ctx *eval.Context) []bool { + ctx.AppendResolvedField(field) + ev := ctx.Event.(*Event) + iterator := &ProcessAncestorsIterator{Root: ev.BaseEvent.ProcessContext.Ancestor} + if regID != "" { + element := iterator.At(ctx, regID, ctx.Registers[regID]) + if element == nil { + return nil + } + result := element.ProcessContext.Process.PIDContext.IsSessionLeader + return []bool{result} + } + if result, ok := ctx.BoolCache[field]; ok { + return result + } + results := newIterator(iterator, "BaseEvent.ProcessContext.Ancestor", ctx, nil, func(ev *Event, current *ProcessCacheEntry) bool { + return current.ProcessContext.Process.PIDContext.IsSessionLeader + }) + ctx.BoolCache[field] = results + return results + }, + Field: field, + Weight: eval.IteratorWeight, + Offset: offset, + }, nil case "process.ancestors.is_thread": return &eval.BoolArrayEvaluator{ EvalFnc: func(ctx *eval.Context) []bool { @@ -12560,6 +12609,17 @@ func (_ *Model) GetEvaluator(field eval.Field, regID eval.RegisterID, offset int Weight: eval.FunctionWeight, Offset: offset, }, nil + case "process.is_session_leader": + return &eval.BoolEvaluator{ + EvalFnc: func(ctx *eval.Context) bool { + ctx.AppendResolvedField(field) + ev := ctx.Event.(*Event) + return ev.BaseEvent.ProcessContext.Process.PIDContext.IsSessionLeader + }, + Field: field, + Weight: eval.FunctionWeight, + Offset: offset, + }, nil case "process.is_thread": return &eval.BoolEvaluator{ EvalFnc: func(ctx *eval.Context) bool { @@ -13987,6 +14047,20 @@ func (_ *Model) GetEvaluator(field eval.Field, regID eval.RegisterID, offset int Weight: eval.FunctionWeight, Offset: offset, }, nil + case "process.parent.is_session_leader": + return &eval.BoolEvaluator{ + EvalFnc: func(ctx *eval.Context) bool { + ctx.AppendResolvedField(field) + ev := ctx.Event.(*Event) + if !ev.BaseEvent.ProcessContext.HasParent() { + return false + } + return ev.BaseEvent.ProcessContext.Parent.PIDContext.IsSessionLeader + }, + Field: field, + Weight: eval.FunctionWeight, + Offset: offset, + }, nil case "process.parent.is_thread": return &eval.BoolEvaluator{ EvalFnc: func(ctx *eval.Context) bool { @@ -17214,6 +17288,33 @@ func (_ *Model) GetEvaluator(field eval.Field, regID eval.RegisterID, offset int Weight: eval.IteratorWeight, Offset: offset, }, nil + case "ptrace.tracee.ancestors.is_session_leader": + return &eval.BoolArrayEvaluator{ + EvalFnc: func(ctx *eval.Context) []bool { + ctx.AppendResolvedField(field) + ev := ctx.Event.(*Event) + iterator := &ProcessAncestorsIterator{Root: ev.PTrace.Tracee.Ancestor} + if regID != "" { + element := iterator.At(ctx, regID, ctx.Registers[regID]) + if element == nil { + return nil + } + result := element.ProcessContext.Process.PIDContext.IsSessionLeader + return []bool{result} + } + if result, ok := ctx.BoolCache[field]; ok { + return result + } + results := newIterator(iterator, "PTrace.Tracee.Ancestor", ctx, nil, func(ev *Event, current *ProcessCacheEntry) bool { + return current.ProcessContext.Process.PIDContext.IsSessionLeader + }) + ctx.BoolCache[field] = results + return results + }, + Field: field, + Weight: eval.IteratorWeight, + Offset: offset, + }, nil case "ptrace.tracee.ancestors.is_thread": return &eval.BoolArrayEvaluator{ EvalFnc: func(ctx *eval.Context) []bool { @@ -18931,6 +19032,17 @@ func (_ *Model) GetEvaluator(field eval.Field, regID eval.RegisterID, offset int Weight: eval.FunctionWeight, Offset: offset, }, nil + case "ptrace.tracee.is_session_leader": + return &eval.BoolEvaluator{ + EvalFnc: func(ctx *eval.Context) bool { + ctx.AppendResolvedField(field) + ev := ctx.Event.(*Event) + return ev.PTrace.Tracee.Process.PIDContext.IsSessionLeader + }, + Field: field, + Weight: eval.FunctionWeight, + Offset: offset, + }, nil case "ptrace.tracee.is_thread": return &eval.BoolEvaluator{ EvalFnc: func(ctx *eval.Context) bool { @@ -20358,6 +20470,20 @@ func (_ *Model) GetEvaluator(field eval.Field, regID eval.RegisterID, offset int Weight: eval.FunctionWeight, Offset: offset, }, nil + case "ptrace.tracee.parent.is_session_leader": + return &eval.BoolEvaluator{ + EvalFnc: func(ctx *eval.Context) bool { + ctx.AppendResolvedField(field) + ev := ctx.Event.(*Event) + if !ev.PTrace.Tracee.HasParent() { + return false + } + return ev.PTrace.Tracee.Parent.PIDContext.IsSessionLeader + }, + Field: field, + Weight: eval.FunctionWeight, + Offset: offset, + }, nil case "ptrace.tracee.parent.is_thread": return &eval.BoolEvaluator{ EvalFnc: func(ctx *eval.Context) bool { @@ -25013,6 +25139,33 @@ func (_ *Model) GetEvaluator(field eval.Field, regID eval.RegisterID, offset int Weight: eval.IteratorWeight, Offset: offset, }, nil + case "setrlimit.target.ancestors.is_session_leader": + return &eval.BoolArrayEvaluator{ + EvalFnc: func(ctx *eval.Context) []bool { + ctx.AppendResolvedField(field) + ev := ctx.Event.(*Event) + iterator := &ProcessAncestorsIterator{Root: ev.Setrlimit.Target.Ancestor} + if regID != "" { + element := iterator.At(ctx, regID, ctx.Registers[regID]) + if element == nil { + return nil + } + result := element.ProcessContext.Process.PIDContext.IsSessionLeader + return []bool{result} + } + if result, ok := ctx.BoolCache[field]; ok { + return result + } + results := newIterator(iterator, "Setrlimit.Target.Ancestor", ctx, nil, func(ev *Event, current *ProcessCacheEntry) bool { + return current.ProcessContext.Process.PIDContext.IsSessionLeader + }) + ctx.BoolCache[field] = results + return results + }, + Field: field, + Weight: eval.IteratorWeight, + Offset: offset, + }, nil case "setrlimit.target.ancestors.is_thread": return &eval.BoolArrayEvaluator{ EvalFnc: func(ctx *eval.Context) []bool { @@ -26730,6 +26883,17 @@ func (_ *Model) GetEvaluator(field eval.Field, regID eval.RegisterID, offset int Weight: eval.FunctionWeight, Offset: offset, }, nil + case "setrlimit.target.is_session_leader": + return &eval.BoolEvaluator{ + EvalFnc: func(ctx *eval.Context) bool { + ctx.AppendResolvedField(field) + ev := ctx.Event.(*Event) + return ev.Setrlimit.Target.Process.PIDContext.IsSessionLeader + }, + Field: field, + Weight: eval.FunctionWeight, + Offset: offset, + }, nil case "setrlimit.target.is_thread": return &eval.BoolEvaluator{ EvalFnc: func(ctx *eval.Context) bool { @@ -28157,6 +28321,20 @@ func (_ *Model) GetEvaluator(field eval.Field, regID eval.RegisterID, offset int Weight: eval.FunctionWeight, Offset: offset, }, nil + case "setrlimit.target.parent.is_session_leader": + return &eval.BoolEvaluator{ + EvalFnc: func(ctx *eval.Context) bool { + ctx.AppendResolvedField(field) + ev := ctx.Event.(*Event) + if !ev.Setrlimit.Target.HasParent() { + return false + } + return ev.Setrlimit.Target.Parent.PIDContext.IsSessionLeader + }, + Field: field, + Weight: eval.FunctionWeight, + Offset: offset, + }, nil case "setrlimit.target.parent.is_thread": return &eval.BoolEvaluator{ EvalFnc: func(ctx *eval.Context) bool { @@ -31906,6 +32084,33 @@ func (_ *Model) GetEvaluator(field eval.Field, regID eval.RegisterID, offset int Weight: eval.IteratorWeight, Offset: offset, }, nil + case "signal.target.ancestors.is_session_leader": + return &eval.BoolArrayEvaluator{ + EvalFnc: func(ctx *eval.Context) []bool { + ctx.AppendResolvedField(field) + ev := ctx.Event.(*Event) + iterator := &ProcessAncestorsIterator{Root: ev.Signal.Target.Ancestor} + if regID != "" { + element := iterator.At(ctx, regID, ctx.Registers[regID]) + if element == nil { + return nil + } + result := element.ProcessContext.Process.PIDContext.IsSessionLeader + return []bool{result} + } + if result, ok := ctx.BoolCache[field]; ok { + return result + } + results := newIterator(iterator, "Signal.Target.Ancestor", ctx, nil, func(ev *Event, current *ProcessCacheEntry) bool { + return current.ProcessContext.Process.PIDContext.IsSessionLeader + }) + ctx.BoolCache[field] = results + return results + }, + Field: field, + Weight: eval.IteratorWeight, + Offset: offset, + }, nil case "signal.target.ancestors.is_thread": return &eval.BoolArrayEvaluator{ EvalFnc: func(ctx *eval.Context) []bool { @@ -33623,6 +33828,17 @@ func (_ *Model) GetEvaluator(field eval.Field, regID eval.RegisterID, offset int Weight: eval.FunctionWeight, Offset: offset, }, nil + case "signal.target.is_session_leader": + return &eval.BoolEvaluator{ + EvalFnc: func(ctx *eval.Context) bool { + ctx.AppendResolvedField(field) + ev := ctx.Event.(*Event) + return ev.Signal.Target.Process.PIDContext.IsSessionLeader + }, + Field: field, + Weight: eval.FunctionWeight, + Offset: offset, + }, nil case "signal.target.is_thread": return &eval.BoolEvaluator{ EvalFnc: func(ctx *eval.Context) bool { @@ -35050,6 +35266,20 @@ func (_ *Model) GetEvaluator(field eval.Field, regID eval.RegisterID, offset int Weight: eval.FunctionWeight, Offset: offset, }, nil + case "signal.target.parent.is_session_leader": + return &eval.BoolEvaluator{ + EvalFnc: func(ctx *eval.Context) bool { + ctx.AppendResolvedField(field) + ev := ctx.Event.(*Event) + if !ev.Signal.Target.HasParent() { + return false + } + return ev.Signal.Target.Parent.PIDContext.IsSessionLeader + }, + Field: field, + Weight: eval.FunctionWeight, + Offset: offset, + }, nil case "signal.target.parent.is_thread": return &eval.BoolEvaluator{ EvalFnc: func(ctx *eval.Context) bool { @@ -36959,6 +37189,7 @@ func (ev *Event) GetFields() []eval.Field { "exec.interpreter.file.user", "exec.is_exec", "exec.is_kworker", + "exec.is_session_leader", "exec.is_thread", "exec.mntns", "exec.netns", @@ -37072,6 +37303,7 @@ func (ev *Event) GetFields() []eval.Field { "exit.interpreter.file.user", "exit.is_exec", "exit.is_kworker", + "exit.is_session_leader", "exit.is_thread", "exit.mntns", "exit.netns", @@ -37448,6 +37680,7 @@ func (ev *Event) GetFields() []eval.Field { "process.ancestors.interpreter.file.user", "process.ancestors.is_exec", "process.ancestors.is_kworker", + "process.ancestors.is_session_leader", "process.ancestors.is_thread", "process.ancestors.length", "process.ancestors.mntns", @@ -37559,6 +37792,7 @@ func (ev *Event) GetFields() []eval.Field { "process.interpreter.file.user", "process.is_exec", "process.is_kworker", + "process.is_session_leader", "process.is_thread", "process.mntns", "process.netns", @@ -37651,6 +37885,7 @@ func (ev *Event) GetFields() []eval.Field { "process.parent.interpreter.file.user", "process.parent.is_exec", "process.parent.is_kworker", + "process.parent.is_session_leader", "process.parent.is_thread", "process.parent.mntns", "process.parent.netns", @@ -37781,6 +38016,7 @@ func (ev *Event) GetFields() []eval.Field { "ptrace.tracee.ancestors.interpreter.file.user", "ptrace.tracee.ancestors.is_exec", "ptrace.tracee.ancestors.is_kworker", + "ptrace.tracee.ancestors.is_session_leader", "ptrace.tracee.ancestors.is_thread", "ptrace.tracee.ancestors.length", "ptrace.tracee.ancestors.mntns", @@ -37892,6 +38128,7 @@ func (ev *Event) GetFields() []eval.Field { "ptrace.tracee.interpreter.file.user", "ptrace.tracee.is_exec", "ptrace.tracee.is_kworker", + "ptrace.tracee.is_session_leader", "ptrace.tracee.is_thread", "ptrace.tracee.mntns", "ptrace.tracee.netns", @@ -37984,6 +38221,7 @@ func (ev *Event) GetFields() []eval.Field { "ptrace.tracee.parent.interpreter.file.user", "ptrace.tracee.parent.is_exec", "ptrace.tracee.parent.is_kworker", + "ptrace.tracee.parent.is_session_leader", "ptrace.tracee.parent.is_thread", "ptrace.tracee.parent.mntns", "ptrace.tracee.parent.netns", @@ -38242,6 +38480,7 @@ func (ev *Event) GetFields() []eval.Field { "setrlimit.target.ancestors.interpreter.file.user", "setrlimit.target.ancestors.is_exec", "setrlimit.target.ancestors.is_kworker", + "setrlimit.target.ancestors.is_session_leader", "setrlimit.target.ancestors.is_thread", "setrlimit.target.ancestors.length", "setrlimit.target.ancestors.mntns", @@ -38353,6 +38592,7 @@ func (ev *Event) GetFields() []eval.Field { "setrlimit.target.interpreter.file.user", "setrlimit.target.is_exec", "setrlimit.target.is_kworker", + "setrlimit.target.is_session_leader", "setrlimit.target.is_thread", "setrlimit.target.mntns", "setrlimit.target.netns", @@ -38445,6 +38685,7 @@ func (ev *Event) GetFields() []eval.Field { "setrlimit.target.parent.interpreter.file.user", "setrlimit.target.parent.is_exec", "setrlimit.target.parent.is_kworker", + "setrlimit.target.parent.is_session_leader", "setrlimit.target.parent.is_thread", "setrlimit.target.parent.mntns", "setrlimit.target.parent.netns", @@ -38622,6 +38863,7 @@ func (ev *Event) GetFields() []eval.Field { "signal.target.ancestors.interpreter.file.user", "signal.target.ancestors.is_exec", "signal.target.ancestors.is_kworker", + "signal.target.ancestors.is_session_leader", "signal.target.ancestors.is_thread", "signal.target.ancestors.length", "signal.target.ancestors.mntns", @@ -38733,6 +38975,7 @@ func (ev *Event) GetFields() []eval.Field { "signal.target.interpreter.file.user", "signal.target.is_exec", "signal.target.is_kworker", + "signal.target.is_session_leader", "signal.target.is_thread", "signal.target.mntns", "signal.target.netns", @@ -38825,6 +39068,7 @@ func (ev *Event) GetFields() []eval.Field { "signal.target.parent.interpreter.file.user", "signal.target.parent.is_exec", "signal.target.parent.is_kworker", + "signal.target.parent.is_session_leader", "signal.target.parent.is_thread", "signal.target.parent.mntns", "signal.target.parent.netns", @@ -39533,6 +39777,8 @@ func (ev *Event) GetFieldMetadata(field eval.Field) (eval.EventType, reflect.Kin return "exec", reflect.Bool, "bool", false, nil case "exec.is_kworker": return "exec", reflect.Bool, "bool", false, nil + case "exec.is_session_leader": + return "exec", reflect.Bool, "bool", false, nil case "exec.is_thread": return "exec", reflect.Bool, "bool", false, nil case "exec.mntns": @@ -39759,6 +40005,8 @@ func (ev *Event) GetFieldMetadata(field eval.Field) (eval.EventType, reflect.Kin return "exit", reflect.Bool, "bool", false, nil case "exit.is_kworker": return "exit", reflect.Bool, "bool", false, nil + case "exit.is_session_leader": + return "exit", reflect.Bool, "bool", false, nil case "exit.is_thread": return "exit", reflect.Bool, "bool", false, nil case "exit.mntns": @@ -40511,6 +40759,8 @@ func (ev *Event) GetFieldMetadata(field eval.Field) (eval.EventType, reflect.Kin return "", reflect.Bool, "bool", false, nil case "process.ancestors.is_kworker": return "", reflect.Bool, "bool", false, nil + case "process.ancestors.is_session_leader": + return "", reflect.Bool, "bool", false, nil case "process.ancestors.is_thread": return "", reflect.Bool, "bool", false, nil case "process.ancestors.length": @@ -40733,6 +40983,8 @@ func (ev *Event) GetFieldMetadata(field eval.Field) (eval.EventType, reflect.Kin return "", reflect.Bool, "bool", false, nil case "process.is_kworker": return "", reflect.Bool, "bool", false, nil + case "process.is_session_leader": + return "", reflect.Bool, "bool", false, nil case "process.is_thread": return "", reflect.Bool, "bool", false, nil case "process.mntns": @@ -40917,6 +41169,8 @@ func (ev *Event) GetFieldMetadata(field eval.Field) (eval.EventType, reflect.Kin return "", reflect.Bool, "bool", false, nil case "process.parent.is_kworker": return "", reflect.Bool, "bool", false, nil + case "process.parent.is_session_leader": + return "", reflect.Bool, "bool", false, nil case "process.parent.is_thread": return "", reflect.Bool, "bool", false, nil case "process.parent.mntns": @@ -41177,6 +41431,8 @@ func (ev *Event) GetFieldMetadata(field eval.Field) (eval.EventType, reflect.Kin return "ptrace", reflect.Bool, "bool", false, nil case "ptrace.tracee.ancestors.is_kworker": return "ptrace", reflect.Bool, "bool", false, nil + case "ptrace.tracee.ancestors.is_session_leader": + return "ptrace", reflect.Bool, "bool", false, nil case "ptrace.tracee.ancestors.is_thread": return "ptrace", reflect.Bool, "bool", false, nil case "ptrace.tracee.ancestors.length": @@ -41399,6 +41655,8 @@ func (ev *Event) GetFieldMetadata(field eval.Field) (eval.EventType, reflect.Kin return "ptrace", reflect.Bool, "bool", false, nil case "ptrace.tracee.is_kworker": return "ptrace", reflect.Bool, "bool", false, nil + case "ptrace.tracee.is_session_leader": + return "ptrace", reflect.Bool, "bool", false, nil case "ptrace.tracee.is_thread": return "ptrace", reflect.Bool, "bool", false, nil case "ptrace.tracee.mntns": @@ -41583,6 +41841,8 @@ func (ev *Event) GetFieldMetadata(field eval.Field) (eval.EventType, reflect.Kin return "ptrace", reflect.Bool, "bool", false, nil case "ptrace.tracee.parent.is_kworker": return "ptrace", reflect.Bool, "bool", false, nil + case "ptrace.tracee.parent.is_session_leader": + return "ptrace", reflect.Bool, "bool", false, nil case "ptrace.tracee.parent.is_thread": return "ptrace", reflect.Bool, "bool", false, nil case "ptrace.tracee.parent.mntns": @@ -42099,6 +42359,8 @@ func (ev *Event) GetFieldMetadata(field eval.Field) (eval.EventType, reflect.Kin return "setrlimit", reflect.Bool, "bool", false, nil case "setrlimit.target.ancestors.is_kworker": return "setrlimit", reflect.Bool, "bool", false, nil + case "setrlimit.target.ancestors.is_session_leader": + return "setrlimit", reflect.Bool, "bool", false, nil case "setrlimit.target.ancestors.is_thread": return "setrlimit", reflect.Bool, "bool", false, nil case "setrlimit.target.ancestors.length": @@ -42321,6 +42583,8 @@ func (ev *Event) GetFieldMetadata(field eval.Field) (eval.EventType, reflect.Kin return "setrlimit", reflect.Bool, "bool", false, nil case "setrlimit.target.is_kworker": return "setrlimit", reflect.Bool, "bool", false, nil + case "setrlimit.target.is_session_leader": + return "setrlimit", reflect.Bool, "bool", false, nil case "setrlimit.target.is_thread": return "setrlimit", reflect.Bool, "bool", false, nil case "setrlimit.target.mntns": @@ -42505,6 +42769,8 @@ func (ev *Event) GetFieldMetadata(field eval.Field) (eval.EventType, reflect.Kin return "setrlimit", reflect.Bool, "bool", false, nil case "setrlimit.target.parent.is_kworker": return "setrlimit", reflect.Bool, "bool", false, nil + case "setrlimit.target.parent.is_session_leader": + return "setrlimit", reflect.Bool, "bool", false, nil case "setrlimit.target.parent.is_thread": return "setrlimit", reflect.Bool, "bool", false, nil case "setrlimit.target.parent.mntns": @@ -42859,6 +43125,8 @@ func (ev *Event) GetFieldMetadata(field eval.Field) (eval.EventType, reflect.Kin return "signal", reflect.Bool, "bool", false, nil case "signal.target.ancestors.is_kworker": return "signal", reflect.Bool, "bool", false, nil + case "signal.target.ancestors.is_session_leader": + return "signal", reflect.Bool, "bool", false, nil case "signal.target.ancestors.is_thread": return "signal", reflect.Bool, "bool", false, nil case "signal.target.ancestors.length": @@ -43081,6 +43349,8 @@ func (ev *Event) GetFieldMetadata(field eval.Field) (eval.EventType, reflect.Kin return "signal", reflect.Bool, "bool", false, nil case "signal.target.is_kworker": return "signal", reflect.Bool, "bool", false, nil + case "signal.target.is_session_leader": + return "signal", reflect.Bool, "bool", false, nil case "signal.target.is_thread": return "signal", reflect.Bool, "bool", false, nil case "signal.target.mntns": @@ -43265,6 +43535,8 @@ func (ev *Event) GetFieldMetadata(field eval.Field) (eval.EventType, reflect.Kin return "signal", reflect.Bool, "bool", false, nil case "signal.target.parent.is_kworker": return "signal", reflect.Bool, "bool", false, nil + case "signal.target.parent.is_session_leader": + return "signal", reflect.Bool, "bool", false, nil case "signal.target.parent.is_thread": return "signal", reflect.Bool, "bool", false, nil case "signal.target.parent.mntns": @@ -44248,6 +44520,8 @@ func (ev *Event) SetFieldValue(field eval.Field, value interface{}) error { return ev.setBoolFieldValue("exec.is_exec", &ev.Exec.Process.IsExec, value) case "exec.is_kworker": return ev.setBoolFieldValue("exec.is_kworker", &ev.Exec.Process.PIDContext.IsKworker, value) + case "exec.is_session_leader": + return ev.setBoolFieldValue("exec.is_session_leader", &ev.Exec.Process.PIDContext.IsSessionLeader, value) case "exec.is_thread": return ev.setBoolFieldValue("exec.is_thread", &ev.Exec.Process.IsThread, value) case "exec.mntns": @@ -44589,6 +44863,8 @@ func (ev *Event) SetFieldValue(field eval.Field, value interface{}) error { return ev.setBoolFieldValue("exit.is_exec", &ev.Exit.Process.IsExec, value) case "exit.is_kworker": return ev.setBoolFieldValue("exit.is_kworker", &ev.Exit.Process.PIDContext.IsKworker, value) + case "exit.is_session_leader": + return ev.setBoolFieldValue("exit.is_session_leader", &ev.Exit.Process.PIDContext.IsSessionLeader, value) case "exit.is_thread": return ev.setBoolFieldValue("exit.is_thread", &ev.Exit.Process.IsThread, value) case "exit.mntns": @@ -45522,6 +45798,8 @@ func (ev *Event) SetFieldValue(field eval.Field, value interface{}) error { return ev.setBoolFieldValue("process.ancestors.is_exec", &ev.BaseEvent.ProcessContext.Ancestor.ProcessContext.Process.IsExec, value) case "process.ancestors.is_kworker": return ev.setBoolFieldValue("process.ancestors.is_kworker", &ev.BaseEvent.ProcessContext.Ancestor.ProcessContext.Process.PIDContext.IsKworker, value) + case "process.ancestors.is_session_leader": + return ev.setBoolFieldValue("process.ancestors.is_session_leader", &ev.BaseEvent.ProcessContext.Ancestor.ProcessContext.Process.PIDContext.IsSessionLeader, value) case "process.ancestors.is_thread": return ev.setBoolFieldValue("process.ancestors.is_thread", &ev.BaseEvent.ProcessContext.Ancestor.ProcessContext.Process.IsThread, value) case "process.ancestors.length": @@ -45859,6 +46137,8 @@ func (ev *Event) SetFieldValue(field eval.Field, value interface{}) error { return ev.setBoolFieldValue("process.is_exec", &ev.BaseEvent.ProcessContext.Process.IsExec, value) case "process.is_kworker": return ev.setBoolFieldValue("process.is_kworker", &ev.BaseEvent.ProcessContext.Process.PIDContext.IsKworker, value) + case "process.is_session_leader": + return ev.setBoolFieldValue("process.is_session_leader", &ev.BaseEvent.ProcessContext.Process.PIDContext.IsSessionLeader, value) case "process.is_thread": return ev.setBoolFieldValue("process.is_thread", &ev.BaseEvent.ProcessContext.Process.IsThread, value) case "process.mntns": @@ -46153,6 +46433,8 @@ func (ev *Event) SetFieldValue(field eval.Field, value interface{}) error { return ev.setBoolFieldValue("process.parent.is_exec", &ev.BaseEvent.ProcessContext.Parent.IsExec, value) case "process.parent.is_kworker": return ev.setBoolFieldValue("process.parent.is_kworker", &ev.BaseEvent.ProcessContext.Parent.PIDContext.IsKworker, value) + case "process.parent.is_session_leader": + return ev.setBoolFieldValue("process.parent.is_session_leader", &ev.BaseEvent.ProcessContext.Parent.PIDContext.IsSessionLeader, value) case "process.parent.is_thread": return ev.setBoolFieldValue("process.parent.is_thread", &ev.BaseEvent.ProcessContext.Parent.IsThread, value) case "process.parent.mntns": @@ -46533,6 +46815,8 @@ func (ev *Event) SetFieldValue(field eval.Field, value interface{}) error { return ev.setBoolFieldValue("ptrace.tracee.ancestors.is_exec", &ev.PTrace.Tracee.Ancestor.ProcessContext.Process.IsExec, value) case "ptrace.tracee.ancestors.is_kworker": return ev.setBoolFieldValue("ptrace.tracee.ancestors.is_kworker", &ev.PTrace.Tracee.Ancestor.ProcessContext.Process.PIDContext.IsKworker, value) + case "ptrace.tracee.ancestors.is_session_leader": + return ev.setBoolFieldValue("ptrace.tracee.ancestors.is_session_leader", &ev.PTrace.Tracee.Ancestor.ProcessContext.Process.PIDContext.IsSessionLeader, value) case "ptrace.tracee.ancestors.is_thread": return ev.setBoolFieldValue("ptrace.tracee.ancestors.is_thread", &ev.PTrace.Tracee.Ancestor.ProcessContext.Process.IsThread, value) case "ptrace.tracee.ancestors.length": @@ -46870,6 +47154,8 @@ func (ev *Event) SetFieldValue(field eval.Field, value interface{}) error { return ev.setBoolFieldValue("ptrace.tracee.is_exec", &ev.PTrace.Tracee.Process.IsExec, value) case "ptrace.tracee.is_kworker": return ev.setBoolFieldValue("ptrace.tracee.is_kworker", &ev.PTrace.Tracee.Process.PIDContext.IsKworker, value) + case "ptrace.tracee.is_session_leader": + return ev.setBoolFieldValue("ptrace.tracee.is_session_leader", &ev.PTrace.Tracee.Process.PIDContext.IsSessionLeader, value) case "ptrace.tracee.is_thread": return ev.setBoolFieldValue("ptrace.tracee.is_thread", &ev.PTrace.Tracee.Process.IsThread, value) case "ptrace.tracee.mntns": @@ -47164,6 +47450,8 @@ func (ev *Event) SetFieldValue(field eval.Field, value interface{}) error { return ev.setBoolFieldValue("ptrace.tracee.parent.is_exec", &ev.PTrace.Tracee.Parent.IsExec, value) case "ptrace.tracee.parent.is_kworker": return ev.setBoolFieldValue("ptrace.tracee.parent.is_kworker", &ev.PTrace.Tracee.Parent.PIDContext.IsKworker, value) + case "ptrace.tracee.parent.is_session_leader": + return ev.setBoolFieldValue("ptrace.tracee.parent.is_session_leader", &ev.PTrace.Tracee.Parent.PIDContext.IsSessionLeader, value) case "ptrace.tracee.parent.is_thread": return ev.setBoolFieldValue("ptrace.tracee.parent.is_thread", &ev.PTrace.Tracee.Parent.IsThread, value) case "ptrace.tracee.parent.mntns": @@ -48334,6 +48622,14 @@ func (ev *Event) SetFieldValue(field eval.Field, value interface{}) error { ev.Setrlimit.Target.Ancestor = &ProcessCacheEntry{} } return ev.setBoolFieldValue("setrlimit.target.ancestors.is_kworker", &ev.Setrlimit.Target.Ancestor.ProcessContext.Process.PIDContext.IsKworker, value) + case "setrlimit.target.ancestors.is_session_leader": + if ev.Setrlimit.Target == nil { + ev.Setrlimit.Target = &ProcessContext{} + } + if ev.Setrlimit.Target.Ancestor == nil { + ev.Setrlimit.Target.Ancestor = &ProcessCacheEntry{} + } + return ev.setBoolFieldValue("setrlimit.target.ancestors.is_session_leader", &ev.Setrlimit.Target.Ancestor.ProcessContext.Process.PIDContext.IsSessionLeader, value) case "setrlimit.target.ancestors.is_thread": if ev.Setrlimit.Target == nil { ev.Setrlimit.Target = &ProcessContext{} @@ -49070,6 +49366,11 @@ func (ev *Event) SetFieldValue(field eval.Field, value interface{}) error { ev.Setrlimit.Target = &ProcessContext{} } return ev.setBoolFieldValue("setrlimit.target.is_kworker", &ev.Setrlimit.Target.Process.PIDContext.IsKworker, value) + case "setrlimit.target.is_session_leader": + if ev.Setrlimit.Target == nil { + ev.Setrlimit.Target = &ProcessContext{} + } + return ev.setBoolFieldValue("setrlimit.target.is_session_leader", &ev.Setrlimit.Target.Process.PIDContext.IsSessionLeader, value) case "setrlimit.target.is_thread": if ev.Setrlimit.Target == nil { ev.Setrlimit.Target = &ProcessContext{} @@ -49907,6 +50208,14 @@ func (ev *Event) SetFieldValue(field eval.Field, value interface{}) error { ev.Setrlimit.Target.Parent = &Process{} } return ev.setBoolFieldValue("setrlimit.target.parent.is_kworker", &ev.Setrlimit.Target.Parent.PIDContext.IsKworker, value) + case "setrlimit.target.parent.is_session_leader": + if ev.Setrlimit.Target == nil { + ev.Setrlimit.Target = &ProcessContext{} + } + if ev.Setrlimit.Target.Parent == nil { + ev.Setrlimit.Target.Parent = &Process{} + } + return ev.setBoolFieldValue("setrlimit.target.parent.is_session_leader", &ev.Setrlimit.Target.Parent.PIDContext.IsSessionLeader, value) case "setrlimit.target.parent.is_thread": if ev.Setrlimit.Target == nil { ev.Setrlimit.Target = &ProcessContext{} @@ -51105,6 +51414,14 @@ func (ev *Event) SetFieldValue(field eval.Field, value interface{}) error { ev.Signal.Target.Ancestor = &ProcessCacheEntry{} } return ev.setBoolFieldValue("signal.target.ancestors.is_kworker", &ev.Signal.Target.Ancestor.ProcessContext.Process.PIDContext.IsKworker, value) + case "signal.target.ancestors.is_session_leader": + if ev.Signal.Target == nil { + ev.Signal.Target = &ProcessContext{} + } + if ev.Signal.Target.Ancestor == nil { + ev.Signal.Target.Ancestor = &ProcessCacheEntry{} + } + return ev.setBoolFieldValue("signal.target.ancestors.is_session_leader", &ev.Signal.Target.Ancestor.ProcessContext.Process.PIDContext.IsSessionLeader, value) case "signal.target.ancestors.is_thread": if ev.Signal.Target == nil { ev.Signal.Target = &ProcessContext{} @@ -51841,6 +52158,11 @@ func (ev *Event) SetFieldValue(field eval.Field, value interface{}) error { ev.Signal.Target = &ProcessContext{} } return ev.setBoolFieldValue("signal.target.is_kworker", &ev.Signal.Target.Process.PIDContext.IsKworker, value) + case "signal.target.is_session_leader": + if ev.Signal.Target == nil { + ev.Signal.Target = &ProcessContext{} + } + return ev.setBoolFieldValue("signal.target.is_session_leader", &ev.Signal.Target.Process.PIDContext.IsSessionLeader, value) case "signal.target.is_thread": if ev.Signal.Target == nil { ev.Signal.Target = &ProcessContext{} @@ -52678,6 +53000,14 @@ func (ev *Event) SetFieldValue(field eval.Field, value interface{}) error { ev.Signal.Target.Parent = &Process{} } return ev.setBoolFieldValue("signal.target.parent.is_kworker", &ev.Signal.Target.Parent.PIDContext.IsKworker, value) + case "signal.target.parent.is_session_leader": + if ev.Signal.Target == nil { + ev.Signal.Target = &ProcessContext{} + } + if ev.Signal.Target.Parent == nil { + ev.Signal.Target.Parent = &Process{} + } + return ev.setBoolFieldValue("signal.target.parent.is_session_leader", &ev.Signal.Target.Parent.PIDContext.IsSessionLeader, value) case "signal.target.parent.is_thread": if ev.Signal.Target == nil { ev.Signal.Target = &ProcessContext{} diff --git a/pkg/security/secl/model/event_deep_copy_unix.go b/pkg/security/secl/model/event_deep_copy_unix.go index 74ef9332bcf5c1..2f95553550d63f 100644 --- a/pkg/security/secl/model/event_deep_copy_unix.go +++ b/pkg/security/secl/model/event_deep_copy_unix.go @@ -186,6 +186,7 @@ func deepCopyPIDContext(fieldToCopy PIDContext) PIDContext { copied := PIDContext{} copied.ExecInode = fieldToCopy.ExecInode copied.IsKworker = fieldToCopy.IsKworker + copied.IsSessionLeader = fieldToCopy.IsSessionLeader copied.MntNS = fieldToCopy.MntNS copied.NSID = fieldToCopy.NSID copied.NetNS = fieldToCopy.NetNS diff --git a/pkg/security/secl/model/marshallers_linux.go b/pkg/security/secl/model/marshallers_linux.go index 936bbde90dc6d1..e0f9575c3c6788 100644 --- a/pkg/security/secl/model/marshallers_linux.go +++ b/pkg/security/secl/model/marshallers_linux.go @@ -14,7 +14,7 @@ import ( const ( // PidCacheEntrySize is the size of the pid_cache_t - PidCacheEntrySize = 88 + PidCacheEntrySize = 96 ) // BinaryMarshaler interface implemented by every event type @@ -147,7 +147,13 @@ func (e *Process) MarshalPidCache(data []byte, bootTime time.Time) (int, error) marshalTime(data[16:24], e.ExitTime.Sub(bootTime)) binary.NativeEndian.PutUint64(data[24:32], e.UserSession.K8SSessionID) binary.NativeEndian.PutUint64(data[32:40], e.ForkFlags) - written := 40 + if e.IsSessionLeader { + binary.NativeEndian.PutUint32(data[40:44], 1) + } else { + binary.NativeEndian.PutUint32(data[40:44], 0) + } + binary.NativeEndian.PutUint32(data[44:48], 0) // padding + written := 48 n, err := MarshalBinary(data[written:], &e.Credentials) if err != nil { diff --git a/pkg/security/secl/model/model_unix.go b/pkg/security/secl/model/model_unix.go index b59d9c923f111f..d0773e3a63156d 100644 --- a/pkg/security/secl/model/model_unix.go +++ b/pkg/security/secl/model/model_unix.go @@ -633,7 +633,8 @@ type PIDContext struct { Tid uint32 `field:"tid"` // SECLDoc[tid] Definition:`Thread ID of the thread` NetNS uint32 `field:"netns"` // SECLDoc[netns] Definition:`NetNS ID of the process` MntNS uint32 `field:"mntns"` // SECLDoc[mntns] Definition:`MNTNS ID of the process` - IsKworker bool `field:"is_kworker"` // SECLDoc[is_kworker] Definition:`Indicates whether the process is a kworker` + IsKworker bool `field:"is_kworker"` // SECLDoc[is_kworker] Definition:`Indicates whether the process is a kworker` + IsSessionLeader bool `field:"is_session_leader"` // SECLDoc[is_session_leader] Definition:`Indicates whether the process is a session leader` PPid uint32 `field:"ppid"` // SECLDoc[ppid] Definition:`Parent process ID` ExecInode uint64 `field:"-"` // used to track exec and event loss UserSessionID uint64 `field:"-"` // used to track user sessions from kernel space diff --git a/pkg/security/secl/model/unmarshallers_linux.go b/pkg/security/secl/model/unmarshallers_linux.go index 9de4dace68523e..b1ca7a3670b9a4 100644 --- a/pkg/security/secl/model/unmarshallers_linux.go +++ b/pkg/security/secl/model/unmarshallers_linux.go @@ -228,20 +228,22 @@ func (e *Process) UnmarshalPidCacheBinary(data []byte) (int, error) { e.ExitTime = unmarshalTime(data[16:24]) e.UserSession.K8SSessionID = binary.NativeEndian.Uint64(data[24:32]) e.ForkFlags = binary.NativeEndian.Uint64(data[32:40]) + e.IsSessionLeader = binary.NativeEndian.Uint32(data[40:44]) > 0 + // data[44:48] is padding // Unmarshal the credentials contained in pid_cache_t - read, err := e.Credentials.UnmarshalBinary(data[40:]) + read, err := e.Credentials.UnmarshalBinary(data[48:]) if err != nil { return 0, err } - read += 40 + read += 48 return validateReadSize(size, read) } // UnmarshalBinary unmarshalls a binary representation of itself func (e *Process) UnmarshalBinary(data []byte) (int, error) { - const size = 292 // size of struct exec_event_t starting from process_entry_t, inclusive + const size = 300 // size of struct exec_event_t starting from process_entry_t, inclusive if len(data) < size { return 0, ErrNotEnoughData } @@ -586,7 +588,7 @@ func (e *SELinuxEvent) UnmarshalBinary(data []byte) (int, error) { // UnmarshalBinary unmarshalls a binary representation of itself, process_context_t kernel side func (p *PIDContext) UnmarshalBinary(data []byte) (int, error) { - if len(data) < 40 { + if len(data) < 48 { return 0, ErrNotEnoughData } @@ -598,8 +600,10 @@ func (p *PIDContext) UnmarshalBinary(data []byte) (int, error) { p.PPid = binary.NativeEndian.Uint32(data[20:24]) p.ExecInode = binary.NativeEndian.Uint64(data[24:32]) p.UserSessionID = binary.NativeEndian.Uint64(data[32:40]) + p.IsSessionLeader = binary.NativeEndian.Uint32(data[40:44]) > 0 + // data[44:48] is padding - return 40, nil + return 48, nil } // UnmarshalBinary unmarshalls a binary representation of itself diff --git a/pkg/security/serializers/deserializers.go b/pkg/security/serializers/deserializers.go index e593fcba4f3c00..0b0bc356da6897 100644 --- a/pkg/security/serializers/deserializers.go +++ b/pkg/security/serializers/deserializers.go @@ -67,7 +67,8 @@ func newProcess(ps *ProcessSerializer) model.Process { PIDContext: model.PIDContext{ Pid: ps.Pid, Tid: ps.Tid, - IsKworker: ps.IsKworker, + IsKworker: ps.IsKworker, + IsSessionLeader: ps.IsSessionLeader, PPid: getPointerValue(ps.PPid), }, } diff --git a/pkg/security/serializers/serializers_base_linux_easyjson.go b/pkg/security/serializers/serializers_base_linux_easyjson.go index 2c54e2ba4805b9..73cc4e4fcc6a62 100644 --- a/pkg/security/serializers/serializers_base_linux_easyjson.go +++ b/pkg/security/serializers/serializers_base_linux_easyjson.go @@ -1153,6 +1153,12 @@ func easyjsonA1e47abeDecodeGithubComDataDogDatadogAgentPkgSecuritySerializers6(i } else { out.IsKworker = bool(in.Bool()) } + case "is_session_leader": + if in.IsNull() { + in.Skip() + } else { + out.IsSessionLeader = bool(in.Bool()) + } case "is_exec": if in.IsNull() { in.Skip() @@ -1527,6 +1533,11 @@ func easyjsonA1e47abeEncodeGithubComDataDogDatadogAgentPkgSecuritySerializers6(o out.RawString(prefix) out.Bool(bool(in.IsKworker)) } + if in.IsSessionLeader { + const prefix string = ",\"is_session_leader\":" + out.RawString(prefix) + out.Bool(bool(in.IsSessionLeader)) + } if in.IsExec { const prefix string = ",\"is_exec\":" out.RawString(prefix) diff --git a/pkg/security/serializers/serializers_linux.go b/pkg/security/serializers/serializers_linux.go index ff688cd61c2a93..5770e285ddc968 100644 --- a/pkg/security/serializers/serializers_linux.go +++ b/pkg/security/serializers/serializers_linux.go @@ -320,6 +320,8 @@ type ProcessSerializer struct { IsThread bool `json:"is_thread,omitempty"` // Indicates whether the process is a kworker IsKworker bool `json:"is_kworker,omitempty"` + // Indicates whether the process is a session leader + IsSessionLeader bool `json:"is_session_leader,omitempty"` // Indicates whether the process entry is from a new binary execution IsExec bool `json:"is_exec,omitempty"` // Indicates whether the process is an exec following another exec @@ -972,7 +974,8 @@ func newProcessSerializer(ps *model.Process, e *model.Event) *ProcessSerializer Envs: envs, EnvsTruncated: envsTruncated, IsThread: ps.IsThread, - IsKworker: ps.IsKworker, + IsKworker: ps.IsKworker, + IsSessionLeader: ps.IsSessionLeader, IsExec: ps.IsExec, IsExecExec: ps.IsExecExec, IsParentMissing: ps.IsParentMissing, @@ -1035,7 +1038,8 @@ func newProcessSerializer(ps *model.Process, e *model.Event) *ProcessSerializer return &ProcessSerializer{ Pid: ps.Pid, Tid: ps.Tid, - IsKworker: ps.IsKworker, + IsKworker: ps.IsKworker, + IsSessionLeader: ps.IsSessionLeader, IsExec: ps.IsExec, IsExecExec: ps.IsExecExec, IsParentMissing: ps.IsParentMissing, diff --git a/pkg/security/serializers/serializers_linux_easyjson.go b/pkg/security/serializers/serializers_linux_easyjson.go index 3447d423d90bfa..68ad4647974ad8 100644 --- a/pkg/security/serializers/serializers_linux_easyjson.go +++ b/pkg/security/serializers/serializers_linux_easyjson.go @@ -2494,6 +2494,12 @@ func easyjsonDdc0fdbeDecodeGithubComDataDogDatadogAgentPkgSecuritySerializers16( } else { out.IsKworker = bool(in.Bool()) } + case "is_session_leader": + if in.IsNull() { + in.Skip() + } else { + out.IsSessionLeader = bool(in.Bool()) + } case "is_exec": if in.IsNull() { in.Skip() @@ -2825,6 +2831,11 @@ func easyjsonDdc0fdbeEncodeGithubComDataDogDatadogAgentPkgSecuritySerializers16( out.RawString(prefix) out.Bool(bool(in.IsKworker)) } + if in.IsSessionLeader { + const prefix string = ",\"is_session_leader\":" + out.RawString(prefix) + out.Bool(bool(in.IsSessionLeader)) + } if in.IsExec { const prefix string = ",\"is_exec\":" out.RawString(prefix) diff --git a/pkg/security/tests/process_test.go b/pkg/security/tests/process_test.go index dc394fb7799e31..e85afc2813dda1 100644 --- a/pkg/security/tests/process_test.go +++ b/pkg/security/tests/process_test.go @@ -1778,6 +1778,57 @@ func TestProcessIsThread(t *testing.T) { }) } +func TestProcessIsSessionLeader(t *testing.T) { + SkipIfNotAvailable(t) + + if ebpfLessEnabled { + t.Skip("is_session_leader not supported in ebpfless mode") + } + + setsidPath := which(t, "setsid") + sleepPath := which(t, "sleep") + shPath := which(t, "sh") + + ruleDefs := []*rules.RuleDefinition{ + { + ID: "test_session_leader", + Expression: fmt.Sprintf(`exec.file.path == "%s" && process.is_session_leader`, shPath), + }, + { + ID: "test_not_session_leader", + Expression: fmt.Sprintf(`exec.file.path == "%s" && !process.is_session_leader`, sleepPath), + }, + } + + test, err := newTestModule(t, nil, ruleDefs) + if err != nil { + t.Fatal(err) + } + defer test.Close() + + t.Run("session-leader", func(t *testing.T) { + test.WaitSignalFromRule(t, func() error { + // setsid creates a new session, making the child process a session leader + cmd := exec.Command(setsidPath, shPath, "-c", "true") + return cmd.Run() + }, func(event *model.Event, rule *rules.Rule) { + assertTriggeredRule(t, rule, "test_session_leader") + assert.True(t, event.ProcessContext.IsSessionLeader, "process should be a session leader") + }, "test_session_leader") + }) + + t.Run("not-session-leader", func(t *testing.T) { + test.WaitSignalFromRule(t, func() error { + // A regular child process is NOT a session leader + cmd := exec.Command(sleepPath, "0") + return cmd.Run() + }, func(event *model.Event, rule *rules.Rule) { + assertTriggeredRule(t, rule, "test_not_session_leader") + assert.False(t, event.ProcessContext.IsSessionLeader, "process should not be a session leader") + }, "test_not_session_leader") + }) +} + func TestProcessExit(t *testing.T) { SkipIfNotAvailable(t) From b704d29d6a9338191fefb4167101329eff01b9ce Mon Sep 17 00:00:00 2001 From: Lorenzo Susini Date: Wed, 25 Mar 2026 14:55:56 +0100 Subject: [PATCH 2/2] CWS: Address review feedback for is_session_leader - Use bitfield for is_kworker and is_session_leader in process_context_t, keeping the struct at 40 bytes instead of growing to 48 - Restore the original if/else in exec handler cookie update Co-Authored-By: Claude Opus 4.6 (1M context) --- pkg/security/ebpf/c/include/hooks/exec.h | 4 +--- .../ebpf/c/include/structs/events_context.h | 5 ++--- pkg/security/secl/model/unmarshallers_linux.go | 13 ++++++------- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/pkg/security/ebpf/c/include/hooks/exec.h b/pkg/security/ebpf/c/include/hooks/exec.h index 82dbee977dfecb..2d006391403658 100644 --- a/pkg/security/ebpf/c/include/hooks/exec.h +++ b/pkg/security/ebpf/c/include/hooks/exec.h @@ -808,9 +808,7 @@ int __attribute__((always_inline)) send_exec_event(ctx_t *ctx) { // update pid <-> cookie mapping if (fork_entry) { fork_entry->cookie = cookie; - } - - if (!fork_entry) { + } else { struct pid_cache_t new_pid_entry = { .cookie = cookie, }; diff --git a/pkg/security/ebpf/c/include/structs/events_context.h b/pkg/security/ebpf/c/include/structs/events_context.h index 8ee85b2ece3c54..c6d8c1bf223e0e 100644 --- a/pkg/security/ebpf/c/include/structs/events_context.h +++ b/pkg/security/ebpf/c/include/structs/events_context.h @@ -29,12 +29,11 @@ struct process_context_t { u32 tid; u32 netns; u32 mntns; - u32 is_kworker; + u32 is_kworker:16; + u32 is_session_leader:16; u32 ppid; u64 inode; u64 user_session_id; - u32 is_session_leader; - u32 padding_session; }; struct ktimeval { diff --git a/pkg/security/secl/model/unmarshallers_linux.go b/pkg/security/secl/model/unmarshallers_linux.go index b1ca7a3670b9a4..23352bd1d40b4a 100644 --- a/pkg/security/secl/model/unmarshallers_linux.go +++ b/pkg/security/secl/model/unmarshallers_linux.go @@ -131,7 +131,7 @@ func (e *CapsetEvent) UnmarshalBinary(data []byte) (int, error) { // UnmarshalBinary unmarshalls a binary representation of itself func (e *Credentials) UnmarshalBinary(data []byte) (int, error) { - if len(data) < 48 { + if len(data) < 40 { return 0, ErrNotEnoughData } @@ -588,7 +588,7 @@ func (e *SELinuxEvent) UnmarshalBinary(data []byte) (int, error) { // UnmarshalBinary unmarshalls a binary representation of itself, process_context_t kernel side func (p *PIDContext) UnmarshalBinary(data []byte) (int, error) { - if len(data) < 48 { + if len(data) < 40 { return 0, ErrNotEnoughData } @@ -596,14 +596,13 @@ func (p *PIDContext) UnmarshalBinary(data []byte) (int, error) { p.Tid = binary.NativeEndian.Uint32(data[4:8]) p.NetNS = binary.NativeEndian.Uint32(data[8:12]) p.MntNS = binary.NativeEndian.Uint32(data[12:16]) - p.IsKworker = binary.NativeEndian.Uint32(data[16:20]) > 0 + p.IsKworker = binary.NativeEndian.Uint16(data[16:18]) > 0 + p.IsSessionLeader = binary.NativeEndian.Uint16(data[18:20]) > 0 p.PPid = binary.NativeEndian.Uint32(data[20:24]) p.ExecInode = binary.NativeEndian.Uint64(data[24:32]) p.UserSessionID = binary.NativeEndian.Uint64(data[32:40]) - p.IsSessionLeader = binary.NativeEndian.Uint32(data[40:44]) > 0 - // data[44:48] is padding - return 48, nil + return 40, nil } // UnmarshalBinary unmarshalls a binary representation of itself @@ -1034,7 +1033,7 @@ func (e *CgroupWriteEvent) UnmarshalBinary(data []byte) (int, error) { // EventUnmarshalBinary unmarshals a binary representation of itself func (adlc *ActivityDumpLoadConfig) EventUnmarshalBinary(data []byte) (int, error) { - if len(data) < 48 { + if len(data) < 40 { return 0, ErrNotEnoughData }