Skip to content

Commit

Permalink
tetragon: detect if binary execution raised process capabilities
Browse files Browse the repository at this point in the history
Add binary_properties.caps_raised bool if set then the current execution
gained new capabilities through binary execution.

This happens when the executed binary has:
1. setuid to root bit set
2. file capabilities.

To inspect which capabilities were raised, check the other permitted
and effective capabilities fields of process_exec.

The capabilities are re-calculated by the capability LSM during execve,
so let's avoid trying to be smart and calculate or guess them...
we just display the final results.

To display these fields, Tetragon must be run with enable-process-cred

The event:
  "process_exec": {
    "process": {
      "exec_id": "OjIwMTAxOTMxNzAzMjc4OjE0MDUwOA==",
      "pid": 140508,
      "uid": 1000,
      "cwd": "/home/tixxdz/work/station/code/src/github.com/tixxdz/tetragon",
      "binary": "/usr/bin/ping",
      "arguments": "ebpf.io",
      "flags": "execve clone",
      "start_time": "2023-11-23T14:32:53.227623175Z",
      "auid": 1000,
      "parent_exec_id": "OjI5NjYzNzAwMDAwMDA6NDQ4NTk=",
      "refcnt": 1,
      "cap": {
        "permitted": [
          "CAP_NET_RAW"
        ],
        "effective": [
          "CAP_NET_RAW"
        ]
      },
      "tid": 140508,
      "process_credentials": {
        "uid": 1000,
        "gid": 1000,
        "euid": 1000,
        "egid": 1000,
        "suid": 1000,
        "sgid": 1000,
        "fsuid": 1000,
        "fsgid": 1000
      },
      "binary_properties": {
        "caps_raised": true
      }
    },

Signed-off-by: Djalal Harouni <[email protected]>
  • Loading branch information
tixxdz committed Dec 6, 2023
1 parent ca5f1c0 commit 5a34a8e
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 7 deletions.
23 changes: 23 additions & 0 deletions bpf/lib/bpf_cred.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,27 @@ struct msg_cred_minimal {
__u32 pad;
} __attribute__((packed));

/*
* Check if "a" is a subset of "set".
* return true if all of the capabilities in "a" are also in "set"
* __cap_issubset(0100, 1111) will return true
* return false if any of the capabilities in "a" are not in "set"
* __cap_issubset(1111, 0100) will return false
*/
static inline __attribute__((always_inline)) bool
__cap_issubset(const __u64 a, const __u64 set)
{
return !(a & ~set);
}

#define __cap_gained(target, source) \
!__cap_issubset(target, source)

/* Right now we operate on global uids, we don't do user namespace translation. */
static inline __attribute__((always_inline)) bool
uid_eq(__u32 left, __u32 right)
{
return left == right;
}

#endif
5 changes: 3 additions & 2 deletions bpf/lib/process.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,9 @@ struct msg_execve_key {
}; // All fields aligned so no 'packed' attribute.

/* Execution and cred related flags shared with userspace */
#define EXEC_SETUID 0x01 /* This is a set-user-id execution */
#define EXEC_SETGID 0x02 /* This is a set-group-id execution */
#define EXEC_SETUID 0x01 /* This is a set-user-id execution */
#define EXEC_SETGID 0x02 /* This is a set-group-id execution */
#define EXEC_BIN_CAPS 0x04 /* This binary execution gained new capabilities through suid setuid or file capabilities execution */

/* This is the struct stored in bpf map to share info between
* different execve hooks.
Expand Down
45 changes: 42 additions & 3 deletions bpf/process/bpf_execve_bprm_commit_creds.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ char _license[] __attribute__((section("license"), used)) = "Dual BSD/GPL";
* current task part of the execve call.
* For such case this hook must be when we are committing the new credentials
* to the task being executed.
* For such the hook must run after:
* bprm_creds_from_file()
* |__cap_bprm_creds_from_file() capability LSM where the bprm is properly set.
*
* It reads the linux_bprm->per_clear flags that are the personality flags to clear
* when we are executing a privilged program. Normally we should check the
Expand Down Expand Up @@ -47,7 +50,7 @@ BPF_KPROBE(tg_kp_bprm_committing_creds, struct linux_binprm *bprm)
struct execve_heap *heap;
struct task_struct *task;
__u32 pid, euid, uid, egid, gid, sec = 0, zero = 0;
__u64 tid;
__u64 tid, permitted, new_permitted, new_ambient = 0;

sec = BPF_CORE_READ(bprm, per_clear);
/* If no flags to clear then this is not a privileged execution */
Expand Down Expand Up @@ -76,12 +79,48 @@ BPF_KPROBE(tg_kp_bprm_committing_creds, struct linux_binprm *bprm)
gid = BPF_CORE_READ(task, cred, gid.val);

/* Is setuid? */
if (euid != uid)
if (!uid_eq(euid, uid))
heap->info.secureexec |= EXEC_SETUID;
/* Is setgid? */
if (egid != gid)
if (!uid_eq(egid, gid))
heap->info.secureexec |= EXEC_SETGID;

/* Ensure that ambient capabilities are not set since they clash with:
* setuid/setgid on the binary.
* file capabilities on the binary.
*
* This is an extra guard. Since if the new ambient capabilities are set then
* there is no way the binary could provide extra capabilities, they cancel
* each other.
*/
BPF_CORE_READ_INTO(&new_ambient, bprm, cred, cap_ambient);
if (new_ambient)
return;

/* Did we gain new capabilities through suid setuid or file capabilities execve?
*
* To determin if we gained new capabilities we compare the current permitted
* set with the new set. This can happen if:
* (1) the setuid of binary is the _mapped_ root id in current or parent owning namespace.
* (2) the file capabilities are set on the binary.
*
* If (1) is satisfied then it is setuid root binary execution in the user
* namespace that gained new capabilities. However to identify such case
* we have to check if the uid is _mapped_ as root id in the current
* user namespace or one of its parent. Currently this operation is not trivial.
*
* Hence we make it simple by just reporting that execution of this binary allowed
* to gain new capabilities which means:
* a root setuid binary or file capabilities execution provided new capabilities.
*
* Ultimately users are only interested to see if
* this binary when executed provided new capabilities.
*/
BPF_CORE_READ_INTO(&permitted, task, cred, cap_permitted);
BPF_CORE_READ_INTO(&new_permitted, bprm, cred, cap_permitted);
if (__cap_gained(new_permitted, permitted))
heap->info.secureexec |= EXEC_BIN_CAPS;

/* Do we cache the entry? */
if (heap->info.secureexec != 0)
execve_joined_info_map_set(tid, &heap->info);
Expand Down
5 changes: 3 additions & 2 deletions pkg/api/processapi/processapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ const (
MsgUnixSize uint32 = 640

/* Execve extra flags */
ExecveSetuid = 0x01
ExecveSetgid = 0x02
ExecveSetuid = 0x01
ExecveSetgid = 0x02
ExecveBinCaps = 0x04 // This binary execution gained new capabilities through suid setuid or file capabilities execution

// flags of MsgCommon
MSG_COMMON_FLAG_RETURN = 0x1
Expand Down
7 changes: 7 additions & 0 deletions pkg/process/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ func (pi *ProcessInternal) UpdateExecOutsideCache(cred bool) (*tetragon.Process,
prop.Setgid = pi.apiBinaryProp.Setgid
update = true
}
if pi.apiBinaryProp.CapsRaised {
prop.CapsRaised = true
update = true
}
}

// Take a copy of the process, add the necessary fields to the
Expand Down Expand Up @@ -308,6 +312,9 @@ func initProcessInternalExec(
if (process.SecureExec & tetragonAPI.ExecveSetgid) != 0 {
apiBinaryProp.Setgid = &wrapperspb.UInt32Value{Value: creds.Egid}
}
if (process.SecureExec & tetragonAPI.ExecveBinCaps) != 0 {
apiBinaryProp.CapsRaised = true
}

// Per thread tracking rules PID == TID
//
Expand Down

0 comments on commit 5a34a8e

Please sign in to comment.