ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

youki 的 libseccomp crate 深度解析:Rust 容器运行时中的 seccomp FFI 绑定与内核安全隔离实践

youki 的 libseccomp crate 深度解析:Rust 容器运行时中的 seccomp FFI 绑定与内核安全隔离实践 容器运行时云原生【免费下载链接】youkiA container runtime written in Rust项目地址https://gitcode.com/gh_mirrors/yo/youki点击查看免费下载本文以 docs/src/user/libseccomp.md 为骨架结合 youki 仓库中 libcontainer 的 seccomp 模块源码、初始化流程与实验性纯 Rust 实现系统讲解 seccomp 内核特性、libseccompFFI 绑定的由来与边界以及 youki 如何在容器 init 进程中把 OCI seccomp 配置翻译成真实的内核过滤器。读完你可以掌握seccomp 在容器场景下的作用机制、youki 中libseccompcrate 的依赖与特性开关、initialize_seccomp的完整实现链路以及仓库中围绕该主题的测试与替代方案。一、先理解背景seccomp 是什么在深入代码之前先明确 seccomp 这一内核特性的本质。正如开发者文档 docs/src/developer/libseccomp.md 所描述的Seccomp 是 Linux 内核提供的一项特性它允许进程进行一次性、不可逆地切换到安全模式。在该模式下进程能够发起的系统调用syscall受到限制对文件描述符的操作也受到约束。具体来说进程被限制为只能执行exit、sigreturn以及读写已打开的文件描述符。也就是说seccomp 让一个进程在内核层面被隔离起来限制它与系统其余部分交互的方式——即使进程本身被攻破、代码被恶意篡改它也无法随意调用execve、socket、open等危险系统调用攻击面被大幅收窄。seccomp 的完整定义可参考其 man pageman 2 seccomp其核心机制是过滤器filter以经典的 BPFcBPF程序形式描述哪些系统调用允许、哪些拒绝动作action命中过滤器后执行的动作如KILL、ERRNO、TRACE、ALLOW、NOTIFY等单向性过滤器一旦安装通过prctl(PR_SET_SECCOMP)或seccomp(2)系统调用便无法撤销除非进程先设置了no_new_privileges或有相应特权。二、libseccompcrate 的定位绑定层而非实现层原文档开篇即点明该 crate 的本质这个 crate 并没有实际实现任何特定功能而是为 seccomp 模块提供 Rust FFI 绑定。这些绑定主要通过 rust-bindgen 对 seccomp 的 C 头文件生成随后针对发现的问题进行人工修正。由于 rust-bindgen 在处理 C 函数宏function macros时存在一些问题这些宏被手工修复。翻译成工程语言它不重新实现 seccomp 策略引擎而是把 C 库 libseccomp 的 APIseccomp_init、seccomp_rule_add、seccomp_load、seccomp_arch_add等以unsafe extern函数和对应数据结构的形式暴露给 Rust绑定来源是 rust-bindgen即对 libseccomp 提供的 C 头文件自动生成FFI声明属于典型的自动生成 人工修补路线人工修正集中在 C 函数宏C 头文件中大量使用宏如把SCMP_ACT_ERRNO(x)、SCMP_ARCH_X86_64等定义为带参数的宏rust-bindgen 无法直接翻译它们youki 团队在生成结果上手工补齐了这些类型常量与构造函数的映射。因此在 youki 的代码中你永远不会直接写seccomp_init(...)这样的 C 调用而是使用libseccomp::ScmpFilterContext、ScmpAction、ScmpArch、ScmpArgCompare、ScmpCompareOp、ScmpSyscall这些 Rust 安全的封装类型。这正是 FFI 绑定层的价值把 C 的指针与错误码语义包装成 Rust 的类型安全接口。从仓库结构看工作区根 Cargo.toml 将libseccomp声明为共享依赖libseccomp 0.4.0即使用当前 crates.io 上最新的 0.4.x 版本。三、依赖关系与特性开关如何引入与关闭libseccomp绑定并非直接挂在 youki 主 crate 下而是作为libcontainer容器控制核心库的可选依赖。看 crates/libcontainer/Cargo.toml[features] default [systemd, v2, v1, libseccomp] libseccomp [dep:libseccomp]同时在依赖段中它是 optional 的libseccomp { workspace true, optional true }这带来两个重要工程含义默认开启default特性集合包含libseccomp因此常规构建cargo build会自动启用 seccomp 支持可裁剪通过--no-default-features -F v2之类的组合可以关闭它。关闭后libcontainer 中所有 seccomp 相关代码都会被#[cfg(feature libseccomp)]条件编译掉例如 crates/libcontainer/src/lib.rs 中的mod seccomp;声明。3.1 为什么需要关闭它musl 静态编译的限制libseccomp 是一个 C 库无法在纯 musl 静态环境下链接。这一点在 crates/libcontainer/README.md 中有明确说明要以 musl 构建必须先移除 libseccomp 依赖因为它会引用无法用 musl 构建的共享库libseccomp。具体做法是使用--no-default-features关闭默认特性再用-F显式开启需要的特性如v2。README 给出了完整的 nightly musl 构建命令# 为 rustup nightly 添加 musl 目标 rustup nightly target add $(uname -m)-unknown-linux-musl # 安装 nightly musl 工具链 rustup nightly toolchain install nightly-$(uname -m)-unknown-linux-musl # 使用 -Zbuild-std 构建 musl 标准库 cargo nightly build -Zbuild-std --target $(uname -m)-unknown-linux-musl --no-default-features -F v2 cargo nightly build --target $(uname -m)-unknown-linux-musl --no-default-features -F v2这也解释了 why当目标平台或出于体积、无 glibc 环境考虑无法链接 C 库时youki 仍可编译运行只是 seccomp 能力被降级——运行时会输出seccomp not available之类的告警见下文初始化流程。四、核心实现剖析initialize_seccomp全流程youki 把OCI seccomp 配置 → 内核过滤器的翻译工作全部封装在 crates/libcontainer/src/seccomp/mod.rs 的initialize_seccomp函数中。它的输入是oci_spec::runtime::LinuxSeccompOCI 运行时规范中linux.seccomp的结构化 Rust 表示输出是ResultOptionio::RawFd——当配置中包含NOTIFY动作时返回监听 fd。整个流程可以拆成七个步骤4.1 前置校验check_seccomp在创建任何过滤器之前先做合法性检查mod.rs#L119-L147NOTIFY不允许作为默认动作如果default_action ScmpActNotify直接返回NotifyAsDefaultAction错误。原因在源码注释中写得很清楚过滤器一旦以 notify 创建容器进程就必须把返回的 fd 交给另一个进程去处理而这又依赖write系统调用若默认动作就是 NOTIFYwrite 也会被过滤拦截进程将直接卡死。runc同样禁止这种做法。NOTIFY不允许用于write系统调用遍历所有 syscall 规则若某条规则动作为ScmpActNotify且名字是write返回NotifyWriteSyscall错误理由同上read/close 也被刻意保留因为接收方处理完通知后需要正常放行这些调用。4.2 创建过滤器上下文let default_action translate_action(seccomp.default_action(), seccomp.default_errno_ret())?; let mut ctx ScmpFilterContext::new(default_action).map_err(...)?;对应 C 层的seccomp_init(action)以一个默认动作创建过滤器上下文。默认动作决定未命中任何规则时怎么办通常容器配置为SCMP_ACT_ALLOW默认放行仅拦截黑名单或SCMP_ACT_ERRNO默认拒绝仅放行白名单。4.3 设置过滤器 flagOCI 规范的seccomp.flags支持四种内核 flag翻译逻辑mod.rs#L161-L176OCI flagLinuxSeccompFilterFlaglibseccomp 调用内核含义SECCOMP_FILTER_FLAG_LOGset_ctl_log(true)未命中的 syscall 记录到审计日志SECCOMP_FILTER_FLAG_LOGSECCOMP_FILTER_FLAG_TSYNCset_ctl_tsync(true)同步应用到进程组所有线程SECCOMP_FILTER_FLAG_TSYNCSECCOMP_FILTER_FLAG_SPEC_ALLOWset_ctl_ssb(true)允许使用被 SSBSpectre v2缓解禁用的 spec 存储绕过缓解SECCOMP_FILTER_FLAG_SPEC_ALLOWSECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECVset_ctl_waitkill(true)处于SECCOMP_RET_USER_NOTIF等待中的进程可被 kill 信号打断SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV注意这里set_ctl_*系列正是 libseccomp 提供的上下文属性设置API 的绑定属于文档所述FFI 绑定在 youki 中的典型用法。4.4 添加架构for arch in architectures { ctx.add_arch(translate_arch(arch))... }通过translate_arch把 OCI 的Arch枚举映射为 libseccomp 的ScmpArch。完整的映射表见 mod.rs#L55-L82覆盖了Native、X86、X86_64、X32、Arm、Aarch64、Mips/Mips64/Mips64n32/Mipsel*、Ppc/Ppc64/Ppc64le、S390/S390x、Riscv64、Parisc*、Loongarch64、M68k、Sh、Sheb等全部 OCI 规范定义的架构为跨架构容器镜像如 x86_64 上跑 arm64 用户态提供过滤支持。4.5 关闭自动 NNPset_ctl_nnp(false)这是 youki 一个精心设计的细节mod.rs#L186-L194ctx.set_ctl_nnp(false)libseccomp 的SCMP_FLTATR_CTL_NNP属性控制seccomp_load时是否自动通过prctl设置no_new_privileges位。正常情况下这很方便但对 OCI 运行时而言如果 spec 的process.noNewPrivileges没有显式开启运行时就不应该替用户设置它这属于进程语义的一部分。所以 youki 主动关掉自动行为把 NNP 的决策权交还给 init 流程自身见第五节。如果因特权不足导致 load 失败就让它失败——不悄悄放宽安全语义。4.6 添加 syscall 规则无参数与带参数遍历seccomp.syscalls()中的每条规则mod.rs#L196-L298跳过冗余规则如果某条规则的动作与默认动作相同说明它不改变任何行为直接continue并打 warn——这既省一次内核过滤器空间也避免 libseccomp 在部分内核上对与默认动作相同的规则报错。按名解析 syscallScmpSyscall::from_name(name)。若解析失败例如当前内核版本不支持该 syscall源码选择skip 并告警而非报错如果无法按名解析很可能内核不支持这个 syscall跳过是安全的。无参数规则ctx.add_rule(action, sc)对应 C 层seccomp_rule_add对 syscall 的所有调用形态生效。带参数规则ctx.add_rule_conditional(action, sc, comparators)对应seccomp_rule_add_exact_array带条件版本。每个条件用ScmpArgCompare::new(index, op, value)描述第 index 个参数需满足某比较关系。参数规则中还有一个与 runc 对齐的细节libseccomp 允许一条规则里有多个参数比较但每个参数索引只能比较一次。当 OCI 配置里出现同一参数索引被比较多次时源码用HashSet检测重复 indexyouki 遵循 runc 的行为——把每个条件拆成独立的规则分别添加add_rule_conditional每次只传单个 comparator。源码注释直接引用了 libseccomp 的seccomp_rule_add(3)手册和 runc 的seccomp_linux.go作为依据。4.7 加载过滤器并可选返回 notify fdctx.load()...; // 对应 seccomp_load安装过滤器此后不可撤销 let fd if is_notify(seccomp) { // 配置中存在 ScmpActNotify 规则时 Some(ctx.get_notify_fd()...) // 拿到 seccomp 通知 fd } else { None };load()通过SECCOMP_SET_MODE_FILTER安装过滤器。源码注释指出其前置条件调用线程要么在其用户命名空间内拥有CAP_SYS_ADMIN要么已经设置了no_new_privs位——这正是 4.5 节中 NNP 决策的意义所在。五、动作与比较符OCI 语义到 libseccomp 的翻译5.1 动作Action翻译表translate_actionmod.rs#L84-L105处理动作 errno 返回值两个维度。errno 缺省时默认使用EPERMlibc::EPERMOCI 动作libseccomp 动作行为SCMP_ACT_KILLScmpAction::KillThread终止触发过滤器的线程SCMP_ACT_KILL_PROCESSScmpAction::KillProcess终止整个进程内核 4.14SCMP_ACT_KILL_THREADScmpAction::KillThread同 KILL仅杀线程SCMP_ACT_TRAPScmpAction::Trap向触发线程发送SIGSYSSCMP_ACT_ERRNOScmpAction::Errno(errno)返回指定 errno默认EPERMSCMP_ACT_TRACEScmpAction::Trace(errno)通知 tracerptraceerrno 需转为 i16失败时报TraceActionSCMP_ACT_ALLOWScmpAction::Allow放行SCMP_ACT_NOTIFYScmpAction::Notify挂起 syscall等待用户态通知处理需要SECCOMP_FILTER_FLAG_NEW_LISTENERSCMP_ACT_LOGScmpAction::Log记录审计日志后放行内核 4.14注意ScmpActKill在 OCI 规范中被映射为 libseccomp 的KillThread这是为了与 runc 保持一致的语义取舍。5.2 比较符Operator翻译表translate_opmod.rs#L107-L117把 OCI 比较运算符映射为ScmpCompareOpOCI 运算符libseccomp 比较语义SCMP_CMP_NENotEqual不等于SCMP_CMP_LTLess小于SCMP_CMP_LELessOrEqual小于等于SCMP_CMP_EQEqual等于SCMP_CMP_GEGreaterEqual大于等于SCMP_CMP_GTGreater大于SCMP_CMP_MASKED_EQMaskedEqual(datum_b)(value mask) datum_b掩码取自 OCI 规则的valueTwo六、在容器 init 流程中的集成时机与顺序的艺术seccomp 过滤器一旦安装就不可撤销且安装本身依赖特权状态因此安装时机直接决定容器安全模型。youki 在 crates/libcontainer/src/process/init/process.rs 中按process.noNewPrivileges是否显式设置分两条路径noNewPrivileges未设置is_none()时——提前安装在丢弃 capabilities 之前初始化 seccomp。源码注释没有 no_new_privilegesseccomp 就是特权操作必须在丢 capabilities 之前做。否则后续 load 会因权限不足失败。此时若libseccomp特性被关闭则打印seccomp not available, unable to enforce no_new_privileges!告警。noNewPrivileges已设置is_some()时——尽量晚安装放到马上要 exec payload 之前让过滤器和 exec 之间的 syscall 数量最小化避免容器进程自身的启动代码意外撞上自己的过滤器。6.1 NOTIFY 场景init 进程与主进程的握手当 seccomp 配置包含NOTIFY规则时initialize_seccomp返回 notify fd。此时 init 进程通过 sync_seccomp 与主进程协作main_sender.seccomp_notify_request(fd)把 fd 通过进程间 channel 发送给主进程内部走 SCM_RIGHTS 完成 fd 的跨进程复制等待wait_for_seccomp_request_done()确认主进程已经拿到 fd 并交给 seccomp 监听器确认后 init 进程才安全地close(fd)fd 已在主进程侧复制。主进程侧的处理在 crates/libcontainer/src/process/container_main_process.rshandle_seccomp_notify构建容器进程状态seccomp_listener.rs#L86 的build_container_process_state将容器映射为 OCI 状态机中的creating或running连同 notify fd 一起通过sync_seccomp_send_msgSCM_RIGHTS 消息发送给用户配置的 seccomp 监听器seccompListenerPath。更值得注意的设计是 InitRequestSequence主进程把 init 侧的一次性请求按阶段排序——Setuphooks 与网络设备无顺序依赖可乱序到达→Seccomp必须在 hooks/网络之后因为过滤器一旦应用init 自身后续 syscall 都将受其约束→Ready。任何乱序或重复请求都会触发UnexpectedInitMessage错误。这个状态机用源码内注释与单元测试init_request_sequence_rejects_seccomp_while_setup_is_pending等双重保障了协议顺序。七、测试与验证仓库里如何证明它工作7.1 单元测试mod.rs#L327-L559seccomp 测试很难写默认的 kill/errno 动作会杀死测试进程本身Rust 测试框架也依赖 syscall。youki 的解法是全部在子进程中执行test_utils::test_in_child_process并刻意选用getcwd这个在 seccomp 规则下会干净地返回错误的 syscalltest_basic默认动作ALLOW对getcwd应用SCMP_ACT_ERRNO且 errno 设为EAGAINgetcwd自身永远不会返回 EAGAIN从而可以断定失败来自过滤器。子进程先prctl::set_no_new_privileges(true)加载过滤器后调用getcwd断言其错误码恰为EAGAINtest_moby直接加载仓库内的真实 fixture crates/libcontainer/src/seccomp/fixture/config.json——一份 Moby 风格的完整 OCI spec含noNewPrivileges: true、受限 capabilities、/proc掩码路径等在子进程中加载其 seccomp 配置验证真实配置可被正确翻译test_seccomp_notifygetcwd配SCMP_ACT_NOTIFY断言initialize_seccomp返回了 notify fdtest_seccomp_conditional_rule_multiple_distinct_args对socket同时约束arg0 AF_INET与arg1 SOCK_STREAM两条不同索引条件验证组合规则test_seccomp_conditional_rule_duplicate_arg_index对socket的arg0施加两个条件 AF_INET且! AF_UNIX验证重复索引被拆分为独立规则与 runc 对齐的逻辑test_seccomp_multiple_syscall_entries_for_same_name同一个socket名字出现两条不同规则的场景。这些测试全部带#[serial]注解——seccomp 过滤器是进程级的并行测试会互相污染串行执行是必要的。7.2 真实配置样例fixture 中 seccomp 相关配置的形态摘自 crates/libcontainer/src/seccomp/fixture/config.json节选{ linux: { seccomp: { defaultAction: SCMP_ACT_ALLOW, architectures: [SCMP_ARCH_X86_64, SCMP_ARCH_X86, SCMP_ARCH_X32], syscalls: [ { names: [personality], action: SCMP_ACT_ALLOW }, { names: [clone, clone3], action: SCMP_ACT_ALLOW, args: [ { index: 0, value: 2114060288, valueTwo: 0, op: SCMP_CMP_MASKED_EQ } ] } ] } } }这正是 OCI 运行时规范中linux.seccomp的 JSON 形态也是 youki 经oci-speccrate 解析后交给initialize_seccomp的输入格式。八、面向未来的替代实验纯 Rust 的 seccomp 实现仓库中还存在一个名为 experiment/seccomp 的实验性项目其 README 明确写道这是一个为了摆脱 libseccomp 而做的实验项目。也就是说libseccompFFI 绑定虽然好用但毕竟依赖外部 C 库。youki 团队正在探索一条完全用 Rust 实现的路径直接生成 cBPF 指令、直接调用seccomp(2)系统调用绕开 libseccomp。该实验位于 experiment/seccomp/src/seccomp.rsSeccomp::apply()直接以SECCOMP_SET_MODE_FILTERSECCOMP_FILTER_FLAG_NEW_LISTENER调用内核并把 BPF 程序VecInstruction提交给内核NotifyFd封装通知 fd支持recv()接收通知与success()应答SECCOMP_IOCTL_NOTIF_SEND等 ioctl定义了SeccompData、SeccompNotif、SeccompNotifResp、SeccompNotifSizes、SeccompNotifAddfd等repr(C)内核交互结构。配套的测试展示了它的两种用法experiment/seccomp/README.md# 应用示例 seccomp 过滤器包含 notify 接收与应答的完整演示 $ cargo test --test filter -- --show-output # 从 OCI JSON 读取配置并导出 BPF 指令 $ cargo test --test readjson -- --show-output其中 tests/filter.rs 通过send_fd/recv_fdSCM_RIGHTS演示了 notify fd 的跨进程传递handle_notifications循环打印每个被拦截 syscall 的 id/pid/nr 并应答tests/libseccomp_readjson.rs 则展示了从 OCI 配置 → 过滤器 →export_bpf导出 BPF 字节码的完整链路逐 8 字节打印code/jt/jf/k字段。这个实验既是对libseccomp绑定层的对照也是理解 cBPF 过滤器格式的最佳入门材料——它与 crates/libcontainer/src/seccomp/mod.rs 中translate_action/translate_op/translate_arch等翻译逻辑形成互文可以对照阅读。九、总结从 docs/src/user/libseccomp.md 的三段式描述出发可以看到 youki 中seccomp 安全能力的完整技术栈绑定层libseccompcraterust-bindgen 生成 人工修复 C 宏提供类型安全的 FFI 接口版本 0.4.0作为 libcontainer 的可选依赖受libseccomp特性开关控制翻译层initialize_seccomp把 OCI 规范的LinuxSeccomp动作、errno、flags、架构、带参数规则逐项翻译为 libseccomp 调用并对NOTIFY默认动作、write通知、冗余规则、重复参数索引等边界情况做了与 runc 对齐的精细化处理集成层init 进程根据noNewPrivileges是否存在选择丢特权前或exec 前两个安装时机NOTIFY 场景通过进程间 channel SCM_RIGHTS 与主进程握手再转交用户配置的 seccomp 监听器未来方向experiment/seccomp 正在探索不依赖 C 库的纯 Rust 实现。对容器安全感兴趣的同学可以按此顺序深入先读 crates/libcontainer/src/seccomp/mod.rs 理解翻译逻辑再看 crates/libcontainer/src/process/init/process.rs 理解安装时机最后对照 experiment/seccomp 的纯 Rust 实现理解 cBPF 与内核通知机制的底层细节。赞分享容器运行时云原生【免费下载链接】youkiA container runtime written in Rust项目地址https://gitcode.com/gh_mirrors/yo/youki点击查看免费下载相关推荐youki架构深度解析理解Rust容器运行时的核心设计youki架构深度解析理解Rust容器运行时的核心设计 youki是用Rust语言实现的OCI容器运行时它遵循开放容器倡议标准提供了高效、安全的容器管理能容器运行时云原生libgit-sys 深度解析用 Rust FFI 绑定 Git 内部 C API 的概念验证 cratelibgit sys 深度解析用 Rust FFI 绑定 Git 内部 C API 的概念验证 crate libgit sys 是 Git 仓库 contr版本控制开发工具CLI5层防护构建容器运行时安全屏障从内核隔离到应用沙箱的深度防御实践5层防护构建容器运行时安全屏障从内核隔离到应用沙箱的深度防御实践 容器技术在现代软件开发中扮演着关键角色而确保容器运行时安全是保障整个系统稳定的核心环节。本操作系统虚拟化系统编程网络上一篇Deep-Live-Cam3步实现专业级实时AI换脸下一篇5步快速上手OpCore-Simplify自动化OpenCore EFI配置终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表