From 59cf365a2cefb6460223cd0d810cd7beda4c4224 Mon Sep 17 00:00:00 2001 From: houyuheng1 Date: Tue, 20 Aug 2024 14:45:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90=E8=BF=9B?= =?UTF-8?q?=E7=A8=8B=E6=8B=A6=E6=88=AA-mkdir=E3=80=81rmdir=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 11 +- config/safeguard.yml | 6 + go.mod | 14 + pkg/audit/app.go | 5 +- pkg/audit/processinterception/audit.go | 171 ++++++++++ pkg/audit/processinterception/manager.go | 169 +++++++++ .../c/restricted-processinterception.bpf.c | 323 ++++++++++++++++++ pkg/bpf/c/syscalls.h | 39 +++ pkg/bpf/c/throttling.h | 28 ++ pkg/bpf/c/vmlinux_macro.h | 65 ++++ pkg/config/config.go | 38 ++- pkg/log/logger.go | 25 ++ 12 files changed, 882 insertions(+), 12 deletions(-) create mode 100644 pkg/audit/processinterception/audit.go create mode 100644 pkg/audit/processinterception/manager.go create mode 100644 pkg/bpf/c/restricted-processinterception.bpf.c create mode 100644 pkg/bpf/c/syscalls.h create mode 100644 pkg/bpf/c/throttling.h create mode 100644 pkg/bpf/c/vmlinux_macro.h diff --git a/Makefile b/Makefile index 2ee4901..b3232d1 100644 --- a/Makefile +++ b/Makefile @@ -42,12 +42,15 @@ bpf-restricted-mount: $(BPF_BUILDDIR)/restricted-mount.bpf.o .PHONY: bpf-restricted-process bpf-restricted-process: $(BPF_BUILDDIR)/restricted-process.bpf.o +.PHONY: bpf-restricted-processinterception +bpf-restricted-processinterception: $(BPF_BUILDDIR)/restricted-processinterception.bpf.o + .PHONY: vmlinux vmlinux: $(shell bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h) .PHONY: build -build: bpf-restricted-network bpf-restricted-file bpf-restricted-mount bpf-restricted-process +build: bpf-restricted-network bpf-restricted-file bpf-restricted-mount bpf-restricted-process bpf-restricted-processinterception mkdir -p build $(CGOFLAG) go build -tags netgo -ldflags '-w -s -extldflags "-static"' -o build/safeguard cmd/safeguard/safeguard.go @@ -57,17 +60,17 @@ build/docker: sudo docker build -t ghcr.io/mrtc0/bouheki:latest . .PHONY: test/unit -test/unit: bpf-restricted-network bpf-restricted-file bpf-restricted-mount bpf-restricted-process +test/unit: bpf-restricted-network bpf-restricted-file bpf-restricted-mount bpf-restricted-process bpf-restricted-processinterception which gotestsum || go install gotest.tools/gotestsum@latest $(CGOFLAG) sudo -E gotestsum -- --mod=vendor -bench=^$$ -race ./... .PHONY: test -test: bpf-restricted-network bpf-restricted-file bpf-restricted-mount bpf-restricted-process +test: bpf-restricted-network bpf-restricted-file bpf-restricted-mount bpf-restricted-process bpf-restricted-processinterception which gotestsum || go install gotest.tools/gotestsum@latest $(CGOFLAG) sudo -E gotestsum -- --tags=integration --mod=vendor -bench=^$$ -race ./... .PHONY: test/integration/specify -test/integration/specify: bpf-restricted-network bpf-restricted-file bpf-restricted-mount bpf-restricted-process +test/integration/specify: bpf-restricted-network bpf-restricted-file bpf-restricted-mount bpf-restricted-process bpf-restricted-processinterception which gotestsum || go install gotest.tools/gotestsum@latest $(CGOFLAG) sudo -E go test -tags integration -run ${NAME} ./... diff --git a/config/safeguard.yml b/config/safeguard.yml index fe75a0f..1a86074 100644 --- a/config/safeguard.yml +++ b/config/safeguard.yml @@ -32,3 +32,9 @@ log: level: info format: json +process_interception: + enable: true + mode: block + target: host + deny: + - '/home/mm' \ No newline at end of file diff --git a/go.mod b/go.mod index 959c302..6fe28aa 100644 --- a/go.mod +++ b/go.mod @@ -12,3 +12,17 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v2 v2.4.0 ) + +require ( + github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/russross/blackfriday/v2 v2.0.1 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + golang.org/x/mod v0.4.2 // indirect + golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 // indirect + golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect + golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect +) diff --git a/pkg/audit/app.go b/pkg/audit/app.go index ef8e713..d58ba95 100644 --- a/pkg/audit/app.go +++ b/pkg/audit/app.go @@ -2,6 +2,7 @@ package audit import ( "context" + "culinux/pkg/audit/processinterception" "errors" "os" "os/signal" @@ -58,13 +59,13 @@ func NewApp(version string) *cli.App { defer cancel() var wg sync.WaitGroup - wg.Add(4) + wg.Add(5) go fileaccess.RunAudit(ctx, &wg, conf) go network.RunAudit(ctx, &wg, conf) go process.RunAudit(ctx, &wg, conf) go mount.RunAudit(ctx, &wg, conf) - + go processinterception.RunAudit(ctx, &wg, conf) wg.Wait() log.Info("Terminate all audit.") return nil diff --git a/pkg/audit/processinterception/audit.go b/pkg/audit/processinterception/audit.go new file mode 100644 index 0000000..393a2d1 --- /dev/null +++ b/pkg/audit/processinterception/audit.go @@ -0,0 +1,171 @@ +package processinterception + +import ( + "C" + + "culinux/pkg/bpf" + log "culinux/pkg/log" + + "github.com/aquasecurity/libbpfgo" +) +import ( + "bytes" + "context" + "encoding/binary" + "io" + "sync" + + "culinux/pkg/audit/helpers" + "culinux/pkg/config" +) + +const ( + BPF_OBJECT_NAME = "restricted-processinterception" + BPF_PROGRAM_NAME = "restricted_processinterception_mkdir" + ALLOWED_PROCESSINTERCEPTION_MAP_NAME = "allowed_processinterception" + DENIED_PROCESSINTERCEPTION_MAP_NAME = "denied_processinterception" + + NEW_UTS_LEN = 64 + PATH_MAX = 255 + TASK_COMM_LEN = 16 +) + +type auditLog struct { + //CGroupID uint64 + PID uint32 + UID uint32 + Ret int32 + //Nodename [NEW_UTS_LEN + 1]byte + Command [TASK_COMM_LEN]byte + //ParentCommand [TASK_COMM_LEN]byte + Path [PATH_MAX]byte +} + +func setupBPFProgram() (*libbpfgo.Module, error) { + bytecode, err := bpf.EmbedFS.ReadFile("bytecode/restricted-processinterception.bpf.o") + if err != nil { + return nil, err + } + mod, err := libbpfgo.NewModuleFromBuffer(bytecode, BPF_OBJECT_NAME) + + if err != nil { + return nil, err + } + + if err = mod.BPFLoadObject(); err != nil { + return nil, err + } + + return mod, nil +} + +func RunAudit(ctx context.Context, wg *sync.WaitGroup, conf *config.Config) error { + log.Info("Launching the processinterception audit...") + defer wg.Done() + + if !conf.RestrictedProcessInterceptionConfig.Enable { + log.Info("processinterception audit is disable. shutdown...") + return nil + } + + mod, err := setupBPFProgram() + if err != nil { + log.Fatal(err) + } + defer mod.Close() + + mgr := Manager{ + mod: mod, + config: conf, + } + + err = mgr.SetConfigToMap() + if err != nil { + log.Fatal(err) + } + + err = mgr.Attach() + if err != nil { + log.Fatal(err) + } + + log.Info("Started the processinterception audit.") + eventChannel := make(chan []byte) + lostChannel := make(chan uint64) + err = mgr.Start(eventChannel, lostChannel) + if err != nil { + log.Fatal(err) + } + + go func() { + for { + eventBytes := <-eventChannel + event, err := parseEvent(eventBytes) + if err != nil { + if err == io.EOF { + return + } + log.Error(err) + continue + } + + auditLog := newAuditLog(event) + auditLog.Info() + } + }() + + <-ctx.Done() + mgr.Close() + log.Info("Terminated the processinterception audit.") + + return nil +} + +func newAuditLog(event auditLog) log.RestrictedProcessinterceptionLog { + auditEvent := log.AuditEventLog{ + Action: retToaction(event.Ret), + //Hostname: helpers.NodenameToString(event.Nodename), + PID: event.PID, + UID: event.UID, + Comm: helpers.CommToString(event.Command), + //ParentComm: helpers.CommToString(event.ParentCommand), + } + + processinterceptionAccessLog := log.RestrictedProcessinterceptionLog{ + AuditEventLog: auditEvent, + Path: pathToString(event.Path), + } + + return processinterceptionAccessLog +} + +func parseEvent(eventBytes []byte) (auditLog, error) { + buf := bytes.NewBuffer(eventBytes) + var event auditLog + err := binary.Read(buf, binary.LittleEndian, &event) + if err != nil { + return auditLog{}, err + } + + return event, nil +} + +func retToaction(ret int32) string { + if ret == 0 { + return "ALLOWED" + } else { + return "BLOCKED" + } +} + +func pathToString(path [PATH_MAX]byte) string { + var s string + for _, b := range path { + if b != 0x00 { + s += string(b) + } else { + break + } + } + return s +} diff --git a/pkg/audit/processinterception/manager.go b/pkg/audit/processinterception/manager.go new file mode 100644 index 0000000..cae1a80 --- /dev/null +++ b/pkg/audit/processinterception/manager.go @@ -0,0 +1,169 @@ +package processinterception + +import ( + "encoding/binary" + "fmt" + "unsafe" + + "culinux/pkg/config" + log "culinux/pkg/log" + + "github.com/aquasecurity/libbpfgo" +) + +const ( + PROCESSINTERCEPTION_CONFIG = "processinterception_safeguard_config_map" + MODE_MONITOR = uint32(0) + MODE_BLOCK = uint32(1) + + TARGET_HOST = uint32(0) + TARGET_CONTAINER = uint32(1) +) + +type Manager struct { + mod *libbpfgo.Module + config *config.Config + pb *libbpfgo.PerfBuffer +} + +func (m *Manager) Start(eventChannel chan []byte, lostChannel chan uint64) error { + pb, err := m.mod.InitPerfBuf("processinterception_events", eventChannel, lostChannel, 1024) + + if err != nil { + return err + } + + pb.Start() + m.pb = pb + + return nil +} + +func (m *Manager) Stop() { + m.pb.Stop() +} + +func (m *Manager) Close() { + m.pb.Close() +} + +func (m *Manager) Attach() error { + for _, prog_name := range []string{"processinterception_mkdir", "processinterception_rmdir"} { + prog, err := m.mod.GetProgram(prog_name) + if err != nil { + return err + } + + _, err = prog.AttachLSM() + if err != nil { + return err + } + + log.Debug(fmt.Sprintf("%s attached.", prog_name)) + } + return nil +} + +func (m *Manager) SetConfigToMap() error { + err := m.setModeAndTarget() + if err != nil { + return err + } + + //err = m.setAllowedFileAccessMap() + //if err != nil { + // return err + //} + + err = m.setDeniedFileAccessMap() + if err != nil { + return err + } + + return nil +} + +func (m *Manager) setAllowedFileAccessMap() error { + map_allowed_files, err := m.mod.GetMap(ALLOWED_PROCESSINTERCEPTION_MAP_NAME) + if err != nil { + return err + } + + allowed_paths := m.config.RestrictedFileAccessConfig.Allow + + for i, path := range allowed_paths { + key := uint8(i) + value := []byte(path) + err = map_allowed_files.Update(unsafe.Pointer(&key), unsafe.Pointer(&value[0])) + if err != nil { + return err + } + } + + return nil +} + +func (m *Manager) setDeniedFileAccessMap() error { + map_denied_files, err := m.mod.GetMap(DENIED_PROCESSINTERCEPTION_MAP_NAME) + if err != nil { + return err + } + + denied_paths := m.config.RestrictedProcessInterceptionConfig.Deny + + /* kernel version greater than 5.10 + for i, path := range denied_paths { + key := uint8(i) + value := []byte(path) + + keyPtr := unsafe.Pointer(&key) + valuePtr := unsafe.Pointer(&value[0]) + err = map_denied_files.Update(keyPtr, valuePtr) + if err != nil { + return err + } + } + */ + + result := "" + for _, path := range denied_paths { + result += path + result += "|" + } + key := uint8(0) + value := []byte(result) + err = map_denied_files.Update(unsafe.Pointer(&key), unsafe.Pointer(&value[0])) + if err != nil { + return err + } + + return nil +} + +func (m *Manager) setModeAndTarget() error { + key := make([]byte, 8) + configMap, err := m.mod.GetMap(PROCESSINTERCEPTION_CONFIG) + if err != nil { + return err + } + + if m.config.IsRestrictedMode("processinterception") { + binary.LittleEndian.PutUint32(key[0:4], MODE_BLOCK) + } else { + binary.LittleEndian.PutUint32(key[0:4], MODE_MONITOR) + } + + if m.config.IsOnlyContainer("processinterception") { + binary.LittleEndian.PutUint32(key[4:8], TARGET_CONTAINER) + } else { + binary.LittleEndian.PutUint32(key[4:8], TARGET_HOST) + } + + k := uint8(0) + err = configMap.Update(unsafe.Pointer(&k), unsafe.Pointer(&key[0])) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/bpf/c/restricted-processinterception.bpf.c b/pkg/bpf/c/restricted-processinterception.bpf.c new file mode 100644 index 0000000..4ef1ef2 --- /dev/null +++ b/pkg/bpf/c/restricted-processinterception.bpf.c @@ -0,0 +1,323 @@ +#include "vmlinux.h" +#include "vmlinux_macro.h" +#include "syscalls.h" +#include "common_structs.h" +#include +#include +#include + + + +#define NAME_MAX 255 +char LICENSE[] SEC("license") = "Dual BSD/GPL"; +#define EPERM 13 +#define PATH_BUFFER 0 +#define MAX_BUFFER_SIZE 32768 +#define MAX_COMBINED_LENGTH 4096 +#define MAX_BUFFERS 1 +#define LOOP_NAME 70 +enum file_hook_type { dpath = 0, dfileread, dfilewrite }; +struct processinterception_event{ +// u64 ts; //事件的时间戳 + + u32 pid; + u32 uid; + int ret; + // char nodename[NEW_UTS_LEN + 1]; + char task[TASK_COMM_LEN]; + // char parent_task[TASK_COMM_LEN]; + unsigned char path[NAME_MAX]; + // unsigned char from_source_path[NAME_MAX]; + + //bufs_k data; +}; + +struct processinterception_safeguard_config { + u32 mode; + u32 target; +}; + +//用于与用户态来回传的events +struct { + __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); + __uint(key_size, sizeof(u32)); + __uint(value_size, sizeof(u32)); +} processinterception_events SEC(".maps"); + +typedef struct buffers { + char buf[MAX_BUFFER_SIZE]; +} bufs_t; + +struct file_path { + unsigned char path[NAME_MAX]; +}; +typedef struct bufkey { + unsigned char path[NAME_MAX]; + // char source[MAX_STRING_SIZE]; +} bufs_k; +struct { + __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); + __type(key, u32); + __type(value, bufs_t); + __uint(max_entries, MAX_BUFFERS); +} bufs SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); + __type(key, u32); + __type(value, bufs_k); + __uint(max_entries, 3); +} bufk SEC(".maps"); +//struct source_path { +// unsigned char from_source_path[NAME_MAX]; +//}; +struct { + __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); + __type(key, u32); + __type(value, u32); + __uint(max_entries, MAX_BUFFERS); +} bufs_off SEC(".maps"); + +BPF_HASH(processinterception_safeguard_config_map, u32, struct processinterception_safeguard_config, 256); +//BPF_HASH(allowed_access_path, u32, struct file_path, 256); +BPF_HASH(denied_processinterception, u32, struct file_path, 256); +//BPF_HASH(weakpasswd_from_source_list, u32, struct source_path, 256); + + +static __always_inline u32 *get_buf_off(int buf_idx) { + return bpf_map_lookup_elem(&bufs_off, &buf_idx); +} +static __always_inline void set_buf_off(int buf_idx, u32 new_off) { + bpf_map_update_elem(&bufs_off, &buf_idx, &new_off, BPF_ANY); +} +static __always_inline bufs_t *get_buf(int idx) { + return bpf_map_lookup_elem(&bufs, &idx); +} +static inline struct mount *real_mount(struct vfsmount *mnt) { + return container_of(mnt, struct mount, mnt); +} + +static __always_inline bool prepend_path(struct path *path, bufs_t *string_p) { + char slash = '/'; + char null = '\0'; + int offset = MAX_COMBINED_LENGTH; + + if (path == NULL || string_p == NULL) { + return false; + } + + struct dentry *dentry = path->dentry; + struct vfsmount *vfsmnt = path->mnt; + + struct mount *mnt = real_mount(vfsmnt); + + struct dentry *parent; + struct dentry *mnt_root; + struct mount *m; + struct qstr d_name; + +#pragma unroll + for (int i = 0; i < 30; i++) { + parent = BPF_CORE_READ(dentry, d_parent); + mnt_root = BPF_CORE_READ(vfsmnt, mnt_root); + + if (dentry == mnt_root) { + m = BPF_CORE_READ(mnt, mnt_parent); + if (mnt != m) { + dentry = BPF_CORE_READ(mnt, mnt_mountpoint); + mnt = BPF_CORE_READ(mnt, mnt_parent); + vfsmnt = &mnt->mnt; + continue; + } + break; + } + + if (dentry == parent) { + break; + } + + // get d_name + d_name = BPF_CORE_READ(dentry, d_name); + + + offset -= (d_name.len + 1); + if (offset < 0) + break; + + int sz = bpf_probe_read_str( + &(string_p->buf[(offset) & (MAX_COMBINED_LENGTH - 1)]), + (d_name.len + 1) & (MAX_COMBINED_LENGTH - 1), d_name.name); + if (sz > 1) { + bpf_probe_read( + &(string_p->buf[(offset + d_name.len) & (MAX_COMBINED_LENGTH - 1)]), + 1, &slash); + } else { + offset += (d_name.len + 1); + } + + dentry = parent; + } + + if (offset == MAX_COMBINED_LENGTH) { + return false; + } + + bpf_probe_read(&(string_p->buf[MAX_COMBINED_LENGTH - 1]), 1, &null); + offset--; + + bpf_probe_read(&(string_p->buf[offset & (MAX_COMBINED_LENGTH - 1)]), 1, + &slash); + set_buf_off(PATH_BUFFER, offset); + return true; +} + +static inline int check_path_hooks(struct path *f_path,struct processinterception_event *event){ + //struct task_struct *t = (struct task_struct *)bpf_get_current_task(); + + // bpf_printk("Access Denied: %s\n", id); + // event *task_info; + int ret = 0; + // u_char *file_path = NULL; + struct file_path *paths; + unsigned int key = 0; + paths = (struct file_path *)bpf_map_lookup_elem(&denied_processinterception, &key); + if (paths == NULL) { + return 0; + } + + bpf_printk("deny paths: %s\n", paths->path); + + u32 zero = 0; + //字符串指针指向map并初始化 + bufs_k *z = bpf_map_lookup_elem(&bufk, &zero); + if (z == NULL) + return 0; + + u32 one = 1; + bufs_k *store = bpf_map_lookup_elem(&bufk, &one); + if (store == NULL) + return 0; + + // Reset value for store + bpf_map_update_elem(&bufk, &one, z, BPF_ANY); + + u32 two = 2; + bufs_k *pk = bpf_map_lookup_elem(&bufk, &two); + if (pk == NULL) + return 0; + + + + bufs_t *path_buf = get_buf(PATH_BUFFER); + if (path_buf == NULL) + return 0; +///*通过实际挂载点计算真实路径*/ + if (!prepend_path(f_path, path_buf)) + return 0; + + u32 *path_offset = get_buf_off(PATH_BUFFER); + if (path_offset == NULL) + return 0; + + //获取到的偏移量存在path_ptr + void *path_ptr = &path_buf->buf[*path_offset]; + //读取路径转换后的字符串并放在store->path里 store是path + bpf_probe_read_str(store->path, NAME_MAX, path_ptr); + + bpf_probe_read_str(event->path, sizeof(event->path), store->path); + + unsigned int i = 0; + unsigned int j = 0; + bool find = true; + unsigned int equali = 0; + #pragma unroll + for (i = 0; i < LOOP_NAME; i++) { + if (paths->path[i] == '\0') { + break; + } + if (paths->path[i]==store->path[j]) { + j = j + 1; + } else { + j = 0; + find = false; + } + + if (paths->path[i] == '|') { + find = true; + } + equali = equali + 1; + if (paths->path[equali + 1] == '|' && find == true) { + ret = -EPERM; + break; + } + + } + + return ret; + // char tmp = path_buf->buf; +//比较内核中获取的地址是否与传过来的地址一致,一致则禁止执行,不一致放行 + + // size_t size = strlen(path_buf->buf, NAME_MAX); + // if (strcmp(paths->path, path_buf->buf, size) == 0) { + // ret = -EPERM; + // } else { + // ret = 0; + // } + // return ret; + +} + +static inline void get_event_info(struct processinterception_event *event){ + // struct task_struct *current_task; + // struct uts_namespace *uts_ns; + // struct mnt_namespace *mnt_ns; + // struct nsproxy *nsproxy; + + // current_task = (struct task_struct *)bpf_get_current_task(); + // BPF_CORE_READ_INTO(&nsproxy, current_task, nsproxy); + // BPF_CORE_READ_INTO(&uts_ns, nsproxy, uts_ns); + // BPF_CORE_READ_INTO(&event->nodename, uts_ns, name.nodename); + + // BPF_CORE_READ_INTO(&mnt_ns, nsproxy, mnt_ns); + // BPF_CORE_READ_INTO(&inum, mnt_ns, ns.inum); + //event->cgroup = bpf_get_current_cgroup_id(); + + event->pid = (u32)(bpf_get_current_pid_tgid() >> 32); + + bpf_get_current_comm(&event->task, sizeof(event->task)); + + // struct task_struct *parent_task = BPF_CORE_READ(current_task, real_parent); + // bpf_probe_read_kernel_str(&event->parent_task, sizeof(event->parent_task), &parent_task->comm); + + u64 uid_gid = bpf_get_current_uid_gid(); + event->uid = uid_gid & 0xFFFFFFFF; +} + + +//挂载在创建目录的函数上 +SEC("lsm/path_mkdir") +int BPF_PROG(processinterception_mkdir, struct path *dir, struct dentry *dentry) { + struct path f_path; + f_path.dentry = dentry; + f_path.mnt = BPF_CORE_READ(dir, mnt); + + struct processinterception_event event = {}; + event.ret = check_path_hooks(&f_path, &event); + get_event_info(&event); + bpf_perf_event_output((void *)ctx, &processinterception_events, BPF_F_CURRENT_CPU, &event, sizeof(event)); + return event.ret; +} + +//挂载在删除目录的函数上 +SEC("lsm/path_rmdir") +int BPF_PROG(processinterception_rmdir, struct path *dir, struct dentry *dentry) { + struct path f_path; + f_path.dentry = dentry; + f_path.mnt = BPF_CORE_READ(dir, mnt); + + struct processinterception_event event = {}; + event.ret = check_path_hooks(&f_path, &event); + get_event_info(&event); + bpf_perf_event_output((void *)ctx, &processinterception_events, BPF_F_CURRENT_CPU, &event, sizeof(event)); + return event.ret; +} \ No newline at end of file diff --git a/pkg/bpf/c/syscalls.h b/pkg/bpf/c/syscalls.h new file mode 100644 index 0000000..0e29d27 --- /dev/null +++ b/pkg/bpf/c/syscalls.h @@ -0,0 +1,39 @@ +// +build ignore +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright 2023 Authors of KubeArmor */ + +#ifndef __SYSCALLS_H +#define __SYSCALLS_H +enum +{ + // file + _FILE_OPEN = 450, + _FILE_PERMISSION = 451, + _FILE_MKNOD = 452, + _FILE_UNLINK = 453, + _FILE_MKDIR = 454, + _FILE_RMDIR= 455, + _FILE_SYMLINK = 456, + _FILE_LINK = 457, + _FILE_RENAME = 8458, + _FILE_CHMOD = 459, + _FILE_TRUNCATE = 460, + + + // network + _SOCKET_CREATE = 461, + _SOCKET_CONNECT = 462, + _SOCKET_ACCEPT = 463, + + //process + _SECURITY_BPRM_CHECK = 352, + + // capabilities + _CAPABLE = 464, + + // dropping alert + _DROPPING_ALERT = 0, + + +}; +#endif /* __SYSCALLS_H */ \ No newline at end of file diff --git a/pkg/bpf/c/throttling.h b/pkg/bpf/c/throttling.h new file mode 100644 index 0000000..5af306c --- /dev/null +++ b/pkg/bpf/c/throttling.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright 2024 Authors of KubeArmor */ + +#ifndef __THROTTLING_H +#define __THROTTLING_H + +struct alert_throttle_state { + u64 first_event_timestamp; + u64 event_count; + u64 throttle; +}; + +struct outer_key { + u32 pid_ns; + u32 mnt_ns; +}; + +struct alert { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 256); + __uint(key_size, sizeof(struct outer_key)); + __uint(value_size, sizeof(struct alert_throttle_state)); + __uint(pinning, LIBBPF_PIN_BY_NAME); +}; + +struct alert kubearmor_alert_throttle SEC(".maps"); + +#endif /* __THROTTLING_H */ \ No newline at end of file diff --git a/pkg/bpf/c/vmlinux_macro.h b/pkg/bpf/c/vmlinux_macro.h new file mode 100644 index 0000000..19c5915 --- /dev/null +++ b/pkg/bpf/c/vmlinux_macro.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright 2022 Authors of KubeArmor */ + +#ifndef __VMLINUX_MACRO_H__ +#define __VMLINUX_MACRO_H__ + +/* -------------------------------------------------------------- + * ## Missing macro/enum in vmlinux BTF (/sys/kernel/btf/vmlinux) + * -------------------------------------------------------------- + * ### DWARF + * --------- + * 1) Macros in C will not part of DWARF section in an ELF file. + * 2) When it comes to struct & enum of a C program, the decision to + * include them in the DWARF section of ELF file depends on whether + * the compiler thinks they will be useful during debugging. + * For example, due to compiler optimization or because of to the way + * the code is written, certain lines of code, struct and enums will + * become unreachable when the ELF file is executed and debugged. The + * compiler deems them as unnecessary debug_info and (by default) do + * not add such struct and enum in the DWARF section of the ELF file + * (unless `-fno-eliminate-unused-debug-types` flag is used). + * + * ### vmlinux BTF + * --------------- + * 1) `/sys/kernel/btf/vmlinux` file is usually generated from the DWARF + * section of the kernel image (vmlinux). So whatever apply to DWARF + * in general applies to vmlinux BTF as well. + * 2) Macros in the kernel source will not part of vmlinux BTF [Ref 1] + * 3) Kernel struct & enum may or may not be present in the vmlinux BTF + * unless the `BTF_TYPE_EMIT*` macros are explicity used on those + * struct and enum in the kernel source [Ref 2 and 3]. + * + * ### Reference + * ------------- + * 1) https://lore.kernel.org/all/CAO658oV9AAcMMbVhjkoq5PtpvbVf41Cd_TBLCORTcf3trtwHfw@mail.gmail.com/T/ + * 2) https://lore.kernel.org/bpf/20210317174132.589276-1-yhs@fb.com/ + * 3) https://elixir.bootlin.com/linux/v5.19.4/source/include/linux/btf.h#L12 + * + * -------------------------------------------------------------- */ + +#ifndef KERNEL_VERSION +#define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + ((c) > 255 ? 255 : (c))) +#endif + +#define LINUX_VERSION_CODE KERNEL_VERSION(LINUX_VERSION_MAJOR, \ + LINUX_VERSION_PATCHLEVEL, \ + LINUX_VERSION_SUBLEVEL) + +/* + * In some kernels (example - Debian 11 w/ kernel 5.10.0-17-amd64), + * the BTF information for the below enum value is not present + * in /sys/kernel/btf/vmlinux. + */ +#define PROC_PID_INIT_INO 0xEFFFFFFCU + +/* + * The following values are define as macro in the kernel until v5.6.19. + * From 5.7, they are defined as enum values. + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0) +#define BPF_ANY 0 +#define BPF_F_CURRENT_CPU 0xffffffffULL +#endif + +#endif diff --git a/pkg/config/config.go b/pkg/config/config.go index 68cc157..3095723 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -7,6 +7,13 @@ import ( "gopkg.in/yaml.v2" ) +type RestrictedProcessInterceptionConfig struct { + Enable bool + Mode string `yaml:"mode"` + Target string `yaml:"target"` + + Deny []string `yaml:"deny"` +} type RestrictedNetworkConfig struct { Enable bool Mode string `yaml:"mode"` @@ -83,12 +90,13 @@ type LogConfig struct { } type Config struct { - RestrictedNetworkConfig `yaml:"network"` - RestrictedFileAccessConfig `yaml:"files"` - RestrictedMountConfig `yaml:"mount"` - RestrictedProcessConfig `yaml:"process"` - DNSProxyConfig `yaml:"dns_proxy"` - Log LogConfig + RestrictedNetworkConfig `yaml:"network"` + RestrictedFileAccessConfig `yaml:"files"` + RestrictedMountConfig `yaml:"mount"` + RestrictedProcessConfig `yaml:"process"` + DNSProxyConfig `yaml:"dns_proxy"` + RestrictedProcessInterceptionConfig `yaml:"process_interception"` + Log LogConfig } func DefaultConfig() *Config { @@ -128,6 +136,12 @@ func DefaultConfig() *Config { Upstreams: []string{}, BindAddresses: []string{"127.0.0.1", "172.17.0.1"}, }, + RestrictedProcessInterceptionConfig: RestrictedProcessInterceptionConfig{ + Enable: true, + Mode: "check", + Target: "host", + Deny: []string{"/"}, + }, Log: LogConfig{ Level: "INFO", Format: "json", @@ -197,6 +211,12 @@ func (c *Config) IsRestrictedMode(target string) bool { } else { return false } + case "processinterception": + if c.RestrictedProcessInterceptionConfig.Mode == "block" { + return true + } else { + return false + } default: return false } @@ -228,6 +248,12 @@ func (c *Config) IsOnlyContainer(target string) bool { } else { return false } + case "process_interception": + if c.RestrictedProcessInterceptionConfig.Target == "container" { + return true + } else { + return false + } default: return false } diff --git a/pkg/log/logger.go b/pkg/log/logger.go index 8df37af..5c2f3b1 100644 --- a/pkg/log/logger.go +++ b/pkg/log/logger.go @@ -143,6 +143,10 @@ type RestrictedProcessLog struct { AuditEventLog PPID uint32 } +type RestrictedProcessinterceptionLog struct { + AuditEventLog + Path string +} func (l *RestrictedNetworkLog) Info() { Logger.WithFields(logrus.Fields{ @@ -158,6 +162,27 @@ func (l *RestrictedNetworkLog) Info() { }).Info("Traffic is trapped in the filter.") } +func (l *RestrictedProcessinterceptionLog) Info() { + Logger.WithFields(logrus.Fields{ + "Action": l.Action, + // "Hostname": l.Hostname, + "PID": l.PID, + "UID": l.UID, + "UName": func(UID uint32) string { + u, err := user.LookupId(strconv.FormatUint(uint64(UID), 10)) + if err != nil { + return "Nan" + } else { + return u.Username + } + }(l.UID), + "Comm": l.Comm, + // "ParentComm": l.ParentComm, + "Path": l.Path, + // "FormSourcePath": l.FromSourcePath, + }).Info("Processinterception event is trapped in the filter.") +} + func (l *RestrictedFileAccessLog) Info() { Logger.WithFields(logrus.Fields{ "Action": l.Action, -- Gitee