13

I have a process that gets started by a damon running as root, now I want to "downgrade" this process's privileges to those of your average user. Is this possible? If yes how?

PS: Running unix on a mac

5 Answers5

15

sudo tcpdump -Z uses initgroups(3), setgid(2) and setuid(2) to drop the root privileges of its own process.

# code taken from: 
# http://www.opensource.apple.com/source/tcpdump/tcpdump-32/tcpdump/tcpdump.c

/* Drop root privileges and chroot if necessary */
static void
droproot(const char *username, const char *chroot_dir)
{
...
            if (initgroups(pw->pw_name, pw->pw_gid) != 0 ||
               setgid(pw->pw_gid) != 0 || setuid(pw->pw_uid) != 0) {
                    fprintf(stderr, "tcpdump: Couldn't change to '%.32s' uid=%lu gid=%lu: %s\n",
                        username, 
                        (unsigned long)pw->pw_uid,
                        (unsigned long)pw->pw_gid,
                        pcap_strerror(errno));
                    exit(1);
            }
...
}
phron
  • 151
  • 3
    Correct. initgroups, setgid, setuid (last!) is precisely the right paradigm on unix, and should always be followed. In addition, a responsible "droproot" function checks that its uid and gid really have been set, even if all three primary functions returned success. – Nicholas Wilson Feb 22 '13 at 22:43
4

The process itself has to call setuid(2). You should also investigate running it inside chroot(8) if you aren't already. As far as I know, there is no way for root to change the uid of another process.

If the reason you are running it as root is to bind ports, I'd suggest running it as a normal user on a higher port and using ipfw(8) on OS X to forward port 80/443/etc to the higher port:

http://support.crashplanpro.com/doku.php/recipe/forward_port_443_to_pro_server_on_mac_osx

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
polynomial
  • 2,461
  • 20
  • 15
  • I put setuid(uid) in my program that runs as root but I need it to run as a normal user & nothing happens i.e. it continues running as root – Samantha Catania Sep 23 '11 at 07:10
  • You probably need to catch the errors from it then(maybe you are getting an EINVAL for the uid). Its unlikely setuid is broken on your machine you can verify by seeing if apache is running as _www. Programmer docs: http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man2/setuid.2.html – polynomial Sep 23 '11 at 07:15
  • you're right it gives a syntax error from the uid even though it is correct. is this the right format setuid(500)? – Samantha Catania Sep 23 '11 at 07:19
  • found the problem: I was trying to run the command through system() but I just run the command it work & solved my problem; thx for the help – Samantha Catania Sep 23 '11 at 07:29
  • No. This is not good advice: calling setuid() alone is absolutely not enough. – Nicholas Wilson Feb 22 '13 at 22:35
3

You can run commands as other users using su:

 su USERNAME -c COMMAND

Will run COMMAND with privileges dropped to USER.


Note that, by default, su will use the target user's shell interpreter to run the command. By contrast, the default behaviour of sudo is to treat the COMMAND as a standalone program, that is run the current environment. Of course these default behaviours can be changed with various switches and environment variables.

  • Looks like su doesn't work if USERNAME has no shell defined (or /bin/false) whereas sudo does work. – Aif Jan 16 '13 at 21:24
  • 1
    @Aif If the user is not allowed to run interactive commands, then the default behaviour su will reflect that. However, one can always override that using the -s switch. Note that the purpose of su is to mimic a given user's behaviour - which is normally influenced by his/her shell. By contrast, sudo would (by default) ignore target user's shell setting. – rozcietrzewiacz Jan 17 '13 at 00:37
  • No, please don't run things using a shell simply to drop privileges. That leaves far too much in the control of an attacker, reading in various config files you don't want to be touching. – Nicholas Wilson Feb 22 '13 at 22:37
2

To drop privileges, you need a non-root user to drop to. Then it's just a matter of switching to that user:

#define UNPRIV_UID  48
#define UNPRIV_GID  48

if (getuid() == 0) { // we are root
    // setting UID/GID requires root privileges, so if you don't set
    // the GID first, you won't be able to do it at all.
    if (setgid(UNPRIV_GID)!=0) die("Failed to set nonroot GID");
    if (setuid(UNPRIV_UID)!=0) die("Failed to set nonroot UID");
}

ASSERT(getuid() != 0); 

Note that this is done within the program itself, rather than in a wrapper script. Many programs require root privileges for some specific purpose (e.g. to bind to a low-numbered port), but don't need root after that. So these programs will start as root, but then drop privileges once they're no longer needed.

If you don't need root privileges at all, then just don't run it as root. E.g.:

# Change this:
myprog -C /my/config/file

# To this:
sudo -u someuser myprog -C /my/config/file
# Or this
su someuser -c "myprog -C /my/config/file"
tylerl
  • 2,468
  • 15
  • 18
  • 1
    Note that this allows the process to recover the privileges if it wishes to! The setuid function only sets the effective UID, not the real UID. You should use setreuid if you don't want the process to be able to get the privileges back. (And the code above doesn't deal with supplementary group privileges either. It's suitable only for launching mostly-trusted code.) – David Schwartz Sep 23 '11 at 10:35
  • @David Schwartz code was intentionally simplified to show the mechanism used. – tylerl Sep 23 '11 at 17:58
  • If you're going to simplify security-critical code, you have to make it very clear that this is what you're doing. And you should say things like "it's just a matter of" when it isn't. – David Schwartz Sep 23 '11 at 23:01
  • 2
    @David Actually, setuid() does set real and saved userids; you may be thinking of seteuid(). Not all systems have setreuid(), so it can't be used quite everywhere. The exact semantics of setuid() are compliced, but if you have euid 0, you will be able to drop all traditional user-id privileges with setuid(). The biggest omission in this answer is that initgroups or setgroups must be called as well as setgid and setuid, and that more thorough assertions should be done at the end. – Nicholas Wilson Feb 22 '13 at 22:41
0

If you're executing a different executable, i.e. you're calling execve or another of the exec familiy of function, perhaps indirectly through a function like system or popen, and the child process should run without privileges from the start, then the simplest way is to get a shell involved, and call su. Here's an overview of how the code might look like in Perl, showing the quoting needed for :

$shell_command = quotemeta($path_to_executable) . " --option";
$shell_command =~ s/'/'\\''/; # protect single quotes for the use as argument to su
$su_command = sprintf("su -c '%s' %s", $shell_command, quotemeta($user_name));
open(PIPE, "$su_command |") or die;

If the child process needs to start as root but drop privileges later, see the code in this answer, which illustrates how to downgrade privileges in a process.