Keyboard meet head

Where my head meets keyboard..

Sudo just a bit

The man page says that sudo allows a permitted user to execute a command as the superuser or another user, as specified by the security policy. However most of the sudo use that I've seen/done is really a privilege escalation with switched user being just a consequence of that. Even the famous XKCD 🥪 webcomic shows sudo being used in this way. Most of the time this small difference does not really matter, but sometimes it's really inconvenient.

This is mostly a note I've written for my self so that I can quickly recall how to sudo when I really only want to do something without the su part. Let's have a look at another less popular but sometimes quite a bit more appropriate option.

What is the problem anyway?🔗

A very common reason to use sudo is to access files that are only readable by root. Making backups is a good example:

sudo rustic backup /etc

This works and backups are made as expected, however one side effect is that by default rustic makes a local cache in ~/.cache/rustic. What that means is that other rustic commands (ie. snapshots, repoionfo, ...) that don't need to access local files (and thus are hopefully not being executed as root) can't use that cache. We could configure some common directory in the rustic config, however the cache files would still have the wrong owner.

So we end up with two different caches for the same repository. This is quite minor issue in practice in this specific case, however it does illustrate the problem well enough.

Capabilities to the rescue🔗

What really happens in the background is that the command ran by sudo is so called privileged process (whose effective user ID is 0) and for these processes all kernel permission checks are bypassed. This is how root is able to read all files when doing the backup. However we don't only have this all-or-nothing option available. Linux also provides us with more granular control in the form of capabilities.

A thread can have one or more of these capabilities without bypassing all checks, so we can provide just enough access to do its work. But most conveniently for our use case, the effective user ID does not have to be 0 - we can run the command as any user and only provide the capabilities required.

For our backup there's one capability that's perfect for what we need:

CAP_DAC_READ_SEARCH: Bypass file read permission checks and directory read and execute permission checks

This will let us read any files and browse contents of all directories without - for example - allowing execution of any binaries that the user can't normally execute or writing to any files. Exactly what we need.

Setting capabilities with capsh🔗

The capsh utility is relatively simple utility that we can use and it's likely already present on your system. Let's have a look at how it can be used:

1sudo -E \
2 capsh \
3 --user=${USER} \
4 --inh=CAP_DAC_READ_SEARCH \
5 --addamb=CAP_DAC_READ_SEARCH \
6 -- -c "rustic backup ${directory}"

Well that's a lot. Let's go line by line:

  1. Run all of this with sudo to escalate the privileges (more on that bellow) and -E to preserve the environment variables as we will be dropping back to our user anyways.
  2. Run capsh with some extra flags:
  3. Assume identity of an user. In most cases the $USER variable is already set to your current user name. (So a user name before sudo is executed) In other words, switch back to current user from root that's provided by sudo.
  4. Make CAP_DAC_READ_SEARCH inheritable, so that we can pass it to child process.
  5. Add the above capability as ambient capability for the process.
  6. Anything after -- is passed as a parameter to /bin/bash executed by capsh. In this case we tell it to run our backup.

"Inheritable", "Ambient", oh my! What is all this?🔗

It all sounds a bit complicated, but let's just look at man page:

Inheritable: This is a set of capabilities preserved across an execve. Inheritable capabilities remain inheritable when executing any program, and inheritable capabilities are added to the permitted set when executing a program that has the corresponding bits set in the file inheritable set.

Aha, so these are the capabilities that can be inherited by child process. This allows capsh to pass it on to the /bin/bash it executes which will then pass it down to rustic. Let's continue reading:

Because inheritable capabilities are not generally preserved across execve when running as a non-root user, applications that wish to run helper programs with elevated capabilities should consider using ambient capabilities

Right, this makes sense. The capability is inheritable, but that does not actually mean the child process will always inherit it. And in fact for non-root users the capability generally won't be inherited. So we need something more in our setup to make it all work. As suggested, let's look at ambient capabilities:

This is a set of capabilities that are preserved across an execve of a program that is not privileged.

Ah okay, this solves our problem then. Do we even need to make it inheritable then? Well yes, because:

no capability can ever be ambient if it is not both permitted and inheritable

💡 Aha, now it makes sense. We essentially tell capsh to drop back to previous user, but make sure the CAP_DAC_READ_SEARCH capability is passed to the command and that all of its child processes will automatically inherit it.

Which means that following command will drop us to a shell, that runs under current user, but any command executed within the shell will inherit the super power of being able to access any file:

capsh --user=${USER} --inh=CAP_DAC_READ_SEARCH --addamb=CAP_DAC_READ_SEARCH -- -c bash

Let's try that:

# Can we see files in root home directory?
$ ls -la /root/.bash_history
-rw------- 1 root root 22896 Mar 18 13:25 /root/.bash_history

# Should anyone other than root be able to see the files?
$ stat /root | grep root
  File: /root
Access: (0700/drwx------)  Uid: (    0/    root)   Gid: (    0/    root)

# Are we root?
$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)

Neat, nobody with super powers!

A note about setcap🔗

Another interesting option is to use setcap to assign capabilities to executable. There's example setup for restic to use this for root-less backups, however it's worth keeping in mind that anyone able to execute the binary will also get the capability. So this is more permanent setup while capsh is perhaps more suitable for more ad-hoc usage or in situations where we don't want to have the capability every time a command is executed.

Fin🔗

Overall capabilities look like pretty handy if maybe a bit unpolished way of providing limited access to powerful features. There's neat project called RootAsRole, that's trying to bring this concept further. It's definitely on my watchlist. In general the UX side of things still isn't great in my opinion. But at least there's a choice and we aren't limited to UID=0.

There's no comment system on this page, but I do accept feedback. If you are interested in commenting, send me a message and I may publish your comments, in edited form.