/* libcap-ng.c -- * Copyright 2009-10 Red Hat Inc., Durham, North Carolina. * All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Authors: * Steve Grubb */ #include "config.h" #include "cap-ng.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYSCALL_H #include #endif #ifdef HAVE_LINUX_SECUREBITS_H #include #endif /* * Some milestones of when things became available: * 2.6.24 kernel XATTR_NAME_CAPS * 2.6.25 kernel PR_CAPBSET_DROP, CAPABILITY_VERSION_2 * 2.6.26 kernel PR_SET_SECUREBITS, SECURE_*_LOCKED, VERSION_3 */ /* External syscall prototypes */ extern int capset(cap_user_header_t header, cap_user_data_t data); extern int capget(cap_user_header_t header, const cap_user_data_t data); // Local defines #define MASK(x) (1U << (x)) #ifdef PR_CAPBSET_DROP #define UPPER_MASK ~(unsigned)((~0U)<<(CAP_LAST_CAP-31)) #else // For v1 systems UPPER_MASK will never be used #define UPPER_MASK (unsigned)(~0U) #endif // Re-define cap_valid so its uniform between V1 and V3 #undef cap_valid #define cap_valid(x) ((x) <= CAP_LAST_CAP) // If we don't have the xattr library, then we can't // compile-in file system capabilities #ifndef HAVE_ATTR_XATTR_H #undef VFS_CAP_U32 #endif #ifdef VFS_CAP_U32 #include #if __BYTE_ORDER == __BIG_ENDIAN #define FIXUP(x) bswap_32(x) #else #define FIXUP(x) (x) #endif #endif #ifndef _LINUX_CAPABILITY_VERSION_1 #define _LINUX_CAPABILITY_VERSION_1 0x19980330 #endif #ifndef _LINUX_CAPABILITY_VERSION_2 #define _LINUX_CAPABILITY_VERSION_2 0x20071026 #endif #ifndef _LINUX_CAPABILITY_VERSION_3 #define _LINUX_CAPABILITY_VERSION_3 0x20080522 #endif // This public API went private in the 2.6.36 kernel - hope it never changes #ifndef XATTR_CAPS_SUFFIX #define XATTR_CAPS_SUFFIX "capability" #endif #ifndef XATTR_SECURITY_PREFIX #define XATTR_SECURITY_PREFIX "security." #endif #ifndef XATTR_NAME_CAPS #define XATTR_NAME_CAPS XATTR_SECURITY_PREFIX XATTR_CAPS_SUFFIX #endif /* Child processes can't get caps back */ #ifndef SECURE_NOROOT #define SECURE_NOROOT 0 #endif #ifndef SECURE_NOROOT_LOCKED #define SECURE_NOROOT_LOCKED 1 /* make bit-0 immutable */ #endif /* Setuid apps run by uid 0 don't get caps back */ #ifndef SECURE_NO_SETUID_FIXUP #define SECURE_NO_SETUID_FIXUP 2 #endif #ifndef SECURE_NO_SETUID_FIXUP_LOCKED #define SECURE_NO_SETUID_FIXUP_LOCKED 3 /* make bit-2 immutable */ #endif // States: new, allocated, initted, updated, applied typedef enum { CAPNG_NEW, CAPNG_ERROR, CAPNG_ALLOCATED, CAPNG_INIT, CAPNG_UPDATED, CAPNG_APPLIED } capng_states_t; // Create an easy data struct out of the kernel definitions typedef union { struct __user_cap_data_struct v1; struct __user_cap_data_struct v3[2]; } cap_data_t; // This struct keeps all state info struct cap_ng { int cap_ver; struct __user_cap_header_struct hdr; cap_data_t data; capng_states_t state; __u32 bounds[2]; }; // Global variables with per thread uniqueness static __thread struct cap_ng m = { 1, {0, 0}, { {0, 0, 0} }, CAPNG_NEW, {0, 0} }; static void init(void) { if (m.state != CAPNG_NEW) return; memset(&m.hdr, 0, sizeof(m.hdr)); (void)capget(&m.hdr, NULL); // Returns -EINVAL if (m.hdr.version == _LINUX_CAPABILITY_VERSION_3 || m.hdr.version == _LINUX_CAPABILITY_VERSION_2) { m.cap_ver = 3; } else if (m.hdr.version == _LINUX_CAPABILITY_VERSION_1) { m.cap_ver = 1; } else { m.state = CAPNG_ERROR; return; } memset(&m.data, 0, sizeof(cap_data_t)); #ifdef HAVE_SYSCALL_H m.hdr.pid = (unsigned)syscall(__NR_gettid); #else m.hdr.pid = (unsigned)getpid(); #endif m.state = CAPNG_ALLOCATED; } void capng_clear(capng_select_t set) { if (m.state == CAPNG_NEW) init(); if (m.state == CAPNG_ERROR) return; if (set & CAPNG_SELECT_CAPS) memset(&m.data, 0, sizeof(cap_data_t)); #ifdef PR_CAPBSET_DROP if (set & CAPNG_SELECT_BOUNDS) memset(m.bounds, 0, sizeof(m.bounds)); #endif m.state = CAPNG_INIT; } void capng_fill(capng_select_t set) { if (m.state == CAPNG_NEW) init(); if (m.state == CAPNG_ERROR) return; if (set & CAPNG_SELECT_CAPS) { if (m.cap_ver == 1) { m.data.v1.effective = 0x7FFFFFFFU; m.data.v1.permitted = 0x7FFFFFFFU; m.data.v1.inheritable = 0; } else { m.data.v3[0].effective = 0xFFFFFFFFU; m.data.v3[0].permitted = 0xFFFFFFFFU; m.data.v3[0].inheritable = 0; m.data.v3[1].effective = 0xFFFFFFFFU; m.data.v3[1].permitted = 0xFFFFFFFFU; m.data.v3[1].inheritable = 0; } } #ifdef PR_CAPBSET_DROP if (set & CAPNG_SELECT_BOUNDS) { unsigned i; for (i=0; imagic_etc); switch (magic & VFS_CAP_REVISION_MASK) { case VFS_CAP_REVISION_1: m.cap_ver = 1; if (size != XATTR_CAPS_SZ_1) return -1; break; case VFS_CAP_REVISION_2: m.cap_ver = 2; if (size != XATTR_CAPS_SZ_2) return -1; break; default: return -1; } // Now stuff the data structures m.data.v3[0].permitted = FIXUP(filedata->data[0].permitted); m.data.v3[1].permitted = FIXUP(filedata->data[1].permitted); m.data.v3[0].inheritable = FIXUP(filedata->data[0].inheritable); m.data.v3[1].inheritable = FIXUP(filedata->data[1].inheritable); if (magic & VFS_CAP_FLAGS_EFFECTIVE) { m.data.v3[0].effective = m.data.v3[0].permitted | m.data.v3[0].inheritable; m.data.v3[1].effective = m.data.v3[1].permitted | m.data.v3[1].inheritable; } else { m.data.v3[0].effective = 0; m.data.v3[1].effective = 0; } return 0; } #endif int capng_get_caps_fd(int fd) { #ifndef VFS_CAP_U32 return -1; #else int rc; struct vfs_cap_data filedata; if (m.state == CAPNG_NEW) init(); if (m.state == CAPNG_ERROR) return -1; rc = fgetxattr(fd, XATTR_NAME_CAPS, &filedata, sizeof(filedata)); if (rc <= 0) return -1; rc = load_data(&filedata, rc); if (rc == 0) m.state = CAPNG_INIT; return rc; #endif } static void v1_update(capng_act_t action, unsigned int capability, __u32 *data) { if (action == CAPNG_ADD) *data |= MASK(capability); else *data &= ~(MASK(capability)); } static void update_effective(capng_act_t action, unsigned int capability, unsigned int idx) { if (action == CAPNG_ADD) m.data.v3[idx].effective |= MASK(capability); else m.data.v3[idx].effective &= ~(MASK(capability)); } static void update_permitted(capng_act_t action, unsigned int capability, unsigned int idx) { if (action == CAPNG_ADD) m.data.v3[idx].permitted |= MASK(capability); else m.data.v3[idx].permitted &= ~(MASK(capability)); } static void update_inheritable(capng_act_t action, unsigned int capability, unsigned int idx) { if (action == CAPNG_ADD) m.data.v3[idx].inheritable |= MASK(capability); else m.data.v3[idx].inheritable &= ~(MASK(capability)); } static void update_bounding_set(capng_act_t action, unsigned int capability, unsigned int idx) { #ifdef PR_CAPBSET_DROP if (action == CAPNG_ADD) m.bounds[idx] |= MASK(capability); else m.bounds[idx] &= ~(MASK(capability)); #endif } int capng_update(capng_act_t action, capng_type_t type, unsigned int capability) { // Before updating, we expect that the data is initialized to something if (m.state < CAPNG_INIT) return -1; if (!cap_valid(capability)) { errno = EINVAL; return -1; } if (m.cap_ver == 1) { if (CAPNG_EFFECTIVE & type) v1_update(action, capability, &m.data.v1.effective); if (CAPNG_PERMITTED & type) v1_update(action, capability, &m.data.v1.permitted); if (CAPNG_INHERITABLE & type) v1_update(action, capability, &m.data.v1.inheritable); } else { int idx; if (capability > 31) { idx = capability>>5; capability %= 32; } else idx = 0; if (CAPNG_EFFECTIVE & type) update_effective(action, capability, idx); if (CAPNG_PERMITTED & type) update_permitted(action, capability, idx); if (CAPNG_INHERITABLE & type) update_inheritable(action, capability, idx); if (CAPNG_BOUNDING_SET & type) update_bounding_set(action, capability, idx); } m.state = CAPNG_UPDATED; return 0; } int capng_updatev(capng_act_t action, capng_type_t type, unsigned int capability, ...) { int rc; unsigned int cap; va_list ap; rc = capng_update(action, type, capability); if (rc) return rc; va_start(ap, capability); cap = va_arg(ap, unsigned int); while (cap_valid(cap)) { rc = capng_update(action, type, cap); if (rc) break; cap = va_arg(ap, unsigned int); } va_end(ap); // See if planned exit or invalid if (cap == (unsigned)-1) rc = 0; else { rc = -1; errno = EINVAL; } return rc; } int capng_apply(capng_select_t set) { int rc = -1; // Before updating, we expect that the data is initialized to something if (m.state < CAPNG_INIT) return -1; if (set & CAPNG_SELECT_BOUNDS) { #ifdef PR_CAPBSET_DROP void *s = capng_save_state(); capng_get_caps_process(); if (capng_have_capability(CAPNG_EFFECTIVE, CAP_SETPCAP)) { int i; capng_restore_state(&s); rc = 0; for (i=0; i <= CAP_LAST_CAP && rc == 0; i++) if (capng_have_capability(CAPNG_BOUNDING_SET, i) == 0) rc = prctl(PR_CAPBSET_DROP, i, 0, 0, 0); if (rc == 0) m.state = CAPNG_APPLIED; } else capng_restore_state(&s); #else rc = 0; #endif } if (set & CAPNG_SELECT_CAPS) { rc = capset((cap_user_header_t)&m.hdr, (cap_user_data_t)&m.data); if (rc == 0) m.state = CAPNG_APPLIED; } return rc; } #ifdef VFS_CAP_U32 static int save_data(struct vfs_cap_data *filedata, int *size) { // Now stuff the data structures if (m.cap_ver == 1) { filedata->data[0].permitted = FIXUP(m.data.v1.permitted); filedata->data[0].inheritable = FIXUP(m.data.v1.inheritable); filedata->magic_etc = FIXUP(VFS_CAP_REVISION_1); *size = XATTR_CAPS_SZ_1; } else { int eff; if (m.data.v3[0].effective || m.data.v3[1].effective) eff = VFS_CAP_FLAGS_EFFECTIVE; else eff = 0; filedata->data[0].permitted = FIXUP(m.data.v3[0].permitted); filedata->data[0].inheritable = FIXUP(m.data.v3[0].inheritable); filedata->data[1].permitted = FIXUP(m.data.v3[1].permitted); filedata->data[1].inheritable = FIXUP(m.data.v3[1].inheritable); filedata->magic_etc = FIXUP(VFS_CAP_REVISION_2 | eff); *size = XATTR_CAPS_SZ_2; } return 0; } #endif int capng_apply_caps_fd(int fd) { #ifndef VFS_CAP_U32 return -1; #else int rc, size; struct vfs_cap_data filedata; struct stat buf; // Before updating, we expect that the data is initialized to something if (m.state < CAPNG_INIT) return -1; if (fstat(fd, &buf) != 0) return -1; if (S_ISLNK(buf.st_mode) || !S_ISREG(buf.st_mode)) { errno = EINVAL; return -1; } if (capng_have_capabilities(CAPNG_SELECT_CAPS) == CAPNG_NONE) rc = fremovexattr(fd, XATTR_NAME_CAPS); else { save_data(&filedata, &size); rc = fsetxattr(fd, XATTR_NAME_CAPS, &filedata, size, 0); } if (rc == 0) m.state = CAPNG_APPLIED; return rc; #endif } // Change uids keeping/removing only certain capabilities // flag to drop supp groups int capng_change_id(int uid, int gid, capng_flags_t flag) { int rc, need_setgid, need_setuid; // Before updating, we expect that the data is initialized to something if (m.state < CAPNG_INIT) return -1; // Check the current capabilities #ifdef PR_CAPBSET_DROP // If newer kernel, we need setpcap to change the bounding set if (capng_have_capability(CAPNG_EFFECTIVE, CAP_SETPCAP) == 0 && flag & CAPNG_CLEAR_BOUNDING) capng_update(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_SETPCAP); #endif if (gid == -1 || capng_have_capability(CAPNG_EFFECTIVE, CAP_SETGID)) need_setgid = 0; else { need_setgid = 1; capng_update(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_SETGID); } if (uid == -1 || capng_have_capability(CAPNG_EFFECTIVE, CAP_SETUID)) need_setuid = 0; else { need_setuid = 1; capng_update(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_SETUID); } // Tell system we want to keep caps across uid change if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0)) return -2; // Change to the temp capabilities rc = capng_apply(CAPNG_SELECT_CAPS); if (rc < 0) return -3; // Clear bounding set if needed while we have CAP_SETPCAP if (flag & CAPNG_CLEAR_BOUNDING) { capng_clear(CAPNG_BOUNDING_SET); rc = capng_apply(CAPNG_SELECT_BOUNDS); if (rc) return -8; } // Change gid if (gid != -1) { rc = setresgid(gid, gid, gid); if (rc) return -4; } // See if we need to unload supplemental groups if ((flag & CAPNG_DROP_SUPP_GRP) && gid != -1) { if (setgroups(0, NULL)) return -5; } // Change uid if (uid != -1) { rc = setresuid(uid, uid, uid); if (rc) return -6; } // Tell it we are done keeping capabilities rc = prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0); if (rc) return -7; // Now throw away CAP_SETPCAP so no more changes if (need_setgid) capng_update(CAPNG_DROP, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_SETGID); if (need_setuid) capng_update(CAPNG_DROP, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_SETUID); // Now drop setpcap & apply capng_update(CAPNG_DROP, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_SETPCAP); rc = capng_apply(CAPNG_SELECT_CAPS); if (rc < 0) return -9; // Done m.state = CAPNG_UPDATED; return 0; } int capng_lock(void) { #ifdef PR_SET_SECUREBITS int rc = prctl(PR_SET_SECUREBITS, 1 << SECURE_NOROOT | 1 << SECURE_NOROOT_LOCKED | 1 << SECURE_NO_SETUID_FIXUP | 1 << SECURE_NO_SETUID_FIXUP_LOCKED, 0, 0, 0); if (rc) return -1; #endif return 0; } // -1 - error, 0 - no caps, 1 partial caps, 2 full caps capng_results_t capng_have_capabilities(capng_select_t set) { int empty = 0, full = 0; // First, try to init with current set if (m.state < CAPNG_INIT) capng_get_caps_process(); // If we still don't have anything, error out if (m.state < CAPNG_INIT) return CAPNG_FAIL; if (set & CAPNG_SELECT_CAPS) { if (m.cap_ver == 1) { if (m.data.v1.effective == 0) empty = 1; // after fill, 30 bits starts from upper to lower else if (m.data.v1.effective == 0x7FFFFFFFU) full = 1; // actual capabilities read from system else if (m.data.v1.effective == 0xFFFFFEFFU) full = 1; else return CAPNG_PARTIAL; } else { if (m.data.v3[0].effective == 0) empty = 1; else if (m.data.v3[0].effective == 0xFFFFFFFFU) full = 1; else return CAPNG_PARTIAL; if ((m.data.v3[1].effective & UPPER_MASK) == 0) empty = 1; else if ((m.data.v3[1].effective & UPPER_MASK) == UPPER_MASK) full = 1; else return CAPNG_PARTIAL; } } #ifdef PR_CAPBSET_DROP if (set & CAPNG_SELECT_BOUNDS) { if (m.bounds[0] == 0) empty = 1; else if (m.bounds[0] == 0xFFFFFFFFU) full = 1; else return CAPNG_PARTIAL; if ((m.bounds[1] & UPPER_MASK) == 0) empty = 1; else if ((m.bounds[1] & UPPER_MASK) == UPPER_MASK) full = 1; else return CAPNG_PARTIAL; } #endif if (empty == 1 && full == 0) return CAPNG_NONE; else if (empty == 0 && full == 1) return CAPNG_FULL; return CAPNG_PARTIAL; } static int check_effective(unsigned int capability, unsigned int idx) { return MASK(capability) & m.data.v3[idx].effective ? 1 : 0; } static int check_permitted(unsigned int capability, unsigned int idx) { return MASK(capability) & m.data.v3[idx].permitted ? 1 : 0; } static int check_inheritable(unsigned int capability, unsigned int idx) { return MASK(capability) & m.data.v3[idx].inheritable ? 1 : 0; } static int bounds_bit_check(unsigned int capability, unsigned int idx) { #ifdef PR_CAPBSET_DROP return MASK(capability) & m.bounds[idx] ? 1 : 0; #else return 0; #endif } static int v1_check(unsigned int capability, __u32 data) { return MASK(capability) & data ? 1 : 0; } int capng_have_capability(capng_type_t which, unsigned int capability) { // First, try to init with current set if (m.state < CAPNG_INIT) capng_get_caps_process(); // If we still don't have anything, error out if (m.state < CAPNG_INIT) return CAPNG_FAIL; if (m.cap_ver == 1 && capability > 31) return 0; if (!cap_valid(capability)) return 0; if (m.cap_ver == 1) { if (which == CAPNG_EFFECTIVE) return v1_check(capability, m.data.v1.effective); else if (which == CAPNG_PERMITTED) return v1_check(capability, m.data.v1.permitted); else if (which == CAPNG_INHERITABLE) return v1_check(capability, m.data.v1.inheritable); } else { unsigned int idx; if (capability > 31) { idx = capability>>5; capability %= 32; } else idx = 0; if (which == CAPNG_EFFECTIVE) return check_effective(capability, idx); else if (which == CAPNG_PERMITTED) return check_permitted(capability, idx); else if (which == CAPNG_INHERITABLE) return check_inheritable(capability, idx); else if (which == CAPNG_BOUNDING_SET) return bounds_bit_check(capability, idx); } return 0; } char *capng_print_caps_numeric(capng_print_t where, capng_select_t set) { char *ptr = NULL; if (m.state < CAPNG_INIT) return ptr; if (where == CAPNG_PRINT_STDOUT) { if (set & CAPNG_SELECT_CAPS) { if (m.cap_ver == 1) { printf( "Effective: %08X\n" "Permitted: %08X\n" "Inheritable: %08X\n", m.data.v1.effective, m.data.v1.permitted, m.data.v1.inheritable); } else { printf( "Effective: %08X, %08X\n" "Permitted: %08X, %08X\n" "Inheritable: %08X, %08X\n", m.data.v3[1].effective & UPPER_MASK, m.data.v3[0].effective, m.data.v3[1].permitted & UPPER_MASK, m.data.v3[0].permitted, m.data.v3[1].inheritable & UPPER_MASK, m.data.v3[0].inheritable); } } #ifdef PR_CAPBSET_DROP if (set & CAPNG_SELECT_BOUNDS) printf("Bounding Set: %08X, %08X\n", m.bounds[1] & UPPER_MASK, m.bounds[0]); #endif } else if (where == CAPNG_PRINT_BUFFER) { if (set & CAPNG_SELECT_CAPS) { // Make it big enough for bounding set, too ptr = malloc(160); if (m.cap_ver == 1) { snprintf(ptr, 160, "Effective: %08X\n" "Permitted: %08X\n" "Inheritable: %08X\n", m.data.v1.effective, m.data.v1.permitted, m.data.v1.inheritable); } else { snprintf(ptr, 160, "Effective: %08X, %08X\n" "Permitted: %08X, %08X\n" "Inheritable: %08X, %08X\n", m.data.v3[1].effective & UPPER_MASK, m.data.v3[0].effective, m.data.v3[1].permitted & UPPER_MASK, m.data.v3[0].permitted, m.data.v3[1].inheritable & UPPER_MASK, m.data.v3[0].inheritable); } } if (set & CAPNG_SELECT_BOUNDS) { #ifdef PR_CAPBSET_DROP char *s; if (ptr == NULL ){ ptr = malloc(40); if (ptr == NULL) return ptr; *ptr = 0; s = ptr; } else s = ptr + strlen(ptr); snprintf(s, 40, "Bounding Set: %08X, %08X\n", m.bounds[1] & UPPER_MASK, m.bounds[0]); #endif } } return ptr; } char *capng_print_caps_text(capng_print_t where, capng_type_t which) { unsigned int i; int once = 0, cnt = 0; char *ptr = NULL; if (m.state < CAPNG_INIT) return ptr; for (i=0; i<=CAP_LAST_CAP; i++) { if (capng_have_capability(which, i)) { const char *n = capng_capability_to_name(i); if (where == CAPNG_PRINT_STDOUT) { if (once == 0) { printf("%s", n); once++; } else printf(", %s", n); } else if (where == CAPNG_PRINT_BUFFER) { int len; if (once == 0) { ptr = malloc(CAP_LAST_CAP*18); if (ptr == NULL) return ptr; len = sprintf(ptr+cnt, "%s", n); once++; } else len = sprintf(ptr+cnt, ", %s", n); if (len > 0) cnt+=len; } } } if (once == 0) { if (where == CAPNG_PRINT_STDOUT) printf("none"); else ptr = strdup("none"); } return ptr; } void *capng_save_state(void) { void *ptr = malloc(sizeof(m)); if (ptr) memcpy(ptr, &m, sizeof(m)); return ptr; } void capng_restore_state(void **state) { if (state) { void *ptr = *state; if (ptr) memcpy(&m, ptr, sizeof(m)); free(ptr); *state = NULL; } }