第
8 篇 把策略限制到 Pod / 容器 /
host。本篇不写规则库:贴一条「监控 /etc」YAML
并不等于看见了每一次访问。真正要钉死的是三类观测各自挂在什么
hook、参数类型把内核对象收成什么字段、以及路径走不完、socket
层选错、cred 不翻译 user namespace 时事件会怎样缺。
本篇在系列中的位置
篇目 核心内容 第 8 篇 · K8s 身份感知策略 策略打中哪些 Pod 第 9 篇 · 文件 / 网络 / 能力观测面 三类 hook 模式与失败表象 第 10 篇 · 强制执行 Override 与 SIGKILL 系列目录 全部篇目
版本锚定:Tetragon v1.7.0(源码 tag v1.7.0,2026-04-29)。YAML 来自 tag
examples/tracingpolicy/;参数类型来自docs/content/en/docs/concepts/tracing-policy/argument_types.md;路径解析来自bpf/lib/bpf_d_path.h。无真实节点则不粘贴伪造tetra getevents/ JSON。
本篇落到五轴里的 Sensor/Hook:选错 hook 或参数类型,表象是「策略已 apply、该文件/连接/提权却没有事件」,不要先改导出过滤。
一、文件路径:在内核对象上走
d_path,不是在 syscall 字符串上猜
1. 为什么不挂
sys_openat 当默认答案
v1.7.0 hooks.md 开篇警告:把 kprobe
挂在系统调用上、参数又是用户态指针时,会引入检查时刻与使用时刻不一致(TOCTTOU)——用户空间可以在
hook
跑完之后、内核真正消费之前改掉指针指向的数据。挂到更靠后的内核函数(例如
LSM security_
hook)时,数据已经拷进内核对象,这条窗口关掉。
文件观测因此有一条工程分叉:
| 挂载点 | 看见的对象 | 典型失败 |
|---|---|---|
syscall(sys_openat /
sys_write) |
用户指针、fd 整数 | 指针可被改写;write 的 fd
整数本身不是路径 |
LSM security_* |
内核 struct file /
struct path |
hook 名随内核版本消失时整份策略加载失败 |
fd_install |
新装入 fd 表的 struct file |
只覆盖「安装 fd」这一刻,不覆盖后续
read/write |
v1.7.0 用例文档 filename-access.md
的选择是:读/写走
security_file_permission(覆盖
read/write、sendfile、copy_file_range、aio/io_uring
等共同权限检查点),mmap 走
security_mmap_file,截断走
security_path_truncate。这是观测模式,不是
MITRE 映射。
2. 参数类型决定「路径」从哪来
argument_types.md
把路径相关类型分成几层,不能互换:
| 类型 | 内核对象 | 选择器能匹配的内容 |
|---|---|---|
file |
struct file * |
解析出的完整路径;可用 Prefix /
Equal 等 |
path |
struct path * |
同样解析路径 |
dentry |
struct dentry * |
单个挂载点内的路径——没有 mount 信息,过不了挂载点 |
filename |
struct filename |
用户提供的文件名字符串 |
fd |
文件描述符 | Tetragon 解析 fd 得到关联路径后再匹配 |
dentry 的边界写得很硬:只有
struct dentry 时没有挂载信息,路径解析停在一个
mountpoint 里。跨 bind mount 或从另一个挂载点走进同一
inode,前缀匹配会 miss。
完整路径检索只在内核 v5.3 及以后可用。更老的内核限制为 256 个路径分量:总长仍可到 4096 字节,但分量个数先到上限。这是文档 caution,不是本机实测。
3.
__d_path_local:走不完就标未解析
源码 bpf/lib/bpf_d_path.h 的
d_path_local() 从 per-CPU
buffer_heap_map 取缓冲,调用
__d_path_local()。后者按 Linux
d_path 同类思路,从目标 path
往当前任务 fs->root
回溯,prepend_name() 把 dentry
名反着写入缓冲。
失败被编码进
error,不是静默写成空字符串:
UNRESOLVED_PATH_COMPONENTS(0x02):路径遍历没走完(dentry 太多)或路径塞不进缓冲。- 已 unlink 的 dentry 会先
prepend(..., " (deleted)", 10),再走prepend_path。 - 循环次数由
PROBE_CWD_READ_ITERATIONS钉死:默认 11;__LARGE_BPF_PROG下 128;__V61_BPF_PROG下 2048。verifier 要求有界循环,走不完就resolved == false。
排障含义:前缀选择器匹配的是解析成功的字符串。路径被标成未解析时,不要把「没事件」写成「进程没碰这个文件」。
4. FollowFD:文档仍在,但已标废弃
selectors.md 把 FollowFD /
UnfollowFD / CopyFD 标为
deprecated due to being unsafe,并写计划在
1.5 移除。v1.7.0
源码与文档里这些动作还在,但本篇不当成现行模式。需要「fd →
路径」时,优先用参数类型 fd(文档:Tetragon
解析 fd 得到关联路径),或在 fd_install
上直接取 file。
tag 示例 examples/tracingpolicy/write.yaml
把 sys_write 的第一个参数写成 int
再 Equal: "1",匹配的是标准输出这个 fd
号,不是路径。这是另一种合法模式,不要和
type: fd 的路径匹配混为一谈。
5. 最小 YAML(文件)
以下摘自 tag
examples/tracingpolicy/filename_monitoring.yaml,只保留
security_file_permission 一则;原文件还挂了
security_mmap_file 与
security_path_truncate。
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: "file-monitoring"
spec:
kprobes:
- call: "security_file_permission"
syscall: false
return: true
args:
- index: 0
type: "file"
- index: 1
type: "int"
returnArg:
index: 0
type: "int"
selectors:
- matchArgs:
- index: 0
operator: "Prefix"
values:
- "/etc/"同一文件里的 security_path_truncate 在
Linux 6.2+
上是已知加载地雷。filename-access.md(v1.7.0)写明:该
hook 在 6.2 被重构为
security_file_truncate(内核提交
3350607dc563);符号往往不再作为可 kprobe
入口。策略引用不存在的 call 时,整份 TracingPolicy
加载失败,于是读/写那两段 hook
也不会出事件。修复方向是换成
security_file_truncate(参数类型改为
file),或两边都写并设
ignore.callNotFound: true。
另一条文档限制:策略匹配的是应用用来访问的那条文件名。经硬链接或不同 bind mount 访问同一文件,不会产生事件。这与 Bishop & Dilger 1996 讨论的「按路径检查、按 inode 使用」是同一类间隙,本篇只把它写成观测失败模式;强制执行侧的 TOCTOU 放到第 10 篇。
二、网络 / socket:层选错,运算符就不成立
1. 参数类型不是「随便填 sock」
argument_types.md 把网络对象拆开:
| 类型 | 内核对象 | matchArgs 可用运算符 |
|---|---|---|
sock |
struct sock(网络层) |
SAddr / DAddr /
SPort / DPort /
Protocol / Family /
State |
socket |
struct socket(BSD 套接字层) |
同上 |
skb |
struct sk_buff |
地址/端口/协议及 Not* 变体;地址支持
CIDR |
sockaddr |
struct sockaddr |
只有 SAddr /
SPort /
Family;没有
DAddr / DPort |
sockaddr_un |
struct sockaddr_un |
Equal / Prefix /
Family 等;抽象套接字名带前导
@;无端口/协议/State |
net_device |
struct net_device |
设备名字符串 |
把 connect() 的地址参数标成
sock、或在 sockaddr 上写
DAddr,属于未在该类型文档中出现的字段用法,本篇不发明。
2. 最小 YAML(网络)
tag examples/tracingpolicy/tcp-connect.yaml
挂的是内核 TCP 函数,不是 sys_connect:
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: "connect"
spec:
kprobes:
- call: "tcp_connect"
syscall: false
args:
- index: 0
type: "sock"
- call: "tcp_close"
syscall: false
args:
- index: 0
type: "sock"
- call: "tcp_sendmsg"
syscall: false
args:
- index: 0
type: "sock"
- index: 2
type: int这是「已经进入 TCP 状态机」的观测:Unix 域套接字、未走到
tcp_connect 的 UDP、以及在 LSM
security_socket_connect
就被拒绝的连接,都不会出现在这三条 kprobe 上。
对照 tag
examples/tracingpolicy/security-socket-connect.yaml:hook
security_socket_connect,参数用
sockaddr,选择器用 Family 限制
AF_INET /
AF_INET6。这是更靠前的安全检查点,看见的是连接参数而不是已建立的
struct sock。v1.7.0 变更日志还点了
sockaddr_un 解码(PR #3980):容器运行时的
containerd.sock / docker.sock 一类
AF_UNIX
路径可以出现在事件里,前提是内核与参数类型对得上。
selectors.md 另有 TrackSock /
UntrackSock:在 hook 处把 sock
放进 BPF map,供后续 hook 引用。文档要求成对维护,否则 map
会坏;且 仅内核 ≥
5.3。本篇不展开成套跟踪协议。
失败表象对照:
| 预期 | 实际 hook | 常见缺口 |
|---|---|---|
| 「所有出站连接」 | 只挂 tcp_connect |
非 TCP、被 LSM 提前拒绝、内核绕过该函数的路径 |
| 「connect 参数」 | sockaddr 却写 DAddr |
该类型没有目的运算符 |
| 「Unix 套接字路径」 | 未用 sockaddr_un |
路径与抽象名(@...)不会出现 |
三、能力与 cred:记录安装时刻的凭证,不翻译 user namespace root
1. msg_cred
里有什么
bpf/lib/bpf_cred.h 把凭证收成
struct msg_cred:uid/gid 的 real / saved /
effective / fs 四套、securebits、三份
capability 位图(permitted /
effective / inheritable)、以及
msg_user_namespace(level、uid、gid、ns_inum)。capability
也可以经 c[3] 数组访问,下标与
caps_permitted / caps_effective /
caps_inheritable 对齐。
执行相关标志与用户态共享:EXEC_SETUID、EXEC_SETGID、EXEC_FILE_CAPS、EXEC_SETUID_ROOT、EXEC_SETGID_ROOT。__is_uid_global_root()
只判断
uid == 0,注释写明当前不支持按 user
namespace 翻译「该命名空间里的 root」。容器里
uid=0 但映射到宿主机非 0
的情况,不能靠这个辅助函数当「宿主机特权」结论。
argument_types.md
对应的观测积木:cred 取整份
struct cred;cap_permitted /
cap_effective / cap_inheritable /
kernel_cap_t / capability
取能力集合或单次检查值。
2. 最小 YAML(能力 / cred)
tag
examples/tracingpolicy/kprobe_commit_creds.yaml
挂的是安装新凭证的内核函数:
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: "commit-creds"
spec:
kprobes:
- call: "commit_creds"
syscall: false
args:
- index: 0
type: "cred"这是「凭证被提交」的观测,不是 capset(2)
系统调用本身。只挂 sys_setuid(tag
examples/tracingpolicy/sys_setuid.yaml 把 uid
标成 int)会漏掉文件
capabilities、setresuid、以及内核路径里直接
commit_creds 的提权。
另一则 tag 示例 fd_install_caps.yaml 不解析
capset,而是用选择器 matchCapabilities:在
fd_install 上要求 Effective 集合
In CAP_CHOWN /
CAP_NET_RAW(isNamespaceCapability: true)。这是「带某能力的进程安装
fd」过滤,不是「能力集合如何变化」的时间线。
失败表象:
| 模式 | 失败 |
|---|---|
只挂 sys_setuid |
非该 syscall 的凭证变更不可见 |
| 把 namespace root 当全局 root | bpf_cred.h 明确不做 userns 翻译 |
用 matchCapabilities 当审计日志 |
未匹配则无事件;不是 capset 前后差分 |
四、三类观测 × 失败表象
flowchart TD
hook["Choose hook"] --> obj["Typed kernel object"]
obj --> path["d_path_local / sock extract / msg_cred"]
path --> sel["In-kernel matchArgs"]
sel -->|"resolved string or fields match"| post["Post event"]
path -->|"UNRESOLVED_PATH_COMPONENTS"| missPath["Prefix match misses"]
hook -->|"symbol missing e.g. truncate 6.2+"| loadFail["Whole policy fails to load"]
hook -->|"syscall pointer"| toctou["User memory changed after check"]
| 观测面 | 推荐模式(v1.7.0 文档/示例) | 失败表象 |
|---|---|---|
| 文件 | security_file_permission +
type: file;截断注意 6.2 符号 |
硬链接/bind mount 无事件;路径未解析;truncate hook 拖垮整份策略 |
| 网络 | tcp_connect + sock,或
security_socket_connect +
sockaddr |
只看见 TCP;sockaddr 上误用
DAddr;Unix 路径未用
sockaddr_un |
| 能力 | commit_creds + cred,或
matchCapabilities |
漏掉非 setuid syscall;userns root 被当成全局 root |
开放问题(本篇立靶,第 10、13 篇回收):生产策略能否接受「按路径字符串匹配」与「按 inode 使用」之间的差;以及 LSM hook 随内核重构消失时,加载失败是否被当成「没有攻击」而不是「策略没挂上」。
五、学术谱系、争论与开放问题
谱系:Bishop & Dilger, Checking
for Race Conditions in File Accesses, Computing
Systems 9(2), 1996
把文件访问里「检查用的名字」和「随后使用的对象」拆开。Garfinkel,
Traps and Pitfalls, NDSS 2003 §4.3
把同一问题写进系统调用插入式安全工具:在用户指针上做检查,内核稍后才用。Tetragon
的文件观测默认偏向 LSM security_ hook(Wright,
Cowan, Smalley, Morris, Kroah-Hartman, Linux Security
Modules, USENIX Security 2002 给出 hook
框架),是为了在内核驻留对象上工作,而不是在 syscall
字符串上工作。路径字符串仍由 d_path
同类遍历生成,硬链接与 bind mount
限制说明:对象身份与路径字符串仍不是同一键。
争论:syscall 覆盖面 vs LSM 稳定性
| 侧 | 主张 | 代价 |
|---|---|---|
| 挂 syscall | 心智接近 strace;符号相对稳定(Tetragon
可补 __x64_ 前缀) |
用户指针 TOCTOU;一个语义对应多个 syscall |
挂 security_* / tcp_* |
数据已在内核对象;一个 hook 覆盖多条 syscall 路径 | 符号随内核重构消失;整份策略加载失败 |
没有文献宣布某一侧在所有内核版本上更优。可检验命题是:策略加载失败是否有独立信号(第 12–13 篇),以及路径未解析是否被当成「无访问」。
开放问题:
- 路径键 vs inode 键:硬链接与 bind mount
下按前缀过滤的漏报,能否被策略作者在不引入 inode
匹配的前提下接受。入口:v1.7.0
filename-access.mdLimitations;Bishop & Dilger 1996。 - hook
符号寿命:
security_path_truncate→security_file_truncate这类重构,加载失败是否应 fail-open 到其余 hook。入口:同文档 Kernel compatibility;ignore.callNotFound。 - user namespace
根:
__is_uid_global_root不做 userns 翻译时,「特权进程」事件的语义边界。入口:bpf/lib/bpf_cred.h。
六、小结
- 文件观测的默认模式是 LSM hook +
file/path类型,由__d_path_local生成字符串;走不完则UNRESOLVED_PATH_COMPONENTS。 dentry停在一个挂载点;硬链接与 bind mount 按文件名匹配会漏。- 网络层
sock/sockaddr/sockaddr_un的运算符集合不同;tcp_connect不是全部套接字。 commit_creds+cred看见凭证安装;全局 root 判断不翻译 user namespace。- v1.7.0 示例
filename_monitoring.yaml在 6.2+ 内核上可能因 truncate 符号整份加载失败。
下一篇把这些 hook 上的动作从观测推进到 Override / SIGKILL,并钉 TOCTOU 仍拦不住什么。
七、参考资料
规范 / 官方文档(A)
- Tetragon v1.7.0 Hook points —
docs/content/en/docs/concepts/tracing-policy/hooks.md(syscall 指针 TOCTOU;fd_install示例)。 - Tetragon v1.7.0 Argument types —
docs/content/en/docs/concepts/tracing-policy/argument_types.md(file/path/dentry/fd/sock/sockaddr/cred;v5.3 完整路径)。 - Tetragon v1.7.0 Selectors —
docs/content/en/docs/concepts/tracing-policy/selectors.md(FollowFD 废弃;TrackSock ≥5.3)。 - Tetragon v1.7.0 Filename access —
docs/content/en/docs/use-cases/filename-access.md(三 hook 模式;6.2 truncate;硬链接限制)。
源码(A)
bpf/lib/bpf_d_path.h(d_path_local、__d_path_local、UNRESOLVED_PATH_COMPONENTS、PROBE_CWD_READ_ITERATIONS)@ v1.7.0bpf/lib/bpf_cred.h(msg_cred、__is_uid_global_root)@ v1.7.0examples/tracingpolicy/filename_monitoring.yaml、tcp-connect.yaml、kprobe_commit_creds.yaml、security-socket-connect.yaml、fd_install_caps.yaml、write.yaml@ v1.7.0- Linux
3350607dc563(security_path_truncate在 6.2 的重构;经 tag 文档引用)
核心论文
- 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.
- Wright, Cowan, Smalley, Morris, Kroah-Hartman. Linux Security Modules: General Security Support for the Linux Kernel. USENIX Security 2002.
站内对照
实验台账
- 本篇无节点命令输出;不伪造路径解析结果或
tetraJSON。
→ 上一篇:K8s 身份感知策略 · 系列目录 · 下一篇:强制执行
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【Tetragon / eBPF】运行时安全全景:缺口、五轴坐标系与 16 篇路线
相对 Cilium 数据面、可观测对照与 Falco 口号,钉清 Tetragon v1.7.0 运行时安全内核缺口;定义进程血缘、Sensor/Hook、策略加载、过滤分层、强制执行五条坐标系,给出 16 篇阅读路线。
【Tetragon / eBPF】Sensor 与 Hook:进程传感器、kprobe、tracepoint、uprobe、LSM 与 fentry
钉清 Tetragon v1.7.0 内置 __base__ 进程传感器与 TracingPolicy 动态 hook 的分工:kprobe/kretprobe、tracepoint/rawtp、uprobe/USDT、LSM 与 fentry 各自能观察什么、依赖哪些内核能力,以及选错 hook 类型时的失败表象。
【Tetragon / eBPF】Agent 生命周期:BTF、安装形态与三条策略加载路径
钉清 Tetragon v1.7.0 agent 从 BTF/CO-RE 发现到 sensor 挂载的失败表象;对照 DaemonSet 与主机安装;说明默认 gRPC Unix socket 与 kubectl/gRPC/静态文件三条路径如何进入同一 collection map。
【Tetragon / eBPF】TracingPolicy:CRD、三条加载路径与 collectionKey
钉清 TracingPolicy 与 TracingPolicyNamespaced 的差异;给出 v1.7.0 CRD 字段总表(含 fentries);说明 kubectl、gRPC、静态文件如何共用 collectionKey;并写明 Enable/Disable gRPC 在默认配置下返回错误。