diff options
Diffstat (limited to '')
-rw-r--r-- | src/linuxcap.c | 95 |
1 files changed, 94 insertions, 1 deletions
diff --git a/src/linuxcap.c b/src/linuxcap.c index 4a2a3ab..63a510f 100644 --- a/src/linuxcap.c +++ b/src/linuxcap.c @@ -40,11 +40,20 @@ static const struct { #ifdef CAP_NET_BIND_SERVICE { CAP_NET_BIND_SERVICE, "cap_net_bind_service" }, #endif +#ifdef CAP_SYS_ADMIN + { CAP_SYS_ADMIN, "cap_sys_admin" }, +#endif /* must be last */ { 0, 0 } }; /* provided by sys/capability.h on some distros */ +static inline int capget(cap_user_header_t hdrp, const cap_user_data_t datap) +{ + return syscall(SYS_capget, hdrp, datap); +} + +/* provided by sys/capability.h on some distros */ static inline int capset(cap_user_header_t hdrp, const cap_user_data_t datap) { return syscall(SYS_capset, hdrp, datap); @@ -53,6 +62,86 @@ static inline int capset(cap_user_header_t hdrp, const cap_user_data_t datap) /* defaults to zero, i.e. we don't keep any cap after setuid() */ static uint32_t caplist; +/* try to check if CAP_NET_ADMIN, CAP_NET_RAW or CAP_SYS_ADMIN are in the + * process Effective set in the case when euid is non-root. If there is a + * match, LSTCHK_NETADM or LSTCHK_SYSADM is unset respectively from + * global.last_checks to avoid warning due to global.last_checks verifications + * later at the process init stage. + * If there is no any supported by haproxy capability in the process Effective + * set, try to check the process Permitted set. In this case we promote from + * Permitted set to Effective only the capabilities, that were marked by user + * via 'capset' keyword in the global section (caplist). If there is match with + * caplist and CAP_NET_ADMIN/CAP_NET_RAW or CAP_SYS_ADMIN are in this list, + * LSTCHK_NETADM or/and LSTCHK_SYSADM will be unset by the same reason. + * We do this only if the current euid is non-root and there is no global.uid. + * Otherwise, the process will continue either to run under root, or it will do + * a transition to unprivileged user later in prepare_caps_for_setuid(), + * which specially manages its capabilities in that case. + * Always returns 0. Diagnostic warnings will be emitted only, if + * LSTCHK_NETADM/LSTCHK_SYSADM is presented in global.last_checks and some + * failures are encountered. + */ +int prepare_caps_from_permitted_set(int from_uid, int to_uid, const char *program_name) +{ + struct __user_cap_data_struct start_cap_data = { }; + struct __user_cap_header_struct cap_hdr = { + .pid = 0, /* current process */ + .version = _LINUX_CAPABILITY_VERSION_1, + }; + + /* started as root */ + if (!from_uid) + return 0; + + /* will change ruid and euid later in set_identity() */ + if (to_uid) + return 0; + + /* first, let's check if CAP_NET_ADMIN or CAP_NET_RAW is already in + * the process effective set. This may happen, when administrator sets + * these capabilities and the file effective bit on haproxy binary via + * setcap, see capabilities man page for details. + */ + if (capget(&cap_hdr, &start_cap_data) == -1) { + if (global.last_checks & (LSTCHK_NETADM | LSTCHK_SYSADM)) + ha_diag_warning("Failed to get process capabilities using capget(): %s. " + "Can't use capabilities that might be set on %s binary " + "by administrator.\n", strerror(errno), program_name); + return 0; + } + + if (start_cap_data.effective & ((1 << CAP_NET_ADMIN)|(1 << CAP_NET_RAW))) { + global.last_checks &= ~LSTCHK_NETADM; + return 0; + } + + if (start_cap_data.effective & ((1 << CAP_SYS_ADMIN))) { + global.last_checks &= ~LSTCHK_SYSADM; + return 0; + } + + /* second, try to check process permitted set, in this case caplist is + * necessary. Allows to put cap_net_bind_service in process effective + * set, if it is in the caplist and also presented in the binary + * permitted set. + */ + if (caplist && start_cap_data.permitted & caplist) { + start_cap_data.effective |= start_cap_data.permitted & caplist; + if (capset(&cap_hdr, &start_cap_data) == 0) { + if (caplist & ((1 << CAP_NET_ADMIN)|(1 << CAP_NET_RAW))) + global.last_checks &= ~LSTCHK_NETADM; + if (caplist & (1 << CAP_SYS_ADMIN)) + global.last_checks &= ~LSTCHK_SYSADM; + } else if (global.last_checks & (LSTCHK_NETADM|LSTCHK_SYSADM)) { + ha_diag_warning("Failed to put capabilities from caplist in %s " + "process Effective capabilities set using capset(): %s\n", + program_name, strerror(errno)); + } + } + + return 0; +} + /* try to apply capabilities before switching UID from <from_uid> to <to_uid>. * In practice we need to do this in 4 steps: * - set PR_SET_KEEPCAPS to preserve caps across the final setuid() @@ -61,7 +150,8 @@ static uint32_t caplist; * - set the effective and permitted caps again * - then the caller can safely call setuid() * On success LSTCHK_NETADM is unset from global.last_checks, if CAP_NET_ADMIN - * or CAP_NET_RAW was found in the caplist from config. + * or CAP_NET_RAW was found in the caplist from config. Same for + * LSTCHK_SYSADM, if CAP_SYS_ADMIN was found in the caplist from config. * We don't do this if the current euid is not zero or if the target uid * is zero. Returns 0 on success, negative on failure. Alerts may be emitted. */ @@ -107,6 +197,9 @@ int prepare_caps_for_setuid(int from_uid, int to_uid) if (caplist & ((1 << CAP_NET_ADMIN)|(1 << CAP_NET_RAW))) global.last_checks &= ~LSTCHK_NETADM; + if (caplist & (1 << CAP_SYS_ADMIN)) + global.last_checks &= ~LSTCHK_SYSADM; + /* all's good */ return 0; } |