File capabilities offer a secure alternative to SUID executables, but can be a little confusing at first.
We all know that SUID binaries are bad news from a security perspective. Fortunately, if your application requires some limited privileges then there is a better way known as capabilities.
To save you reading the article above in detail, in essence this allows processes which start as root, and hence have permission to do anything, to retain certain limited abilities chosen from this set when they drop privileges and run as a standard user. This means that if an attacker manages to compromise the process via a buffer overrun or similar exploit, they can’t take advantage of anything except the specific minimal privileges that the process actually needs.
This is great for services, which are typically always run as root, but what
about command-line utilities? Fortunately this is catered for as well,
provided you have the correct utilities installed. If you’re on Ubuntu you’ll
need the libcap2-bin
package, for example. You’ll also need to be running a
non-archaic kernel (anything since 2.6.24).
These features allow you to associate capabilities with executables, in a
similar way to setting the SUID bit but only for the specific capabilities set.
The setcap
utility is used to add and remove capabilities from a file.
The first step is to select the capabilities you need. For the purposes of this
blog, I’ll assume there’s a network diagnostic tool called tracewalk
which
needs to be able to use raw sockets. This would typically require the
application to be run as root, but consulting the list it turns
out that only the CAP_NET_RAW
capability is required.
Assuming you’re in the directory where the tracewalk
binary is located, you
can add this capability as follows:
sudo setcap cap_net_raw=eip tracewalk
For now ignore that =eip
suffix on the capability, I’ll explain that in a
second. Note that the capability name is lowercase. You can now verify that
you’ve set the capabilities properly with:
setcap -v cap_new_raw=eip tracewalk
Or you can recover a list of all capabilities set on a given executable:
getcap tracewalk
For reference, you can also remove all capabilities from an executable with:
setcap -r tracewalk
At this point you should be able to run the executable as an unprivileged user and it should have the ability to deal with raw sockets, but none of the other privileges that the root user has.
So, what’s the meaning of the strange =eip
suffix? This requires a brief
digression into the nature of capabilities. Each process has three sets of
capabilities — inheritable, permitted and effective:
CAP_NET_RAW
is in
the effective set.fork()
or clone()
operation the child process is always given a duplicate of the capabilities of
the parent process, since at this point it’s still running the same executable.
The inheritable set is used when an exec()
(or similar) is called to
replace the running executable with another. At this point the permitted set
of the process is masked with the inheritable set to obtain the permitted
set that will be used for the new process.So, the setcap
utility allows us to add capabilities to these three sets
independently for a given executable. Note that the meaning of the groups is
interpreted slightly different for file permissions, however:
When specifying capabilities via setcap
the three letters e
, i
and p
refer to the effective, inhertable and pemitted sets respectively. So the
earlier specification:
sudo setcap cap_net_raw=eip tracewalk
… specifies that the CAP_NET_RAW
capability should be added to the
permitted and inheritable sets and that the effective bit should also be
set. This will replace any previously set capabilities on the file. To set
multiple capabilities, use a comma-separated list:
sudo setcap cap_net_admin,cap_net_raw=eip tracewalk
The capabilities man page discusses this all in more detail, but hopefully this post has demystified things slightly. The only remaining things to mention are a few caveats and gotchas.
Firstly, file capabilities don’t work with symlinks — you have to apply them to the binary itself (i.e. the target of the symlink).
Secondly, they don’t work with interpreted scripts. For example, if you have a
Python script that you’d like to assign a capability to, you have to assign it
to the Python interpreter itself. Obviously this is a potential security issue
because then all scripts run with that interpreter will have the specified
capability, although it’s still significantly better than making it SUID. The
most common workaround appears to be to write a separate executable in C or
similar which can perform the required operations and invoke that from within
the script. This is similar to the approach used by Wireshark
which uses the binary /usr/bin/dumpcap
to perform privileged operations:
$ getcap /usr/bin/dumpcap
/usr/bin/dumpcap = cap_net_admin,cap_net_raw+eip
Thirdly, file capabilities are disabled if you use the LD_LIBRARY_PATH
environment variable for hopefully obvious security reasons1. The same
also applies to LD_PRELOAD
as far as I’m aware.
Because an attacker could obviously subvert one of the standard
libraries and use LD_LIBRARY_PATH
to cause their subverted
library to be invoked in preference to the system one, and hence
have their own arbitrary code executed with the same privileges
as the calling application. ↩