D. J. Bernstein

Secure interprocess communication

UNIX kernel designers should add a function
     #include <sys/types.h>
     #include <unistd.h>

     int getpeereid(int s,uid_t *u,gid_t *g);
that returns the effective uid and effective gid of the peer connected to a UNIX-domain stream socket, recorded when the peer called connect() or listen(). This page explains why.

The basic problem

Situation: You are a daemon, running under a special uid. You need to accept files submitted by local users. You need to know which uid (and gid) submitted each file, for access control or for accounting.

The rest of this page discusses several possible submission mechanisms.

Mechanism 1: setuid

The user runs a setuid program and provides the file as input to the program. The program talks to the daemon through an inode (typically a directory or a UNIX-domain socket) writable only by that uid.

Problem: A setuid program must operate in a very dangerous environment. A user is under complete control of its fds, args, environ, cwd, tty, rlimits, timers, signals, and more. Even worse, the list of controlled items varies from one vendor's UNIX to the next, so it is very difficult to write portable code that cleans up everything. (The same comments apply to setgid programs.)

Despite this problem, setuid programs are the most popular submission mechanism. They are used to submit passwords, mail messages, printer data, cron scripts, etc. They are responsible for a huge number of locally exploitable UNIX security holes.

Mechanism 2: UNIX-domain sockets

The user connects to a world-writable UNIX-domain stream socket. The daemon accepts the connection and calls getpeereid() to find the effective uid that called connect().

Problem: getpeereid() is not yet portable. Here's what I've heard about OS support so far: FreeBSD supports getpeereid() starting in version 4.6. OpenBSD supports getpeereid() starting in version 3.0. Linux provides the necessary information through the SO_PEERCRED option to getsockopt(), but it doesn't support the getpeereid() interface. Solaris doesn't provide the necessary information at all.

I suggested a getpeeruid() API in a bugtraq discussion in 1998. William E. Baxter designed the getpeereid() interface and wrote getpeereid() patches for several systems.

(There are other interfaces, such as NetBSD's LOCAL_CREDS, that don't return credentials until the user writes some data. This means that an unauthorized user can chew up all available connections to the daemon, preventing anyone else from connecting.)

Some applications that can take advantage of getpeereid() when it is available (and have various problems when it is not): KDE (specifically kdesu), OpenLDAP, OpenSSH, PostgreSQL, and ucspi-ipc.

Mechanism 3: doors

The user connects to the daemon through a door. The daemon looks up the uid with door_cred().

Problem: Doors are not portable. They are included in Solaris, and there is an experimental Linux implementation, but they are much too complicated to be easily included in new systems. Implementing getpeereid() is easier.

Mechanism 4: loopback NFS servers

The user writes the file to an NFS-mounted filesystem. The NFS server is the daemon.

Problem 1: NFS service is difficult to implement. Typical daemon implementors are not going to want to piece together a file from NFS write requests. It would be much easier to use getpeereid().

Problem 2: NFS service requires cooperation from root. Unless a daemon runs user programs (such as local mail delivery agents), it shouldn't need root privileges.

Mechanism 5: loopback STREAMS

The user makes a ticotsord STREAMS connection to the daemon. The daemon looks up the uid with TL_IOC_CREDOPT.

Problem: STREAMS are not portable. They are included in SVR4-based systems, but they are much too complicated to be easily included in new systems.

Mechanism 6: email

The user sends a mail message to a special address. The daemon receives messages at that address.

Problem: Using a generic communication tool merely passes the buck. How does the mail program find the uid?