why setuid fails after capset is used?

162 views Asked by At

trying to figure out the linux capabilities interface, i came across with an unexpected issue (for me at least). When seting the capabilities of a process with the capset syscall the kernel rejects a change of userid with the setuid syscall. Does anybody know why setuid fails?

This is code i wrote to test this behavior:

#undef _POSIX_SOURCE  
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <linux/capability.h>
#include <sys/capability.h>
#include <string.h>

int main(int argc, char** argv){
    struct __user_cap_header_struct cap_header;
    struct __user_cap_data_struct cap_data;
    int cap_res;
    FILE *file;
    int sockfd;
    
    cap_header.pid = getpid();
    cap_header.version = _LINUX_CAPABILITY_VERSION_1;
    
    __u32 cap_mask = 0;
    cap_mask |= (1 << CAP_DAC_OVERRIDE);
    cap_mask |= (1 << CAP_SETUID);

    printf("You selected mask: %x\n", cap_mask);
    
    cap_data.effective = cap_mask;    
    cap_data.permitted = cap_mask;
    cap_data.inheritable = cap_mask;

    cap_res = capset(&cap_header, &cap_data);
    if(cap_res < 0){
        printf("Trying to apply mask: FAIL\n", cap_mask);
    } else {
        printf("Capability set correctly\n");
    }

    int uid = atol(argv[1]);
    int setuid_res = setuid(uid);
    if (setuid_res == -1){
        printf("7w7\n");
    } else {
        printf("UID set correctly\n");
    }
}

compiled with:

$ gcc -g test1.c -o test1

Output is (for user id: 1000)

$ # ./test1 1000
You selected mask: 2
Capability set correctly
7w7
1

There are 1 answers

1
Tinkerer On BEST ANSWER

I think you might be missing a couple of steps in your question:

  1. How do you give the binary some privilege?
  2. It looks like you are trying to use cap_dac_override to achieve what cap_setuid is intended for.

Rewriting the program as follows:

#undef _POSIX_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <linux/capability.h>
#include <sys/capability.h>
#include <string.h>

int main(int argc, char** argv){
    struct __user_cap_header_struct cap_header;
    struct __user_cap_data_struct cap_data;
    int cap_res;

    // need to start from known data. C does not guarantee these are
    // zero filled by default. You could declare them static to get
    // that.
    memset(&cap_header, 0, sizeof(cap_header));
    memset(&cap_data, 0, sizeof(cap_data));

    cap_header.pid = getpid();
    cap_header.version = _LINUX_CAPABILITY_VERSION_1;

    __u32 cap_mask = 0;
    cap_mask |= (1 << CAP_SETUID);

    printf("You selected mask: %x\n", cap_mask);

    cap_data.effective = cap_mask;
    cap_data.permitted = cap_mask;
    // not needed: cap_data.inheritable = cap_mask;

    cap_res = capset(&cap_header, &cap_data);
    if(cap_res < 0){
        printf("Trying to apply mask: FAIL\n", cap_mask);
        exit(1);
    } else {
        printf("Capability set correctly\n");
    }

    if (argc != 2) {
        printf("usage: %s <uid>\n", argv[0]);
        exit(1);
    }
    int uid = atol(argv[1]);
    int setuid_res = setuid(uid);
    if (setuid_res == -1){
        printf("7w7\n");
    } else {
        printf("UID set correctly to %d\n", uid);
    }
}

You can run the program like this:

$ sudo ./test1 1000
You selected mask: 80
Capability set correctly
UID set correctly to 1000

Or, using a file capability:

$ sudo setcap cap_setuid=p ./test1
$ ./test1 1000
You selected mask: 80
Capability set correctly
UID set correctly to 1000

This will work if you want to use the first 32 capabilities. However, there are ~40 of them under Linux at present, so I'd suggest you look into using the libcap API instead which figures out all of the kernel ABI details for you.