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