文章

eBPF 核心机制与安全

eBPF 核心机制与安全

eBPF 是什么(一段简史)

阶段名称能力
1992BPF(cBPF)仅用于包过滤(tcpdump),基于很有限的虚拟机指令
2014eBPF(extended BPF)通用字节码引擎,不止网络;引入 map、helper、verifier
2016+成熟挂载点扩展到 kprobe/tracepoint/XDP/cgroup/socket/LSM,成为可观测性与网络事实标准

今天说”BPF”和”eBPF”基本同义;“cBPF”才是老式包过滤器。

程序执行生命周期

graph TD
    A[用户写 eBPF 程序 C/Python] --> B[LLVM/clang 编译为 eBPF 字节码]
    B --> C["bpf() syscall 加载到内核"]
    C --> D{Verifier 静态校验}
    D -->|拒绝| Z[加载失败 报错]
    D -->|通过| E[JIT 编译为本地机器码]
    E --> F[attach 到 Hook 点]
    F --> G[事件触发时在内核态执行]
    G --> H[(读写 BPF Map)]
    H --> I[用户态工具读结果/下发配置]

关键点:加载(bpf syscall)只有一次,运行时触发(hook 事件)是纯内核态执行,用户态不再参与每条事件的处理。

Verifier(校验器)—— 安全的根基

传统内核模块(LKM)有 bug 就 panic 整个内核。eBPF 靠 verifier 在加载前把危险程序挡在门外。它主要检查:

检查项具体内容
指针校验只允许访问被批准的上下文(ctx)和 map 内的内存;禁止任意解引用、禁止未初始化指针读
有界循环不允许无限循环;循环必须有 verifier 可证明的退出上界(早期版本直接禁止循环,5.x 起允许有界循环)
指令数上限单程序指令数上限约 100 万(防止 DoS)
栈大小限制栈上限 512 字节(需更大用 map 存)
可达性不可达指令会被拒绝
helper 白名单只能调用当前上下文允许的 bpf_helper 函数(如 bpf_probe_read/bpf_trace_printk
能力检查某些 helper(如修改 skb)需对应 CAP_* 能力

结论:eBPF 程序”要么被 verifier 拒绝,要么安全运行”,不存在”跑起来再崩内核”的中间态。

JIT 编译

verifier 通过后,字节码由内核 JIT 编译器(如 x86/arm64 后端)编译为本地机器码,直接执行,而非解释执行——性能接近原生 C 函数。可通过 bpftool prog show 看是否 jited

Hook 点全景(深入)

eBPF 程序必须挂载到某个 hook 才能生效。不同 hook 对应不同程序类型与能力:

Hook 点挂载位置触发时机典型用途程序类型
kprobe任意内核函数入口进入该函数时函数级追踪、延迟统计BPF_PROG_TYPE_KPROBE
kretprobe任意内核函数返回该函数返回时函数耗时、返回值同上
uprobe用户态程序函数进入用户态函数时追踪 MySQL/Redis/Nginx 内部BPF_PROG_TYPE_KPROBE
tracepoint内核静态稳定插桩点内核到达该埋点时稳定的 syscall/调度事件BPF_PROG_TYPE_TRACEPOINT
perf_event性能计数器采样/ PMC 溢出时CPU 火焰图、硬件计数BPF_PROG_TYPE_PERF_EVENT
XDP网卡驱动最早收包点驱动刚拿到 skb(甚至前)DDoS 防护、高性能 LB、包过滤BPF_PROG_TYPE_XDP
TC(clsact)网络栈 ingress/egress包进出协议栈时容器网络策略、流量整形BPF_PROG_TYPE_SCHED_CLS
cgroupcgroup v2 层级该 cgroup 内流量/套接字事件按 cgroup 的流量/套接字策略BPF_PROG_TYPE_CGROUP_SKB
socket / sock_opssocket 层连接建立/数据收发时socket LB、无 sidecar 加速BPF_PROG_TYPE_SOCK_OPS
LSM内核安全钩子安全决策点(open/exec 等)运行时安全策略BPF_PROG_TYPE_LSM

应用型重点记三类:XDP/TC(网络)、kprobe/tracepoint(观测)、cgroup/socket(容器级策略)。Cilium 主要吃 XDP + TC + socket。

程序类型 vs attach 类型

  • 程序类型(prog type):决定程序能用哪些 helper、能访问什么上下文(如 BPF_PROG_TYPE_XDP 拿到 xdp_md 而非 pt_regs)。
  • attach 类型(attach type):决定程序挂到哪个具体 hook(如同一个 cgroup_skb 程序可 attach 为 ingressegress)。
  • 二者必须匹配,否则 bpf() 加载报 EINVAL

BPF Map 深入(内核态↔用户态桥梁)

eBPF 程序本身无状态——不能声明持久全局变量。所有跨事件、跨内核/用户态的数据都走 BPF Map

Map 类型用途容量行为
hash / array通用 KV 统计(每 PID 的 syscall 计数)满则写入失败(-E2BIG
percpu_hash / percpu_array每 CPU 独立计数,避免多核抢占高性能场景必备;满静默丢弃
lru_hash满时淘汰最久未用条目(缓存语义)不报错,自动淘汰
ringbuf / perf_event_array事件流(每条连接元数据推用户态)ringbuf 满则事件丢失
lpm_trie最长前缀匹配Cilium 路由/LB 查找核心
sockmap / sockhashsocket 重定向socket LB、无 sidecar 加速
stack_trace栈追踪(火焰图)
sock存 socket 引用

percpu_* / ringbuf 满后静默丢弃不报错——观测数据”看起来变少”可能是 map 爆了(详见 eBPF 排障实战 生产反模式)。

Cilium 的 Service 负载均衡用 lpm_trie + sockmap 存 “VIP:Port → 后端 Endpoint” 映射,包进来直接查 map 改写,全程不过 iptables。

eBPF vs 传统内核模块(LKM)

维度LKMeBPF
安全性bug 可 panic 内核verifier 预校验,安全
加载insmod,需编译匹配内核LLVM 编译字节码,运行时加载
数据出口自己实现(procfs/debugfs)标准 BPF Map
热更新需卸载/重装可原子替换(bpftool)
崩溃影响可能整机挂程序拒绝加载或异常被隔离

关联知识

学习时间

阶段时间备注
核心机制2026-07-16verifier 检查清单、JIT、Hook 点触发时机、Map 全类型与容量行为、prog/attach 类型区分