Subject: Re: Addition to force open to open only regular files
To: NetBSD Kernel Technical Discussion List <email@example.com>
From: Greg A. Woods <firstname.lastname@example.org>
Date: 11/30/2000 15:36:26
[ On Saturday, November 25, 2000 at 13:34:34 (+0700), Robert Elz wrote: ]
> Subject: Re: Addition to force open to open only regular files
> Date: Fri, 24 Nov 2000 02:14:55 -0500 (EST)
> From: email@example.com (Greg A. Woods)
> Message-ID: <20001124071455.957D44@proven.weird.com>
> | In the end it all depends on just how eager you are to avoid what I
> | suspect is the primary cause of buffer overflow and printf format trust
> | exploits in set-ID programs -- i.e. exploits that are made possible by
> | saved-set-ID swapping.
> Do you have any evidence of this at all? It sounds totally bogus
> to me. It sounds as if you're confusing "code run as the user"
> with "the user's code" though surely it cannot be that.
Yes, actually, I do have one concrete example: smail-3. The exploit
against it was why it no longer ever uses seteuid(). Of course it still
runs as root much of the time, so it's not really avoiding 99% of
possible new exploits.
Furthermore I've read and understand enough set-ID code and working
exploits against such code to be reasonably certain that without
seteuid() the changes necessary to keep such code working with just
plain setuid() would almost certainly guarantee that far less of that
code would continue to run with privileges and thus far less of it would
be vulnerable to currently known exploits.
It is certainly true that of all existing known exploits against set-ID
programs which take advantage of the access() / open() race would have
been avoided if even the most restricted version of open_as() were
employed instead. Of course using seteuid(getuid()) ... seteuid(oeuid)
around the file access is just as an effective a fix (if perhaps
slightly more complex).
In the case of the more capable version of open_as() that I proposed,
the question is perhaps still there since I'm just guessing based on my
experience with existing set-ID programs and existing buffer-overflow
exploits and such. I don't have any the results of scientific study
that would show whether or not my hypothesis is true over a larger
sample of programs than just one. It would certainly be interesting to
examine existing exploits against set-ID programs though and see if they
would have been avoided if the program had been forced to drop its
privileges completely much earlier in its execution (which of course a
restricted seteuid() and enhanced open_as() would allow it to do).
> Without doubt, all code in a setuid binary (or at least, all code before
> it does setuid(getuid())) needs to be very carefully sanitised. That
> includes code that is run after an ID swap. Buffer overflows (and such)
> are no more allowable there than they are in any other part of a setuid
> program, and
Unfortunately the currently widely believed fallacy that seteuid() is
sufficient to drop privileges and keep the code that executes in this
manner "safe" has persisted amongst many programmers.
> I can't imagine why anyone would believe that a programmer
> who was insanely careful to always do ...
> fgets(buf, sizeof buf, stdin);
> strncpy(buf, from, sizeof buf);
> and such is suddenly going to start writing
> strcpy(buf, from);
> just because there's a seteuid(getuid()) call preceding the latter code.
> That simply doesn't make sense.
Sometimes exploits are not due to such obvious mistakes.
> I have now been totally convinced that open_as() is a total waste of
> time, and offers nothing at all.
It's only purpose is to solve the access() / open() race in systems
which do not offer seteuid().
If you're insisting on keeping seteuid() then you're right -- there's no
point to implementing open_as().
However if you want to get rid of seteuid() so that buffer overflow
exploits, printf format string exploits, and whatever new and as yet
unknown exploits come along to add unauthorised code to a running set-ID
process, can be restricted such that they are far less effective then
a restricted version of open_as() will offer a solution to the access()
/ open() race for set-ID processes that must safely access a user-owned
The more powerful version of open_as() that I also proposed (i.e. the
one which would allow file access as the effective ID after a restricted
seteuid(getuid()) call) would make it even easier for a set-ID
programmer to decide to permanently drop all privileges, except those
offered by this version of open_as(), right at the very start of the
program. As I initially warned, and as others have since reminded us,
this still allows sophisticated exploit to eventually achieve enhanced
privileges again, but I believe the reduced risk is worthwhile since of
all the possible exploits I've dreamt of to date, all are detectable
*before* they can be taken advantage of, and thus a good intrusion
detection system could prevent them from being successful.
> Given that that you have said
> that the only uid/gid pair that you would ever use there are getuid()
> and getgid(), which means actually passing them through the syscall is
> just a waste of time - so better would be realopen() with the same args
> as open, but using different credentials to do the permission checks.
That's just for library routines that want to safely open a user-owned
As I've already discussed, set-ID programmers could still make use of
the uid/gid parameters for purposes such as safely accessing an
arbitrary user's ~/.forward file from within a monolithic mailer that
runs as root all of the time.
Of course in the case of the more enhanced version of open_as() which
still allows a restricted form of seteuid() the set-ID programmer would
still want to pass the original effective-IDs to open_as().
> Then of course, to be complete, we also need realunlink() reallink()
> realrename(), realmkdir(), realrmdir(), realstat() reallstat(), realsymlink()
> realmkfifo(), realsocket() (for the unix domain case) .....
Why? What requirement would such functions fulfill? Are there known
race conditions in their use which need to be avoided? Only socket()
returns a file descriptor....
A socket_as() call might be a useful companion to the enhanced open_as()
that I've described, but that's about it I think. (and thus it would be
useful in the ftpd case too)
> But then, it seems like the sequence
> euid = geteuid();
> fd = open();
> has exactly the same effects as the above - aside from the paranoid
> desire to make the last of those illegal. ie: nothing new is needed
> at all.
> In that precise usage, I can't see how even you can complain that
> there's any difference at all between the above sequence and the
> open_as() you're championing.
Well of course! There is no difference, which is why open_as() can
successfully solve the problem of seteuid()!
> Most likely the uid swapping and swap back adds some potential problems
> that weren't there before - but so does everything new - nothing comes
> without some risk.
The problem with allowing a process to regain its set-ID privileges is
that it creates much much more risk than it returns in features.
Except for the odd really special case, such as ftpd neeeding a
privileged TCP port *after* it has successfully accepted the credentials
of a normal user, the requirements fulfilled by seteuid() are apparently
only to avoid the access() / open() race.
> Again, what is important is to evaluate the risk,
> see if it is possible to be safe, if so, document that method, and then
> leave it to people to do things the right way.
Absolutely. However giving programmers more rope than necessary to
fulfill their requirements has been proven to be quite dangerous.
> There seem to be some involved in this debate who want to make it easy
> for people to write setuid programs that are safe - that's an insane
> desire, there's no point even attempting it. Writing setuid programs
> has always been hard, and will always be hard.
Well, since the current techniques available to set-ID programmers make
it easy for even experienced and expert programmers to make mistakes and
incorrect assumptions about the relative safety of their code, I think
it's foolish to ignore features which will guide even experience
programmers into writing safer code.
I know lots of people who've suffered losses from exploits that I
believe would have been avoided with better features that fulfill the
same requirements, and all of those people very much wish to avoid the
next style of exploit which may come along and cause them trouble
Greg A. Woods
+1 416 218-0098 VE3TCP <firstname.lastname@example.org> <robohack!woods>
Planix, Inc. <email@example.com>; Secrets of the Weird <firstname.lastname@example.org>