Introduction

Hello, and welcome back to CS631 "Advanced programming in the UNIX Environment". This is week three, segment three, where we're looking at how the permissions defined in the struct stat st_mode are applied. Remember from our previous segment that a process has both an effective, as well as a real uid, and an effective as well as a real GID associated with it, and that we said that it is the effective uid that is used to determine access.

Permissions

So let's take a closer look at the order in which permission checks are done: the permissions of a file are encoded in the struct stats st_mode, and as you know, they are broken down into three groups: 'owner' or 'user', 'group', and 'other'. So in order to open a file... you need, first of all, execute permissions on the directory the file is in. This is because in order to open the file, you need to find the mapping of the file name to inode, which we said earlier is stored in the file of type "directory". Performing this lookup requires execute permissions on the directory; read permissions of a directory are required only to list the contents of the directory, to iterate over each entry. Also note that you need execute permissions on all directories in the path specified when opening the file. This makes sense, since to open the full path name, say /var/tmp/dir, you need to first open /var and hence require execute permissions on /, then tmp/ under 'var' and so on and so on. Verify that this holds by creating a directory containing multiple deeper subdirectories; change into that directory and create a file, then remove execute permissions from your parent directory. Can you still open the file you created? Try both the relative and absolute path name. Anyway, so you need to be able to execute the directory the file is in to open a file. Next, to open a file in read- or read-write mode, you require read permissions, and to open a file in write- or read-write mode, you need write permissions - seems fair. To use O_TRUNC, you need write permissions, since truncating a file modifies it. To create a new file, you need write and execute permissions on the directory in which you're creating this file. This should make sense, since creating a file requires you to add an entry in the directory, to write into this directory. To delete a file, the same holds true. This is perhaps somewhat counterintuitive: the permissions on the file itself do not matter. This is because deleting a file does not really change the file! It changes the directory the file is in. All you're doing when deleting a file is editing the directory and removing the link inside the directory. This is why overly permissive modes such as 777 on a directory are so disastrous: any user can remove any file in such a directory and then recreate it, regardless of the permissions on the file itself. We'll get back to this in our next lecture on file systems, but it's a good idea to confirm this for yourself by creating a directory like that and adding and removing files as different users. Finally, to execute a file you need execute permissions, but not read permissions. You can verify this for your compiler executables: change permissions to remove read access to all users, then try to run the program. Note, though, that for programs that are written in an interpreted language, such as shell scripts or python programs, you do need read permissions, because executing those programs actually means you are executing the interpreter binary and then feed the file as input to the interpreter.

Access grants

Give that a try as well. Anyway, knowing which permissions we need, we can now look at the order of consideration of the euid and egid, which will influence which permission set - user, group, or other - are going to take effect. Note that this decision tree here is ordered; that is, the first condition that applies decides. They are: if your process has euid 0, then access is granted - that's it. Note that this is irrespective of the permissions on the file; they are simply not considered. If you are root, you get to do whatever you want. Next, if the effective uid matches the st_uid on the file in question, then the system looks at the user permission bits. If those allow access, then you're good; if those do not allow access, you lose. Of interest here is that this ends the decision making process: your group membership or the permissions for others are simply not considered. If you are not root, and your euid does not match the st_uid, then we go on and look at the effective group id. If that matches the st_gid, and the group permissions and questions are set, you can access - if not, you lose. And finally, if you are neither root, nor euid matches the st_uid field, nor your egid matches the st_gid, then, and only then, do we look at the other permissions and grant or deny access based on those.

Examples

This first match decision making can lead to some possibly unexpected results for people not used to unix permissions and ownership, so let's show a few examples: First, let's note our uid and groups; next, we create a file and remove any and all permissions from it, like so. To illustrate that root can do whatever they want, let's become the super user. Okay, we're root. As we can see here, even though file permissions prohibit any read access from anybody, root can still access and read the file. The owner, however, cannot. This is because the owner permission bits are not set. User 'fred' is in the group 'users', and of course cannot display the file that has no permission set at all. Let's change the group permissions on the file. User 'jschauma' is in the group 'wheel'; 'file' is owned by the group 'wheel' and group permissions allow reading, but user jschauma will still not be able to display the file. This is because 'jschauma' is the owner, and as explained before, once the uid check is met, the system doesn't care about the group permissions. User permissions deny access to any user with euid equal to st_uid. Let's change the group ownership to 'users'. Now for 'jschauma', this doesn't make a difference: we still can't read it. But for user 'fred', there is a difference, because fred is in the group 'users', and group permissions allow read access, and fred is not the owner of the file, so fred is granted access. Okay, now let's revoke group access again, but allow others to read the file. Now fred can't read the file again, since the match on the group id prevents the system from considering the other permissions. Let's change the group ownership back to 'wheel', but leave the other permissions as they are. And here we go: fred can read the file again, because fred is not in the group 'wheel', and so this time the 'other' permissions are the ones relevant for fred's access. Of course root can still access the file, no surprise. Finally, notice that trying to remove a file where the owner does not have write access yields a prompt here. This is merely a nicety of the rm(1) command, however, a way to ask "are you sure?". The system call to unlink the file does not require any confirmation.

Recap

Okay, time for another break. We've run through a number of examples that illustrate the order of checks performed by the system to determine whether or not to grant access to the file. Make sure to go back and run these examples yourself; just watching these videos is not going to be as helpful to you understanding as running them yourself. And when you do play around with the permissions and ownerships, change things around and make sure the outcomes are what you expected. In our next segment, we look at the syscalls to make the various chmod and chown changes we've seen. In fact, after the next video segment, you should be able to implement those commands yourself, for the most part. Hope you stick around, and thanks for watching!