Subject: Re: Addition to force open to open only regular files
To: None <tech-kern@netbsd.org>
From: Greg A. Woods <woods@weird.com>
List: tech-kern
Date: 11/23/2000 13:11:48
[ On Thursday, November 23, 2000 at 22:25:39 (+0900), Noriyuki Soda wrote: ]
> Subject: Re: Addition to force open to open only regular files
>
> According to your next message, it seems you are misunderstanding
> how saved-uid/gid works.
> The exploits of saved-set-ID do not really exist.

They might not exist in POSIX, depending on just what non-root set-ID
processes are capable of, however they are very very very real in *BSD!
(and GNU/Linux!)

So, please tell that to the people who's machines have been "rooted"
because of it!  That includes a large fraction of the number of machines
compromised because of buffer overflows in setuid-root programs!

It's even worse in systems like UNIX System V Release 4.x where *BSD
compatability was added to the system without taking into account the
new semantics setreuid() added.  Solaris SunOS-5 was vulnerable to /proc
attacks to set-ID-root programs up until at least 5.3, and perhaps even
5.4, because of this problem, and because the /proc authors did not
anticipate the dangerous setreuid() semantics being added to their
system.  Obviously it's possible to duplicate checks made in other
similar parts of the system so as to provide security in /proc.  The
trick is in knowing that those checks are necessary, and they were not
when /proc was first implemented, and the people who integrated *BSD
compatability did not notice that they were not present either.  (That's
a historical interpretation of how that bug came into being -- reality
may have differed slightly but the result was the same.)

> Please read below.
> 
> soda> (*) The reason that our saved-uid/gid feature is not compatible
> soda>	with POSIX_SAVED_ID is:
> soda>	In POSIX_SAVED_ID, only root-setuid program can drop
> soda>	it's saved uid by setuid(2), normal-user-setuid program
> soda>	cannot drop it's saved uid.
> soda>	In NetBSD, normal-user-setuid program can drop it's
> soda>	saved uid privilege.
> 
> woods> No, that's wrong because even in NetBSD a setuid-non-root program can
> woods> drop its saved-set-ID privs.
> 
> ???
> Yes, "setuid-non-root program can drop its saved-set-ID privilege in NetBSD".
> That's what I said.
> 
> Hmm, probably my poor english ability confused you?
> 
> But, "setuid-non-root program cannot drop its saved-set-ID privilege
> in POSIX". That's is why NetBSD doesn't conform to POSIX about
> saved-set-ID.

Sorry, let's start that again.  It's not your command of English that's
at question here -- I made a mistake there too.  You are correct -- a
non-root set-ID process cannot drop its privileges in a pure POSIX
system.  Unfortunately this is not the first time I've forgotten that
fact since I've often confused POSIX with implementations that provide a
restricted seteuid() call which correct this "mistake" in POSIX (but
which still does not permit set-ID-root processes to regain privs).

> woods> The difference in POSIX is that seteuid() also sets the saved
> woods> set-user-ID value for the current process if the effective user-ID is
> woods> that of the superuser.  I.e. in POSIX a setuid-root process can never
> woods> regain its root privs after calling seteuid(getuid());
> 
> No. The above your sentence is completely wrong.
> Please re-read POSIX.1 carefully.

Those particular statements are in fact correct, outside of the use of
"seteuid()", which of course POSIX does not have.  See below....

> First, POSIX doesn't define seteuid() at all.
> POSIX only defines setuid().

Yes -- however in POSIX/SysV non-root set-ID processes can still choose
to set their effective-IDs to match either their saved-set-IDs or their
real-IDs, i.e. they can swap between real and effective IDs.

However a POSIX/SysV setuid-root process can only set its real,
effective, and saved-set IDs to be new values once (unless it sets them
to zero again, of course).

So, indeed in POSIX a setuid-root process can never regain its root
privileges after dropping to some other ID.

> Second, in NetBSD (and also in all POSIX based systems I know),
> setuid-root process can regain its root privilege by the following code:
> 	euid = geteuid();
> 	seteuid(real_uid);
> 	... do something which should be done in real-uid privilege ...
> 	seteuid(euid);

> This is what "saved-set-ID" intended, although because POSIX doesn't
> define seteuid(), this behaviour is implementation-defined.

A fully POSIX or SVID compliant system will never permit a setuid-root
process to regain its root privileges.  This is by design, not by
accident of implementation or ommission in the specification.  It might
appear upon reading just the specification of saved-set-ID that this is
an omission, but it is most definitely not.  POSIX saved-set-ID
semantics were defined well after 4BSD setreuid() was in use to swap
between effective and real IDs and at least some members of the POSIX
committee were well aware of the potential danger in allowing
set-ID-root processes to regain their privileges.

Processes under the pure Unix security model must never be permitted to
regain their privileges.  The only way a process can ever obtain
privilege in the pure Unix security model is by inheritance from another
privileged process [i.e. through fork()], or by virtue of the fact that
it was started from a setuid binary image file.  This is VERY critical
to the model!  Within the definition of the model processes must never
be permitted to regain their set-ID privileges after dropping them!  A
new process image, one that is set-ID, must be loaded in order to gain
privileges.

Yes implementations have been devised which make saved-set-ID swapping
safe to some extent.  In UNIX System V Release 3.2 and POSIX this
ability is only permitted as a compromise for non-root set-ID processes.
In *BSD it is permitted for all set-ID processes, but obviously with
less success since it has directly resulted in the vast array of
exploits against existing programs.

> Third, in both POSIX and NetBSD, setuid-root process can permanently
> revoke its set-ID privilege by the following code:
> 	setuid(real_uid);

Yes.

> Thus, in both POSIX and NetBSD, the buffer-overflow exploit example
> is impossible against setuid-root process after the process did
> the setuid(real_uid).

Yes.

However most processes use seteuid() and thus to not permanently revoke
their privileges -- which is why buffer overflow, printf() format, and
other similar types of exploits are potentially even more dangerous than
some people seem to realise.

> I think the comment about this in our <sys/unistd.h> leads
> misunderstandings. Perhaps this is why you misunderstood.
> I believe we should rewrite the first half of the comment.

I do not mis-understand -- I only fail to communicate my understanding
properly.  I have been writing and maintaining successful, portable,
set-ID programs for well over a decade.  So far as I know I've always
found fixes for my programs before they've been exploited!  :-)

> Fourth, in NetBSD, setuid-non-root process can permanently revoke
> its set-ID privilege by the following code:
> 	setuid(real_uid);
> Thus, in NetBSD, the buffer-overflow exploit example is impossible
> against even setuid-non-root process after the process did the
> setuid(real_uid).

> In POSIX, the buffer-overflow exploit is *possible* against
> setuid-non-root process, because setuid-non-root process *cannot*
> permanently revoke its saved-set-ID in POSIX.

Yes, exactly.

> This is what NetBSD and POSIX differs, and why NetBSD never
> defines _POSIX_SAVED_IDS.

If only that were true.  Unfortunately in *BSD most set-ID programs only
use seteuid() [or setreuid()], not setuid(), because they wish to be
able to swap between their real and effective IDs.

There's a vast gulf of distance and difference separating the ideal
set-ID program from actual examples.  The list of programs I posted
which use seteuid() is not the entire list of set-ID processes and of
the remainder there are still more than enough which do not drop their
privileges permanently as soon as possible with setuid(getuid()).

If *all* set-ID programmers *always* followed *all* of the rules of
set-ID programming, this would not be an issue, and indeed setreuid()
and seteuid() probably would not even have been dreampt up in the first
place since everyone would stick to the pure Unix security model!  The
only problem with the original implementations of the Unix security
model was that having separate access() and open() system calls was not
considered significantly dangerous.  Certainly if open_as() had been
added instead of setreuid() and/or seteuid() we'd be a lot further ahead
in this game of keeping one step ahead of set-ID exploits.  It may even
have been considered back then, but unfortunately the argument for the
generality of seteuid() won out.

Meanwhile users seem to want to eat their cake and still have it too, so
programmers continue to run *far* too much code with privileges (or at
least the ability to regain them) than they should ever be permitted to
do.

> woods> I.e. in POSIX the buffer-overflow exploit example above is impossible
> woods> against setuid-root processes, but still possible against
> woods> setuid-non-root processes.
> 
> The above your sentence is correct.
> 
> woods> In *BSD even a setuid-root program can regain its root privileges,
> woods> i.e. *BSD allows all saved-set-ID swapping, thus the vulnerability in
> woods> face of a buffer overflow is always possible, even for setuid-root
> woods> programs.
> 
> The above your sentence is wrong.

Well, perhaps my claim is not complete.   It's certainly true for a very
significant number of core set-ID programs though!

> As written in the above my sentence, *BSD can permanently revoke its
> set-ID privilege by calling setuid(real_uid).
> If an application program intended to revoke its set-ID privilege
> permanently, the application program should call setuid(real_uid).

I'm not disputing this -- I am only saying that this is not common
(enough) practice.

> There is no exploit that open_as() can fix but saved-ID feature
> cannot fix. Thus, open_as() is not needed.

Hopefully now you will see that your claim is not true in practice and
that in fact open_as() in place of saved-set-ID swapping will fix these
problems.

The most significant, and real, problem that set-ID programmers have
been trying to solve with first setreuid() and later seteuid() is to
avoid the access()/open() race condition.  Open_as() can do this IFF
saved-set-ID swapping is disabled.

Of course there are several other not-so-real "problems" that
programmers have put setreuid() and seteuid() to use with.  However in
all cases that I've examined or am aware of these "problems" are easily
solved by other means -- means that are in fact already dictated by the
various de facto rules of writing safe, portable, set-ID programs in the
first place!  Indeed most of the so-called solutions to these "problems"
are what dictate the inability of some such programs to use
setuid(getuid()) to permanently drop privileges -- these "solutions"
require instead that the program continue to run with full privileges
(whether directly, or within reach of a simple seteuid(0) call).

The only safe thing to do is to disable saved-set-ID/real-ID swapping
and to implement open_as() in some form or another.

I've already implemented an fopen_as_user() function [in smail-3] and I
can assure you that it is far better to have this code in the kernel
than to have it in user-land.  The user-land implementation is extremely
complex and large (perhaps an order of magnitude larger than it would be
in the kernel), and it is unlikely to be possible to use safely in a
standard library routine anyway since it uses fork() and setuid().

-- 
							Greg A. Woods

+1 416 218-0098      VE3TCP      <gwoods@acm.org>      <robohack!woods>
Planix, Inc. <woods@planix.com>; Secrets of the Weird <woods@weird.com>