summaryrefslogtreecommitdiffstats
path: root/src/linuxcap.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/linuxcap.c95
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;
}