Bug 217592

Summary: Unexpected output from getcap: "path ="
Product: Tools Reporter: parke.nexus
Component: libcapAssignee: Andrew G. Morgan (morgan)
Status: RESOLVED CODE_FIX    
Severity: normal CC: morgan, parke.nexus
Priority: P3    
Hardware: All   
OS: Linux   
Kernel Version: Subsystem:
Regression: No Bisected commit-id:

Description parke.nexus 2023-06-25 03:01:27 UTC
Is this a bug?

# setcap cap_net_admin=ep /bin/true
# getcap /bin/true
/bin/true cap_net_admin=ep
# setcap cap_net_admin=e /bin/true
# getcap /bin/true
/bin/true =

Actual output (as above):
/bin/true =

Expected output:
/bin/true cap_net_admin=e

Tested on Arch Linux with libcap-2.69.1.

(I have no idea if setting a permission to =e is actually useful.  But I would expect meaningful output from getcap regardless.)
Comment 1 Andrew G. Morgan 2023-06-25 03:22:29 UTC
This is expected behavior. The Linux kernel doesn't view cap_net_admin=e as a meaningful capability. It ultimately interprets it to mean "no capabilities" (aka. "=").

Capabilities on files have 3 dimensions: 'p', 'i', and 'e'.

Unlike the 'p' and 'i' full flags, in the file system the 'e' is a single boolean "on" or "off". It is referred to as the "legacy bit".

If 'p' and 'i' have bits enabled on the program file, they can influence the 'p' bits of the executed program's runtime. All the single 'e' bit on the file does, however, is to also raise all of the process 'e' bits that are in the process 'p' bits before the first instruction of the program is executed.

https://sites.google.com/site/fullycapable

has some example articles that you might find useful (the Inheriting privilege one steps through most of the details). Including a link to this paper:

https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/33528.pdf

which includes some worked examples of using file capabilities.
Comment 2 parke.nexus 2023-06-25 03:31:27 UTC
Thanks for the explanation.

Perhaps it would be worth adding a sentence to the getcap man page explaining the meaning of "path =".

I had assumed that if no permissions were set, then nothing would be output.
Comment 3 Andrew G. Morgan 2023-06-25 03:37:58 UTC
That sounds reasonable. Also, I think I should really make setcap at least warn that what is being attempted isn't going to work as expected.
Comment 4 Andrew G. Morgan 2023-06-25 03:47:49 UTC
I should note that

  path =

is "forced to no privilege", vs no change of privilege.

Initially, this semantic difference meant you could suppress root inherited privilege on such a file if it was run by root. But, for some reason, the kernel developers after a number of years decided to suppress those semantics and reassert root should always get =ep independent of what the file capabilities say.

That being said,

  path =

kills Ambient inheritance, because file capabilities override Ambient capabilities.
Comment 5 parke.nexus 2023-06-25 03:57:14 UTC
Yes, a setcap warning would also be helpful.

I had not realized that file-effective was a single (i.e. per-file) bit.  I was puzzled as to why examples showed both both p and e being set, when all I really cared about was that the new process have the effective cap_net_admin capability.
Comment 6 parke.nexus 2023-06-25 04:04:36 UTC
I definitely think it is worth editing the getcap man page to explain the difference between:

(a) no output, and
(b) "path ="
Comment 7 Andrew G. Morgan 2023-06-25 04:27:39 UTC
So. It turns out, if you try this on a non-existent file, you will see an error of the following form:

# setcap cap_net_admin=e /bin/notthere
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/notthere' (No such file or directory)

This "NOTE: ..." was basically attempting to explain why the kernel might have rejected the file capability. What is odd is that the kernel is not rejecting it... I wonder if this is some sort of kernel regression?
Comment 8 Andrew G. Morgan 2023-06-25 04:42:37 UTC
OK, so this was never enforced by the kernel. The kernel never sees the full capability, it only receives the single bit for the Effective part and all of the processing is inside libcap.

I'm going to restructure setcap to make such a bad file capability harder to set.
Comment 9 Andrew G. Morgan 2023-06-25 04:57:45 UTC
man setcap does contain some information:

       The special capability string, '-r', is used to remove a capability set
       from a file. **Note, setting an empty capability set is not the  same  as
       removing  it**.  An empty set can be used to guarantee a file is not exe‐
       cuted with privilege in spite of the fact  that  the  prevailing  ambi‐
       ent+inheritable  sets  would  otherwise bestow capabilities on executed
       binaries.
Comment 10 parke.nexus 2023-06-25 05:10:14 UTC
I had glanced at that, but did not read it closely because it did not seem relevant to my situation.  Even if I had read it closely, I'm not sure it would have helped, as I did not know what "=" meant.

IMO, better would be:

Note that setting an empty capability set (via `setcap = path`) differs from removing a file's capability set (via `setcap -r path`).
Comment 11 parke.nexus 2023-06-25 05:30:00 UTC
I had thought that `setcap '' path` would remove all capabilities.

Perhaps it is worth printing a warning (or even returning an error?) when <capabilities> is the empty string?

Warning: Please use `setcap -r path` to remove the capability set or `setcap = path` to set an empty capability set.