the djb way

daemontools


envuidgid, setuidgid privelege control

The daemontools package comes with two utilities for controlling the priveleges available to service daemons: envuidgid and setuidgid. Each is invoked similarly:

envuidgid user program ...

setuidgid user program ...

That is, each looks up "user" in the system account database (/etc/passwd), does something with the results, then runs program and its arguments with execve(2).

But each is designed for a specific purpose. Knowing when and how to use which is an important part of putting up secure services with daemontools.

First envuidgid. By itself it does very little. If "user" is found, it simply sets two environmental variables, $UID and $GID, with the user id and group id of the user, and puts these variables into the process environment of the sub-program.

That is, envuidgid by itself doesn't do anything to change the real or effective uid of the process. All it does is set up a couple of environmental variables. It is something like using the id(1) utility in a shell script:


#!/bin/sh
# envid.sh
# usage:  envid.sh user program ...
# ===
if [ $# -lt 2 ] ; then
  exit 111
fi

USER=$1; shift

UID=`id -u ${USER} 2>/dev/null`
GID=`id -g ${USER} 2>/dev/null`

if [ ${UID}"X" != "X" -a ${GID}"X" != "X" ] ; then
  exec env UID=${UID} GID=${GID} $@
else
  exit 111
fi

### that's all, folks!

The envuidgid utility may be run by any user, and the sub-program will run with exactly the same priveleges as the caller. Normally envuidgid is only used in applications where the sub-program is designed specifically to look for the $UID and $GID variables; the sub-program then takes responsibility for actually dropping priveleges.

In contrast, setuidgid actually does change the real and effective ids (uid and gid) of the process with setuid(2) and setgid(2). The setuidgid utility may only be run by root.

Because "run" scripts start out with root priveleges, setuidgid may be used as an effective security measure to drop the priveleges of the sub-program to an unpriveleged account. Once the priveleges are dropped by setuidgid, there is no way the sub-program can regain them. This is what you are going for.

When to use which? Of course the general principle is to run with the least priveleges possible, as soon as possible. Actual usage depends on the type of run script, and the requirements of the sub-program.

In some cases, the sub-program absolutely requires root priveleges, and there is nothing you can do about it. Examples include the apmd and dhcpd services. Each of these needs root priveleges for low-level interaction with the system and configuring network interfaces. They will fail if you try to run them with setuidgid as a non-priveleged user.

In other cases, a service can be setup to drop priveleges directly with setuidgid. Examples include the fetchmail service, and all of our multilog run scripts. These services run fine with unpriveleged accounts, simply by chown(8)-ing the specific files and directories they may need to access.

Most of the services we run, however, will be network servers running under tcpserver in the ucspi-tcp package, and this is where we see the effective use of envuidgid.

Many network services, such as SMTP, HTTP, and POP3, are expected to run on standard ports (25, 80, 110). Since all these are in the range of what are known as "priveleged" port numbers (that is, < 1024), the process listening to the socket needs to have root priveleges. So tcpserver generally needs to start out as root.

After making the connection, however, it is desirable to drop priveleges.

There are a few common idioms for this in the "run" scripts for network services. One is to first use the id(1) utility to get specific arguments for the -u and -g options of tcpserver:


#!/bin/sh
# mydaemon/run
#===
UID=`id -u safeuser`
GID=`id -g safeuser`

exec \
  tcpserver -u $UID -g $GID \
  0 999 \
    mydaemon

### that's all folks!

Now tcpserver will listen for incoming connections as root, but arrange to drop priveleges to "safeuser" before spawning mydaemon.

The -U option (uppercase "U") to tcpserver is designed to cooperate with envuidgid, to achieve the same effect:


#!/bin/sh
# mydaemon/run
#===
exec \
  envuidgid safeuser \
    tcpserver -U \
    0 999 \
      mydaemon

### that's all folks!

This tells tcpserver to read the values found in $UID and $GID. Remember, the envuidgid utility is simply used to setup these variables. It is tcpserver itself that actually drops priveleges to the uid/gid of "safeuser", before forking mydaemon.

There is no functional difference between these two idioms. The first may be preferable if you want to perform some script validity tests on $UID and $GID, before exec-ing the service. Otherwise we prefer the use of tcpserver -U, setup with envuidgid.

Either way, both of the above work as though tcpserver itself calls setuidgid, and are comparable to:


#!/bin/sh
# mydaemon/run
#===
exec \
  tcpserver \
  0 999 \
    setuidgid safeuser mydaemon

### that's all folks!

Since tcpserver has the setuidgid functionality built-in, however, there is no good reason to setup a script this way, and we have never seen this idiom used in practice. (It would be slightly less efficient as setuidgid would be invoked each time tcpserver forks for a new connection.)

As you encounter the various "run" scripts on-line and in later sections, you will find many variations on the theme, depending on the design and requirements of the server. The qmail-smtpd executable in the qmail-smptd service, for example, needs to be run as the specific user "qmaild", and either of the idioms shown above are suitable for the purpose.

On the other hand, the qmail-popup executable in the qmail-pop3d service is purposefully designed to run as root, and no use of envuidgid or setuidgid is required or possible. The qmail-popup -> checkpassword -> qmail-pop3d command chain itself is designed to control access and drop priveleges.

Finally, yet another example may be found in the publicfile httpd service. The abbreviated run script looks something like this:


#!/bin/sh
# httpd/run (abbreviated)
# ===
HTTP_ARCHIVE=/path/to/webdocs
exec 2>&1

exec envuidgid safeuser \
  tcpserver \
  0 80 \
    /usr/local/bin/publicfile/httpd ${HTTP_ARCHIVE}

### that's all, folks!

Does it seem like we left something out? Although envuidgid is here, none of the options to tcpserver are used to actually drop priveleges to "safeuser". What's going on here?

The answer is that the httpd executable itself uses the $UID and $GID variables setup by envuidgid. Although httpd starts out as root, once it chroot(8)s to the document archive directory, it makes its own arrangements to drop priveleges to "safeuser".

The point is that each script needs to be designed with a good understanding of the exact privelege requirements and facilities of the service itself. By knowing when and where to use envuidgid and setuidgid, perfect privelege control is then possible.


Copyright © 2004, Wayne Marshall.
All rights reserved.

Last edit 2004.06.03, wcm.