云原生安全项目Falco

打印 上一主题 下一主题

主题 1022|帖子 1022|积分 3070

云原生安全项目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服务FalcosidekickFalco社区开发的告警转发服务,支持将告警事件转发到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下
  1. // SPDX-License-Identifier: GPL-2.0-only OR MIT
  2. /*
  3. Copyright (C) 2023 The Falco Authors.
  4. This file is dual licensed under either the MIT or GPL 2. See MIT.txt
  5. or GPL2.txt for full copies of the license.
  6. */
  7. #ifndef __QUIRKS_H   // 防止头文件被多次包含
  8. #define __QUIRKS_H
  9. #include <linux/version.h>
  10. // 确保内核版本大于等于 4.14,因为 eBPF 的一些功能在此版本之后才支持
  11. #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0)
  12. #error Kernel version must be >= 4.14 with eBPF enabled
  13. #endif
  14. // 处理 4.13 到 4.14.4 之间内核的结构随机化问题
  15. // 这部分代码定义了用于开始和结束随机化字段的宏
  16. #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0) && LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 4)
  17. #define randomized_struct_fields_start struct {
  18. #define randomized_struct_fields_end \
  19.     }                                \
  20.     ;
  21. #endif
  22. // 对于内核版本小于 4.15 的情况,定义 BPF_FORBIDS_ZERO_ACCESS 宏
  23. // 这可能与内核对 BPF 的零字节内存访问限制有关
  24. #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0)
  25. #define BPF_FORBIDS_ZERO_ACCESS
  26. #endif
  27. // 如果系统架构是 x86_64、ARM64、S390 或 PPC64,并且内核版本大于等于 4.17,定义 BPF_SUPPORTS_RAW_TRACEPOINTS
  28. // 这是为了确保系统支持 eBPF 的 raw_tracepoints 功能
  29. #if (defined(CONFIG_X86_64) || defined(CONFIG_ARM64) || defined(CONFIG_S390) || defined(CONFIG_PPC64)) && \
  30.     LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0)
  31. #define BPF_SUPPORTS_RAW_TRACEPOINTS
  32. #endif
  33. // 如果启用了 CAPTURE_SCHED_PROC_FORK 功能,但系统不支持 raw_tracepoints,则抛出编译错误
  34. #if CAPTURE_SCHED_PROC_FORK && !defined(BPF_SUPPORTS_RAW_TRACEPOINTS)
  35. #error The CAPTURE_SCHED_PROC_FORK support requires 'raw_tracepoints' so kernel versions greater or equal than '4.17'.
  36. #endif
  37. // 由于 clang 编译器不支持 asm_volatile_goto,因此取消定义 asm_volatile_goto 并将其替换为无效的 asm 指令
  38. #include <linux/types.h>
  39. #ifdef asm_volatile_goto
  40. #undef asm_volatile_goto
  41. #define asm_volatile_goto(...) asm volatile("invalid use of asm_volatile_goto")
  42. #endif
  43. // 内核在 5.4 之后引入了 asm_inline,但为了兼容旧版本内核,我们将 asm_inline 替换为 asm
  44. #ifdef asm_inline
  45. #undef asm_inline
  46. #define asm_inline asm
  47. #endif
  48. #endif /* __QUIRKS_H */
复制代码
driver/bpf/types.h下的选择raw_tracepoint大概tracepoint
  1. #ifdef BPF_SUPPORTS_RAW_TRACEPOINTS
  2. #define TP_NAME "raw_tracepoint/"
  3. #else
  4. #define TP_NAME "tracepoint/"
  5. #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追踪点事件的代码入口的核心代码
  1. // 定义一个 BPF 探针,用于捕获系统调用进入事件
  2. BPF_PROBE("raw_syscalls/", sys_enter, sys_enter_args) {
  3.     const struct syscall_evt_pair *sc_evt = NULL;  // 用于存储系统调用的事件配置信息
  4.     ppm_event_code evt_type = -1;  // 事件类型,初始化为 -1,表示未知事件类型
  5.     int drop_flags = 0;  // 标志位,用于标记是否需要丢弃事件
  6.     long id = 0;  // 系统调用号
  7.     bool enabled = false;  // 用于标记系统调用是否感兴趣(是否应该捕获)
  8.     int socketcall_syscall_id = -1;  // 用于处理 `socketcall` 系统调用的 ID
  9.     // 获取当前系统调用号
  10.     id = bpf_syscall_get_nr(ctx);
  11.     // 如果系统调用号无效(小于0)或超出预定义的系统调用表大小,直接返回
  12.     if(id < 0 || id >= SYSCALL_TABLE_SIZE)
  13.         return 0;
  14.     // 判断是否为 IA32 模拟模式(32 位系统调用),此时需要处理 32 位系统调用
  15.     if(bpf_in_ia32_syscall()) {
  16.         // 当前仅支持 x86_64 架构上的 32 位模拟
  17. #if defined(CONFIG_X86_64) && defined(CONFIG_IA32_EMULATION)
  18.         if(id == __NR_ia32_socketcall) {
  19.             // 如果是 32 位 socket 调用,存储其系统调用号
  20.             socketcall_syscall_id = __NR_ia32_socketcall;
  21.         } else {
  22.             // 将 32 位系统调用号转换为对应的 64 位系统调用号
  23.             id = convert_ia32_to_64(id);
  24.             // 如果该调用号无效(未能转换为 64 位),则丢弃
  25.             if(id == -1) {
  26.                 return 0;
  27.             }
  28.         }
  29. #else
  30.         // 如果不支持 IA32 模拟模式,直接返回
  31.         return 0;
  32. #endif
  33.     } else {
  34.         // 对于非 IA32 的系统调用,如果系统中定义了 `socketcall`,存储其系统调用号
  35. #ifdef __NR_socketcall
  36.         socketcall_syscall_id = __NR_socketcall;
  37. #endif
  38.     }
  39.     // 对于 `socketcall` 系统调用进行特殊处理
  40.     if(id == socketcall_syscall_id) {
  41. #ifdef BPF_SUPPORTS_RAW_TRACEPOINTS
  42.         // 如果支持 RAW_TRACEPOINTS,处理网络系统调用
  43.         bool is_syscall_return = false;
  44.         int return_code = convert_network_syscalls(ctx, &is_syscall_return);
  45.         if(return_code == -1) {
  46.             // 如果网络系统调用参数不正确,直接丢弃该系统调用
  47.             return 0;
  48.         }
  49.         if(!is_syscall_return) {
  50.             // 如果不是返回事件,设置事件类型
  51.             evt_type = return_code;
  52.             drop_flags = UF_USED;  // 标志该事件使用了
  53.         } else {
  54.             // 如果是返回事件,更新系统调用号
  55.             id = return_code;
  56.         }
  57. #else
  58.         // 如果不支持 RAW_TRACEPOINTS,则不支持 socketcall,直接返回
  59.         return 0;
  60. #endif
  61.     }
  62.     // 如果 `evt_type` 不等于 -1(即已经确定事件类型),跳过过滤逻辑
  63.     // 因为此时事件类型与系统调用号无关
  64.     if(evt_type == -1) {
  65.         // 判断当前系统调用是否是感兴趣的系统调用
  66.         enabled = is_syscall_interesting(id);
  67.         if(!enabled) {
  68.             return 0;  // 不感兴趣的系统调用,直接返回
  69.         }
  70.         // 获取系统调用的配置信息
  71.         sc_evt = get_syscall_info(id);
  72.         if(!sc_evt)
  73.             return 0;
  74.         // 根据配置信息设置事件类型和丢弃标志
  75.         if(sc_evt->flags & UF_USED) {
  76.             evt_type = sc_evt->enter_event_type;  // 设置进入事件类型
  77.             drop_flags = sc_evt->flags;
  78.         } else {
  79.             evt_type = PPME_GENERIC_E;  // 如果未使用,设置为通用进入事件类型
  80.             drop_flags = UF_ALWAYS_DROP;  // 标志该事件总是丢弃
  81.         }
  82.     }
  83.     // 如果支持 RAW_TRACEPOINTS,则调用事件填充器,将事件传递出去
  84. #ifdef BPF_SUPPORTS_RAW_TRACEPOINTS
  85.     call_filler(ctx, ctx, evt_type, drop_flags, socketcall_syscall_id);
  86. #else
  87.     // 如果不支持 RAW_TRACEPOINTS,使用备用实现,复制系统调用参数并调用填充器
  88.     struct sys_enter_args stack_ctx;
  89.     memcpy(stack_ctx.args, ctx->args, sizeof(ctx->args));  // 复制系统调用参数
  90.     if(stash_args(stack_ctx.args))
  91.         return 0;
  92.     // 执行具体的系统调用事件处理函数
  93.     call_filler(ctx, &stack_ctx, evt_type, drop_flags, socketcall_syscall_id);
  94. #endif
  95.     return 0;
  96. }
复制代码
详细的事件逻辑在call_filler函数中,call_filler函数的核心代码如下。
  1. // 内联函数,用于调用填充器(filler),生成并处理事件数据
  2. static __always_inline void call_filler(void *ctx,                       // eBPF 上下文
  3.                                         void *stack_ctx,                 // 用于存储系统调用参数的上下文
  4.                                         ppm_event_code evt_type,         // 事件类型
  5.                                         enum syscall_flags drop_flags,   // 丢弃标志,标记是否需要丢弃事件
  6.                                         int socketcall_syscall_id) {     // `socketcall` 系统调用的 ID
  7.     struct scap_bpf_settings *settings;                                  // 系统设置
  8.     const struct ppm_event_entry *filler_info;                           // 填充器信息
  9.     struct scap_bpf_per_cpu_state *state;                                // 每个 CPU 的状态
  10.     unsigned long long pid;                                              // 进程 ID
  11.     unsigned long long ts;                                               // 时间戳
  12.     unsigned int cpu;                                                    // 当前 CPU 编号
  13.     // 获取当前处理的 CPU 编号
  14.     cpu = bpf_get_smp_processor_id();
  15.     // 获取当前 CPU 的状态
  16.     state = get_local_state(cpu);
  17.     if(!state)
  18.         return;
  19.     // 获取系统设置
  20.     settings = get_bpf_settings();
  21.     if(!settings)
  22.         return;
  23.     // 尝试获取状态锁,如果无法获取,直接返回
  24.     if(!acquire_local_state(state))
  25.         return;
  26.     // 特殊处理 CPU 0,如果 CPU 热插拔标志已设置,生成 CPU 热插拔事件
  27.     if(cpu == 0 && state->hotplug_cpu != 0) {
  28.         evt_type = PPME_CPU_HOTPLUG_E;  // 事件类型设置为 CPU 热插拔事件
  29.         drop_flags = UF_NEVER_DROP;     // 标记该事件永远不丢弃
  30.     }
  31.     // 获取系统启动时间,加上当前内核时间,生成事件时间戳
  32.     ts = settings->boot_time + bpf_ktime_get_boot_ns();
  33.     // 重置 CPU 状态中的事件上下文,更新事件类型和时间戳
  34.     reset_tail_ctx(state, evt_type, ts);
  35.     // 判断该事件是否需要丢弃,如果需要丢弃,则跳转到清理步骤
  36.     if(drop_event(stack_ctx, state, evt_type, settings, drop_flags))
  37.         goto cleanup;
  38.     // 增加事件计数
  39.     ++state->n_evts;
  40.     // 记录 `socketcall` 系统调用 ID
  41.     state->tail_ctx.socketcall_syscall_id = socketcall_syscall_id;
  42.     // 根据事件获取事件填充器的信息
  43.     filler_info = get_event_filler_info(state->tail_ctx.evt_type);
  44.     if(!filler_info)
  45.         goto cleanup;
  46.     // 调用填充器,处理事件并将其记录下来
  47.     bpf_tail_call(ctx, &tail_map, filler_info->filler_id);
  48.     // 如果填充器调用失败,打印调试信息
  49.     bpf_printk("Can't tail call filler evt=%d, filler=%d\n",
  50.                state->tail_ctx.evt_type,
  51.                filler_info->filler_id);
  52. cleanup:
  53.     // 释放状态锁,进行资源清理
  54.     release_local_state(state);
  55. }
复制代码
重要是两个关键点,一是通过get_event_filler_info根据事件类型获得对应的filter_info信息
  1. // 内联函数,用于获取事件填充器的信息
  2. // 参数:
  3. // - event_type: 要查询的事件类型
  4. // 返回值:
  5. // - 指向 `ppm_event_entry` 结构的指针,包含与该事件类型相关的填充器信息
  6. static __always_inline const struct ppm_event_entry *get_event_filler_info(
  7.         ppm_event_code event_type) {   // 事件类型
  8.     const struct ppm_event_entry *e;   // 指向填充器信息的指针
  9.     // 从填充器表(fillers_table)中根据事件类型查找对应的填充器信息
  10.     e = bpf_map_lookup_elem(&fillers_table, &event_type);
  11.    
  12.     // 如果未找到与该事件类型对应的填充器信息,打印调试信息
  13.     if(!e)
  14.         bpf_printk("no filler info for %d\n", event_type);  // 输出事件类型
  15.     // 返回填充器信息,如果没有找到则返回 NULL
  16.     return e;
  17. }
复制代码
对应的fillers_table的内容在driver/fillers_table.c,此中重要定义了用来存储与不同事件相关联的事件填充器。在系统调用大概内核事件发生时,程序会根据事件类型查找对应的填充器(通过事件类型如 PPME_GENERIC_E),然后调用该填充器来提取和格式化捕获到的事件数据。
  1. const struct ppm_event_entry g_ppm_events[PPM_EVENT_MAX] = {
  2.         [PPME_GENERIC_E] = {FILLER_REF(sys_generic)},
  3.         [PPME_GENERIC_X] = {FILLER_REF(sys_generic)},
  4.         [PPME_SYSCALL_OPEN_E] = {FILLER_REF(sys_open_e)},
  5.         [PPME_SYSCALL_OPEN_X] = {FILLER_REF(sys_open_x)},
  6.         [PPME_SYSCALL_CLOSE_E] = {FILLER_REF(sys_close_e)},
  7.         [PPME_SYSCALL_CLOSE_X] = {FILLER_REF(sys_close_x)},
  8.         [PPME_SYSCALL_READ_E] = {FILLER_REF(sys_read_e)},
  9.     /*省略部分代码*/
  10. }   
复制代码
另一个是通过bpf_tail_call使用eBPF尾调用特性执行tail_map中存储的filler_info->filler_id指向的eBPF程序。从fillers_tables.c的定义可知,open系统调用指向的是tail_map中存储的sys_open_e事件处理函数。
  1. FILLER(sys_open_e, true) {
  2.     uint32_t flags;
  3.     unsigned long val;
  4.     uint32_t mode;
  5.     int res;
  6.     /* Parameter 1: name (type: PT_FSPATH) */
  7.     // 获取系统调用的第一个参数(文件路径),并将其存储到 `val` 中
  8.     val = bpf_syscall_get_argument(data, 0);
  9.     // 将获取的文件路径参数传递到 ring buffer,用于后续的用户态分析
  10.     res = bpf_val_to_ring(data, val);
  11.     // 检查操作是否成功,如果不成功则返回错误
  12.     CHECK_RES(res);
  13.     /* Parameter 2: flags (type: PT_FLAGS32) */
  14.     // 获取系统调用的第二个参数(flags),这通常用于描述文件的打开模式
  15.     flags = (uint32_t)bpf_syscall_get_argument(data, 1);
  16.     // 将 flags 转换为 Falco 支持的标准化格式(SCAP 格式)
  17.     flags = open_flags_to_scap(flags);
  18.     // 将标准化后的 flags 推送到 ring buffer
  19.     res = bpf_push_u32_to_ring(data, flags);
  20.     // 检查操作是否成功,如果不成功则返回错误
  21.     CHECK_RES(res);
  22.     /* Parameter 3: mode (type: PT_UINT32) */
  23.     // 获取系统调用的第三个参数(mode),表示文件的权限模式
  24.     mode = (uint32_t)bpf_syscall_get_argument(data, 2);
  25.     // 将文件的权限模式转换为标准化格式,必要时进行修正
  26.     mode = open_modes_to_scap(val, mode);
  27.     // 将标准化后的 mode 推送到 ring buffer 并返回操作结果
  28.     return bpf_push_u32_to_ring(data, mode);
  29. }
复制代码
末了将获取的事件通过ring buffer通过push_evt_frame函数保存,push_evt_frame函数对应的源码在dirver/bpf/ring_helpers.h中
  1. static __always_inline int push_evt_frame(void *ctx, struct filler_data *data) {
  2.         // 检查当前事件的参数数量是否与预期一致
  3.         if(data->state->tail_ctx.curarg != data->evt->nparams) {
  4.                 // 如果参数数量不一致,打印错误日志,表示当前事件的参数处理存在问题
  5.                 bpf_printk("corrupted filler for event type %d (added %u args, should have added %u)\n",
  6.                            data->state->tail_ctx.evt_type,
  7.                            data->state->tail_ctx.curarg,
  8.                            data->evt->nparams);
  9.                 // 返回表示出现 BUG 的错误码
  10.                 return PPM_FAILURE_BUG;
  11.         }
  12.         // 检查事件长度是否超过了 `PERF_EVENT_MAX_SIZE` 限制
  13.         if(data->state->tail_ctx.len > PERF_EVENT_MAX_SIZE) {
  14.                 // 如果长度超限,返回表示缓冲区已满的错误码
  15.                 return PPM_FAILURE_FRAME_SCRATCH_MAP_FULL;
  16.         }
  17.         // 调整事件长度信息(比如可能存在一些 padding 操作)
  18.         fixup_evt_len(data->buf, data->state->tail_ctx.len);
  19. #ifdef BPF_FORBIDS_ZERO_ACCESS
  20.         // 根据 BPF_FORBIDS_ZERO_ACCESS 宏,处理某些平台上禁止访问 0 大小的数据问题
  21.         int res = bpf_perf_event_output(ctx,
  22.                                         &perf_map,
  23.                                         BPF_F_CURRENT_CPU,
  24.                                         data->buf,
  25.                                         ((data->state->tail_ctx.len - 1) & SCRATCH_SIZE_MAX) + 1);
  26. #else
  27.         // 将事件的数据推送到 perf 事件缓冲区中,供用户态读取分析
  28.         int res = bpf_perf_event_output(ctx,
  29.                                         &perf_map,
  30.                                         BPF_F_CURRENT_CPU,
  31.                                         data->buf,
  32.                                         data->state->tail_ctx.len & SCRATCH_SIZE_MAX);
  33. #endif
  34.         // 处理返回值为 -ENOENT 或 -EOPNOTSUPP 的情况
  35.         if(res == -ENOENT || res == -EOPNOTSUPP) {
  36.                 /*
  37.                  * ENOENT = 可能有新的 CPU 在线但用户态程序未打开相应的 perf 事件
  38.                  *
  39.                  * EOPNOTSUPP = 可能某个 CPU 下线,导致 perf 事件通道关闭
  40.                  *
  41.                  * 在 CPU 0 上调度一个热插拔事件
  42.                  */
  43.                 struct scap_bpf_per_cpu_state *state = get_local_state(0);
  44.                 // 如果无法获取状态,返回表示 BUG 的错误码
  45.                 if(!state)
  46.                         return PPM_FAILURE_BUG;
  47.                 // 记录发生热插拔事件的 CPU ID 并打印日志
  48.                 state->hotplug_cpu = bpf_get_smp_processor_id();
  49.                 bpf_printk("detected hotplug event, cpu=%d\n", state->hotplug_cpu);
  50.         // 如果返回值为 -ENOSPC,表示 BPF perf 缓冲区已满
  51.         } else if(res == -ENOSPC) {
  52.                 bpf_printk("bpf_perf_buffer full\n");
  53.                 // 返回表示缓冲区已满的错误码
  54.                 return PPM_FAILURE_BUFFER_FULL;
  55.         // 如果返回值是其他错误码,打印错误日志并返回 BUG 错误码
  56.         } else if(res) {
  57.                 bpf_printk("bpf_perf_event_output failed, res=%d\n", res);
  58.                 return PPM_FAILURE_BUG;
  59.         }
  60.         // 成功推送事件,返回 PPM_SUCCESS
  61.         return PPM_SUCCESS;
  62. }
复制代码
在push_evt_frame函数中使用bpf_perf_event_output辅助函数将eBPF程序中获取的事件数据发送到perf_map中,在dirver/bpf/maps.h中
  1. struct bpf_map_def __bpf_section("maps") perf_map = {
  2.     // 定义 BPF 映射的类型,这里使用的是 BPF_MAP_TYPE_PERF_EVENT_ARRAY 类型。
  3.     // 该类型允许将数据发送到 perf 事件缓冲区供用户态读取。
  4.     .type = BPF_MAP_TYPE_PERF_EVENT_ARRAY,
  5.     // key_size 表示 key 的大小。对于 perf 事件数组,key 是一个 CPU ID,
  6.     // 因此这里指定 key 的大小为 uint32_t(4 字节)。
  7.     .key_size = sizeof(uint32_t),
  8.     // value_size 表示值的大小。对于 perf 事件数组,value 通常也是 CPU 相关的,
  9.     // 因此同样指定为 uint32_t(4 字节)。
  10.     .value_size = sizeof(uint32_t),
  11.     // max_entries 用于指定映射中可以容纳的最大条目数。对于 perf 事件数组,
  12.     // max_entries 通常表示可以容纳的 CPU 数。这里设置为 0,表示该值将在运行时确定。
  13.     .max_entries = 0,
  14. };
复制代码
Falco的eBPF探针基于这个类型为BPF_MAP_TYPE_EVENT_ARRAY的eBPF MAP实现了在eBPF程序中将数据从内核态中通报给用户态程序的能力,用户态程序通过读取这个perf_map中保存的数据,在用户态实现后续的业务逻辑。
用户态模块
​ falcosecurity/libs堆栈包括了4个用户态模块:libscap,libsinsp,engine,falco。这些模块通过消费驱动从内核中收集的事件数据,收集系统中的状态信息,分析事件和应用用户自定义规则,最终实现了Falco项目的用户态程序所依赖的核心功能。

  • libscap模块功能
    falcosecurity/libs堆栈中libscap模块重要包含三个功能:管理事件源(控制事件收罗),收集系统状态及读写scap文件
  • libsinsp模块功能
    falcosecurity/libs堆栈中的libsinsp模块重要包含状态引擎,分析事件,过滤器以及格式化输出
  • engine模块功能
    falcosecurity/libs堆栈中的engine模块是Falco中规则引擎的前端入口,该模块重要实现了下面的功能

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

  • falco模块功能
    falcosecurity/libs堆栈中的falco模块实现了用户能直接接触到的一些高条理的特性,比如下令行工具,自定义告警输出方式,一个gRPC服务,一个HTTP服务等功能。
Falco的底层特性的实现代码都位于falcosecurity/libs堆栈中,falcosecurity/libs堆栈中得到代码通过组合libs中的底层特性最终实现了面向用户的此产品——Falco。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

东湖之滨

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表