Re: BEWARE! Linux seteuid is broken!

Theodore Y. Ts'o (tytso@mit.edu)
Mon, 17 Jun 1996 21:36:56 -0400


OK, I've done some research into this problem, and the real problem is
that BSD has changed how they handled seteuid(). (This is not the first
time BSD has jerked around how certain BSD system calls have worked.)
If it's any consolation, your program would have failed on any BSD 4.3
OS (or BSD 4.3 derivitive, which should include many commercial OS's),
since it makes BSD 4.4 seteuid() assumptions.

I implemented setreuid() following the BSD 4.3 semantics. I did this in
in the pre0.99 days, *before* BSD386 came out. Part of my requirements
when I implemented setreuid() was that programs that used setreuid()
assuming the old BSD 4.3 semantics wouldn't get bitten by a security bug
because they didn't know about the POSIX_SAVED_IDS option.

BSD 4.4 took a completely different path; BSD 4.4 does *not* implement
POSIX.1 saved setuid. Hence, BSD 4.4 does not define POSIX_SAVED_IDS.
BSD 4.4 does store a saved setuid, though, and internally it is called
svuid. However, svuid is *not* used in the setuid() call; it is POSIX
without POSIX_SAVED_IDS. Instead, BSD 4.4 *changed* the definition of
seteuid() so that it always works like POSIX setuid() assuming that you
were always the unprivileged user. That is, it would let you set the
euid to either the real uid or the saved uid, but that's all it would
do.

In contrast, BSD 4.3 had seteuid(), but it didn't handle saved setuid;
if your uid=1800, and your euid=0, and you do a seteuid(1800), it would
set euid to 1800 and there was no way to go back. In BSD 4.3, the way
to flip between root and user privs was to use setreuid() to switch back
and forth: setreuid(1800, 0) and setreuid(0, 1800).

As I said before, when I implemented POSIX_SAVED_IDS during the 0.99 (or
was it 0.13, I don't remember) days, I implemented true POSIX_SAVED_IDS,
and then I implemented setreuid() and seteuid() to be BSD 4.3
compatible. The real problem is that BSD 4.4 isn't POSIX_SAVED_IDS
compatible, and it's also not BSD 4.3 compatible.

Furthermore, BSD 4.4 deprecated setreuid(), although it does emulate it
correctly. NetBSD emulates it using the following algorithm:

setreuid(-1, -1) --> return 0 (correct)
setreuid(-1, N) --> seteuid(N) (correct)
setreuid(N, -1) --> seteuid(N) (NOT CORRECT)
setreuid(N, N) --> setuid(N) (correct)
setreuid(N, M) --> setuid(N) (Close, but NOT CORRECT)

FreeBSD emulates setreuid() using a different algorithm, which
interestingly enough looks like they stole the algorithm from Linux. At
the very least, the algorithm for when to set the saved user id argument
is *exactly* the same as the Linux algorithm. Very suspicious....

The only difference is the FreeBSD allows the real uid to be changed if
the new real uid matches the saved uid, where as Linux doesn't allow
this. Linux does allow the real uid to be changed if the new real uid
matches the current effective uid, and Free BSD doesn't allow this.
I'll have to think over whether FreeBSD's method might be more correct
than what we have now; it's not obvious at first glance which is more
correct.

The real difference is that FreeBSD treats seteuid() the way BSD 4.4
does, instead of the way BSD 4.3 does. Since Linux is BSD 4.3
compatible, seteuid() is treated like setreuid(-1, euid) --- and this
would be correct for BSD 4.3.

So, the answer is not to change setreuid() --- setreuid() is correct as
it currently stands in Linux. Rather, now that we know that BSD 4.4 has
gratuitously changed how seteuid() works, it's certainly reasonable for
us to put support into the kernel for BSD 4.4 seteuid(). In fact,
here's the bit code to do it (see below). Of course, it doesn't include
the code to actually put it into the sys call table, and the code to
change libc to actually use it, but I'll leave that as an exercise to
the reader. :-)

- Ted

P.S. The other lesson to take from this is that how seteuid(),
setreuid(), etc. work differes from operating system to operating
system. Even FreeBSD does things slightly differently from NetBSD which
does things a little differently from BSD 4.4. So calling Linux
"broken" because it doesn't follow things the same way as <your favorite
pet operating system> is a bit unfair, I think.

===================================================================
RCS file: kernel/RCS/sys.c,v
retrieving revision 1.1
diff -u -r1.1 kernel/sys.c
--- kernel/sys.c 1996/06/17 23:38:03 1.1
+++ kernel/sys.c 1996/06/17 23:43:25
@@ -501,6 +501,20 @@
return(0);
}

+
+/*
+ * This function is compatible with BSD 4.4 (and derivitive) seteuid()
+ * functions. Note that this is **different** from BSD 4.3 seteuid().
+ */
+asmlinkage int sys_seteuid(uid_t euid)
+{
+ if ((euid == current->uid) || (euid == current->suid) || suser())
+ current->fsuid = current->euid = euid;
+ else
+ return -EPERM;
+ return (0);
+}
+
/*
* "setfsuid()" sets the fsuid - the uid used for filesystem checks. This
* is used for "access()" and for the NFS daemon (letting nfsd stay at