2. eBPF程序生命周期:加载、验证、附加、分离、卸载,以及每个阶段对租户隔离的影响

eBPF程序从编写到销毁,经历五个核心阶段:加载、验证、附加、分离、卸载。每个阶段都是内核安全模型的检查点,也是租户隔离策略的生效锚点。理解各阶段内核态与用户态的交互,是设计多租户eBPF控制平面的基础。

核心认知: eBPF生命周期不是简单的“加载→运行→卸载”线性流程。验证阶段是隔离的第一道防线,附加阶段决定隔离边界,分离与卸载阶段则影响资源回收与残留风险。多租户场景下,每个阶段都必须有明确的租户上下文标记。

2.1 生命周期全景:五阶段与内核交互点

下图展示eBPF程序从用户态提交到内核态销毁的完整路径,以及每个阶段涉及的关键内核组件与租户隔离检查点。

eBPF程序生命周期五阶段与租户隔离检查点 eBPF程序生命周期五阶段与租户隔离检查点 ① 加载 bpf() syscall ② 验证 verifier + BPF LSM ③ 附加 attach to hook ④ 分离 detach from hook ⑤ 卸载 free resources 每个阶段均需执行租户隔离检查:租户身份验证 → 资源配额校验 → 权限边界确认 内核组件交互: bpf() 系统调用 BPF Verifier BPF LSM / Capability Map / Prog Array 各阶段对租户隔离的影响: • 加载阶段:校验租户身份与资源配额,拒绝超限请求 • 验证阶段:检查程序是否越权访问其他租户的map或上下文 • 附加阶段:绑定租户标签到hook点,确保事件过滤正确 • 分离阶段:清理hook点上的租户上下文,防止残留 • 卸载阶段:回收所有租户关联资源,避免内存泄漏

2.2 加载阶段:身份注入与配额校验

加载是eBPF程序进入内核的第一道门。用户态通过 bpf() 系统调用传入程序字节码,内核在创建 struct bpf_prog 对象时,必须完成租户身份绑定。

隔离关键点: 加载阶段必须将租户ID(Tenant ID)写入程序元数据。内核通过 bpf_prog->aux->tenant_id 字段(需内核补丁支持)或cgroup/namespace上下文推导租户归属。控制平面应在加载前完成配额检查,避免无效加载消耗内核资源。
加载子阶段 内核操作 租户隔离检查项 失败后果
字节码复制 copy_from_user() 校验用户态地址合法性 返回 -EFAULT
程序元数据创建 分配 bpf_prog 结构体 注入租户ID、检查租户程序总数配额 返回 -ENOSPC
许可证检查 GPL兼容性验证 (全局策略,不区分租户) 返回 -EINVAL
运行环境初始化 设置prog type、expected attach type 校验租户是否被允许使用该prog type 返回 -EPERM

2.3 验证阶段:隔离的第一道防线

BPF验证器(verifier)是内核最严格的代码审查器。它执行深度优先遍历,模拟每条指令的执行路径,确保程序不会崩溃内核、不会泄露信息、不会越权访问。在多租户环境中,验证器必须额外检查跨租户访问。

验证阶段的租户隔离检查流程 验证阶段的租户隔离检查流程 CFG构建 控制流图生成 指令模拟 逐路径状态跟踪 安全检查 越界/空指针/类型 隔离检查 跨租户访问审计 租户隔离专项检查 1. map访问:检查map所属租户是否与程序租户一致 2. helper调用:禁止调用跨租户数据泄露的helper 3. 指针泄漏:禁止将内核指针传递给其他租户 4. 上下文访问:限制只能访问本租户的事件上下文
隔离风险提示: 标准BPF验证器不感知租户概念。如果内核未打租户隔离补丁,验证器不会阻止程序访问其他租户的map。这是多租户eBPF部署中最常见的安全漏洞。必须通过BPF LSM或自定义验证器扩展来强制执行跨租户访问控制。

2.4 附加阶段:绑定租户上下文到Hook点

验证通过后,程序通过 BPF_PROG_ATTACH 或 BPF_RAW_TRACEPOINT_OPEN 等命令附加到指定事件源。附加操作将程序与内核中的hook点关联,此时必须将租户上下文写入hook点的私有数据区。

附加类型 Hook点示例 租户上下文绑定方式 隔离粒度
cgroup BPF cgroup/{inet_ingress, inet_egress} cgroup ID 映射到租户ID 容器/Pod级别
tc BPF sch_clsact / sch_ingress 网络命名空间 + 设备索引 网络设备级别
XDP netdev 驱动层 接口索引 + 租户标签 物理/虚拟网卡级别
tracepoint/kprobe 系统调用、内核函数 当前进程的cgroup/namespace 进程/容器级别
LSM BPF security_* hook BPF LSM 上下文中的租户字段 内核对象级别

2.5 分离阶段:清理Hook点上的租户残留

分离操作通过 BPF_PROG_DETACH 或关闭程序文件描述符触发。内核将程序从hook点的程序链表中移除,但不会立即释放程序本身(引用计数机制)。

隔离关键点: 分离阶段必须清理hook点上缓存的租户上下文。如果hook点维护了per-tenant的统计计数器或过滤规则,分离时必须同步清除,防止后续附加的其他租户程序读取到残留数据。控制平面应在分离后执行租户资源记账更新。

分离阶段的隔离检查清单:

  • Hook点清理: 确认程序已从hook点的程序数组中移除,无并发执行路径
  • 租户上下文清除: 删除hook点私有数据中与该程序关联的租户标签
  • 引用计数检查: 确保无其他hook点或map仍引用该程序
  • 审计日志记录: 记录租户ID、程序ID、分离时间、分离原因

2.6 卸载阶段:资源回收与隔离终结

当程序的引用计数降为零(所有文件描述符关闭、所有hook点分离),内核触发 __bpf_prog_put() 释放 bpf_prog 结构体及其关联资源。这是租户隔离的最后一道保障。

资源类型 回收动作 隔离影响 泄漏风险
指令镜像 (insnsi) kfree(prog->insnsi) 释放租户代码内存 低
JIT编译代码 bpf_jit_free(prog) 释放可执行内存 中(需确认无其他CPU执行)
map引用 bpf_map_put() 递减引用 可能触发map卸载 高(循环引用导致泄漏)
辅助数据结构 释放btf、line_info等 释放调试元数据 低
租户审计记录 更新租户资源统计 扣减租户程序计数 中(计数不一致)
隔离风险提示: map的引用计数是卸载阶段最常见的泄漏点。如果程序A引用了租户B的map,程序A卸载时未正确释放map引用,会导致租户B的map无法卸载,造成跨租户资源泄漏。控制平面必须维护全局的“程序↔map”引用关系图,在卸载时执行完整性检查。

2.7 生命周期各阶段隔离策略总结

下表汇总五个阶段的核心隔离策略、检查时机与推荐实现方式,供控制平面设计参考。

生命周期阶段 核心隔离策略 检查时机 推荐实现方式
加载 租户身份注入 + 配额校验 bpf() 系统调用入口 内核补丁:bpf_prog->aux->tenant_id
验证 跨租户访问控制 + 信息泄漏防护 verifier 指令模拟阶段 BPF LSM + verifier 扩展
附加 租户上下文绑定 + hook点隔离 BPF_PROG_ATTACH 命令处理 cgroup/namespace 映射 + 租户标签
分离 hook点清理 + 租户残留清除 BPF_PROG_DETACH 或 fd关闭 hook点私有数据清理回调
卸载 资源回收 + 引用完整性检查 __bpf_prog_put() 执行 引用关系图 + 循环引用检测
设计建议: 控制平面应在用户态维护一份“租户→程序→map→hook点”的完整拓扑图。每次生命周期变更时,先在图谱中模拟执行隔离检查,通过后再下发到内核。这种“先模拟、后执行”的策略可以避免内核态隔离检查失败导致的程序拒绝服务。