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) |
|