3

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?

  • I recall that you need a call to su(2). that is a system function that will su. see man 2 su. (I don't have a unix at hand to give more precision). – Archemar Jun 8 '17 at 5:02
  • 1
    ecryptfs is not an external filesystem, it is an encrypted one. Please post findmnt -T ~ | cat. – sourcejedi Jun 8 '17 at 7:02
  • 1
    It might also be an LSM issue (SELinux or Apparmor). Therefore, even if you do not tag your questions as specific to a distribution, please post which Linux distribution you are using. E.g. I know how to look for SELinux logs but the same method might not work for Apparmor. – sourcejedi Jun 8 '17 at 7:04
4

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.

  • Ah, right, bash dropping privileges. But that depends on if /bin/sh is bash or something else. (Even if it is, Debian's Bash is apparently patched to not drop privileges if called as sh.) So somewhat system-dependant. Judging from the first test, your sh doesn't seem to drop privileges either, you can still see the euid=0 part in the id call that didn't explicitly go through bash -c. – ilkkachu Jun 8 '17 at 18:13
  • 1
    @ilkkachu that's an interesting point; one that had passed me by. On my Debian system /bin/sh is a symlink to /bin/dash, and that dash (apparently always) drops privileges. I think I probably need to test that. – roaima Jun 8 '17 at 19:11
  • ITYM dash never drops privilege? (or am I really confused now) – ilkkachu Jun 8 '17 at 19:15
  • I am confused. I have ubuntu 16.04 and doing something like "setuid(0)" at the beginning worked. my shell is still bash, so why would it work? My uid was showing up as my username, but once I did setuid(0) everything worked. – Pranay Jun 8 '17 at 21:16
  • 1
    @Pranay that will have worked because you set your Real UID to match the Effective UID before calling system(), so the shell has nothing to reduce its inherited root privileges to. – roaima Jun 8 '17 at 22:53
3

This is a two-step process.

  1. set executable suid bit as you did.

  2. call to setuid(uid_t uid) as per man setuid.

if you purpose is only apt-get, you might whish to use sudo instead.

  • Does system() drop the setuid privilege then? Or why wouldn't apt-get work if started from a setuid wrapper? At least system("whois") from a setuid wrapper does show root for me, so I think the problem here is probably somewhere else. – ilkkachu Jun 8 '17 at 9:48
  • this worked!. Once I set the setuid(0) as well as do all that I mention in the question. – Pranay Jun 8 '17 at 21:09
  • However, I am now confused, didn't chmod do this already? I thought it meant setting the userID to be that of the owner and the process will run as the owner rather than the user. – Pranay Jun 8 '17 at 21:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.