Linux Capabilities: Hardening Linux binaries by removing setuid

Linux Capabilities

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.

Root permissions

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:

CAP_NET_RAW
* use RAW and PACKET sockets;
* bind to any address for transparent proxying.

Great, this seems exactly what we want!

Capability sets

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.

Setting capabilities

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 (149.210.134.182) 56(84) bytes of data.
64 bytes from cisofy.com (149.210.134.182): icmp_seq=1 ttl=58 time=10.5 ms
64 bytes from cisofy.com (149.210.134.182): 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.

One more thing...

Keep learning

So you are interested in Linux security? Join the Linux Security Expert training program, a practical and lab-based training ground. For those who want to become (or stay) a Linux security expert.

See training package




Lynis Enterprise screenshot to help with system hardeningSecurity scanning with Lynis and Lynis Enterprise

Run automated security scans and increase your defenses. Lynis is an open source security tool to perform in-depth audits. It helps with system hardening, vulnerability discovery, and compliance.


Download

16 comments

  • quascquasc

    I know this is probably a stupid question, but I’ll ask it anyway: why doesn’t the ping command work anyway without if there’s read and execute for others?

    Reply
    • With only those permissions it would not be able to open a network socket. Or in other words, it requires root permissions to use just a little part of the Linux kernel (the code to transmit/receive a network packet).

      Reply
  • quascquasc

    If I see that you’re so kind, I’ll ask another question: how come you cannot get rid of any special permissions (except 1, meaning t, the sticky bit) with binary commands?
    I’ve tested it both in centos and ubuntu.
    chmod 4777 dir will set setuid on dir.
    chmod 0777 dir has absolutely no effect.
    (same thing is happening start with with 2777 or 6777 – so any even number; the odd number (1) can be cleared with binary). The only way around it is chmod -s dir.
    How come?

    Reply
    • That is intentional behavior of the user tool. This is to avoid “chmod 777 dir” removing actual an existing setuid/sticky bit. Each operating system acts a little bit different regarding this. On Linux it is common to use “chmod u-s dir” to remove the setuid bit.

      Reply
  • quascquasc

    I’ve been asking this question on linux forums and I’ve got TONS of text of replies. You wouldn’t believe it. None of it made as much sense as what you’ve just said is a few words about unintentional removal of a special permission :) That’s what I was looking for, the exact rationale behind not being able to remove it with binary chmod. Thanks!

    Reply
  • quascquasc

    But now that I think again, why isn’t it the same with the sticky bit? Why can you remove the sticky bit with binary chmod? (Centos 6.6, Ubuntu, I bet in many other distros)

    Reply
    • Actually, while the man page of chmod specifically states it for setuid/setgid that you can not remove it with numerical values, in my case that works as well. So for both the sticky and setuid bit, they get dropped. In other words, there are differences between systems in protecting these special bits for accidental removal, but at the same time the man page might be incorrect as well :)

      Reply
  • quascquasc

    I use Centos 6.6 (and I’ve also tested it in Xubuntu 14.04) and I cannot change setuit/gid with binary form. Someone else, who uses Centos 5.11, told me he can. It might have something to do with newer distros too. It’s really awkard, though, especially for a beginner, who craves consistency, at least between the same distros. But then again, if I think of systemd…

    Reply
    • Totally understandable you get confused. This is exactly the reason we created Lynis for auditing security of Unix/Linux systems, as there are so many different ways. One distribution may use useradd to create a new user, while others use adduser. Best of all, even if two different Linux distros both use useradd, the available parameters are different.. Anyways, at least you know the reason behind the behavior and most people won’t even encounter it :)

      Reply
  • MattMatt

    This should be read in conjunction with https://forums.grsecurity.net/viewtopic.php?f=7&t=2522&sid=c6fbcf62fd5d3472562540a7e608ce4e#p10271

    The specific example here limits the damage that can be done by compromising ping, but many other programs require capabilities that are more or less root equivalent.

    Reply
  • Good article, clear explanation. I have just used it to enable ping for normal users on some of my Debian 8 systems. Some even more recent distributions appear to have it set already, such as the May 10th 2016 release of Raspbian.

    Reply
  • Huaixi ChenHuaixi Chen

    Hi Michael,

    I am using system with kernel 2.6.32-504.23.4.el6.x86_64. The ‘ping’ example only works by using “setcap cap_net_raw+ep /bin/ping”. Without effective capacities set, it still complains “ping: icmp open socket: Operation not permitted”.

    regards,
    Huaixi

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.