Above, I used a lot of handwaving to describe how a setuid program should temporarily drop privileges when accing files specified by the invoking user.
An even better approach is to drop your privilege at the very beinning of the main procedure, and regain it only in those parts of the code that need to run with increased privilege. Usually, it's a lot easier to determine which files have to be opened with increased privilege than finding all the things you're better advised doing with user privilege.
Here's some sample code geared towards a POSIX platform (i.e. it assumes that the kernel supports saved ids):
uid_t ruid, euid;
void drop_privs()
{
if (seteuid(ruid) < 0)
fatal("drop_privs");
}
void regain_privs()
{
if (seteuid(euid) < 0)
fatal("regain_privs");
}
int main(int argc, char **argv)
{
ruid = getuid();
euid = geteuid();
drop_privs();
...
regain_privs();
do_privileged_stuff();
drop_privs();
...
}
This approach has a side effect that's worth mentioning. Quite often, your application uses libraries that do strange things you may not even be aware of, like opening files specified via environment variables, or reading files from the invoking user's home directory.
For instance, the C library on Linux contains a DNS resolver3.6 that honors a bunch of environment variables. One of them, called RESOLV_HOST_CONF, can be used to specify a special configuration file. If the resolver is unable to parse the file, it will print an error message, and display the offending line.
This has been abused several times, commonly using setuid root applications like rlogin:
$ export RESOLV_HOST_CONF=/etc/shadow $ rlogin foo.bar.com /etc/shadow: line 1: bad command root:xyW12ooklg:10971:0::7:7:: /etc/shadow: line 2: bad command bin:*:10547:0::7:7:: ... [contents of /etc/shadow follow]
What happens is that rlogin invokes the gethostbyname function to convert the host name foo.bar.com to an IP address. This function is part of the resolver, and invokes the code that evaluates the RESOLV_HOST_CONF environment variable and tries to parse the file specified. Needless to say, the contents of the shadow database are not exactly what the parser expects, so it complains about each line in turn.
One way to address this problem is to fix it inside the library (see
section
below). However, you may not always be
able to modify the library; either because you don't have the source
code, or because you don't want to your application to depend on a
specifically patched library.
However, this loophole is effectively closed by the privilege dropping approach outlined above. If rlogin would drop privileges early on, the call to gethostbyname would be executed with the privilege of the user who invoked rlogin. Consequently, the attempt to open /etc/shadow would fail, and nothing would be revealed.
Still, this approach isn't a magic cap that protects you from all harm.
On one hand, temporarily dropping privilege helps nothing in the face of
stack smashing attacks (described in chapter
). If
the attacker can inject arbitrary machine code into your application
and have it executed, it's a matter of inserting a seteuid()
system call to revert your ``protection''.
A second problem are temporary files. In many cases you do not want these file to be created with the privilege of the invoking user. TODO: Silly example ahead: Assume you want to create a temporary shell script you later want to feed to a shell running with increased privilege, it is sort of important to not allow the user to add his own commands to the file. So in this case, you should create the file as root.