Subject: Please don't use access(2) Date: 1 Mar 1988 Newsgroups: comp.lang.c,comp.unix.questions,comp.unix.wizards Distribution: comp Keywords: access(2), permissions, setuid/setgid Netlanders, This comes up all the time and it has been very frustrating. I want to get this off my chest, see if anybody else has seen this problem, get some suggestions, and plea for people not to misuse the access(2) system call. First, a little background. We develop business-type applications using a commercial database package on a 3B2 but I have seen this problem all over. We like to set these systems up in a secure manner with protected project areas and limited entry points (say, just a single "inventory" command that calls up a menu). To do this, we provide a front-end program (the "inventory" program mentioned above) and make it set-group-id to the project. Here, assume that the group is "inv" and only the project uses it (everybody else is "staff" or whatever). All the project directories are rwxrwx--- and this effectively keeps everybody else out. It keeps the inventory package out too. The problem is with the access(2) system call. Reading the manual page of this function makes you think that this is a handy-dandy "does the file exist?" function and it is used this way by a lot of programs. The curious/insighful person might wonder why they would make a system call like this when stat(2) can trivially do the same job -- access(2) should be access(3). I wondered for a long time too. Access(2) should almost never be used. It does what stat(2) does except it uses the REAL userid and not the EFFECTIVE userid like every other system call uses. It is designed for use by setuid/setgid programs to verify the permissions of the "underlying" user. To illustrate, let's say that the uucp Systems or L.sys file on your machine is unreadable by anybody other than uucp but you want to see it. So, you run /usr/bin/cu, which is setuid "uucp" and call another system. You figure that ~%put /usr/lib/uucp/Systems /tmp/Systems.stolen will write the file over there. Uucp clearly has permission to access this file, and the open(2) call will work just fine, but cu won't do this for you, probably because of access(2) [note: several other methods come to mind but this is a pretty good demo of access]. When you are running cu, your permissions might be as follows: real uid = friedl (that's me) effective uid = uucp (that's him/her) When cu does an access("/usr/lib/uucp/Systems, READ), it is asking "does the real uid (friedl) have READ access to this file?" Access says no so cu tells me to get lost. It is possible to implement access(2) yourself with stat and getuid/getgid but it is a little tricky and error prone. Presumably the original Unix guys decided to put it in the kernel to keep it safe and easy. "OK", you say, "so we have this handy function that does a useful task. If I'm not running setuid or setgid then it doesn't matter". You're right, your program may not be setuid but I might want to run it that way as part of a larger system. This messes me up. [Disclaimer: I don't *know* that the shell is doing things this way but it sure looks like it]. When the shell is asked to execute a command without a full pathname, it searches along the $PATH for the program you've requested. For each component in $PATH, it constructs the full pathname and runs access(2) on it. If access says OK then it does an actual exec. I would guess this is done because access(2) is a lot quicker than trying a lot of execs and having them fail, but I'm not sure. The problem comes about when the directory is open to the group but closed to other. Say I have a program: 10 -rwxr-x--- 1 inv inv 4724 Feb 26 22:57 /usr/acct/report and "/usr/acct" is in my search path. In a shell script called from a menu I try to execute "report" without a full pathname. The shell looks in the $PATH, does access("/usr/acct/report", EXEC) but it fails because *my* personal group (staff) has no permissions and I get a message to that effect from the shell. If I type the command so $PATH is not used, it works fine, presumably because the shell believes you know where it is and just does an exec directly without the access. This problem occurs whether setuid or setgid is used: if the effective user/group has permission but the underlying (real) user/group does not, access(2) fails even though exec(2) would not. There are a number of possible solutions to this: (1) Forget setuid/setgid. I really don't like this because it requires full access for everybody. I have a little sign on my wall: _____ / //\ / // \ | chmod 777 | \ // / \/____/ (imagine the sign being red and round). I think 777 is sloppy. (2) Make all the commands open. This is what we usually do but I don't like it: I make the main project directory rwxrwx--x and give the minimum permissions possible. I have to really keep on my toes to have a secure system but not forget a chmod a+x somewhere. (3) Use full pathnames. Sigh, that is really a lot of work. For the really secure systems it is the only way to really do it but it sure makes scripts messy and hard to maintain. (4) Make all the users in the same group. This is not a bad idea but it gets messy if we develop more than one unrelated system on a machine -- this happens a lot. (5) Fix the shell ha ha ha. (6) Something I haven't thought of. Anybody? If the shell were the only place it would not be so tough but it is not. The test(1) command uses access(2) for some of its queries, so if [ -r $filename ] ; then ... says "not readable" for the same reasons, so $filename must be readable by everybody. Note: I understand that System V Release 3 fixed test(1) but the $PATH search still seems to be broken. There are others: The tmpnam(3) function call uses access(2), the Informix screen manager (sperform) uses it when looking for forms to run, etc., etc. [coming down to the wire] Am I correct that the majority of uses of access(2) are incorrect? What can be done about this? Anybody have any thoughts? Sigh, Steve #-------------------------- note 2 ---------------------------- I have since found a way to make this work. The front-end to the accounting system is setuid root and setgid acct, and the *first* thing that the program does is: main() { real_grpid = getgid(); /* save user's group */ setgid(getegid()); /* all grpid = acct */ setuid(getuid()); /* turn off setuid */ .... The *first line make both real and effective userid the same as the effective, which in this case is "acct". Then root gives up its setuid permissions, so when these two lines have finished our permissions are: -----userid---- ---grpid---- real eff real eff ------ ------ ----- ----- before friedl root staff acct after friedl friedl acct acct The setuid to root is necessary for the program to be able to run the setgid() call. It is normally scary to have a setuid root program, but we have not been able to find any holes in this scheme. The one problem with it is that we lose any indication of the user's group id in the kernel, but we can save it in a variable and use it as needed.