Re: Wiping out setuid programs

From: D. J. Bernstein (djbat_private)
Date: Sun Jan 10 1999 - 20:19:49 PST

  • Next message: Daniel J. Frasnelli: "Solaris (2.)7 patch list"

    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.
    



    This archive was generated by hypermail 2b30 : Fri Apr 13 2001 - 14:28:39 PDT