diff options
Diffstat (limited to 'src/linuxcap.c')
-rw-r--r-- | src/linuxcap.c | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/src/linuxcap.c b/src/linuxcap.c new file mode 100644 index 0000000..919086c --- /dev/null +++ b/src/linuxcap.c @@ -0,0 +1,191 @@ +/* + * Minimal handling of Linux kernel capabilities + * + * Copyright 2000-2023 Willy Tarreau <w@1wt.eu> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +/* Depending on distros, some have capset(), others use the more complicated + * libcap. Let's stick to what we need and the kernel documents (capset). + * Note that prctl is needed here. + */ +#include <linux/capability.h> +#include <sys/prctl.h> +#include <errno.h> +#include <unistd.h> +#include <syscall.h> + +#include <haproxy/api.h> +#include <haproxy/cfgparse.h> +#include <haproxy/errors.h> +#include <haproxy/tools.h> + +/* supported names, zero-terminated */ +static const struct { + int cap; + const char *name; +} known_caps[] = { +#ifdef CAP_NET_RAW + { CAP_NET_RAW, "cap_net_raw" }, +#endif +#ifdef CAP_NET_ADMIN + { CAP_NET_ADMIN, "cap_net_admin" }, +#endif +#ifdef CAP_NET_BIND_SERVICE + { CAP_NET_BIND_SERVICE, "cap_net_bind_service" }, +#endif + /* must be last */ + { 0, 0 } +}; + +/* 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); +} + +/* defaults to zero, i.e. we don't keep any cap after setuid() */ +static uint32_t caplist; + +/* 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() + * - set the effective and permitted caps ; + * - switch euid to non-zero + * - set the effective and permitted caps again + * - then the caller can safely call setuid() + * 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 or warnings + * may be emitted. + */ +int prepare_caps_for_setuid(int from_uid, int to_uid) +{ + struct __user_cap_data_struct cap_data = { }; + struct __user_cap_header_struct cap_hdr = { + .pid = 0, /* current process */ + .version = _LINUX_CAPABILITY_VERSION_1, + }; + + if (from_uid != 0) + return 0; + + if (!to_uid) + return 0; + + if (!caplist) + return 0; + + if (prctl(PR_SET_KEEPCAPS, 1) == -1) { + ha_alert("Failed to preserve capabilities using prctl(): %s\n", strerror(errno)); + return -1; + } + + cap_data.effective = cap_data.permitted = caplist | (1 << CAP_SETUID); + if (capset(&cap_hdr, &cap_data) == -1) { + ha_alert("Failed to preset the capabilities to preserve using capset(): %s\n", strerror(errno)); + return -1; + } + + if (seteuid(to_uid) == -1) { + ha_alert("Failed to set effective uid to %d: %s\n", to_uid, strerror(errno)); + return -1; + } + + cap_data.effective = cap_data.permitted = caplist | (1 << CAP_SETUID); + if (capset(&cap_hdr, &cap_data) == -1) { + ha_alert("Failed to set the final capabilities using capset(): %s\n", strerror(errno)); + return -1; + } + /* all's good */ + return 0; +} + +/* finalize the capabilities after setuid(). The most important is to drop the + * CAP_SET_SETUID capability, which would otherwise allow to switch back to any + * UID and recover everything. + */ +int finalize_caps_after_setuid(int from_uid, int to_uid) +{ + struct __user_cap_data_struct cap_data = { }; + struct __user_cap_header_struct cap_hdr = { + .pid = 0, /* current process */ + .version = _LINUX_CAPABILITY_VERSION_1, + }; + + if (from_uid != 0) + return 0; + + if (!to_uid) + return 0; + + if (!caplist) + return 0; + + cap_data.effective = cap_data.permitted = caplist; + if (capset(&cap_hdr, &cap_data) == -1) { + ha_alert("Failed to drop the setuid capability using capset(): %s\n", strerror(errno)); + return -1; + } + /* all's good */ + return 0; +} + +/* parse the "setcap" global keyword. Returns -1 on failure, 0 on success. */ +static int cfg_parse_global_setcap(char **args, int section_type, + struct proxy *curpx, const struct proxy *defpx, + const char *file, int line, char **err) +{ + char *name = args[1]; + char *next; + uint32_t caps = 0; + int id; + + if (!*name) { + memprintf(err, "'%s' : missing capability name(s). ", args[0]); + goto dump_caps; + } + + while (name && *name) { + next = strchr(name, ','); + if (next) + *(next++) = '\0'; + + for (id = 0; known_caps[id].cap; id++) { + if (strcmp(name, known_caps[id].name) == 0) { + caps |= 1U << known_caps[id].cap; + break; + } + } + + if (!known_caps[id].cap) { + memprintf(err, "'%s' : unsupported capability '%s'. ", args[0], args[1]); + goto dump_caps; + } + name = next; + } + + caplist |= caps; + return 0; + + + dump_caps: + memprintf(err, "%s Supported ones are: ", *err); + + for (id = 0; known_caps[id].cap; id++) + memprintf(err, "%s%s%s%s", *err, + id ? known_caps[id+1].cap ? ", " : " and " : "", + known_caps[id].name, known_caps[id+1].cap ? "" : "."); + return -1; +} + +static struct cfg_kw_list cfg_kws = {ILH, { + { CFG_GLOBAL, "setcap", cfg_parse_global_setcap }, + { 0, NULL, NULL } +}}; + +INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws); |