深入分析 Seccomp BPF
2025-10-23
| 2025-10-23
字数 7019阅读时长 18 分钟
type
status
date
slug
summary
tags
category
icon
password
Property
Oct 23, 2025 12:16 PM

概述

Seccomp (Secure Computing) 是 Linux 内核提供的一种安全机制,允许进程限制自己可以执行的系统调用。Seccomp Mode 2 (SECCOMP_MODE_FILTER) 使用 BPF (Berkeley Packet Filter) 程序实现灵活的系统调用过滤。但是不同于 eBPF,Seccomp BPF 使用 cBPF,对比 eBPF 仅保留了部分指令可以使用。
Seccomp BPF 的安装和验证的主要流程如下:
安装
  1. 用户空间通过 prctl 提交 BPF 程序
  1. 内核验证权限(no_new_privsCAP_SYS_ADMIN
  1. 从用户空间安全复制 BPF 指令
  1. 执行严格的 BPF 验证(基本检查 → 经典检查 → seccomp 特定检查)
  1. 转换为 eBPF 格式并尝试 JIT 编译
  1. 构建系统调用缓存以优化性能
  1. 附加到进程的过滤器链
  1. 设置 TIF_SECCOMP 标志启用检查
验证
  1. 系统调用入口检测 TIF_SECCOMP 标志
  1. 填充 seccomp_data 结构(系统调用号、参数、架构等)
  1. 首先检查缓存(快速路径)
  1. 遍历过滤器链,执行每个 BPF 程序
  1. 选择最严格的返回动作
  1. 根据动作类型处理(允许、拒绝、跟踪、杀死等)
  1. 记录审计日志(如果配置)
具体源码分析见下文

安装流程

1. 用户空间接口

用户通过 prctl 系统调用安装 seccomp 过滤器:

2. 内核调用链

2.1 入口函数

该函数是 prctl(PR_SET_SECCOMP, ...) 的内核入口点,负责: - 根据 seccomp_mode 确定操作类型(STRICT 或 FILTER) - 将参数转换为内部格式 - 调用 do_seccomp() 执行实际操作
关键代码流程:

2.2 统一入口处理

该函数是 prctlseccomp 系统调用的统一处理入口: - SECCOMP_SET_MODE_STRICT: 调用 seccomp_set_mode_strict() - SECCOMP_SET_MODE_FILTER: 调用 seccomp_set_mode_filter() - SECCOMP_GET_ACTION_AVAIL: 查询动作是否可用 - SECCOMP_GET_NOTIF_SIZES: 获取通知结构大小

2.3 Filter 模式设置

这是安装 BPF 过滤器的核心函数,执行以下步骤:
步骤 1:验证 flags
支持的 flags 包括: - SECCOMP_FILTER_FLAG_TSYNC: 同步所有线程 - SECCOMP_FILTER_FLAG_LOG: 记录所有非 ALLOW 动作 - SECCOMP_FILTER_FLAG_SPEC_ALLOW: 允许规避推测执行缓解 - SECCOMP_FILTER_FLAG_NEW_LISTENER: 创建用户态通知监听器
步骤 2:准备过滤器
步骤 3:获取锁
步骤 4:检查并附加过滤器
步骤 5:设置 seccomp 模式

3. 过滤器准备

3.1 从用户空间复制过滤器

该函数处理用户空间到内核空间的过滤器转换:
处理兼容模式:
调用核心准备函数:

3.2 核心过滤器准备

权限检查:
必须满足以下条件之一: - 进程设置了 no_new_privs 标志 - 进程具有 CAP_SYS_ADMIN 能力
分配 seccomp_filter 结构:
创建 BPF 程序:
这里调用了 net/core/filter.c 中的函数,seccomp_check_filter 作为转换函数传递。
初始化引用计数:

4. BPF 程序创建与验证

4.1 从用户空间创建 BPF 程序

验证基本参数:
分配 BPF 程序结构:
从用户空间复制指令:
保存原始程序(用于检查点恢复):
准备并验证过滤器:

4.2 准备 BPF 过滤器

检查经典 BPF 指令:
这一步验证: - 指令长度不超过 BPF_MAXINSNS - 没有越界跳转 - 内存访问合法 - 不使用除零操作
执行 seccomp 特定转换:

4.3 Seccomp 特定检查

该函数对 seccomp BPF 程序进行特殊处理:
重定向数据加载指令:
BPF_LD 改为 BPF_LDX 以确保从 seccomp_data 结构加载数据,而不是网络包数据。
替换长度加载:
允许的指令白名单: 只允许安全的 ALU、跳转、内存操作和返回指令,拒绝其他所有指令。
JIT 编译:
如果架构支持,将 BPF 程序编译为原生机器码以提高性能。
解释器迁移:
如果无法 JIT 编译,将经典 BPF (cBPF) 转换为扩展 BPF (eBPF) 供解释器使用。

5. 附加过滤器

验证过滤器总长度:
限制:MAX_INSNS_PER_PATH = (1 << 18) / sizeof(struct sock_filter) = 256KB
链接过滤器:
过滤器以链表形式组织,新过滤器总是添加到链表头部。
线程同步(如果需要):

6. 设置 Seccomp 模式

设置模式:
内存屏障:
启用推测执行缓解:
设置 TIF_SECCOMP 标志:
此标志导致内核在每次系统调用时检查 seccomp 过滤器。

验证执行流程

1. 系统调用拦截

当进程执行系统调用时,如果设置了 TIF_SECCOMP 标志,内核会在系统调用处理前调用 seccomp 检查。
架构相关入口:

2. Seccomp 检查入口

检查暂停标志:
获取系统调用号:
根据模式分发:

3. 过滤器执行

3.1 填充 seccomp_data

3.2 运行过滤器链

获取过滤器链:
检查缓存:
系统调用缓存优化:如果某个系统调用对所有过滤器都返回 ALLOW,将其缓存以避免重复执行 BPF 程序。
遍历过滤器链:
优先级规则: - 数值越小,优先级越高(更严格) - SECCOMP_RET_KILL_PROCESS < SECCOMP_RET_KILL_THREAD < SECCOMP_RET_TRAP < … < SECCOMP_RET_ALLOW

3.3 BPF 程序执行

BPF 程序通过以下方式之一执行:
  1. JIT 编译的原生代码(如果 fp->jited == true
  1. eBPF 解释器(如果未 JIT 编译)
BPF 程序接收 seccomp_data 结构作为输入,返回一个 32 位动作值。

4. 动作处理

根据过滤器返回值执行相应动作:

4.1 SECCOMP_RET_ERRNO

4.2 SECCOMP_RET_TRAP

发送的信号信息:

4.3 SECCOMP_RET_TRACE

4.4 SECCOMP_RET_USER_NOTIF

用户态通知机制: 1. 将系统调用信息发送给用户态监督进程 2. 阻塞当前进程等待响应 3. 根据用户态响应决定如何处理系统调用

4.5 SECCOMP_RET_LOG

4.6 SECCOMP_RET_ALLOW

4.7 SECCOMP_RET_KILL_THREAD / SECCOMP_RET_KILL_PROCESS

5. 日志记录

根据 /proc/sys/kernel/seccomp/actions_logged 配置决定是否记录:
审计日志包含: - 系统调用号 - 信号(如果有) - Seccomp 动作 - 进程 PID、UID 等

关键数据结构

1. seccomp_filter

生命周期: - refs: 包括直接任务、依赖过滤器、用户通知监听器 - users: 只包括直接关联的任务 - 当 users 到达 0 时,不能再有新任务关联 - 当 refs 到达 0 时,释放过滤器

2. seccomp_data

这是传递给 BPF 程序的数据结构,大小为 64 字节。

3. task_struct.seccomp

4. action_cache

缓存构建(源码:kernel/seccomp.c:825):
对每个系统调用号: 1. 用固定的 nrarch 模拟执行 BPF 程序 2. 如果返回 SECCOMP_RET_ALLOW,在位图中设置对应位 3. 继承前一个过滤器的缓存(新过滤器只能更严格)
缓存使用:

BPF 程序处理

1. cBPF 到 eBPF 转换

转换过程: 1. 第一遍:计算转换后长度 2. 第二遍:实际转换指令并计算跳转偏移 3. 第三遍(如需要):调整跳转偏移
指令映射示例:

2. JIT 编译

启用条件: - 内核配置 CONFIG_BPF_JIT=y - 架构支持(x86_64, ARM64, etc.) - /proc/sys/net/core/bpf_jit_enable 设置
优势: - 原生机器码执行,性能提升 2-4 倍 - 减少指令分发开销
缺点: - 增加内核攻击面 - 消耗更多内存

3. 解释器执行

如果无法 JIT 编译,使用 eBPF 解释器:

安全机制

1. 验证器检查

基本检查 (bpf_check_basics_ok): - 程序不为空 - 最后一条指令是 RET - 没有无效指令
经典 BPF 检查 (bpf_check_classic): - 没有越界跳转 - 没有后向跳转(防止循环) - 内存访问合法 - 除数不为零检查
Seccomp 特定检查 (seccomp_check_filter): - 只允许白名单中的指令 - 数据访问限制为 seccomp_data 结构 - 强制 4 字节对齐访问

2. 长度限制

限制总指令数为 256KB,防止: - DoS 攻击(过长的执行时间) - 内存耗尽

3. 权限要求

安装 seccomp 过滤器需要:
no_new_privs 标志: - 通过 prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) 设置 - 禁止进程及其子进程获得新权限 - 防止特权提升攻击

4. 单向门机制

一旦设置 seccomp 模式,无法更改或移除,只能添加更严格的过滤器。

5. 内存屏障

确保多核系统中的内存一致性。

6. 推测执行缓解

默认启用推测执行缓解(如 SSBD),防止 Spectre 类攻击。

深入 Seccomp User Notification 机制

Seccomp 用户态通知 (User Notification) 允许用户空间进程拦截和处理被 seccomp 过滤的系统调用。与传统的 SECCOMP_RET_ERRNOSECCOMP_RET_KILL 不同,SECCOMP_RET_USER_NOTIF 可以将系统调用决策权委托给用户空间的监督进程。被过滤的系统调用会阻塞,等待用户空间监督进程的决策,并将系统调用号、参数、架构等信息传递给用户空间。监督进程可以返回错误码、返回值,或允许系统调用继续执行,也支持通过 SECCOMP_IOCTL_NOTIF_ADDFD 向被监督进程注入文件描述符。

整体架构

notion image
关键数据结构:

核心数据结构

1. 内核通知对象 (seccomp_knotif)

状态机转换
  • INIT: 通知已创建,等待监督进程读取
  • SENT: 监督进程已读取,等待响应
  • REPLIED: 监督进程已响应,可以唤醒被监督进程

2. 通知容器 (struct notification)

设计要点
  • 大多数 seccomp 过滤器不使用通知,因此单独分配此结构以节省内存
  • request 信号量:初始值为 0,每个新通知 up(),读取时 down()
  • next_id:随机初始化防止 ID 预测攻击

3. FD 注入对象 (seccomp_kaddfd)

4. 用户空间接口结构

seccomp_notif (通知请求)

seccomp_notif_resp (通知响应)

标志位
  • SECCOMP_USER_NOTIF_FLAG_CONTINUE (0x1):允许系统调用继续执行(有 TOCTOU 风险)

seccomp_notif_addfd (FD 注入请求)

完整工作流程

阶段 1:Listener FD 创建

1.1 安装带 NEW_LISTENER 标志的过滤器

用户空间代码:

1.2 内核处理流程

1.3 init_listener 函数

seccomp_notify_ops 文件操作:

阶段 2:系统调用拦截与通知

2.1 触发 SECCOMP_RET_USER_NOTIF

被监督进程执行系统调用 → 匹配 BPF 过滤器 → 返回 SECCOMP_RET_USER_NOTIF

2.2 seccomp_do_user_notification 详解

这是用户态通知的核心函数,被监督进程的系统调用会在此阻塞。
关键点
  1. 通知对象在栈上seccomp_knotif n 在被监督进程的内核栈上分配,生命周期与系统调用相同
  1. 数据指针有效性n.data 指向 __seccomp_filtersd,在整个通知期间有效
  1. 多次唤醒do-while 循环处理 addfd 请求,每次注入 FD 都会唤醒一次
  1. 信号中断:如果被监督进程收到信号,wait_for_completion_interruptible 返回错误

阶段 3:监督进程读取通知

3.1 等待通知 (poll/epoll)

监督进程使用 pollepoll 监听 listener fd:

3.2 读取通知 (ioctl NOTIF_RECV)

状态转换INIT → SENT

阶段 4:监督进程发送响应

4.1 发送响应 (ioctl NOTIF_SEND)

状态转换SENT → REPLIED

4.2 验证通知有效性 (ioctl NOTIF_ID_VALID)

监督进程在处理通知前可以验证通知是否仍然有效(被监督进程可能已收到信号退出):

阶段 5:FD 注入机制

5.1 使用场景

FD 注入允许监督进程将自己的文件描述符"传递"给被监督进程,典型场景:
  • 容器运行时代理设备访问:容器进程 open("/dev/fuse") → 监督进程打开真实设备 → 注入 fd
  • 网络代理:容器进程 socket() → 监督进程创建 socket 并配置 → 注入 fd

5.2 注入流程 (ioctl NOTIF_ADDFD)

5.3 被监督进程处理 addfd

seccomp_do_user_notification 的循环中调用:
双向等待机制
  1. 监督进程在 seccomp_notify_addfd 中等待 kaddfd.completion
  1. 被监督进程在 seccomp_do_user_notification 中被唤醒,调用 seccomp_handle_addfd 安装 fd 并 complete()

阶段 6:清理与关闭

6.1 Listener 关闭

当监督进程关闭 listener fd 时:

Android 平台的 Seccomp 使用

在 Android 中,Seccomp 是多层安全机制的一部分,搭配 SELinux、命名空间隔离、UID/GID 沙箱、权限模型 等共同构建应用安全边界。
Android 从 Android 8.0 开始引入了对 App 进程的 Seccomp 过滤器
设计目标是:
  1. 减少应用可访问的系统调用数量;
  1. 阻止利用内核漏洞的攻击面;
  1. 对特权进程(如 zygote 或 system_server)使用更严格的策略。
参考 bionic/libc/seccom/seccomp_policy.cpp Android 的 Seccomp 机制分为4个类型:
进程类型
过滤器类型
主要作用
普通 App 进程
App filter
限定 App 允许的 syscall 集合
App Zygote 进程
App Zygote filter
更严格,只允许必要的 syscall
System 进程
System filter
系统进程的特定白名单
UID/GID 改变辅助过滤器
SetUid/Gid filter
限制 setresuid/setresgid 参数范围
在 android 系统中,最终可用的 SYSCALL 是由 SYSCALL - BLOCKLIST + ALLOWLIST 组成的,同时会参考 SECCOMP_PRIORITY.TXT,把高频 syscall 放在 BPF 判断的“快路径”前面,以减少匹配开销。
具体的这些文件也可以在 bionic 源码中找到:bionic/libc/
具体的计算逻辑可以参考 genseccomp.py
 
  • Kernel
  • Linux
  • 博客重建解决爱思助手导致的 Mac 锁屏不熄屏问题
    Loading...
    目录
    0%