Why didn't that work?

This article is a semi random list intended to help explain why some things related to capabilities don't work when you try them. Typically, they will be explained in terms of an example that uses capsh, setcap, getcap and captree to execute. (File a bug if you want something added to the list; see the navigation helper at the top-left of this page.)

[As of the time of writing the examples in this article require the libcap-2.57+ libraries and binaries to execute.]

Raising Inheritable bits

Ordinarily, a process can add any Inheritable Flag Value it already has in its Permitted Flag. That is, this works  (--strict was added in libcap-2.57 to suppress some silent fix-ups that capsh does to help make things easier when using --inh, --drop and --caps):

$ sudo capsh --strict --caps="cap_setuid=p" --current \

  --inh=cap_setuid --current 

while this fails:

$ sudo capsh --strict --caps="cap_setuid=p" --current \

  --inh=cap_chown --current

Raising any other Values is considered a privileged operation, and requires CAP_SETPCAP to be Effective. For example, this works:

$ sudo capsh --strict --caps="cap_setpcap=ep" --current \

  --inh=cap_setpcap,cap_setuid --current --drop=cap_setpcap --current

yet this fails:

$ sudo capsh --strict --caps="cap_setpcap=p" --current \

  --inh=cap_setpcap,cap_setuid --current --drop=cap_setpcap --current

This next two fail because CAP_SETPCAP's reach is bounded by the current Bounding vector:

$ sudo capsh --strict --caps="cap_setpcap=ep" --current \

  --drop=cap_setpcap --current --inh=cap_setpcap,cap_setuid --current

$ sudo capsh --strict --caps="cap_setpcap=ep" --current \

  --drop=cap_setuid --current --inh=cap_setpcap,cap_setuid --current

Setting invalid capabilities

File capabilities look as if they a structured like process capabilities, especially if you browse them via the text API (cap_get_text() etc). However, they obey subtly different constraints.

Consider how this works:

$ sudo capsh --strict --caps="cap_setuid=pi cap_setgid+pe" --current 

Current: cap_setuid=ip cap_setgid+ep

Current IAB: cap_setuid

Whereas this does not:

$ sudo capsh --strict --caps="cap_setuid=pe cap_setgid+ie" --current 

Unable to set capabilities [--caps=cap_setuid=pe cap_setgid+ie]

It is not the capsh program that is complaining, but the kernel rejecting setting a capability state where the Effective flag (e) contains values not also present in the Permitted flag (p). In this case, cap_setgid is not Permitted so it can't be Effective.

File capabilities obey slightly different rules. The filesystem representation of capabilities is capable of representing arbitrary Permitted and Inheritable flags (p and i), but the Effective (e) flag is much more limited in scope. It holds a single Boolean value: true or false. When setting a file capability, the richer in memory cap_t value can hold values not expressible in the format supported by the filesystem. The setcap utility tries to look out for situations where the user is requesting something invalid as a file system capability.

This works:

$ sudo setcap "cap_setuid=i cap_setgid+p" /bin/true

$ getcap /bin/true

/bin/true cap_setuid=i cap_setgid+p

As does this:

$ sudo setcap "cap_setuid=ie cap_setgid+pe" /bin/true

$ getcap /bin/true

/bin/true cap_setuid=ei cap_setgid+ep

But, this does not work out as expected:

$ sudo setcap "cap_setuid=i cap_setgid+pe" /bin/true 

NOTE: Under Linux, effective file capabilities must either be empty, or

      exactly match the union of selected permitted and inheritable bits.

Failed to set capabilities on file `/bin/true' (Invalid argument)

The note in the output is pointing out an aspect of the single Effective bit that needs to be understood. A pre-2.70 exception to this protection in setcap is attempting something like this:

$ sudo setcap "cap_setuid=e cap_setgid+pe" /bin/true

$ getcap /bin/true

/bin/true cap_setgid=ep

Which has silently dropped the cap_setuid=e. Allowing an attempt like this to succeed in setting something other than the requested value is considered a bug: setcap (from libcap 2.70+) will generate this error:

$ sudo setcap "cap_setuid=e cap_setgid+pe" /bin/true 

Error: under Linux, effective file capabilities must either be empty, or

       exactly match the union of selected permitted and inheritable bits.

Note, clean up after trying the above:

$ sudo setcap -r /bin/true 

$ getcap -v /bin/true

/bin/true

Raising Ambient bits

Ambient capabilities can only be raised while they are also raised in the Inheritable and Permitted process capability flags.

This works:

$ sudo capsh --strict --caps="cap_setuid=pi" --current --addamb=cap_setuid --current

These fail:

$ sudo capsh --strict --caps="cap_setuid=i" --current --addamb=cap_setuid --current

$ sudo capsh --strict --caps="cap_setuid=p" --current --addamb=cap_setuid --current

Also, Ambient capabilities are fragile with respect to those Flags: if you lower an Inheritable or Permitted Value, then that Ambient Value is lost to the process:

$ sudo capsh --strict --caps="cap_setuid=pi" --addamb="cap_setuid" --current --inh= --current

$ sudo capsh --strict --caps="cap_setuid=pi" --addamb="cap_setuid" --current --caps="cap_setuid=i" --current

Ambient capabilities are fully reset (but Inheritable Process Capabilities are not, since these are how POSIX.1e inheritance reacts to actual File Inheritable Capabilities) when a process executes a binary endowed with a File Capability (even an empty one):

$ cp $(which getpcaps) ./test-getpcaps

$ sudo setcap = ./test-getpcaps

$ sudo capsh --user=$(whoami) --iab='^cap_setuid' --current -- -c 'exec ./test-getpcaps $$'

To the greatest extent, in the absence of a file capability, Ambient Capabilities act like File Inheritable Capabilities at execution time.

Ambient capabilities are not blocked by the Bounding set:

$ sudo capsh --user=$(whoami) --caps="cap_setpcap=ep cap_setuid+p" \

  --current \

  --iab='!^cap_setuid,!%cap_setpcap' --current \

  --addamb=cap_setpcap == --current

When changing the UID of a process, if the setuid-fixup mechanism is invoked, the ambient capability vector is reset. That is, if the initial user is root (UID=0) and argument --uid or --user is used to change the process to some other user identity, the kernel will likely drop any raised Ambient bits. This dropping can be prevented if the SECBIT_NO_SETUID_FIXUP bit (Octal 0004) is raised for the process. However, this can also be avoided if the --user change is performed before raising ambient bits.

These two preserve the Ambient bit:

sudo capsh --iab='^cap_perfmon' --secbits=0004 --user=$(whoami) --current

sudo capsh --user=$(whoami) --iab='^cap_perfmon' --current

This one does not preserve the Ambient bit because the default, HYBRID capability mode, will invoke the setuid fixup mechanism:

sudo capsh --iab='^cap_perfmon' --user=$(whoami) --current


Quirks of hybrid inheritance

The root user does not experience file capabilities the same way other users do. Consider this:

$ cp $(which capsh) $(whoami)-capsh

$ sudo setcap cap_chown=pe $(whoami)-capsh

$ ./$(whoami)-capsh --current

Current: cap_chown=ep

Current IAB: 

$ sudo ./$(whoami)-capsh --current

Current: =ep

Current IAB: 

That is, root does not execute the file paying any attention to its file-configured capabilities. Instead, its rootness preempts the detail of the file's own capabilities.

Note: this wasn't always the case for HYBRID mode. The kernel developers, however, thought this behavior was less-surprising so they reordered the preemption from the explicit file capability preempting to the being-root part having dominance.

One protective residue of the file capability, however, (introduced to address the first legacy support snafu with root emulation via capabilities) is that the root user is still protected by the kernel when executing a legacy binary with insufficient privilege. Consider this:

$ sudo ./$(whoami)-capsh --drop=cap_chown == --current

execve './morgan-capsh' failed!

What the kernel is doing here is noticing that the binary has been configured with a file capability to require the CAP_CHOWN Permitted flag capability, which (==) is being denied by the Bounding vector. Since the binary is considered legacy (it has its Effective file capability raised), the kernel believes the binary has no way to recognize it is insufficiently privileged while running... To protect the system from insufficiently privileged execution, it denies the program permission to re-exec*() itself.

A, perhaps, unexpected quirk of the default inheritance is how root obtains capabilities. At exec*() time, in the default (HYBRID) mode, the kernel force grants all available capabilities if the user is [E]UID=0 (aka root). 

Since the kernel default process state is not to grant any Inheritable capabilities at all (these are only ever raised by user processes), it has lead to the widely held misconception that you can fully block individual capabilities to root by engaging the Bounding set:

$ sudo capsh --drop=cap_setuid --current == --current

Current: =ep

Current IAB: !cap_setuid

Current: =ep cap_setuid-ep

Current IAB: !cap_setuid

Here, we disable CAP_SETUID by dropping it from the bounding set and after exec*() (the == argument) we observe that the root running process is not quite as privileged.

However, should processes execute in an environment where the Inheritable set is not lowered the bounding set has no blocking effect on root's ability to raise all capabilities:

$ sudo capsh --inh=all --drop=cap_setuid --current == --current

Current: =eip

Current IAB: cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,!%cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read

Current: =eip

Current IAB: cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,!%cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read

Beware of sudo and other PAM enabled apps

This took some time to debug (despite authoring a lot of the code involved)! In general, binaries tend not to manipulate the Inheritable capability flag of the launching process. PAM enabled apps can be configured to do this by your local admin (or Linux distribution maintainers). There is a pam_cap.so module (documentation), which can do just that. On one of my systems, the default is for sudo to include pam_cap.so in its PAM auth stack, and the default fallback rule for all users in the /etc/security/capability.conf file is:

## 'everyone else' gets no inheritable capabilities (restrictive config)

none  *

Which means that sudo will never preserve the Inheritable or Ambient capabilities of the invoking process. This setting makes sense, because inadvertently inheriting unexpected capabilities through sudo is surprising. In all cases, that is, except when you want them preserved! For that, you want all * (which doesn't mean all are granted, only the Inheritable flag is preserved).