[LWN Logo]

Date:	Sat, 9 Jan 1999 10:58:54 -0000
From:	"D. J. Bernstein" <djb@CR.YP.TO>
Subject:      Re: Wiping out setuid programs
To:	BUGTRAQ@NETSPACE.ORG

Several people have asked about the costs and benefits of splitting a
setuid program into an unprivileged user process and a non-setuid daemon.


C=08Co=08os=08st=08ts=08s.=08. It shouldn't take more than five minutes for=
 a kernel implementor
to support getpeereuid(). For example, Linux has "struct ucred peercred"
inside struct sock, and a SO_PEERCRED macro in sys/socket.h, used by the
following four syscalls:

   * socket() sets peercred.uid to -1.

   * listen() sets peercred.uid to the euid of the current process.

   * connect() copies peercred.uid from the listening socket to the
     connecting socket, and copies the euid of the current process to
     peercred.uid in the new connected socket.

   * getsockopt(...SO_PEERCRED...) copies the peercred to user space.

There's similar code to handle gid (and pid). The entire implementation
is about twenty lines long.

Given widespread kernel support for getpeereuid(), it's easy to split a
setuid program. All you have to do is identify the atomic operations
that the program performs upon restricted files, and move the code for
those operations to a separate daemon.


B=08Be=08en=08ne=08ef=08fi=08it=08ts=08s.=08. Two common types of code are =
no longer security-critical when
a setuid program is split:

   * Code in the unprivileged user process is safe.

     For example, /usr/bin/at has quite a bit of user-interface code,
     such as code to parse dates from argv. If that code loses its
     privilege then it is no longer a security threat to root.

     One can of course obtain the same benefit without getpeereuid():
     split the setuid program into an unprivileged user program and a
     smaller setuid program. Why don't most programmers do this? Because
     they don't see the benefits, whereas they do see the simplicity of
     using just one process. That isn't an option if setuid disappears.

   * Code in the daemon that deals with stdin, stdout, stderr, argv,
     envp, cwd, timers, et al. is safe.

     What makes setuid programs so difficult to review is the number of
     ways that the code could interact with an untrusted user. An OS
     library routine might secretly use the environment, for example;
     this has led to many security holes. There's much less to worry
     about when user data is isolated inside controlled files.

What's left is a tiny percentage of today's setuid code: the part that
reads untrusted user data. Securing this code against local attackers is
just like securing network code against remote attackers.


For comparison, here's the idea mentioned by Steve Bellovin. Put your
restricted file into a protected directory. Change its mode to 777. Now
the setuid program can

   (1) chdir() to the protected directory,
   (2) setuid(getuid()), and
   (3) read and write the restricted file.

Does this idea reduce the amount of security-critical code? Does it save
time for security reviewers? Can the programmer relax?

No! Step #3 no longer has a privileged uid but it still has a privileged
cwd. The attacker is still trying to convert his control over portions
of the process state into control over the restricted file. This idea is
like replacing setuid with setgid: it might turn a disaster into a
marginally less impressive disaster, but it makes no contribution to the
goal of eliminating all disasters.


---Dan


P.S. Beware that the setuid() syscall is inherently dangerous on some
UNIX variants, because the user is allowed to attach to the process
before the process calls execve().

This produces security holes in programs with privileges that aren't
dropped until at or just before execve()---for example, the cwd as
described above, or secrets hidden in stdio buffers, or close-on-exec
file descriptors for /etc/shadow. I'll leave precise damage assessment
as an amusing exercise for the reader.

My favorite workaround is to make the binary unreadable; I haven't found
any vendors silly enough to allow tracing here. Note that this prohibits
root-squashed NFS mounting for root-owned binaries.