| |
@@ -1,17 +1,28 @@
|
| |
/* Copyright (C) 2011,2015 the GSS-PROXY contributors, see COPYING for license */
|
| |
|
| |
- #include <stdlib.h>
|
| |
- #include <sys/types.h>
|
| |
- #include <sys/stat.h>
|
| |
+ #include <config.h>
|
| |
+
|
| |
+ #include <errno.h>
|
| |
+ #include <fcntl.h>
|
| |
+ #include <grp.h>
|
| |
#include <locale.h>
|
| |
+ #include <pwd.h>
|
| |
#include <signal.h>
|
| |
- #include <fcntl.h>
|
| |
- #include <errno.h>
|
| |
+ #include <stdio.h>
|
| |
+ #include <stdlib.h>
|
| |
#include <string.h>
|
| |
+ #include <sys/stat.h>
|
| |
+ #include <sys/types.h>
|
| |
#include <unistd.h>
|
| |
- #include <stdio.h>
|
| |
- #include <pwd.h>
|
| |
- #include <grp.h>
|
| |
+
|
| |
+ #ifdef HAVE_CAP
|
| |
+
|
| |
+ #include <linux/capability.h>
|
| |
+ #include <sys/capability.h>
|
| |
+ #include <sys/prctl.h>
|
| |
+
|
| |
+ #endif
|
| |
+
|
| |
#include "gp_proxy.h"
|
| |
|
| |
void init_server(bool daemonize, int *wait_fd)
|
| |
@@ -218,10 +229,29 @@
|
| |
struct passwd *pw, pws;
|
| |
int ret;
|
| |
|
| |
- if (cfg->proxy_user == NULL) {
|
| |
- /* not dropping privs */
|
| |
- return 0;
|
| |
+ #ifdef HAVE_CAP
|
| |
+ /* When a thread that has a zero value for one or more of
|
| |
+ * its user IDs resets all of these values to nonzero
|
| |
+ * a permitted capability set is also cleared.
|
| |
+ *
|
| |
+ * Permitted capability set is used as a limiting superset
|
| |
+ * for the effective capabilities, which in turn are used
|
| |
+ * by the kernel to perform permission checks.
|
| |
+ *
|
| |
+ * This means that such a thread can never reacquire those
|
| |
+ * capabilities then.
|
| |
+ *
|
| |
+ * To change the default behavior SECBIT_KEEP_CAPS flag can
|
| |
+ * be used. This flag allows keeping permitted capability set
|
| |
+ * during UID switching. */
|
| |
+ ret = prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
|
| |
+ if (ret) {
|
| |
+ ret = errno;
|
| |
+ GPDEBUG("Failed to set keep capabilities: [%d:%s]\n",
|
| |
+ ret, gp_strerror(ret));
|
| |
+ return ret;
|
| |
}
|
| |
+ #endif
|
| |
|
| |
ret = getpwnam_r(cfg->proxy_user, &pws, buf, 2048, &pw);
|
| |
if (ret) {
|
| |
@@ -253,5 +283,166 @@
|
| |
return ret;
|
| |
}
|
| |
|
| |
+ #ifdef HAVE_CAP
|
| |
+ /* keep only CAP_SYS_PTRACE capability,
|
| |
+ * all the other are redundant */
|
| |
+ ret = drop_caps();
|
| |
+ if (ret) {
|
| |
+ return ret;
|
| |
+ }
|
| |
+
|
| |
+ /* restore SECBIT_KEEP_CAPS */
|
| |
+ if (prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0)) {
|
| |
+ ret = errno;
|
| |
+ GPDEBUG("Failed to reset keep capabilities: [%d:%s]\n",
|
| |
+ ret, gp_strerror(ret));
|
| |
+ return ret;
|
| |
+ }
|
| |
+ #endif
|
| |
+
|
| |
return 0;
|
| |
}
|
| |
+
|
| |
+ #ifdef HAVE_CAP
|
| |
+ /* The capability bounding set is a security mechanism that is
|
| |
+ * used to limit the file permitted capabilities. To prevent
|
| |
+ * applying any of them capability bounding set has to be
|
| |
+ * constrained.
|
| |
+ *
|
| |
+ * To drop capabilities from the bounding set a thread has to
|
| |
+ * have CAP_SETPCAP capability. */
|
| |
+ int clear_bound_caps()
|
| |
+ {
|
| |
+ cap_t caps = NULL;
|
| |
+ cap_value_t cap = 0;
|
| |
+ const cap_value_t setpcap_list[] = { CAP_SETPCAP };
|
| |
+ int ret;
|
| |
+
|
| |
+ /* obtain a copy of current process capability sets
|
| |
+ * to raise CAP_SETPCAP */
|
| |
+ caps = cap_get_proc();
|
| |
+ if (caps == NULL) {
|
| |
+ ret = errno;
|
| |
+ GPDEBUG("Failed to get current capabilities: [%d:%s]\n",
|
| |
+ ret, gp_strerror(ret));
|
| |
+ goto done;
|
| |
+ }
|
| |
+
|
| |
+ /* raise CAP_SETPCAP within an effective set */
|
| |
+ if (cap_set_flag(caps, CAP_EFFECTIVE, 1, setpcap_list, CAP_SET) == -1) {
|
| |
+ ret = errno;
|
| |
+ GPDEBUG("Failed to raise setpcap capability flag: [%d:%s]\n",
|
| |
+ ret, gp_strerror(ret));
|
| |
+ goto done;
|
| |
+ }
|
| |
+
|
| |
+ /* apply back our capability sets to the current process */
|
| |
+ if (cap_set_proc(caps) == -1) {
|
| |
+ ret = errno;
|
| |
+ GPDEBUG("Failed to set capabilities: [%d:%s]\n",
|
| |
+ ret, gp_strerror(ret));
|
| |
+ goto done;
|
| |
+ }
|
| |
+
|
| |
+ /* having CAP_SETPCAP within an effective set completely drop
|
| |
+ * the bounding set capability */
|
| |
+ while (CAP_IS_SUPPORTED(cap)) {
|
| |
+ if (cap_drop_bound(cap) != 0) {
|
| |
+ ret = errno;
|
| |
+ GPDEBUG("Failed to drop bounding set capability: [%d:%s]\n",
|
| |
+ ret, gp_strerror(ret));
|
| |
+ goto done;
|
| |
+ }
|
| |
+ cap++;
|
| |
+ }
|
| |
+ ret = 0;
|
| |
+
|
| |
+ done:
|
| |
+ if (caps && cap_free(caps) == -1) {
|
| |
+ ret = errno;
|
| |
+ GPDEBUG("Failed to free capability state: [%d:%s]\n",
|
| |
+ ret, gp_strerror(ret));
|
| |
+ }
|
| |
+ return ret;
|
| |
+ }
|
| |
+
|
| |
+ /* To serve a 'program =' functionality ("If specified, this
|
| |
+ * service will only match when the program being run is the
|
| |
+ * specified string.") a non-privileged user has to have a
|
| |
+ * read permission on "/proc/[PID]/exe". This can be achieved
|
| |
+ * by CAP_SYS_PTRACE capability.
|
| |
+ *
|
| |
+ * For now thread has an effective capability set inherited from
|
| |
+ * privileged user because of SECBIT_KEEP_CAPS flag. But required
|
| |
+ * is only CAP_SYS_PTRACE within the effective and permitted sets.
|
| |
+ * It needs to restrict redundant privileged capabilities of a
|
| |
+ * non-privileged user. */
|
| |
+ int drop_caps()
|
| |
+ {
|
| |
+ cap_t caps = NULL;
|
| |
+ int ret;
|
| |
+ const cap_value_t ptrace_list[] = { CAP_SYS_PTRACE };
|
| |
+
|
| |
+ /* to limit a set of file permitted capabilities completely
|
| |
+ * drop the bounding set */
|
| |
+ ret = clear_bound_caps();
|
| |
+ if (ret) {
|
| |
+ goto done;
|
| |
+ }
|
| |
+
|
| |
+ ret = CAP_IS_SUPPORTED(CAP_SYS_PTRACE);
|
| |
+ if (ret == -1) {
|
| |
+ ret = errno;
|
| |
+ GPDEBUG("Failed to check if capability is supported: [%d:%s]\n",
|
| |
+ ret, gp_strerror(ret));
|
| |
+ goto done;
|
| |
+ } else if (!ret) {
|
| |
+ GPDEBUG("Capability CAPS_SYS_PTRACE is not supported\n");
|
| |
+ ret = EINVAL;
|
| |
+ goto done;
|
| |
+ }
|
| |
+
|
| |
+ /* allocates a clear capability state */
|
| |
+ caps = cap_init();
|
| |
+ if (caps == NULL) {
|
| |
+ ret = errno;
|
| |
+ GPDEBUG("Failed to init capabilities: [%d:%s]\n",
|
| |
+ ret, gp_strerror(ret));
|
| |
+ goto done;
|
| |
+ }
|
| |
+
|
| |
+ /* to raise CAP_SYS_PTRACE within the effective set a same
|
| |
+ * capability has to be present within the permitted one */
|
| |
+ if (cap_set_flag(caps, CAP_PERMITTED, 1, ptrace_list, CAP_SET) == -1) {
|
| |
+ ret = errno;
|
| |
+ GPDEBUG("Failed to set permitted ptrace capability flag: [%d:%s]\n",
|
| |
+ ret, gp_strerror(ret));
|
| |
+ goto done;
|
| |
+ }
|
| |
+
|
| |
+ /* raise CAP_SYS_PTRACE within the effective set */
|
| |
+ if (cap_set_flag(caps, CAP_EFFECTIVE, 1, ptrace_list, CAP_SET) == -1) {
|
| |
+ ret = errno;
|
| |
+ GPDEBUG("Failed to set effective ptrace capability flag: [%d:%s]\n",
|
| |
+ ret, gp_strerror(ret));
|
| |
+ goto done;
|
| |
+ }
|
| |
+
|
| |
+ /* apply our new capability sets to the current process */
|
| |
+ if (cap_set_proc(caps) == -1) {
|
| |
+ ret = errno;
|
| |
+ GPDEBUG("Failed to set capabilities: [%d:%s]\n",
|
| |
+ ret, gp_strerror(ret));
|
| |
+ goto done;
|
| |
+ }
|
| |
+ ret = 0;
|
| |
+
|
| |
+ done:
|
| |
+ if (caps && cap_free(caps) == -1) {
|
| |
+ ret = errno;
|
| |
+ GPDEBUG("Failed to free capability state: [%d:%s]\n",
|
| |
+ ret, gp_strerror(ret));
|
| |
+ }
|
| |
+ return ret;
|
| |
+ }
|
| |
+ #endif
|
| |
A non-root user should have CAP_SYS_PTRACE capability to
read '/proc/[PID]/exe'.
Actual capabilities become as expected:
Reading of "/proc/[PID]/exe" is successful.
Fixes: https://pagure.io/gssproxy/issue/239
Signed-off-by: Stanislav Levin slev@altlinux.org