Search code examples
clinuxpermissionssuid

Program with SUID flag not working properly


I have a simple program to check if a specified user (1001) has read access to a specified file. The owner of the program is 'root' and additionally the program has the 's' (suid) flag set. After executing 'sudo su' and running the program the result is correct. The 'access' functions shows that user 1001 does not have access to the file.
Output:

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

But after logging in as user 1000, who is the owner of the tested file and has all rights to it, the result is incorrect. The program returns that user 1001 has read access and all displayed messages indicate that the program correctly switched to user 1001.
Output:

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

Where is the problem?? I found an error in the library??
The program is compiled and run under Ubuntu 22.04.
Compiler: gcc version 11.4.0 (Ubuntu 11.4.0-1ubuntu1~22.04)

Program source compiled with '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;
}

Solution

  • access and euidaccess are returning 0 because you're getting access via the user's supplemental group IDs.

    Besides each user's primary group ID, they can have any number of supplemental group IDs. The getgroups function returns these IDs. If we add the following after each printing of the real, effective, and saved user/group IDs:

    gid_t groups[10];
    int i, cnt;
    
    cnt = getgroups(10, groups);
    printf("groups: ");
    for (i=0;i<cnt;i++) {
        printf("%d ", groups[i]);
    }
    printf("\n");
    

    When I then run this with the file in question owned by nobody:nobody (99:99) using sudo as user nobody (and the user ID to change to as 1000), I get the following output:

    Program user ids at start: real: 99, efective: 0, set: 0
    groups: 99 
    Switch to user uid: 1000 gid: 1000
    Program user id's after switch: real: 1000, efective: 1000, set: 1000
    Program group id's after switch: real: 1000, efective: 1000, set: 1000
    groups: 99 
    File read permission acces: 0, euidaccess: 0
    

    Here, we can see that while the user's primary group ID is 1000 after the switch, it still has group ID 99 as a supplemental group ID, which allows it to access the file.

    If you changed the file's permissions to 0600 instead of 0660 so that only the file's owner can read it, you would get the result you expect.

    Alternately, if you call setgroups with only the primary group ID before changing your user ID, it will remove the set of supplementary groups so that access will return -1.

    groups[0]=pw->pw_gid;
    setgroups(1, groups);