jaslip
jaslip

Reputation: 423

How to drop privilege temporarily from root?

I am developing a daemon running as root, but need to call an API with the user, I checked the API codes, it uses getuid() to get the user.

If root user drops privilege by setuid() , it can't be restored to root. If calling seteuid(), the API will still do something as user uid=0.

I think fork before accessing API and setuid in the child process should work, but even if COW , it will cost much if calling API many times. Is it possible to solve the problem except using process pool?

Upvotes: 7

Views: 4633

Answers (4)

msc
msc

Reputation: 34648

From here:

Normally when a process is executed, the effective, real, and saved user and group IDs are all set to the real user and group ID of the process's parent, respectively. However, when the setuid bit is set on an executable, the effective and saved user IDs are set to the user ID that owns the file.

Upvotes: -1

Luis Colorado
Luis Colorado

Reputation: 12698

Just call seteuid(2) to do the appropiate unprivileged stuff. seteuid(2) allows to switch between the real(or saved) user id (the one that launches the suid program or root in your case) and the suid user id (the one the suid program belongs to) so there should be no problem to regain privileged user id afterwards (as the saved user id is root, you don't have any issue to switch to it again and again).

If you change uids with setuid(2) you'll change all (effective, saved and real uids) and this is only allowed to the root user (or a program setuid root, and there's no way back then).

Look at the next example:

File pru49015.c:

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

int main(int argc, char **argv)
{
    int opt, suid = getuid(), /* this is the saved uid */
        uid = 0;
    while ((opt = getopt(argc, argv, "i:")) != EOF) {
        switch (opt) {
        case 'i': uid = atoi(optarg); break;
        }
    }
    /* execute this program with root privileges, like setuid root, for example */
    printf("real uid=%d; effective uid=%d\n", getuid(), geteuid());
    seteuid(uid);  /* change to the non-privileged id configured */
    printf("real uid=%d; effective uid=%d\n", getuid(), geteuid());
    seteuid(suid); /* return back to saved uid */
    printf("real uid=%d; effective uid=%d\n", getuid(), geteuid());
}

You will get an output like this:

$ pru49015 -i 37
real uid=502; effective uid=0
real uid=502; effective uid=37
real uid=502; effective uid=502

when used as a setuid-root program

If you use it as root, you'll get the following output:

$ sudo pru$$ -i 37
real uid=0; effective uid=0
real uid=0; effective uid=37
real uid=0; effective uid=0

The mechanism is that you are allowed on a setuid- program to switch between the user you are (let's call it the saved user id) and the user the program runs setuid to (the called effective user id or suid user) as many times as you want.

Upvotes: 4

Iharob Al Asimi
Iharob Al Asimi

Reputation: 53016

Yes! Create a single process to call the API with the appropriate UID and communicate with the rest of the program through a Pipe, a UNIX domain socket or (shared memory)1.

I mean, fork only once and keep the privileged user running another process. Then create communication between the two if needed and as needed. Also, you might want to consider using dbus since it also integrates perfectly with systemd and on modern linux you want your daemon to interact nicely with both.

Note: I am by no means an expert on the subject, but this is a simple idea that seems clear to me. You don't need to create a process for every call to the API. This is a good example of the XY problem, the real problem that you want to solve, has nothing to do with avoiding to fork() multiple times because the idea of doing that is the wrong solution. You only need to fork() once, drop privileges and stay there without privileges, communicating with the parent process if/as needed.


1Any IPC mechanism that works for you.

Upvotes: 8

Toby Speight
Toby Speight

Reputation: 30881

You can store the former effective uid in the saved UID of the process:

uid_t real = getuid();
uid_t privileged = geteuid();
setresuid(real, real, privileged);
do_API_call(); // API's getuid() call now returns real
setresuid(real, privileged, -1); // allowed, since saved==privileged

There's a corresponding setresgid to use saved GIDs, too.


Note that this answer is specific to Linux (as per question tags). A similar call exists on HP-UX and some BSD systems, but I haven't checked that the semantics are identical.


Actually, on further reading setreuid() should be sufficient (and POSIX-conformant). setuid() says:

If the effective UID of the caller is root (more precisely: if the caller has the CAP_SETUID capability), the real UID and saved set-user-ID are also set.

and

If the user is root or the program is set-user-ID-root, special care must be taken. The setuid() function checks the effective user ID of the caller and if it is the superuser, all process-related user ID's are set to uid. After this has occurred, it is impossible for the program to regain root privileges.

but there is no such statement for setreuid().

Upvotes: 1

Related Questions