Hello, and welcome back to CS631 "Advanced Programming in the UNIX Environment". This is week 3, segment 2, and we're getting even more close up and personal with the struct stat, which we met in our last segment. In the next few sessions, we'll focus primarily on the st_mode member and we will discuss how file permissions are set and applied. To do that, we'll also learn a little bit more about file ownership and the user IDs of a process, the focus of this video segment. --- Every process running on a Unix system, has six or more UIDs associated with it. They are: The real user ID and the real group ID. They represent who we really are. This is important, since a process may change the ID that it would like the system to consider when granting permissions. For that, the system uses the process's _effective_ user ID and group ID. For group permissions, a process may further have additional, secondary or supplementary group IDs associated with it. Now in most cases, the effective UID and the real UID are identical, just like the effective GID and the real GID are likely identical. However, in some cases it is useful to allow an executable to be run with changed, often elevated privileges, including even those of root. - In that case, we set a special bit on the file's st_mode -- the setuid bit -- and the system will set the effective UID, or euid, to that of the owner of the executable. The real UID will remain that of the user. Similarly, the same can be done using group ownership and the setgid bit. We'll show an example in just a second. When a setuid process is exec'd, the so-called saved set-user-ID is set to the effective user-id. This is important: a call to change the effective user-id later will only be allowed if it is to change the euid to the current real uid, or the saved-setuid. By using and allowing the saved-setuid, a program is then able to toggle its effective UID between the elevated privileges and the privileges of the real user. The behavior and semantics of these saved UIDs is a bit confusing, as the historical behavior was inconsistent between the BSD and System V derived systems. I have some links in the code examples for you to read up on, if you're interested, but the important take-away here is that 1) exec sets the saved-setuid and saved-setgid; 2) changing the effective uid or effective group id is allowed to either the real uid or the saved-setuid or group ids respectively. --- Let's illustrate the need for the ability to assume another user's privileges by example: As you probably know, the ping(1) command sends out ICMP echo-request packets to the destination, and awaiting echo-reply packets in return. But sending ICMP packets requires the program to open a network socket in raw mode, which in turn requires super-user privileges. Without the ability to elevate privileges, only root could ping hosts. This is what that would look like: No good. The only difference here is the setuid bit on the executable, as indicated by the 's' in the permissions string. Let's try to fix our copy of ping, by setting that bit: Well, no surprise, that's not good enough. Remember, the setuid bit says "when a user executes this file, set the effective UID to that of the owner of this file". But we already are user "jschauma", so our effective UID already is the one that this setuid bit would enforce. In other words: the setuid bit is only meaningful when the st_uid field in the struct stat of the executable is different from the euid of the user executing the command. --- Now while the setuid bit is a commonly used mechanism -- and can, if the program in question is not written with particular care, lead to serious security issues -- the concept also applies to group IDs. Again, let's illustate by example: In this case, we have two different users logged into our system, user "jschauma" on pts/0 in the left hand terminal window, and user "fred" on pts/1 in the right hand terminal window. [pause] When you have a multi-user system, it sometimes is necessary or desirable to be able to send a message to all currently logged in users. For this, our Unix system comes with the "wall(1)" command, that allows any user to send a message to all other users to be displayed on their terminal. Here, let's say hi to everybody. As a result, we see the broadcast message displayed on all users' terminals, including the sender's. Fred, over here on the right, is delighted to receive such a kind message and replies in kind. As the broadcast message indicates, the communication happens via the pseudo terminal, pts/0 and pts/1 in this case. Let's look at them: [pause] Each user logged in on the system gets a pseudo terminal; messages written to this device are displayed on the user's terminal. But, for hopefully obvious reasons we can't just let every user write to everybody's terminal, and the file permissions shown here reflect that. But we notice that these devices _do_ have group write permissions set, and that the wall(1) program, while not setuid root, is setgid and has a group owner of 'tty'. That is, when this command is executed - no matter by whom -- then it will run with an effective gid of 'tty', and since group 'tty' can write to the terminals of the logged in users, the wall(1) command succeeds. [end] This, by the way, is also a good illustration of the concept of "least privilege" -- a process should only be given and use the privileges it needs, not more. Of course we could have made the wall(1) command setuid root, and it would have worked just as well. However, now if there was some sort of vulnerability found in the wall(1) command, you ѕuddenly have a root code execution vulnerability. By using a dedicated group and setting wall(1) setgid instead, we limit the potential risk here - a good approach that we should keep in mind going forward. --- Ok, so how does setuid work? In order to change the real and/or effective UIDs of a process, we can call the 'setuid(2)' and 'seteuid(2)' syscalls. To determine what the current UID or eUID is, there are the getuid(2) and geteuid(2) system calls. As we mentioned a few minutes ago, calling seteuid(2) will succeed if the argument given is either the current real UID, or the saved-setuid, and it will _only_ set the eUID. setuid(2), on the other hand, will always set _both_ the real UID as well as the eUID, as well as the saved-setuid. This means that after a call to setuid(2), you can no longer regain any possible elevated privileges you may have had before. This is by design and intent: It is often useful to allow a process temporary elevated privileges to access a certain resource, but it's undesirable for that process to retain the elevated privileges for the duration of its execution. For example, consider a web server: only processes with superuser privileges are able to bind to a port below 1024, but we wouldn't want a web server to then run with root privileges while it's serving content. So the right thing to do here is to elevate your privileges at program startup, bind to the port, and then drop your privileges permanently. Again, an example of the principle of least privilege. --- Here's our code and command example to show setuid(2)/seteuid(2) in action: Our program myuids.c has a function that will display the current real and effective UIDs. If those are identical, then there's nothing special going on and we can just exit. Otherwise, we will try to drop our elevated privileges by changing the effective UID to the real UID via a call to 'seteuid(ruid)'. If this succeeds, then our _effective_ UID is now the same as our real UID, meaning we no longer have elevated privileges. But we are still able to gain back the elevated privileges in the next step. After that, we call setuid -- not seteuid --, which will set our real, effective, _and saved_ setuid to the current, unprivileged, real UID. There's no turning back from that now - our next call to seteuid(2) should fail. Let's run it. Ok, nothing special going on here: of course our real and effective UIDs will reflect who we are: UID 1000. Our permissions on the executable are normal, and the file is owned by this current user. Now what happens when another user executes this program? Let's ask Fred: Fred has UID 1001, but since the permissions on the program include read and execute for 'others', Fred can run this program, and the output will again show both effective and real UIDs to be in sync, 1001. Now back to the owner of this file. Let's change it to be setuid. Again, if we run this as "jschauma", then there's no difference, since we already have eUID and real UID 1000. But for Fred things are a bit different now: When Fred executes the program, it will set the effective UID to that of the owner of the file, "jschauma'. And so... We find that the effective UID is 1000 (jschauma), real UID 1001 (fred). Dropping privileges to get euid and ruid 1001 succeeds... and because we used seteuid(), not setuid(), we are able to get back our elevated privileges and become euid "jschauma" again. Next, let's call setuid(). After this call, we are eUID and rUID 1001 - fred, but now with no more way to gain back eUID 1000! Any attempt to regain these privileges will fail. Alright, so with seteuid(2), we can change our effective UID and then possibly gain access to resources we normally wouldn't have. But we also may encounter situations where we _are_ running with elevated privileges, but need to determine whether or not our real UID _would_ be allowed to access a resource. Normally, doing that would mean dropping privileges, trying the action, noting whether it fails or not, and then raising privileges again, which is (a) cumbersome, and (b) not atomic. So instead, we have... --- ...the access(2) system call. Given the path name and the mode we're interested in testing, it will tell us whether the _real_ UID would be able to perform the action. Consistent with the other calls we've seen, this call also comes in an f- variant, again resolving relative paths under the directory filedescriptor passed as the first argument. Let's show a quick example of how one might use the access(2) system call: --- The program is about as simple as you can imagine: we perform the access(2) check, then we try to open the given file. We expect both to suceed or fail if euid and real uid are the same, but to give possibly different results when they are different. Let's give it a go. First, we try it out against a file we know we have permissions to read, /etc/passwd. Next, we try it out against a file that we know we do _not_ have permission to read, /etc/master.passwd. So far, so obvious. Now let's make it more interesting by changing the ownership on the executable to root, and then setting the setuid bit. Now on /etc/passwd, nothing changes, since our real UID can open that file in read-only mode, but for /etc/master.passwd, we now see the value of the access(2) system call: The program can tell us that the real UID would _not_ be allowed to open this file, even though our euid can, and thus succeeds. [pause] A quick side note here: note that the file a.out is now owned by root, and is setuid. What happens if we try to compile our program again? That should try to overwrite the existing file. But the user "jschauma" should not be allowed to write to file. Imagine it could - if this user could write to the setuid, root-owned file, then it could replace the binary with anything they liked, including a root shell, for example. That doesn't sound good. Let's try: Hmm, the compiler did not complain. And it did write a new a.out - so... how did it do that? Tell you what - why don't you think about this and come up with a theory and test, and we'll come back to this in our next lecture, ok? --- Ok, time for a break. Let's recap: A process has an effective userID as well as a real userID, just like an effective groupID as well as a real groupID. If the setuid (setgid) bit is set in the permissions, the effective UID (GID) will become that of the st_uid (st_gid) of the file's struct stat at execution time. You can switch between these using the seteuid(2) system call, but once you call setuid(2), you can't go back. The effective UID and group ID are the ones used for file permission checks, but we can check whether the real ID would have permission via the access(2) system call. With that in mind, we'll move on to the details of file permission checks in our next video segment. Stay tuned!