Index Home About Blog
From: Chris Torek <torek@elf.bsdi.com>
Newsgroups: comp.unix.wizards
Subject: Re: UIDs
Date: 3 Sep 1995 04:51:30 GMT
Organization: Berkeley Software Design, Inc.
Lines: 210

In an article whose referent has been lost, curt@cynic.portal.ca
(Curt Sampson) writes:
>>Under the BSD view of the world ... the effective UID determines
>>your privileges, and this effective UID may be changed to anything
>>(if you are root) or to your real UID and back (if you are an SUID
>>program). The real UID never changes, because that shows who you
>>`really' are.

In article <42a5fa$i34@news.iastate.edu> Casper H.S. Dik <casper@fwi.uva.nl>
writes:
>This is a change from the original 4.x code.  Why was this change
>made?  I also wonder why you call this the "BSD view of the world".

Below I will describe the actual history, as far as I can remember it.
As you note, however, this is not especially a `BSD view'.

>It's more akin the SYSV view of the world: SVR4 has 2 system calls:
>seteuid() and setuid().  Seteuid() allows you to swap your effective
>uid to the real uid and to the saved uid.  setuid() does the same,
>unless when you're root: in that case all privileges are revoked.

>4.3 BSD didn't have saved uids ...

This is correct.

In 4.1BSD Unix, each process had two user IDs, the `real' and
`effective' (let us call them `ruid' and `euid').  As far as I
know, this goes back to the inception of `setuid' processes.  There
was exactly one system call, `setuid()'.  The effective UID determined
permissions and file ownership.

If a process was running as the super-user (euid == 0), setuid()
could set an arbitrary UID.  This would set both ruid and euid.
The original ruid was irrelevant.

If a process was running as an ordinary user (euid != 0), the
most one could do was to set the euid to the ruid, giving up the
set-ID privileges permanently (or until the next exec() call).

There are two major problems with this system (or two aspects of one
problem):

 a) any process with ruid != euid can only access files accessible
    by the euid, and all new files created are owned by the euid;
 b) it is impossible to give up special privileges temporarily.

A process running with euid==0 could read any file.  This meant
that setuid-root programs could be used to read otherwise-unreadable
files.  The access() system call was added to allow such programs
to check the permissions via the ruid (access() was more or less
equivalent to `swap IDs, test operation, swap IDs back, return
status of test').  Unfortunately, this call is virtually useless.
If euid!=0, the result of the access() call is worthless, because
the program may not be able to open or create the file anyway.  If
euid==0, the problem is subtler:  Although the process can do
anything (it is running as the super-user), by the time the process
has its answer (e.g., `yes, it is OK for Joe to look at that file'),
the answer could be out of date---the original file might have been
removed or renamed, or some directory along the way might have
changed, etc.

Thus, this model (which persisted into 4.1BSD and various versions
of System V) is fundamentally flawed and had to be replaced.

For 4.2BSD, the CSRG came up with a simple redesign.  They left
the two UIDs alone, but changed the system call from `setuid()' to
`setreuid()'.  Processes could now directly control both their IDs,
under the following rules:

	- the super-user (euid==0) could set any pair of IDs;
	- anyone else could set either ID to the previous value
	  of either ID.

This allowed login, etc., to permanently become the appropriate user:

	error = setreuid(desired_uid, desired_uid);

Any process that was running set-ID to any user could permanently
give up its special privileges:

	error = setreuid(ruid, ruid);

or, if it desired, permanently change into its effective user:

	error = setreuid(euid, euid);

(this is not especially useful, but not harmful either), or it could
do the most important operation.  Namely, it could *swap* real and
effective IDs temporarily:

	error1 = setreuid(euid, ruid);	/* swap temporarily */
	error2 = open(...);		/* do some operation w/ ruid perm's */
	error3 = setreuid(ruid, euid);	/* swap back */

The open() call (or mkdir(), or whatever) in the middle is done
with the real user's permissions, and can do only whatever the real
user can do, yet the process can go back to being setuid.

The main problems with this model are that it is somewhat cumbersome
(e.g., three system calls above) and that it is modal (for instance,
a signal handler that occurs between the first and second setreuid()
is running with the UIDs swapped, which may be undesirable).  Some
people dislike it because it is relatively easy to swap an odd
number of times, instead of an even number, and wind up `running
backwards'.

Somewhere along the USG (System III / System V) development, someone
else realized that there was a problem.  Unfortunately, as was
typical of USG, they implemented a reasonable idea absolutely
terribly, so that it did not solve anything.

The USG folks came up with the idea of a `saved setuid': a third UID
associated with each process.  We can call this the svuid (to go with
the ruid and euid).

If done right, `saved setuid' solves the file permissions problems
just as well as setreuid().  A seteuid() call is permitted if:

	- euid == 0, or
	- euid == ruid, or
	- euid == svuid.

The seteuid call sets the euid but leaves the ruid and svuid unchanged.
The process can then make calls like open() or mkdir() and have the
appropriate permissions applied.  As casper@fwi.uva.nl notes:

>The weak part of this model is that a non-root set-uid process
>has no way to permanently revoke its special privileges ...

This is not necessarily true, though it was true in the System V
world (at least through SVR4).

The mistake the USG folks made was that they spelled the seteuid()
system call `setuid'.  One might wonder how they could do that, since
there was already an existing setuid() system call.

What USG Unix (System III and System V through SVR3) did was to
select the desired system call based on the euid.  The top-level
setuid() code looked more or less like this:

	if (euid == 0) {
		... do a setuid() call ...
	} else {
		... do a seteuid() call ...
	}

This meant that any process running as root could not call seteuid()
at all.  Of course, these processes are the ones for which a working
seteuid() is the most important.  Thus, as implemented in USG Unix,
saved setuid was not very useful, and Sys3 and Sys5 were generally
quite insecure.

When 4.4BSD was being modified to implement (often for the first
time) various POSIX specifications, the old setreuid() model was
replaced with a System V / POSIX style `saved setuid' model.  In
4.4BSD, however, the seteuid() system call was added directly, and
the setuid() call was changed as well.

The rules in 4.4BSD are (from setuid(2)):

     The setuid() function sets the real and effective user IDs and the saved
     set-user-ID of the current process to the specified value.  The setuid()
     function is permitted if the specified ID is equal to the real user ID of
     the process, or if the effective user ID is that of the super user.

     The seteuid() function ... sets the effective user ID ... of the current
     process.  The effective user ID may be set to the value of the real user
     ID or the saved set-user-ID ... in this way, the effective user ID of a
     set-user-ID executable may be toggled by switching to the real user ID,
     then re-enabled by reverting to the set-user-ID value.

Note that 4.4BSD therefore *does* allow ordinary (non-super-user or
euid!=0) setuid processes to give up all their special privileges,
simply by calling setuid(ruid).  By calling seteuid(), they can do
various operations as the real or effective user as desired.  This
is still just as cumbersome as the setreuid() model, and just as modal,
but it is safer in that it is impossible to swap IDs the wrong number
of times.

I cannot speak for SVR4.  One would hope that they would finally have
thought through the security implications of the old System III model,
and fixed it.

>Can you no longer do setreuid(euid, euid) in BSD 4.4?

The setreuid() call is now a C library compatibility function.  It
attempts to divine the intent of the caller and call setuid() or
seteuid() as appropriate.  The answer, unfortunately, is `you can
no longer do that'.

>that's one of the things most sorely missed in SVR4.
>(E.g, it maks it impossible to have a set-uid some user program
>run "rsh host" as the user it's set-uid to)

This is an interesting point.  Perhaps setuid() should be permitted
even if the argument is that of the effective user ID.  (This has
to be done after forking, even when using setreuid(), if the original
process is to continue, of course.)

I rather think the best solution to the problems of setuid processes
is to remove them entirely, a la Plan 9 (after all, now that we
have real IPC, it is easy enough to run `agents' instead, and get
the agents to act on behalf of other users).  Unfortunately, this
is not feasible in Unix; the setuid and super-user models are too
deeply embedded in its history.
-- 
In-Real-Life: Chris Torek, Berkeley Software Design Inc
Berkeley, CA	Domain:	torek@bsdi.com	+1 510 549 1145
  `... if we wish to count lines of code, we should not regard them as
   ``lines produced'' but as ``lines spent.'' '  --Edsger Dijkstra

From: casper@fwi.uva.nl (Casper H.S. Dik)
Newsgroups: comp.unix.wizards
Subject: Re: UIDs
Date: 3 Sep 1995 15:35:16 GMT
Organization: FWI, University of Amsterdam
Lines: 104
X-Organisation: Faculty of Mathematics, Computer Science, Physics & Astronomy
                University of Amsterdam
                Plantage Muidergracht 24
                NL-1018 TV Amsterdam
                The Netherlands
X-Phone:        +31 20 525 5200
X-Fax:          +31 20 525 5101

Chris Torek <torek@elf.bsdi.com> writes:

>>The weak part of this model is that a non-root set-uid process
>>has no way to permanently revoke its special privileges ...

>This is not necessarily true, though it was true in the System V
>world (at least through SVR4).

And still is true in SVR4.x (and Solaris 2.x).

>The mistake the USG folks made was that they spelled the seteuid()
>system call `setuid'.  One might wonder how they could do that, since
>there was already an existing setuid() system call.

Big mistake indeed.

>This meant that any process running as root could not call seteuid()
>at all.  Of course, these processes are the ones for which a working
>seteuid() is the most important.  Thus, as implemented in USG Unix,
>saved setuid was not very useful, and Sys3 and Sys5 were generally
>quite insecure.

This was fixed in SVR4.  All processes can now call seteuid() as
the system call has been externalized.  Setuid() behaves the
same as seteuid(), except when you're root.  When you're root,
setuid() sets euid svuid and ruid back to one and the same value.
As non-root you can only set euid to svuid or ruid, but you can never
change the svuid.

>The rules in 4.4BSD are (from setuid(2)):

>     The setuid() function sets the real and effective user IDs and the saved
>     set-user-ID of the current process to the specified value.  The setuid()
>     function is permitted if the specified ID is equal to the real user ID of
>     the process, or if the effective user ID is that of the super user.

This is the behaviour in SVR4 iff euid == 0, if euid != 0, the behaviour
is the same as seteuid():

>     The seteuid() function ... sets the effective user ID ... of the current
>     process.  The effective user ID may be set to the value of the real user
>     ID or the saved set-user-ID ... in this way, the effective user ID of a
>     set-user-ID executable may be toggled by switching to the real user ID,
>     then re-enabled by reverting to the set-user-ID value.

In SVR4 the important functionality of revoke forever is not present.

>I cannot speak for SVR4.  One would hope that they would finally have
>thought through the security implications of the old System III model,
>and fixed it.

Partly fixed in that you can swap uids around and write safe set-uid
executables based on that.

>>Can you no longer do setreuid(euid, euid) in BSD 4.4?

>The setreuid() call is now a C library compatibility function.  It
>attempts to divine the intent of the caller and call setuid() or
>seteuid() as appropriate.  The answer, unfortunately, is `you can
>no longer do that'.

Just like the SVR4 setreuid() function from libucb does.

>>that's one of the things most sorely missed in SVR4.
>>(E.g, it maks it impossible to have a set-uid some user program
>>run "rsh host" as the user it's set-uid to)

>This is an interesting point.  Perhaps setuid() should be permitted
>even if the argument is that of the effective user ID.  (This has
>to be done after forking, even when using setreuid(), if the original
>process is to continue, of course.)

Or if you don't care to recover the uid.  E.g., if you want to write
a program that allows users in a certain group to become some kind of
non-human user with an interactive shell, without having to
type a password through su, you can use a set-uid "that-uid" program
in 4.3 BSD/SunOS 4, but you need to make a set-uid root program
in SVR4/BSD 4.4.

For compatibility reasons with SunOS 4.1.x, Solaris 2.5 reintroduces
the setreuid()/setregid() *system* calls that behave exactly like
their SunOS 4 namesakes.

Change the semantics of setuid()/seteuid() is not possible in Solaris 2.x,
to remain compatible with the SVID.

>I rather think the best solution to the problems of setuid processes
>is to remove them entirely, a la Plan 9 (after all, now that we
>have real IPC, it is easy enough to run `agents' instead, and get
>the agents to act on behalf of other users).  Unfortunately, this
>is not feasible in Unix; the setuid and super-user models are too
>deeply embedded in its history.

Agreed.  Set-uid is evil.  Many years after the inception of set-uid
programs, how to write them is still not well understood by
the majority of people who write them.


Casper
-- 
Casper Dik - Network Security Engineer - Sun Microsystems
This article is posted from my guest account at the University
of Amsterdam.  My real-life e-mail address is: Casper.Dik@Holland.Sun.COM
Opinions expressed here are mine (but you're welcome to share them with me)

Index Home About Blog