CVE-2020-8835: Linux Kernel BPF 验证器寄存器范围计算漏洞分析与复现
漏洞概述
CVE-2020-8835 是 Linux 内核中的一个权限提升漏洞,由 Manfred Paul 发现并在 Pwn2Own 2020 比赛中演示。该漏洞存在于 Linux 内核的 BPF (Berkeley Packet Filter) 子系统中,具体是 BPF 验证器未能正确计算某些特定操作的寄存器范围,导致寄存器边界计算不正确,进而引发越界读取和写入。
受影响版本
该漏洞在 Linux 内核 commit 581738a681b6 中引入,影响以下内核版本:
- 5.4.0 至 5.5.0 之间的版本
- 特定配置下可能影响更早版本
漏洞背景
BPF 子系统简介
BPF 是 Linux 内核中的一个虚拟机,最初设计用于高效网络数据包过滤,后来扩展为通用执行引擎,可用于多种内核功能如:
- 网络数据包过滤
- 系统调用过滤
- 性能分析
- 安全监控
BPF 程序运行在内核空间,因此安全性至关重要。BPF 验证器负责确保 BPF 程序不会危害系统安全。
BPF 验证器的作用
BPF 验证器的主要职责包括:
- 确保程序不会崩溃或损坏内核
- 确保程序会终止(无无限循环)
- 验证所有内存访问的有效性
- 验证寄存器使用和值范围
漏洞技术细节
根本原因
漏洞的根本原因在于 BPF 验证器未能正确计算某些特定操作的寄存器范围,特别是当处理 32 位子寄存器时。验证器错误地假设了某些操作的寄存器边界,导致可以构造特殊的 BPF 程序绕过验证器的检查。
具体问题
在 adjust_scalar_min_max_vals() 函数中,当处理 BPF_RSH(右移)操作时,验证器错误地保留了源寄存器的最小值,而没有考虑右移操作实际上会减小该值。这导致验证器对寄存器值的范围计算不正确。
利用原理
攻击者可以构造一个 BPF 程序:
- 通过精心设计的右移操作使验证器错误计算寄存器范围
- 利用这个错误的范围计算绕过数组边界检查
- 实现越界内存访问
- 通过修改内核数据结构提升权限
漏洞复现
环境准备
需要准备以下环境:
- 受影响的内核版本(如 5.4.0 至 5.5.0)
- 普通用户权限
- BPF 相关功能启用(CONFIG_BPF_SYSCALL=y)
复现步骤
- 编译漏洞利用代码(通常是一个精心构造的 BPF 程序)
- 以普通用户身份运行利用程序
- 验证是否获得 root 权限
示例利用代码
#include <linux/bpf.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <errno.h>
int bpf(enum bpf_cmd cmd, union bpf_attr *attr, unsigned int size) {
return syscall(__NR_bpf, cmd, attr, size);
}
void trigger_vuln() {
union bpf_attr attr = {
.prog_type = BPF_PROG_TYPE_SOCKET_FILTER,
.insn_cnt = 10,
.insns = (unsigned long)vuln_prog,
.license = (unsigned long)"GPL",
};
int fd = bpf(BPF_PROG_LOAD, &attr, sizeof(attr));
if (fd < 0) {
perror("bpf");
exit(1);
}
close(fd);
}
int main() {
trigger_vuln();
return 0;
}
注意:实际利用代码需要包含精心构造的 BPF 指令序列,这里仅为框架示例。
漏洞修复
官方修复
该漏洞在 Linux 内核 commit 32bf09a4 中修复,主要修改了 adjust_scalar_min_max_vals() 函数中处理右移操作的逻辑。
修复关键点
- 正确处理右移操作后的寄存器最小值
- 确保范围计算考虑操作的实际效果
- 添加额外的边界检查
缓解措施
如果无法立即更新内核,可考虑以下缓解措施:
- 限制非特权用户的 BPF 使用:
sysctl kernel.unprivileged_bpf_disabled=1 - 使用 seccomp 过滤 bpf 系统调用
- 禁用 BPF JIT(CONFIG_BPF_JIT_ALWAYS_OFF)
漏洞影响评估
影响范围
- 本地权限提升
- 从普通用户提升到 root 权限
- 需要 CAP_BPF 或非特权 BPF 启用
利用复杂度
- 需要深入理解 BPF 验证器工作原理
- 需要精确构造 BPF 指令序列
- 利用成功率较高
深入技术分析
BPF 验证器寄存器跟踪机制
BPF 验证器使用抽象解释技术跟踪寄存器的可能值范围。对于每个寄存器,验证器维护:
- 64 位有符号最小值 (smin_value)
- 64 位有符号最大值 (smax_value)
- 64 位无符号最小值 (umin_value)
- 64 位无符号最大值 (umax_value)
漏洞触发路径
- 验证器处理 BPF_RSH 操作时调用
adjust_scalar_min_max_vals() - 对于源寄存器的最小值,错误地保留了原始值
- 导致后续边界检查基于错误的范围计算
- 允许越界内存访问
寄存器状态污染
漏洞允许攻击者"污染"寄存器状态,使验证器认为寄存器值在安全范围内,而实际执行时可能超出该范围。
防御建议
- 及时更新内核到修复版本
- 遵循最小权限原则,限制非特权 BPF 使用
- 监控和审计 BPF 程序加载行为
- 考虑使用内核安全模块如 SELinux 限制 BPF 功能
总结
CVE-2020-8835 是一个典型的 BPF 验证器逻辑漏洞,展示了即使精心设计的安全机制也可能因微妙的逻辑错误而失效。该漏洞强调了:
- 内核子系统安全验证的重要性
- 边界条件处理的复杂性
- 权限提升攻击面的多样性
理解此类漏洞有助于更好地设计安全验证机制和防御策略。