原文发布于 quant67.com,转载请保留出处。
第 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.md、tracing-policy/mode.md、selectors.md的 Override / Signal 节,以及bpf/process/bpf_enforcer.c。无CONFIG_BPF_KPROBE_OVERRIDE的内核上不把 Override 写成已生效。无真实节点则不粘贴伪造阻断输出。
一、Override:函数不执行,调用方拿到 argError
selectors.md 把动作分成两类:Sigkill、Override、FollowFD 族、Post、TrackSock 等在 内核 BPF 里执行 ;GetUrl 与 DnsLookup 在用户态收到事件之后才发生。强制执行轴只讨论前一类里的 Override / Signal。把阻断写成「agent 收到 JSON 再 kubectl delete」,已经离开 v1.7.0 的 inline 模型。
v1.7.0 Enforcement 文档把第一种强制执行定义成:改写函数返回值,使该函数永不执行,改为把一个值(通常是错误)返回给调用者。 一般只有系统调用和安全检查函数允许以这种方式改返回值。策略侧字段是 matchActions 里的 Override,错误码写在 argError。
selectors.md 把同一件事写得更操作化:Sigkill 终止整个进程;Override 在被 kprobe 的函数位置上 运行,返回 argError 指定的值。之后是否停下来,取决于内核路径或用户态如何处理这个返回值。
内核依赖写在 note 里,不是可选项:
Override走内核 error injection 框架,仅当内核编译了CONFIG_BPF_KPROBE_OVERRIDE。- 覆盖系统调用是主用例;其它可注入函数带
ALLOW_ERROR_INJECTION(),可在/sys/kernel/debug/error_injection/list核对。 - 从内核 5.7 起,覆盖
security_hook 也成为可能。
没有该 config 却把策略写成「已经阻断」,是本篇明确禁止的结论。加载失败或动作被跳过时,排障落到第 13 篇,不要用「YAML 里有 Override」当证据。
tag 示例 examples/tracingpolicy/override-security.yaml(以下按 tag 原文,未改字段):
yaml
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 进入、返回 int。argRegs 可以改寄存器,文档写明接口可能再变。本篇不把 uprobe Override 当成通用阻断手段。
二、Signal / Sigkill:进程停,当前 syscall 可以已经生效
第二种强制执行是向进程发信号。Sigkill 从内核同步终止匹配选择器的那个进程;Signal 用 argSig 指定信号号,argSig: 9 与 Sigkill 等价。
Enforcement 文档用 write() 钉死副作用边界:
在
write()系统调用里发送SIGKILL,不保证 数据不会被写入文件。它保证进程被同步终止(线程也会停)。某些场景下「进程已停、不再处理返回值」就够了。若要确保这次操作本身不完成,应把Signal与Override组合。
这与「杀了进程等于回滚这次 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 已出现的为准,不发明第三种动作名):
yaml
matchActions:
- action: Override
argError: -1
- action: Signal
argSig: 9
顺序与 first-match 语义见第 7 篇。本篇只要求:官方文档把「拦住这次操作」定义成 Override 负责函数体不跑,Signal 负责进程不再继续;缺任何一侧,保证就回到上表那一行。
三、monitor 与 enforce:动作可以在不改 YAML 的情况下被抹掉
mode.md 把策略模式分成两种:
monitoring:强制执行操作被省略(elided)enforcement:强制执行操作被尊重并执行
tetra tracingpolicy list 列出每条策略的 MODE 列。文档示例(文档示例 ,非本机输出)表头为 ID / NAME / STATE / FILTERID / NAMESPACE / SENSORS / KERNELMEMORY / MODE,同一张表出现 enforce 与 monitor 两种短名。STATE 是 enabled/disabled,与 MODE 正交:enabled + monitor 表示策略在跑、强制执行被省略。
写在 CR 里的形状(mode.md 文档示例,经删减):
yaml
apiVersion: cilium.io/v1alpha1
kind: TracingPolicyNamespaced
metadata:
name: "enforce-policy"
namespace: "default"
spec:
options:
- name: "policy-mode"
value: "monitor"
模式有三处来源,优先级从低到高:
- 写在策略自身
spec.options,键policy-mode(示例值为"monitor")。 - 加载时指定:
tetra tracingpolicy add --mode monitor policy.yaml。 - 运行时经 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.c 的 do_enforcer()(源码摘录,删减了 map 未命中的早退):
c
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_data(error 与 signal 两个 __s16 字段)。
bpf_enforcer.h 的 do_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.yaml 是 list: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() 句是同一类缺口。生产上可接受边界------例如审计磁盘已写入但进程已死------是开放问题,不是本版本能关闭的不变量。
开放问题:
- TOCTOU 可接受边界 :策略挂在
sys_*用户指针上时,是否必须改挂security_*才允许 enforce。入口:hooks.md警告;Bishop & Dilger 1996;第 9 篇路径模式。 - monitor 优先级误用 :运行时
set-mode monitor盖住 CR 里的 enforce 之后,审计日志仍显示策略「enabled」。入口:mode.md三条来源。 - enforcer map 丢失 :
ENFORCER_MISSED_OVERWRITTEN/NOACTION在多线程密集 Notify 时是否可观察。入口:bpf_enforcer.h;第 12 篇 metrics 不覆盖这两类丢失,除非另有独立指标(本篇不编名字)。
七、小结
- Override:函数不跑 ,返回
argError;没有CONFIG_BPF_KPROBE_OVERRIDE就不能当阻断。 - SIGKILL:进程停 ,不保证 当前 syscall 无副作用;官方例子是
write()。 - 要让这次操作不完成:按官方文档组合 Signal 与 Override。
monitoring/enforcement可以在不改 hook YAML 的情况下关掉或打开动作;运行时set-mode优先级最高。- TOCTOU:Bishop & Dilger 1996 → Garfinkel 2003 §4.3 → Tetragon 用 LSM/inline 动作缩小窗口,不删除 SIGKILL 副作用与用户指针 hook 的窗口。
--keep-sensors-on-exit只保证停机时强制执行仍在,不保证事件仍被导出。
下一篇把「策略匹配了但事件不见了」从强制执行挪到节流与环缓冲。
八、参考资料
规范 / 官方文档(A)
- Tetragon v1.7.0 Enforcement ---
docs/content/en/docs/concepts/enforcement/_index.md(Override 永不执行;SIGKILL 与write();组合 Signal+Override)。 - Tetragon v1.7.0 Enforcement Mode ---
docs/content/en/docs/concepts/tracing-policy/mode.md。 - Tetragon v1.7.0 Selectors --- Override / Sigkill / Signal / NotifyEnforcer。
- Tetragon v1.7.0 Persistent enforcement ---
docs/content/en/docs/concepts/enforcement/persistent-enforcement.md(指针,非全文)。 - Tetragon v1.7.0 Hook points --- syscall 指针 TOCTOU 警告。
源码(A)
bpf/process/bpf_enforcer.c(do_enforcer、override_return、fmodret_enforcer)@ v1.7.0bpf/process/bpf_enforcer.h(do_enforcer_action、enforcer_missed_notifications)@ v1.7.0examples/tracingpolicy/override-security.yaml、killer.yaml@ v1.7.0
核心论文
- Bishop, Dilger. Checking for Race Conditions in File Accesses . Computing Systems 9(2), 1996.
- Garfinkel. Traps and Pitfalls: Practical Problems in System Call Interposition Based Security Tools. NDSS 2003, §4.3.
站内对照
实验台账
- Override vs Sigkill 演练未跑;本篇不给伪造
Killed终端输出。文档 persistent 示例中的Killed不转载为本机结果。