, der Mouse <mouse@Collatz.McRCIM.McGill.EDU>
From: Greg A. Woods <firstname.lastname@example.org>
Date: 05/26/1996 15:43:41
[ On Sun, May 26, 1996 at 10:25:52 (-0400), Greg A. Woods wrote: ]
> Subject: Re: setreuid() and setregid()
> [ On Sat, May 25, 1996 at 15:46:51 (EDT), Greg Hudson wrote: ]
> > Subject: Re: setreuid() and setregid()
> > > That depends on which "UNIX" model you're thinking of. Only 4.2BSD
> > > and newer BSDs allow a setuid-root process to temporarily become
> > > another and euid and then return to euid==0.
> > Not so. Solaris 2.4, AIX 3.2.5, and IRIX 5.2 have seteuid(), HPUX
> > 9.05 has setresuid(), and most of those systems also have setreuid()
> > for compatibility with BSD 4.3.
> Indeed, Solaris-2, and other(/all?) SysVr4's have added seteuid(). I'm
> not quite sure how this system call is classified. I don't have a copy
> of the SVID-III to see what it has to say about why the call is there,
> if indeed it is.
I've just re-read a bit of Stevens' "Advanced Programming in the UNIX
Environment" (which I finally remembered had the most lucid description
of the various set*id() functions and their impact), and I'd like to
correct some things I may have said and add some more ideas.
Stevens confirms that seteuid() (and setegid) are proposed extensions to
POSIX.1 and are provided in all SysVr4 implementations. He hints that
seteuid() *DOES* allow a privileged user to temporarily set only the
euid. I'll call this proposed extension POSIX.1a here, though this may
be confusing and/or wrong if there's a real POSIX.1a (as I suspect there
is) but it doesn't include this proposal.
It seems the seteuid() call is (in the presence of _POSIX_SAVED_IDS,
anyway) a slightly safer and more straight forward way to temporarily
give up euid==0, and also fixes the problem with 4.3bsd where ruid is
overloaded as a "temporary" variable, since it must be clobbered with
"0" in order to allow the switch of euid and unless the program is
careful to remember which state it is in it is easy to get confused.
I think the following example shows how a setuid-root process might
temporarily become a non-privileged user in both systems:
4.3BSD POSIX.1a (with SAVED_IDS)
setreuid(0, getuid()); seteuid(getuid());
/* work as ruid */ /* work as ruid */
setreuid(geteuid(), 0); seteuid(0); /* i.e. saved set-user-ID */
Note that without _POSIX_SAVED_IDS it should be impossible for a
POSIX.1a compliant system to return to the euid==0 state, since
seteuid() cannot determine what the set-user-ID binary owner was.
According to Sevens the proposal requires SAVED_IDS to always be
In view of C2-like TCB requirements I still don't agree with allowing
this ability to temporarily give up root privileges. However I far
prefer the proposed seteuid() to the old setreuid(), so indeed it should
continue to be present only if COMPAT_43 (42?) is defined.
Note that according to Greg Hudson:
> NetBSD model:
> - For all setuid executables, setuid(getuid())
> permanently revokes privileges, setting
> - For all setuid executables, seteuid(getuid())
> temporarily revokes privileges, setting euid=ruid.
> Fortunately, the NetBSD model is consistent with the POSIX model
> (albeit without defining _POSIX_SAVED_IDS, so you have to explicitly
> test for NetBSD) for the case of setuid root executables.
The mere presence of the seteuid() interface, according to the POSIX.1a
proposal as stated by Stevens, permits you to assume _POSIX_SAVED_IDS is
indeed defined. If NetBSD is to maintain conformance with POSIX, it
should most definitely add the definition (and correct any other
behavior this may require).
Back to the original proposal now I've more understanding:
From: email@example.com (Charles M. Hannum)
Subject: setreuid() and setregid()
> I find it fairly bogus that we implement these functions incorrectly.
> I propose implementing them as specified in 4.3BSD, with three
> additional changes to enforce the 4.4BSD security model:
> 1) If we change the real ID, also change the saved ID to the same
> thing. This provides a `downward slope'; you can use setreuid() and
> setregid() as many times as you want, exactly as in 4.3BSD, but
> afterwards you can only use setuid(), seteuid(), setgid() and
> setegid() to change the effective ID to the real ID (which is also the
> saved ID), and thus revoke any extra privileges you have.
Hmm.... Are you sure you want to do this? If the user calls
setreuid(0, 1), then you'll be setting svuid to 0 too, which is in
effect an upward slope!
I'm not sure what feature of the 4.4BSD security model you are trying to
enforce in this way. Surely with POSIX.1 setuid() [and .1a seteuid()],
compliance, svuid can be left alone without any worry, as all this will
allow is for a program to also uset seteuid() to swap privs at the same
time as using setreuid() [though of course this would only be possible
with newly written programs!].
Since NetBSD does have the "saved set-user-ID" feature anyway, should
you not also define _POSIX_SAVED_IDS and at the same time fix setuid()
to work as defined by POSIX.1? It seems at first glance that the
implementation is easy to fix, though the implications may not be.
There's a wonderful diagram on p.217 of Stevens book that really helps
show what should happen. I'll try to reproduce it here in ASCII:
suser() == 0 suser() == 0 suser() == 0
setreuid(newruid, neweuid) setuid(newuid) seteuid(neweuid)
| \ /|\ /
| ---------\-----------/ | \ ---------
| / \ | \ /
| / ---- / ------/------
| | \ / / \
| | \ / --------- \
| | | | / |
v v v v v v
+--------+ unpriv +--------+ unpriv +---------+
| p_ruid |<==setreuid==>| cr_uid |<--setreuid---| p_svuid |
+--------+ +--------+ +---------+
| ^ ^ ^ ^ |
| | | | | |
| / \ \ / |
| / \ --exec of setuid-- |
| / \ |
setuid or seteuid setuid or seteuid
I think I've translated the POSIX wording to NetBSD kernel credentials
names with enough similarity to reality to still have meaning.
Note that according to this diagram an unprivileged setreuid() can
either swap euid and ruid, or can set euid to svuid, thus adding yet
another way for a setuid-root process to regain privileges.
The diagram leaves as an exercise to the viewer the job of filling in
the appropriate and allowed arguments to the unprivileged calls.
> 2) Any places which compare the real and saved IDs to check whether a
> process is still in a set-ID context (e.g. coredump()) must be changed
> to also compare the effective ID. This does not create any
> unnecessary restrictions when only using the setuid(), seteuid(),
> setgid() and setegid() functions, because the fact that the real and
> saved IDs are the same means that the effective ID must also be the
> same unless your real ID is root (in which case it's arguably a bug
> that we currently allow core dumps).
Yikes -- you mean euid wasn't being checked for coredump(), etc.?
> 3) Both functions must set P_SUGID to disable ptrace(2) and procfs.
> Does anyone object to the preceeding changes?
Nope, assuming the correction I note for (1) above (though I'd also like
to see some easy kernel config option to "disable" seteuid() and
setreuid() for the euid==0 case to aid C2 compliance).
Greg A. Woods
+1 416 443-1734 VE3TCP robohack!woods
Planix, Inc. <firstname.lastname@example.org>; Secrets Of The Weird <email@example.com>