Subject: Re: commoning up code that changes uids and gids
To: Robert Elz <kre@munnari.OZ.AU>
From: Greg A. Woods <woods@weird.com>
List: tech-kern
Date: 03/06/2003 17:46:57
[ On Thursday, March 6, 2003 at 18:02:44 (+0700), Robert Elz wrote: ]
> Subject: Re: commoning up code that changes uids and gids 
>
>     Date:        Wed, 5 Mar 2003 04:19:54 -0500 (EST)
>     From:        "Greg A. Woods" <woods@weird.com>
>     Message-ID:  <m18qV46-000B3KC@proven.weird.com>
> 
>   | Yes, of course, but it's never safe in a system using the unix security
>   | model to raise privileges again in the same process.
> 
> 
> main()
> {
> 	int uid = getuid();
> 	int eid = geteuid();
> 
> 	setreuid(eid, uid);
> 	/* nothing done here */
> 	setreuid(uid, eid);
> 	exit(0);
> }
> 
> Do you want to revise your "never" ??

Nope, definitely not.

I'm sure you remember the nay-sayers who claimed race conditions in
various filesytem operations that were split into multiple sequential
system calls (exactly as above) could never be exploited, but then along
came a very simple shell script which proved them wrong in no time flat,
and we've been cleaning up the mess ever since.

I.e. that "nothing done here" comment does not imply that the OS and its
other process contexts won't be able to do something significant between
those system calls, including something which affects that process.
Certainly such an artificial example makes it extrememly difficult to
trigger an exploit attempt at the proper time, and it narrows the range
of potentially successful exploit types because of the artificial time
pressure, but it also clearly does not make it absolutely impossible for
any exploit to catch it unawares.

The Unix security model is clearly based on the sole use of the exec()
of set-user-ID programs to raise privileges and the sole use of the
setuid() system call to lower privileges.  The proof is not only in the
patent itself, but in the many bugs which have caused problems for
systems which have violated this model.  Any time there's any chance of
any other process or remote client of lesser privilege taking control
over process which can raise its priviledge level again then the same
risks are faced as would be present if the process could be taken over
while it is still running with its full priviledges.  Remember that
there are many more ways of process influcencing another process if that
first process has the same privileges as the target than if the target
process has higher privileges than the attacker -- i.e. a process in the
state of having temporarily lowered its privileges is _more_ vulnerable
than a process that never drops its privileges, all other things being
equal.  Thus this dangerous and unnecessary hack of allowing a single
process to lower and then regain its privileges clearly violates the
Unix security model and results in more risk than is otherwise necessary.

This horrid hack of allowing processes to regain their privileges was
apparently introduced simply because someone(s) foolishly violated
Hoare's axiom that "Premature optimization is the root of all evil in
programming."  I.e. the inventors of this abomination no doubt thought
it was more important for such security sensitve code to be able to
modify its privilege levels down and up again with great efficiency than
it was to preserve the safety built into the original Unix security
model.  This unfounded fear of fork()/exec() inefficiencies in Unix is
quite out of hand, especially in this example.  Careful examination of
the performance profile of any real-world application that (mis)uses the
likes of setreuid() will undoutably show that re-implementing it to use
the proper Unix security model will not adversely affect its overall
performance, nor its overall impact on the host system, at all;
especially if at the same time other more valuable optimisations can be
found.  The traversal of privilege levels can never be a performance
critical and often repeating operation in any sanely designed
application, and an insanely designed one deserves bad performance!

Of course what small inefficiencies the Unix security model demands can,
in many cases, be alleviated significantly with an open_as_user() system
call which combines the current access() and open() calls into a
meaningful race-free atomic operation...  assuming "we" still have some
reason to run mountains of code in a state of elevated privilege.  Why
do "we" fear to combine access() and open() into some sane and useful
new system call when we've already done much the same for all other
system calls which take pathname parameters?

I can tell you from the direct experience gained by having fixed serious
exploitable vulnerabilities in several programs which previously
mis-used setreuid() that even without the advantage of other system bugs
to make exploits easier, it's still far more likely for code using
setreuid() to be found vulnerable than code which adheres strictly to
the basic Unix security model alone.  System bugs simply make it
additionally possible to exploit even otherwise correct programs.  The
combination has been disastrous as unfortunately there have been more
than enough real-world examples of both classes of bugs.  While David's
new common id-munging code may help improve the reliability of existing
kernel facilities it in no way guarantees the reliability of future
features -- only only the complete removal of all process privilege
restoration features and the return to the basic Unix security model can
do that (assuming privileged code is always _only_ static-linked, of
course :-).

-- 
								Greg A. Woods

+1 416 218-0098;            <g.a.woods@ieee.org>;           <woods@robohack.ca>
Planix, Inc. <woods@planix.com>; VE3TCP; Secrets of the Weird <woods@weird.com>