Chapter 9: Privilege Management Objectives: (a) Describe how permissions are managed and controlled in a multi-user OS environment. (b) Explain how users can be afforded the limited ability to execute commands with escalated privileges. NEW EMAIL PROCEDURES GIVING YOU AN EMAIL ACCOUNT 1. Files in C 1.1. Introduction We can use the C language to open files, read from files, write to files and close files. In C, a file name is tied to an integer called the file descriptor. Once we tie a file name to a file descriptor, we work only with the file descriptor. 1.2. Opening a File Let’s look at a C program, that I’ve named fun_with_files.c that opens a file named stuff: #include<stdio.h> #include<stdlib.h> #include<string.h> #include<fcntl.h> #include<sys/stat.h> These two lines tell the compiler that your program intends to use files. If you will be using files, just (blindly!) include these two lines. This line should be the only line that looks insane to you! Do not fear! This line will be explained below. int main( int argc , char *argv[ ] ) { int fd ; fd = open( "stuff" , O_RDWR|O_CREAT|O_APPEND , S_IRUSR|S_IWUSR ); printf("The assigned value of the file descriptor is %d\n",fd); if ( fd == -1 ) { printf( "\nFailed to open.\n"); } } Notice first that we have a few new #include directives at the top of the program. These are necessary for C to work with files. Don’t worry about these lines of code (other than ensuring that they are at the top of the program). The only craziness in the program is the line: fd = open( "stuff" , O_RDWR|O_CREAT|O_APPEND , S_IRUSR|S_IWUSR ); At its heart, this line of code ties the file named stuff to an integer value which is placed in the variable fd. This line of code essentially asks the C compiler: “I, the programmer, want to use the file named stuff. I might want to read what is in it. I might want to write to it. I know that you, the C compiler, do not want to see the file name (in this case, stuff) running around the program, so give me an integer to use as a stand-in for the file named stuff, and place that integer in the variable that I’ve named fd. Then, later, 1 if I want to do something with the file named stuff, I’ll just refer to the integer fd” So, let's run the program and see what happens! First, I will look at my home directory: Now, I will compile and run the program: We see that C assigned the file named stuff to the file descriptor value of 3 (i.e., fd = 3). If, when dealing with files, C simply cannot figure out what to do, it will assign the integer -1 to the file descriptor. That is why we placed that final if statement at the end—in order to check if the compiler encountered some difficulty. At this point, you might be thinking: "Okay, so what? What did the program do?" Well, if I now list the files in my home directory once again, I see: Notice that the file stuff now exists! 1.3. Looking at a File So far, we have looked at files using the nano editor. Oftentimes we want to look at the contents of a file, with no intention whatsoever to edit the file. In cases where we merely want to view the contents of a file, there is no need to open an editor. Linux provides us with the cat command. The command cat filename will display the contents of the file named filename. For example, if I enter: cat fun_with_files.c I see: The contents of the file named fun_with_files.c 2 Let's observe the contents of the file named stuff using cat: and I see … nothing. At this point, you might be thinking: "Okay, so what? What did the program do?" 1.4. Writing to a File Let's answer the question: What did the program do?" The program did create the file named stuff, but that is all it did. Specifically, it did not write any contents to the file. The file has been created, but is empty. So, when we used the cat command to display the contents of the file named stuff, we did indeed see the contents: nothing. So, let’s modify our program so that we can add content to our file. Before presenting the program, we need to introduce two additional string functions: • • strnlen( buffer ) returns the length of the string named buffer strncat( buffer , "\n" , 1 ) adds a new-line character to the end of the string buffer. Practice Problem 9.1 What is the output of the program shown below? #include<stdio.h> #include<string.h> int main( int argc , char *argv[ ] ) { char my_string[15] = "USNA Rules!" ; printf( "The string is %s" , my_string ) ; printf("The string's length is: %d\n" , strnlen( my_string ) ); strncat( my_string , "\n" , 1 ) ; strncat( my_string , "\n" , 1 ) ; printf( "The string is %s" , my_string ) ; printf("The string's length is: %d\n" , strnlen( my_string ) ); } Solution: Notice that the strnlen function does not count the terminating NULL as one of the characters in the string. The terminating NULL (the byte of all zeroes) is certainly there (that is, after all, how the first and third printf statements above knew when to stop), but the presumption is made that when the programmer wants to know the length of the string, the only concern is with the number of characters preceding the NULL. Now, armed with these two new string commands, let’s modify our program fun_with_files.c so that when we execute the program, we will include a text string as a command line argument. The program will append the text string to the end of the file stuff using the write command. The next page displays our modified program, still named fun_with_files.c . Let's suppose I run this program with the command line argument "To be a midshipman", as shown below: 3 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. #include<stdio.h> #include<stdlib.h> #include<string.h> #include<fcntl.h> #include<sys/stat.h> int main( int argc , char *argv[ ] ) { int fd ; char *buffer ; buffer = (char *)malloc(100); strcpy( buffer , argv[ 1 ] ); strncat( buffer , "\n" , 1 ); fd = open("stuff", O_RDWR|O_CREAT|O_APPEND , S_IRUSR|S_IWUSR ); if ( fd == -1 ) { printf( "\nFailed to open.\n"); exit(0); } write( fd , buffer , strnlen( buffer ) ); free( buffer ); } Line 9 declares the integer variable that will hold the file descriptor. Recall that the file descriptor is just a stand-in for the name of the file we will be reading from and writing to. Line 10 declares a string named buffer that, in line 12, is allocated 100 bytes of space on the heap. Line 14 copies argv[1] into buffer , and line 16 adds a newline character to the end of the string named buffer. At line 17, the picture of our program in main memory looks like this: 4 Now, line 18 ties the file named stuff to a file descriptor. Lines 20-24 are added to the program so that the user is informed if the compiler had some trouble opening the file. The key line of the program that actually adds something to our file named stuff is line 25: write( fd , buffer , strnlen( buffer ) ); This writes the data pointed to by buffer (which in this case is the string To be a midshipman followed by a new line) into the file whose file descriptor is fd (which is the file named stuff). The third argument to the function tells how many bytes to write. Let’s run the program, showing the contents of the file named stuff. Let's run this program two more times, examining the file named stuff along the way: If I decide to look at the file named stuff using nano, I see: 5 Practice Problem 9.2 What would happen if we did not have quotation marks in our command line arguments above? Solution: You should appreciate the fact that the file stuff is permanent. If you close VMware and turn off your computer, the file stuff will be there when you turn your computer back on. 1.5. File Closing. After you have finished writing to a file, the file should be closed. The syntax to close a file is close( fd ); Again, note that closing a file doesn't delete it! The file is still there, and, if opened by a program later will be in precisely the same condition it was in before it was closed. So, to make the preceding program perfect, we should close the file by adding this line to the end of the program. 2. Linux Access Privileges 2.1 File Access for Reading, Writing and Executing. Every file has access privileges that control who can read, write and execute the file. Every directory has similar privileges, but, for directories, read means “read the contents of the directory,” write means “add or remove files from the directory” and execute means copy files from the directory. You can see the access privileges for a file or directory by entering the ls –l command. The –l stands for long; that is, we want the long listing! A line produced by this command (one line will be produced for each file and directory) might look like this: -rwxr-x--x 1 jones happymids 1024 July 16 17:12 happy_times.exe Here is what the various fields in this line mean: - rwxr-x--x 1 jones happymids 1024 July 16 17:12 6 happy_times.exe The file owner The file size The file name The file creation date The file owner also belongs to this group This is our main focus for today… these are the access privileges. More on this below! The dash indicates a file. If this line item was a directory, the first symbol would have been a d. The important points above: (1) The file is named happy_times.exe and (2) the owner of the file is jones and (3) the access privileges are rwxr-x--x. Let’s look more closely at the nine symbols that comprise the access privileges. The symbols used are: • • • • r w x - for read (which also allows the copying of the file) for write for execute for no access The first three symbols (i.e., the first triplet) refer to the file owner, the second triplet refers to the owner's group and the third triplet refers to the general public (everyone who has an account on your system). So, given the access privileges that we see in the example above: • • • The owner (jones) can read, write to and execute the file happy_times.exe. The group (happymids) can read and execute the file happy_times.exe but cannot write to it. The general public can do nothing other than execute the file happy_times.exe. The access privileges are called the mode of the file or directory. The owner of a file can change the access privileges for a file using the change mode (chmod) command. The command’s format is: Practice Problem 9.3 What are the access privileges for happytimes.exe after the command shown above is entered? Solution: 7 Practice Problem 9.4 What command would remove the ability for the public to execute happytimes.exe? Solution: Practice Problem 9.5 What single command using the assign operator would assign the public the ability to read and execute happytimes.exe? Solution: It should be reiterated that only the owner can change a file's mode. Wait… that's not right… who else can change a file's mode? Practice Problem 9.6 Who, besides the file's owner, can change a file's mode. Solution. Practice Problem 9.7 And just how does one get to be the owner of a file anyway? Solution: 3. Giving Up a Little Control with sudo and setuid 3.1. User Accounts Let's take our earlier program, fun_with_files.c and—since we are eight pages into this chapter and no longer having much fun—rename it as: note1.c. Recall that this program takes a text string as a command line argument, and appends the text string to the end of a file. Let's give the file we read from and write to the more practical name of notes. This file named notes will reside in the /tmp directory. #include<stdio.h> #include<stdlib.h> #include<string.h> #include<fcntl.h> #include<sys/stat.h> int main( int argc , char *argv[ ] ) { int fd ; char *buffer ; buffer = (char *)malloc(100); strcpy( buffer , argv[ 1 ] ); strncat( buffer , "\n" , 1 ); fd = open("/tmp/notes", O_RDWR|O_CREAT|O_APPEND , S_IRUSR|S_IWUSR ); if ( fd == -1 ) { printf( "\nFailed to open.\n"); } write( fd , buffer , strnlen( buffer ) ); free( buffer ); close( fd ); } When we compile a program, the executable object code file is named a.out. We can change the name of the object code to a name of our choosing by using the –o specifier. In the screen capture below, I named the executable code as note1.exe. 8 Let’s look at the read, write and execute permissions for the source code (note1.c), the object code (note1.exe) and the text file that this program creates and writes to (/tmp/notes): It would seem that the executable program that we wrote (note1.exe) can be executed by anyone, but the file /tmp/notes can only be read and written to by the user named midshipman (since the file is owned by user midshipman). How many users have accounts on our system? How can we find out? Recall that in Linux, every user is given a directory under the home directory. So, let's see who has folders under the home directory. There is one user that is not shown on this list: the user named root. Of all accounts on a Linux system, the account named root has special privileges and full access rights over the entire system. The root account is owned by the system administrator, and has the ability to read, write and execute all files in anyone’s account. Each user who has an account on a Linux system has a unique user ID number, which you can determine by using the id command. For example, to determine the unique ID number for joe, we enter If we do this for all users we determine the following IDs: root mia joe instructor midshipman 0 500 501 998 999 There is a command that allows us to switch users… to switch from midshipman to, say joe. This command is the su command. Let’s try it! 9 This command failed! The su command asks for the password of the target account (in this case, joe’s password). The only time a password will not be asked for is if the su command is entered by the root user! Hopefully you will agree that it makes sense to restrict this command to run without a password only for the system administrator! If we wanted to switch to the root user, we would type just su, but, again, this will ask for the root user’s password. Since executing commands as the root user is potentially very dangerous, Linux provides a more restricted version of the su command, called sudo. Using sudo allows us to execute a single command as the root user. After using sudo, the very next command will revert you back to the privileges of your account. 1 Since using sudo gives you root privileges (even for only a single command), a user is prompted for a password every time they use sudo. This should make sense, since if anyone could use the sudo command, then anyone can act as the root user, one command at a time. In a Linux system, the system administrator (root) may give a few trusted assistants sudo privileges. In order for you to explore privilege management, and get a fuller understanding of how permissions are managed behind the scenes, your textbook author (Jon Erickson) has set up your VMware software so that you, the user midshipman, can use the sudo command without a password. This will allow you to switch your identity to that of other users in order to see the system from their perspective. You must always keep in mind that this is not something that an ordinary user would ever be able to do. (http://xkcd.com) So… let’s use sudo to become the user joe! We enter: sudo su joe and see, I’m joe! 2 3.2. The setuid Permission As joe, can I execute the program note1.exe? Looking at the permissions for this file from Section 3.1 above, the answer would seem to be "yes". Let's try to execute note1.exe: 1 The meaning of the acronym sudo is unclear. In some texts it is presented as "switch user (to root and) do". In some texts it is presented as "super user do". In some texts it is presented as "substitute user (for root and) do". Of course all texts are in agreement about what the sudo command actually does! 2 Although sudo only provides me the ability to execute one command as root, if the command is to switch to another user, you will remain as that user. In other words, you do not revert back to the former user after one command. 10 The program did not work. Looking back at the program at the start of Section 3.1, it looks like we invoked the if statement: if ( fd == -1 ) { printf( "\nFailed to open.\n"); } Although joe has permission to execute the program note1.exe, joe is not permitted to write to the file notes. Suppose we want joe to be able to execute the program note1.exe and actually make changes to the file named /tmp/notes, but we still do not want him to be able to read or write directly to the file /tmp/notes. In other words, we do not want joe to be able to write to the file by using, say, nano or some other program, but we do want him to be able to write to the file provided he only does this by using the note1.exe program. You may be surprised to know that this sort of scenario occurs quite frequently! Linux handles this by providing a special permission called the set user ID (setuid) permission. If an executable program has the setuid flag set, then whenever the program is executed, it will behave as though it were being executed by the owner. In other words, if we set the setuid flag for the file note1.exe, then when joe executes the program, the program will run as if the owner (midshipman) was executing it. This is good because the user named midshipman is the only one who can write to the file /tmp/notes . The owner, midshipman, can set the setuid flag for note1.exe . Let's switch back to the user named midshipman by typing exit, and then: chmod u+s note1.exe Let's do this and look at a listing of the file permissions: Note the s in the execute field for the owner. That is the indicator that the setuid flag is set. So… let’s go back to being joe and let’s see if joe can now add notes to /tmp/notes. We enter: sudo su joe then Note that joe cannot directly read the file notes (see the permissions for /tmp/notes given in Section 3.1 above). The only way joe can have an effect on the file tmp/notes is through the use of the program note1.exe. If we exit, and look at the file as midshipman: 11 It worked! Now joe—or anyone—can use the program (but only midshipman can read the file /tmp/notes). Practice Problem 9.8 You are viewing the access privileges of a file exam1.sh and they read: -r-xr-xr-- . (a) What privileges for this file are granted to the owner? (b) You give the command chmod g-x exam1.sh . What access privilege(s) did you change and to whom do they apply? Solution: (a) (b) Practice Problem 9.9 The following is the output of ls –l for the shutdown command, which is a system administration program. We can see that it is owned by the root user (administrator) and appears to be executable by everyone. That is, in actuality, not the case, since the program named shutdown actually calls other programs, and these other programs can only be executed by the root user. How can the root user modify the permissions to this program to allow anyone to shut down the computer? (Give the command, then an explanation of how it solves this problem.) Solution: Practice Problem 9.10 What does the sudo command accomplish? Solution: Practice Problem 9.11 Who can execute the sudo command? Solution: Practice Problem 9.12 Consider the long listing for three files, shown below. The file note1.c is a C program that writes to the file /tmp/notes. The file note1.exe is the compiled version of note1.c. 12 The system has four users: midshipman, smith, jones and, of course, root. (a) The user smith executes the file note1.exe and notices that his attempts to write to the file /tmp/notes are not successful. Explain why. Solution: (b) Suppose it was necessary to grant users the ability to write to the file /tmp/notes, but only when executing the program note1.exe. Your friend proposes two ways of accomplishing this: (i) Enter the command: chmod u+w /tmp/notes OR (ii) Enter the command: chmod u+s note1.exe Which option do you select and why? Solution: APPENDIX 3: More about the write command Let’s go back to that cryptic line of code: fd = open( "stuff" , O_RDWR|O_CREAT|O_APPEND , S_IRUSR|S_IWUSR ); and talk about the second argument to the open function fd = open( "stuff" , O_RDWR|O_CREAT|O_APPEND , S_IRUSR|S_IWUSR ); this second argument reads: O_RDWR|O_CREAT|O_APPEND The first item, O_RDWR tells the compiler that the program intends to read and write to the file stuff. fd = open( "stuff" , O_RDWR|O_CREAT|O_APPEND , S_IRUSR|S_IWUSR ); We have two other choices for this first flag: • If we only want the program to read a file (but never write to it): O_RDONLY • If we only want a program to write to a file (but never read it): O_WRONLY The second flag, O_CREAT , tells the compiler to create the file if it does not already exist. fd = open( "stuff" , O_RDWR|O_CREAT|O_APPEND , S_IRUSR|S_IWUSR ); The third flag, O_APPEND , tells the compiler to write data by appending it to the end of the file. fd = open( "stuff" , O_RDWR|O_CREAT|O_APPEND , S_IRUSR|S_IWUSR ); Our cryptic line of code should now make sense except for that last (third) argument: 3 Okay, let's end this chapter on an upbeat note: THIS APPENDIX IS NOT TESTABLE! 13 fd = open( "stuff" , O_RDWR|O_CREAT|O_APPEND , S_IRUSR|S_IWUSR ); Last but not least, let's discuss the third argument: fd = open( "stuff" , O_RDWR|O_CREAT|O_APPEND , S_IRUSR|S_IWUSR ); The third argument is used to set the file permissions. The choices to place in the third argument are: S_IRUSR S_IWUSR S_IXUSR The owner has permission to read the file The owner has permission to write to the file The owner has permission to execute the file S_IRGRP S_IWGRP S_IXGRP The owner’s group has permission to read the file The owner’s group has permission to write to the file The owner’s group has permission to execute the file S_IROTH S_IWOTH S_IXOTH Anyone has permission to read the file Anyone has permission to write to the file Anyone has permission to execute the file I could put as many of these choices in the third line as I choose, separating my choices with a vertical bar. What should be the result if I now entered: ls –l stuff 14