尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SerenityOS 的 seteuid / setegid 系统调用:从 man 页到内核 Credentials 实现的完整解析

SerenityOS 的 seteuid / setegid 系统调用:从 man 页到内核 Credentials 实现的完整解析 SerenityOS 的 seteuid / setegid 系统调用从 man 页到内核 Credentials 实现的完整解析【免费下载链接】serenityThe Serenity Operating System 项目地址: https://gitcode.com/GitHub_Trending/se/serenity本篇以 SerenityOS 的手册页 seteuid(2) 为主体完整讲解seteuid与setegid这两个系统调用的原型、语义、权限限制、返回值与错误码并沿调用链深入内核源码 Kernel/Syscalls/setuid.cpp剖析 SerenityOS 如何基于三元组 UID/GID 模型real / effective / saved以原子方式替换进程的凭据对象以及修改 effective ID 时内核的附带副作用如清除 dumpable 位。读完本文你既能按 man 页规范正确调用这两个 API也能理解 SerenityOS 中凭据变更的底层实现路径与安全设计。函数原型与 LibC 封装手册页给出的调用原型如下出自 seteuid(2) 手册页 的 Synopsis 一节#include unistd.h int seteuid(uid_t); int setegid(gid_t);两者分别设置当前进程的有效用户 IDeffective user ID, euid与有效组 IDeffective group ID, egid。在 SerenityOS 的 C 库中这两个原型声明于 Userland/Libraries/LibC/unistd.h实现位于 Userland/Libraries/LibC/unistd.cppint seteuid(uid_t euid) { int rc syscall(SC_seteuid, euid); return rc 0 ? -1 : rc; } int setegid(gid_t egid) { int rc syscall(SC_setegid, egid); return rc 0 ? -1 : rc; }从源码结构看封装层非常薄把参数传入对应的系统调用号SC_seteuid/SC_setegid若内核返回负值表示Error则置位errno并返回 -1否则透传内核返回值。真正的权限判断与凭据变更全部发生在内核侧。real / effective / saved理解 effective ID 的前提手册页 Description 一节的这句话是理解这两个函数的关键For non-superusers, the effective ID can only be set to the current real or saved ID.对非超级用户而言effective ID 只能被设置为当前的 real ID 或 saved ID。这背后是 Unix 系系统的三元组身份模型。SerenityOS 在 setuid_overview(7) 手册页 中对该模型有系统说明每个进程同时持有三组 ID——real真实、effective有效与 saved保存ID权限检查使用的是effective ID。普通进程执行新程序时三者都继承自父进程而执行带 Set-UID 位的可执行文件SUID 二进制时进程以父进程的 ID 作为 real ID却把 effective ID 和 saved ID 设为该文件的所有者 ID例如 SerenityOS 中/bin/ping被设为 SUID root使任何用户执行它时都以 root 身份运行以获得组网权限。正是三元组设计让 SUID 程序可以在特权身份与普通身份之间来回切换seteuid(getuid())可以把 effective ID 降回真实用户而 saved ID 仍保留着提权后的 ID便于稍后恢复。man 页特别强调了一个易踩的边界seteuid(geteuid())在当前 effective ID 既不等于 real ID 也不等于 saved ID时会失败。也就是说把 effective ID 设成它现在的值并不总是无操作的合法调用——非超级用户每次变更的目标都必须落在 real 或 saved ID 之中这条规则在内核源码中是硬性实现的见下文。返回值与错误码及其源码依据man 页规定调用成功返回0失败返回-1并设置errno可能的错误码只有两个错误码man 页含义内核源码依据EPERM新 ID 既不等价于 real ID 也不等价于 saved ID且当前不是超级用户setuid.cpp#L23-L24EINVAL新 ID 被设置为非法值-1setuid.cpp#L17-L18对照内核实现 Process::sys$seteuidErrorOrFlatPtr Process::sys$seteuid(UserID new_euid) { VERIFY_NO_PROCESS_BIG_LOCK(this); TRY(require_promise(Pledge::id)); if (new_euid (uid_t)-1) return EINVAL; return with_mutable_protected_data( - ErrorOrFlatPtr { auto const credentials *protected_data.credentials; if (new_euid ! credentials.uid() new_euid ! credentials.suid() !credentials.is_superuser()) return EPERM; // ... 构建并替换新凭据 }); }三个值得注意的实现细节-1的语义与setuid系列函数不同。在setuid/seteuid/setgid/setegid中参数-1不是保持不变的占位符而是直接非法需要保持某项不变的语义由setreuid/setresuid提供在这些函数里-1才表示沿用当前值见 setuid.cpp#L144-L155 中sys$setreuid的处理。man 页把这一点写进了EINVAL条目。EPERM判定式。内核逐字实现了 man 页的规则new_euid必须等于credentials.uid()real ID或credentials.suid()saved ID除非进程是超级用户。而is_superuser()的定义在 Kernel/Security/Credentials.h 中是一行return euid() 0;——SerenityOS 以 effective UID 是否为 0 来判定超级用户。Pledge 约束。两个系统调用进入业务逻辑前都会执行require_promise(Pledge::id)进程必须先通过pledge声明id能力否则调用直接失败。这是 SerenityOS 特有的进程自我约束机制意味着即使写对了参数未声明idpledge 的进程也无法修改身份。sys$setegidsetuid.cpp#L45-L76的逻辑与sys$seteuid完全对称只是比较对象换成了组new_egid必须等于credentials.gid()或credentials.sgid()且同样先做-1的EINVAL检查、同样要求Pledge::id。内核如何替换凭据Credentials 的原子重建seteuid/setegid成功后进程的身份并不是原地改一个字段而是整份凭据对象被替换。内核流程为通过with_mutable_protected_data取得进程受保护的私有数据该机制保证凭据只在内核内被安全读写用Credentials::create(...)以旧凭据 新的 euid或 egid构造一份全新凭据构造签名见 Kernel/Security/Credentials.h#L17——它完整接收uid, gid, euid, egid, suid, sgid, extra_gids, sid, pgid九个分量protected_data.credentials move(new_credentials);整体替换。其中seteuid的构造过程setuid.cpp#L26-L35为auto new_credentials TRY(Credentials::create( credentials.uid(), // real uid不变 credentials.gid(), // real gid不变 new_euid, // euid本次变更的目标 credentials.egid(), // egid不变 credentials.suid(), // saved uid不变 credentials.sgid(), // saved gid不变 credentials.extra_gids(), credentials.sid(), credentials.pgid()));由此可以确认 man 页未展开、但对安全行为至关重要的两点seteuid不动 saved UID。对比同文件中的 sys$setuidsetuid会同时把 real、effective、saved 三个分量都设为新值new_uid, ..., new_uid, ..., new_uid从而永久放弃切换回原身份的能力。这正对应 setuid_overview(7) 中永久丢弃 SUID 权限的描述而seteuid只动 effective 分量saved ID 保留旧值因此是可逆的临时提权/降权。写 SUID 程序时二者的选择直接决定权限是暂时还是永久。effective ID 变化会使进程 dumpable 位被清除。两处实现都有相同片段如 setuid.cpp#L37-L38if (credentials.euid() ! new_euid) protected_data.dumpable false;这是沿袭 POSIX 的经典安全行为身份切换后进程可能接触到更高特权的数据因此内核将其标记为不可产生 core dump防止敏感内存通过核心转储文件泄漏。也就是说即便seteuid调用成功且 ID 数值未变只要它相对原 effective ID 发生了变化dumpable 就会被置位且不可恢复。真实调用示例LibC posix_spawn 的权限回落仓库中存在seteuid/setegid的直接使用案例可用来印证上述语义。LibC 的posix_spawn实现 Userland/Libraries/LibC/spawn.cpp#L38-L43 在子进程中执行完以特权身份完成的操作后把 effective ID 降回真实 IDif (seteuid(getuid()) 0) { perror(posix_spawn seteuid); // ... } if (setegid(getgid()) 0) { perror(posix_spawn setegid); // ... }这里正是 man 页描述的典型场景把 effective ID 设为getuid()/getgid()real ID。由于目标值等于 real ID该调用满足非超级用户只能设为 real 或 saved ID的限制而成功若子进程当前 effective ID 已不等于 real 也不等于 saved ID例如被恶意代码改写过同样的调用就会按 EPERM 失败——这就是 man 页特别提示seteuid(geteuid())可能失败的原因。与其他身份系统调用的关系seteuid(2) 手册页 的 See also 一节列出了完整的身份 API 家族。结合 Kernel/Syscalls/setuid.cpp 中同一套实现可以按可变分量快速区分各调用以下链接均指向仓库内对应的 man 页只读查询geteuid(2) / getegid(2)、getuid(2) / getgid(2)、getresuid(2) / getresgid(2)只改 effective 分量可逆本文的seteuid/setegid同时改 real effective saved 分量不可逆setuid(2) / setgid(2)逐项精细控制三元组-1表示沿用现值setresuid(2) / setresgid(2)模型总述setuid_overview(7)。该总述文档还给出了 SUID 程序最常见的临时降权惯用法值得与seteuid对照阅读// 临时放弃 SUID 特权同时把旧 euid 存入 saved ID 以便恢复 if (setresuid(-1, new_uid, geteuid()) 0) return OH_NO;seteuid的定位恰好是其中最简单的单分量版本只动 effective、不动 saved调用最简、语义最直接适合只需要在现有 real/saved 两个身份之间来回切换的场景。小结seteuid/setegid在 SerenityOS 中是薄封装的系统调用权限判定与凭据变更全部在内核 Kernel/Syscalls/setuid.cpp 完成非超级用户调用时新 effective ID 必须等于当前 real ID 或 saved ID否则EPERM参数为-1直接EINVAL不同于setresuid系列中-1表示保持不变成功路径上内核通过Credentials::create整份重建并替换凭据seteuid不修改 saved UID因此是可逆的身份切换setuid则三分量同改、不可逆额外前提进程须持有Pledge::id能力effective ID 变化还会清除进程 dumpable 位以防 core dump 泄密完整语义模型见 Base/usr/share/man/man7/setuid_overview.md它解释了 SUID 二进制为何需要三元组 ID以及临时/永久丢弃权限的正确姿势。【免费下载链接】serenityThe Serenity Operating System 项目地址: https://gitcode.com/GitHub_Trending/se/serenity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表