tags:

views:

118

answers:

2

I have a daemon which gets started as root (so it can bind to low ports). After initialisation I'd very much like to have it drop root privileges for safety reasons.

Can anyone point me at a known correct piece of code in C which will do this?

I've read the man pages, I've looked at various implementations of this in different applications, and they're all different, and some of them are really complex. This is security-related code, and I really don't want to reinvent the same mistakes that other people are making. What I'm looking for is a best practice, known good, portable library function that I can use in the knowledge that it's going to get it right. Does such a thing exist?

For reference: I'm starting as root; I need to change to run under a different uid and gid; I need to have the supplementary groups set up correctly; I don't need to change back to root privileges afterwards.

A: 

If you are using a debian based GNU/Linux environment, you can use:

start-stop-daemon --chuid user:group
licorna
That changes the user/group _before_ starting the process, which wouldn't work for this question.
David Zaslavsky
+7  A: 

In order to drop all privileges (user and group), you need to drop the group before the user. Given that userid and groupid contains the IDs of the user and the group you want to drop to, and assuming that the effective IDs are also root, this is accomplished by calling setuid() and setgid():

if (getuid() == 0) {
    /* process is running as root, drop privileges */
    if (setgid(groupid) != 0)
        fatal("setgid: Unable to drop group privileges: %s", strerror(errno));
    if (setuid(userid) != 0)
        fatal("setuid: Unable to drop user privileges: %S", strerror(errno));
}

If you are paranoid, you can try to get your root privileges back, which should fail. If it doesn't fail, you bailout:

 if (setuid(0) != -1)
     fatal("ERROR: Managed to regain root privileges?");

Also, if you are still paranoid, you may want to seteuid() and setegid() too, but it shouldn't be necessary, since setuid() and setgid() already set all the IDs if the process is owned by root.

The supplementary group list is a problem, because there is no POSIX function to set supplementary groups (there is getgroups(), but no setgroups()). There is a BSD and Linux extension setgroups() that you can use, it this concerns you.

You should also chdir("/") or to any other directory, so that the process doesn't remain in a root-owned directory.

Since your question is about Unix in general, this is the very general approach. Note that in Linux this is no longer the preferred approach. In current Linux versions, you should set the CAP_NET_BIND_SERVICE capability on the executable, and run it as a normal user. No root access is needed.

Juliano
You'd want to set the gid as well - and this might vary slightly across unixes regarding wether setuid/setgid actually messes with the real and saved id's as well
nos
This does indeed need to be a portable solution, so no Linux capability fu allowed, I'm afraid. And I have indeed tried the simple approach with setuid() and setgid(); it doesn't set the groups correctly (and if you don't call setgroups(), apparently you can end up still being a member of some of root's groups!).
David Given
@nos Thanks. Expanded to cover groups. If the process is owned by root (as the OP mentioned) or if it is setuid-root, then setuid() and setgid() already set all IDs (real, effective and saved). This is in the specification. Otherwise, the implementation would not be POSIX-conforming.
Juliano
If setuid()/setgid() set all the IDs, why then do most implementations I've seen (including the OpenSSH one that nos linked to) call seteuid()/setegid() as well? Why does it prefer to use setresuid() and setreuid() rather than basic setuid()?
David Given
@David I do not know. Perhaps for compatibility with some broken Unix implementation, for safety, etc. Perhaps because it considers process not running as root (which is not your case). You can check the POSIX standard about the implementation of setuid(), it is clear about the semantics of setuid(): http://www.opengroup.org/onlinepubs/000095399/functions/setuid.html . You asked for a portable solution, setresuid() and setreuid() are not portable.
Juliano