我们来深入学习 setfsgid 和 setfsuid 这两个系统调用。(https://www.calcguide.tech/2025/08/19/setfsuid系统调用及示例/)
1. 函数介绍
在 Linux 系统中,每个进程都有一套与之相关的用户 ID (UID) 和组 ID (GID),比如:
真实用户 ID (Real UID): 登录系统的用户 ID。
有效用户 ID (Effective UID): 决定进程当前拥有哪些权限,用于权限检查。
保存的设置用户 ID (Saved Set-UID): 用于在有效 UID 和真实 UID 之间切换。
通常,文件访问权限检查是基于进程的 有效 UID 和 有效 GID 进行的。
data-ad-format="fluid" data-ad-layout-key="-7k+ex-4a-9w+4a">但是,Linux 提供了两个特殊的系统调用:setfsuid 和 setfsgid。
setfsuid (Set File System UID): 用来设置或修改进程的 File System UID (文件系统用户 ID)。
setfsgid (Set File System GID): 用来设置或修改进程的 File System GID (文件系统组 ID)。
关键点:这两个 ID 只用于文件系统的权限检查!它们不影响其他任何权限检查(比如信号发送权限、进程调度优先级等)。当内核执行文件访问权限检查时,它会使用 文件系统 UID/GID 而不是 有效 UID/GID。
默认情况下:当一个进程启动时,它的 文件系统 UID/GID 会被自动设置为与 有效 UID/GID 相同。
为什么需要它们?(主要场景)一个经典的例子是 NFS (Network File System) 服务器:1. NFS 服务器进程通常以 root 权限运行,因此它的 有效 UID 是 0 (root)。2. 但是,当它代表一个非 root 用户(比如 UID 1000)访问 NFS 文件系统上的文件时,它需要进行权限检查,仿佛是 UID 1000 在访问。3. 如果只使用 setuid/seteuid 来切换有效 UID,会影响服务器进程的其他权限(比如绑定到特权端口)。4. 所以,NFS 服务器可以调用 setfsuid(1000),这样在进行文件权限检查时,内核会看到 UID 是 1000,但进程的其他权限(如有效 UID 仍然是 root)保持不变。
简单来说:setfsuid 和 setfsgid 允许进程在进行文件访问权限检查时,“伪装”成另一个用户或组,而不会影响进程的其他特权。
2. 函数原型
1 | #include <sys/fsuid.h> // 包含函数声明 (在某些系统上可能在 unistd.h 或其他地方) |
3. 功能
分别设置调用进程的文件系统用户 ID (FS-UID) 和文件系统组 ID (FS-GID),仅用于文件系统的访问权限检查。
4. 参数
fsuid:
uid_t 类型。
指定要设置的新文件系统用户 ID。
fsgid:
gid_t 类型。
指定要设置的新文件系统组 ID。
5. 返回值
这两个函数的返回值比较特殊:
总是返回调用者之前的 文件系统 UID (setfsuid) 或 文件系统 GID (setfsgid)。
无论调用成功与否!
这与其他大多数系统调用不同(它们通常成功返回 0 或文件描述符,失败返回 -1)。这种设计意味着你无法仅通过返回值判断调用是否成功。
那么如何判断是否成功呢?通常的做法是:1. 先调用 setfsuid/setfsgid。2. 然后立即再次调用 setfsuid/setfsgid,传入同一个 ID。3. 如果两次返回的旧 ID 相同,说明第一次调用成功了;如果不同,则说明第一次调用失败。
调用失败的情况:调用通常会成功,但在某些安全模型下或传入无效 ID 时可能会失败。不过,失败的具体条件比较复杂,实践中很少遇到。
6. 相似函数或关联函数
setuid / seteuid / setreuid / setresuid: 用于设置进程的真实、有效、保存的用户 ID。这些会影响所有权限检查,而不仅仅是文件系统权限。
setgid / setegid / setregid / setresgid: 用于设置进程的组 ID。
getuid / geteuid / getgid / getegid: 获取进程的真实和有效 UID/GID。
capset / capget: 用于设置和获取进程的能力 (capabilities),这是 Linux 中更细粒度的权限控制机制,可以作为 setuid/setfsuid 的现代替代方案。
文件系统权限检查: 内核在 open, read, write, stat 等系统调用时执行的检查。
7. 示例代码
由于 setfsuid/setfsgid 的使用场景比较特殊(主要是像 NFS 这样的服务器程序),编写一个完全展示其效果的用户态程序比较困难,因为它需要特定的文件系统环境和权限设置。下面的示例将演示如何调用它们,并尝试解释其行为。
1 | #define _GNU_SOURCE // 启用 GNU 扩展以使用 getresuid 等 |
编译和运行:
1 | # 假设代码保存在 fsuid_fsgid_example.c 中 |
预期输出 (作为普通用户运行):
1 | --- Demonstrating setfsuid and setfsgid --- |
总结:对于 Linux 编程新手,setfsuid 和 setfsgid 是比较特殊的系统调用。它们不是日常编程中会频繁使用的。理解它们有助于理解 Linux 权限模型的细节,特别是文件系统权限检查是如何与进程的其他权限分离的。在编写需要代表不同用户执行文件操作的系统服务(如文件服务器)时,它们会很有用。在常规应用程序开发中,使用标准的 setuid/seteuid 或现代的 capabilities 通常更合适。