第 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 原文,未改字段):
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: -1selectors.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
已出现的为准,不发明第三种动作名):
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 把策略模式分成两种:
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
文档示例,经删减):
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
未命中的早退):
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不转载为本机结果。
→ 上一篇:文件 / 网络 / 能力观测面 · 系列目录 · 下一篇:节流与观测税
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【Tetragon / eBPF】选择器与内核过滤:AND、first-match 与动作挂载点
钉清 TracingPolicy 选择器在 v1.7.0 的匹配语义(selector 内 AND、每 hook 最多 5 个、first-match OR);覆盖 matchArgs/matchBinaries/matchParentBinaries 与 Post/NoPost/Override/Sigkill/Signal;区分内核动作与用户态 GetUrl/DnsLookup。
【Tetragon / eBPF】排障坐标系:无事件、策略不生效、导出缺口、误阻断与血缘空洞
按五轴映射 Tetragon v1.7.0 故障:无事件落在 Hook/BTF/加载,策略不生效落在 collectionKey 撞名与 policyfilter,JSON allow/deny 不等于 gRPC,Override 依赖 CONFIG_BPF_KPROBE_OVERRIDE,血缘空洞来自 execve_map 未命中与 procFS 回填。
Tetragon / eBPF 运行时安全:从进程血缘到 TracingPolicy
补齐站内 Cilium 数据面与可观测对照之上的运行时安全内核层:进程模型、Sensor/Hook、TracingPolicy 与内核过滤、强制执行、事件导出与节流,并以排障与相对 Falco/CNP 的选型收束。
【Tetragon / eBPF】运行时安全全景:缺口、五轴坐标系与 16 篇路线
相对 Cilium 数据面、可观测对照与 Falco 口号,钉清 Tetragon v1.7.0 运行时安全内核缺口;定义进程血缘、Sensor/Hook、策略加载、过滤分层、强制执行五条坐标系,给出 16 篇阅读路线。