Linux Capabilities: Hardening Linux binaries by removing setuid
Hardening Linux binaries by removing setuid
Normally Unix based systems use two kind of processes: privileged and unprivileged. The first category is usually used for administrative purposes, like starting and stopping other processes, tuning the kernel and opening sockets.
The command ping is a great example why even small programs needs root permissions. In a first glance you might consider this tool to be simple: send a package to a host and see if it responds. The truth is that a network socket needs to be opened, to send an ICMP package.
Let’s have a look at the ping binary:
$ ls -l /bin/ping -rwsr-xr-x 1 root root 44168 May 7 23:51 /bin/ping
So the binary itself is small in size. It actually turns red in our console. This is due to the setuid bit, which can be seen due to the small “s” (where normally the “x” is).
Next step is to remove the setuid bit from the binary and see the result:
$ sudo chmod u-s /bin/ping$ ls -l /bin/ping -rwxr-xr-x 1 root root 44168 May 7 23:51 /bin/ping
Removing the setuid bit has turned the binary into green, which is the common color for executable files and binaries.
Now let’s try using the ping command:
$ ping cisofy.com ping: icmp open socket: Operation not permitted
So that clearly shows that our beloved ping command is not longer working. For a system without normal users this would be great regarding hardening. The less setuid binaries, the better.
In this case we assume we want normal users to still use the ping command. So let’s add a capability which allows to open up a network based socket. Look in the man page we find the cap_net_raw capability:
* use RAW and PACKET sockets;
* bind to any address for transparent proxying.
Great, this seems exactly what we want!
Now the challenge is understanding how to apply the capability the proper way. Just slapping the capability onto the binary is not enough, we will also need to define how it applies. This is done via a capability sets.
Each process thread has three capability sets, which may contain some, all or none of the following sets. From the man pages:
Effective – the capabilities used by the kernel to perform permission checks for the thread.
Permitted – the capabilities that the thread may assume (i.e., a limiting superset for the effective and inheritable sets). If a thread drops a capability from its permitted set, it can never re-acquire that capability (unless it exec()s a set-user-ID-root program).
Inheritable – the capabilities preserved across an execve(2). A child created via fork(2) inherits copies of its parent’s capability sets. See below for a discussion of the treatment of capabilities during exec(). Using capset(2), a thread may manipulate its own capability sets, or, if it has the CAP_SETPCAP capability, those of a thread in another process.
The effective set is needed when performing a specific system call in which it needs to have a specific capability.
So this means for a normal binary, which will not create child processes, the permitted will do. This means at best it will be able to use the capability. It may be a limited superset of what inheritable and effective will provide. For processes which fork other processes, we might need to inherit the capabilities. In that case, use the inheritable set.
So let’s use this knowledge and apply it to the binary:
$ sudo setcap cap_net_raw+p /bin/ping $ sudo getcap /bin/ping /bin/ping = cap_net_raw+p
We set the capability with setcap and tell it to set the permitted set. Now we test again and the ping command works like it did before.
$ ping cisofy.com PING cisofy.com (18.104.22.168) 56(84) bytes of data. 64 bytes from cisofy.com (22.214.171.124): icmp_seq=1 ttl=58 time=10.5 ms 64 bytes from cisofy.com (126.96.36.199): icmp_seq=2 ttl=58 time=11.8 ms
Why not use setuid?
Usually it makes sense to allow a trusted binary to use root permissions to execute. The unfortunate thing with software is that it may contain bugs. So even the smallest mistake with a setuid binary may result in total compromise.
Using capabilities gives a binary root permissions for only a limited set of systems calls. So even if there would be a software leak, it may not even be abused. For example the ping command in this article. This command would not be able to change ownership of a file, with the chown system call, where a setuid binary would have been.