Subject: O_REG_ONLY, O_NOFOLLOW, open_ass(), and other such beasts
To: None <tech-kern@netbsd.org>
From: Charles M. Hannum <root@ihack.net>
List: tech-kern
Date: 12/05/2000 19:27:38
So it seems to me the issues can be summarized as follows.  Everything
that applies to UIDs also applies (more or less symmetrically) to
GIDs, so I will merely speak of UIDs.

* setreuid() makes it difficult to do POSIX-style euid swapping.

  This is certainly problematic, but I note that nothing in NetBSD is
  using setreuid(), except in a few cases where it's a degenerate case
  and could be a call to setuid() or seteuid() instead.  So, this does
  not seem to be an issue for NetBSD libraries or application
  programs.

  It is true that we must assume that setuid programs do not use
  setreuid() in order to code euid-swapping safely in libraries.  But
  we also assume that setuid programs don't use gets() and other
  `unsafe' functions.  Protecting against broken third party software
  is an intractible problem.  Ergo, it is pointless to try to `solve'
  it in this one case alone.

* We do not want setuid programs or other programs running as root to
  inappropriately open devices, named pipes, etc.

  I note several things:

  * In general, it is already bad policy, if not completely incorrect,
    for such programs to be opening files as root (or the saved user
    ID, anyway, if not root).  This has in the past led to documented
    holes, were people were able to directly read or infer the data in
    a file owned by someone else.  Therefore, such programs should be
    opening the file/device/socket/pipe as the original user ID
    anyway.

  * A normal user cannot create device nodes.  However, they may have
    permissions on a device.  A normal user *can* create sockets and
    pipes.  This may cause a setuid program to rewind a tape drive or
    stall trying to read from a pipe.

    So what?  If the user has appropriate permissions, they can dd to
    the device or screw with it in some other way.  BFD.  We should
    not be second-guessing the user and preventing them from doing
    something when it does not pose any security risk.

  * But what about /etc/security?

    First of all, /etc/security, like all good programs, should be
    reading the files as the target user.  If the file is not owned or
    openable by that user, then *login(1) and other programs should be
    rejecting it anyway*, so there is no reason to check it further.
    (Last I checked, most of them actually did this.)  (I can think of
    at least one environment right off the top of my head in which
    this is a security *win* -- marking the user's home directory
    sticky and creating empty .klogin and .ssh files owned by root, to
    prevent that user from using those facilities -- without having
    /etc/security bitch about it every night.)

    Given this, the same comments as before apply.  If the user can
    already do this himself, there is no particular reason to protect
    against it in /etc/security.  `Don't give permissions to untrusted
    users.'  However, careful application of O_NDELAY/O_NONBLOCK can
    still prevent socket and pipe stalls.  (The user can't *create* a
    device node anyway.)

    Ergo, it's not clear that O_REG_ONLY buys us an actual security.
    It *is* clear, though, that it *cannot* be used in place of
    swapping in the correct credentials.  Using the wrong credentials
    is still fatally flawed and must not be allowed.

  * What about symlink attacks?

    There is still a possibility -- especially in shared writable
    directories, or if the user is foolish enough to have a symlink
    into a shared writable directory -- of symlink race attacks.  This
    is not specific to setuid or natively root programs, nor is it
    even specific to the device/socket/pipe `issue'.  One solution to
    it is O_NOFOLLOW, which I believe at least one other system is
    already using.


My conclusions from this are three-fold

* O_REG_ONLY is not terribly useful (except perhaps in place of using
  O_NDELAY/O_NONBLOCK), and the proper approach is to thoroughly audit
  libraries to make sure they are doing UID swapping where
  appropriate.

* As a separate endeavour, we should consider implementing O_NOFOLLOW
  to prevent symlink race attacks (but, given the previous, this will
  generally only affect systems where users have done something
  actively stupid).

* Readers may have noticed I didn't mention open_as() anywhere else.
  Draw your own conclusions on that.


Does anyone have further comments on this subject?