SUID权限程序异常:切换用户后文件权限检测结果不一致
问题:SUID程序权限检测异常分析
问题背景
我编写了一个由root用户拥有且设置了SUID标志的程序,用于检测指定用户(UID 1001)对目标文件的读权限。目标文件权限为-rw-rw---- 1 1000 1000,仅UID/GID 1000拥有读写权限。
两种运行结果
情况1:root身份运行(sudo su)
结果正确,显示UID 1001无文件读权限:
Program user ids at start: real: 0, efective: 0, set: 0 Switch to user uid: 1001 gid: 1001 Program user id's after switch: real: 1001, efective: 1001, set: 1001 Program group id's after switch: real: 1001, efective: 1001, set: 1001 File read permission acces: -1, euidacces: -1
情况2:UID 1000身份运行
结果异常:尽管程序显示已成功切换到UID 1001,却错误返回该用户拥有文件读权限:
Program user ids at start: real: 1000, efective: 0, set: 0 Switch to user uid: 1001 gid: 1001 Program user id's after switch: real: 1001, efective: 1001, set: 1001 Program group id's after switch: real: 1001, efective: 1001, set: 1001 File read permission acces: 0, euidaccess: 0
程序环境与源码
- 系统:Ubuntu 22.04
- 编译器:gcc 11.4.0 (Ubuntu 11.4.0-1ubuntu1~22.04)
- 编译命令:
gcc test.c -o test2
程序源码:
#define _GNU_SOURCE #include <stdio.h> #include <unistd.h> #include <errno.h> #include <pwd.h> #include <fcntl.h> int main(int argc, char **argv) { uid_t uid = 1001, ruid, euid, suid; uid_t rgid, egid, sgid; const char *path = "/backed11/test2.bin"; // Output from ls: -rw-rw---- 1 1000 1000 16401 gru 30 11:49 /backed11/test2.bin struct passwd *pw = getpwuid(uid); if(pw == NULL) { printf("Incorrect user ID: %d errno: %d\n", uid, errno); return(errno); } if(getresuid(&ruid, &euid, &suid) < 0) { printf("getresuid error: %d\n", errno); return(errno); } printf("Program user ids at start: real: %d, efective: %d, set: %d\n", ruid, euid, suid); printf("Switch to user uid: %d gid: %d\n", pw->pw_uid, pw->pw_gid); int st = setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid); if(st < 0) { printf("setregid error for gid %d : %d\n", pw->pw_gid, errno); return(errno); } st = setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid); if(st < 0) { printf("setreuid error: %d\n", errno); return(errno); } if(getresuid(&ruid, &euid, &suid) < 0) { printf("getresuid error: %d\n", errno); return(errno); } if(getresgid(&rgid, &egid, &sgid) < 0) { printf("getresgid error: %d\n", errno); return(errno); } printf("Program user id's after switch: real: %d, efective: %d, set: %d\n", ruid, euid, suid); printf("Program group id's after switch: real: %d, efective: %d, set: %d\n", rgid, egid, sgid); st = access(path, R_OK); int st2 = euidaccess(path, R_OK); printf("File read permission acces: %d, euidaccess: %d\n", st, st2); return 0; }
问题
为什么UID 1001明明没有目标文件的读权限,用UID 1000运行SUID程序时会返回有权限?是库函数存在错误吗?
解答
这不是库函数的问题,核心原因是Linux内核的文件权限检查逻辑:当进程的真实UID(RUID)曾经是文件的所有者时,内核会直接赋予该进程对文件的全部权限,哪怕后续切换了有效UID(EUID)和保存的SUID。
具体原因拆解
- 当用UID 1000运行SUID程序时,进程启动时的真实UID是1000,正好是目标文件的所有者。之后调用
setresuid(1001,1001,1001)修改了三个UID,但内核在权限检查时会优先判断真实UID是否为文件所有者——只要真实UID曾经是所有者,即使被修改,仍会给予进程全部文件权限。 - 当用root运行时,进程启动时真实UID是0,切换到1001后真实UID变为1001,不是文件所有者,所以权限检查符合预期。
验证与解决方法
- 验证方式:可以尝试用
open()函数替代access/euidaccess,open()会严格按照当前EUID检查权限,不受真实UID是所有者的影响。 - 解决代码示例:将最后的权限检查替换为:
int fd = open(path, O_RDONLY); printf("Open result: %d, errno: %d\n", fd, errno); if (fd >= 0) close(fd);
这样就能得到UID 1001对文件的真实权限结果。
内容的提问来源于stack exchange,提问作者HeS
相关产品推荐
相关产品推荐

