Skip to content

Commit

Permalink
feature(controller): create processes lifecycle functions
Browse files Browse the repository at this point in the history
- Create 3 signal events:

  1. SignalSchedProcessFork
  2. SignalSchedProcessExec
  3. SignalSchedProcessExit

- Create the eBPF programs for the signal events (based on regular ones)

- Create the controller processing functions (parsing) for the events

- Organize controller functions in their own files:
  - containers.go (for the containers lifecycle functions)
  - processes.go (for the processes lifecycle functions)
  • Loading branch information
rafaeldtinoco committed Aug 25, 2023
1 parent 61ffd96 commit a7cf5c4
Show file tree
Hide file tree
Showing 11 changed files with 661 additions and 85 deletions.
4 changes: 0 additions & 4 deletions pkg/ebpf/c/common/buffer.h
Expand Up @@ -448,10 +448,6 @@ statfunc int events_perf_submit(program_data_t *p, u32 id, long ret)
p->event->context.eventid = id;
p->event->context.retval = ret;

// KEEP THIS FOR DEBUGGING (until process tree is fully implemented)
// u32 hash = (u64) hash_u32_and_u64(p->event->context.task.pid,
// p->event->context.task.start_time); bpf_printk("hash: %u", hash);

// Get Stack trace
if (p->config->options & OPT_CAPTURE_STACK_TRACES) {
int stack_id = bpf_get_stackid(p->ctx, &stack_addresses, BPF_F_USER_STACK);
Expand Down
220 changes: 212 additions & 8 deletions pkg/ebpf/c/tracee.bpf.c
Expand Up @@ -4118,6 +4118,8 @@ int BPF_KPROBE(trace_ret_do_init_module)
return events_perf_submit(&p, DO_INIT_MODULE, ret_val);
}

// clang-format off

SEC("kprobe/load_elf_phdrs")
int BPF_KPROBE(trace_load_elf_phdrs)
{
Expand All @@ -4136,12 +4138,12 @@ int BPF_KPROBE(trace_load_elf_phdrs)
}

struct file *loaded_elf = (struct file *) PT_REGS_PARM2(ctx);
const char *elf_pathname =
(char *) get_path_str(__builtin_preserve_access_index(&loaded_elf->f_path));
const char *elf_pathname = (char *) get_path_str(__builtin_preserve_access_index(&loaded_elf->f_path));

// The interpreter field will be updated for any loading of an elf, both for the binary and for
// the interpreter. Because the interpreter is loaded only after the executed elf is loaded, the
// value of the executed binary should be overridden by the interpreter.

// The interpreter field will be updated for any loading of an elf, both for the binary
// and for the interpreter. Because the interpreter is loaded only after the executed elf is
// loaded, the value of the executed binary should be overridden by the interpreter.
size_t sz = sizeof(proc_info->interpreter.pathname);
bpf_probe_read_str(proc_info->interpreter.pathname, sz, elf_pathname);
proc_info->interpreter.id.device = get_dev_from_file(loaded_elf);
Expand All @@ -4151,15 +4153,15 @@ int BPF_KPROBE(trace_load_elf_phdrs)
if (should_submit(LOAD_ELF_PHDRS, p.event)) {
save_str_to_buf(&p.event->args_buf, (void *) elf_pathname, 0);
save_to_submit_buf(&p.event->args_buf, &proc_info->interpreter.id.device, sizeof(dev_t), 1);
save_to_submit_buf(
&p.event->args_buf, &proc_info->interpreter.id.inode, sizeof(unsigned long), 2);

save_to_submit_buf(&p.event->args_buf, &proc_info->interpreter.id.inode, sizeof(unsigned long), 2);
events_perf_submit(&p, LOAD_ELF_PHDRS, 0);
}

return 0;
}

// clang-format on

SEC("kprobe/security_file_permission")
int BPF_KPROBE(trace_security_file_permission)
{
Expand Down Expand Up @@ -5939,6 +5941,8 @@ CGROUP_SKB_HANDLE_FUNCTION(proto_tcp_http)
return 1; // NOTE: might block HTTP here if needed (return 0)
}

// clang-format on

//
// Control Plane Programs
//
Expand All @@ -5947,6 +5951,8 @@ CGROUP_SKB_HANDLE_FUNCTION(proto_tcp_http)
// these events in the main perf buffer.
//

// Containers Lifecyle

SEC("raw_tracepoint/cgroup_mkdir_signal")
int cgroup_mkdir_signal(struct bpf_raw_tracepoint_args *ctx)
{
Expand Down Expand Up @@ -6016,4 +6022,202 @@ int cgroup_rmdir_signal(struct bpf_raw_tracepoint_args *ctx)
return 0;
}

// Processes Lifecycle

// NOTE: sched_process_fork is called by kernel_clone(), which is executed during
// clone() calls as well, not only fork(). This means that sched_process_fork()
// is also able to pick the creation of LWPs through clone().

SEC("raw_tracepoint/sched_process_fork")
int sched_process_fork_signal(struct bpf_raw_tracepoint_args *ctx)
{
controlplane_signal_t *signal = init_controlplane_signal();
if (unlikely(signal == NULL))
return 0;

struct task_struct *parent = (struct task_struct *) ctx->args[0];
struct task_struct *leader = BPF_CORE_READ(parent, group_leader);

// Pick the leader (and not LWPs) as parent to construct the process tree:

u64 parent_starttime = get_task_start_time(leader);
int parent_pid = get_task_host_pid(leader);
int parent_tgid = get_task_host_tgid(leader);
int parent_ns_pid = get_task_ns_pid(leader);
int parent_ns_tgid = get_task_ns_tgid(leader);

struct task_struct *child = (struct task_struct *) ctx->args[1];

u64 child_starttime = get_task_start_time(child);
int child_pid = get_task_host_pid(child);
int child_tgid = get_task_host_tgid(child);
int child_ns_pid = get_task_ns_pid(child);
int child_ns_tgid = get_task_ns_tgid(child);

// Hashes

u32 task_hash = hash_u32_and_u64(get_task_host_tgid(child), child_starttime);
u32 parent_hash = hash_u32_and_u64(get_task_host_tgid(parent), parent_starttime);
save_to_submit_buf(&signal->args_buf, (void *) &task_hash, sizeof(u32), 0);
save_to_submit_buf(&signal->args_buf, (void *) &parent_hash, sizeof(u32), 1);

// Fork logic

// parent
save_to_submit_buf(&signal->args_buf, (void *) &parent_tgid, sizeof(int), 2);
save_to_submit_buf(&signal->args_buf, (void *) &parent_ns_tgid, sizeof(int), 3);
save_to_submit_buf(&signal->args_buf, (void *) &parent_pid, sizeof(int), 4);
save_to_submit_buf(&signal->args_buf, (void *) &parent_ns_pid, sizeof(int), 5);
save_to_submit_buf(&signal->args_buf, (void *) &parent_starttime, sizeof(u64), 6);
// child
save_to_submit_buf(&signal->args_buf, (void *) &child_tgid, sizeof(int), 7);
save_to_submit_buf(&signal->args_buf, (void *) &child_ns_tgid, sizeof(int), 8);
save_to_submit_buf(&signal->args_buf, (void *) &child_pid, sizeof(int), 9);
save_to_submit_buf(&signal->args_buf, (void *) &child_ns_pid, sizeof(int), 10);
save_to_submit_buf(&signal->args_buf, (void *) &child_starttime, sizeof(u64), 11);

signal_perf_submit(ctx, signal, SIGNAL_SCHED_PROCESS_FORK);

return 0;
}

// clang-format off

SEC("raw_tracepoint/sched_process_exec")
int sched_process_exec_signal(struct bpf_raw_tracepoint_args *ctx)
{
controlplane_signal_t *signal = init_controlplane_signal();
if (unlikely(signal == NULL))
return 0;

// Hashes

u64 id = bpf_get_current_pid_tgid();
u32 host_tid = id;
u32 host_pid = id >> 32;

struct task_struct *task = (struct task_struct *) ctx->args[0];
if (task == NULL)
return -1;
struct task_struct *parent = get_parent_task(task);
if (parent == NULL)
return -1;
struct task_struct *leader = BPF_CORE_READ(parent, group_leader);
if (leader == NULL)
return -1;

// Hash is always calculated with TID + START_TIME, for processes PID == TID
u32 task_hash = hash_u32_and_u64(get_task_host_tgid(task), get_task_start_time(task));
save_to_submit_buf(&signal->args_buf, (void *) &task_hash, sizeof(u32), 0);

// Exec logic

struct linux_binprm *bprm = (struct linux_binprm *) ctx->args[2];
if (bprm == NULL)
return -1;

// The proc_info entry is created by sched_process_fork regular probe.
proc_info_t *proc_info = bpf_map_lookup_elem(&proc_info_map, &host_pid);
if (proc_info == NULL)
return 0;

struct file *file = get_file_ptr_from_bprm(bprm);
void *file_path = get_path_str(__builtin_preserve_access_index(&file->f_path));

const char *filename = get_binprm_filename(bprm);
dev_t s_dev = get_dev_from_file(file);
unsigned long inode_nr = get_inode_nr_from_file(file);
u64 ctime = get_ctime_nanosec_from_file(file);
umode_t inode_mode = get_inode_mode_from_file(file);

save_str_to_buf(&signal->args_buf, (void *) filename, 1); // cmdpath
save_str_to_buf(&signal->args_buf, file_path, 2); // pathname
save_to_submit_buf(&signal->args_buf, &s_dev, sizeof(dev_t), 3); // dev
save_to_submit_buf(&signal->args_buf, &inode_nr, sizeof(unsigned long), 4); // inode
save_to_submit_buf(&signal->args_buf, &ctime, sizeof(u64), 5); // ctime
save_to_submit_buf(&signal->args_buf, &inode_mode, sizeof(umode_t), 6); // inode_mode

// The proc_info interpreter field is set by "load_elf_phdrs" kprobe program.
if (proc_info->interpreter.id.inode != 0 && (proc_info->interpreter.id.device != s_dev || proc_info->interpreter.id.inode != inode_nr)) {
save_str_to_buf(&signal->args_buf, &proc_info->interpreter.pathname, 7); // interpreter_pathname
save_to_submit_buf(&signal->args_buf,&proc_info->interpreter.id.device,sizeof(dev_t),8); // interpreter_dev
save_to_submit_buf(&signal->args_buf,&proc_info->interpreter.id.inode,sizeof(unsigned long),9); // interpreter_inode
save_to_submit_buf(&signal->args_buf,&proc_info->interpreter.id.ctime,sizeof(u64),10); // interpreter_ctime
}

struct mm_struct *mm = get_mm_from_task(task); // bprm->mm is null here, but task->mm is not

unsigned long arg_start, arg_end;
arg_start = get_arg_start_from_mm(mm);
arg_end = get_arg_end_from_mm(mm);
int argc = get_argc_from_bprm(bprm);

struct file *stdin_file = get_struct_file_from_fd(0);
unsigned short stdin_type = get_inode_mode_from_file(stdin_file) & S_IFMT;
void *stdin_path = get_path_str(__builtin_preserve_access_index(&stdin_file->f_path));
const char *interp = get_binprm_interp(bprm);

int invoked_from_kernel = 0;
if (get_task_parent_flags(task) & PF_KTHREAD)
invoked_from_kernel = 1;

save_args_str_arr_to_buf(&signal->args_buf, (void *) arg_start, (void *) arg_end, argc, 11); // argv
save_str_to_buf(&signal->args_buf, (void *) interp, 12); // interp
save_to_submit_buf(&signal->args_buf, &stdin_type, sizeof(unsigned short), 13); // stdin_type
save_str_to_buf(&signal->args_buf, stdin_path, 14); // stdin_path
save_to_submit_buf(&signal->args_buf, &invoked_from_kernel, sizeof(int), 15); // invoked_from_kernel

signal_perf_submit(ctx, signal, SIGNAL_SCHED_PROCESS_EXEC);

return 0;
}

// clang-format on

SEC("raw_tracepoint/sched_process_exit")
int sched_process_exit_signal(struct bpf_raw_tracepoint_args *ctx)
{
controlplane_signal_t *signal = init_controlplane_signal();
if (unlikely(signal == NULL))
return 0;

struct task_struct *task = (struct task_struct *) bpf_get_current_task();

// hashes

u64 id = bpf_get_current_pid_tgid();
u32 host_tid = id;
u32 host_pid = id >> 32;

struct task_struct *parent = get_parent_task(task);
if (parent == NULL)
return -1;
struct task_struct *leader = BPF_CORE_READ(parent, group_leader);
if (leader == NULL)
return -1;

u32 task_hash = hash_u32_and_u64(get_task_host_tgid(task), get_task_start_time(task));
save_to_submit_buf(&signal->args_buf, (void *) &task_hash, sizeof(u32), 0);

// exit logic

bool group_dead = false;
struct signal_struct *s = BPF_CORE_READ(task, signal);
atomic_t live = BPF_CORE_READ(s, live);

if (live.counter == 0)
group_dead = true;

long exit_code = get_task_exit_code(task);
u64 exit_time = bpf_ktime_get_ns();

save_to_submit_buf(&signal->args_buf, (void *) &exit_code, sizeof(long), 1);
save_to_submit_buf(&signal->args_buf, (void *) &exit_time, sizeof(u64), 2);
save_to_submit_buf(&signal->args_buf, (void *) &group_dead, sizeof(bool), 3);

signal_perf_submit(ctx, signal, SIGNAL_SCHED_PROCESS_EXIT);

return 0;
}

// END OF Control Plane Programs
5 changes: 4 additions & 1 deletion pkg/ebpf/c/types.h
Expand Up @@ -131,7 +131,10 @@ enum event_id_e
enum signal_evet_id_e
{
SIGNAL_CGROUP_MKDIR = 5000,
SIGNAL_CGROUP_RMDIR
SIGNAL_CGROUP_RMDIR,
SIGNAL_SCHED_PROCESS_FORK,
SIGNAL_SCHED_PROCESS_EXEC,
SIGNAL_SCHED_PROCESS_EXIT,
};

typedef struct args {
Expand Down
21 changes: 21 additions & 0 deletions pkg/ebpf/c/vmlinux.h
Expand Up @@ -228,6 +228,27 @@ struct bpf_raw_tracepoint_args {
__u64 args[0];
};

struct trace_entry {
short unsigned int type;
unsigned char flags;
unsigned char preempt_count;
int pid;
};

struct trace_event_raw_sys_enter {
struct trace_entry ent;
long int id;
long unsigned int args[6];
char __data[0];
};

struct trace_event_raw_sys_exit {
struct trace_entry ent;
long int id;
long int ret;
char __data[0];
};

struct list_head {
struct list_head *next;
struct list_head *prev;
Expand Down
76 changes: 76 additions & 0 deletions pkg/ebpf/controlplane/containers.go
@@ -0,0 +1,76 @@
package controlplane

import (
"github.com/aquasecurity/tracee/pkg/capabilities"
"github.com/aquasecurity/tracee/pkg/errfmt"
"github.com/aquasecurity/tracee/pkg/events/parse"
"github.com/aquasecurity/tracee/pkg/logger"
"github.com/aquasecurity/tracee/types/trace"
)

//
// Containers Lifecycle
//

// processCgroupMkdir handles the cgroup_mkdir signal.
func (p *Controller) processCgroupMkdir(args []trace.Argument) error {
cgroupId, err := parse.ArgVal[uint64](args, "cgroup_id")
if err != nil {
return errfmt.Errorf("error parsing cgroup_mkdir signal args: %v", err)
}
path, err := parse.ArgVal[string](args, "cgroup_path")
if err != nil {
return errfmt.Errorf("error parsing cgroup_mkdir signal args: %v", err)
}
hId, err := parse.ArgVal[uint32](args, "hierarchy_id")
if err != nil {
return errfmt.Errorf("error parsing cgroup_mkdir signal args: %v", err)
}
info, err := p.cgroupManager.CgroupMkdir(cgroupId, path, hId)
if err != nil {
return errfmt.WrapError(err)
}
if info.Container.ContainerId == "" && !info.Dead {
// If cgroupId is from a regular cgroup directory, and not the container base directory
// (from known runtimes), it should be removed from the containers bpf map.
err := capabilities.GetInstance().EBPF(
func() error {
return p.cgroupManager.RemoveFromBPFMap(p.bpfModule, cgroupId, hId)
},
)
if err != nil {
// If the cgroupId was not found in bpf map, this could mean that it is not a container
// cgroup and, as a systemd cgroup, could have been created and removed very quickly. In
// this case, we don't want to return an error.
logger.Debugw("Failed to remove entry from containers bpf map", "error", err)
}
return errfmt.WrapError(err)
}

if p.enrichEnabled {
// If cgroupId belongs to a container, enrich now (in a goroutine)
go func() {
_, err := p.cgroupManager.EnrichCgroupInfo(cgroupId)
if err != nil {
logger.Errorw("error triggering container enrich in control plane", "error", err)
}
}()
}

return nil
}

// processCgroupRmdir handles the cgroup_rmdir signal.
func (p *Controller) processCgroupRmdir(args []trace.Argument) error {
cgroupId, err := parse.ArgVal[uint64](args, "cgroup_id")
if err != nil {
return errfmt.Errorf("error parsing cgroup_rmdir args: %v", err)
}

hId, err := parse.ArgVal[uint32](args, "hierarchy_id")
if err != nil {
return errfmt.Errorf("error parsing cgroup_rmdir args: %v", err)
}
p.cgroupManager.CgroupRemove(cgroupId, hId)
return nil
}

0 comments on commit a7cf5c4

Please sign in to comment.