COMP 3000 2022F Assignment 2 Solutions 1. [2] What internal commands does 3000shell2.c support? Are these the same as those supported by 3000shell.c? Explain briefly. A: 3000shell2 supports the internal commands exit (line 243), pinfo (line 247), and become (line 253). All other commands are external. 3000shell, in contrast, supports the internal commands exit and plist. So only one, exit, is the same for both programs. 2. [2] If you build 3000shell2.c the same as you did 3000shell.c (from Tutorial 3, the given gcc command), what does the command "pinfo" report? What about if you build it as you did 3000userlogin.c (from Tutorial 4, the Makefile)? Explain any differences you see. A: On the class VM, with the standard build, you should get: uid=1000, euid=1000, gid=1000, egid=1000 But if you compile it as it was for Tutorial 4, using something similar to the given Makefile, you should get: uid=1000, euid=0, gid=1000, egid=1000 They are the same except for the different euid. This is because in Tutorial 4 we changed the binary to be owned by root and to be setuid root, thus making any process running that program binary run with an effective user ID of root (uid 0). (Otherwise they were compiled the same.) 3. [2] What is the purpose of lines 188 and 189 (the calls to seteuid() and setegid())? Do they always do something useful? A: These lines are important because they cause 3000shell2 to drop root privileges before running any programs, if we aren't becoming another user. These lines only do something if the process's euid is not the same as its uid, and its euid is 0. This is because euid!=uid and egid!=gid is only true if the binary is setuid or setgid, and these system calls will only work if the binary is setuid root. (Otherwise, the process can't change its own effective gid or uid to anything but its own gid and uid.) 4. [1] What is the syntax of the "become" command? A: become ... where ... is the arguments (if any) to the command. 5. [1] What must be true for the "become" command to work? A: The become command only works if the program's euid is 0 (it is running as root). 6. [2] Modify the "become" command so it takes a uid, not just a username. A: Usernames can't start with a number, so we just have to check if the first character of the username is a number, and if so we convert it to an int, cast it to a uid_t, and use that as the uid. We then have to do the lookup using getpwuid not getpwnam. The rest of the logic is the same. [FIXME: ADD CODE CHANGES] So we replace lines 60-64 with the following code: if (isdigit(username[0])) { /* Assume username is actually a uid */ uid = (uid_t) atoi(username); pw_entry = getpwuid(uid); if (pw_entry == NULL) { fprintf(stderr, "Could not find uid %s.\n", username); exit(-2); } } else { pw_entry = getpwnam(username); if (pw_entry == NULL) { fprintf(stderr, "Could not find user %s.\n", username); exit(-2); } } 7. [2] How does the behavior of 3000shell2 change when it is run setuid root versus running as root? Why? A: When 3000shell2 is run as setuid root, commands are run as the user who started 3000shell2 (student on the VMs) unless the become command is used. If 3000shell2 is run as root, however, all commands are run as root. This difference is because 3000shell2 changes its euid and egid to be its uid and gid, respectively, before doing an execve of an external command. Thus while it is setuid root and thus runs with euid=0, this goes away before any commands are run, meaning that they are run as the logged in user. 8. [2] Does output redirection work for any of the internal commands? Explain briefly. A: Output redirection doesn't work with exit or pinfo because they are executed before the > is even parsed. become, however, does work with output redirection because it is processed before > is parsed and thus output can still be redirected. 9. [4] Create a command "background" that runs a program in the background, redirecting standard in, out, and error. background takes at least four arguments: * a file to be opened for standard input, * a file to be opened for standard output (erasing any existing file and creating it if necessary), * a file to be opened for standard error (erasing any existing file and created it if necessary), and * a program to run (either an absolute path or a program in the current PATH) with zero or more arguments. Note that background should ignore any > or & characters included. A: The easiest way to do this is to extend run_program() to handle redirecting of standard in and standard error along with standard out. We can then follow the template of the other commands. First, we change the way we call run_program() on line 285: run_program(args, background, NULL, stdout_fn, NULL, path, envp, new_user); At line 262, just before background=0, we insert the following: if (!strcmp(args[0], "background")) { if (nargs < 5) { fprintf(stderr, "ERROR: need more arguments for " "background\n"); continue; } stdin_fn = args[1]; stdout_fn = args[2]; stderr_fn = args[3]; run_program(args + 4, 1, stdin_fn, stdout_fn, stderr_fn, path, envp, new_user); continue; } Note that we call run_program() with args+4 (skipping background and its arguments) and we pass in a 1 for background, meaning the command should be run in the background. We modify the declaration of run_program() as follows: void run_program(char *args[], int background, char *stdin_fn, char *stdout_fn, char *stderr_fn, char *path, char *envp[], char *new_user) (This now has stdin_fn and stderr_fn.) We then use these new arguments as follows, by replacing lines 194-198 (the processing of stdout_fn) with the following: if (stdin_fn != NULL) { fd = open(stdin_fn, O_RDONLY); if (fd == -1) { fprintf(stderr, "Couldn't open %s for reading.\n", stdin_fn); exit(-1); } dup2(fd, 0); close(fd); } if (stdout_fn != NULL) { fd = creat(stdout_fn, 0666); if (fd == -1) { fprintf(stderr, "Couldn't open %s for writing.\n", stdout_fn); exit(-1); } dup2(fd, 1); close(fd); } if (stderr_fn != NULL) { fd = creat(stderr_fn, 0666); if (fd == -1) { fprintf(stderr, "Couldn't open %s for writing.\n", stderr_fn); exit(-1); } dup2(fd, 2); close(fd); } Note that now we check for errors after opening/creating the files. 10. [2] How can you ensure that the new "background" can be combined with the "become" command? Why does this work? A: We just have to make sure the background command processing is done around line 262, after "background" has been processed but before "&" processing. This is because after "become" has been processed the argument list is just like a normal command, and so the rest of 3000shell2's logic can work as before. As long as we make the background command work in this context the two can be combined.