CS360 Lecture notes -- The Setuid bit, passwd, time


In order for these programs to work, you should ssh to the machine cetus1 and then cd to /var/tmp/Setuid. The setuid bit only works when the executable file is on the same machine as the user executing it.


Real and Effective User ID's

When a program runs, the process has two user id numbers. These are the "real" and "effective" user id's. The real id is the id number of the user that is executing the process. The effective id controls file permissions, and is a little subtle. It is the focus of this lecture.

The system calls getuid() and geteuid() return the real and effective user ID's. The program print_ids.c prints these out:

main()
{
  printf("UID:  %d\n", getuid());
  printf("EUID: %d\n", geteuid());
  exit(0);
}

When I run it, I get the following:

UNIX> print_ids
UID:  192
EUID: 192
UNIX> 
You will get different values, because your user ids are different from mine. However, you will notice that the real and effective ids are the same.

The setuid bit is a bit in the protection mode of an executable file. You set it (typically) by doing a chmod to 04755. Below, we copy the executable to print_ids_setuid and set the setuid bit. It results in a different long listing -- there is now an s where the execute bit should go:

UNIX> cp print_ids print_ids_setuid
UNIX> chmod 04755 print_ids_setuid
UNIX> ls -l print_ids*
-rwxr-xr-x 1 plank loci 11106 2010-02-17 18:09 print_ids
-rw-r--r-- 1 plank loci   174 2010-02-17 18:09 print_ids.c
-rwsr-xr-x 1 plank loci 11106 2010-02-17 18:12 print_ids_setuid
UNIX> 
Now, suppose there's a user named fred with a user id of 555. Here's what will happen to Fred:
UNIX> print_ids
UID:  555
EUID: 555
UNIX> print_ids_setuid 
UID:  555
EUID: 192
UNIX> 
The setuid bit has resulted in Fred having my effective user id. This allows Fred to have my permissions when manipulating files. Let's give a simple example in write_log_file.c:

static char *LOGFILE = "/var/tmp/Setuid/logfile.txt";

main()
{
  FILE *f;
  time_t t;

  printf("UID:  %d\n", getuid());
  printf("EUID: %d\n", geteuid());

  t = time(0);
  f = fopen(LOGFILE, "a");
  if (f == NULL) { perror(LOGFILE); exit(1); }
  fprintf(f, "%-8s %s", getenv("USER"), ctime(&t));
  fclose(f);

  exit(0);
}

When I run it, I get the following:

UNIX> write_log_file
UID:  192
EUID: 192
UNIX> ls -l logfile.txt
-rw-r--r-- 1 plank loci 34 2010-02-17 18:20 logfile.txt
UNIX> cat logfile.txt
plank    Wed Feb 17 18:20:33 2010
UNIX> 
When Fred runs it, he gets an error:
UNIX> write_log_file
UID:  555
EUID: 555
/var/tmp/Setuid/logfile.txt: Permission denied
UNIX> 
However, if I copy it to write_log_file_setuid and set the setuid bit, Fred inherits my permissions and can open and write to the file:
UNIX> ls -l write_log_file_setuid
-rwsr-xr-x 1 plank loci 11502 2010-02-17 18:23 write_log_file_setuid
UNIX> write_log_file_setuid
UID:  555
EUID: 192
UNIX> cat logfile.txt
plank    Wed Feb 17 18:20:33 2010
fred     Wed Feb 17 18:23:59 2010
UNIX> 
That's nice -- I have allowed very limited access to my files, and it is done in a clean and safe way. Fred cannot vi my file -- he can only modify it by calling write_log_file_setuid.

Many root programs use the setuid bit to give users limited access to root-only resources. Do a long listing of /bin and /usr/bin you'll see a few:

UNIX> ls -l /bin | grep rws
-rwsr-xr-x 1 root root   27152 2009-03-05 17:53 fusermount
-rwsr-xr-x 1 root root   78016 2009-02-18 16:42 mount
-rwsr-xr-x 1 root root   33168 2007-12-10 14:03 ping
-rwsr-xr-x 1 root root   28968 2007-12-10 14:03 ping6
-rwsr-xr-x 1 root root   32688 2009-04-04 01:50 su
-rwsr-xr-x 1 root root   56472 2009-02-18 16:42 umount
UNIX> ls -l /usr/bin | grep rws
-rwsr-xr-x 1 root   root       15168 2007-12-10 14:03 arping
-rwsr-sr-x 1 daemon daemon     48376 2009-04-17 03:52 at
-rwsr-xr-x 1 root   root       41784 2009-04-04 01:50 chfn
-rwsr-xr-x 1 root   root       32952 2009-04-04 01:50 chsh
-rwsr-xr-x 1 root   root       51256 2009-04-04 01:50 gpasswd
-rwsr-xr-x 1 root   lpadmin    14632 2009-10-31 19:36 lppasswd
-rwsr-xr-x 1 root   root       62368 2008-11-05 08:24 mtr
-rwsr-xr-x 1 root   root       28208 2009-04-04 01:50 newgrp
-rwsr-xr-x 1 root   root       42776 2009-04-04 01:50 passwd
-rwsr-sr-x 1 root   mail       85576 2007-03-27 14:38 procmail
-rwsr-xr-x 1 root   root       75624 2009-11-11 05:00 pulseaudio
-rwsr-xr-x 1 root   root       10536 2008-11-08 06:36 slock
-rwsr-xr-x 2 root   root      131040 2009-02-16 22:24 sudo
-rwsr-xr-x 2 root   root      131040 2009-02-16 22:24 sudoedit
-rwsr-xr-x 1 root   root       15296 2007-12-10 14:03 traceroute6.iputils
-rwsr-sr-x 1 root   root       10472 2009-04-03 03:44 X
UNIX> 

setuid() and seteuid()

These are system calls that allow you to set your real and effective user ids to different values. The useful one here is seteuid(), which allows you to toggle your effective user id back and forth between your real user id and the owner of a program that has the setuid bit set.

For example, the file secret-file.txt is protected to be readable by me, but by no one else:

UNIX> ls -l secret-file.txt 
-r-------- 1 plank loci 30 2010-02-17 18:34 secret-file.txt
UNIX> 
The program read_secret toggles the effective user id to show that when it is set to the owner of read_secret (me), you can read the file, but when it is not, you can't. Here's the code (read_secret.c):

static char *SECRET_COMMAND = "cat /var/tmp/Setuid/secret-file.txt";

main()
{
  FILE *f;
  time_t t;
  int euid, uid, i;

  uid = getuid();
  euid = geteuid();

  printf("UID:  %d\n", getuid());
  printf("EUID: %d\n", geteuid());
  printf("\nCalling '%s'\n", SECRET_COMMAND);
  i = system(SECRET_COMMAND);

  printf("\nSetting euid to %d and calling '%s'\n", uid, SECRET_COMMAND);
  seteuid(uid);
  i = system(SECRET_COMMAND);

  printf("\nSetting euid to %d and calling '%s'\n", euid, SECRET_COMMAND);
  seteuid(euid);
  i = system(SECRET_COMMAND);

  exit(0);
}

UNIX> And here's what happens when Fred runs it.

UNIX> read_secret 
UID:  555
EUID: 192

Calling 'cat /var/tmp/Setuid/secret-file.txt'
Secret contents are exciting!

Setting euid to 555 and calling 'cat /var/tmp/Setuid/secret-file.txt'
cat: secret-file.txt: Permission denied

Setting euid to 192 and calling 'cat /var/tmp/Setuid/secret-file.txt'
Secret contents are exciting!
UNIX> 

Dangerous things

When you set the setuid bit on a program of yours, you must be extra careful that you are not allowing other users to do too much. For example, the following program would be foolish: junk.c

main()
{
  system("sh");
}

This would give any user who runs it a shell running with your permissions.

There are other dangerous programs whose exposure is more subtle. For example, back in my gradaute school days, I inherited a grading program that worked roughly like labsubmit.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

main(int argc, char **argv)
{
  char *un;
  char *command;
  int i;
  int maxlen;
  char *mydir = "/home/TA/lab-submissions";

  un = getenv("USER");
  maxlen = 0;

  for (i = 1; i < argc; i++) if (strlen(argv[i]) > maxlen) maxlen = strlen(argv[i]);
  command = (char *) malloc(sizeof(char)*(maxlen*2)+200);

  sprintf(command, "%s/%s", mydir, un);
  mkdir(command, 0700);
  printf("Made directory %s\n", command);

  for (i = 1; i < argc; i++) {
    sprintf(command, "cp %s %s/%s/%s", argv[i], mydir, un, argv[i]);
    printf("%s\n", command);
    system(command);
  }
  exit(0);
}

Since there is no directory "/home/TA", there's no problem running this program. I'm also not setting the setuid bit.

However, the setuid bit was supposed to be set. The intent of this program was to have users copy their programs to the TA's lab directory, and the programs would be owned by the TA's, and not the students. This was a better solution than, for example, having the students put the programs into a directory that the TA's could view. Why? It meant that the students couldn't backdate the files using touch, because once the files were in the TA's directory and labsubmit had exited, the students couldn't touch the files. Also, since the file and directory were owned by the TA and not the students, students couldn't see each others' submitted code. An alternative would have been to have the students tar up their code and mail it to the TA, but in those days, our mailers didn't have as much room, and if students tarred up executables with their code, we might run out of mail space.

If we run it as it was intended, it's a bit hard to read, since those system() calls all fail:

UNIX> labsubmit print_ids.c read_secret.c write_log_file.c
Made directory /home/TA/lab-submissions/plank
cp print_ids.c /home/TA/lab-submissions/plank/print_ids.c
cp: /home/TA/lab-submissions/plank/print_ids.c: No such file or directory
cp read_secret.c /home/TA/lab-submissions/plank/read_secret.c
cp: /home/TA/lab-submissions/plank/read_secret.c: No such file or directory
cp write_log_file.c /home/TA/lab-submissions/plank/write_log_file.c
cp: /home/TA/lab-submissions/plank/write_log_file.c: No such file or directory
UNIX> 
Let's run it again and suppress standard error by using sh -c and redirecting standard error to /dev/null (remember this trick -- it's a good one):
UNIX> sh -c 'labsubmit print_ids.c read_secret.c write_log_file.c 2> /dev/null'
Made directory /home/TA/lab-submissions/plank
cp print_ids.c /home/TA/lab-submissions/plank/print_ids.c
cp read_secret.c /home/TA/lab-submissions/plank/read_secret.c
cp write_log_file.c /home/TA/lab-submissions/plank/write_log_file.c
UNIX> 
That's exactly what we'd want -- a directory called plank would be created and the three C files would be copied there.

However, ther's a serious security problem with this program -- what if I call it with some ".."'s in the path name. Then I can create files in my TA's home directory! You might say "Big deal -- so you can put some files in the TA's directory". Well, it is a big deal. Suppose that the TA's labsubmit is in the TA's home directory. Then consider the following sequence of actions:

UNIX> mkdir dastardly
UNIX> cd dastardly
UNIX> cp ~TA/.cshrc .
UNIX> echo "cp /bin/sh .my-private-shell" >> .cshrc
UNIX> echo "chmod 04755 .my-private-shell" >> .cshrc
UNIX> mkdir dir1
UNIX> mkdir dir1/dir2
UNIX> cd dir1/dir2
UNIX> ~TA/labsubmit ../../.cshrc
Made directory /home/TA/lab-submissions/plank
cp ../../.cshrc /home/TA/lab-submissions/plank/../../.cshrc
UNIX> 
What I've done is copy the TA's .cshrc file to my directory, then added the following two lines to the end of it:

cp /bin/sh .my-private-shell
chmod 04755 .my-private-shell

Then I organized it so that when I called labsubmit, it copied this new .cshrc over top of the old one. The next time that the TA calls csh, it executes those two lines, and now sitting in /home/TA/.my-private-shell is a sh program with the setuid bit set. Whoever runs it will run it with the TA's permissions set.

I'll wait until that happens, and then the first thing that I'll do with my shell is delete those two lines in the .cshrc file. Think the TA's will ever figure out that they have a problem?

That's a big security leak. Make sure you understand that.


Strange Things

Consider the following program (in infiltrator_1.c):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

main()
{
  char *un;
  int uid;
  int euid;
  char *s;

  un = getenv("USER");
  uid = getuid();
  euid = geteuid();

  s = (char *) malloc(sizeof(char)*(strlen(un)*2+300));
  chmod(".", 0777);

  seteuid(uid);
  sprintf(s, "echo 'This is my file' > %s.txt; chmod 0400 %s.txt\n", un, un);
  system(s);

  seteuid(euid);
  chmod(".", 0755);
  exit(0);
}

The chmod() commands make it so that freddy can create a file in my directory under his ownership.

Suppose I compile this and set the executable's setuid bit. And suppose the user "freddy" runs it. This will create the file freddy.txt in my directory, owned by freddy, with protections that are only readable by freddy:

UNIX> ls -l freddy.txt
-r--------   1 freddy  plank  16 Feb 19 11:44 freddy.txt
UNIX> cat freddy.txt
cat: freddy.txt: Permission denied
UNIX> rm -f freddy.txt
UNIX> ls -l freddy.txt
ls: freddy.txt: No such file or directory
UNIX> 
As you can see, I can't read it, but I can delete it since it's in my directory.

Let's change it now so that freddy creates a directory in my directory instead. The code is in infiltrator_2.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

main()
{
  char *un;
  int uid;
  int euid;
  char *s;

  un = getenv("USER");
  uid = getuid();
  euid = geteuid();

  s = (char *) malloc(sizeof(char)*(strlen(un)*2+300));
  chmod(".", 0777);

  seteuid(uid);
  mkdir(un, 0700);
  chdir(un);
  sprintf(s, "echo 'This is my file' > %s.txt; chmod 0400 %s.txt\n", un, un);
  system(s);
  chdir(".."); 

  seteuid(euid);
  chmod(".", 0755);
  exit(0);
}

Suppose I compile it and set the setuid bit again. When freddy runs it, we see the following:

UNIX> ls -l | grep freddy
drwx------   3 freddy  plank    102 Feb 19 11:49 freddy
UNIX> ls freddy
ls: freddy: Permission denied
UNIX> cd freddy
tcsh: freddy: Permission denied.
UNIX> rm -rf freddy
rm: freddy: Permission denied
UNIX> 
That stinks. Freddy now has a directory in my files. He can do anything he wants with it, and I can't see it or delete it. Why can't I delete a directory when I could delete a file? Because you can't delete directories that you can't read, and you can't delete non-empty directories. When freddy deletes all the files in freddy and changes the permissions to 0755, then I can delete it. Go over this example in detail so that you understand it.

freddy:
UNIX> chmod 0755 freddy
UNIX> chmod 0644 freddy/freddy.txt
UNIX> 











UNIX> rm freddy/freddy.txt
UNIX> chmod 0700 freddy
UNIX> 





UNIX> chmod 0755 freddy
UNIX> 
me:



UNIX> ls -l | grep freddy
drwxr-xr-x   3 freddy  plank    102 Feb 19 12:01 freddy
UNIX> ls -l freddy
total 8
-rw-r--r--   1 freddy  plank  16 Feb 19 12:01 freddy.txt
UNIX> rm -rf freddy
rm: freddy/freddy.txt: Permission denied
rm: freddy: Directory not empty
UNIX> rm -rf freddy/freddy.txt
rm: freddy/freddy.txt: Permission denied
UNIX> 



UNIX> ls -l | grep freddy
drwx------   2 freddy  plank     68 Feb 19 12:04 freddy
UNIX> rm -rf freddy
rm: freddy: Permission denied
UNIX> 


UNIX> ls -l | grep freddy
drwxr-xr-x   2 freddy  plank     68 Feb 19 12:04 freddy
UNIX> rm -rf freddy
UNIX> ls -l | grep freddy
UNIX> 


/etc/passwd

Read the book -- sections 6.2 and 6.3.

Time

time(): Read section 6.9 and/or the man page for "ctime()". I give a very basic definition below -- the book's explanation is much better. The program random_times.c has a bunch of examples of using the functions.

Look at the 5th line of output when you run random_times(), as compared to the 6th and 7th lines. What looks wrong? Why does this happen? Make sure you understand this, and if you don't, ask the TA. It's something that we have discussed in class.


(a basic sketchy explanation of time stuff:)

Time has two forms in Unix:  time_t, and struct tm.

time_t is a long:  seconds since 00:00:00, jan 1, 1970 UTC

struct tm is:

struct tm {
               int tm_sec;      /* seconds (0 - 61) */
               int tm_min;      /* minutes (0 - 59) */
               int tm_hour;     /* hours (0 - 23) */
               int tm_mday;     /* day of month (1 - 31) */
               int tm_mon;      /* month of year (0 - 11) */
               int tm_year;     /* year since 1900 */
               int tm_wday;     /* day of week (Sunday = 0) */
               int tm_yday;     /* day of year (0 - 365) */
               int tm_isdst;    /* 1 if DST in effect */
               char *tm_zone;   /* abbreviation of timezone name */
               long tm_gmtoff;  /* offset from GMT in seconds */
}

The procedures described in chapter 6.9, and in the man page for ctime()
give you ways to manipulate and use these two forms of time.


time_t time(time_t *t) -- returns current time in a time_t, which is a long.
                          This is in the ret val, and in t.

char *ctime(time_t *t) -- returns a string representing the current time
                          in the local time zone.

struct tm *localtime(time_t *t) -- returns a struct tm for t in the 
                          local time zone.

struct tm *gmtime(time_t *t) -- returns a struct tm for t in greenwich
                          mean time.

char *asctime(struct tm *t) -- returns a string representation of t.

strftime(char *s, int len, char *fmt, struct tm *t) -- is like sprintf,
                           only it formats from the struct tm structure.

time_t mktime(struct tm *t) -- converts a struct tm into a time_t.