云原生安全项目Falco

云原生安全项目Falco

前言

​ eBPF灵活高效的特性被广泛应用于性能,安全和系统监控等领域。然而这样的灵活性也是一把双刃剑,虽然eBPF验证器会对用户自定义的代码实施一定的安全校验和运行限制,但是无法阻止漏洞的发生,也无法阻止有经验的攻击者通过各种手段获得执行恶意eBPF程序的特权,特别是eBPF技术可以让攻击者一最短的路径获得内核中运行自定义代码的能力,这对于恶意攻击者来说是非常有吸引力的。

​ eBPF的灵活型给了攻击者很大的发挥孔吉纳,尤其是熟悉内核编程的攻击者。基于eBPF提供的辅助函数和开发模式,攻击者可以针对于特顶场景快速编写恶意程序,比如窃取或者串改指定的文件内容,劫持未加密的网络绘画或劫持指定的管道程序发起命令执行攻击,甚至进一步在系统日志或进程列表中隐藏攻击痕迹。

​ 但是是否因为上述的缺点,我们就不能在系统上启用eBPF的特性呢?显然不是,任何技术的创新都会伴随新的安全风险,企业需要根据自身应用场景和安全防护水平来进行风险管理,在权衡安全防护措施实施成本的签一下并判断是否隐喻新的技术变更。下面是一些措施来帮助发现或抵御基于eBPF技术的恶意攻击。

  • 完善针对eBPF程序的安全审核与评估。
  • 监控和审计eBPF程序
  • 严格遵循最小化权限原则部署应用(主要是在云原生环境下)
  • 使用eBPF程序来实时监控基于eBPF的恶意攻击。
  • 通过签名等手段保证eBPF程序的安全性。通过对运行在环境中的eBPF程序签名的校验可以有效阻止不可信恶意程序的执行,在底层硬件的支持下,这样的设计思路很可能成为未来eBPF程序安全增强的重要方向。

项目介绍

Falco是Sysding公司在2016年5月正式开启的一款云原生运行时安全项目。

功能

​ Falco的主要功能是基于实时观测到的应用和容器的运行时安全威胁检测和告警,同时,Falco支持通过插件的方式检测和告警其他不同类型的安全风险,

​ 用户及可以将系统调用事件作为Falco规则引擎处理的事件源,又可以将Kubernetes事件,云上活动事件身体任意第三方系统产生的事件作为Falco的事件源。

Falco通过驱动的方式内置了两种捕获系统调用事件事件的方法。

  • 基于内核模块技术的内核模块驱动。Falco默认使用该方法捕获系统调用事件。
  • 基于eBPF技术的eBPF探针驱动。
驱动种类 优点 缺点
内核模块 支持2.6及以上版本的内核 更低的性能损耗 程序异常可能会导致内核崩溃 部分环境不允许安装内核模块
eBPF探针 更安全,不会导致内核崩溃 支持在运行时动态加载,不需要借助dkms之类的内核模块工具进行辅助加载 可以在部分不允许安装内核模块的环境下运行 只支持4.14及以上的内核版本 不是所有的系统都支持eBPF急事

​ Falco还可以通过插件的方式接受来自第三方系统的事件作为事件源,比如Kubernetes审计日志事件,AWS云上活动事件,GitHub Webhook事件等来自第三方的外部事件。

​ Falco强大的规则引擎支持灵活的规则语法,方便用户根据需求为事件源中产生的事件配置各类安全策略,从而在触发规则时产生安全告警。当有时间触发告警后,Falco支持将告警发送到多个途径。

告警方式 说明
标准输出 将告警信息输出到Falco程序的标准输出中
文件 将告警信息写入指定的文件
syslog 将告警信息发送给syslog服务
执行程序 在触发告警的时候将告警信息作为标准输入执行指定的程序
HTTP 将告警信息发给指定的HTTP或HTTPS服务
JSON 将前面发送的告警信息转换为JSON格式
gRPC 将告警信息发送个指定的gRPC服务
Falcosidekick Falco社区开发的告警转发服务,支持将告警事件转发到50余个外部系统中

场景

常见的使用场景

  • 实时监控和审计特定的Linux系统调用事件,基于这些事件检测各种安全风险。比如。敏感文件读写操作,命令执行操作,非预期的网络请求,权限变更或权限提升操作等。
  • 基于Falco强大的规则引擎分析Kubernetes审计事件中存在的安全风险。比如,创建特权容器,挂载主机敏感目录,使用主机网络,授权高危权限,不符合阻止或团队安全规范的风险操作等安全风险,

基于Falco灵活的规则引擎,用户即可以使用它内置的强大内核系统调用探针实时监控主机或容器的行为和活动,在触发规则策略后实时产生安全告警,又可以将安全事件集成到第三方系统,自动化的进行安全告警或安全运营工作

架构和实现原理

架构

​ Falco使用内置的规则引擎分析来自内置的内核模块或者eBPF探针所获取的事件。放某一事件满足用户定义的规则时,Falco会产生相应的告警事件,告警事件将默认输出到Falco程序中,用户也可以自定义多种告警事件的输出方式和渠道。

驱动

​ Falco中系统调用的采集是通过驱动实现的,通过驱动实现了系统调用事件的采集,事件的打包和编码,通过零复制技术实现了将事件从内核态传输到用户态的事件传输功能。常用的驱动有两种:内核模块和eBPF探针。

​ 当前代码主要是基于Linux跟踪点技术,将eBPF程序附加到特定的静态跟踪点(主要是raw_syscalls/sys_enter和raw_syscalls/sys_exit这两个跟踪点),实现实时追踪所有系统调用的需求。

​ eBPF探针会根据内核版本自动选择使用raw_tracepoint或tracepoint类型的跟踪点技术,当系统的内核版本大于过等于4.17是使用raw_tracepoint,小于时使用tracepoint类型。在/driver/bpf/types.h下

c 复制代码
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/*

Copyright (C) 2023 The Falco Authors.

This file is dual licensed under either the MIT or GPL 2. See MIT.txt
or GPL2.txt for full copies of the license.

*/
#ifndef __QUIRKS_H   // 防止头文件被多次包含
#define __QUIRKS_H

#include <linux/version.h>

// 确保内核版本大于等于 4.14,因为 eBPF 的一些功能在此版本之后才支持
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0)
#error Kernel version must be >= 4.14 with eBPF enabled
#endif

// 处理 4.13 到 4.14.4 之间内核的结构随机化问题
// 这部分代码定义了用于开始和结束随机化字段的宏
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0) && LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 4)
#define randomized_struct_fields_start struct {
#define randomized_struct_fields_end \
    }                                \
    ;
#endif

// 对于内核版本小于 4.15 的情况,定义 BPF_FORBIDS_ZERO_ACCESS 宏
// 这可能与内核对 BPF 的零字节内存访问限制有关
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0)
#define BPF_FORBIDS_ZERO_ACCESS
#endif

// 如果系统架构是 x86_64、ARM64、S390 或 PPC64,并且内核版本大于等于 4.17,定义 BPF_SUPPORTS_RAW_TRACEPOINTS
// 这是为了确保系统支持 eBPF 的 raw_tracepoints 功能
#if (defined(CONFIG_X86_64) || defined(CONFIG_ARM64) || defined(CONFIG_S390) || defined(CONFIG_PPC64)) && \
    LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0)
#define BPF_SUPPORTS_RAW_TRACEPOINTS
#endif

// 如果启用了 CAPTURE_SCHED_PROC_FORK 功能,但系统不支持 raw_tracepoints,则抛出编译错误
#if CAPTURE_SCHED_PROC_FORK && !defined(BPF_SUPPORTS_RAW_TRACEPOINTS)
#error The CAPTURE_SCHED_PROC_FORK support requires 'raw_tracepoints' so kernel versions greater or equal than '4.17'.
#endif

// 由于 clang 编译器不支持 asm_volatile_goto,因此取消定义 asm_volatile_goto 并将其替换为无效的 asm 指令
#include <linux/types.h>
#ifdef asm_volatile_goto
#undef asm_volatile_goto
#define asm_volatile_goto(...) asm volatile("invalid use of asm_volatile_goto")
#endif

// 内核在 5.4 之后引入了 asm_inline,但为了兼容旧版本内核,我们将 asm_inline 替换为 asm
#ifdef asm_inline
#undef asm_inline
#define asm_inline asm
#endif

#endif /* __QUIRKS_H */

driver/bpf/types.h下的选择raw_tracepoint或者tracepoint

c 复制代码
#ifdef BPF_SUPPORTS_RAW_TRACEPOINTS
#define TP_NAME "raw_tracepoint/"
#else
#define TP_NAME "tracepoint/"
#endif

Falco的eBPF探针主要追踪了下面这些静态跟踪点的内核事件在driver/bpf/probe.c

主要定义了8个跟踪点

跟踪点 用途
raw_sysscalls/sys_enter 追踪所有系统调用执行事件
raw_sysscalls/sys_exit 追踪所有系统调用执行完成事件
sched/sched_process_exit 追踪进程推出事件
sched/sched_switch 追踪CPU上的金额和那个切换事件
exceptions/page_fault_user 追踪用户态页错误事件
exceptions/page_fault_kernel 追踪内核态页错误事件
signal/signal_deliver 追踪进行信号接收事件

eBPF探针事件的处理逻辑,主要以跟踪open系统调用为例子。

raw_sysscalls/sys_enter追踪点事件的代码入口的核心代码

c 复制代码
// 定义一个 BPF 探针,用于捕获系统调用进入事件
BPF_PROBE("raw_syscalls/", sys_enter, sys_enter_args) {
    const struct syscall_evt_pair *sc_evt = NULL;  // 用于存储系统调用的事件配置信息
    ppm_event_code evt_type = -1;  // 事件类型,初始化为 -1,表示未知事件类型
    int drop_flags = 0;  // 标志位,用于标记是否需要丢弃事件
    long id = 0;  // 系统调用号
    bool enabled = false;  // 用于标记系统调用是否感兴趣(是否应该捕获)
    int socketcall_syscall_id = -1;  // 用于处理 `socketcall` 系统调用的 ID

    // 获取当前系统调用号
    id = bpf_syscall_get_nr(ctx);
    // 如果系统调用号无效(小于0)或超出预定义的系统调用表大小,直接返回
    if(id < 0 || id >= SYSCALL_TABLE_SIZE)
        return 0;

    // 判断是否为 IA32 模拟模式(32 位系统调用),此时需要处理 32 位系统调用
    if(bpf_in_ia32_syscall()) {
        // 当前仅支持 x86_64 架构上的 32 位模拟
#if defined(CONFIG_X86_64) && defined(CONFIG_IA32_EMULATION)
        if(id == __NR_ia32_socketcall) {
            // 如果是 32 位 socket 调用,存储其系统调用号
            socketcall_syscall_id = __NR_ia32_socketcall;
        } else {
            // 将 32 位系统调用号转换为对应的 64 位系统调用号
            id = convert_ia32_to_64(id);
            // 如果该调用号无效(未能转换为 64 位),则丢弃
            if(id == -1) {
                return 0;
            }
        }
#else
        // 如果不支持 IA32 模拟模式,直接返回
        return 0;
#endif
    } else {
        // 对于非 IA32 的系统调用,如果系统中定义了 `socketcall`,存储其系统调用号
#ifdef __NR_socketcall
        socketcall_syscall_id = __NR_socketcall;
#endif
    }

    // 对于 `socketcall` 系统调用进行特殊处理
    if(id == socketcall_syscall_id) {
#ifdef BPF_SUPPORTS_RAW_TRACEPOINTS
        // 如果支持 RAW_TRACEPOINTS,处理网络系统调用
        bool is_syscall_return = false;
        int return_code = convert_network_syscalls(ctx, &is_syscall_return);
        if(return_code == -1) {
            // 如果网络系统调用参数不正确,直接丢弃该系统调用
            return 0;
        }
        if(!is_syscall_return) {
            // 如果不是返回事件,设置事件类型
            evt_type = return_code;
            drop_flags = UF_USED;  // 标志该事件使用了
        } else {
            // 如果是返回事件,更新系统调用号
            id = return_code;
        }
#else
        // 如果不支持 RAW_TRACEPOINTS,则不支持 socketcall,直接返回
        return 0;
#endif
    }

    // 如果 `evt_type` 不等于 -1(即已经确定事件类型),跳过过滤逻辑
    // 因为此时事件类型与系统调用号无关
    if(evt_type == -1) {
        // 判断当前系统调用是否是感兴趣的系统调用
        enabled = is_syscall_interesting(id);
        if(!enabled) {
            return 0;  // 不感兴趣的系统调用,直接返回
        }

        // 获取系统调用的配置信息
        sc_evt = get_syscall_info(id);
        if(!sc_evt)
            return 0;

        // 根据配置信息设置事件类型和丢弃标志
        if(sc_evt->flags & UF_USED) {
            evt_type = sc_evt->enter_event_type;  // 设置进入事件类型
            drop_flags = sc_evt->flags;
        } else {
            evt_type = PPME_GENERIC_E;  // 如果未使用,设置为通用进入事件类型
            drop_flags = UF_ALWAYS_DROP;  // 标志该事件总是丢弃
        }
    }

    // 如果支持 RAW_TRACEPOINTS,则调用事件填充器,将事件传递出去
#ifdef BPF_SUPPORTS_RAW_TRACEPOINTS
    call_filler(ctx, ctx, evt_type, drop_flags, socketcall_syscall_id);
#else
    // 如果不支持 RAW_TRACEPOINTS,使用备用实现,复制系统调用参数并调用填充器
    struct sys_enter_args stack_ctx;

    memcpy(stack_ctx.args, ctx->args, sizeof(ctx->args));  // 复制系统调用参数
    if(stash_args(stack_ctx.args))
        return 0;

    // 执行具体的系统调用事件处理函数
    call_filler(ctx, &stack_ctx, evt_type, drop_flags, socketcall_syscall_id);
#endif
    return 0;
}

具体的事件逻辑在call_filler函数中,call_filler函数的核心代码如下。

c 复制代码
// 内联函数,用于调用填充器(filler),生成并处理事件数据
static __always_inline void call_filler(void *ctx,                       // eBPF 上下文
                                        void *stack_ctx,                 // 用于存储系统调用参数的上下文
                                        ppm_event_code evt_type,         // 事件类型
                                        enum syscall_flags drop_flags,   // 丢弃标志,标记是否需要丢弃事件
                                        int socketcall_syscall_id) {     // `socketcall` 系统调用的 ID
    struct scap_bpf_settings *settings;                                  // 系统设置
    const struct ppm_event_entry *filler_info;                           // 填充器信息
    struct scap_bpf_per_cpu_state *state;                                // 每个 CPU 的状态
    unsigned long long pid;                                              // 进程 ID
    unsigned long long ts;                                               // 时间戳
    unsigned int cpu;                                                    // 当前 CPU 编号

    // 获取当前处理的 CPU 编号
    cpu = bpf_get_smp_processor_id();

    // 获取当前 CPU 的状态
    state = get_local_state(cpu);
    if(!state)
        return;

    // 获取系统设置
    settings = get_bpf_settings();
    if(!settings)
        return;

    // 尝试获取状态锁,如果无法获取,直接返回
    if(!acquire_local_state(state))
        return;

    // 特殊处理 CPU 0,如果 CPU 热插拔标志已设置,生成 CPU 热插拔事件
    if(cpu == 0 && state->hotplug_cpu != 0) {
        evt_type = PPME_CPU_HOTPLUG_E;  // 事件类型设置为 CPU 热插拔事件
        drop_flags = UF_NEVER_DROP;     // 标记该事件永远不丢弃
    }

    // 获取系统启动时间,加上当前内核时间,生成事件时间戳
    ts = settings->boot_time + bpf_ktime_get_boot_ns();

    // 重置 CPU 状态中的事件上下文,更新事件类型和时间戳
    reset_tail_ctx(state, evt_type, ts);

    // 判断该事件是否需要丢弃,如果需要丢弃,则跳转到清理步骤
    if(drop_event(stack_ctx, state, evt_type, settings, drop_flags))
        goto cleanup;

    // 增加事件计数
    ++state->n_evts;

    // 记录 `socketcall` 系统调用 ID
    state->tail_ctx.socketcall_syscall_id = socketcall_syscall_id;

    // 根据事件获取事件填充器的信息
    filler_info = get_event_filler_info(state->tail_ctx.evt_type);
    if(!filler_info)
        goto cleanup;

    // 调用填充器,处理事件并将其记录下来
    bpf_tail_call(ctx, &tail_map, filler_info->filler_id);

    // 如果填充器调用失败,打印调试信息
    bpf_printk("Can't tail call filler evt=%d, filler=%d\n",
               state->tail_ctx.evt_type,
               filler_info->filler_id);

cleanup:
    // 释放状态锁,进行资源清理
    release_local_state(state);
}

主要是两个关键点,一是通过get_event_filler_info根据事件类型获得对应的filter_info信息

c 复制代码
// 内联函数,用于获取事件填充器的信息
// 参数:
// - event_type: 要查询的事件类型
// 返回值:
// - 指向 `ppm_event_entry` 结构的指针,包含与该事件类型相关的填充器信息
static __always_inline const struct ppm_event_entry *get_event_filler_info(
        ppm_event_code event_type) {   // 事件类型
    const struct ppm_event_entry *e;   // 指向填充器信息的指针

    // 从填充器表(fillers_table)中根据事件类型查找对应的填充器信息
    e = bpf_map_lookup_elem(&fillers_table, &event_type);
    
    // 如果未找到与该事件类型对应的填充器信息,打印调试信息
    if(!e)
        bpf_printk("no filler info for %d\n", event_type);  // 输出事件类型

    // 返回填充器信息,如果没有找到则返回 NULL
    return e;
}

对应的fillers_table的内容在driver/fillers_table.c,其中主要定义了用来存储与不同事件相关联的事件填充器。在系统调用或者内核事件发生时,程序会根据事件类型查找对应的填充器(通过事件类型如 PPME_GENERIC_E),然后调用该填充器来提取和格式化捕获到的事件数据。

c 复制代码
const struct ppm_event_entry g_ppm_events[PPM_EVENT_MAX] = {
        [PPME_GENERIC_E] = {FILLER_REF(sys_generic)},
        [PPME_GENERIC_X] = {FILLER_REF(sys_generic)},
        [PPME_SYSCALL_OPEN_E] = {FILLER_REF(sys_open_e)},
        [PPME_SYSCALL_OPEN_X] = {FILLER_REF(sys_open_x)},
        [PPME_SYSCALL_CLOSE_E] = {FILLER_REF(sys_close_e)},
        [PPME_SYSCALL_CLOSE_X] = {FILLER_REF(sys_close_x)},
        [PPME_SYSCALL_READ_E] = {FILLER_REF(sys_read_e)},
    /*省略部分代码*/
 }   

另一个是通过bpf_tail_call使用eBPF尾调用特性执行tail_map中存储的filler_info->filler_id指向的eBPF程序。从fillers_tables.c的定义可知,open系统调用指向的是tail_map中存储的sys_open_e事件处理函数。

c 复制代码
FILLER(sys_open_e, true) {
    uint32_t flags;
    unsigned long val;
    uint32_t mode;
    int res;

    /* Parameter 1: name (type: PT_FSPATH) */
    // 获取系统调用的第一个参数(文件路径),并将其存储到 `val` 中
    val = bpf_syscall_get_argument(data, 0);
    // 将获取的文件路径参数传递到 ring buffer,用于后续的用户态分析
    res = bpf_val_to_ring(data, val);
    // 检查操作是否成功,如果不成功则返回错误
    CHECK_RES(res);

    /* Parameter 2: flags (type: PT_FLAGS32) */
    // 获取系统调用的第二个参数(flags),这通常用于描述文件的打开模式
    flags = (uint32_t)bpf_syscall_get_argument(data, 1);
    // 将 flags 转换为 Falco 支持的标准化格式(SCAP 格式)
    flags = open_flags_to_scap(flags);
    // 将标准化后的 flags 推送到 ring buffer
    res = bpf_push_u32_to_ring(data, flags);
    // 检查操作是否成功,如果不成功则返回错误
    CHECK_RES(res);

    /* Parameter 3: mode (type: PT_UINT32) */
    // 获取系统调用的第三个参数(mode),表示文件的权限模式
    mode = (uint32_t)bpf_syscall_get_argument(data, 2);
    // 将文件的权限模式转换为标准化格式,必要时进行修正
    mode = open_modes_to_scap(val, mode);
    // 将标准化后的 mode 推送到 ring buffer 并返回操作结果
    return bpf_push_u32_to_ring(data, mode);
}

最后将获取的事件通过ring buffer通过push_evt_frame函数保存,push_evt_frame函数对应的源码在dirver/bpf/ring_helpers.h中

c 复制代码
static __always_inline int push_evt_frame(void *ctx, struct filler_data *data) {
	// 检查当前事件的参数数量是否与预期一致
	if(data->state->tail_ctx.curarg != data->evt->nparams) {
		// 如果参数数量不一致,打印错误日志,表示当前事件的参数处理存在问题
		bpf_printk("corrupted filler for event type %d (added %u args, should have added %u)\n",
		           data->state->tail_ctx.evt_type,
		           data->state->tail_ctx.curarg,
		           data->evt->nparams);
		// 返回表示出现 BUG 的错误码
		return PPM_FAILURE_BUG;
	}

	// 检查事件长度是否超过了 `PERF_EVENT_MAX_SIZE` 限制
	if(data->state->tail_ctx.len > PERF_EVENT_MAX_SIZE) {
		// 如果长度超限,返回表示缓冲区已满的错误码
		return PPM_FAILURE_FRAME_SCRATCH_MAP_FULL;
	}

	// 调整事件长度信息(比如可能存在一些 padding 操作)
	fixup_evt_len(data->buf, data->state->tail_ctx.len);

#ifdef BPF_FORBIDS_ZERO_ACCESS
	// 根据 BPF_FORBIDS_ZERO_ACCESS 宏,处理某些平台上禁止访问 0 大小的数据问题
	int res = bpf_perf_event_output(ctx,
	                                &perf_map,
	                                BPF_F_CURRENT_CPU,
	                                data->buf,
	                                ((data->state->tail_ctx.len - 1) & SCRATCH_SIZE_MAX) + 1);
#else
	// 将事件的数据推送到 perf 事件缓冲区中,供用户态读取分析
	int res = bpf_perf_event_output(ctx,
	                                &perf_map,
	                                BPF_F_CURRENT_CPU,
	                                data->buf,
	                                data->state->tail_ctx.len & SCRATCH_SIZE_MAX);
#endif

	// 处理返回值为 -ENOENT 或 -EOPNOTSUPP 的情况
	if(res == -ENOENT || res == -EOPNOTSUPP) {
		/*
		 * ENOENT = 可能有新的 CPU 在线但用户态程序未打开相应的 perf 事件
		 * 
		 * EOPNOTSUPP = 可能某个 CPU 下线,导致 perf 事件通道关闭
		 * 
		 * 在 CPU 0 上调度一个热插拔事件
		 */
		struct scap_bpf_per_cpu_state *state = get_local_state(0);

		// 如果无法获取状态,返回表示 BUG 的错误码
		if(!state)
			return PPM_FAILURE_BUG;

		// 记录发生热插拔事件的 CPU ID 并打印日志
		state->hotplug_cpu = bpf_get_smp_processor_id();
		bpf_printk("detected hotplug event, cpu=%d\n", state->hotplug_cpu);

	// 如果返回值为 -ENOSPC,表示 BPF perf 缓冲区已满
	} else if(res == -ENOSPC) {
		bpf_printk("bpf_perf_buffer full\n");
		// 返回表示缓冲区已满的错误码
		return PPM_FAILURE_BUFFER_FULL;

	// 如果返回值是其他错误码,打印错误日志并返回 BUG 错误码
	} else if(res) {
		bpf_printk("bpf_perf_event_output failed, res=%d\n", res);
		return PPM_FAILURE_BUG;
	}

	// 成功推送事件,返回 PPM_SUCCESS
	return PPM_SUCCESS;
}

在push_evt_frame函数中使用bpf_perf_event_output辅助函数将eBPF程序中获取的事件数据发送到perf_map中,在dirver/bpf/maps.h中

c 复制代码
struct bpf_map_def __bpf_section("maps") perf_map = {
    // 定义 BPF 映射的类型,这里使用的是 BPF_MAP_TYPE_PERF_EVENT_ARRAY 类型。
    // 该类型允许将数据发送到 perf 事件缓冲区供用户态读取。
    .type = BPF_MAP_TYPE_PERF_EVENT_ARRAY,

    // key_size 表示 key 的大小。对于 perf 事件数组,key 是一个 CPU ID,
    // 因此这里指定 key 的大小为 uint32_t(4 字节)。
    .key_size = sizeof(uint32_t),

    // value_size 表示值的大小。对于 perf 事件数组,value 通常也是 CPU 相关的,
    // 因此同样指定为 uint32_t(4 字节)。
    .value_size = sizeof(uint32_t),

    // max_entries 用于指定映射中可以容纳的最大条目数。对于 perf 事件数组,
    // max_entries 通常表示可以容纳的 CPU 数。这里设置为 0,表示该值将在运行时确定。
    .max_entries = 0,
};

Falco的eBPF探针基于这个类型为BPF_MAP_TYPE_EVENT_ARRAY的eBPF MAP实现了在eBPF程序中将数据从内核态中传递给用户态程序的能力,用户态程序通过读取这个perf_map中保存的数据,在用户态实现后续的业务逻辑。

用户态模块

​ falcosecurity/libs仓库包括了4个用户态模块:libscap,libsinsp,engine,falco。这些模块通过消费驱动从内核中收集的事件数据,收集系统中的状态信息,解析事件和应用用户自定义规则,最终实现了Falco项目的用户态程序所依赖的核心功能。

  1. libscap模块功能

    falcosecurity/libs仓库中libscap模块主要包含三个功能:管理事件源(控制事件采集),收集系统状态及读写scap文件

  2. libsinsp模块功能

    falcosecurity/libs仓库中的libsinsp模块主要包含状态引擎,解析事件,过滤器以及格式化输出

  3. engine模块功能

    falcosecurity/libs仓库中的engine模块是Falco中规则引擎的前端入口,该模块主要实现了下面的功能

    • 加载规则文件
    • 解析规则文件中定义的规则
    • 基于libsinsp模块使用规则中定义的表达式对事件进行评估及格式化规则输出信息
    • 对于评估后需要告警的规则使用格式化后的输出内容产生相应的告警事件。
  4. falco模块功能

    falcosecurity/libs仓库中的falco模块实现了用户能直接接触到的一些高层次的特性,比如命令行工具,自定义告警输出方式,一个gRPC服务,一个HTTP服务等功能。

Falco的底层特性的实现代码都位于falcosecurity/libs仓库中,falcosecurity/libs仓库中得到代码通过组合libs中的底层特性最终实现了面向用户的此产品------Falco。

相关推荐
hero_th13 分钟前
[Ubuntu] 文件/目录权限更改
linux·ubuntu
花花少年22 分钟前
pip在ubuntu下换源
linux·ubuntu·pip
黑龙江亿林等级保护测评23 分钟前
做等保二级备案需要准备哪些材料
网络·安全·金融·智能路由器·ddos
y0ungsheep41 分钟前
[GXYCTF 2019]Ping Ping Ping 题解(多种解题方式)
linux·web安全·网络安全·php
星海幻影41 分钟前
网络安全知识见闻终章 ?
网络·安全·web安全
kinlon.liu43 分钟前
安全日志记录的重要性
服务器·网络·安全·安全架构·1024程序员节
海绵波波1071 小时前
Webserver(1.6)Linux系统IO函数
linux·运维·服务器
马戏团小丑1 小时前
fastjson/jackson对getter,setter和constructor的区分
java·安全
czme1 小时前
线程和进程
linux·数据结构·计算机网络
华东设计之美1 小时前
etcd多实例配置
linux·服务器·etcd