BPF安全
“安全”一词涵盖了广泛的任务:
- 安全分析 - 用于实时取证的嗅探活动- 权限调试- 可执行文件的白名单- 对恶意软件的逆向工程
- 监控 - 定制化审计- 基于主机的入侵检测系统(HIDS)- 基于容器的入侵检测系统(CIDS)
- 策略执行 - 网络防火墙- 检测恶意软件,动态阻塞数据包以及其他入侵行为
安全工程与性能工程相类似,这两者都与要对各种大量不同的软件进行分析。
计算机的安全概括了三个特性,私密性(Confidentiality)、 完整性(Integrity)、可用性(Availability),简称 CIA。
- 私密性就是数据不被未授权的人看到
- 完整性是指存储或传输的信息不被篡改
- 可用性是指自己的设备在需要使用的时候能够使用
计算机系统应对安全挑战的方法主要有四种:隔离,控制,审计,混淆
BPF在安全方面可以做什么
BPF可以帮助完成下面的安全任务,包括分析,监控,策略执行。
安全分析
BPF可以回答的问题包括:
- 正在被执行的进程有哪些?
- 什么网络链接正在被建立,来自哪个进程?
- 什么系统权限正在被请求,被那个进程请求?
- 系统内正在发生哪些权限拒绝错误?
- 是否在以给定参数调用当前内核函数和用户态函数(检测实时入侵行为)?
也可以根据可跟踪的目标列表来总结BPF跟踪的分析和监视功能。
安全监控
BPF跟踪程序可以用于安全监控和入侵检测。当前的监控方案通常使用可加载内核模块来观测内核和数据包事件。然而,这些模块本身也引入了内核错误和漏洞的风险。BPF程序自带验证程序,并使用现有的内核技术。相比来说也更加安全。
BPF跟踪还针对效率进行优化。
BPF监控的一个重要行为特点是在极高工作负载下的行为设计。BPF输出缓冲区射表大小有限制,如果达到了这些上限,某些事件可能会被忽略。这可以被攻击者所利用,可以创造海量事件淹没系统,从而逃避适当的日志记录或策略执行。当这些限制被超过时BPF会收到通知,并且可以汇报到用户态以采取合适的措施。任何基于BFF跟踪构建的安全方案都需要记录这些溢出和事件缺失的情况,以满足“不可反悔”(Non-repudiation)的设计要求。
linux中的BPF和安全
BPF程序附加到Linux安全模块(LSM)钩子(也称为BPF-LSM)的能力是内核的一个新添加的功能,正在被用于“防止有趣的安全攻击”。和其它类型的BPF程序一样,可以附加到LSM钩子(或系统调用)的BPF程序具有与跟踪,网络或其他程序类型不同的特定功能。这些程序可以读取任意的内核数据并拒绝操作,但它们也可以休眠,这是 BPF 程序的新特性。这意味着如果它们访问的用户空间地址已被换出,程序可能会引发一个小故障,因此不可能通过引用已换出的内存来规避这些钩子。
安全可观察性需要策略和背景
安全工具和报告事件的可观察行工具的区别在于,安全工具需要能够区分正常情况下的预期事件和可能正在发生恶意活动的事件。
策略不仅要考虑系统正常运行时的正常行为,还要考虑预期的错误路径行为。例如,如果物理磁盘满了,应用程序可能会开始发送网络信息,提醒用户注意。
定义什么是预期行为,什么非预期行为是策略的工作。安全工具会将活动和策略进行比较,并在活动超出策略范围,变得可疑是采取一些行动。这些行动通常包括生成安全事件日志,该日志通常会被发送到安全信息事件管理平台。可能会向人员发出警报,要求调查所发生的事件。调查的时候上下文信息越多肯定越可能找出事件的根本原因,并确定这是否是一次攻击,哪些组件受到了影响,攻击是如何产生的,何时发生的以及谁对此负责。这就需要工具从单纯的日志记录转变为名副其实的“安全可观察性”。
方法和工具
eBPF程序用于检测和执行安全事件的一些方法。
eBPF程序可以附加到各种事件上,但是多年来用于安全的一组事件是系统调用。系统调用并不是使用eBPF实现安全工具最有效的方法。
使用系统调用处理安全事件
系统调用是用户空间应用程序与内核之间的接口。如果能限制一个应用程序所能使用的系统调用,就能限制它所能做的事情。
系统调用限制工具Seccomp
seccomp 是 "安全计算(SECure COMPuting)"的缩写。在其原始或 “严格” 模式下,seccomp 用于将进程可使用的系统调用限制为一个很小的子集:
read()
、
write()
、
_exit()
和
sigreturn()
。严格模式的目的是允许用户运行不受信任的代码,但是不能让这些代码做恶意的事情。
严格模式的限制性很强,有的应用程序都需要很大的系统调用集,但这个应用程序不可能需要全部的系统调用。所以,采用这中灵活的方式来限制任何特定应用程序可以使用的调用集是有意义的。
seccomp模式没有使用其允许的固定系统调用子集,而是使用BPF代码来过滤允许和不允许的系统调用,常用在容器领域,称为seccomp-bpf更为恰当。
在 seccomp-bpf 中,加载了一组充当过滤器的 BPF 指令。每次调用系统调用时,都会触发过滤器。过滤器代码可以访问传递给系统调用的参数,以便它可以根据系统调用本身和传递给它的参数做出决策。结果是一组可能的行动之一,包括:
- 允许系统调用继续进行
- 将错误代码返回给用户空间应用程序
- 杀死线程
- 通知用户空间应用程序 (seccomp-unotify)(从内核版本 5.0 开始)
系统调用跟踪安全工具(Syscall-Tracking Security Tools)
最著名的就是Falco,他提供安全警报,在默认情况下,Falco是作为内核模块安装的,但是也有eBPF版本,用户可以制定规则来确定哪些事件和安全有关,当发生与规则中定义的策略不符的事件时,Falco可以生成各种格式的警报。
由于 eBPF 程序可以动态加载,并能检测由已有进程触发的事件,因此 Falco 等工具可将策略应用于已在运行的应用程序工作负载。用户可以修改所应用的规则集,而无需修改应用程序或其配置。这与 seccomp 配置文件形成鲜明对比,后者必须在应用程序进程启动时应用。
问题就是当eBPF程序在系统调用入口点被触发时,它可以访问用户空间传递给该系统调用参数,如果这些参数是指针,内核在对这些数据进行操作之前需要将指向的数据复制到自己的数据结构中。问题就出在eBPF程序检查数据之后,内核复制之前,攻击者是有机会修改该数据的。因此,执行系统调用的数据可能与eBPF程序捕获的数据不同。
为了确保检查的信息和内核执行的信息一致,eBPF程序应该附加到参数复制到内核内存后发生的事件。但是由于系统调用代码对数据处理方式不同,内核中没有一个共同的位置来完成这项工作。
不过有一个定义明确的接口可以安全的附加到eBPF程序,Linux安全模块(LSM)API。这就需要一个相对较新的eBPF功能:BPF LSM。
BPF LSM
这个值得好好的研究一下。
LSM接口提供了一组钩子,每个钩子都在内核即将对内核数据结构进行操作之前触发。钩子调用的函数可以决定是否允许操作继续进行。该接口最初是为了允许安全工具以内核模块的形式实现而提供的;BPF LSM对此进行扩展,以便eBPF程序可以附加到相同的挂钩点。使用 LSM BPF,eBPF 程序可由 LSM 钩子事件触发
值得一提的是,系统调用和LSM挂钩并不是一一对应的,如果某个系统调用有可能从安全角度做一些有趣的事情,那么处理该系统调就会触发一个或多个挂钩。
LSM框架介绍
LSM(linux Security Module)中文翻译就是内核安全模块,从名字上来看是安全模块,其实LSM是一个在内核各个安全模块的基础上抽象出的,轻量级安全访问控制框架。该框架只是提供一个支持安全模块的接口,本身并不具备增加系统安全性的功能,具体的工作还是交给各个安全模块来做。
通俗一点来说,LSM只是搭建了一个舞台,具体的表演还是要具体实现的模块。
LSM在内核中主要体现为一组安全相关的函数,是提供实施强制访问控制(MAC)模块的必要组件。可实现策略与内核源代码解耦,
安全函数在系统调用的执行路径中被调用,对用户态进程访问的内核资源对象进行强制访问控制,受保护的对象包括很多。
截止到内核5.7版本,一共有九个安全模块的实现,SELinux、SMACK、AppArmor、TOMOYO、Yama、LoadPin、SafeSetID 和 Lockdown 和 BPF(5.7 版本支持) 。
Seccomp和LSM都可以实现内核级别的限制进程和系统的交互方式,但是安全计算模式(seccomp)是关于限制进程可以进行的系统调用,相比之下,LSM是关于限制对内核中对象的访问。
LSM框架
LSM框架提供了一个模块化的架构,该架构提供了内置于内核的“钩子”(hook),并允许安装安全模块,从而加强访问控制。
主要是由五部分组成:
- 在关键的特定内核数据结构中加入了安全域。
- 在内核源码中不同的关键点中插入对安全钩子函数的调用。
- 提供了一个通用的安全系统调用,允许安全模块为安全相关的应用编写新的系统调用。
- 提供了从之策注销函数,使得访问控制策略可以以内核模块的方式实现,主要是通过security_add_hooks 和 security_delete_hooks;
- 将capabilities逻辑的大部分移植到一个可选的安全模块
具体实现上,LSM框架通过提供一系列的Hook,钩子函数来控制对内核对象的操作,其本质就是插桩法。
举个例子,文件open函数的访问过程。
- 通过系统调用进入内核后,系统首先进行错误检查。
- 错误检查通过之后,就是DAC检查,就是传统的权限检查吗,也就是自主访问控制。
- 通过之后才会进行强制访问控制MAC,强制访问控制是不允许主题干涉的一种访问控制,采用安全标识,信息分级等信息敏感性进行访问控制,并通过比较主题的级别和敏感性来确定是否允许访问。
LSM中的钩子函数
详细可见下面的源码分析。
LSM BPF
在LSM BPF出现之前,能够实施安全策略目标的方式主要就是两种:配置现有的安全模块,或自定义的编写内核模块。LSM BPF提供了第三种可以实现的方案,灵活且安全,并具有可编程性。
在内核5.7版本中引入了在LSM中提供对BPF的支持。使用 LSM BPF,开发人员能够在无需配置或加载内核模块的情况下编写精细策略。LSM BPF 程序会在加载时进行验证,然后在调用路径中到达 LSM hook 时执行。
详细可见下面的源码分析。
Cilium Tetragon
Tetragon是Cilium项目的一部分。主要是建立一个框架,将eBPF程序附加到Linux内核中的任意函数上,而不是附加到LSM API钩子上。主要设计与Kubernets环境,用于定义eBPF程序应附加的一组事件,eBPF代码需要检查的条件以及满足条件是应采取的行动。
和LSM BPF程序一样,Tetragon程序也会访问上下文信息,从而完全在内核中做出决定。与项用户空间报告所特定类型事件不同,安全相关事件也可以在内核中进行过滤,只是超出策略范围的事件才会向用户空间报告。
大多数的eBPF安全工具都是使用eBPF程序来检测恶意事件,这些事件会通知用户空间应用程序,然后该应用程序可以采取行动。
用户空间应用程序执行的任何操作都是异步发生的,到那时可能为时已晚——也许数据可能已被泄露,或者攻击者可能已将恶意代码持久保存到磁盘上。从内核到用户空间的异步通知允许攻击有一定的事件继续进行。
在5.3以及更高的内核版本中,有一个名为
bpf_send_signal()
的 BPF 辅助函数。Tetragon使用这个功能进行预防性安全。如果策略定义了Sigkill操作,则任何匹配事件都将导致Tetragon eBPF代码生成SIGKILL信号,该信号将终止尝试超出策略操作的进程。
也就是说,内核正在执行的,被eBPF代码判定为超出策略的活动将被阻止完成。但是配置不正确的策略可能会导致不必到的终止应用程序,刚开始可以在“审计(audit)”模式下运行,该模式会生成安全事件,但不会应用SIGKILL强制,直到认为该策列不会破坏任何东西后。
Linux5.15内核中关于安全源码的分析
eBPF验证器(Verifier)
是eBPF安全的核心,确保eBPF程序在运行之前经过静态验证以防止非法访问内存或执行不安全的操作。
intbpf_check(structbpf_prog**prog,union bpf_attr *attr,bpfptr_t uattr){
u64 start_time =ktime_get_ns();// 获取验证器开始时间,用于计算验证时间structbpf_verifier_env*env;// 定义验证器环境structbpf_verifier_log*log;// 定义日志int i, len, ret =-EINVAL;// 初始化一些变量
bool is_priv;// 检查用户是否拥有特权权限// 为 bpf_verifier_env 分配内存。它包含了程序的验证状态和日志信息
env =kzalloc(sizeof(structbpf_verifier_env), GFP_KERNEL);if(!env)return-ENOMEM;// 如果内存分配失败,返回错误
log =&env->log;// 初始化日志指针
len =(*prog)->len;// 获取程序的指令长度// 分配内存给每条指令的辅助数据,保存原始索引信息
env->insn_aux_data =vzalloc(array_size(sizeof(structbpf_insn_aux_data), len));
ret =-ENOMEM;if(!env->insn_aux_data)goto err_free_env;// 如果内存分配失败,跳转到清理部分for(i =0; i < len; i++)
env->insn_aux_data[i].orig_idx = i;// 初始化每条指令的原始索引
env->prog =*prog;// 将程序指针赋值给验证环境
env->fd_array =make_bpfptr(attr->fd_array, uattr.is_kernel);// 生成文件描述符数组指针
is_priv =bpf_capable();// 检查是否具有执行 eBPF 的权限// 初始化验证器状态mark_verifier_state_clean(env);// 处理 BPF_LD_IMM64 伪指令,这是将立即数加载到寄存器的特殊指令
ret =resolve_pseudo_ldimm64(env);if(ret <0)goto skip_full_check;// 如果处理失败,跳转到后续部分// 检查程序的控制流图,确保它是合法的
ret =check_cfg(env);if(ret <0)goto skip_full_check;// 如果控制流检查失败,跳过后续的完整检查// 验证子程序(如果有的话)模拟整个运行过程,特别是对于指针访问的每个细节进行检查
ret =do_check_subprogs(env);//!!!!!这里就是经常编写ebpf程序容易报错的地方
ret = ret ?:do_check_main(env);// 验证主程序
skip_full_check:// 释放在验证期间分配的状态表内存kvfree(env->explored_states);// 优化程序中的循环结构if(ret ==0)
ret =optimize_bpf_loop(env);// 如果用户有特权,移除程序中的死代码if(is_priv){if(ret ==0)
ret =opt_remove_dead_code(env);}else{// 如果没有特权,只清理死代码(不移除)if(ret ==0)sanitize_dead_code(env);}// 转换上下文中的特定访问,如对网络包字段的访问if(ret ==0)
ret =convert_ctx_accesses(env);// 修正函数调用参数,确保其合法性if(ret ==0)
ret =fixup_call_args(env);//将调用指令call 6 调整为真实的函数地址// 记录验证所花费的总时间
env->verification_time =ktime_get_ns()- start_time;
err_free_env:// 清理分配的验证器环境kfree(env);return ret;}
下面的代码是定义ebpf验证器的环境结构,提供跟踪和管理eBPF程序验证过程
structbpf_verifier_env{
u32 insn_idx;// 当前正在验证的 BPF 指令的索引,指向程序中当前处理的指令位置
u32 prev_insn_idx;// 上一条被处理的指令索引,用于在指令之间进行跳转时进行状态回溯。structbpf_prog*prog;// 指向正在验证的 eBPF 程序结构体。该结构体包含了 BPF 程序的字节码、程序类型和辅助信息。conststructbpf_verifier_ops*ops;// 指向 BPF 验证操作的函数指针表,根据不同的 BPF 程序类型(如网络过滤、跟踪等)调用相应的验证逻辑。structbpf_verifier_stack_elem*head;// 指向当前待处理的验证状态栈链表的头节点,栈结构存储着不同状态分支的信息。int stack_size;// 当前验证器状态栈中堆积的状态数量。用于管理并行的分支状态。
bool strict_alignment;// 用于指示是否执行严格的内存对齐检查。如果为真,程序将在某些架构上要求更严格的指针对齐。
bool test_state_freq;// 用于测试验证器在不同修剪频率下的行为,测试时可能改变状态修剪的频率以进行调试。structbpf_verifier_state*cur_state;// 当前正在处理的 BPF 验证状态,保存寄存器和栈的状态信息。structbpf_verifier_state_list**explored_states;// 已探索的状态列表,用于优化搜索过程以避免重复验证已经处理的状态。structbpf_verifier_state_list*free_list;// 空闲状态链表,用于重复利用已经释放的状态,减少内存分配的开销。structbpf_map*used_maps[MAX_USED_MAPS];// 保存 eBPF 程序中使用的 BPF map 的数组,最大数量由 `MAX_USED_MAPS` 定义。structbtf_mod_pair used_btfs[MAX_USED_BTFS];// 保存 eBPF 程序使用的 BTF(BPF Type Format)对象的数组,最大数量由 `MAX_USED_BTFS` 定义。
u32 used_map_cnt;// 当前验证过程中使用到的 BPF map 的数量。
u32 used_btf_cnt;// 当前验证过程中使用到的 BTF 对象数量。
u32 id_gen;// 用于生成唯一寄存器 ID 的计数器,每次生成新的寄存器时该值递增。
bool explore_alu_limits;// 标志是否对 ALU (算术逻辑单元) 的边界进行探索和检查,通常用于确保边界检查的安全性。
bool allow_ptr_leaks;// 是否允许指针泄露。如果为真,验证器将允许程序在某些条件下访问潜在的指针泄露。
bool allow_uninit_stack;// 是否允许未初始化的栈空间访问。若为真,则可以访问未初始化的栈,但这可能引入风险。
bool allow_ptr_to_map_access;// 是否允许直接通过指针访问 BPF map。如果为真,验证器将允许通过指针访问 map 元素。
bool bpf_capable;// 表示当前用户是否有能力执行 eBPF 程序。通常与内核权限或安全模块相关联。
bool bypass_spec_v1;// 标志是否绕过 CPU 的第一版旁路漏洞防护机制(例如 Spectre v1),用于加速验证过程。
bool bypass_spec_v4;// 标志是否绕过 CPU 的第四版旁路漏洞防护机制(例如 Spectre v4)。
bool seen_direct_write;// 记录在验证过程中是否遇到直接的内存写操作。这有助于判断程序是否尝试修改内存中的关键数据。structbpf_insn_aux_data*insn_aux_data;// 每条 BPF 指令的辅助状态数据数组,保存该指令的附加信息,例如原始索引等。conststructbpf_line_info*prev_linfo;// 保存前一条指令的调试信息(如行号、文件名等),用于生成调试日志。structbpf_verifier_log log;// 用于保存验证器输出的日志信息,日志可以包括验证失败的详细原因,帮助开发者调试。structbpf_subprog_info subprog_info[BPF_MAX_SUBPROGS +1];// 保存子程序信息的数组,BPF 程序可能包含多个子程序,这些子程序的信息存储在此数组中。structbpf_id_pair idmap_scratch[BPF_ID_MAP_SIZE];// 用于 ID 映射的临时缓存,通常在处理寄存器或状态 ID 时使用。struct{int*insn_state;// 控制流图(CFG)中每条指令的状态数组。int*insn_stack;// 控制流图中每条指令的栈状态数组。int cur_stack;// 当前指令的栈状态指针,用于跟踪栈指针的变化。} cfg;// 存储控制流图相关的状态信息,用于分析程序的控制流结构。
u32 pass_cnt;// `do_check()` 函数被调用的次数,表示验证器已经遍历指令的次数。
u32 subprog_cnt;// 当前程序中子程序的数量,BPF 程序可以有多个子程序,每个子程序都会单独验证。
u32 prev_insn_processed, insn_processed;// `insn_processed` 保存总共分析的指令数量,`prev_insn_processed` 保存之前阶段分析的数量,供增量统计使用。
u32 prev_jmps_processed, jmps_processed;// `jmps_processed` 保存总共分析的跳转、调用、退出指令数量, `prev_jmps_processed` 保存之前阶段处理的数量,供增量统计使用。
u64 verification_time;// 用于保存整个验证过程所耗费的时间,以纳秒为单位。
u32 max_states_per_insn;// 在处理分支指令时,保存最大可能存储的状态数量。这限制了状态分支的数量,防止内存消耗过大。
u32 total_states;// 保存分配的验证状态总数量,表示在整个程序验证过程中分配的状态数量,反映内存使用情况。
u32 peak_states;// 验证过程中状态的峰值数量,代表最大状态并行的规模,该值通常会影响内存消耗。
u32 longest_mark_read_walk;// 在 liveness 标记过程中,走过的最长寄存器链条。用于分析寄存器的使用情况。bpfptr_t fd_array;// 指向文件描述符数组的指针,通常在验证过程中用于处理用户空间与内核空间的交互。char type_str_buf[TYPE_STR_BUF_LEN];// 用于在调试和日志中生成寄存器类型的字符串表示,保存生成的寄存器类型描述。};
LSM Hook点
LSM 在内核中暴露了一系列 hook 点,用于监控进程、文件、网络等操作的安全策略。
LSM背后的核心概念就是LSM钩子,LSM钩子暴露在内核的关键位置,可通过挂钩进行管制的操作示例包括:
- 文件系统操作
- 打开,创建,移动和删除文件
- 挂载和卸载文件系统
- task/process operations 任务/进程操作
- 分配和释放任务,更改任务的用户组标识
- 套接字操作
- 创建和绑定套接字
- 接受发送消息
系统中 LSM 钩子都列在 Linux 内核源码的头文件 include/linux/bpf_lsm.h中查看到。不同的操作对应场景可通过头部注释获.
*union security_list_options - Linux Security Module hook function list
// 定义了 Linux Security Modules (LSM) 的钩子函数列表,供各个安全模块使用。* Security hooks for program execution operations.// 负责程序执行操作的安全钩子。用于在进程执行时进行安全检查,比如是否允许执行某个程序。* Security hooks for mount using fs_context.// 挂载文件系统的安全钩子。确保文件系统挂载时遵循权限控制,防止未经授权的文件系统挂载。* Security hooks for filesystem operations.// 文件系统操作的安全钩子。用于控制对文件系统的读写、创建和删除操作,确保这些操作符合安全策略。* Security hooks for inode operations.// inode 操作的安全钩子。控制对 inode 元数据(如文件权限、所有者等)的访问和修改,保护文件系统安全。* Security hooks for kernfs node operations
// 用于保护内核文件系统(kernfs)节点操作的安全钩子,确保系统资源管理的安全性。* Security hooks for file operations
// 文件操作钩子,控制文件的打开、关闭、读写等操作,确保进程只能执行符合安全策略的文件操作。// Security hooks for task operations.// 进程任务的安全钩子。用于监控和限制进程的行为,防止进程之间进行不当的交互或访问系统资源。* Security hooks for Netlink messaging.// Netlink 消息传递的安全钩子。控制进程间通信,防止未经授权的消息传递或系统配置修改。* Security hooks for Unix domain networking.// Unix 域套接字操作的安全钩子。用于本地进程间通信的控制,确保进程间通信符合安全策略。* Security hooks for socket operations.// 套接字操作的安全钩子。用于控制网络连接和数据传输的安全策略,防止未经授权的网络访问。* Security hooks for SCTP
// SCTP(流控制传输协议)操作的安全钩子。用于保护基于 SCTP 协议的网络通信,防止网络层面的攻击。* Security hooks for Infiniband
// Infiniband 操作的安全钩子。用于保护高性能网络通信(如 Infiniband)的安全性,防止未授权的操作。* Security hooks for XFRM operations.// XFRM(IPsec 扩展框架)操作的安全钩子。确保加密和解密操作符合系统安全策略,保护网络流量安全。* Security hooks affecting all Key Management operations
// 密钥管理操作的安全钩子。控制密钥的创建、修改和删除,确保加密密钥的安全使用。* Security hooks for individual messages held in System V IPC message queues
// 用于控制 System V IPC 消息队列中的每个消息的安全钩子,确保进程间通信的安全性。* Security hooks for System V IPC Message Queues
// System V 消息队列操作的安全钩子。用于保护进程间通过消息队列进行的通信,防止信息泄露。* Security hooks for System V Shared Memory Segments
// System V 共享内存段的安全钩子。用于控制进程对共享内存的访问,确保共享内存的安全使用。* Security hooks for System V Semaphores
// System V 信号量操作的安全钩子。控制进程对信号量的操作,确保进程同步操作符合系统的安全策略。* Security hooks for Audit
// 审计系统操作的安全钩子。用于生成和控制审计日志,确保安全事件被正确记录。* Security hooks for the general notification queue:// 通用通知队列操作的安全钩子。确保进程对通知队列的访问和使用符合安全策略。* Security hooks for using the eBPF maps and programs functionalities through eBPF syscalls.// eBPF 映射和程序操作的安全钩子。用于控制 eBPF 程序的加载和执行,防止未经授权的 eBPF 使用。* Security hooks for perf events
// 性能事件的安全钩子。控制性能监控事件,防止未授权的进程获取系统性能数据。* Security hooks for io_uring
// io_uring 异步 I/O 操作的安全钩子。用于控制 I/O 操作的安全性,确保异步 I/O 操作不被滥用。
每个定义的钩子函数都包含对应的参数,这些参数根据哪些程序可以实施策略决策提供上下文。在5.15内核中一共有55个,在6.10内核中增加到了247个。
其中 LSM_HOOK 定义格式如下:
LSM_HOOK(<return_type>,<default_value>,<hook_name>, args...)
LSM提供的大多数钩子都会返回一个整数值(也有部分返void,标识忽略运行结果),返回值的定义如下:
- 0等于同意授权;
- ENOMEM无可用内存;
- EACCESS,安全策略拒绝访问;
- EPERM,执行此操作需要权限。
LSM BPF
在5.15内核中,BPF自身安全相关的LSM hook函数有7个,在内核security/security.c 中查看通过编译条件宏 CONFIG_BPF_SYSCALL 控制,主要设涉及BPF 系统调用、BPF 程序和 BPF map 相关操作:
#ifdefCONFIG_BPF_SYSCALL// 如果内核配置启用了 BPF 系统调用支持(CONFIG_BPF_SYSCALL),则定义以下钩子// 用于控制 BPF 系统调用的安全钩子。// `cmd` 表示系统调用的命令类型,`attr` 是 BPF 的参数,`size` 是参数大小。LSM_HOOK(int,0, bpf,int cmd,union bpf_attr *attr,unsignedint size)// 用于控制 BPF 映射的安全钩子。// `map` 是 BPF 映射对象,`fmode` 是文件操作模式(如只读或可写)。LSM_HOOK(int,0, bpf_map,structbpf_map*map,fmode_t fmode)// 用于控制 BPF 程序的安全钩子。// `prog` 是 BPF 程序对象,通过该钩子可以验证 BPF 程序是否符合安全策略。LSM_HOOK(int,0, bpf_prog,structbpf_prog*prog)// 用于在分配 BPF 映射时执行安全检查的钩子。// 该钩子可以确保在分配 BPF 映射时,分配过程符合系统的安全策略。LSM_HOOK(int,0, bpf_map_alloc_security,structbpf_map*map)// 用于在释放 BPF 映射时执行安全处理的钩子。// `map` 是要释放的 BPF 映射,通过该钩子可清理与映射相关的安全资源。LSM_HOOK(void, LSM_RET_VOID, bpf_map_free_security,structbpf_map*map)// 用于在分配 BPF 程序时执行安全检查的钩子。// `aux` 是 BPF 程序的辅助信息,钩子确保 BPF 程序的分配过程符合安全要求。LSM_HOOK(int,0, bpf_prog_alloc_security,structbpf_prog_aux*aux)// 用于在释放 BPF 程序时执行安全处理的钩子。// `aux` 是与 BPF 程序关联的辅助信息,钩子用于释放与 BPF 程序相关的安全资源。LSM_HOOK(void, LSM_RET_VOID, bpf_prog_free_security,structbpf_prog_aux*aux)#endif/* CONFIG_BPF_SYSCALL */
每个钩子针对特定的BPF操作提供了对应的安全见检查和处理机制,确保只有符合安全策略的BPF操作被允许执行。
版权归原作者 Super-Lzzx 所有, 如有侵权,请联系我们删除。