pete > courses > CS 315 Fall 26 > lecture 12: permissions, ownership, and sudo


Lecture 13: permissions, ownership, and sudo

Goals


recall the discussion Tuesday regarding the PATH

as I said, it’s an environment variable that enumerates the collection of directories that are searched for executables

at the time, I deferred discussion of environment variables to a later date; now is that date


the environment is a set of key/value pairs, called environment variables, that store information about the state of the world in which a process is executing

each process has an environment (though it may be empty)

upon fork, the child process inherits the environment of the parent process

when calling exec, one may specify a new environment for the new program


this is all well and good, but what does the environment look like?

the env program will print out the environment it inherits from the shell:

$ env

we can see that it contains information like the home directory of the user running the process (HOME)

the name of the shell it was fork’ed from (SHELL)

and so on

collectively, they entail a bunch of information about the current operating… environment that the program might find useful

for instance, if the program wants to save configuration files in the user’s home directory, it would use the value of the HOME environment variable


one interesting environment variable is EDITOR

which, unsurprisingly, identifies the user’s preferred editor

have you ever run git commit without specifying a message with the -m parameter? what happens?

it opens an editor!

how does it know what editor to open?

it uses the value of the EDITOR environment variable!


you might next wonder how a program accesses environment variables

I’m so glad you asked: myenv.c

just like argv, the variable environ is an array of character pointers

each character pointer in the array points to a string of the form "KEY=value"

(by convention, environment variable names are in all caps)

thus this program uses the same pointer-traversal technique for arrays that we discussed a few weeks ago

recall that the extern keyword indicates that the named variable is allocated elsewhere—it tells the compiler a) not to make room in memory for it and b) to register it as an undefined symbol that needs to be linked (this time a variable, though, and not a function)


implicit in this conversation of environment variables is that is the idea that a process runs on behalf of a user

this is absolutely true

in fact, every process runs on behalf of exactly one user

with that in mind, let us revisit the notion of file permissions that you became familiar with in the context of assignment 1

$ ls -l
total 20
-rw-r--r-- 1 pete pete  272 Nov  1 22:07 Makefile
-rw-r--r-- 1 pete pete  175 Nov  1 08:52 myenv.c
-rw-r--r-- 1 pete pete  280 Nov  1 09:03 mysudo.c
-rw-r--r-- 1 pete pete 5679 Nov  1 22:07 notes.md

permissions are shown in the left-most field

the first character represents the type of the file ("-" for a regular file)

the first "rwx" applies to the owner of the file (in this case "pete")

the second "rwx" applies to the users who are part of the group associated with the file (in this case, also "pete")

and the third "rwx" applies to everybody else


first question: how are the username and group name printed?

thinking back to assignment 1, you used the getpwuid and getgrgid functions to convert from a numerical identifier to the corresponding text

(how the numeric identifier itself is stored on disk is specific to the filesystem, which is how files are organized on disk, and is implementation-dependent)

so where is the data that allows these functions to perform the translation?

answer: the "user database" is traditionally stored in the file /etc/passwd

(tradition dictates that systemwide configuration files live under /etc, pronounced "et-see", and they are most often plain text files)

this file contains one line per user on the system, and each line is composed of multiple fields separated by colons

for instance:

pete:x:1000:1000::/home/pete:/bin/bash

decode: username:password:uid:gid:name:home-directory:shell

so the getpwuid function searches through this file for a matching uid and returns the corresponding string

(group information is in /etc/group, which has much the same format, though fewer fields)


you will note the password field for all users is "x"

that is because the password itself is not stored in this file

it used to be, but it was deemed insecure

since users need to be able to see the uid/username mapping on a regular basis, it was decided to leave this file otherwise intact, but to move the passwords themselves to a different file

$ ls -l /etc/shadow
-rw------- 1 root root 897 Dec  3  2017 /etc/shadow

note that it is readable and writeable only by the root user

root is a special user, with uid 0, that has complete access to the entire system

it can even override permissions (eg, even if the 'r' permission on a given file isn’t set, root can read it)


with all this in mind, how and when are permissions checked?

when open is called, the kernel compares the uid associated with the process that called open, the permissions of the file, and the requested operation (eg, O_RDONLY)

if that user has permission to perform the requested operation, open returns successfully; otherwise it returns error


which begs the question: how does one go about changing a file’s mode bits (ie, the permissions)?

one uses the chmod program (which in turn uses the chmod(2) syscall)

the basic pattern is "chmod perms files"

there are two ways to specify permissions: either as the exact set of permissions you want, or a modification of the current permissions

in the case of the former, we specify whose permissions to change ('u’ser, ’g', or ’o’ther), followed by a "+" or "-" for add or remove, followed by the particular permission

if we start with these permissions:

$ ls -l notes.md
-rw-r--r-- 1 pete pete 8992 Nov  1 09:18 notes.md

we can add ("+") the ’w’rite permission for ’g’roup and ’o’ther thusly:

$ chmod go+w notes.md
$ ls -l notes.md
-rw-rw-rw- 1 pete pete 8992 Nov  1 09:18 notes.md

we can add the e’x’ecute permission for everybody thusly:

$ chmod +x notes.md
-rwxrwxrwx 1 pete pete 8992 Nov  1 09:18 notes.md

(we can even try to run it, in which case exec will produce garbage output)

when specifying specific permission (rather than modifications) we use the octal representation, where each octal digit represents the permissions for either user, group, or other

each bit within the octal value represents a different permission: 4 for read, 2 for write, 1 for execute: add them up to get the permission you want

a typical permission for data files is 644, which is read/write for owner (6), read for group (4), and read for everybody else (4):

$ chmod 644 notes.md
-rw-r--r-- 1 pete pete 8992 Nov  1 09:18 notes.md

a typical permission for programs is 755, which is rxwr-xr-x


now let’s tie this notion of users back to last lecture’s discussion of fork and exec

first, uid is inherited across fork

that is, if a process owned by uid 1234 forks, then the child process will also be owned by uid 1234


with that in mind, let us review what sudo does

it allows us to run a program as if we were a different user

which implies that there must exist the facility to change the uid of a running process

and so there is: the setuid syscall

here’s a super simple implementation of sudo: mysudo.c

it changes to uid 0 (ie, root) and then calls exec—super straightforward

if we compile and run it…

$ gcc -g -Wall -pedantic -o mysudo mysudo.c
$ ./mysudo whoami
setuid: Operation not permitted

it fails!

this is totally reasonable: if all it took to run a program as root was to stuff in a call to setuid(0), it would be trivial for any user to elevate permissions, which is a disaster waiting to happen


instead, we want this program to run with root permissions in the first place

but how can we do that if we want any old user to be able to run it themselves?

enter: the "setuid bit"

this is a special permission on a file that says "no matter which user runs this program, it should always run with the uid of the file’s owner"

therefore, if the file is owned by root, has the setuid bit set, and user "pete" runs it, the resulting process will be owned by root

there are two steps to accomplishing this

change ownership to root:

$ sudo chown root mysudo
$ ls -l mysudo
-rwxr-xr-x 1 root pete 19352 Nov  1 09:00 mysudo

and set the setuid bit:

$ sudo chmod u+s mysudo
$ ls -l mysudo
-rwsr-xr-x 1 root pete 19352 Nov  1 09:00 mysudo

note that the "x" user permission has been replaced with "s": this is how we know the setuid bit is…. set

(and yes, setuid implies the execute permission)


THIS IS A VERY DANGEROUS PROGRAM TO KEEP AROUND

WITH IT, ANY USER CAN RUN ANY COMMAND WITH ROOT PERMISSIONS

so the real sudo adds on top of this a bunch of configuration that allows an administrator to specify who is allowed to use it and under what conditions

the configuration file is /etc/sudoers, which format you can read about in its manpage: sudoers(5)

(ooh! a new manual section! as man(1) tells us, section 5 is for "file formats and conventions")

Last modified: