The description of SECBIT_KEEP_CAPS is misleading about the effects on the effective capabilities of a process during a switch to nonzero UIDs. The effective set is cleared based on the effective UID switching to a nonzero value, even if SECBIT_KEEP_CAPS is set. However, with this bit set, the effective and permitted sets are not cleared if the real and saved set-user-ID are set to nonzero values. This was tested using the following C code and reading the kernel source at security/commoncap.c: cap_emulate_setxuid #define _GNU_SOURCE 1 #include <linux/securebits.h> #include <stdio.h> #include <string.h> #include <sys/capability.h> #include <sys/prctl.h> #include <unistd.h> void print_caps(void) { cap_t current = cap_get_proc(); if (!current) { perror("Current caps"); return; } char *text = cap_to_text(current, NULL); if (!text) { perror("Converting caps to text"); goto free_caps; } printf("Capabilities: %s\n", text); cap_free(text); free_caps: cap_free(current); } void print_creds(void) { uid_t ruid, suid, euid; if (getresuid(&ruid, &euid, &suid)) { perror("Error getting UIDs"); return; } printf("real = %d, effective = %d, saved set-user-ID = %d\n", ruid, euid, suid); } void set_caps(int size, const cap_value_t *caps) { cap_t current = cap_init(); if (!current) { perror("Error getting current caps"); return; } if (cap_clear(current)) { perror("Error clearing caps"); } if (cap_set_flag(current, CAP_INHERITABLE, size, caps, CAP_SET)) { perror("setting caps"); goto free_caps; } if (cap_set_flag(current, CAP_EFFECTIVE, size, caps, CAP_SET)) { perror("setting caps"); goto free_caps; } if (cap_set_flag(current, CAP_PERMITTED, size, caps, CAP_SET)) { perror("setting caps"); goto free_caps; } if (cap_set_proc(current)) { perror("Comitting caps"); goto free_caps; } free_caps: cap_free(current); } const cap_value_t caps[] = {CAP_SETUID, CAP_SETPCAP}; const size_t num_caps = sizeof(caps) / sizeof(cap_value_t); int main(int argc, char **argv) { puts("[+] Dropping most capabilities to reduce amount of console output..."); set_caps(num_caps, caps); puts("[+] Dropped capabilities. Starting with these credentials and capabilities:"); print_caps(); print_creds(); if (argc >= 2 && 0 == strncmp(argv[1], "keep", 4)) { puts("[+] Setting SECBIT_KEEP_CAPS bit"); if (prctl(PR_SET_SECUREBITS, SECBIT_KEEP_CAPS, 0, 0, 0)) { perror("Setting secure bits"); return 1; } } puts("[+] Setting effective UID to 1000"); if (seteuid(1000)) { perror("Error setting effective UID"); return 2; } print_caps(); print_creds(); puts("[+] Raising caps again"); set_caps(num_caps, caps); print_caps(); print_creds(); puts("[+] Setting all remaining UIDs to nonzero values"); if (setreuid(1000, 1000)) { perror("Error setting all UIDs to 1000"); return 3; } print_caps(); print_creds(); return 0; } --- man7/capabilities.7 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/man7/capabilities.7 b/man7/capabilities.7 index e5a3ce50d..c9fd45718 100644 --- a/man7/capabilities.7 +++ b/man7/capabilities.7 @@ -1450,6 +1450,13 @@ in those sets. This flag is always cleared on an .BR execve (2). .IP +Note that even with the +.B SECBIT_KEEP_CAPS +flag set, the effective capabilities of a thread are cleared when it +switches its effective UID to a nonzero value. However, if the effective +UID is already nonzero and a thread subsequently switches all other UIDs +to nonzero values, then the effective capabilities will not be cleared. +.IP The setting of the .B SECBIT_KEEP_CAPS flag is ignored if the -- 2.19.1