Linux Setuid – Fixing Setuid Root Not Working

linuxprivilegessetuid

Objective : Run a program as root (C++ binary).
The same as : SetUID bit not working in Ubuntu?

And : Why setuid does not work on executable?

./a.out output:

E: Could not open lock file /var/lib/dpkg/lock - open (13: Permission denied)
E: Unable to lock the administration directory (/var/lib/dpkg/), are you root?
psurana //output for "whoami" Look below for the code.

ls -l output:

-rwsrwxr-x 1 root root 46136 Jun 7 20:13 a.out

The Code :

#include <string>
#include <stdlib.h>
int main(int argc, char *argv[]){
        std::string input = "apt-get install " + std::string(argv[1]);
        system(input.c_str());
        system("whoami");
        return 0;
}

Details: : compiled the program and then did chown root:root a.out && chmod u+s a.out. Please look above for ls -l output.

I still do not get the root privileges and the output for system("whoami") in the code is my own username on the machine.

Reading the two linked questions did not yield me anywhere. :(.
both the creator and the owner of the file are root. The setuid bit is set, so it should work. The filesystem is not external either, it is my own machine. How can I make this work?

Best Answer

If you change the code like this you can see the effective and real UIDs:

#include <string>
#include <stdlib.h>
int main(int argc, char *argv[]){
        system("id");
        system("bash -c id");
        return 0;
}

On my system this returns these two lines (I've used ... to skip irrelevant groups):

uid=1001(roaima) gid=1001(roaima) euid=0(root) groups=1001(roaima),24(cdrom),...,103(vboxsf)
uid=1001(roaima) gid=1001(roaima) groups=1001(roaima),24(cdrom),...,103(vboxsf)

As you can see, the raw call to id returns an Effective UID of 0 (root), but the Real UID is still my own. This is what you would expect.

However, you can see that the bash -c id call has stripped the Effective UID away so it is no longer running as root. This is documented under man bash as follows:

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, no startup files are read, shell functions are not inherited from the environment, the SHELLOPTS, BASHOPTS, CDPATH, and GLOBIGNORE variables, if they appear in the environment, are ignored, and the effective user id is set to the real user id. If the -p option is supplied at invocation, the startup behavior is the same, but the effective user id is not reset.

So the solution here should be to include the -p flag.

(You can find out about the process by which bash resets its UID at Setuid bit seems to have no effect on bash.)

However, the story's not finished here because I know you're going to say you didn't invoke bash. Unfortunately for you, that's pretty much what system() does on your behalf, and it doesn't allow you to specify -p.

strace discards the root privileges, but here's enough of the strace -f ./a.out output for you to see what's going on:

execve("./a.out", ["./a.out"], [/* 44 vars */]) = 0
brk(0)                                  = 0x24f1000
...
clone(child_stack=0, flags=CLONE_PARENT_SETTID|SIGCHLD, parent_tidptr=0x7ffee0d42a1c) = 4619
wait4(4619, Process 4619 attached
 <unfinished ...>

At this point the child process kicks off, ready to run our id

[pid  4619] rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7f100eb270e0}, NULL, 8) = 0
[pid  4619] rt_sigaction(SIGQUIT, {SIG_DFL, [], SA_RESTORER, 0x7f100eb270e0}, NULL, 8) = 0
[pid  4619] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
[pid  4619] execve("/bin/sh", ["sh", "-c", "id"], [/* 44 vars */]) = 0
[pid  4619] brk(0)                      = 0x7f849dd71000
[pid  4619] brk(0)                      = 0x7f849dd71000
...
[pid  4619] clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f849d1d89d0) = 4620

Now we have a shell running, and it will have discarded our Effective UID. Next you'll see it starting the id command and writing its output to stdout for you:

Process 4620 attached
[pid  4619] wait4(-1,  <unfinished ...>
[pid  4620] execve("/usr/bin/id", ["id"], [/* 44 vars */]) = 0
[pid  4620] brk(0)                      = 0x1785000
...
[pid  4620] write(1, "uid=1001(roaima) gid=1001(roaim"..., 149) = 149
uid=1001(roaima) gid=1001(roaima) groups=1001(roaima),24(cdrom),...,103(vboxsf)
...

The solution for you here will be either to use one of the exec*() family directly, or to include a call to setuid(0), or to configure a tool such as sudo to allow you to call your target program directly and (presumably) without a password.

Of these options I'd personally go with the sudo solution. The authors of that spent a long time ensuring the code was safe against (un)intended escalation of privilege attacks.

Related Question