Making Root Unprivileged
As mentioned previously, Linux continues to emulate a privileged root user. This is done by perverting the capabilities behavior of exec() and setuid(). Briefly, if your effective userid is 0 when you execute a file, or if you execute a setuid root file, your permitted and effective sets are filled. If only your “real” userid is 0 (for instance, a root-owned process executes a setuid-nonroot file), only your permitted set is filled. When you change part of your userid from root to nonroot, your effective capability set is cleared. When you permanently change your userid from root to nonroot, your permitted set is cleared as well. And, if you switch your effective userid back to root, your permitted set is copied back into your effective set.
This behavior is controlled by a per-process set of securebits. One controls the setuid() behavior, and another controls the exec() behavior. They can be turned on using prctl(), and they can be locked such that neither the task nor its descendants can turn the bits back off.
In order to exploit POSIX capabilities fully, both kernel and userspace must be set up properly. The easiest and safest way to experiment with such core changes is to do so in a virtual machine. Although everything shown here could just as well be done on your native Linux installation, for simplicity, I assume you are installing a minimal, stock Fedora system under qemu or kvm.
My first working prototype of a rootless system was done on a Gentoo system. Ubuntu Intrepid and SLES11 should come with file capabilities enabled. However, Fedora 10 wins as being the most capability-ready distribution to date, so I use it for this demonstration. To get started, download a Fedora 10 DVD from download.fedoraproject.org (call the file f10.iso), then create a qemu hard disk image and boot kvm using:
# qemu-img create f10.img 6G # kvm -hda f10.img -cdrom f10.iso -m 512M -boot d
Then, proceed with the Fedora installation instructions. Make sure to install software development, and skip office and productivity tools for this image.
After rebooting, disable SELinux through the menu entries System→Administration→SELinux management. Change the top entry from Enforcing to Disabled, then reboot. Although there is no inherent reason why SELinux cannot be used with file capabilities, it does require some SELinux policy modifications.
Because you will be removing the root user's privilege, you'll want other users to receive ambient privileges at login. This is done using the pam_cap.so PAM module. To enable its use, add the line:
auth required pam_cap.so
to /etc/pam.d/system-auth. The order of these entries does matter, and improper order can prevent your entry from being used. I made it the second entry, after pam_env.so. Now, test by creating a user with some privilege:
# adduser -m netadmin # passwd netadmin # for f in /sbin/ifconfig /sbin/ip /sbin/route; do # setcap cap_net_admin=ei $f # done
The above creates user netadmin and sets his password, then adds the cap_net_admin capability to the inheritable and effective sets for three network configuration programs. If you now type ls /sbin/ifconfig, you'll notice the entry is marked in red. This is similar to how setuid binaries, such as /bin/ping, are marked, and it's a nice touch to let you easily tell which binaries ought to be treated with extra care or to detect mistaken privilege leakage.
You also must create the /etc/security/capability.conf file, which pam_cap.so will consult on each login. The file should contain:
cap_net_admin netadmin none *
The first line says that when user netadmin logs in, pam_cap.so should add the cap_net_admin capability to pI for its login shell. The second line, which is very important, says that everyone else (*) should receive no capabilities. Now, log in as user netadmin and play with the network:
hallyn@kvm# su - netadmin netadmin@kvm# ifconfig eth0 down
Success! You just downed the network as a nonroot user.
Now you're ready to make root unprivileged. As a first step, you will just restrict network logins over SSH. To make this as easy as possible, simply start sshd through a wrapper that sets and locks all securebits before calling the real sshd. The source for the wrapper is shown in Listing 1.