From 33d0916f8144d7468f6253b8a26f4da1963354c9 Mon Sep 17 00:00:00 2001 From: Michael Kerrisk Date: Tue, 12 Feb 2019 16:56:13 +0100 Subject: [PATCH] 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 \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 --- man7/capabilities.7 | 94 ++++++++++++++++++++++++++------------------- 1 file changed, 55 insertions(+), 39 deletions(-) diff --git a/man7/capabilities.7 b/man7/capabilities.7 index 226e2c33b0..9873f9dbf4 100644 --- a/man7/capabilities.7 +++ b/man7/capabilities.7 @@ -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 -.BR execve (2)s -a set-user-ID-root program, or when a process with an effective UID of 0 +Thus, when a process with nonzero UIDs .BR execve (2)s -a program, -it 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 +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 capability set, -.\" but no effective capabilities +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. -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). -- 2.39.5