Originally Published: Wednesday, 20 October 1999 Author: Jim Hewlett
Published to: enchance_articles_security/Advanced Security Articles Page: 1/1 - [Printable]

Linux Capabilites
By Jim Hewlett
October 20 - 26

There will be undiscovered holes in software, and we want to minimize the impact a breach in a daemon or service will have on your machine. This idea of hardening your system is what this article focuses on. Traditional unix has the root user which can do anything, read, write, whatever...

   Page 1 of 1  

Linux Capabilities

There will be undiscovered holes in software, and we want to minimize the impact a breach in a daemon or service will have on your machine. This idea of hardening your system is what this article focuses on. Traditional unix has the root user which can do anything, read, write, whatever. You know the drill. Obviously this is a problem if your machine is cracked, the attacker now has control over your machine. Let's say, for example, your car breaks down and you have to take it in to the local garage to have it fixed. You would give the mechanic the keys to your car only. It would be silly to also give the mechanic the keys to your house, mailbox, job, or whatever else. Traditional root user exit stage left, Linux Capabilities enter stage right.
  • The How and Why

    Linux Capabilities create the ability to give processes specific capabilities to do only the job required and nothing more. In other words, splitting the root power up into little pieces. Take a peek at /usr/include/linux/capability.h for a list of caps. In a real capabilities only model, uid 0 is just a normal user. There are 3 capability 'sets'

    Inheritable, effective, and permitted.

    Permitted contains everything that the current process is allowed to do. Effective contains everything that the current process can currently do. Inheritable tells us which processes we will allow to gain caps from us. Each of those sets may contain multiple capabilities. If you're permitted to do everything, but your effective set is empty, you can't really do anything special.

    However, you as a process can set your effective set to include anything in your permitted set. It's like an on/off set. As example lets use named. I set cap_net_bind_service+ep and no i set. Now named itself can bind to low ports (cap_net_bind_service), and that is set to the effective and permitted (+ep). Now, anything exec()'d by named has no privs at all. This is because we did not set the inheritable (remember, that's i).

    In a capabilities only model, init is started with =eip, which means it has a full set of everything in all 3 sets. From there you need a capabilities aware init to pass caps to what needs them, or file system information to do it. Since neither exist right now, the kernel has some compatibility mode stuff in it (remember that Linux Capabilities are still a work in progress).

    When exec() is called, if your effective id is 0 (aka suid, or actually root) your effective and permitted set is set to what your inheritable set was when you called exec(). If your effective id is not 0, your effective and permitted sets are cleared.

    Example:

    ~[zeppelin(imagine-dragon)1]$: ps PID TTY TIME CMD 23540 tty5 00:00:00 bash 23528 tty5 00:00:00 ps

    (shell is pid 23540)

    This is a normal user:
    ~[zeppelin(imagine-dragon)3]$: /sbin/getpcaps 23540 Capabilities for `23540': =i Capabilities for `(null)': =i
    The following is normal for root. I had a full inheritable set, so when euid==0 is true, the kernel set my effective and permitted sets to match my inheritable:
    ~[zeppelin(imagine-dragon)4]$: su - [~(root@imagine-dragon)1]#: /sbin/getpcaps 23540 Capabilities for `23540': =i Capabilities for `(null)': =eip
    Exit the root shell, rerun command:
    ~[zeppelin(imagine-dragon)5]$: /sbin/getpcaps 23540 Capabilities for `23540': =i Capabilities for `(null)': =i
    Now I don't have anything in my permitted set, so I can't change my own caps. Swap over to root console:
    [~(root@imagine-dragon)120]#: setpcaps cap_setuid,cap_setgid=i 23540 [caps set to:= cap_setgid,cap_setuid+i] [caps set on 23540]
    Back to the normal user and rerun getpcaps:
    ~[zeppelin(imagine-dragon)6]$: /sbin/getpcaps 23540 Capabilities for `23540': = cap_setgid,cap_setuid+i Capabilities for `(null)': = cap_setgid,cap_setuid+i
    You can restrict caps like that. So I can't actually setuid or setgid as zeppelin now, because it's not in my effective set. I'm saying only inherit those 2 capabilities. now, I su:
    ~[zeppelin(imagine-dragon)7]$: su - su: fchown to STDIN: Operation not permitted /dev/tty5: Operation not permitted

    [~(root@imagine-dragon)1]#: /sbin/getpcaps 23540 Capabilities for `23540': = cap_setgid,cap_setuid+i Capabilities for `(null)': = cap_setgid,cap_setuid+eip

    Kernel only could set what I could inherit. Right now I have a root shell, but I can only set uid/gid.

    With every good thing, there is something that isn't so. Compatibility mode has some quirks.

    [~(root@imagine-dragon)121]#: setpcaps =eip 23540 [caps set to:=eip] [caps set on 23540]
    I set the normal user's shell to have all capabilities and that shell can do anything now. Not so! Consider the following:
    ~[zeppelin(imagine-dragon)8]$: /sbin/getpcaps 23540 Capabilities for `23540': =eip Capabilities for `(null)': =i
    The second zeppelin runs an external program, compatibility mode drops the caps. So the question is, how can I cap named? If I setuid first then I lose the caps to set my caps. If I set my caps and then setuid, I have to give myself the setuid cap, and then setuid and lose my caps. That's where our good friend Mister Wizard comes in (no, not the Nickelodeon wizard. Although, he's pretty neat too.). He has modified libcap's execcap program to do a nifty trick. It spawns a root process, which therefore has all caps. The main process setuid's and therefore loses all caps. The spawned process sets the main process's caps back to god mode and dies. The main process now has god caps, under a normal user. Then the main process drops its own caps to what they should be. All his mods set the inheritable set to null, so that if someone breaches named (In my examples it is chrooted. See Peter Clark's article on doing this), they don't have:
    1. permission to chroot
    2. a shell to run, and even if they could spawn a program,
    3. zero caps (and suid programs have no effect).
    One thing to remember with this is if someone breaches named and there's bash somewhere in the chroot and somehow ends up as root in a classical unix system nearly everything is owned by root. The shell will have zero caps (due to the null inheritable set) but will still be able to do other basic things, such as read and change files. /etc/shadow for example. Standard rules apply here. This is why we don't just drop caps on daemons and let them run as root. Also, we'll want to have each daemon run as its own user. Having different processes with the same UID/GID is bad because if one is compromised, the user can change config files or any number of different things of other process. Maxing out resource limits between the same UID/GID is another reason to run different user/group.

  • Time to put on your C hat (the one with the lil' propellers on top)

    Now some code to show how the modification is done to our example above (named). The + (plus) signs are what was added to the existing code. Don't forget to add #include <sys/capability.h> to the top of the file which contains the main() function (ns_main.c for named) with the other #includes and use -lcap when linking

    Named binds to a privileged port, so it needs cap_net_bind_service. In named's main procedure it chroots to whatever dir you gave it. Then it does setuid() and setgid() and continues to run. Let's modify where it does setuid and setgid.

    These variables are defined for later use:

    + int uidtoset=getuid(),gidtoset=getgid(); + cap_t cap_d; + cap_value_t cap_values[]={ + CAP_NET_BIND_SERVICE + };

    Now call libcap's init proc:

    + cap_d = cap_init(); + if (!cap_d) { + ns_panic(ns_log_security, 1, "Could not allocate capabilities struct: %s", + strerror(errno)); + }

    Now we figure out what uid and gid we need to set to (different for each program. group_ and user_ stuff is named specific.):

    + if (group_name != NULL) { + gidtoset=group_id; + ns_info(ns_log_security, "group = %s", group_name); + } + if (user_name != NULL) { + if (getuid() == 0 && initgroups(user_name, group_id) < 0) + ns_panic(ns_log_security, 1, "initgroups(%s, %d): %s", + user_name, (int)group_id, strerror(errno)); + endgrent(); + endpwent(); + uidtoset=user_id; + ns_info(ns_log_security, "user = %s", user_name); + }

    Now that a proper uidtoset and gidtoset are setup and all the supplemental groups are set, we get to the fun stuff. Starting with setting the structure to null for inherited, effective and permitted:

    + cap_clear(cap_d);

    Set CAP_NET_BIND_SERVICE for permitted and effective (the 1 value that was defined in cap_values array):

    + cap_set_flag(cap_d, CAP_PERMITTED, 1, cap_values, CAP_SET); + cap_set_flag(cap_d, CAP_EFFECTIVE, 1, cap_values, CAP_SET);

    Now for the nifty function (cap_set_me_up) Mister Wizard made that gets around losing caps when you change UID/GID which was explained earlier:

    + ns_info(ns_log_security, "setting capabilities"); + if (cap_set_me_up(uidtoset,gidtoset,cap_d)) { + ns_panic(ns_log_security, 1, "cap_set_me_up failed: %s", + strerror(errno)); + }

    Now named capped, time to clean up:

    + ns_info(ns_log_security, "capability struct set"); + cap_free(&cap_d); + }

    Well there you have it. Named should compile and run capped. Test it out with getpcaps (comes with libcap).

    (from ps):

    named 30479 0.0 1.4 2988 1876 ? S Oct02 0:12 named -u named -g named -t /home2/services/named -s

    You will see something like:

    boboshrimps(root):~# getpcaps 30479 Capabilities for `30479': = cap_net_bind_service+ep Capabilities for `(null)': =eip

    You'll need Mister Wizard's modified version to get around the cap quirk you read about earlier. Get it here. If there are any problems reaching the site, let me know.

    One last modification needs to be done before all of this will actually work. This involves some kernel changes. Yes, I know. I can hear you all screaming about your uptimes. Change in linux/include/linux/capability.h:

    #define CAP_INIT_EFF_SET to_cap_t(~0 & ~CAP_TO_MASK(CAP_SETPCAP)) #define CAP_INIT_INH_SET to_cap_t(~0 & ~CAP_TO_MASK(CAP_SETPCAP))

    to:

    #define CAP_INIT_EFF_SET { ~0 } #define CAP_INIT_INH_SET { ~0 }

    Thus giving init a full set of caps. Compile and reboot. Currently for Linux 2.2 we have to do all these mods to daemons/whatever in order to use caps. However, Linux may eventually have file system support, so there will be no need for hacking up code.

    Lastly, but most importantly, I'd like to thank Mister Wizard for letting me borrow his gray matter.

    Comments, suggestions, or additions are welcome. Just point them to jh@linux.com

    Jim Hewlett is a freelance UNIX Systems Administrator with over 4 years of experience maintaining machines and networks throughout the south-east.





   Page 1 of 1