ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SerenityOS setuid/setgid 系统调用:进程用户与组 ID 切换的实现与使用

2026/9/9 23:59:37 拓冰建站 浏览量
SerenityOS setuid/setgid 系统调用:进程用户与组 ID 切换的实现与使用 SerenityOS setuid/setgid 系统调用进程用户与组 ID 切换的实现与使用【免费下载链接】serenityThe Serenity Operating System 项目地址: https://gitcode.com/GitHub_Trending/se/serenity本文围绕 SerenityOS 手册页 setuid.md 展开讲解setuid/setgid这两个系统调用的原型、语义、返回值与错误码并结合内核实现 Kernel/Syscalls/setuid.cpp 和用户态封装 Userland/Libraries/LibC/unistd.cpp 深入剖析其底层执行流程。读完后你可以掌握如何在 SerenityOS 中合法地切换进程的 real/effective/saved ID、调用会失败的确切条件以及 SUID 二进制降权场景下这套 API 的正确用法。函数原型与基本语义setuid和setgid的声明位于 C 标准库头文件 Userland/Libraries/LibC/unistd.h第 70–71 行#include unistd.h int setuid(uid_t); int setgid(gid_t);手册页给出的行为定义是将 real、effective、saved 三组用户或组ID 全部设置为给定的 ID。这一点可以从内核源码 Kernel/Syscalls/setuid.cpp 得到印证——Process::sys$setuid在构造新的Credentials时把uid()、euid()、suid()三个字段都填入了new_uidauto new_credentials TRY(Credentials::create( new_uid, // uid credentials.gid(), new_uid, // euid credentials.egid(), new_uid, // suid credentials.sgid(), credentials.extra_gids(), credentials.sid(), credentials.pgid()));这与只修改单一 ID 的seteuid/setresuid等函数形成对比setuid是一次性“全量覆盖”副作用是之后无法通过 saved ID 恢复原来的 effective ID。三套 IDreal/effective/saved的完整语义背景参见同仓库的手册页 setuid_overview.md。非超级用户的权限约束手册页规定对于非超级用户给定的 ID 必须等于当前的 real ID 或 effective ID否则调用失败。对应内核实现中的检查逻辑Kernel/Syscalls/setuid.cppif (new_uid ! credentials.uid() new_uid ! credentials.euid() !credentials.is_superuser()) return EPERM;setgid的检查完全同构只是比较gid()与egid()Kernel/Syscalls/setuid.cpp。返回值与错误码调用成功返回0失败返回-1并设置errno。手册页列出的错误条件与内核源码一一对应错误码触发条件内核源码依据EPERM新 ID 不等于 real ID 也不等于 effective ID且当前进程不是超级用户sys$setuid/sys$setgid中的EPERM返回路径EINVAL新 ID 取值为-1Kernel/Syscalls/setuid.cppif (new_uid (uid_t)-1) return EINVAL;用户态封装在 Userland/Libraries/LibC/unistd.cpp 中只是薄薄一层系统调用转发内核返回负值错误码时转换为-1并写入errnoint setuid(uid_t uid) { int rc syscall(SC_setuid, uid); __RETURN_WITH_ERRNO(rc, rc, -1); } int setgid(gid_t gid) { int rc syscall(SC_setgid, gid); __RETURN_WITH_ERRNO(rc, rc, -1); }内核实现细节手册页之外的三个要点通读sys$setuid的完整实现Kernel/Syscalls/setuid.cpp可以发现三个手册页未展开、但对理解 SerenityOS 安全模型很重要的细节。1. 需要 Pledgeid承诺函数入口处有TRY(require_promise(Pledge::id));从源码结构看SerenityOS 继承了 pledge/unveil 式的系统调用授权机制进程必须先通过pledge系统调用声明id能力才被允许执行身份切换类调用。因此即使一个程序在 SerenityOS 中调用了setuid若其 pledge 承诺未包含id调用也会失败。这一点与手册页中仅描述EPERM/EINVAL的视角并不冲突而是前置的门控。2. 凭证以整体换入方式原子替换修改 ID 不是逐字段改结构体而是通过Credentials::create构造一份全新的凭证对象再经由with_mutable_protected_data整体替换protected_data.credentials。同一文件内还实现了整套身份切换家族sys$seteuid、sys$setegid、sys$setreuid、sys$setresuid、sys$setregid、sys$setresgid、sys$setgroups共享同一套凭证替换与权限检查模式便于对照阅读。3. effective ID 变化会使进程变为“不可 dump”每个set*uid/gid实现里都有同一段收尾逻辑if (credentials.euid() ! new_uid) protected_data.dumpable false;即一旦 effective ID 发生变化内核会把该进程标记为不可转储。这是一种常见的安全实践提权/降权后的进程不应再暴露核心转储防止其中的敏感数据被低权限进程读取。使用setuid提权后这一点会直接影响调试手段的选择。典型应用场景SUID 二进制的安全降权手册页第 7 章 setuid_overview.md 详细解释了 SerenityOS 中 real/effective/saved 三 ID 的来源当进程执行带有 Set User ID 位的可执行文件时它以二进制属主作为 effective/saved ID以父进程的 ID 作为 real ID。SUID 程序常需要临时或永久放弃多余权限。结合本文讨论的setuid永久降权的最简单写法是引自该手册页if (setresuid(new_uid, new_uid, new_uid) 0) return OH_NO;手册页特别指出在 SerenityOS 上这通常等价于直接调用setuid(new_uid)——因为setuid会把三个 ID 一次性全部覆盖为新值saved ID 也随之丢失等效 ID 便再也无法被设置回高权限值。反之如果只需临时降权并保留恢复能力则应使用seteuid或setresuid(-1, new_uid, geteuid())这类只动 effective ID 的调用setuid反而是最“重”的选项。相关接口与延伸阅读手册页的 See also 部分列出了完整的身份 ID 接口族这些接口在 SerenityOS 仓库中均有对应实现与手册页geteuid(2)/getegid(2)—— 获取 effective ID手册页见 geteuid.mdgetuid(2)/getgid(2)—— 获取 real ID手册页见 getuid.mdgetresuid(2)/getresgid(2)—— 获取三组 ID手册页见 getresuid.mdseteuid(2)/setegid(2)—— 只改 effective ID手册页见 seteuid.md、setegid.mdsetresuid(2)/setresgid(2)—— 语义最清晰、最灵活的三 ID 设置接口手册页见 setresuid.md、setresgid.md概念总览setuid_overview.md其中建议优先使用setresuid/setresgid/getresuid/getresgid因为它们的语义最易推理。小结setuid/setgid将 real、effective、saved 三组 ID 一次性设置为给定值是身份切换接口族中语义最“彻底”的函数失败条件仅有两条非超级用户且新 ID 不属于现有 real/effective IDEPERM以及新 ID 为-1EINVAL内核实现还包含两点手册页未强调的机制pledge的id承诺前置检查以及 effective ID 变化后自动关闭 core dumpdumpable false需要保留恢复能力的临时降权场景应改用seteuid/setresuidsetuid适合确定性地、不可逆地放弃多余权限。【免费下载链接】serenityThe Serenity Operating System 项目地址: https://gitcode.com/GitHub_Trending/se/serenity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考