大纲

usage: ceccomp <asm|disasm|emu|trace|probe|version|help> [FILE] [-q|--quiet]
               [-f|--format FMT] [-a|--arch ARCH] [-p|--pid PID] [-s|--seize]
               [-o|--output FILE] [-c|--color WHEN] ...

概念

内核使用BPF过滤器来限制系统调用规则,并使用 seccompprctl 两个系统调用来安装过滤器。以下是一个以十六进制表示的限制 execve 系统调用的简单过滤器:

1: 20 00 00 00 00 00 00 00     $A = $syscall_nr
2: 15 00 00 01 3b 00 00 00     if ($A != execve) goto 4
3: 06 00 00 00 00 00 00 00     return KILL
4: 06 00 00 00 00 00 ff 7f     return ALLOW

以上十六进制的部分就是内核收到的过滤器,而 ceccomp 负责把它拿来反汇编为人类可读的文本。 例如左侧的 行号 和右侧的 伪代码

Important
之后我会使用 TEXT 作为BPF过滤器人类可读文本(伪代码)的缩写, 使用 RAW 作为BPF过滤器原始格式的缩写,请记住这个约定。

描述

ceccomp 有5个主要的功能,它基本上是C版本的 seccomp-tools ,然而, 有一些不同的地方你需要知道,它们会在在每个子命令的章节中被注明。

asm - 汇编

ceccomp asm [-c WHEN] [-a ARCH] [-f FMT] [TEXT]

TEXT 汇编为 RAW 。适用于将手写的过滤器规则嵌入到C代码中, 或希望观察一些 TEXT 对应的原始字节码。

-c, --color WHEN

决定了 ceccomp 何时输出有颜色的文本。当设置为 auto 时, ceccomp 会在输出目标是一个“tty”时打印颜色。可以是 autonever 或者 always 。 默认值是 auto

-a, --arch ARCH

可以设置为任何 libseccomp 支持的架构。它将被用于决定系统调用名称对应的系统调用号。 例如,在 x86_64 上,就像上面的基本示例,你可以写 "execve" 而不是数字 59 指代系统调用号。如果不设置这个参数,则通过 uname 提取当前系统的架构。 你的系统上的默认值是 aarch64 。

-f, --format FMT

决定了 ceccomp 如何输出二进制格式的 BPF 字节码。可以是 hexfmthexlineraw 或者 c-macro。你可以在 ceccomp 示例 节中找到示例输出。默认值是 hexline

TEXT

一个可选的文件名,其中存放了需要被汇编的 TEXT 。不设置则从 标准输入 中读取。- 被视作 标准输入

Important
4.0 版本 大幅修改了汇编的语法,请查阅下面的语法参考!

查看 TEXT 语法参考 一节可以找到如何手写规则。一些示例会在 ceccomp 示例 一节中展示。

Note
4.0 版本 开始考虑端序。如果目标架构 ARCH 的端序与机器端序不同,则会在输出前反转过滤器(CODE 和 K)。
Note
4.3 版本 开始,新的 FMT c-macro 被加入用以轻松将 seccomp 规则移植到其他架构上。
命令 差别

seccomp-tools asm

使用它自己的语法汇编,有点像脚本;可以汇编会被内核拒绝的错误 TEXT

ceccomp asm

你可以直接拿着 disasm 的输出来汇编,不需要学习新语法;默认使用 标准输入 作为输入

disasm - 反汇编

ceccomp disasm [-c WHEN] [-a ARCH] [-e] [RAW]

反汇编 RAWTEXT 。适用于当你无法使用 trace 看到过滤器时,必须手动提取过滤器, 然后检查其含义。

-c, --color WHEN

参数描述可以在 asm - 汇编 一节中找到。 disasm 可能会打印更多有颜色的文本,包括针对 TEXT 的语法高亮。

-a, --arch ARCH

可以设置为任何 libseccomp 支持的架构。它将被用于决定 RAW 中的系统调用号如何被翻译为系统调用名。 例如,在 x86_64 上,在比较系统调用号时,数字 0x3b 将被翻译为 execve ,可以看上面的基本示例。 你的系统上的默认值是 aarch64 。

-e, --ebpf

认为 RAW 是 eBPF 指令字节而非 cBPF 过滤器字节。这个标志被设计用于测试 eBPF 到 cBPF 的转换是否正常工作。要使用这个功能,请使用 eBPF 或内核模块导出进程内安装的从 seccomp 过滤器转换过来的 eBPF 指令。任何不是由内核生成的输入都可能产生未定义行为。

RAW

包含原始 BPF 代码的二进制文件。如果不设置则将 标准输入 作为输入。- 被视作 标准输入。这个文件是与架构有关的,所以它在不同架构之间可能不是通用的。

Note
4.0 版本 开始考虑端序。如果目标架构 ARCH 的端序与机器端序不同,则会在解码前反转过滤器(CODE 和 K)。
Note
当且仅当在某一行的架构可以被确定时,ceccomp 才会尝试用那个架构解析那个系统调用号。 如果是外部架构(不等于你设置的架构),它会被附加到系统调用名前。你可能会注意到在一些情况下, seccomp-tools 能解析一些系统调用名,而 ceccomp 不能,这可能是因为此时架构不能被确定。
命令 差别

seccomp-tools disasm

用它自己的语法反汇编;永远不会检查 RAW 是否合法

ceccomp disasm

用 ceccomp 语法反汇编,并且默认将 标准输入 作为输入;严格检查架构, 并且永远打印外部架构名

emu - 模拟

ceccomp emu [-c WHEN] [-a ARCH] [-q] TEXT SYSCALL_NAME/SYSCALL_NR [ARGS[0] ARGS[1] ... ARGS[5] PC]

按照 TEXT 中描述的规则,模拟从 PC 调用 syscall(SYSCALL_NR, ARGS[0], ARGS[1], …​, ARGS[5]) 的结果。适用于在不实际运行程序或不想手动检查规则时,查看触发系统调用的结果。 这个子命令适合用来自动检测一个过滤器。

-c, --color WHEN

参数描述可以在 asm - 汇编 一节中找到。 emu 可能会打印更多有颜色的文本,包括针对 TEXT 的语法高亮以及跳过的伪代码。

SYSCALL_NAME/SYSCALL_NR

如果你设置了 SYSCALL_NAME (比如 execve ),那么它会基于 ARCH 先被翻译为对应的 SYSCALL_NR 。或者你可以直接设置 SYSCALL_NR (例如 59 )。然后会测试这个系统调用号经过 BPF 过滤器处理后的输出并打印出来。这个参数是必填的。

ARGS[0-5]PC

当调用系统调用时对应寄存器的值。例如,在 x86_64 上,它们分别对应了 rdirsirdxr10r8r9rip 。它们的默认值都是0。

-a, --arch ARCH

参数描述可以在 asm - 汇编 一节中找到。

TEXT

一个文件名,其中存放了需要被测试的 TEXT 规则。注意文件名 不能 被忽略,因为 ceccomp 无法判断一个位置参数是系统调用号还是文件名。使用 - 可以指代 标准输入

-q, --quiet

只打印过滤器的模拟结果。例如,最后一行模拟的伪代码是 return KILL,那么将会打印 KILL

命令 差别

seccomp-tools emu

RAW 作为输入

ceccomp emu

TEXT 作为输入,并默认将 标准输入 作为输入;可以设置 PC

trace - 运行时捕获过滤器

ceccomp trace [-c WHEN] [-o FILE] PROGRAM [program-args]
              [-c WHEN] -p PID [-s]

使用第一行的命令可以利用调试在 PROGRAM 运行中加载过滤器时动态捕获过滤器; 第二行的命令可以从 PID 对应的进程中提取出 seccomp 过滤器,或通过调试 PID 捕获后续的 seccomp 过滤器;一旦捕获到了过滤器, 将会以 TEXT 的格式将它打印出来。你可以从两个格式中选择一个使用。 适用于运行一个程序是捕获BPF过滤器最简单的方式或者一个安装了 seccomp 过滤器的程序正在等待输入。

-c, --color WHEN

参数描述可以在 asm - 汇编 一节中找到。 trace 可能会打印更多有颜色的文本,包括针对 TEXT 的语法高亮。

-o, --output FILE

PROGRAM 会产生很多输出到 标准错误 时可能很有用。 ceccomp 允许用户关闭 标准输入标准输出 来限制 PROGRAM 的输入和输出,所以 当运行 PROGRAMceccomp 使用 标准错误 来打印消息。如果你想在别的文件中看见 TEXT 的话请设置 FILE- 被视作 标准输出

PROGRAM

设置为你想运行的程序,并且 program-args 将作为它的参数, 就像运行 shell 命令 exec PROGRAM program-args

-p, --pid PID

设置为你想检查的 pid。 PIDPROGRAM 相冲突;你只能在一条命令中动态运行一个程序,或者检查一个 pid。没有 -s 标志,trace pid 会尝试使用 ptrace(PTRACE_SECCOMP_GET_FILTER)PID 中提取 seccomp 过滤器,这个操作在一些系统上可能不可用。这里的 pid 其实是进程(线程)ID,你可以追踪一个线程或进程。

-s, --seize

只适用于 TRACE PID 模式。 设置这个标志将会覆盖 trace pid 的行为为像 trace prog 模式一样把调试器挂到 PID 上并持续跟踪 seccomp 过滤器的加载。这个标志引入于 4.0 版本。

-q, --quiet

设置这个标志将会在检测到进程分叉、退出或加载 seccomp 过滤器时抑制多余的 [INFO] 输出。 这个标志引入于 4.0 版本。

Note
要想从 PID 中提取过滤器,你需要 CAP_SYS_ADMIN (没有 -s 标志),同时还可能需要 CAP_SYS_PTRACE ,最简单的获取它们的方法是用 sudo 运行 ceccomp
Note
3.1 版本 开始引入了多进程支持,并且当被调试进程 fork/resolve/exit 时,将会打印一条额外的 INFO 信息。你可以使用像 ceccomp trace -q PROG 2>/dev/null 这样的命令丢弃它。
Note
4.2 版本 开始 dump 子命令作为 trace 的别名被加入,用以保持和 seccomp-tools 的兼容性。
命令 差别

seccomp-tools dump

可以设置输出格式;每一个过滤器可以输出到不同的文件;当 PROGRAM 加载了 LIMIT 个过滤器后就杀死程序;将 PROGRAM 包装在 sh -c 中运行

ceccomp tracececcomp dump

所有过滤器被输出到同一个文件;永远不会杀死 PROGRAMPROGRAM 是直接被执行的, 所以不需要 ./ ;当 fork 时,显式打印事件;能够附加调试器到 pid 上动态捕捉 seccomp 过滤器

probe - 快速测试常见的系统调用

ceccomp probe [-c WHEN] [-o FILE] [-q] PROGRAM [program-args]

program-args 为参数运行 PROGRAM 来捕获 第一个 seccomp 过滤器, 然后杀死所有子进程。适用于快速测试一个程序的规则并检测潜在的 seccomp 规则问题。

所有参数描述都可以在 trace - 运行时捕获过滤器 一节中找到。

这个子命令的输出是一系列常见的系统调用的模拟结果,例如 execveopen 等。 如果过滤器本身并不能阻拦系统调用,那你一眼就能看出来。

这个子命令的典型输出如下所示,更多完整的实例可以在 ceccomp 示例 一节中找到。

open      -> ALLOW
read      -> ALLOW
write     -> ALLOW
execve    -> KILL
execveat  -> KILL
mmap      -> ALLOW
mprotect  -> ALLOW
openat    -> ALLOW
sendfile  -> ALLOW
ptrace    -> ERRNO(1)
fork      -> ALLOW
Note
seccomp-tools 没有等价的子命令。

capture - 用 eBPF 捕获过滤器

ceccomp capture [-c WHEN] [-p PID]

未设置 PID 时在全局范围内捕获 seccomp 过滤器。eBPF 程序将追踪 seccomp_check_filter 内核函数,可靠地拦截内核将加载的 seccomp 过滤器,而相比 trace - 运行时捕获过滤器 没有条件竞争的风险。这个功能在下方被称为“全局捕获”。在 x86_64 上,Linux 5.15 是运行该功能的最低推荐内核版本。

当设置了 PID 时,将安装用来从对应进程(其实是线程)中提取已安装过的 seccomp 过滤器的 eBPF 程序。注意在内核中,pid 指线程 id,而 tgid 指进程 id。eBPF 程序会直接把由原始 seccomp 过滤器转换后的 eBPF 指令抽取出来并转换回去,用以执行 disasm - 反汇编。这个功能不需要 CONFIG_CHECKPOINT_RESTORE 内核配置,甚至在 ceccomp 处在 seccomp 状态时仍能工作。这个功能在下方被称为“捕获 PID”。Linux 6.2 是运行该功能的最低内核版本,因为这时 bpf_task_from_pid 函数才被引入。

Important
当内核迁移 seccomp 过滤器时,由于一些不同的 cBPF 指令会被映射到相同的 eBPF 指令序列,因此从 cBPF 到 eBPF 的转换是有损的。结果是“捕获 PID”的输出可能和原始的 seccomp 过滤器并不一致,尽管它们在功能上是等价的。

目前,compat task 检测只支持 x86_64aarch64。 这个功能允许 ceccomp 识别一个任务是原生的 (64 位内核上的 64 位任务),还是“兼容的” (64 位内核上的 32 位任务),由此 ceccomp 才能把正确的架构标识传给 libseccomp 来查询捕获到的 seccomp 过滤器中的系统调用的名字。

注意 eBPF 利用异步环形缓冲区来向用户态进程发回数据,所以如果同时加载了好多 seccomp 过滤器,缓冲区可能会耗尽并且您需要运行 bpftool prog trace 来看到详细错误。由于我们不能将错误通过满的缓冲区发回来,因此那些溢出的过滤器对没有阅读内核消息的用户来说实际上是不可见的。

-c, --color WHEN

参数描述可以在 asm - 汇编 一节中找到。

-p, --pid PID

从哪个 进程 (线程) 中提取 seccomp 过滤器指令。

Tip
所有出现在“全局捕获”或“捕获 PID”中的 PID初始命名空间 PID,而不是当前命名空间中的 PID。推荐只在初始命名空间中运行 ceccomp capture
Note
此模块是可选的,您可以在配置阶段将其禁用。此模块需要 libbpf 才能工作。
Note
此子命令在 4.3 版本 中被引入。

TEXT 语法参考

Important
4.0 版本 提高了词法分析器代码的可读性,伴随着大幅修改的语法。前缀为 # 的行现在是注释了。同时 行号 被替换为 标签,现在词法分析器根据标签定义来决定跳转到哪里,而不是根据 TEXT 文件中的行号。

这里有正确的 TEXT 格式,以类 EBNF 语法描述:https://github.com/ceccomp/ceccomp/issues/17#issuecomment-3610531705 。 没兴趣了解 EBNF?请继续阅读以下的例子。

其余未描述到的BPF操作都被内核禁止了。

注释与标签

ceccomp disasm 展示了很多东西,但对于 asm 来说有些是可选的。

#Label  CODE  JT   JF      K
#---------------------------------
 L0001: 0x06 0x00 0x00 0x7fff0000 return ALLOW
#---------------------------------

任何在 # 之后的文本将被 asm 丢弃,就像一些脚本语言一样。

允许空行。

标签声明是一个从行首开始并以 : 结尾的标识符,例如 L0001。标识符是一个以字母为首,中间只包含字母、数字和下划线 _ 的字符串。标签只在它是 goto 的目标时才是必要的,由 disasm 添加的多余的标签知识为了可读性。例如在 if ($A == 0) goto somewhere 中,somewhere 是一个标签并且必须在这行伪代码后声明。标签声明可以单独占据一行,也可以放置在伪代码的前面。

由 disasm 生成的 CODEJTJFK 值会被 asm 丢弃,asm 之解析 K 之后的有效伪代码。

Caution
ceccomp disasmseccomp-tools disasm 的输出之间有很多细微的差别, 以下是一个典型的输出示例。同时有些伪代码是不同的,所以不要盲目将 seccomp-tools 的输出管道给 ceccomp。
line  CODE  JT   JF      K
=================================
0000: 0x06 0x00 0x00 0x7fff0000  return ALLOW

赋值

A 可以直接赋值为 seccomp 属性。由于内核限制, X 不能直接赋值为 seccomp 属性。

$A = $arch
$A = $syscall_nr

要给 A 赋值为这些64位长的字段,必须使用 low_ 或者 high_ 的前缀。

$A = $low_pc
$A = $high_pc
$A = $low_args[0]
$A = $high_args[0]
...
$A = $low_args[5]
$A = $high_args[5]

一个特殊的属性是 sizeof(struct seccomp_data) ,它可以直接赋值给 AX

$A = $scmp_data_len
$X = $scmp_data_len

临时内存是32位的,要想访问它们,你可以使用十六进制或者十进制的索引。 AX 都是可赋值的。给 AX 赋值为立即数接受任意格式的数字,只要你使用 "0x" 或者 "0b" 等前缀正确表达数字是几进制的。

$X = $mem[0]
$A = $mem[0xf]
$A = $mem[15] # both hex and dec index are OK
$A = 0
$X = 0x3b
$A = 0b1111
$A = 0333

你还可以将 X 赋值给 A 或者反过来。将 XA 赋值给临时内存当然可以。

$A = $X
$X = $A
$mem[3] = $X
$mem[0x4] = $A

数学运算

你可以以多种方式操作 A

$A += 30
$A -= 4
$A *= 9
$A /= 1
$A &= 7
$A >>= 6

右值也可以是 X

$A &= $X
$A |= $X
$A ^= $X
$A <<= $X

想要对 A 取反可以这么做。

$A = -$A

当…​时向下跳转

无条件跳转:

goto L3

当…​跳转:

if ($A == execve) goto L3
if ($A != 1234) goto L4
if ($A & $X) goto L5
if !($A & 7) goto L6
if ($A <= $X) goto L7

当条件为真时跳转到…​,条件为假时跳转到…​:

if ($A > $X) goto L3, else goto L4
if ($A >= 4567) goto L5, else goto L6

只有 在做条件判断时,你才能将数字替换为系统调用号或架构名。在以上的例子中, 0x3bexecve 替换。所有系统调用名将会以你设置的架构解析为系统调用号。 如果你希望解析外部架构(不等于你设置的架构)的系统调用名, 请在前面附加架构名和一个点。例如,你设置的架构是 x86_64,并且你正在写 aarch64 架构的规则,请这样写:

if ($A == aarch64.read) goto 5

注意当你手动使用 -a aarch64 将架构设置为 aarch64 时, 你可以在伪代码中忽略 aarch64.

返回码

返回寄存器 A 的值:

return $A

或者返回一个立即数,多余的字段放在 () 里。 TRACETRAPERRNO 接受一个额外的字段,如果没有 () ,它们将被视为 行为(0)

return 0x13371337
return KILL
return KILL_PROCESS
return TRAP(123)
return ERRNO(0)
return TRACE
return TRACE(3)
return LOG
return NOTIFY

简短的例子

下面的 TEXT 对与 asm 来说是正确的,这段 TEXT 阻止了 amd64 的 execveexecveat 系统调用:

$A = $syscall_nr
if ($A == execve) goto forbid
if ($A == execveat) goto forbid
return ALLOW
forbid: return KILL

限制

为了更好的性能,Ceccomp asm 对 TEXT 有一些限制。

  1. 由于 TEXT 是个文本文件,'\0' 不能出现在 TEXT 中。

  2. 一行必须短于 384 字节

  3. 一个 TEXT 文件必须短于 16384 行。

  4. 一个 TEXT 文件必须小于 4 MiB。

并且对于 asm 和 disasm 来说,有效的伪代码(能被编码或解码为 BPF 的)必须少于等于 4096 条,这是内核规定的。

一个关于 ceccomp asm 有趣的知识:任何在 TEXT 文件中基本的 ANSI 颜色,例如 \x1b[31m,会在处理时被丢弃。

ceccomp 示例

asm 示例

asm

disasm 示例

disasm

emu 示例

emu

trace 示例

运行程序:

trace

如果设置了 -o FILE

output trick

PID 模式:

trace pid

跟踪 PID 模式:

trace seize

zsh下PID模式可以使用补全:

trace completion

probe 示例

probe

capture 示例

全局捕获:

capture

捕获进程中的过滤器:

capture pid

仓库

https://github.com/ceccomp/ceccomp 可以找到源代码。 欢迎提交 Pull Requests 和 Issues !

Copyright © 2025-现在,基于 GPLv3 或更新版本分发。