From 9be10be1109d957ad3e699c53d93e761a446654d Mon Sep 17 00:00:00 2001 From: William Findlay Date: Mon, 3 Aug 2020 21:07:33 -0400 Subject: [PATCH] Introduce LSM probes and enforcement * Replace sched:* events with equivalent LSM hooks * Remove signal stack code from raw_syscalls and move into an rt_sigreturn-specific system call return tracepoint * Monitor at LSM level rather than system call level * Implement enforcement --- Makefile | 2 +- bin/ebph | 1 + ebph/api.py | 1 + ebph/bpf/bpf_program.c | 743 ++++++++++++++++++++++++++++++++------- ebph/bpf/bpf_program.h | 5 +- ebph/bpf/lsm.h | 114 ++++++ ebph/bpf_program.py | 21 +- ebph/defs.py | 14 +- ebph/logger.py | 4 +- ebph/structs.py | 111 +++++- setup.py | 2 +- tests/driver/.gitignore | 1 + tests/driver/Makefile | 5 +- tests/driver/malicious.c | 36 ++ 14 files changed, 915 insertions(+), 145 deletions(-) create mode 100644 ebph/bpf/lsm.h create mode 100644 tests/driver/malicious.c diff --git a/Makefile b/Makefile index 6244c2b..764080a 100644 --- a/Makefile +++ b/Makefile @@ -16,4 +16,4 @@ systemd: sudo su -c "/bin/sh systemd/create_service.sh" test: dev - $(MAKE) -C tests + sudo su -c "$(MAKE) -C tests" diff --git a/bin/ebph b/bin/ebph index 6dfa1b4..996c4a1 100755 --- a/bin/ebph +++ b/bin/ebph @@ -165,6 +165,7 @@ def parse_args(sys_args: List[str]): 'normal-factor-den': EBPH_SETTINGS.NORMAL_FACTOR_DEN, 'anomaly-limit': EBPH_SETTINGS.ANOMALY_LIMIT, 'tolerize-limit': EBPH_SETTINGS.TOLERIZE_LIMIT, + 'enforcing': EBPH_SETTINGS.ENFORCING, } _set.add_argument( diff --git a/ebph/api.py b/ebph/api.py index 34ceb91..b0643b7 100644 --- a/ebph/api.py +++ b/ebph/api.py @@ -92,6 +92,7 @@ def get_status() -> Dict: 'ebpH Version': __version__, 'Monitoring': bool(API.bpf_program.get_setting(EBPH_SETTINGS.MONITORING)), 'Logging New Seq': bool(API.bpf_program.get_setting(EBPH_SETTINGS.LOG_SEQUENCES)), + 'Enforcing': bool(API.bpf_program.get_setting(EBPH_SETTINGS.ENFORCING)), 'Profiles': f'{num_profiles} ({num_training} training ({num_frozen} frozen), {num_normal} normal)', 'Processes': f'{num_processes} ({num_threads} threads)', 'Normal Wait': ns_to_delta_str(API.bpf_program.get_setting(EBPH_SETTINGS.NORMAL_WAIT)), diff --git a/ebph/bpf/bpf_program.c b/ebph/bpf/bpf_program.c index b9780db..9cc0ff9 100644 --- a/ebph/bpf/bpf_program.c +++ b/ebph/bpf/bpf_program.c @@ -263,71 +263,9 @@ static __always_inline void ebph_log_tolerize_limit(struct ebph_task_state_t *s, } /* ========================================================================= - * BPF Programs + * LSM Programs * ========================================================================= */ -TRACEPOINT_PROBE(raw_syscalls, sys_enter) -{ - bool monitoring = ebph_get_setting(EBPH_SETTING_MONITORING); - if (!monitoring) { - return 0; - } - - if (args->id < 0) { - return 0; - } - - u32 pid = bpf_get_current_pid_tgid(); - - struct ebph_task_state_t *task_state = task_states.lookup(&pid); - if (!task_state) { - return 0; - } - - if (args->id == EBPH_SYS_RT_SIGRETURN) { - if (!ebph_pop_seq(task_state)) { - // TODO: log warning - } - } - - ebph_handle_syscall(task_state, (u16)args->id); - - return 0; -} - -RAW_TRACEPOINT_PROBE(sched_process_fork) -{ - bool monitoring = ebph_get_setting(EBPH_SETTING_MONITORING); - if (!monitoring) { - return 0; - } - - struct ebph_task_state_t *parent_state; - struct ebph_task_state_t *child_state; - - struct task_struct *p = (struct task_struct *)ctx->args[0]; - struct task_struct *c = (struct task_struct *)ctx->args[1]; - - u32 ppid = p->pid; - - // Look up parent task state if it exists - parent_state = task_states.lookup(&ppid); - if (!parent_state) { - return 0; - } - - u32 cpid = c->pid; - u32 ctgid = c->tgid; - - child_state = ebph_new_task_state(cpid, ctgid, parent_state->profile_key); - if (!child_state) { - // TODO: log error - return 1; - } - - return 0; -} - static __always_inline int ebph_do_exec_common(u64 profile_key, u32 pid, u32 tgid, const char *pathname) { @@ -369,15 +307,111 @@ static __always_inline int ebph_do_exec_common(u64 profile_key, u32 pid, return 0; } -RAW_TRACEPOINT_PROBE(sched_process_exec) +static __always_inline int ebph_do_lsm_common(enum ebph_lsm_id_t lsm, + unsigned int tolerance_threshold) { + // If we are not monitoring, get out bool monitoring = ebph_get_setting(EBPH_SETTING_MONITORING); if (!monitoring) { return 0; } - /* Yoink the linux_binprm */ - struct linux_binprm *bprm = (struct linux_binprm *)ctx->args[2]; + u32 pid = bpf_get_current_pid_tgid(); + + // Look up task state + struct ebph_task_state_t *s = task_states.lookup(&pid); + if (!s) { + return 0; + } + + // Look up profile + struct ebph_profile_t *p = profiles.lookup(&s->profile_key); + if (!p) { + // TODO log error + return 0; + } + + // Look up current sequence + struct ebph_sequence_t *sequence = ebph_peek_seq(s); + if (!sequence) { + // TODO log error + return 0; + } + + lock_xadd(&p->count, 1); + lock_xadd(&s->count, 1); + + // Insert lsm id into sequence + for (int i = EBPH_SEQLEN - 1; i > 0; i--) { + sequence->calls[i] = sequence->calls[i - 1]; + } + sequence->calls[0] = lsm; + + ebph_do_train(s, p, sequence); + + // Update normal status if we are frozen and have reached normal_time + if ((p->status & EBPH_PROFILE_STATUS_FROZEN) && + !(p->status & EBPH_PROFILE_STATUS_NORMAL) && + ebph_current_time() > p->normal_time) { + ebph_start_normal(s->profile_key, s, p); + } + + ebph_do_normal(s, p, sequence); + + struct ebph_alf_t *alf = locality_frames.lookup(&s->pid); + if (!alf) { + // TODO log error + return 0; + } + + // If the process has exceeded the tolerize limit, reset its training state + int lfc = s->total_lfc; + if ((p->status & EBPH_PROFILE_STATUS_NORMAL) && + lfc > ebph_get_setting(EBPH_SETTING_TOLERIZE_LIMIT)) { + ebph_reset_training_data(s->profile_key, s, p); + ebph_log_tolerize_limit(s, alf); + } + + if (tolerance_threshold == 0) { + return 0; + } + + if (!ebph_get_setting(EBPH_SETTING_ENFORCING)) { + return 0; + } + + return lfc > tolerance_threshold ? -EPERM : 0; +} + +// TRACEPOINT_PROBE(raw_syscalls, sys_enter) +//{ +// bool monitoring = ebph_get_setting(EBPH_SETTING_MONITORING); +// if (!monitoring) { +// return 0; +// } +// +// if (args->id < 0) { +// return 0; +// } +// +// u32 pid = bpf_get_current_pid_tgid(); +// +// struct ebph_task_state_t *task_state = task_states.lookup(&pid); +// if (!task_state) { +// return 0; +// } +// +// ebph_handle_syscall(task_state, (u16)args->id); +// +// return 0; +//} + +LSM_PROBE(bprm_check_security, struct linux_binprm *bprm) +{ + bool monitoring = ebph_get_setting(EBPH_SETTING_MONITORING); + if (!monitoring) { + return 0; + } /* Calculate profile_key by taking inode number and filesystem device * number together */ @@ -392,18 +426,47 @@ RAW_TRACEPOINT_PROBE(sched_process_exec) // TODO: change this to bpf_d_path when it comes out (Linux 5.9?) const char *pathname = bprm->file->f_path.dentry->d_name.name; - return ebph_do_exec_common(profile_key, pid, tgid, pathname); + ebph_do_exec_common(profile_key, pid, tgid, pathname); + + return ebph_do_lsm_common(EBPH_BPRM_CHECK_SECURITY, EBPH_TOLERANCE_LOW); } -/* When a task exits */ -RAW_TRACEPOINT_PROBE(sched_process_exit) +LSM_PROBE(task_alloc, struct task_struct *task, unsigned long clone_flags) { bool monitoring = ebph_get_setting(EBPH_SETTING_MONITORING); if (!monitoring) { return 0; } - u32 pid = bpf_get_current_pid_tgid(); + struct ebph_task_state_t *parent_state; + struct ebph_task_state_t *child_state; + + struct task_struct *p = task; + struct task_struct *c = task->parent; + + u32 ppid = p->pid; + + // Look up parent task state if it exists + parent_state = task_states.lookup(&ppid); + if (!parent_state) { + return 0; + } + + u32 cpid = c->pid; + u32 ctgid = c->tgid; + + child_state = ebph_new_task_state(cpid, ctgid, parent_state->profile_key); + if (!child_state) { + // TODO: log error + return 0; + } + + return ebph_do_lsm_common(EBPH_TASK_ALLOC, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(task_free, struct task_struct *task) +{ + u32 pid = task->pid; task_states.delete(&pid); locality_frames.delete(&pid); @@ -416,9 +479,484 @@ RAW_TRACEPOINT_PROBE(sched_process_exit) sequences.delete(&key); } + bool monitoring = ebph_get_setting(EBPH_SETTING_MONITORING); + if (!monitoring) { + return 0; + } + + // We do NOT want to call ebph_do_lsm_common here + return 0; +} + +LSM_PROBE(task_setpgid, int unused) +{ + return ebph_do_lsm_common(EBPH_TASK_SETPGID, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(task_getpgid, int unused) +{ + return ebph_do_lsm_common(EBPH_TASK_GETPGID, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(task_getsid, int unused) +{ + return ebph_do_lsm_common(EBPH_TASK_GETSID, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(task_setnice, int unused) +{ + return ebph_do_lsm_common(EBPH_TASK_SETNICE, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(task_setioprio, int unused) +{ + return ebph_do_lsm_common(EBPH_TASK_SETIOPRIO, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(task_getioprio, int unused) +{ + return ebph_do_lsm_common(EBPH_TASK_GETIOPRIO, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(task_prlimit, int unused) +{ + return ebph_do_lsm_common(EBPH_TASK_PRLIMIT, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(task_setrlimit, int unused) +{ + return ebph_do_lsm_common(EBPH_TASK_SETRLIMIT, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(task_setscheduler, int unused) +{ + // TODO probably need to treat this as a special case + // to reduce non-determinism + return ebph_do_lsm_common(EBPH_TASK_SETSCHEDULER, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(task_getscheduler, int unused) +{ + // TODO probably need to treat this as a special case + // to reduce non-determinism + return ebph_do_lsm_common(EBPH_TASK_GETSCHEDULER, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(task_movememory, int unused) +{ + return ebph_do_lsm_common(EBPH_TASK_MOVEMEMORY, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(task_kill, int unused) +{ + return ebph_do_lsm_common(EBPH_TASK_KILL, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(task_prctl, int unused) +{ + return ebph_do_lsm_common(EBPH_TASK_PRCTL, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(sb_statfs, int unused) +{ + return ebph_do_lsm_common(EBPH_SB_STATFS, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(sb_mount, int unused) +{ + return ebph_do_lsm_common(EBPH_SB_MOUNT, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(sb_remount, int unused) +{ + return ebph_do_lsm_common(EBPH_SB_REMOUNT, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(sb_umount, int unused) +{ + return ebph_do_lsm_common(EBPH_SB_UMOUNT, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(sb_pivotroot, int unused) +{ + return ebph_do_lsm_common(EBPH_SB_PIVOTROOT, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(move_mount, int unused) +{ + return ebph_do_lsm_common(EBPH_MOVE_MOUNT, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(inode_create, int unused) +{ + return ebph_do_lsm_common(EBPH_INODE_CREATE, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(inode_link, int unused) +{ + return ebph_do_lsm_common(EBPH_INODE_LINK, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(inode_symlink, int unused) +{ + return ebph_do_lsm_common(EBPH_INODE_SYMLINK, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(inode_mkdir, int unused) +{ + return ebph_do_lsm_common(EBPH_INODE_MKDIR, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(inode_rmdir, int unused) +{ + return ebph_do_lsm_common(EBPH_INODE_RMDIR, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(inode_mknod, int unused) +{ + return ebph_do_lsm_common(EBPH_INODE_MKNOD, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(inode_rename, int unused) +{ + return ebph_do_lsm_common(EBPH_INODE_RENAME, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(inode_readlink, int unused) +{ + return ebph_do_lsm_common(EBPH_INODE_READLINK, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(inode_follow_link, int unused) +{ + return ebph_do_lsm_common(EBPH_INODE_FOLLOW_LINK, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(inode_permission, int unused) +{ + // TODO: split this into READ, WRITE, APPEND, EXEC + return ebph_do_lsm_common(EBPH_INODE_PERMISSION, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(inode_setattr, int unused) +{ + return ebph_do_lsm_common(EBPH_INODE_SETATTR, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(inode_getattr, int unused) +{ + return ebph_do_lsm_common(EBPH_INODE_GETATTR, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(inode_setxattr, int unused) +{ + return ebph_do_lsm_common(EBPH_INODE_SETXATTR, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(inode_getxattr, int unused) +{ + return ebph_do_lsm_common(EBPH_INODE_GETXATTR, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(inode_listxattr, int unused) +{ + return ebph_do_lsm_common(EBPH_INODE_LISTXATTR, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(inode_removexattr, int unused) +{ + return ebph_do_lsm_common(EBPH_INODE_REMOVEXATTR, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(file_permission, int unused) +{ + // TODO: split this into READ, WRITE, APPEND, EXEC + return ebph_do_lsm_common(EBPH_FILE_PERMISSION, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(file_ioctl, int unused) +{ + return ebph_do_lsm_common(EBPH_FILE_IOCTL, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(mmap_addr, int unused) +{ + return ebph_do_lsm_common(EBPH_MMAP_ADDR, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(mmap_file, int unused) +{ + return ebph_do_lsm_common(EBPH_MMAP_FILE, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(file_mprotect, int unused) +{ + return ebph_do_lsm_common(EBPH_FILE_MPROTECT, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(file_lock, int unused) +{ + return ebph_do_lsm_common(EBPH_FILE_LOCK, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(file_fcntl, int unused) +{ + return ebph_do_lsm_common(EBPH_FILE_FCNTL, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(file_send_sigiotask, int unused) +{ + return ebph_do_lsm_common(EBPH_FILE_SEND_SIGIOTASK, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(file_receive, int unused) +{ + return ebph_do_lsm_common(EBPH_FILE_RECEIVE, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(unix_stream_connect, int unused) +{ + // TODO consider moving this to a common socket id + return ebph_do_lsm_common(EBPH_UNIX_STREAM_CONNECT, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(unix_may_send, int unused) +{ + // TODO consider moving this to a common socket id + return ebph_do_lsm_common(EBPH_UNIX_MAY_SEND, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(socket_create, int unused) +{ + return ebph_do_lsm_common(EBPH_SOCKET_CREATE, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(socket_socketpair, int unused) +{ + return ebph_do_lsm_common(EBPH_SOCKET_SOCKETPAIR, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(socket_bind, int unused) +{ + return ebph_do_lsm_common(EBPH_SOCKET_BIND, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(socket_connect, int unused) +{ + return ebph_do_lsm_common(EBPH_SOCKET_CONNECT, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(socket_listen, int unused) +{ + return ebph_do_lsm_common(EBPH_SOCKET_LISTEN, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(socket_accept, int unused) +{ + return ebph_do_lsm_common(EBPH_SOCKET_ACCEPT, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(socket_sendmsg, int unused) +{ + return ebph_do_lsm_common(EBPH_SOCKET_SENDMSG, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(socket_recvmsg, int unused) +{ + return ebph_do_lsm_common(EBPH_SOCKET_RECVMSG, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(socket_getsockname, int unused) +{ + return ebph_do_lsm_common(EBPH_SOCKET_GETSOCKNAME, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(socket_getpeername, int unused) +{ + return ebph_do_lsm_common(EBPH_SOCKET_GETPEERNAME, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(socket_getsockopt, int unused) +{ + return ebph_do_lsm_common(EBPH_SOCKET_GETSOCKOPT, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(socket_setsockopt, int unused) +{ + return ebph_do_lsm_common(EBPH_SOCKET_SETSOCKOPT, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(socket_shutdown, int unused) +{ + return ebph_do_lsm_common(EBPH_SOCKET_SHUTDOWN, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(tun_dev_create, int unused) +{ + return ebph_do_lsm_common(EBPH_TUN_DEV_CREATE, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(tun_dev_attach, int unused) +{ + return ebph_do_lsm_common(EBPH_TUN_DEV_ATTACH, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(key_alloc, int unused) +{ + return ebph_do_lsm_common(EBPH_KEY_ALLOC, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(key_free, int unused) +{ + return ebph_do_lsm_common(EBPH_KEY_FREE, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(key_permission, int unused) +{ + return ebph_do_lsm_common(EBPH_KEY_PERMISSION, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(ipc_permission, int unused) +{ + return ebph_do_lsm_common(EBPH_IPC_PERMISSION, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(msg_queue_associate, int unused) +{ + return ebph_do_lsm_common(EBPH_MSG_QUEUE_ASSOCIATE, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(msg_queue_msgctl, int unused) +{ + return ebph_do_lsm_common(EBPH_MSG_QUEUE_MSGCTL, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(msg_queue_msgsnd, int unused) +{ + return ebph_do_lsm_common(EBPH_MSG_QUEUE_MSGSND, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(shm_associate, int unused) +{ + return ebph_do_lsm_common(EBPH_SHM_ASSOCIATE, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(shm_shmctl, int unused) +{ + return ebph_do_lsm_common(EBPH_SHM_SHMCTL, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(shm_shmat, int unused) +{ + return ebph_do_lsm_common(EBPH_SHM_SHMAT, EBPH_TOLERANCE_HIGH); +} + +/* TODO: maybe add hooks for system V semaphores... need to check if this can + * cause a deadlock with our runtime allocated maps */ + +/* TODO: maybe add binder hooks here */ + +LSM_PROBE(ptrace_access_check, int unused) +{ + return ebph_do_lsm_common(EBPH_PTRACE_ACCESS_CHECK, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(ptrace_traceme, int unused) +{ + return ebph_do_lsm_common(EBPH_PTRACE_TRACEME, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(capget, int unused) +{ + // TODO: maybe split this by capabilities + return ebph_do_lsm_common(EBPH_CAPGET, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(capset, int unused) +{ + // TODO: maybe split this by capabilities + return ebph_do_lsm_common(EBPH_CAPSET, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(capable, int unused) +{ + // TODO: maybe split this by capabilities + return ebph_do_lsm_common(EBPH_CAPABLE, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(quotactl, int unused) +{ + return ebph_do_lsm_common(EBPH_QUOTACTL, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(quota_on, int unused) +{ + return ebph_do_lsm_common(EBPH_QUOTA_ON, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(syslog, int unused) +{ + return ebph_do_lsm_common(EBPH_SYSLOG, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(settime, int unused) +{ + return ebph_do_lsm_common(EBPH_SETTIME, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(vm_enough_memory, int unused) +{ + return ebph_do_lsm_common(EBPH_VM_ENOUGH_MEMORY, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(bpf, int unused) +{ + // TODO: treat ebpH as a special case + return ebph_do_lsm_common(EBPH_BPF, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(bpf_map, int unused) +{ + // TODO: treat ebpH as a special case + return ebph_do_lsm_common(EBPH_BPF_MAP, EBPH_TOLERANCE_HIGH); +} + +LSM_PROBE(bpf_prog, int unused) +{ + // TODO: treat ebpH as a special case + return ebph_do_lsm_common(EBPH_BPF_PROG, EBPH_TOLERANCE_HIGH); +} + +/* TODO: maybe add locked_down hook */ + +LSM_PROBE(perf_event_open, int unused) +{ + // TODO: treat ebpH as a special case + return ebph_do_lsm_common(EBPH_PERF_EVENT_OPEN, EBPH_TOLERANCE_HIGH); +} + +/* ========================================================================= + * Signal Tracepoints + * ========================================================================= */ + +/* Signal bookkeeping */ +TRACEPOINT_PROBE(syscalls, sys_exit_rt_sigreturn) +{ + u32 pid = bpf_get_current_pid_tgid(); + + struct ebph_task_state_t *task_state = task_states.lookup(&pid); + if (!task_state) { + return 0; + } + + if (!ebph_pop_seq(task_state)) { + // TODO: log warning + } + return 0; } +/* Signal bookkeeping */ TRACEPOINT_PROBE(signal, signal_deliver) { bool monitoring = ebph_get_setting(EBPH_SETTING_MONITORING); @@ -752,8 +1290,8 @@ static __always_inline u64 ebph_current_time() static __always_inline u8 ebph_get_training_data(u64 profile_key, u16 curr, u16 prev) { - u32 idx = (curr * EBPH_NUM_SYSCALLS) + prev; - if (idx >= (EBPH_NUM_SYSCALLS * EBPH_NUM_SYSCALLS)) { + u32 idx = (curr * EBPH_LSM_MAX) + prev; + if (idx >= (EBPH_LSM_MAX * EBPH_LSM_MAX)) { // TODO log error return 0; } @@ -778,8 +1316,8 @@ static __always_inline u8 ebph_get_training_data(u64 profile_key, u16 curr, static __always_inline u8 ebph_get_testing_data(u64 profile_key, u16 curr, u16 prev) { - u32 idx = (curr * EBPH_NUM_SYSCALLS) + prev; - if (idx >= (EBPH_NUM_SYSCALLS * EBPH_NUM_SYSCALLS)) { + u32 idx = (curr * EBPH_LSM_MAX) + prev; + if (idx >= (EBPH_LSM_MAX * EBPH_LSM_MAX)) { // TODO log error return 0; } @@ -802,8 +1340,8 @@ static __always_inline u8 ebph_get_testing_data(u64 profile_key, u16 curr, static __always_inline int ebph_set_training_data(u64 profile_key, u16 curr, u16 prev, u8 new_flag) { - u32 idx = (curr * EBPH_NUM_SYSCALLS) + prev; - if (idx >= (EBPH_NUM_SYSCALLS * EBPH_NUM_SYSCALLS)) { + u32 idx = (curr * EBPH_LSM_MAX) + prev; + if (idx >= (EBPH_LSM_MAX * EBPH_LSM_MAX)) { // TODO log error return -1; } @@ -1146,56 +1684,3 @@ static __always_inline void ebph_do_normal(struct ebph_task_state_t *task_state, ebph_add_anomaly_count(task_state, profile, anomalies); } - -/* Process a new syscall. */ -static __always_inline void ebph_handle_syscall(struct ebph_task_state_t *s, - u16 syscall) -{ - // Look up profile - struct ebph_profile_t *p = profiles.lookup(&s->profile_key); - if (!p) { - // TODO log error - return; - } - - // Look up current sequence - struct ebph_sequence_t *sequence = ebph_peek_seq(s); - if (!sequence) { - // TODO log error - return; - } - - lock_xadd(&p->count, 1); - lock_xadd(&s->count, 1); - - // Insert syscall into sequence - for (int i = EBPH_SEQLEN - 1; i > 0; i--) { - sequence->calls[i] = sequence->calls[i - 1]; - } - sequence->calls[0] = syscall; - - ebph_do_train(s, p, sequence); - - // Update normal status if we are frozen and have reached normal_time - if ((p->status & EBPH_PROFILE_STATUS_FROZEN) && - !(p->status & EBPH_PROFILE_STATUS_NORMAL) && - ebph_current_time() > p->normal_time) { - ebph_start_normal(s->profile_key, s, p); - } - - ebph_do_normal(s, p, sequence); - - struct ebph_alf_t *alf = locality_frames.lookup(&s->pid); - if (!alf) { - // TODO log error - return; - } - - // If the process has exceeded the tolerize limit, reset its training state - int lfc = s->total_lfc; - if ((p->status & EBPH_PROFILE_STATUS_NORMAL) && - lfc > ebph_get_setting(EBPH_SETTING_TOLERIZE_LIMIT)) { - ebph_reset_training_data(s->profile_key, s, p); - ebph_log_tolerize_limit(s, alf); - } -} diff --git a/ebph/bpf/bpf_program.h b/ebph/bpf/bpf_program.h index 33f2971..e6ae971 100644 --- a/ebph/bpf/bpf_program.h +++ b/ebph/bpf/bpf_program.h @@ -28,6 +28,8 @@ #include #include +#include "lsm.h" + /* ========================================================================= * Data Structures and Types * ========================================================================= */ @@ -41,6 +43,7 @@ enum ebph_setting_key_t : int { EBPH_SETTING_NORMAL_FACTOR_DEN, EBPH_SETTING_ANOMALY_LIMIT, EBPH_SETTING_TOLERIZE_LIMIT, + EBPH_SETTING_ENFORCING, EBPH_SETTING__END, // This must be the last entry }; @@ -70,7 +73,7 @@ struct ebph_sequence_t { }; struct ebph_flags_t { - u8 flags[EBPH_NUM_SYSCALLS * EBPH_NUM_SYSCALLS]; + u8 flags[EBPH_LSM_MAX * EBPH_LSM_MAX]; }; /* Current status of the ebpH profile. diff --git a/ebph/bpf/lsm.h b/ebph/bpf/lsm.h new file mode 100644 index 0000000..d75436d --- /dev/null +++ b/ebph/bpf/lsm.h @@ -0,0 +1,114 @@ +/* ebpH (Extended BPF Process Homeostasis) A host-based IDS written in eBPF. + * ebpH Copyright (C) 2019-2020 William Findlay + * pH Copyright (C) 1999-2003 Anil Somayaji and (C) 2008 Mario Van Velzen + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Provides a unique ID for each LSM program. + * + * 2020-Aug-04 William Findlay Created this. + */ + +enum ebph_lsm_id_t : u16 { + EBPH_BPRM_CHECK_SECURITY = 0, + EBPH_TASK_ALLOC, + EBPH_TASK_FREE, + EBPH_TASK_SETPGID, + EBPH_TASK_GETPGID, + EBPH_TASK_GETSID, + EBPH_TASK_SETNICE, + EBPH_TASK_SETIOPRIO, + EBPH_TASK_GETIOPRIO, + EBPH_TASK_PRLIMIT, + EBPH_TASK_SETRLIMIT, + EBPH_TASK_SETSCHEDULER, + EBPH_TASK_GETSCHEDULER, + EBPH_TASK_MOVEMEMORY, + EBPH_TASK_KILL, // TODO: split this into coarse signal categories + EBPH_TASK_PRCTL, + EBPH_SB_STATFS, + EBPH_SB_MOUNT, + EBPH_SB_REMOUNT, + EBPH_SB_UMOUNT, + EBPH_SB_PIVOTROOT, + EBPH_MOVE_MOUNT, + EBPH_INODE_CREATE, + EBPH_INODE_LINK, + EBPH_INODE_SYMLINK, + EBPH_INODE_MKDIR, + EBPH_INODE_RMDIR, + EBPH_INODE_MKNOD, + EBPH_INODE_RENAME, + EBPH_INODE_READLINK, + EBPH_INODE_FOLLOW_LINK, + EBPH_INODE_PERMISSION, // TODO: split this into READ, WRITE, APPEND, EXEC + EBPH_INODE_SETATTR, + EBPH_INODE_GETATTR, + EBPH_INODE_SETXATTR, + EBPH_INODE_GETXATTR, + EBPH_INODE_LISTXATTR, + EBPH_INODE_REMOVEXATTR, + EBPH_FILE_PERMISSION, // TODO: split this into READ, WRITE, APPEND, EXEC + EBPH_FILE_IOCTL, + EBPH_MMAP_ADDR, + EBPH_MMAP_FILE, + EBPH_FILE_MPROTECT, + EBPH_FILE_LOCK, + EBPH_FILE_FCNTL, + EBPH_FILE_SEND_SIGIOTASK, + EBPH_FILE_RECEIVE, + EBPH_UNIX_STREAM_CONNECT, + EBPH_UNIX_MAY_SEND, + EBPH_SOCKET_CREATE, + EBPH_SOCKET_SOCKETPAIR, + EBPH_SOCKET_BIND, + EBPH_SOCKET_CONNECT, + EBPH_SOCKET_LISTEN, + EBPH_SOCKET_ACCEPT, + EBPH_SOCKET_SENDMSG, + EBPH_SOCKET_RECVMSG, + EBPH_SOCKET_GETSOCKNAME, + EBPH_SOCKET_GETPEERNAME, + EBPH_SOCKET_GETSOCKOPT, + EBPH_SOCKET_SETSOCKOPT, + EBPH_SOCKET_SHUTDOWN, + EBPH_TUN_DEV_CREATE, + EBPH_TUN_DEV_ATTACH, + EBPH_KEY_ALLOC, + EBPH_KEY_FREE, + EBPH_KEY_PERMISSION, // TODO: maybe split this into operations + EBPH_IPC_PERMISSION, + EBPH_MSG_QUEUE_ASSOCIATE, + EBPH_MSG_QUEUE_MSGCTL, + EBPH_MSG_QUEUE_MSGSND, + EBPH_MSG_QUEUE_MSGRCV, + EBPH_SHM_ASSOCIATE, + EBPH_SHM_SHMCTL, + EBPH_SHM_SHMAT, + EBPH_PTRACE_ACCESS_CHECK, + EBPH_PTRACE_TRACEME, + EBPH_CAPGET, + EBPH_CAPSET, + EBPH_CAPABLE, + EBPH_QUOTACTL, + EBPH_QUOTA_ON, + EBPH_SYSLOG, + EBPH_SETTIME, + EBPH_VM_ENOUGH_MEMORY, + EBPH_BPF, + EBPH_BPF_MAP, + EBPH_BPF_PROG, + EBPH_PERF_EVENT_OPEN, + EBPH_LSM_MAX, // This must always be the last entry +}; diff --git a/ebph/bpf_program.py b/ebph/bpf_program.py index 4141d65..842221b 100644 --- a/ebph/bpf_program.py +++ b/ebph/bpf_program.py @@ -39,6 +39,7 @@ EBPHProfileStruct, EBPH_SETTINGS, calculate_profile_magic, + EBPH_LSM, ) from ebph import defs @@ -95,7 +96,11 @@ def __init__(self, debug: bool = False, log_sequences: bool = False, auto_save = atexit.register(self._cleanup) - self.change_setting(EBPH_SETTINGS.LOG_SEQUENCES, log_sequences) + if log_sequences: + self.change_setting(EBPH_SETTINGS.LOG_SEQUENCES, log_sequences) + + if defs.ENFORCING: + self.change_setting(EBPH_SETTINGS.ENFORCING, defs.ENFORCING) self.change_setting(EBPH_SETTINGS.NORMAL_WAIT, defs.NORMAL_WAIT) self.change_setting(EBPH_SETTINGS.NORMAL_FACTOR, defs.NORMAL_FACTOR) @@ -380,6 +385,10 @@ def new_profile_events(ctx, event, size): for later use. """ pathname = event.pathname.decode('utf-8') + try: + pass + except Exception: + pass self.profile_key_to_exe[event.profile_key] = pathname if self.debug: @@ -397,14 +406,14 @@ def anomaly_events(ctx, event, size): Log anomalies. """ exe = self.profile_key_to_exe[event.profile_key] - syscall_number = event.syscall - syscall_name = self.syscall_number_to_name[syscall_number] + number = event.syscall + name = EBPH_LSM.get_name(number) misses = event.misses pid = event.pid count = event.task_count logger.audit( - f'Anomalous {syscall_name} ({misses} misses) ' + f'Anomalous {name} ({misses} misses) ' f'in PID {pid} ({exe}) after {count} calls.' ) @@ -416,8 +425,10 @@ def new_sequence_events(ctx, event, size): Log new sequences. """ exe = self.profile_key_to_exe[event.profile_key] + if not exe: + exe = event.profile_key sequence = [ - self.syscall_number_to_name[call] + EBPH_LSM.get_name(call) for call in event.sequence if call != defs.BPF_DEFINES['EBPH_EMPTY'] ] diff --git a/ebph/defs.py b/ebph/defs.py index 83444ba..78cd1f6 100644 --- a/ebph/defs.py +++ b/ebph/defs.py @@ -52,6 +52,9 @@ #NORMAL_WAIT = 1000000000 * 60 * 10 # 10 minutes #NORMAL_WAIT = 1000000000 * 30 # 30 seconds +# Start in enforcing mode +ENFORCING = False + PATH_MAX = 4096 # Compiler defines used in BPF program @@ -61,9 +64,6 @@ # Maximum number of active processes at a given time 'EBPH_MAX_PROCESSES': 10240, - # Number of system calls - 'EBPH_NUM_SYSCALLS': 450, - # Length of a sequence 'EBPH_SEQLEN': 9, # Number of frames in sequence stack @@ -72,8 +72,14 @@ # Length of ALF 'EBPH_LOCALITY_WIN': 128, - # The empty system call + # The empty lsm call 'EBPH_EMPTY': 9999, + + # TODO: tweak these to sensible values + 'EBPH_TOLERANCE_LOW': 3, + 'EBPH_TOLERANCE_MEDIUM': 10, + 'EBPH_TOLERANCE_HIGH': 30, + 'EBPH_TOLERANCE_ALWAYS': 0, } LOG_DIR = '/var/log/ebpH' diff --git a/ebph/logger.py b/ebph/logger.py index 040ec8c..4e1c025 100644 --- a/ebph/logger.py +++ b/ebph/logger.py @@ -184,7 +184,7 @@ def color_time(time: str): return Fore.GREEN + time def color_logger(logger: str): - return Fore.MAGENTA + logger + return Fore.LIGHTBLACK_EX + logger def color_category(category: str): if 'info' in category: @@ -195,6 +195,8 @@ def color_category(category: str): color = Fore.YELLOW elif 'audit' in category: color = Fore.LIGHTYELLOW_EX + elif 'newseq' in category: + color = Fore.LIGHTMAGENTA_EX elif 'error' in category: color = Fore.RED else: diff --git a/ebph/structs.py b/ebph/structs.py index e18e7a3..ff1f289 100644 --- a/ebph/structs.py +++ b/ebph/structs.py @@ -22,6 +22,7 @@ """ import os +import sys from pprint import pformat import ctypes as ct from enum import IntEnum, IntFlag, unique, auto @@ -72,7 +73,114 @@ class EBPH_SETTINGS(IntEnum): NORMAL_FACTOR_DEN = auto() ANOMALY_LIMIT = auto() TOLERIZE_LIMIT = auto() + ENFORCING = auto() +@unique +class EBPH_LSM(IntEnum): + """ + The various LSM programs that ebpH tracks. + Warning: Keep in sync with BPF program. + """ + BPRM_CHECK_SECURITY = 0 + TASK_ALLOC = auto() + TASK_FREE = auto() + TASK_SETPGID = auto() + TASK_GETPGID = auto() + TASK_GETSID = auto() + TASK_SETNICE = auto() + TASK_SETIOPRIO = auto() + TASK_GETIOPRIO = auto() + TASK_PRLIMIT = auto() + TASK_SETRLIMIT = auto() + TASK_SETSCHEDULER = auto() + TASK_GETSCHEDULER = auto() + TASK_MOVEMEMORY = auto() + TASK_KILL = auto() # TODO: split this into coarse signal categories + TASK_PRCTL = auto() + SB_STATFS = auto() + SB_MOUNT = auto() + SB_REMOUNT = auto() + SB_UMOUNT = auto() + SB_PIVOTROOT = auto() + MOVE_MOUNT = auto() + INODE_CREATE = auto() + INODE_LINK = auto() + INODE_SYMLINK = auto() + INODE_MKDIR = auto() + INODE_RMDIR = auto() + INODE_MKNOD = auto() + INODE_RENAME = auto() + INODE_READLINK = auto() + INODE_FOLLOW_LINK = auto() + INODE_PERMISSION = auto() # TODO: split this into READ, WRITE, APPEND, EXEC + INODE_SETATTR = auto() + INODE_GETATTR = auto() + INODE_SETXATTR = auto() + INODE_GETXATTR = auto() + INODE_LISTXATTR = auto() + INODE_REMOVEXATTR = auto() + FILE_PERMISSION = auto() # TODO: split this into READ, WRITE, APPEND, EXEC + FILE_IOCTL = auto() + MMAP_ADDR = auto() + MMAP_FILE = auto() + FILE_MPROTECT = auto() + FILE_LOCK = auto() + FILE_FCNTL = auto() + FILE_SEND_SIGIOTASK = auto() + FILE_RECEIVE = auto() + UNIX_STREAM_CONNECT = auto() + UNIX_MAY_SEND = auto() + SOCKET_CREATE = auto() + SOCKET_SOCKETPAIR = auto() + SOCKET_BIND = auto() + SOCKET_CONNECT = auto() + SOCKET_LISTEN = auto() + SOCKET_ACCEPT = auto() + SOCKET_SENDMSG = auto() + SOCKET_RECVMSG = auto() + SOCKET_GETSOCKNAME = auto() + SOCKET_GETPEERNAME = auto() + SOCKET_GETSOCKOPT = auto() + SOCKET_SETSOCKOPT = auto() + SOCKET_SHUTDOWN = auto() + TUN_DEV_CREATE = auto() + TUN_DEV_ATTACH = auto() + KEY_ALLOC = auto() + KEY_FREE = auto() + KEY_PERMISSION = auto() # TODO: maybe split this into operations + IPC_PERMISSION = auto() + MSG_QUEUE_ASSOCIATE = auto() + MSG_QUEUE_MSGCTL = auto() + MSG_QUEUE_MSGSND = auto() + MSG_QUEUE_MSGRCV = auto() + SHM_ASSOCIATE = auto() + SHM_SHMCTL = auto() + SHM_SHMAT = auto() + PTRACE_ACCESS_CHECK = auto() + PTRACE_TRACEME = auto() + CAPGET = auto() + CAPSET = auto() + CAPABLE = auto() + QUOTACTL = auto() + QUOTA_ON = auto() + SYSLOG = auto() + SETTIME = auto() + VM_ENOUGH_MEMORY = auto() + BPF = auto() + BPF_MAP = auto() + BPF_PROG = auto() + PERF_EVENT_OPEN = auto() + LSM_MAX = auto() # This must always be the last entry + + @staticmethod + def get_name(num: int) -> str: + try: + return EBPH_LSM(num).name.lower() + except ValueError: + return 'empty' + + +NUM_LSM = int(EBPH_LSM.LSM_MAX) class EBPHProfileDataStruct(ct.Structure): """ @@ -82,8 +190,7 @@ class EBPHProfileDataStruct(ct.Structure): _fields_ = ( ( 'flags', - ct.c_uint8 * (defs.BPF_DEFINES['EBPH_NUM_SYSCALLS'] - * defs.BPF_DEFINES['EBPH_NUM_SYSCALLS']), + ct.c_uint8 * ((NUM_LSM * NUM_LSM) & sys.maxsize), ), ) diff --git a/setup.py b/setup.py index c0294c8..94d1ddc 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ from distutils.core import setup, Extension from distutils.command.build_ext import build_ext -version = '0.3.0' +version = '0.4.0' class ct_build_ext(build_ext): def build_extension(self, ext): diff --git a/tests/driver/.gitignore b/tests/driver/.gitignore index ce01362..7b97aac 100644 --- a/tests/driver/.gitignore +++ b/tests/driver/.gitignore @@ -1 +1,2 @@ hello +malicious diff --git a/tests/driver/Makefile b/tests/driver/Makefile index 17e1b21..cae8bcd 100644 --- a/tests/driver/Makefile +++ b/tests/driver/Makefile @@ -2,7 +2,7 @@ CC = gcc LDLAGS = CFLAGS = -O0 -EXES = hello +EXES = hello malicious .PHONY: all all: $(EXES) @@ -13,3 +13,6 @@ clean: hello: hello.c $(CC) $(CFLAGS) $(LDFLAGS) -o hello hello.c + +malicious: malicious.c + $(CC) $(CFLAGS) $(LDFLAGS) -o malicious malicious.c diff --git a/tests/driver/malicious.c b/tests/driver/malicious.c new file mode 100644 index 0000000..816e8f1 --- /dev/null +++ b/tests/driver/malicious.c @@ -0,0 +1,36 @@ +/* ebpH (Extended BPF Process Homeostasis) A host-based IDS written in eBPF. + * ebpH Copyright (C) 2019-2020 William Findlay + * pH Copyright (C) 1999-2003 Anil Somayaji and (C) 2008 Mario Van Velzen + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * A simple hello world program. + * + * 2020-Jul-16 William Findlay Created this. + */ + +#include +#include +#include +#include + +int main(int argc, char **argv) { + if (argc > 1) { + char *arg_list[] = {"ls", "-lah", NULL}; + execvp(arg_list[0], arg_list); + printf("Failed with %s\n", strerror(errno)); + } + + write(1, "Hello, world!\n", 14); +}