Subject: O_REG_ONLY, O_NOFOLLOW, open_ass(), and other such beasts
To: None <firstname.lastname@example.org>
From: Charles M. Hannum <email@example.com>
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
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
* 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
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
* 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
* 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?