= ceccomp(1)
dbgbgtf <dudududumaxver@outlook.com>; RocketDev <ma2014119@outlook.com>
:doctype: manpage
:docdatetime: {TAG_TIME}
:manmanual: Ceccomp Manual
:mansource: ceccomp {VERSION}
:manversion: {VERSION}
:imagesdir: images/
:stylesheet: boot-slate.css

== 名称

ceccomp - 一个分析安全计算 (seccomp) 过滤器的工具

== 大纲

    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过滤器来限制系统调用规则，并使用 `seccomp` 和 `prctl` 两个系统调用来安装过滤器。以下是一个以十六进制表示的限制 `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_ 对应的原始字节码。

WHEN::
决定了 `ceccomp` 何时输出有颜色的文本。当设置为 _auto_ 时， `ceccomp` 会在输出目标是一个“tty”时打印颜色。可以是 _auto_ 、 _never_ 或者 _always_ 。 默认值是 _auto_ 。

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

NOTE: 从 _4.0 版本_ 开始考虑端序。如果目标架构 *ARCH* 的端序与机器端序不同，则会在输出前反转过滤器（CODE 和 K）。

FMT::
决定了 `ceccomp` 如何输出二进制格式的BPF字节码。可以是 _hexfmt_ 、 _hexline_ 或者 _raw_ 。你可以在 <<ceccomp 示例>> 节中找到示例输出。默认值是 _hexline_ 。

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

IMPORTANT: _4.0 版本_ 大幅修改了汇编的语法，请查阅下面的语法参考！

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

|===
|命令|差别

|`seccomp-tools asm`
|使用它自己的语法汇编，有点像脚本；可以汇编会被内核拒绝的错误 _TEXT_

|`ceccomp asm`
|你可以直接拿着 `disasm` 的输出来汇编，不需要学习新语法；默认使用 _标准输入_ 作为输入
|===

=== disasm - 反汇编

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

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

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

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

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])` 的结果。适用于在不实际运行程序或不想手动检查规则时，查看触发系统调用的结果。 这个子命令适合用来自动检测一个过滤器。

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

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

ARGS[0-5] 和 PC::
当调用系统调用时对应寄存器的值。例如，在 x86_64 上，它们分别对应了 `rdi` 、 `rsi` 、 `rdx` 、 `r10` 、 `r8` 、 `r9` 和 `rip` 。它们的默认值都是0。

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 过滤器的程序正在等待输入。

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

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

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

PID::
设置为你想检查的 pid。 *PID* 和 *PROGRAM* 相冲突；你只能在一条命令中动态运行一个程序， 或者检查一个 pid。没有 `-s` 标志，trace pid 会尝试使用 `ptrace(PTRACE_SECCOMP_GET_FILTER)` 从 *PID* 中提取 seccomp 过滤器，这个操作在一些系统上可能不可用。

-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` 这样的命令丢弃它。

|===
|命令|差别

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

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

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

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

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

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

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

这个子命令的典型输出如下所示，更多完整的实例可以在 <<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` 没有等价的子命令。

== TEXT 语法参考

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

这里有正确的 _TEXT_ 格式，以类 EBNF 语法描述：https://github.com/dbgbgtf1/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 生成的 `CODE`、`JT`、`JF` 和 `K` 值会被 asm 丢弃，asm 之解析 `K` 之后的有效伪代码。

NOTE: `ceccomp disasm` 和 `seccomp-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)` ，它可以直接赋值给 `A` 或 `X` 。

    $A = $scmp_data_len
    $X = $scmp_data_len

临时内存是32位的，要想访问它们，你可以使用十六进制或者十进制的索引。 `A` 和 `X` 都是可赋值的。给 `A` 或 `X` 赋值为立即数接受任意格式的数字，只要你使用 "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` 或者反过来。将 `X` 或 `A` 赋值给临时内存当然可以。

    $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

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

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

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

=== 返回码

返回寄存器 `A` 的值：

    return $A

或者返回一个立即数，多余的字段放在 `()` 里。 `TRACE` 、 `TRAP` 和 `ERRNO` 接受一个额外的字段，如果没有 `()` ，它们将被视为 `行为(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 的 `execve` 和 `execveat` 系统调用：

    $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 示例

ifdef::backend-manpage[]
手册不能显示图片，因此如果想看示例请参阅html版本。
endif::[]

ifndef::backend-manpage[]
=== asm 示例
image::asm.webp[]
=== disasm 示例
image::disasm.webp[]
=== emu 示例
image::emu.webp[]
=== trace 示例
运行程序：

image::trace.webp[]

如果设置了 `-o FILE` ：

image::output_trick.webp[]

PID 模式：

image::trace_pid.webp[]

跟踪 PID 模式：

image::trace_seize.webp[]

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

image::trace_completion.webp[]

=== probe 示例
image::probe.webp[]
endif::[]

== 仓库

在 https://github.com/dbgbgtf1/Ceccomp 可以找到源代码。 欢迎提交 Pull Requests 和 Issues ！

Copyright (C) 2025-现在，基于 GPLv3 或更新版本分发。
