Bugtraq mailing list archives

Re: Wiping out setuid programs


From: djb () CR YP TO (D. J. Bernstein)
Date: Mon, 11 Jan 1999 04:19:49 -0000


1. Managing a privileged daemon connection is no more difficult than
managing any other privileged open file.

For example, there's a library routine today that opens /etc/shadow,
inside a root utility that calls setuid() and then runs a user program.

Does this mean that user programs have access to /etc/shadow? Do we have
to change read() to check the caller's euid, instead of preserving the
privileges sneakily obtained by the euid that called open()?

Of course not. The library sets the close-on-exec bit. The privileged
descriptor is closed before the user program starts. End of story.
(Except for systems like Solaris. See my previous note on setuid().)

The library routine might be changed tomorrow to open a connection to a
daemon through a UNIX-domain socket accessible only to root. Or it might
be changed to open a connection to a daemon that grants access based on
getpeereuid(). Yes, the resulting file descriptors are privileged. No,
that's not a problem. That's how file descriptors have always worked.

You can think of a world-writable socket with getpeereuid() as a giant
farm of sockets, with one socket writable by each user. Or you can think
of it as a restricted socket, with a setuid helper utility that makes a
connection, writes getuid(), drops privileges, and runs a user program;
such utilities have been available for years. In any case, it's obvious
how to use it correctly.


2. I'm not saying that sendmsg() uids are completely useless. Alan Cox
gives the example of a root web server running scripts as various users,
and logging some packets of data from each script. Today this server
needs to create at least one pipe per user. With sendmsg() uids, all the
scripts could share a single open file for logging.

However, whether or not we have sendmsg() uids, we need connect() uids,
so that connection-oriented daemons can account for all resource use.
Implementing streams on top of datagrams is an unnecessary roadblock.
(Implementing datagrams on top of streams is no big deal.)


3. It's certainly true that serious configuration errors may prevent a
daemon from starting. But the same is true of setuid programs.

It's also true that serious programming errors may cause a daemon to
crash. That's why there are monitoring tools that automatically restart
dead daemons. This feature is built into init under System V.

What about daemons that insist on running in the background? No problem:

   #!/bin/sh
   thebackgrounddaemon 5>&1 | cat >/dev/null

This works for typical daemons that close descriptors 0, 1, and 2.


Steven M. Bellovin writes:
and, on many platforms, the
utterly buggy implementation of UNIX domain sockets and (especially) file
descriptor passing.

I agree that fd passing is a fertile ground for kernel bugs, up to and
including panics. But I've never had any problems with basic I/O through
UNIX-domain stream sockets.

Look at all of the remote attacks on sendmail, or all the buffer overflows!

I see from http://pobox.com/~djb/docs/maildisasters/sendmail.html that,
between sendmail 8.6 in 1993 and sendmail 8.8.7 in 1997, there were
eight bugs that allowed attackers to run programs as unauthorized uids:

   8.8.3: Local attack. Would have been impossible if local delivery had
   been run under the target user's uid.

   8.8.2: Local attack. Would have been impossible if the daemon hadn't
   been setuid.

   8.8.0/8.8.1: Remote attack---the MIME 7-to-8 buffer overflow.

   8.7.5: Local attack. Would have been impossible on a well-managed
   system if the queue runner hadn't been setuid. (This is one of the
   security holes I found; the real problem is an obvious flaw in the
   UNIX getpw*() API.)

   8.7.5: Local attack. Would have been impossible if the program
   building a user's From line hadn't been setuid.

   8.7.3/8.6.12: Remote attack, as I recall---the bizarre-PTR problem.

   8.6.6: Local attack. Would have been impossible if the argv-parsing
   code hadn't been setuid.

   8.6.5: Local attack. Would have been impossible if local delivery had
   been run under the target user's uid.

Eliminating setuid would have stopped four of these holes. Reducing the
local delivery privileges in a straightforward way would have stopped
two more. Running header conversions in a controlled playground would
have stopped the other two.

We can use string-safe languages like Java, and we can store temporary
files in some place that isn't world-writable -- and then we can trip
over bizarre host names, inappropriate code paths that allow for
command execution in the wrong spot, etc.

And we can develop better programming tools that reduce your error rate
even more. For example, text0-format files would have stopped the
bizarre-PTR problem.

But I'm talking about something much more fundamental: reducing the
amount of code in which bugs can lead to security holes. Yes, there's a
lot of buggy code, but most of that code can be taken out of the loop.
What's left is small enough to carefully review.

---Dan

P.S. I'm curious whether the mailing list and web archives can handle
RFC 822-endorsed b bo ol ld df fa ac ce e, without MUA Q-P corruption this time.



Current thread: