ARTICLE DETAIL

资讯详情

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

SerenityOS 内核容器机制深度解析:VFS 根上下文、进程列表与 Hostname 隔离

SerenityOS 内核容器机制深度解析:VFS 根上下文、进程列表与 Hostname 隔离 SerenityOS 内核容器机制深度解析VFS 根上下文、进程列表与 Hostname 隔离【免费下载链接】serenityThe Serenity Operating System 项目地址: https://gitcode.com/GitHub_Trending/se/serenity本指南以 Documentation/Kernel/Containers.md 为核心脉络系统讲解 SerenityOS 内核如何通过 VFS 根上下文VFS Root Context、作用域进程列表Scoped Process List与主机名上下文Hostname Context三类资源实现进程级隔离并完整梳理unshare_open/unshare_create/unshare_enter三条系统调用及 Jail 安全机制的内核实现与用户态调用链。读完本文你将掌握 SerenityOS 容器从资源创建、进程进入、exec 切换直到 jail 强制的完整生命周期并能对照runc实用工具读懂真实容器配置文件的部署流程。什么是容器SerenityOS 中的“概念性建构”在 SerenityOS 生态中容器Container被定义为一种概念性建构conceptual establishment它并不对应某个单一内核对象而是通过一组对外暴露的、不共享的资源unshared resources将用户程序彼此隔离。这些资源包括但不限于PID 视图process list——进程只能看到与自己同属一个作用域的进程文件系统视图filesystem view——进程只能看到自己根上下文下的挂载树主机名hostname——一组进程共享一个独立的主机名命名空间。这种“组合多种隔离维度、叠加为一个容器”的设计与 Linux 中 namespace 的思想同源但实现独立是 SerenityOS 内核在 Kernel/ 目录下自研的机制而非对 Linux 代码的移植。内核侧的三种隔离机制当前内核Kernel/对外暴露三类可能的隔离机制分别由独立的引用计数对象与全局链表管理VFS 根上下文VFS Root ContextsVFS虚拟文件系统根上下文是每个进程持有的、用于“观察一棵文件系统树”的上下文。一个 VFS 根上下文持有两部分关键数据挂载表mount table该上下文内的全部挂载关系根目录 Custody上下文的根目录句柄。用户进程既可以持有所有默认程序共享的全局上下文也可以持有特殊上下文来限制自身的文件系统视图。从源码看该机制实现在 Kernel/FileSystem/VFSRootContext.h通过create_with_empty_ramfs(...)/create_with_filesystem(...)工厂方法创建并可通过AddToGlobalContextList枚举控制是否加入全局上下文链表每个上下文拥有独立的IndexIDAK_TYPEDEF_DISTINCT_ORDERED_ID(u64, IndexID)作为全局索引提供root_custody()、add_new_mount()、unmount()等接口管理根目录与挂载点。文档明确其生命周期规则VFS 根上下文挂接在全局链表中当其最后一个挂载即根挂载被卸载时才会从全局链表中移除。这意味着只要根文件系统还挂着即使没有进程引用该上下文它依然存活在全局链表中可供后续unshare_enter按 id 重新进入。进程列表Process lists进程列表分为两类全局进程列表所有进程默认挂接其上作用域进程列表scoped process list由用户进程显式创建并持有引用。当进程持有某个作用域进程列表的引用后它只能看到位于同一列表上的其他进程从而实现对 PID 视图的隔离。实现见 Kernel/Tasks/ScopedProcessList.h基于AtomicRefCounted的引用计数对象内部以IntrusiveList维护attached_processes()集合拥有独立IndexID与全局链表节点m_list_node通过attach(Process)/detach(BadgeProcess)维护成员关系m_attach_count记录附着进程数。其生命周期规则与主机名上下文一致作用域进程列表挂接在全局链表上当最后一个仍附着于该列表的进程与之分离时列表才从全局链表中移除。主机名上下文Hostname contexts主机名上下文让一组用户进程共享一个自定义的主机名。任何一个持有该上下文引用的进程都可以修改主机名且修改会立即反映给所有附着在该上下文上的其他进程从而实现主机名维度的“命名空间”隔离。实现见 Kernel/Tasks/HostnameContext.h同样基于AtomicRefCounted通过create_initial()创建内核初始上下文、create_with_name(StringView)创建命名上下文主机名缓冲区为FixedStringBufferUTSNAME_ENTRY_LEN - 1受 POSIXstruct utsname条目长度约束并用RecursiveSpinlockProtected保护并发访问拥有独立IndexID、全局链表节点与m_attach_count引用计数。生命周期规则上下文挂接在全局链表中当最后一个附着进程分离时才从链表移除。内核与用户空间的三条接口内核通过 3 条系统调用完成资源隔离的“创建—初始化—进入”三步操作声明位于 Kernel/API/Syscall.h第 195197 行并注册为NeedsBigProcessLock::No系统调用参数结构体职责unshare_openSC_unshare_open_params { int type; }为一种新的非共享资源创建文件描述符返回可操作的 fdunshare_createSC_unshare_create_params { int unshared_resource_fd; }真正创建非共享资源返回资源的索引号idunshare_enterSC_unshare_enter_params { int type; int id; int flags; }使当前进程按资源索引进入附着到指定资源资源类型与进入标志类型与标志定义于 Kernel/API/Unshare.henum class UnshareType { ScopedProcessList 1, VFSRootContext 2, HostnameContext 3, }; enum class UnshareEnterFlags : u32 { None 0, CurrentProgram 1 0, // 立即生效 AfterExec 1 1, // 在下次 exec 时生效 };UnshareEnterFlags的两个标志值得重点说明CurrentProgram立即生效调用后当前进程立即替换/附着到新资源AfterExecexec 后生效资源先被记录到进程的m_exec_*槽位待下次exec时统一替换。内核实现中Process维护了m_exec_vfs_root_context、m_exec_hostname_context、m_exec_scoped_process_list三个AfterExecResource槽位见 Kernel/Tasks/Process.h并在execve系统调用路径中通过replace_resource一次性应用见 Kernel/Syscalls/execve.cpp 第 636638 行。这保证容器入口程序启动瞬间“原子”切换到隔离环境中。内核实现细节三条系统调用的完整实现位于 Kernel/Syscalls/unshare.cpp其公共前置条件为调用进程必须持有pledge(unshare)承诺require_promise(Pledge::unshare)调用进程必须为超级用户credentials-is_superuser()否则返回EPERM若进程已处于jail状态is_jailed()直接返回EPERM——jail 与资源切换互斥。unshare_open根据params.type分派到ScopedProcessList/VFSRootContext/HostnameContext三种类型创建对应的 Kernel/FileSystem/UnsharedResourceFile.h一个不提供读写、仅作为资源句柄的特殊File子类随后在进程 fd 表中分配新 fd 并设置FD_CLOEXEC。非法类型返回ENOTSUP负数类型返回EINVAL。unshare_create通过 fd 找回对应的UnsharedResourceFile校验is_unshared_resource_file()否则EINVAL然后调用其initialize_resource()。该方法见 Kernel/FileSystem/UnsharedResourceFile.cpp按类型完成资源实体创建并返回索引号ScopedProcessListScopedProcessList::create()VFSRootContext若此前已通过 ioctl 附加了根文件系统则VFSRootContext::create_with_filesystem(...)否则以空的 ramfs创建create_with_empty_ramfs(...)均加入全局上下文链表HostnameContext基于进程当前附着上下文的内容派生新上下文。unshare_enter要求flags ! None否则EINVAL然后按params.type通过*_for_id(params.id)从全局链表查找资源再根据CurrentProgram/AfterExec标志分别替换当前上下文或写入m_exec_*槽位。用户态封装LibCore为应用开发者提供了同名的 C 封装见 Userland/Libraries/LibCore/System.cppErrorOrunsigned unshare_open(Kernel::UnshareType type); // 返回 fd ErrorOru32 unshare_create(unsigned fd); // 返回资源索引号 ErrorOrvoid unshare_enter(Kernel::UnshareType type, unsigned index, int flags);三个封装内部均通过syscall(SC_unshare_*, params)进入内核并使用HANDLE_SYSCALL_RETURN_VALUE宏把负值错误码转换为Error。Jail将容器固化为安全沙箱当用户进程被“直到退出”式 jailjailed until exit后它不能再创建或附着到其他任何资源。这使得 jail 成为构建安全沙箱化容器的有效机制用户程序及其全部后代进程将始终使用容器创建时选定的那套资源无法中途逃逸或换用别的隔离上下文。内核侧的强制点Process::is_jailed()见 Kernel/Tasks/Process.h判断依据是jailed_until_exit.was_set() || jailed_until_exec即支持“直到退出”与“直到 exec”两种时限。jail 的强制力体现在多处资源切换被禁止unshare_open/unshare_create/unshare_enter在入口处即检查is_jailed()并返回EPERM见 Kernel/Syscalls/unshare.cpp设备访问受限Device::open检查is_jailed() !is_openable_by_jailed_processes()时返回EPERM见 Kernel/Devices/Device.cpp 第 115 行内核配置只读化SysFS 下的布尔/字符串内核配置项在写入路径上同样拒绝 jailed 进程见 Kernel/FileSystem/SysFS/Subsystems/Kernel/Configuration/BooleanVariable.cpp 与 StringVariable.cpp防止容器内进程篡改全局内核参数。用户态入口用户态通过prctl进入 jail 模式LibCore封装见 Userland/Libraries/LibCore/System.cppenter_jail_mode_until_exit()调用prctl(PR_SET_JAILED_UNTIL_EXIT, ...)另有enter_jail_mode_until_exec()PR_SET_JAILED_UNTIL_EXEC对应“直到下次 exec 解除”的弱化模式。实战runc 工具如何组装一个容器Userland/Utilities/runc/是 SerenityOS 上以用户态方式组装容器的参考实现它把本文所述的三类资源与 jail 机制串成完整流程。命令行用法Userland/Utilities/runc/main.cpp 中serenity_main解析以下参数runc [-p|--pid-isolation] [-j|--enforce-jail] [-E|--preserve-env] \ [-f|--configuration json-config] [command...]-p/--pid-isolation创建并进入新的作用域进程列表-j/--enforce-jail对容器强制 jail 限制-E/--preserve-env保留用户环境变量执行命令-f/--configuration使用 JSON 配置文件部署容器与位置参数命令二选一位置参数command要在容器内以提升权限运行的命令。不使用配置文件时要求至少指定--pid-isolation或--enforce-jail之一否则报错Cant create a container with no attributes (jail/pid-isolation).。JSON 配置文件格式使用-f时配置文件是 JSON 对象必需/可选字段如下解析逻辑见extract_values_from_file字段类型说明jailbool必需是否启用 jail 强制pid-isolationbool必需是否启用 PID 隔离作用域进程列表commandstring必需容器入口命令layoutarray必需VFS 根上下文的挂载布局创建序列hostnamestring 或 null可选但必须显式指定其一为字符串时创建并进入新的主机名上下文解析器对非法组合如hostname同时为 null 与字符串会拒绝加载。挂载布局由 Userland/Utilities/runc/LayoutParsing.cpp 与 Userland/Utilities/runc/VFSRootContextLayout.cpp 处理支持“在临时目录中构造根挂载点 → 按序列创建后续挂载 → 复制挂载骨架到新的 VFS 根上下文”的流程。部署序列六步组装一个容器deploy_container_based_on_config_file严格按以下顺序部署源码注释亦明确此顺序不可颠倒创建 PID 隔离unshare_open(ScopedProcessList)→unshare_create(fd)→unshare_enter(ScopedProcessList, index, AfterExec)创建 VFS 根上下文在临时目录构造布局并应用到新 VFS 根上下文附着 VFS 根上下文unshare_enter(VFSRootContext, id, AfterExec)随后卸载并清理临时目录创建并附着主机名上下文unshare_open(HostnameContext)→unshare_create(fd)→unshare_enter(HostnameContext, id, CurrentProgram)→sethostname(hostname)强制 jailenter_jail_mode_until_exit()exec 入口命令exec_command(command, false)此时第 1、3 步中标记为AfterExec的资源随 exec 一并生效。注意第 4 步使用CurrentProgram立即生效而非AfterExec因为要在 exec 前先以新主机名调用sethostname从而把容器主机名“烙进”新上下文。最小权限原则pledge 的逐步收紧部署过程中pledge承诺被逐级收窄体现最小权限原则读配置前pledge(stdio rpath wpath cpath proc mount unshare exec fattr chown)构造完 VFS 布局后移除fattr/chown... mount unshare exec附着完主机名上下文后移除unshare... mount execjail 后移除proc... exec最终以最少的权限执行容器命令。关键源码索引官方文档Documentation/Kernel/Containers.md系统调用实现Kernel/Syscalls/unshare.cpp资源类型与标志定义Kernel/API/Unshare.h系统调用号与参数结构Kernel/API/Syscall.h资源句柄文件Kernel/FileSystem/UnsharedResourceFile.h、Kernel/FileSystem/UnsharedResourceFile.cpp三类隔离对象Kernel/FileSystem/VFSRootContext.h、Kernel/Tasks/ScopedProcessList.h、Kernel/Tasks/HostnameContext.h进程侧资源槽位与 jail 状态Kernel/Tasks/Process.h、Kernel/Syscalls/execve.cpp用户态封装Userland/Libraries/LibCore/System.cpp容器部署工具Userland/Utilities/runc/main.cpp【免费下载链接】serenityThe Serenity Operating System 项目地址: https://gitcode.com/GitHub_Trending/se/serenity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表