【runc 1.4.2】(Part 7)runc 1.4.2 超深度分析 — Seccomp安全、CRIU检查点、网络、辅助子系统

发布时间:2026/6/11 16:08:00

【runc 1.4.2】(Part 7)runc 1.4.2 超深度分析 — Seccomp安全、CRIU检查点、网络、辅助子系统 runc 1.4.2 超深度分析 — Seccomp安全、CRIU检查点、网络、辅助子系统源码seccomp/seccomp_linux.go(350行) seccomp/patchbpf/enosys_linux.go(746行) criu_linux.go(1209行) network_linux.go(232行) 辅助模块一、Seccomp — 系统调用过滤1.1 InitSeccomp 核心流程funcInitSeccomp(config*configs.Seccomp)(int,error){// ─── Step 1: 解析默认动作 ───defaultAction,_:getAction(config.DefaultAction,config.DefaultErrnoRet)// SCMP_ACT_KILL / SCMP_ACT_ERRNO / SCMP_ACT_ALLOW / SCMP_ACT_TRACE / SCMP_ACT_LOG / SCMP_ACT_NOTIFY// ─── Step 2: Notify 限制检查 ───// - write 系统调用不能使用 SCMP_ACT_NOTIFY (会死锁)// - 默认动作不能是 SCMP_ACT_NOTIFYfor_,call:rangeconfig.Syscalls{ifcall.Actionconfigs.Notify{ifcall.Namewrite{return-1,errors.New(SCMP_ACT_NOTIFY cannot be used for the write syscall)}}}// ─── Step 3: 创建 BPF Filter ───filter,_:libseccomp.NewFilter(defaultAction)// ─── Step 4: 添加架构 ───for_,arch:rangeconfig.Architectures{scmpArch,_:libseccomp.GetArchFromString(arch)filter.AddArch(scmpArch)}// ─── Step 5: 添加系统调用规则 ───for_,call:rangeconfig.Syscalls{action,_:getAction(call.Action,call.ErrnoRet)for_,name:rangecall.Names{syscallID,_:libseccomp.GetSyscallFromName(name)iflen(call.Args)0{// 无参数条件 → 直接添加规则filter.AddRule(syscallID,action)}else{// 有参数条件 → 添加条件规则conditions:buildConditions(call.Args)filter.AddRuleConditional(syscallID,action,conditions)}}}// ─── Step 6: 设置优化标志 ───filter.SetTsync(true)// 线程同步应用filter.SetLogBit(true)// 记录被拒绝的系统调用// ─── Step 7: 补丁 BPF (ENOSYS stub) ───iferr:patchbpf.PatchSeccomp(config,filter);err!nil{return-1,err}// ─── Step 8: 加载 Filter 到内核 ───iferr:filter.Load();err!nil{return-1,err}// ─── Step 9: 返回 Notify fd ───seccompFd:-1ifconfig.HasNotify(){seccompFd,_filter.GetNotifyFd()// SCMP_ACT_NOTIFY 需要 fd 来接收通知}returnseccompFd,nil}Seccomp 执行时序runc (parent)Linux KernellibseccompInitSeccomprunc initrunc (parent)Linux KernellibseccompInitSeccomprunc initpidfd_getfd() 获取 fd 副本→ 传给 seccomp agentalt[SCMP_ACT_NOTIFY]InitSeccomp(config)NewFilter(defaultAction)AddArch / AddRule / AddRuleConditionalpatchbpf.PatchSeccomp (ENOSYS stub)filter.Load()prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, prog)OKfilter.GetNotifyFd()seccompFdsyncParentSeccomp(seccompFd)procSeccompDone二、patchbpf/enosys — ENOSYS 错误 Stub2.1 问题当多架构容器在 64 位系统上运行时32 位兼容系统调用可能不存在。如果 seccomp 规则阻止了这些调用进程可能收到错误的 errno 而非 ENOSYS。2.2 PatchSeccomp 解决方案PatchSeccomp 的作用 1. 检查 seccomp 规则是否包含 32-bit 兼容系统调用 2. 为缺失的兼容调用添加 ENOSYS 返回规则 3. 确保程序收到正确的 ENOSYS 而非其他错误三、CRIU — Checkpoint/Restore3.1 CRIU 集成架构内核CRIU (外部进程)libcontainerrunc CLIrunc checkpointrunc restoreContainercriuMgr (swrk)criu_linux.gocriu (checkpoint/restore in userspace)CRIU RPC (protobuf)Checkpoint images(pages-*.img, pagemap-*.img, ...)Linux Kernel(ptrace, /proc, userfaultfd)3.2 Checkpoint 流程func(c*Container)Checkpoint(criuOpts*CriuOpts)error{c.m.Lock()deferc.m.Unlock()// ─── Step 1: 状态检查 ───status,_:c.currentStatus()ifstatusStopped||statusPaused{returnfmt.Errorf(container must be running to checkpoint)}// ─── Step 2: 创建 CRIU 管理器 ───criuMgr:criuMgr{container:c,criuOpts:criuOpts,allowTCP:criuOpts.AllowOpenTCP,allowUnix:criuOpts.AllowExternalUnixConnections,}// ─── Step 3: 准备检查点目录 ───imageDir:criuOpts.ImagesDirectoryifimageDir{imageDirgetDefaultImagePath()}// ─── Step 4: 运行 CRIU ───returncriuMgr.dump(imageDir)}3.3 Restore 流程func(c*Container)Restore(process*Process,criuOpts*CriuOpts)error{c.m.Lock()deferc.m.Unlock()// ─── Step 1: 状态检查 ───status,_:c.currentStatus()ifstatus!Stopped{returnfmt.Errorf(container must be stopped to restore)}// ─── Step 2: CRIU 恢复 ───// 恢复进程树 namespace cgroup 内存状态iferr:c.restore(process,criuOpts);err!nil{returnerr}// ─── Step 3: 更新状态 ───c.staterestoredState{c:c,imageDir:criuOpts.ImagesDirectory}returnnil}CRIU 支持的特性特性条件描述TCP 连接allowTCP保持打开的 TCP 连接Unix 连接allowUnix保持打开的 Unix socketShell JobshellJob恢复 shell 作业File LocksfileLocks恢复文件锁Network管理恢复网络命名空间四、Network — 网络策略4.1 网络策略接口typestrategyinterface{initialize(config*network)error}4.2 三种网络策略策略函数描述veth(*vethStrategy).initialize()创建 veth pair一端在容器一端在宿主机loopback(*loopbackStrategy).initialize()激活 lo 接口bridge(未直接实现)通过 veth route 实现veth 策略流程宿主机 namespacenetlinkrunc init (容器)宿主机 namespacenetlinkrunc init (容器)vethXXXXX (host端) vethXXXXX (container端)LinkAdd(veth pair)host 端 vethLinkSetUp(container端)AddrAdd(container端, IP)RouteAdd(default route via gateway)配置 host 端 (bridge/路由)五、Capabilities — Linux 能力typeCapabilitiesstruct{Bounding[]string// 限制可获得的能力集Effective[]string// 实际生效的能力集Inheritable[]string// 可继承的能力集Permitted[]string// 允许的能力集Ambient[]string// 环境能力集 (Linux 4.3)}Capability 操作顺序1. ApplyBoundingSet() → prctl(PR_CAPBSET_DROP) 限制 bounding set 2. SetKeepCaps() → prctl(PR_SET_KEEPCAPS) 保留 caps 给 setuid 3. setupUser() → setgid setuid 切换用户 4. ClearKeepCaps() → prctl(PR_SET_KEEPCAPS,0) 清除保留标记 5. ApplyCaps() → capset() 设置最终能力集六、exeseal — 二进制克隆安全6.1 问题如果容器进程能覆写 /proc/self/exe 指向的 runc 二进制文件其他容器可能执行恶意代码。6.2 解决方案funcCloneSelfExe(stateDirstring)(*os.File,error){// 将 /proc/self/exe 克隆到 stateDir 下的临时文件// 使用 memfd_create 或 overlayfs// runc init 使用克隆后的二进制执行// 容器无法影响克隆后的文件}exeseal 两种实现实现函数条件memfd_createclonedBinaryMemfdCreate()内核 3.17overlayfsclonedBinaryOverlayfs()降级方案七、pathrs — 安全路径操作7.1 问题TOCTOU (Time-of-check to time-of-use) 攻击检查和操作之间路径可能被替换。7.2 解决方案// 使用 open_tree(2) O_PATH 获取安全的路径引用// 然后通过 /proc/self/fd/N 操作避免路径解析funcProcSelfOpen(pathstring,flagsint)(*os.File,error){// 1. 打开 /proc/thread-self/ 作为 fd// 2. openat(fd, path, flags|O_NOFOLLOW)// 3. 返回安全的 fd}八、Intel RDT — 资源监控typeManagerstruct{config*configs.Config idstringpathstring// resctrl 文件系统路径}func(m*Manager)Set(config*configs.Config)error{// 设置 L3 cache allocation (CAT)// 设置 MBA (Memory Bandwidth Allocation)// 写入 resctrl 文件系统}func(m*Manager)GetStats()(*Stats,error){// 读取 CMT (Cache Monitoring Technology) 统计// 读取 MBM (Memory Bandwidth Monitoring) 统计}九、辅助工具汇总模块行数核心功能utils/cmsg.go80SendFd/RecvFd (SCM_RIGHTS)utils/utils.go100WriteJSON/ResolveRootfs/DecodeStateutils/utils_unix.go287CloseExecFrom/UnsafeCloseFrom/NewSockPairinternal/pathrs/170安全路径操作 (openat/O_PATH)internal/sys/60WriteSysctls/verifyInodeinternal/userns/160User namespace fd ID 映射system/linux.go50SetKeepCaps/ClearKeepCaps/GetParentDeathSignalsystem/proc.go30Stat (进程状态查询)keys/keyctl.go50JoinSessionKeyring/ModKeyringPermlogs/logs.go30日志管道初始化env.go40prepareEnv (HOME/PATH 设置)十、设计模式总结#模式体现1策略模式Network strategy (veth/loopback/bridge)2BPF 规则链Seccomp filter 多条规则3Notify 代理SCMP_ACT_NOTIFY → seccomp agent4二进制克隆exeseal 防篡改5安全路径pathrs 防 TOCTOU6RPC 集成CRIU protobuf RPC7ENOSYS 补丁patchbpf 自动补充缺失调用8资源监控Intel RDT CAT/MBM/CMT9fd 传递SCM_RIGHTS (cmsg)10线程安全LockOSThread SetTsync

相关新闻