Score:0

setuid does not work with standard user account

hm flag

Look at this c program:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    printf("UID:  %d\n", getuid());
    printf("EUID: %d\n", geteuid());
    system("id");

    printf("res=%d\n", setuid(1001));

    printf("UID:  %d\n", getuid());
    printf("EUID: %d\n", geteuid());
    system("id");
    return 0;
}

My user account is "test" (id 1000). I have a second user account: "test2" (id 1001).

Here is what I've done:

gcc test.c -o ./a.out
sudo chown test2 ./a.out
sudo chmod u+s ./a.out

Now, here is what happens if I launch ./a.out:

UID:  1000
EUID: 1001
uid=1000(test) gid=1000(test) groups=1000(test),...
res=0
UID:  1000
EUID: 1001
uid=1000(test) gid=1000(test) groups=1000(test),...
          

I do not understand why I do not see uid=1001 in the second part...

I have tried this:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    printf("UID:  %d\n", getuid());
    printf("EUID: %d\n", geteuid());
    system("id");

    printf("res=%d\n", setuid(0));  // <- Here is the change

    printf("UID:  %d\n", getuid());
    printf("EUID: %d\n", geteuid());
    system("id");
    return 0;
}
          
gcc test.c -o ./a.out
sudo chown root ./a.out
sudo chmod u+s ./a.out

Here is what I get when running ./a.out:

UID:  1000
EUID: 0
uid=1000(test) gid=1000(test) groups=1000(test),...
res=0
UID:  0
EUID: 0
uid=0(root) gid=1000(test) groups=1000(test),...
           

Now, it works.

So I don't understand why setuid does not work with test2 user and works with root users...

Any idea ?

Thanks

Andrew Henle avatar
ph flag
How secure do you think your system would be if a normal user like `test2` had the privilege to change to a different UID?
Bob5421 avatar
hm flag
I do not understand what you mean
Score:5
in flag

There are a couple of things going on here, which cause this confusion. Setuid does work in both cases, it just doesn't work the way you think it would. First, there is the issue of real and effective user IDs. According to the setuid(2) man page

If the calling process is privileged (more precisely: if the process has the CAP_SETUID capability in its user namespace), the real UID and saved set-user-ID are also set.

This also means that is the calling process does not have the CAP_SETUID capability (which is the case of your ordinary users), then the UID will not change, only the EUID.

So we move on to the system() call. Calling system("anything") is the equivalent of calling this:

execl("/bin/sh", "sh", "-c", "anything", (char *) NULL);

So it spawns /bin/sh, providing arguments so that a new shell will execute your command. I assume you use bash as your shell, because bash works so

If the shell is started with the effective user (group) id not equal to the real user (group) id, and the -p option is not supplied, (...) the effective user id is set to the real user id.

So if your process does not have the CAP_SETUID capability, it won't change the real user ID, only the effective UID. Then, when bash is spawned, it drops the effective UID, since UID is not equals to EUID. You can confirm this by calling

system("touch /tmp/blah")

and adding

FILE *file = fopen("/tmp/blah2", "w+");
fclose(file);

to your program. You will see that the owner of /tmp/blah will be "test" (since the EUID will be dropped on shell execution), but /tmp/blah2 will belong to "test2".

vn flag
A very good answer
Bob5421 avatar
hm flag
Thanks but there is something I do not understand: "This also means that is the calling process does not have the CAP_SETUID capability (..), then the UID will not change, only the EUID.". For my second test, the calling process do not have CAP_SETUID. But it works...
in flag
But it did have `CAP_SETUID`, since it was a root process. If an executable has the setuid bit set, it runs in the name of the owning user. That is why printing the EUID yielded 0, even before calling `setuid()`. It was effectively a root process from the beginning, and as such, it had `CAP_SETUID`.
I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.