You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 05:37:31