capabilities.7: Substantially rework "Capabilities and execution of programs by root"

Rework for improved clarity, and also to include missing details
on the case where (1) the binary that is being executed has
capabilities attached and (2) the real user ID of the process is
not 0 (root) and (3) the effective user ID of the process is 0
(root).

Kernel code analysis and some test code (GPLv3 licensed) below.

======

My analysis of security/commoncaps.c capabilities handling
(from Linux 4.20 source):

execve() eventually calls __do_execve_file():

__do_execve_file()
  |
  +-prepare_bprm_creds(&bprm)
  |  |
  |  +-prepare_exec_creds()
  |  |  |
  |  |  +-prepare_creds()
  |  |     |
  |  |     | // Returns copy of existing creds
  |  |     |
  |  |     +-security_prepare_creds()
  |  |        |
  |  |        +-cred_prepare() [via hook]
  |  |           // Seems to do nothing for commoncaps
  |  |
  |  // Returns creds provided by prepare_creds()
  |
  // Places creds returned by prepare_exec_creds() in bprm->creds
  |
  |
  +-prepare_binprm(&bprm) // bprm from prepare_bprm_creds()
     |
     +-bprm_fill_uid(&bprm)
     |
     |  // Places current credentials into bprm
     |
     |  // Performs set-UID & set-GID transitions if those file bits are set
     |
     +-security_bprm_set_creds(&bprm)
        |
        +-bprm_set_creds(&bprm) [via hook]
           |
           +-cap_bprm_set_creds(&bprm)
              |
              // effective = false
              |
              +-get_file_caps(&bprm, &effective, &has_fcap)
              |  |
              |  +-get_vfs_caps_from_disk(..., &vcaps)
              |  |
              |  |  // Fetches file capabilities from disk and places in vcaps
              |  |
              |  +-bprm_caps_from_vfs_caps(&vcaps, &bprm, &effective, &has_fcap)
              |
              |     // If file effective bit is set: effective = true
              |     //
              |     // If file has capabilities: has_fcap |= true
              |     //
              |     // Perform execve transformation:
              |     //     P'(perm) = F(inh) & P(Inh) | F(Perm) & P(bset)
              |
              +-handle_privileged_root(&bprm, has_fcap, &effective, root_uid)
              |
              |  // If has_fcap && (rUID != root && eUID == root) then
              |  //     return without doing anything
              |  //
              |  // If rUID == root || eUID == root then
              |  //    P'(perm) = P(inh) | P(bset)
              |  //
              |  // If eUID == root then
              |  //     effective = true
              |
              // Perform execve() transformation:
              //
              //     P'(Amb) = (privprog) ? 0 : P(Amb)
              //     P'(Perm) |= P'(Amb)
              //     P'(Eff) = effective ? P'(Perm) : P'(Amb)

Summary

1. Perform set-UID/set-GID transformations

2. P'(Amb) = (privprog) ? 0 : P(Amb)

3. If [process has nonzero UIDs] OR
   ([file has caps] && [rUID != root && eUID == root]), then

        P'(perm) = F(inh) & P(Inh) | F(Perm) & P(bset) | P'(Amb)

   else // ~ [process has rUID == root || eUID == root]

        P'(perm) = P(inh) | P(bset) | P'(Amb)

4. P'(Eff) = (F(eff) || eUID == root) ? P'(Perm) : P'(Amb)

======

$ cat show_creds_and_caps_long.c

int
main(int argc, char *argv[])
{
    uid_t ruid, euid, suid;
    gid_t rgid, egid, sgid;
    cap_t caps;
    char *s;

    if (getresuid(&ruid, &euid, &suid) == -1) {
        perror("getresuid");
        exit(EXIT_FAILURE);
    }

    if (getresgid(&rgid, &egid, &sgid) == -1) {
        perror("getresgid");
        exit(EXIT_FAILURE);
    }

    printf("UID: %5ld (real), %5ld (effective), %5ld (saved)\n",
            (long) ruid, (long) euid, (long) suid);
    printf("GID: %5ld (real), %5ld (effective), %5ld (saved)\n",
            (long) rgid, (long) egid, (long) sgid);

    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        exit(EXIT_FAILURE);
    }
    s = cap_to_text(caps, NULL);
    if (s == NULL) {
        perror("cap_to_text");
        exit(EXIT_FAILURE);
    }
    printf("Capabilities: %s\n", s);

    cap_free(caps);
    cap_free(s);

    exit(EXIT_SUCCESS);
}

$ cat cred_launcher.c

                        } while (0)

                        do { fprintf(stderr, "Usage: "); \
                             fprintf(stderr, msg, progName); \
                             exit(EXIT_FAILURE); } while (0)

int
main(int argc, char *argv[])
{
    uid_t r, e, s;

    if (argc != 5 || strcmp(argv[1], "--help") == 0)
        usageErr("%s rUID eUID sUID <prog>\n", argv[0]);

    r = atoi(argv[1]);
    e = atoi(argv[2]);
    s = atoi(argv[3]);

    if (setresuid(r, e, s) == -1)
        errExit("setresuid");

    if (getresuid(&r, &e, &s) == -1)
        errExit("getresuid");

    execv(argv[4], &argv[4]);
    errExit("execve");
}

$ cc -o cred_launcher cred_launcher.c
$ cc -o show_creds_and_caps_long show_creds_and_caps_long.c -lcap

$ sudo ./cred_launcher 1000 0 1000 ./show_creds_and_caps_long
UID:  1000 (real),     0 (effective),     0 (saved)
GID:     0 (real),     0 (effective),     0 (saved)
Capabilities: =ep

$ sudo setcap cap_kill=pe show_creds_and_caps_long
$ sudo ./cred_launcher 1000 0 1000 ./show_creds_and_caps_long
UID:  1000 (real),     0 (effective),     0 (saved)
GID:     0 (real),     0 (effective),     0 (saved)
Capabilities: = cap_kill+ep

The final program execution above shows the special casing
that occurs in handle_privileged_root() for the case where:

    rUID != root && eUID == root && [file has capabilities]

======

Signed-off-by: Michael Kerrisk <mtk.manpages@gmail.com>
This commit is contained in:
Michael Kerrisk 2019-02-12 16:56:13 +01:00
parent cc0fb214da
commit 33d0916f81
1 changed files with 55 additions and 39 deletions

View File

@ -1186,61 +1186,77 @@ since it does not employ the
API.
.\"
.SS Capabilities and execution of programs by root
.\" See cap_bprm_set_creds() and handle_privileged_root() in
.\" security/commoncap.c (Linux 5.0 source)
.\" See cap_bprm_set_creds(), bprm_caps_from_vfs_cap() and
.\" handle_privileged_root() in security/commoncap.c (Linux 5.0 source)
In order to mirror traditional UNIX semantics,
execution of programs by root (UID 0)
as well as execution of set-user-ID-root programs
result in special treatment of capabilities during an
.BR execve (2).
the kernel performs special treatment of file capabilities when
a process with UID 0 (root) executes a program and
when a set-user-ID-root program is executed.
.PP
When a process with nonzero UIDs executes a binary:
After having performed any changes to the process effective ID that
were triggered by the set-user-ID mode bit of the binary\(eme.g.,
switching the effective user ID to 0 (root) because
a set-user-ID-root program was executed\(emthe
kernel calculates the file capability sets as follows:
.IP 1. 3
If the real or effective user ID of the process is 0 (root),
then the file inheritable and permitted sets are defined to be all ones
then the file inheritable and permitted sets are ignored;
instead they are notionally considered to be all ones
(i.e., all capabilities enabled).
(There is one exception to this behavior, described below in
.IR "Set-user-ID-root programs that have file capabilities" .)
.IP 2.
If the effective user ID of the process is 0 (root) or
the file effective bit is in fact enabled,
then the file effective bit is defined to be one (enabled).
then the file effective bit is notionally defined to be one (enabled).
.PP
If a process with nonzero user IDs executes a set-user-ID-root binary
that does not have attached capabilities,
the file capability sets are considered to be all ones.
(See below for a discussion of what happens
when a process with nonzero UIDs executes a binary
that is both set-user-ID root and has attached file capabilities.)
These notional values for the file's capability sets are then used
as described above to calculate the transformation of the process's
capabilities during
.BR execve (2).
.PP
The upshot of the above rules,
combined with the capabilities transformations described above,
is as follows:
.IP * 3
When a process with nonzero UIDs
Thus, when a process with nonzero UIDs
.BR execve (2)s
a set-user-ID-root program, or when a process with an effective UID of 0
a set-user-ID-root program that does not have capabilities attached,
or when a process whose real and effective UIDs are zero
.BR execve (2)s
a program,
it gains all capabilities in its permitted and effective capability sets,
a program, the calculation of the process's new
permitted capabilities simplifies to:
.PP
.in +4n
.EX
P'(permitted) = P(inheritable) | P(bounding)
P'(effective) = P'(permitted)
.EE
.in
.PP
Consequently, the process gains all capabilities in its permitted and
effective capability sets,
except those masked out by the capability bounding set.
.IP *
When a process with a real UID of 0
.BR execve (2)s
a program,
it gains all capabilities in its permitted capability set,
.\" but no effective capabilities
except those masked out by the capability bounding set.
If, in addition, the file permitted capability bit is on,
the process's new permitted capabilities are also assigned
to its effective set.
(In the calculation of P'(permitted),
the P'(ambient) term can be simplified away because it is by
definition a proper subset of P(inheritable).)
.PP
The above special treatments of user ID 0 can be disabled using the
securebits mechanism described below.
The special treatments of user ID 0 (root) described in this subsection
can be disabled using the securebits mechanism described below.
.\"
.\"
.SS Set-user-ID-root programs that have file capabilities
Executing a program that is both set-user-ID root and has
file capabilities by a process that has nonzero UIDs
will cause the process to gain just the
capabilities granted by the program
There is one exception to the behavior described under
.IR "Capabilities and execution of programs by root" .
If (a) the binary that is being executed has capabilities attached and
(b) the real user ID of the process is
.I not
0 (root) and
(c) the effective user ID of the process
.I is
0 (root), then the file capability bits are honored
(i.e., they are not notionally considered to be all ones).
The usual way in which this situation can arise is when executing
a set-UID-root program that also has file capabilities.
When such a program is executed,
the process gains just the capabilities granted by the program
(i.e., not all capabilities,
as would occur when executing a set-user-ID-root program
that does not have any associated file capabilities).