Messages in this thread Patch in this message |  | | Date | Mon, 17 Jun 1996 21:36:56 -0400 | From | "Theodore Y. Ts'o" <> | Subject | Re: BEWARE! Linux seteuid is broken! |
| |
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
|  |