Separate /tmp directories for each user

A mistake that programmers often make is to create files in /tmp without setting the flags O_CREAT and O_EXCL when opening the file. It is then it is possible for a malicious user to compromise the account of the person who creates the files in /tmp. This includes the root account, since programs like sort and vi have this bug.

Another misfeature is that programs create temporary files without the user being aware of it. Consequently, it may be that a program reveals private data from a non-world-reabable directory by copying the data into /tmp (and honouring the user's umask).

It is impracticable to replace all programs that have this bug, and the bug is really a more fundamental design problem with /tmp. The fact that /tmp is a shared directory leads to most of the problems. A partial solution is to replace the shared /tmp with a copy of /tmp for each user. Some files won't fit in with the scheme because they don't belong to any user, for example, the X11 directories. .X11-unix should be moved out of /tmp anyway to avoid cookie interception, and .X11-lock should be moved out to avoid a denial of service attack against the X-server. Similar arguments can be used to justify moving other shared files out of /tmp.

Originally, I had thought of having the temporary directory depend on the uid (or fsuid) of the reading process. This leads to confusion with programs that use seteuid to change between root and another user. It also produces surprising results if a user passes a setuid program a filename in /tmp. Rather than having the temporary directory depend on a uid, I decided to make it a property of a tree of processes, so the location of /tmp is set by the login program, and is inherited by child processes, analogous to the way that the root and working directories are handled.

To implement this, I added a chtmp system call. Since the behaviour of setuid programs can be altered using this system call, I made the system call require the chroot capability. The real location of /tmp is then visible in /proc, as a pseudo-symlink. (It's not a real symlink since it you don't require access to the parent directories of the target of the link in order to follow the link.) To start using the chtmp mechanism, the filename /tmp is made to be a symlink to /proc/self/tmp, so that changing directory to /tmp changes to the temporary directory for that process. init is started with '/' as its temporary directory. I added a chtmp program, which is just like chroot, and started all of init's children using this program, setting /tmp to /temp/root.

Only root has access to root's temporary directory, so when a program changes user, it needs to change temporary directory too. Identifying such programs isn't too difficult - to change user, a program must be running as root, or be setuid root. To locate the programs, use find -perm -04000, and look in /etc/inittab, /etc/init.d/*, /etc/inetd.conf and /etc/cron.*/*. If a program is missed, the chances are that the program will stop working alogether rather than start producing incorrect output since it won't be able to write to /tmp. In addition, since .X11-unix has moved, the X libraries and some programs will need modification.

I identified the programs that change user as:

To cope with the new location of /tmp/.X11-unix, I modified: Additional attention was needed by updatedb, since a file is written by the 'nobody' user and later read by root, and by tmpwatch, since it had to know about the new filesystem structure. Netscape Navigator broke since it was linked with the libc5 X libraries that I hadn't modified. I found that a symlink from /tmp/.X11-unix to /temp/X/X11-unix was an acceptable workaround.

The new structure for /tmp is:

  /temp
    |`---- root
    |`---- nobody
    |`---- X
    |      |`---- X11-unix/X0
    |      |`---- X11-unix/X1
    |       `---- X0-lock
    |`---- user1
    |`---- user2
     `---- user3
           [etc]

  /tmp -> /proc/self/tmp
The permissions of the user temporary directories are drwx-----T. The sticky bit is there simply to keep sshd quiet. Since sshd requires the sticky bit, I also use it to notice when directory creation is complete. (Creating the directory involves mkdir, chown and chmod, which cannot be made atomic; I regard the process as complete after the chmod.)

Kernel patch

Under Linux, each architecture's systems calls are implemented separately. I have only implemented chtmp() for i386. A surprising feature of the patch is how small it is. The patch is against kernel 2.1.106. The interesting parts of the patch are in fs/open.c, fs/proc/base.c and /fs/proc/link.c.

[View the patch]

chtmp program

This is equivelant to chroot, but for changing the temporary directory.

[View the program]

tmpdir library

To make life a little easier patching other applications, I made a tiny shared library to create a user's temporary directory. The library provides only one function:
int mkusertemp(const char *user, uid_t uid, gid_t gid);
Since the application program has typically just done a getpwnam to find which user to change to, I don't bother doing the lookup in the library. This function creates the temporary directory and chtmp()s to it.

[View the function]

Source

tmp-per-user.tar.gz

Peter Benie <pjb1008@cam.ac.uk>