Chrooting a DNS server on Solaris

Adam Shostack

Other DNS software

You may want to use other DNS software, rather than BIND, or use BIND's built-in chroot features. Dan Bernstien maintains a pacakge djbdns with a $500 reward for anyone who finds a security flaw in his code. He also maintains a list of other DNS software that you might want to consider.

Overview

In response to the ongoing problems with DNS, I choose to constrain its operations via chroot. This document lays out the steps needed to build, confine, and test a DNS server. This is somewhat Solaris specific.

Get the latest source and patches

The latest official source is available from The Internet Software Consortium. The Secnet patches are available from ftp.secnet.com/pub/patches/, as is a tar file of the applied patches.

Unpack and build bind.

All the normal install stuff. See the README & Install for bind.

Create a staging area

Make a clean space to test in. It will need dev etc usr and var directories. Each should be mode 755, owned by root. Files in bold are copied from the build of the dns server by hand. Other files are your standard DNS host versions.
  • dev is unpopulated for now.
  • etc
    • named.boot (as appropriate for dns server.)
    • netconfig
    • syslog.conf
  • usr
    • lib
      • ld.so.1
      • libdl.so.1
      • libnsl.so.1
      • libresolv.so.2
      • lib44bsd.a
      • libintl.so.1
      • libresolv.a
      • libsocket.so.1
      • libc.so.
      • libmp.so.
      • libresolv.so
      • libw.so.1
    • sbin
      • in.named
      • named-xfer
    • share/lib/zoneinfo/US/*
    • tmp is a link to ../var/tmp
    • ucblib is a link to lib
  • var
    • log Populate as directed in syslog.conf
    • named polpulate as appropriate for name server
    • run
    • tmp
In the real usr/sbin, you'll want to place:
  • in.named.reload
  • in.named.restart
  • in.ndc
Each will need to be modified with the true path to named, since you'll be using them from outside the chrooted area.

Test install

If you have multiple machines in a firewall or DMZ environment, you may wish to test on a machine other than your main server. If you're behind a packet filter, you can open port 53 (TCP and UCP) to your test host, and test that the new server works as expected.
Otherwise, you'll need to stop your real DNS server, and quickly test the new one. In the test area, make devices as follows:
mknod conslog c 21 0
mknod log c 21 5
mknod null c 13 2
mknod syscon c 0 0 
mknod tcp c 11 42
mknod udp c 11 41
mknod zero c 13 12
chmod 666 tcp udp
chmod 620 syscon
chgrp tty syscon
chgrp sys conslog null tcp udp zero
Test invoke the server:
cd $TESTDNS
chroot $TESTDNS usr/sbin/in.named (possibly with a -d option to debug)
The server should be able to start properly, use dig and nslookup to test it.

Install for real

Be sure to build the devices in the new partition. Make sure that the startup script invokes chroot.

Go back to the machine you tested on. Remove the entire test area. Be sure that the device copies are gone.

A word about Janus

I considered trying to run the DNS server under Janus, an experimental tool for constraining untrusted code from Berkeley. I chose not to because Janus runs only on Solaris, and I have DNS servers that are not running Solaris. The idea is pretty neat; control things at the system call layer on a per process basis. It requires more trust than chroot that the code is working correctly, but it lets you control things more granularly.

Running as root

If you have a lot of domains, you'll find yourself sending a SIGHUP to named on a regular basis. Since named closes its ports when this happens, it needs to rebind, and it needs privledge to do that. So it requires code mods to run as something other than root, and I expect they need to be fairly substantial. If your Operating system lets someone other than root bind to reserved ports, then you could use that mechanism, and simply have bind run with the appropriate user or group id.

Its also worth noting that code running as root can break out of a chroot "jail." I don't know of an egg to do this, but thats no guarantee that none exists. So what you're doing is protecting yourself a bit until the egg gets written. (An egg is the code that hatches to give you root; there are eggs at the core of buffer overflows.)