Abolish Root Daemons!


Pruning the root daemons.
(Info on the pic)

Carol Hurwitz, hurwitz@cs.berkeley.edu
Scott McPeak, smcpeak@cs.berkeley.edu
CS 261, Fall 2000
Released materials:

1. Introduction

The fundamental goal of our project is to change the set of primitives available to network daemons, such that they can accomplish their tasks without ever running as root.

It is the fact that so many daemons run as root which makes them such an attractive target for attackers. At the present, if a malicious user gains control of ftpd, then immediately all root privileges are available. We wish to limit the possibility of attack to the usual (or yet to be discovered) vulnerablilities of a user program which allow an attacker to obtain root privileges. Thus we will have made access to root privileges a two-step process rather than one.

At very least, this is a deterrent factor. At best, there might not be a vulnerability that lets the attacker gain root (yeah right). Somewhere in between is the possibility of successfully getting a log of the initial attack to stable, read-only storage before the attacker gains root and cleans the logs.

2. Our project

We intend to work within the existing framework of Linux as it stands today. We aim to develop a system which can in fact be easily used by security-conscious sysadmins. Daemons will require only a few lines of code changes (if any), and kernel changes should be only a few tens of lines (ideally to be incorporated into the mainstream Linux kernel).

The primitives that we will examine and modify:

  1. chroot(2)
  2. bind(2) of privileged ports
  3. the authentication process

3. chroot(2)

3.1. Example: anonymous FTP

When someone logs in to FTP as "annonymous" we want to call chroot(2) in order to restrict their access to the section of the file system where the files that are available by anonymous FTP are stored. At this point we do not need to change the process' credentials; ftpd does not become the user in this case, since it is not even known who it is. (It is still desirable to distinguish the daemon user used when ftpd first accepts the connection, and the daemon user used for the duration of an anonymous FTP connection. A design for this is still in flux.)

3.2. chroot(2)'s semantics

chroot(2) as it now stands must be called from a program running as root. It makes a permanent change to the file system access that is passed on to any child processes: any file names presented to open(2) will be interpreted relative to chroot(2)'s argument.

The reason chroot(2) must be privileged is subtle. If ordinary users are allowed to chroot(2) to arbitrary places, it changes the names of certain, trusted files. For instance if you make the following call,

         chroot("/tmp");
the name /etc/passwd would now refer to /tmp/etc/passwd and someone in control of the contents of /tmp (typically any user can write to /tmp) could have installed whatever files he/she wished as a subdirectory of /tmp.

Then they could run su(1), which would now only see the malicious forgeries of /etc/passwd and /etc/shadow due to the name change implied by running chroot(2). The ordinary user could now logon as root via the call to su(1), using the passwd and shadow files that he/she had installed.

3.3. Our changes

We will change chroot(2) so that it can run from anywhere but we will disable the ability for any setuid program to run after chroot(2) has been called. This solves the above vulnerability because the jailed process now can never gain privileges.

Due to an implementation detail in the kernel, it is possible for a process to use chroot(2) itself to escape the jail. (And there is not an easy way to fix this.) The following code will do it:

  int fd = open("/", O_RDONLY);        // get jail's / as an fd
  mkdir("testdir");
  chroot("testdir");                   // make nested jail -- key to escape!
  fchdir(fd);                          // back to first jail's /
  for (int i=0; i<10; i++) {
    chdir("..");                       // successively higher ("/../"=="/")
  }
  chroot(".");                         // final "jail" is real /
  execl("/bin/sh", "/bin/sh", NULL);   // unjailed shell

In current systems, this escape is not possible because chroot(2) is privileged (there are many ways to escape the jail if you get root inside it). In the new system, we can prevent this by disabling chroot(2) inside the jail.

Thus, these changes will eliminate the need for ftpd to run as root in order to restrict access to the file system for anonymous FTP.

4.1. bind(2) of privileged ports

4.2. Example: sshd

sshd must listen to port 22.

4.3. bind(2)'s semantics

bind(2) allows a process to bind a privileged port. At the present one must run as root in order to call bind(2) with a port argument in the range [1,1023]. This is what it means for those ports to be privileged.

4.4. Our changes

We are going to allow programs in a special group to bind(2) privileged ports. By using a group, we can grant the privilege to any subset of daemons we choose.

This means that if someone subverts sshd they will be able to bind privileged ports, but this is no worse than the present situation, where subversion leads to total compromise.

5. Authentication

5.1. Present Situation

When an ftp session is initiatied by a client, inetd starts the running of ftpd. The ftp daemon runs as root during the authentication phase of establishing a connection with the client who has requested the service. After authentication ftpd runs as the user, with limited privileges.

Currently, programs that need to authenticate do something similar to the following:

  // data
  char username[80], password[80];
  struct passwd *pw;

  // get authentication info
  prompt(username, 80, "username: ");
  prompt(password, 80, "password: ");
  pw = getpwnam(username);

  // check password
  if (0==strcmp(crypt(passwd, pw->pw_passwd), pw->pw_passwd)) {
    // become that user
    setegid((gid_t)pw->pw_gid);               // primary group
    initgroups(pw->pw_name, pw->pw_gid);      // additional groups
    seteuid((uid_t)pw->pw_uid);               // user id
  }

In the above code, getpwnam only returns a useful password if the process runs as root (because /etc/shadow is only readable by root). Further, the setegid, initgroups, and seteuid calls are all privileged. For these reasons, daemons that authenticate must run as root on current operating systems.

5.2. Authentication Subsystem

We propose to export the authentication check, and the setuid call, to a trusted subsystem. We will allow processes running as ordinary users to become any other user, by presenting a username and password to a trusted authentication subsystem. The above sequence will change to something like:

  // data
  char username[80], password[80];

  // get authentication info
  prompt(username, 80, "username: ");
  prompt(password, 80, "password: ");

  // check it, and if success, become that user
  auth_check(username, password);

The trusted subsystem will be implemented as a combination of a separate, run-as-root server to do the authentication check, and a loadable kernel module to serve as trusted intermediary and to do the setuid.

The kernel module is needed because there is presently no way for one process, even one running as root, to adjust the process credentials of another process.

5.3. PAM

In principle, we can use PAM (Pluggable Authentication Modules) to minimize the changes needed in the daemon code, if the daemon has already been written to know about PAM. PAM provides generic authentication interfaces, and we would simply write a new PAM module that talked to the special authentication device driver.

6. Further Extensions

Network daemons are the highest priority, since they are remotely accessible. However, a large number of setuid-root programs could have their setuid bits stripped if they utilize the above mechanisms, especially the authentication subsystem.

As a trivial example, su(1) need not be setuid-root, since its entire job is subsumed by the authentication subsystem. Another example is xlock(1), which just needs to check passwords.

7. Miscellaneous

This section is for thoughts we haven't integrated into the structure above.

8. Milestones