mjl blog
November 26th 2017

Ding with privilege separation

Ding v0.1.0 has been released just now. Biggest change? Privilege separation. Get it at https://github.com/mjl-/ding.

Ding runs builds under a unique user id (uid), so builds can’t interfere with each other. It already did before this new release. But it was a kludge to set up, using a mix of “sudo” and a setuid binary. Not a good way to seduce new users into trying Ding…

With this new release, Ding no longer needs a setuid binary or sudo rules. Instead, it simply runs as root! That way, it has full permission to do whatever it needs to do. Just kidding of course. Ding indeed does start as root. But it forks off a child process immediately. That unprivileged child process handles all HTTP requests, and manages all builds. The process running as root stays around, but does very little. It communicates with the unprivileged process through a socket using a simple protocol. The child process can ask the root process to execute some operations that require privilege:

  1. Chown’ing files that have been cloned to a unique uid.
  2. Removing files from old builds (owned by a unique uid).
  3. Starting a build under a unique uid.

The third operation is the most interesting one. The root process starts a new process that executes build.sh. It starts that process with pipes as stdout and stderr. The file descriptors of the reading ends of the pipes are then passed to the unprivileged child process. Sending file descriptors to another process is an old unix feature. It’s a bit obscure and involves sending specially formed messages out-of-bound over a unix socket. But it works quite nicely. The unprivileged process can now track the output of the command. An additional file descriptor is used to send the exit status to the unprivileged process.

Sounds quite complicated. Is it worth it?

Yes. It’s a layer of defense. This technique was popularized in OpenBSD. OpenSSH is one of the most well-known pieces of software that uses privilege separation. But many daemons in OpenBSD use the same technique. If the unprivileged child process is comprimised, it still cannot execute any privileged operations. The code running as root is small and should be easy to review.

Of course, Ding is written in Go. And many existing daemons using the privilege separation technique are written in C. C is more prone to abuse. A single buffer overflow can lead to compromise. Go programs would detect such problems. True, but as I said. It’s another layer of defense.

It’s a hostile internet out there. You never have enough protection.