eBPF-vArmor ①

admin 2023年12月25日22:50:15评论12 views字数 8965阅读29分53秒阅读模式

观察模式

跟踪和记录进程创建与执行

代码取自 vArmor-ebpf

tracepoint__sched__sched_process_fork 函数,通过 eBPF 机制监听 sched_process_fork 事件,在事件触发时,提取相关进程的信息和环境变量数据,并将这些数据输出到 events map 中以供进一步处理或分析。sched_process_fork 事件在 Linux 内核中是当新的进程被创建时触发的。当一个进程调用 fork() 系统调用创建了一个子进程时,就会触发这个事件。

tracepoint__sched__sched_process_exec 函数,通过 eBPF 机制监听 sched_process_exec 事件,在事件触发时,收集相关进程执行的信息,包括父进程、子进程的 PID、TGID、进程名,以及执行的文件名和特定的环境变量,并输出到 events map 中以供进一步处理或分析。sched_process_exec 事件在进程执行新程序时被触发。

// SPDX-License-Identifier: GPL-2.0// Copyright 2023 vArmor-ebpf Authors
#include "vmlinux.h"#include "bpf_helpers.h"#include "bpf_core_read.h"
// 设置文件名和环境变量的最大长度,环境变量提取的最大循环次数#define MAX_FILENAME_LEN 64#define MAX_ENV_LEN 256#define MAX_ENV_EXTRACT_LOOP_COUNT 400#define TASK_COMM_LEN 16
char __license[] SEC("license") = "GPL";
// 声明一个 mapstruct { __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);} events SEC(".maps");
struct event { u32 type; // 事件类型 u32 parent_pid; // 父进程的 PID u32 parent_tgid; // TGID u32 child_pid; // 子进程的 PID u32 child_tgid; // TGID unsigned char parent_task[TASK_COMM_LEN]; // 父进程任务名称 unsigned char child_task[TASK_COMM_LEN]; // 子进程任务名称 unsigned char filename[MAX_FILENAME_LEN]; // 文件名 unsigned char env[MAX_ENV_LEN]; // 环境信息 u32 num;};// 为了防止编译器给出未使用变量的警告const struct event *unused __attribute__((unused));
// https://elixir.bootlin.com/linux/v5.4.196/source/kernel/fork.c#L2388// https://elixir.bootlin.com/linux/v5.4.196/source/include/trace/events/sched.h#L287// 监听 sched_process_fork 事件,当事件发生时调用 tracepoint__sched__sched_process_forkSEC("raw_tracepoint/sched_process_fork")int tracepoint__sched__sched_process_fork(struct bpf_raw_tracepoint_args *ctx){ // TP_PROTO(struct task_struct *parent, struct task_struct *child) // 将 ctx 结构体中的参数解析为父进程和子进程的 task_struct 对象。 struct task_struct *parent = (struct task_struct *)ctx->args[0]; struct task_struct *child = (struct task_struct *)ctx->args[1];
struct event event = {}; // 设置事件的类型为1 event.type = 1; // 从父任务结构体中读取父进程的 PID BPF_CORE_READ_INTO(&event.parent_pid, parent, pid); // 从父任务结构体中读取父进程的 TGID BPF_CORE_READ_INTO(&event.parent_tgid, parent, tgid); // 从父任务结构体中读取父进程的名称 BPF_CORE_READ_STR_INTO(&event.parent_task, parent, comm); // 从当前任务结构体中读取当前进程的 PID BPF_CORE_READ_INTO(&event.child_pid, child, pid); // 从当前任务结构体中读取当前进程的 TGID BPF_CORE_READ_INTO(&event.child_tgid, child, tgid); // 从当前任务结构体中读取当前进程的名称 BPF_CORE_READ_STR_INTO(&event.child_task, child, comm);
u64 env_start = 0; u64 env_end = 0; int i = 0; int len = 0; // 从当前任务结构体的 mm 结构体中读取环境变量的起始地址 BPF_CORE_READ_INTO(&env_start, parent, mm, env_start); // 从当前任务结构体的 mm 结构体中读取环境变量的结束地址 BPF_CORE_READ_INTO(&env_end, parent, mm, env_end);
// 循环,尝试从父进程的环境变量中提取特定格式的信息 while(i < MAX_ENV_EXTRACT_LOOP_COUNT && env_start < env_end ) { // bpf_probe_read_kernel_str 用于从内核空间读取字符串,并将其存储在 event.env 中 len = bpf_probe_read_kernel_str(&event.env, sizeof(event.env), (void *)env_start); if ( len <= 0 ) { break; } else if ( event.env[0] == 'V' && // vArmor把自己排除了 event.env[1] == 'A' && event.env[2] == 'R' && event.env[3] == 'M' && event.env[4] == 'O' && event.env[5] == 'R' && event.env[6] == '=' ) { break; } else { env_start = env_start + len; event.env[0] = 0; i++; } } event.num = i; // 将 event 事件数据输出到 events map 中 bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event));
return 0;}
// https://elixir.bootlin.com/linux/v5.4.196/source/fs/exec.c#L1722SEC("raw_tracepoint/sched_process_exec")int tracepoint__sched__sched_process_exec(struct bpf_raw_tracepoint_args *ctx){ // TP_PROTO(struct task_struct *p, pid_t old_pid, struct linux_binprm *bprm) // 获取当前任务结构体的指针,获取 linux_binprm 结构体的指针 struct task_struct *current = (struct task_struct *)ctx->args[0]; struct linux_binprm *bprm = (struct linux_binprm *)ctx->args[2]; // 获取当前任务的父任务结构体的指针 struct task_struct *parent = BPF_CORE_READ(current, parent);
struct event event = {};
event.type = 2; // 设置 event 的类型为 2(表示进程执行事件) // 从父任务结构体中读取父进程的 PID BPF_CORE_READ_INTO(&event.parent_pid, parent, pid); // 从父任务结构体中读取父进程的 TGID BPF_CORE_READ_INTO(&event.parent_tgid, parent, tgid); // 从父任务结构体中读取父进程的名称 BPF_CORE_READ_STR_INTO(&event.parent_task, parent, comm); // 从当前任务结构体中读取当前进程的 PID BPF_CORE_READ_INTO(&event.child_pid, child, pid); // 从当前任务结构体中读取当前进程的 TGID BPF_CORE_READ_INTO(&event.child_tgid, child, tgid); // 从当前任务结构体中读取当前进程的名称 BPF_CORE_READ_STR_INTO(&event.child_task, child, comm); // 从 linux_binprm 结构体中读取执行文件的名称 bpf_probe_read_kernel_str(&event.filename, sizeof(event.filename), BPF_CORE_READ(bprm, filename));

u64 env_start = 0; u64 env_end = 0; int i = 0; int len = 0;
// 从当前任务结构体的 mm 结构体中读取环境变量的起始地址 BPF_CORE_READ_INTO(&env_start, current, mm, env_start); // 从当前任务结构体的 mm 结构体中读取环境变量的结束地址 BPF_CORE_READ_INTO(&env_end, current, mm, env_end); while(i < MAX_ENV_EXTRACT_LOOP_COUNT && env_start < env_end ) { // 从用户空间读取环境变量字符串 len = bpf_probe_read_user_str(&event.env, sizeof(event.env), (void *)env_start);
if ( len <= 0 ) { break; } else if ( event.env[0] == 'V' && event.env[1] == 'A' && event.env[2] == 'R' && event.env[3] == 'M' && event.env[4] == 'O' && event.env[5] == 'R' && event.env[6] == '=' ) { break; } else { env_start = env_start + len; event.env[0] = 0; i++; } }
event.num = i; bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event)); return 0;}


阻断模式

阻止向黑名单地址/网段进行访问

vArmor 具备拦截禁止访问云服务器的 metadata service 的能力,eBPF-vArmor ①

添加规则eBPF-vArmor ①

vArmor-eBPF 中的拦截实现

SEC("lsm/socket_connect")// 传参 正在进行连接的套接字、正在连接的远程地址、地址结构的长度int BPF_PROG(varmor_socket_connect, struct socket *sock, struct sockaddr *address, int addrlen) {  // 查地址类型是否是IPv4或IPv6,都不是就不关心  if (address->sa_family != AF_INET && address->sa_family != AF_INET6)    return 0;
// 检索当前任务 struct task_struct *current = (struct task_struct *)bpf_get_current_task();
// 获取当前任务的挂载命名空间ID,并获取此命名空间的规则 u32 mnt_ns = get_task_mnt_ns_id(current); u32 *vnet_inner = get_net_inner_map(mnt_ns); if (vnet_inner == NULL) return 0;
DEBUG_PRINT("================ lsm/socket_connect ================");
DEBUG_PRINT("socket status: 0x%x", sock->state); DEBUG_PRINT("socket type: 0x%x", sock->type); DEBUG_PRINT("socket flags: 0x%x", sock->flags);
// 进行网络规则匹配检查 return iterate_net_inner_map(vnet_inner, address);}


关键函数 iterate_net_inner_map 

static __noinline int iterate_net_inner_map(u32 *vnet_inner, struct sockaddr *address) {  u32 inner_id, ip, i;  bool match;
for(inner_id=0; inner_id<NET_INNER_MAP_ENTRIES_MAX; inner_id++) { struct net_rule *rule = get_net_rule(vnet_inner, inner_id); if (rule == NULL) { // 规则为空情况下 DEBUG_PRINT(""); DEBUG_PRINT("access allowed"); return 0; }
DEBUG_PRINT("---- rule id: %d ----", inner_id); match = true; if (address->sa_family == AF_INET) { // IPv4的情况 struct sockaddr_in *addr4 = (struct sockaddr_in *) address; //传入的地址转换为 struct sockaddr_in 类型 DEBUG_PRINT("IPv4 address: %x", addr4->sin_addr.s_addr); // 输出地址 DEBUG_PRINT("IPv4 port: %x", addr4->sin_port); // 输出端口
if (rule->flags & CIDR_MATCH) { // 如果规则设置了 CIDR_MATCH 标志,将会进行 CIDR 匹配 for (i = 0; i < 4; i++) { ip = (addr4->sin_addr.s_addr >> (8 * i)) & 0xff; if ((ip & rule->mask[i]) != rule->address[i]) { //与规则中的掩码不匹配 match = false; break; } } } else if (rule->flags & PRECISE_MATCH) { // 如果规则设置了 PRECISE_MATCH 标志,则进行精确匹配 for (i = 0; i < 4; i++) { ip = (addr4->sin_addr.s_addr >> (8 * i)) & 0xff; if (ip != rule->address[i]) { // 直接比较是否完全相同 match = false; break; } } }
if (match && (rule->flags & PORT_MATCH) && (rule->port != bpf_ntohs(addr4->sin_port))) { match = false; } // 匹配成功,则拒绝访问,返回权限错误 if (match) { DEBUG_PRINT(""); DEBUG_PRINT("access denied"); return -EPERM; } } else { // IPv6 struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) address; struct in6_addr ip6addr = BPF_CORE_READ(addr6, sin6_addr);
DEBUG_PRINT("IPv6 address: %d:%d", ip6addr.in6_u.u6_addr8[0], ip6addr.in6_u.u6_addr8[1]); DEBUG_PRINT("IPv6 address: %d:%d", ip6addr.in6_u.u6_addr8[2], ip6addr.in6_u.u6_addr8[3]); DEBUG_PRINT("IPv6 address: %d:%d", ip6addr.in6_u.u6_addr8[4], ip6addr.in6_u.u6_addr8[5]); DEBUG_PRINT("IPv6 address: %d:%d", ip6addr.in6_u.u6_addr8[6], ip6addr.in6_u.u6_addr8[7]); DEBUG_PRINT("IPv6 port: %d", bpf_ntohs(addr6->sin6_port));
if (rule->flags & CIDR_MATCH) { for (i = 0; i < 16; i++) { ip = ip6addr.in6_u.u6_addr8[i]; if ((ip & rule->mask[i]) != rule->address[i]) { match = false; break; } } } else if (rule->flags & PRECISE_MATCH) { for (i = 0; i < 16; i++) { ip = ip6addr.in6_u.u6_addr8[i]; if (ip != rule->address[i]) { match = false; break; } } }
if (match && (rule->flags & PORT_MATCH) && (rule->port != bpf_ntohs(addr6->sin6_port))) { match = false; } if (match) { DEBUG_PRINT(""); DEBUG_PRINT("access denied"); return -EPERM; } } }
DEBUG_PRINT(""); DEBUG_PRINT("access allowed"); return 0;}


禁用 capabilities

vArmor 具备禁用所有 capabilities、禁用特权 capability、禁用任意 capability 的能力,eBPF-vArmor ①

eBPF-vArmor ①

vArmor-eBPF 中的拦截实现

SEC("lsm/capable")// 传参 调用者的权限信息、正在执行的命名空间、检查的权限标志、附加选项、返回值int BPF_PROG(varmor_capable, const struct cred *cred, struct user_namespace *ns, int cap, unsigned int opts, int ret) {  // 获取当前任务的指针  struct task_struct *current = (struct task_struct *)bpf_get_current_task();
// 获取当前任务的挂载命名空间ID,并获取此命名空间的规则 u32 mnt_ns = get_task_mnt_ns_id(current); u64 *deny_caps = get_capability_rules(mnt_ns); if (deny_caps == 0) return ret;
DEBUG_PRINT("================ lsm/capable ================"); // 将权限 cap 转换为其对应的掩码 u64 request_cap_mask = CAP_TO_MASK(cap);
if (*deny_caps & request_cap_mask) { // 获取当前任务的用户命名空间 current_ns,以及任务的权限 struct user_namespace *current_ns = get_task_user_ns(current); kernel_cap_t current_cap_effective = get_task_cap_effective(current); u64 current_effective_mask = *(u64 *)&current_cap_effective;
DEBUG_PRINT("task(mnt ns: %u) current_effective_mask: 0x%lx, request_cap_mask: 0x%lx", mnt_ns, current_effective_mask, request_cap_mask);
// 在写入/tmp目录时与overlayfs兼容 if (current_ns == ns && current_effective_mask == 0x1fffeffffff) { return ret; }
// 与cgroupv2环境中的containerd兼容 if (current_ns == ns && current_effective_mask == 0x1ffffffffff) { return ret; } // 直接阻断相关 capability DEBUG_PRINT("task(mnt ns: %u) is not allowed to use capability: 0x%x", mnt_ns, cap); return -EPERM; }
return ret;}


参考文章

https://blog.spoock.com/2023/08/23/eBPF-vArmor/







原文始发于微信公众号(安全小将李坦然):eBPF-vArmor ①

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月25日22:50:15
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   eBPF-vArmor ①https://cn-sec.com/archives/2229176.html

发表评论

匿名网友 填写信息