土法炼钢 · 系统与基础设施

【Tetragon / eBPF】强制执行:Override 与 SIGKILL 各自保证什么

文章导航

分类入口
kubernetessecurity
标签入口
#tetragon#ebpf#enforcement#override#sigkill#toctou#tracingpolicy#v1.7.0

目录

第 9 篇 把观测挂到 LSM / tcp_* / commit_creds。本篇回答强制执行轴上最容易写错的一句:发了 SIGKILL 是否等于这次操作没发生。 官方结论是否定的。Override 让函数根本不跑;Signal 保证进程同步终止,不保证当前 syscall 没有副作用。要拦住这次操作,文档要求把两者组合。

本篇在系列中的位置

篇目 核心内容
第 9 篇 · 文件 / 网络 / 能力 观测 hook 与失败表象
第 10 篇 · 强制执行 Override vs Signal;mode;TOCTOU
第 11 篇 · 节流与观测税 事件被丢掉的另一层
系列目录 全部篇目

版本锚定:Tetragon v1.7.0。机制叙述对齐 tag docs/content/en/docs/concepts/enforcement/_index.mdtracing-policy/mode.mdselectors.md 的 Override / Signal 节,以及 bpf/process/bpf_enforcer.c。无 CONFIG_BPF_KPROBE_OVERRIDE 的内核上不把 Override 写成已生效。无真实节点则不粘贴伪造阻断输出。


一、Override:函数不执行,调用方拿到 argError

selectors.md 把动作分成两类:SigkillOverrideFollowFD 族、PostTrackSock 等在 内核 BPF 里执行GetUrlDnsLookup 在用户态收到事件之后才发生。强制执行轴只讨论前一类里的 Override / Signal。把阻断写成「agent 收到 JSON 再 kubectl delete」,已经离开 v1.7.0 的 inline 模型。

v1.7.0 Enforcement 文档把第一种强制执行定义成:改写函数返回值,使该函数永不执行,改为把一个值(通常是错误)返回给调用者。 一般只有系统调用和安全检查函数允许以这种方式改返回值。策略侧字段是 matchActions 里的 Override,错误码写在 argError

selectors.md 把同一件事写得更操作化:Sigkill 终止整个进程;Override 在被 kprobe 的函数位置上运行,返回 argError 指定的值。之后是否停下来,取决于内核路径或用户态如何处理这个返回值。

内核依赖写在 note 里,不是可选项:

没有该 config 却把策略写成「已经阻断」,是本篇明确禁止的结论。加载失败或动作被跳过时,排障落到第 13 篇,不要用「YAML 里有 Override」当证据。

tag 示例 examples/tracingpolicy/override-security.yaml(以下按 tag 原文,未改字段):

apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: "security-override"
spec:
  kprobes:
  - call: "security_inode_mkdir"
    syscall: false
    selectors:
    - matchBinaries:
      - operator: "In"
        values:
        - "/usr/bin/bash"
      matchActions:
      - action: Override
        argError: -1

selectors.md 另给 sys_symlinkat 在路径等于 /etc/passwd\0 时返回 -1 的例子。两者都是「这次调用失败」,不是「进程消失」。argError: -1 对用户态通常表现为失败;具体 errno 是否被翻译成 -EPERM / -EACCES 取决于被覆盖函数的约定,文档没有为每个 hook 列一张表,本篇也不编。

uprobe 上的 Override 是另一条、更窄的路:文档警告容易把被跟踪进程打崩;x86_64;还要求 uprobe 打在函数开头、经 call 进入、返回 intargRegs 可以改寄存器,文档写明接口可能再变。本篇不把 uprobe Override 当成通用阻断手段。


二、Signal / Sigkill:进程停,当前 syscall 可以已经生效

第二种强制执行是向进程发信号。Sigkill 从内核同步终止匹配选择器的那个进程;SignalargSig 指定信号号,argSig: 9Sigkill 等价。

Enforcement 文档用 write() 钉死副作用边界:

write() 系统调用里发送 SIGKILL不保证数据不会被写入文件。它保证进程被同步终止(线程也会停)。某些场景下「进程已停、不再处理返回值」就够了。若要确保这次操作本身不完成,应把 SignalOverride 组合

这与「杀了进程等于回滚这次 syscall」相反。write 已经把数据推进文件之前或之中被杀,磁盘上的字节可以已经存在。对 connect / unlink / chmod 同类问题成立与否取决于 hook 落在函数入口还是返回点、以及内核是否已产生副作用——文档只把 write() 写成可核对反例,本篇不推广成未核过的 syscall 表。

selectors.md 的 Sigkill 示例挂在 sys_write、用 type: fd 匹配 /etc/passwd,并对命名空间 PID 0/1 做 NotIn。注释写明 kubectl exec 会在容器 PID 命名空间里长出新进程,不是 PID 1 的孩子。这是选择器细节,不是「SIGKILL 让 write 没发生」。同一文档要求:若计划把该动作用于强制执行,先读 Enforcement 节——也就是本篇正在钉的那份。

组合写法在策略里就是两个 matchActions 项(字段以 selectors.md 已出现的为准,不发明第三种动作名):

matchActions:
- action: Override
  argError: -1
- action: Signal
  argSig: 9

顺序与 first-match 语义见第 7 篇。本篇只要求:官方文档把「拦住这次操作」定义成 Override 负责函数体不跑,Signal 负责进程不再继续;缺任何一侧,保证就回到上表那一行。

sequenceDiagram
  participant Proc as Process
  participant Sys as Syscall body
  participant Hook as BPF selector
  participant FS as File or net effect
  Proc->>Sys: enter write or other call
  Sys->>Hook: kprobe at chosen hook
  alt Override
    Hook-->>Proc: return argError, body skipped
  else Signal only
    Hook->>Proc: SIGKILL
    Sys->>FS: side effects may already exist
  else Signal plus Override
    Hook->>Proc: SIGKILL
    Hook-->>Proc: body skipped if Override applies
  end

三、monitor 与 enforce:动作可以在不改 YAML 的情况下被抹掉

mode.md 把策略模式分成两种:

tetra tracingpolicy list 列出每条策略的 MODE 列。文档示例(文档示例,非本机输出)表头为 ID / NAME / STATE / FILTERID / NAMESPACE / SENSORS / KERNELMEMORY / MODE,同一张表出现 enforcemonitor 两种短名。STATE 是 enabled/disabled,与 MODE 正交:enabled + monitor 表示策略在跑、强制执行被省略。

写在 CR 里的形状(mode.md 文档示例,经删减):

apiVersion: cilium.io/v1alpha1
kind: TracingPolicyNamespaced
metadata:
  name: "enforce-policy"
  namespace: "default"
spec:
  options:
    - name: "policy-mode"
      value: "monitor"

模式有三处来源,优先级从低到高:

  1. 写在策略自身 spec.options,键 policy-mode(示例值为 "monitor")。
  2. 加载时指定:tetra tracingpolicy add --mode monitor policy.yaml
  3. 运行时经 gRPC:tetra tp set-mode --namespace pizza enforce-security enforce

排障含义:YAML 里仍写着 Sigkill / Override,但策略处于 monitor 时,这些动作不会发生。把「策略里有阻断」等同于「节点上在阻断」,会误判。第 7 篇讨论选择器 first-match;本篇只钉 mode 可以把已挂上的强制执行动作关掉


四、TOCTOU 谱系:从文件竞态到 inline BPF,仍拦不住的窗口

1. 经典定义

Bishop & Dilger(Checking for Race Conditions in File Accesses, Computing Systems 9(2), 1996)把文件访问里的竞态写成:检查时刻看到的路径/权限,与随后使用时刻的 inode 不必是同一个对象。经典形态是 access() 通过后再 open()——中间可以换成符号链接。论文的对象是「按名字检查、按对象使用」这条裂缝,不是某个具体 LSM。

Garfinkel(Traps and Pitfalls, NDSS 2003 §4.3)把同一裂缝移到系统调用插入式工具,并使用 TOCTTOU(time-of-check to time-of-use)这个已经在 Bishop & Dilger 工作里钉死的名字。§4.3 的要点是:工具在用户内存上做检查,内核稍后才使用该指针。 攻击者可以在两次之间替换指针指向的内容。Garfinkel 还区分了「检查窗口」与「工具自己的延迟」:用户态策略引擎若先把事件拷出内核再决定杀进程,窗口比 inline 检查更大。这不是实现 bug 清单,而是接口形状:检查与使用若不是同一原子步骤,窗口就存在。

v1.7.0 hooks.md 把这条谱系接到 Tetragon:挂系统调用且参数是用户态指针时,存在 TOCTOU;挂后续内核函数(LSM security_ hook)时,操作的是已经拷进内核的状态,这条用户指针窗口关掉。

2. Tetragon 的分叉:inline 动作

相对用户态规则引擎(在事件出内核之后再决定杀不杀),Tetragon 把 Override / Sigkill / Signal 标成 在内核 BPF 里执行selectors.md note;GetUrl / DnsLookup 才是用户态)。这消除的是「事件排队到 agent 再 SIGKILL」那一段延迟,不消除 Garfinkel 写的「syscall 参数仍是用户指针」窗口,也不消除 Enforcement 文档写的「SIGKILL 不撤销已发生的 write」。

工业反例留给第 14 篇:Falco GHSA-6v9j-2vm2-ghf7 / CVE-2022-26316 是用户态读 argv/sockaddr 的检查窗口。本篇只需要这一句对照:Tetragon 把动作推进内核,并没有把 Bishop–Dilger 的对象替换问题从文件系统里删掉。

3. bpf_enforcer.c:延迟到 enforcer 程序才动手

直接写在 kprobe 选择器上的 Override / Sigkill 走 generic kprobe 路径。另一条路径是 NotifyEnforcer:先把 {error, signal} 写入 enforcer_data map(键为当前 pid_tgid),再由独立的 enforcer 程序执行。

bpf/process/bpf_enforcer.cdo_enforcer()(源码摘录,删减了 map 未命中的早退):

FUNC_INLINE int do_enforcer(void *ctx)
{
        __u64 id = get_current_pid_tgid();
        struct enforcer_data *data;

        data = map_lookup_elem(&enforcer_data, &id);
        if (!data)
                return 0;

        if (data->signal)
                send_signal(data->signal);

        map_delete_elem(&enforcer_data, &id);
        return data->error;
}

若编译了 __BPF_OVERRIDE_RETURN,kprobe 节里对非零返回值调用 override_return(ctx, ret)。否则走 fmod_ret/security_task_prctl 占位(注释写明正常运行时函数名由 Tetragon 动态设置)。信号在 override 之前发出:这与 Enforcement 文档「组合 Signal + Override」一致——enforcer 路径上两者可以写进同一条 enforcer_dataerrorsignal 两个 __s16 字段)。

bpf_enforcer.hdo_enforcer_action() 在 map 里已有条目时记 ENFORCER_MISSED_OVERWRITTEN;清理时若通知从未被消费则记 ENFORCER_MISSED_NOACTION。这是强制执行路径上的丢失信号,不是观测节流。

selectors.md 写明 NotifyEnforcer 面向 缺少 multi-kprobe 快速挂载 的内核,用 raw syscall tracepoint 挂 enforcer。kprobe_multi 可用时,同一需求可直接写 kprobe 动作。tag examples/tracingpolicy/killer.yamllist:dups + NotifyEnforcer / argError: -1 / argSig: 9 的完整示例。本篇不把 killer 示例展开成规则百科。


五、Persistent enforcement:仅作边界指针

v1.7.0 docs/content/en/docs/concepts/enforcement/persistent-enforcement.md 的目标是:agent 进程消失后,强制执行策略仍保持运行。 开关是 --keep-sensors-on-exit。退出时程序/map/link 仍 pin 在 /sys/fs/bpf/tetragon。新进程启动会把已有目录改名为 /sys/fs/bpf/tetragon_old,装上配置的策略,再删掉 _old

文档自己写的限制:停机期间 收不到事件,只有强制执行还在。 本篇不重写 pin 细节、不讨论晚于 v1.7.0 的 gRPC 策略持久化开关。把它记成:观测管道与强制执行管道可以暂时解耦;解耦期间第 12 篇的 SIEM 文件 sink 是空的,第 4 篇的 gRPC 也同样没有消费者。新进程启动仍会搬走旧 bpf 树——「keep on exit」不是「restart 后自动接管同一批 pin」,而是给加载新策略留出重叠窗口。细节以 tag 该页三条启动步骤为准。


六、Override 与 Signal 保证对照

口径:v1.7.0 Enforcement 文档 + selectors.md。无本机实测时长或成功率。

维度 Override Signal / Sigkill
当前函数体 不执行 可能已执行或部分执行
返回值 调用方收到 argError 进程可能已无法处理返回值
进程生命 默认仍活着,除非另有 Signal 同步终止(SIGKILL)或按 argSig
内核依赖 CONFIG_BPF_KPROBE_OVERRIDE;可注入函数列表 bpf_send_signal 路径;不依赖 override config
官方反例 write() 仍可能把数据写入文件
要拦住这次操作 单独 Override 针对该函数 文档要求与 Override 组合
monitor mode 动作被省略 动作被省略

争论(有文献支撑):Override 的正确性依赖「该函数允许 error injection、且调用方把错误当失败」;SIGKILL 的简便性依赖「杀进程就够了」。Garfinkel 2003 的工具若只做杀死而不撤销,与 Tetragon 文档的 write() 句是同一类缺口。生产上可接受边界——例如审计磁盘已写入但进程已死——是开放问题,不是本版本能关闭的不变量。

开放问题

  1. TOCTOU 可接受边界:策略挂在 sys_* 用户指针上时,是否必须改挂 security_* 才允许 enforce。入口:hooks.md 警告;Bishop & Dilger 1996;第 9 篇路径模式。
  2. monitor 优先级误用:运行时 set-mode monitor 盖住 CR 里的 enforce 之后,审计日志仍显示策略「enabled」。入口:mode.md 三条来源。
  3. enforcer map 丢失ENFORCER_MISSED_OVERWRITTEN / NOACTION 在多线程密集 Notify 时是否可观察。入口:bpf_enforcer.h;第 12 篇 metrics 不覆盖这两类丢失,除非另有独立指标(本篇不编名字)。

七、小结

  1. Override:函数不跑,返回 argError;没有 CONFIG_BPF_KPROBE_OVERRIDE 就不能当阻断。
  2. SIGKILL:进程停不保证当前 syscall 无副作用;官方例子是 write()
  3. 要让这次操作不完成:按官方文档组合 Signal 与 Override。
  4. monitoring / enforcement 可以在不改 hook YAML 的情况下关掉或打开动作;运行时 set-mode 优先级最高。
  5. TOCTOU:Bishop & Dilger 1996 → Garfinkel 2003 §4.3 → Tetragon 用 LSM/inline 动作缩小窗口,不删除 SIGKILL 副作用与用户指针 hook 的窗口。
  6. --keep-sensors-on-exit 只保证停机时强制执行仍在,不保证事件仍被导出。

下一篇把「策略匹配了但事件不见了」从强制执行挪到节流与环缓冲。


八、参考资料

规范 / 官方文档(A)

源码(A)

核心论文

站内对照

实验台账


上一篇:文件 / 网络 / 能力观测面 · 系列目录 · 下一篇:节流与观测税

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。

2026-08-17 · kubernetes / security

Tetragon / eBPF 运行时安全:从进程血缘到 TracingPolicy

补齐站内 Cilium 数据面与可观测对照之上的运行时安全内核层:进程模型、Sensor/Hook、TracingPolicy 与内核过滤、强制执行、事件导出与节流,并以排障与相对 Falco/CNP 的选型收束。


By .