From 32ded3e0172e0fae89cf70965e1c0406c1db883b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
Date: Tue, 2 Apr 2019 10:13:21 +0200
Subject: [PATCH 1/7] High: libservices: fix use-after-free wrt. alert handling
This could possibly lead to unsolicited information disclosure by the
means of standard output of the immediately preceding agent/resource
execution leaking into the log stream under some circumstances.
It was hence assigned CVE-2019-3885.
The provoked pathological state of pacemaker-execd daemon progresses
towards crashing it for hitting segmentation fault.
---
lib/services/services.c | 40 +---------------------------------------
lib/services/services_linux.c | 35 +++++++++++++++++++++++++++++++----
2 files changed, 32 insertions(+), 43 deletions(-)
diff --git a/lib/services/services.c b/lib/services/services.c
index 313567f..d3537d0 100644
--- a/lib/services/services.c
+++ b/lib/services/services.c
@@ -373,35 +373,6 @@ services_action_user(svc_action_t *op, const char *user)
return crm_user_lookup(user, &(op->opaque->uid), &(op->opaque->gid));
}
-static void
-set_alert_env(gpointer key, gpointer value, gpointer user_data)
-{
- int rc;
-
- if (value) {
- rc = setenv(key, value, 1);
- } else {
- rc = unsetenv(key);
- }
-
- if (rc < 0) {
- crm_perror(LOG_ERR, "setenv %s=%s",
- (char*)key, (value? (char*)value : ""));
- } else {
- crm_trace("setenv %s=%s", (char*)key, (value? (char*)value : ""));
- }
-}
-
-static void
-unset_alert_env(gpointer key, gpointer value, gpointer user_data)
-{
- if (unsetenv(key) < 0) {
- crm_perror(LOG_ERR, "unset %s", (char*)key);
- } else {
- crm_trace("unset %s", (char*)key);
- }
-}
-
/*!
* \brief Execute an alert agent action
*
@@ -416,18 +387,9 @@ unset_alert_env(gpointer key, gpointer value, gpointer user_data)
gboolean
services_alert_async(svc_action_t *action, void (*cb)(svc_action_t *op))
{
- gboolean responsible;
-
action->synchronous = false;
action->opaque->callback = cb;
- if (action->params) {
- g_hash_table_foreach(action->params, set_alert_env, NULL);
- }
- responsible = services_os_action_execute(action);
- if (action->params) {
- g_hash_table_foreach(action->params, unset_alert_env, NULL);
- }
- return responsible;
+ return services_os_action_execute(action);
}
#if SUPPORT_DBUS
diff --git a/lib/services/services_linux.c b/lib/services/services_linux.c
index a04a8f9..90c1f44 100644
--- a/lib/services/services_linux.c
+++ b/lib/services/services_linux.c
@@ -160,6 +160,25 @@ set_ocf_env_with_prefix(gpointer key, gpointer value, gpointer user_data)
set_ocf_env(buffer, value, user_data);
}
+static void
+set_alert_env(gpointer key, gpointer value, gpointer user_data)
+{
+ int rc;
+
+ if (value != NULL) {
+ rc = setenv(key, value, 1);
+ } else {
+ rc = unsetenv(key);
+ }
+
+ if (rc < 0) {
+ crm_perror(LOG_ERR, "setenv %s=%s",
+ (char*)key, (value? (char*)value : ""));
+ } else {
+ crm_trace("setenv %s=%s", (char*)key, (value? (char*)value : ""));
+ }
+}
+
/*!
* \internal
* \brief Add environment variables suitable for an action
@@ -169,12 +188,20 @@ set_ocf_env_with_prefix(gpointer key, gpointer value, gpointer user_data)
static void
add_action_env_vars(const svc_action_t *op)
{
- if (safe_str_eq(op->standard, PCMK_RESOURCE_CLASS_OCF) == FALSE) {
- return;
+ void (*env_setter)(gpointer, gpointer, gpointer) = NULL;
+ if (op->agent == NULL) {
+ env_setter = set_alert_env; /* we deal with alert handler */
+
+ } else if (safe_str_eq(op->standard, PCMK_RESOURCE_CLASS_OCF)) {
+ env_setter = set_ocf_env_with_prefix;
}
- if (op->params) {
- g_hash_table_foreach(op->params, set_ocf_env_with_prefix, NULL);
+ if (env_setter != NULL && op->params != NULL) {
+ g_hash_table_foreach(op->params, env_setter, NULL);
+ }
+
+ if (env_setter == NULL || env_setter == set_alert_env) {
+ return;
}
set_ocf_env("OCF_RA_VERSION_MAJOR", "1", NULL);
--
1.8.3.1
From 912f5d9ce983339e939e4cc55f27791f8c9baa18 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
Date: Mon, 15 Apr 2019 17:09:50 +0200
Subject: [PATCH 2/7] High: pacemakerd vs. IPC/procfs confused deputy
authenticity issue (0/4)
[0/4: make crm_pid_active more precise as to when detections fail]
It would be bad if the function claimed the process is not active
when the only obstacle in the detection process was that none of the
detection methods worked for a plain lack of permissions to apply
them. Also, do some other minor cleanup of the function and add its
documentation. As an additional measure, log spamming is kept at
minimum for repeated queries about the same PID.
---
include/crm/common/internal.h | 21 +++++++++++++
lib/common/pid.c | 68 +++++++++++++++++++++++++++----------------
2 files changed, 64 insertions(+), 25 deletions(-)
diff --git a/include/crm/common/internal.h b/include/crm/common/internal.h
index f2944f5..6775d14 100644
--- a/include/crm/common/internal.h
+++ b/include/crm/common/internal.h
@@ -49,7 +49,28 @@ void crm_schema_cleanup(void);
/* internal functions related to process IDs (from pid.c) */
+/*!
+ * \internal
+ * \brief Detect if process per PID and optionally exe path (component) exists
+ *
+ * \param[in] pid PID of process assumed alive, disproving of which to try
+ * \param[in] daemon exe path (component) to possibly match with procfs entry
+ *
+ * \return -1 on invalid PID specification, -2 when the calling process has no
+ * (is refused an) ability to (dis)prove the predicate,
+ * 0 if the negation of the predicate is confirmed (check-through-kill
+ * indicates so, or the subsequent check-through-procfs-match on
+ * \p daemon when provided and procfs available at the standard path),
+ * 1 if it cannot be disproved (reliably [modulo race conditions]
+ * when \p daemon provided, procfs available at the standard path
+ * and the calling process has permissions to access the respective
+ * procfs location, less so otherwise, since mere check-through-kill
+ * is exercised without powers to exclude PID recycled in the interim).
+ *
+ * \note This function cannot be used to verify \e authenticity of the process.
+ */
int crm_pid_active(long pid, const char *daemon);
+
long crm_pidfile_inuse(const char *filename, long mypid, const char *daemon);
long crm_read_pidfile(const char *filename);
int crm_lock_pidfile(const char *filename, const char *name);
diff --git a/lib/common/pid.c b/lib/common/pid.c
index 803799e..2439680 100644
--- a/lib/common/pid.c
+++ b/lib/common/pid.c
@@ -1,5 +1,7 @@
/*
- * Copyright 2004-2018 Andrew Beekhof <andrew@beekhof.net>
+ * Copyright 2004-2019 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
@@ -20,17 +22,21 @@
int
crm_pid_active(long pid, const char *daemon)
{
+ static int last_asked_pid = 0; /* log spam prevention */
+#if SUPPORT_PROCFS
static int have_proc_pid = 0;
+#else
+ static int have_proc_pid = -1;
+#endif
+ int rc = 0;
if (have_proc_pid == 0) {
+ /* evaluation of /proc/PID/exe applicability via self-introspection */
char proc_path[PATH_MAX], exe_path[PATH_MAX];
-
- // Make sure pid hasn't been reused by another process
snprintf(proc_path, sizeof(proc_path), "/proc/%lu/exe",
- (long unsigned int)getpid());
-
+ (long unsigned int) getpid());
have_proc_pid = 1;
- if (readlink(proc_path, exe_path, PATH_MAX - 1) < 0) {
+ if (readlink(proc_path, exe_path, sizeof(exe_path) - 1) < 0) {
have_proc_pid = -1;
}
}
@@ -38,40 +44,52 @@ crm_pid_active(long pid, const char *daemon)
if (pid <= 0) {
return -1;
- } else if ((kill(pid, 0) < 0) && (errno == ESRCH)) {
- return 0;
+ } else if ((rc = kill(pid, 0)) < 0 && errno == ESRCH) {
+ return 0; /* no such PID detected */
- } else if ((daemon == NULL) || (have_proc_pid == -1)) {
- return 1;
+ } else if (rc < 0 && have_proc_pid == -1) {
+ if (last_asked_pid != pid) {
+ crm_info("Cannot examine PID %ld: %s", pid, strerror(errno));
+ last_asked_pid = pid;
+ }
+ return -2; /* errno != ESRCH */
+
+ } else if (rc == 0 && (daemon == NULL || have_proc_pid == -1)) {
+ return 1; /* kill as the only indicator, cannot double check */
} else {
- int rc = 0;
+ /* make sure PID hasn't been reused by another process
+ XXX: might still be just a zombie, which could confuse decisions */
+ bool checked_through_kill = (rc == 0);
char proc_path[PATH_MAX], exe_path[PATH_MAX], myexe_path[PATH_MAX];
-
- // Make sure pid hasn't been reused by another process
snprintf(proc_path, sizeof(proc_path), "/proc/%ld/exe", pid);
- rc = readlink(proc_path, exe_path, PATH_MAX - 1);
+ rc = readlink(proc_path, exe_path, sizeof(exe_path) - 1);
if ((rc < 0) && (errno == EACCES)) {
- crm_perror(LOG_INFO, "Could not read from %s", proc_path);
- return 1;
+ if (last_asked_pid != pid) {
+ crm_info("Could not read from %s: %s", proc_path,
+ strerror(errno));
+ last_asked_pid = pid;
+ }
+ return checked_through_kill ? 1 : -2;
} else if (rc < 0) {
- crm_perror(LOG_ERR, "Could not read from %s", proc_path);
- return 0;
+ if (last_asked_pid != pid) {
+ crm_err("Could not read from %s: %s (%d)", proc_path,
+ strerror(errno), errno);
+ last_asked_pid = pid;
+ }
+ return 0; /* most likely errno == ENOENT */
}
-
- exe_path[rc] = 0;
+ exe_path[rc] = '\0';
if (daemon[0] != '/') {
- rc = snprintf(myexe_path, sizeof(proc_path), CRM_DAEMON_DIR"/%s",
+ rc = snprintf(myexe_path, sizeof(myexe_path), CRM_DAEMON_DIR"/%s",
daemon);
- myexe_path[rc] = 0;
} else {
- rc = snprintf(myexe_path, sizeof(proc_path), "%s", daemon);
- myexe_path[rc] = 0;
+ rc = snprintf(myexe_path, sizeof(myexe_path), "%s", daemon);
}
- if (strcmp(exe_path, myexe_path) == 0) {
+ if (rc > 0 && rc < sizeof(myexe_path) && !strcmp(exe_path, myexe_path)) {
return 1;
}
}
--
1.8.3.1
From 1148f45da977113dff588cdd1cfebb7a47760b32 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
Date: Mon, 15 Apr 2019 17:10:07 +0200
Subject: [PATCH 3/7] High: pacemakerd vs. IPC/procfs confused deputy
authenticity issue (1/4)
[1/4: new helpers to allow IPC client side to authenticate the server]
The title problem here could possibly lead to local privilege escalation
up to the root's level (and implicitly unguarded by some additional
protection layers like SELinux unless the defaults constrained further).
Main problem is that the authenticity assumptions were built on two,
seemingly mutually supporting legs leading to two CVEs assigned:
* procfs (mere process existence and right path to binary check)
used to verify (this part was assigned CVE-2018-16878), and
* one-way only client-server authentication, putting the client
here at the mercy of the server not necessarily cross-verified
per the above point if at all (this part was assigned
CVE-2018-16877)
whereas these two were in fact orthogonal, tearing security
assumptions about the "passive influencers" in the pacemaker's daemon
resilience-friendly constellation (orchestrated by the main of them,
pacemakerd) apart. Moreover, procfs-based approach is discouraged
for other reasons.
The layout of the basic fix is as follows:
* 1/4: new helpers to allow IPC client side to authenticate the server
(this commit, along with unifying subsequent solution for
both CVEs)
* 2/4: pacemakerd to trust pre-existing processes via new checks instead
(along with unifying solution for both CVEs)
* 3/4: other daemons to authenticate IPC servers of fellow processes
(along with addressing CVE-2018-16877 alone, for parts of
pacemaker not covered earlier)
* 4/4: CPG users to be careful about now-more-probable rival processes
(this is merely to mitigate corner case fallout from the new
approaches taken to face CVE-2018-16878 in particular;
courtesy of Yan Gao of SUSE for the reporting this)
With "basic", it is meant that it constitutes a self-contained best
effort solution with some compromises that can only be overcome with the
assistance of IPC library, libqb, as is also elaborated in messages of
remaining "fix" commits. Beside that, also conventional encapsulation
of server-by-client authentication would be useful, but lack thereof
is not an obstacle (more so should there by any security related
neglectations on the IPC client side and its connection initiating
arrangement within libqb that has a potential to strike as early as
when the authenticity of the server side is yet to be examined).
One extra kludge that's introduced for FreeBSD lacking Unix socket to
remote peer PID mapping is masquerading such an unspecified PID with
value of 1, since that shall always be around as "init" task and,
deferring to proof by contradiction, cannot be pacemakerd-spawned
child either even if PID 1 was pacemakerd (and running such a child
alone is rather nonsensical). The code making decisions based on that
value must acknowledge this craze and refrain from killing/signalling
the underlying process on this platform (but shall in general follow
the same elsewhere, keep in mind systemd socket-based activation for
instance, which would end up in such a situation easily!).
---
configure.ac | 43 +++++++++++
include/crm/common/Makefile.am | 3 +-
include/crm/common/ipc.h | 40 ++++++++++-
include/crm/common/ipc_internal.h | 69 ++++++++++++++++++
lib/common/ipc.c | 145 +++++++++++++++++++++++++++++++++++++-
5 files changed, 296 insertions(+), 4 deletions(-)
create mode 100644 include/crm/common/ipc_internal.h
diff --git a/configure.ac b/configure.ac
index f272587..7228989 100644
--- a/configure.ac
+++ b/configure.ac
@@ -425,6 +425,48 @@ do
fi
done
+us_auth=
+AC_CHECK_HEADER([sys/socket.h], [
+ AC_CHECK_DECL([SO_PEERCRED], [
+ # Linux
+ AC_CHECK_TYPE([struct ucred], [
+ us_auth=peercred_ucred;
+ AC_DEFINE([US_AUTH_PEERCRED_UCRED], [1],
+ [Define if Unix socket auth method is
+ getsockopt(s, SO_PEERCRED, &ucred, ...)])
+ ], [
+ # OpenBSD
+ AC_CHECK_TYPE([struct sockpeercred], [
+ us_auth=localpeercred_sockepeercred;
+ AC_DEFINE([US_AUTH_PEERCRED_SOCKPEERCRED], [1],
+ [Define if Unix socket auth method is
+ getsockopt(s, SO_PEERCRED, &sockpeercred, ...)])
+ ], [], [[#include <sys/socket.h>]])
+ ], [[#define _GNU_SOURCE
+ #include <sys/socket.h>]])
+ ], [], [[#include <sys/socket.h>]])
+])
+
+if test -z "${us_auth}"; then
+ # FreeBSD
+ AC_CHECK_DECL([getpeereid], [
+ us_auth=getpeereid;
+ AC_DEFINE([US_AUTH_GETPEEREID], [1],
+ [Define if Unix socket auth method is
+ getpeereid(s, &uid, &gid)])
+ ], [
+ # Solaris/OpenIndiana
+ AC_CHECK_DECL([getpeerucred], [
+ us_auth=getpeerucred;
+ AC_DEFINE([US_AUTH_GETPEERUCRED], [1],
+ [Define if Unix socket auth method is
+ getpeercred(s, &ucred)])
+ ], [
+ AC_MSG_ERROR([No way to authenticate a Unix socket peer])
+ ], [[#include <ucred.h>]])
+ ])
+fi
+
dnl This OS-based decision-making is poor autotools practice;
dnl feature-based mechanisms are strongly preferred.
dnl
@@ -1845,3 +1887,4 @@ AC_MSG_RESULT([ LDFLAGS_HARDENED_EXE = ${LDFLAGS_HARDENED_EXE}])
AC_MSG_RESULT([ LDFLAGS_HARDENED_LIB = ${LDFLAGS_HARDENED_LIB}])
AC_MSG_RESULT([ Libraries = ${LIBS}])
AC_MSG_RESULT([ Stack Libraries = ${CLUSTERLIBS}])
+AC_MSG_RESULT([ Unix socket auth method = ${us_auth}])
diff --git a/include/crm/common/Makefile.am b/include/crm/common/Makefile.am
index 7400152..be19e5e 100644
--- a/include/crm/common/Makefile.am
+++ b/include/crm/common/Makefile.am
@@ -14,3 +14,4 @@ headerdir=$(pkgincludedir)/crm/common
header_HEADERS = xml.h ipc.h util.h iso8601.h mainloop.h logging.h results.h
noinst_HEADERS = cib_secrets.h ipcs.h internal.h alerts_internal.h \
- iso8601_internal.h remote_internal.h xml_internal.h
+ iso8601_internal.h remote_internal.h xml_internal.h \
+ ipc_internal.h
diff --git a/include/crm/common/ipc.h b/include/crm/common/ipc.h
index 84a3a71..3249662 100644
--- a/include/crm/common/ipc.h
+++ b/include/crm/common/ipc.h
@@ -1,5 +1,7 @@
/*
- * Copyright 2013-2018 Andrew Beekhof <andrew@beekhof.net>
+ * Copyright 2004-2019 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
@@ -75,6 +75,44 @@ uint32_t crm_ipc_buffer_flags(crm_ipc_t * client);
const char *crm_ipc_name(crm_ipc_t * client);
unsigned int crm_ipc_default_buffer_size(void);
+/*!
+ * \brief Check the authenticity of the IPC socket peer process
+ *
+ * If everything goes well, peer's authenticity is verified by the means
+ * of comparing against provided referential UID and GID (either satisfies),
+ * and the result of this check can be deduced from the return value.
+ * As an exception, detected UID of 0 ("root") satisfies arbitrary
+ * provided referential daemon's credentials.
+ *
+ * \param[in] sock IPC related, connected Unix socket to check peer of
+ * \param[in] refuid referential UID to check against
+ * \param[in] refgid referential GID to check against
+ * \param[out] gotpid to optionally store obtained PID of the peer
+ * (not available on FreeBSD, special value of 1
+ * used instead, and the caller is required to
+ * special case this value respectively)
+ * \param[out] gotuid to optionally store obtained UID of the peer
+ * \param[out] gotgid to optionally store obtained GID of the peer
+ *
+ * \return 0 if IPC related socket's peer is not authentic given the
+ * referential credentials (see above), 1 if it is,
+ * negative value on error (generally expressing -errno unless
+ * it was zero even on nonhappy path, -pcmk_err_generic is
+ * returned then; no message is directly emitted)
+ *
+ * \note While this function is tolerant on what constitutes authorized
+ * IPC daemon process (its effective user matches UID=0 or \p refuid,
+ * or at least its group matches \p refroup), either or both (in case
+ * of UID=0) mismatches on the expected credentials of such peer
+ * process \e shall be investigated at the caller when value of 1
+ * gets returned there, since higher-than-expected privileges in
+ * respect to the expected/intended credentials possibly violate
+ * the least privilege principle and may pose an additional risk
+ * (i.e. such accidental inconsistency shall be eventually fixed).
+ */
+int crm_ipc_is_authentic_process(int sock, uid_t refuid, gid_t refgid,
+ pid_t *gotpid, uid_t *gotuid, gid_t *gotgid);
+
/* Utils */
xmlNode *create_hello_message(const char *uuid, const char *client_name,
const char *major_version, const char *minor_version);
diff --git a/include/crm/common/ipc_internal.h b/include/crm/common/ipc_internal.h
new file mode 100644
index 0000000..41a6653
--- /dev/null
+++ b/include/crm/common/ipc_internal.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2019 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__IPC_INTERNAL_H
+#define PCMK__IPC_INTERNAL_H
+
+#include <sys/types.h>
+
+#include <crm_config.h> /* US_AUTH_GETPEEREID */
+
+
+/* denotes "non yieldable PID" on FreeBSD, or actual PID1 in scenarios that
+ require a delicate handling anyway (socket-based activation with systemd);
+ we can be reasonably sure that this PID is never possessed by the actual
+ child daemon, as it gets taken either by the proper init, or by pacemakerd
+ itself (i.e. this precludes anything else); note that value of zero
+ is meant to carry "unset" meaning, and better not to bet on/conditionalize
+ over signedness of pid_t */
+#define PCMK__SPECIAL_PID 1
+
+#if defined(US_AUTH_GETPEEREID)
+/* on FreeBSD, we don't want to expose "non-yieldable PID" (leading to
+ "IPC liveness check only") as its nominal representation, which could
+ cause confusion -- this is unambiguous as long as there's no
+ socket-based activation like with systemd (very improbable) */
+#define PCMK__SPECIAL_PID_AS_0(p) (((p) == PCMK__SPECIAL_PID) ? 0 : (p))
+#else
+#define PCMK__SPECIAL_PID_AS_0(p) (p)
+#endif
+
+/*!
+ * \internal
+ * \brief Check the authenticity and liveness of the process via IPC end-point
+ *
+ * When IPC daemon under given IPC end-point (name) detected, its authenticity
+ * is verified by the means of comparing against provided referential UID and
+ * GID, and the result of this check can be deduced from the return value.
+ * As an exception, referential UID of 0 (~ root) satisfies arbitrary
+ * detected daemon's credentials.
+ *
+ * \param[in] name IPC name to base the search on
+ * \param[in] refuid referential UID to check against
+ * \param[in] refgid referential GID to check against
+ * \param[out] gotpid to optionally store obtained PID of the found process
+ * upon returning 1 or -2
+ * (not available on FreeBSD, special value of 1,
+ * see PCMK__SPECIAL_PID, used instead, and the caller
+ * is required to special case this value respectively)
+ *
+ * \return 0 if no trace of IPC peer's liveness detected, 1 if it was,
+ * -1 on error, and -2 when the IPC blocked with unauthorized
+ * process (log message emitted in both latter cases)
+ *
+ * \note This function emits a log message also in case there isn't a perfect
+ * match in respect to \p reguid and/or \p refgid, for a possible
+ * least privilege principle violation.
+ *
+ * \see crm_ipc_is_authentic_process
+ */
+int pcmk__ipc_is_authentic_process_active(const char *name, uid_t refuid,
+ gid_t refgid, pid_t *gotpid);
+
+#endif
diff --git a/lib/common/ipc.c b/lib/common/ipc.c
index c582ccf..aa055d3 100644
--- a/lib/common/ipc.c
+++ b/lib/common/ipc.c
@@ -1,5 +1,7 @@
/*
- * Copyright 2004-2018 Andrew Beekhof <andrew@beekhof.net>
+ * Copyright 2004-2019 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
@@ -7,6 +9,17 @@
#include <crm_internal.h>
+#if defined(US_AUTH_PEERCRED_UCRED) || defined(US_AUTH_PEERCRED_SOCKPEERCRED)
+# ifdef US_AUTH_PEERCRED_UCRED
+# ifndef _GNU_SOURCE
+# define _GNU_SOURCE
+# endif
+# endif
+# include <sys/socket.h>
+#elif defined(US_AUTH_GETPEERUCRED)
+# include <ucred.h>
+#endif
+
#include <sys/param.h>
#include <stdio.h>
@@ -19,11 +32,13 @@
#include <fcntl.h>
#include <bzlib.h>
-#include <crm/crm.h>
+#include <crm/crm.h> /* indirectly: pcmk_err_generic */
#include <crm/msg_xml.h>
#include <crm/common/ipc.h>
#include <crm/common/ipcs.h>
+#include <crm/common/ipc_internal.h> /* PCMK__SPECIAL_PID* */
+
#define PCMK_IPC_VERSION 1
/* Evict clients whose event queue grows this large (by default) */
@@ -1394,6 +1409,132 @@ crm_ipc_send(crm_ipc_t * client, xmlNode * message, enum crm_ipc_flags flags, in
return rc;
}
+int
+crm_ipc_is_authentic_process(int sock, uid_t refuid, gid_t refgid,
+ pid_t *gotpid, uid_t *gotuid, gid_t *gotgid) {
+ int ret = 0;
+ pid_t found_pid = 0; uid_t found_uid = 0; gid_t found_gid = 0;
+#if defined(US_AUTH_PEERCRED_UCRED)
+ struct ucred ucred;
+ socklen_t ucred_len = sizeof(ucred);
+
+ if (!getsockopt(sock, SOL_SOCKET, SO_PEERCRED,
+ &ucred, &ucred_len)
+ && ucred_len == sizeof(ucred)) {
+ found_pid = ucred.pid; found_uid = ucred.uid; found_gid = ucred.gid;
+
+#elif defined(US_AUTH_PEERCRED_SOCKPEERCRED)
+ struct sockpeercred sockpeercred;
+ socklen_t sockpeercred_len = sizeof(sockpeercred);
+
+ if (!getsockopt(sock, SOL_SOCKET, SO_PEERCRED,
+ &sockpeercred, &sockpeercred_len)
+ && sockpeercred_len == sizeof(sockpeercred_len)) {
+ found_pid = sockpeercred.pid;
+ found_uid = sockpeercred.uid; found_gid = sockpeercred.gid;
+
+#elif defined(US_AUTH_GETPEEREID)
+ if (!getpeereid(sock, &found_uid, &found_gid)) {
+ found_pid = PCMK__SPECIAL_PID; /* cannot obtain PID (FreeBSD) */
+
+#elif defined(US_AUTH_GETPEERUCRED)
+ ucred_t *ucred;
+ if (!getpeerucred(sock, &ucred)) {
+ errno = 0;
+ found_pid = ucred_getpid(ucred);
+ found_uid = ucred_geteuid(ucred); found_gid = ucred_getegid(ucred);
+ ret = -errno;
+ ucred_free(ucred);
+ if (ret) {
+ return (ret < 0) ? ret : -pcmk_err_generic;
+ }
+
+#else
+# error "No way to authenticate a Unix socket peer"
+ errno = 0;
+ if (0) {
+#endif
+ if (gotpid != NULL) {
+ *gotpid = found_pid;
+ }
+ if (gotuid != NULL) {
+ *gotuid = found_uid;
+ }
+ if (gotgid != NULL) {
+ *gotgid = found_gid;
+ }
+ ret = (found_uid == 0 || found_uid == refuid || found_gid == refgid);
+ } else {
+ ret = (errno > 0) ? -errno : -pcmk_err_generic;
+ }
+
+ return ret;
+}
+
+int
+pcmk__ipc_is_authentic_process_active(const char *name, uid_t refuid,
+ gid_t refgid, pid_t *gotpid) {
+ static char last_asked_name[PATH_MAX / 2] = ""; /* log spam prevention */
+ int fd, ret = 0;
+ pid_t found_pid = 0; uid_t found_uid = 0; gid_t found_gid = 0;
+ qb_ipcc_connection_t *c;
+
+ if ((c = qb_ipcc_connect(name, 0)) == NULL) {
+ crm_info("Could not connect to %s IPC: %s", name, strerror(errno));
+
+ } else if ((ret = qb_ipcc_fd_get(c, &fd))) {
+ crm_err("Could not get fd from %s IPC: %s (%d)", name,
+ strerror(-ret), -ret);
+ ret = -1;
+
+ } else if ((ret = crm_ipc_is_authentic_process(fd, refuid, refgid,
+ &found_pid, &found_uid,
+ &found_gid)) < 0) {
+ if (ret == -pcmk_err_generic) {
+ crm_err("Could not get peer credentials from %s IPC", name);
+ } else {
+ crm_err("Could not get peer credentials from %s IPC: %s (%d)",
+ name, strerror(-ret), -ret);
+ }
+ ret = -1;
+
+ } else {
+ if (gotpid != NULL) {
+ *gotpid = found_pid;
+ }
+
+ if (!ret) {
+ crm_err("Daemon (IPC %s) effectively blocked with unauthorized"
+ " process %lld (uid: %lld, gid: %lld)",
+ name, (long long) PCMK__SPECIAL_PID_AS_0(found_pid),
+ (long long) found_uid, (long long) found_gid);
+ ret = -2;
+ } else if ((found_uid != refuid || found_gid != refgid)
+ && strncmp(last_asked_name, name, sizeof(last_asked_name))) {
+ if (!found_uid && refuid) {
+ crm_warn("Daemon (IPC %s) runs as root, whereas the expected"
+ " credentials are %lld:%lld, hazard of violating"
+ " the least privilege principle",
+ name, (long long) refuid, (long long) refgid);
+ } else {
+ crm_notice("Daemon (IPC %s) runs as %lld:%lld, whereas the"
+ " expected credentials are %lld:%lld, which may"
+ " mean a different set of privileges than expected",
+ name, (long long) found_uid, (long long) found_gid,
+ (long long) refuid, (long long) refgid);
+ }
+ memccpy(last_asked_name, name, '\0', sizeof(last_asked_name));
+ }
+ }
+
+ if (ret) { /* here, !ret only when we could not initially connect */
+ qb_ipcc_disconnect(c);
+ }
+
+ return ret;
+}
+
+
/* Utils */
xmlNode *
--
1.8.3.1
From 970736b1c7ad5c78cc5295a4231e546104d55893 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
Date: Tue, 16 Apr 2019 00:13:06 +0200
Subject: [PATCH 4/7] High: pacemakerd vs. IPC/procfs confused deputy
authenticity issue (2/4)
[2/4: pacemakerd to trust pre-existing processes via new checks instead]
In pacemakerd in the context of entrusting pre-existing processes,
we now resort to procfs-based solution only in boundary, fouled cases,
and primarily examine the credentials of the processes already
occupying known IPC end-points before adopting them.
The commit applies the new helpers from 1/1 so as to close the both
related sensitive problems, CVE-2018-16877 and CVE-2018-16878, in
a unified manner, this time limited to the main daemon of pacemaker
(pacemakerd).
To be noted that it is clearly not 100% for this purpose for still
allowing for TOCTTOU, but that's what commit (3/3) is meant to solve
for the most part, plus there may be optimizations solving this concern
as a side effect, but that requires an active assistance on the libqb
side (https://github.com/ClusterLabs/libqb/issues/325) since any
improvement on pacemaker side in isolation would be very
cumbersome if generally possible at all, but either way
means a new, soft compatibility encumberment.
As a follow-up to what was put in preceding 1/3 commit, PID of 1 tracked
as child's identification on FreeBSD (or when socket-based activation is
used with systemd) is treated specially, incl. special precaution with
child's PID discovered as 1 elsewhere.
v2: courtesy of Yan Gao of SUSE for early discovery and report for
what's primarily solved with 4/4 commit, in extension, child
daemons in the initialization phase coinciding with IPC-feasibility
based process scan in pacemakerd in a way that those are missed
(although they are to come up fully just moments later only
to interfere with naturally spawned ones) are now considered so
that if any native children later fail for said clash, the
pre-existing counterpart may get adopted instead of ending up
with repeated spawn-bury loop ad nauseam without real progress
(note that PCMK_fail_fast=true could possibly help, but that's
rather a big hammer not suitable for all the use cases, not
the ones we try to deal with gracefully here)
---
daemons/pacemakerd/pacemakerd.c | 406 ++++++++++++++++++++++++++++++++++------
1 file changed, 345 insertions(+), 61 deletions(-)
diff --git a/daemons/pacemakerd/pacemakerd.c b/daemons/pacemakerd/pacemakerd.c
index eca7f4d..bebf938 100644
--- a/daemons/pacemakerd/pacemakerd.c
+++ b/daemons/pacemakerd/pacemakerd.c
@@ -12,17 +12,23 @@
#include <pwd.h>
#include <grp.h>
+#include <poll.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/reboot.h>
+#include <crm/crm.h> /* indirectly: CRM_EX_* */
+#include <crm/cib/internal.h> /* cib_channel_ro */
#include <crm/msg_xml.h>
#include <crm/common/ipcs.h>
#include <crm/common/mainloop.h>
#include <crm/cluster/internal.h>
#include <crm/cluster.h>
+
+#include <crm/common/ipc_internal.h> /* PCMK__SPECIAL_PID*, ... */
+
#ifdef SUPPORT_COROSYNC
#include <corosync/cfg.h>
#endif
@@ -33,6 +39,7 @@
static gboolean pcmk_quorate = FALSE;
static gboolean fatal_error = FALSE;
static GMainLoop *mainloop = NULL;
+static bool global_keep_tracking = false;
#define PCMK_PROCESS_CHECK_INTERVAL 5
@@ -50,6 +57,7 @@ typedef struct pcmk_child_s {
const char *name;
const char *uid;
const char *command;
+ const char *endpoint; /* IPC server name */
gboolean active_before_startup;
} pcmk_child_t;
@@ -64,30 +72,38 @@ static pcmk_child_t pcmk_children[] = {
},
{
0, crm_proc_execd, 3, 0, TRUE, "pacemaker-execd",
- NULL, CRM_DAEMON_DIR "/pacemaker-execd"
+ NULL, CRM_DAEMON_DIR "/pacemaker-execd",
+ CRM_SYSTEM_LRMD
},
{
0, crm_proc_based, 1, 0, TRUE, "pacemaker-based",
- CRM_DAEMON_USER, CRM_DAEMON_DIR "/pacemaker-based"
+ CRM_DAEMON_USER, CRM_DAEMON_DIR "/pacemaker-based",
+ CIB_CHANNEL_RO
},
{
0, crm_proc_controld, 6, 0, TRUE, "pacemaker-controld",
- CRM_DAEMON_USER, CRM_DAEMON_DIR "/pacemaker-controld"
+ CRM_DAEMON_USER, CRM_DAEMON_DIR "/pacemaker-controld",
+ CRM_SYSTEM_CRMD
},
{
0, crm_proc_attrd, 4, 0, TRUE, "pacemaker-attrd",
- CRM_DAEMON_USER, CRM_DAEMON_DIR "/pacemaker-attrd"
+ CRM_DAEMON_USER, CRM_DAEMON_DIR "/pacemaker-attrd",
+ T_ATTRD
},
{
0, crm_proc_schedulerd, 5, 0, TRUE, "pacemaker-schedulerd",
- CRM_DAEMON_USER, CRM_DAEMON_DIR "/pacemaker-schedulerd"
+ CRM_DAEMON_USER, CRM_DAEMON_DIR "/pacemaker-schedulerd",
+ CRM_SYSTEM_PENGINE
},
{
0, crm_proc_fenced, 2, 0, TRUE, "pacemaker-fenced",
- NULL, CRM_DAEMON_DIR "/pacemaker-fenced"
+ NULL, CRM_DAEMON_DIR "/pacemaker-fenced",
+ "stonith-ng"
},
};
+static gboolean check_active_before_startup_processes(gpointer user_data);
+static int pcmk_child_active(pcmk_child_t *child);
static gboolean start_child(pcmk_child_t * child);
static gboolean update_node_processes(uint32_t id, const char *uname,
uint32_t procs);
@@ -130,14 +146,31 @@ pcmk_process_exit(pcmk_child_t * child)
}
if (shutdown_trigger) {
+ /* resume step-wise shutdown (returned TRUE yields no parallelizing) */
mainloop_set_trigger(shutdown_trigger);
+ /* intended to speed up propagating expected lay-off of the daemons? */
update_node_processes(local_nodeid, NULL, get_process_list());
- } else if (child->respawn && crm_is_true(getenv("PCMK_fail_fast"))) {
+ } else if (!child->respawn) {
+ /* nothing to do */
+
+ } else if (crm_is_true(getenv("PCMK_fail_fast"))) {
crm_err("Rebooting system because of %s", child->name);
pcmk_panic(__FUNCTION__);
- } else if (child->respawn) {
+ } else if (pcmk_child_active(child) == 1) {
+ crm_warn("One-off suppressing strict respawning of a child process %s,"
+ " appears alright per %s IPC end-point",
+ child->name, child->endpoint);
+ /* need to monitor how it evolves, and start new process if badly */
+ child->active_before_startup = TRUE;
+ if (!global_keep_tracking) {
+ global_keep_tracking = true;
+ g_timeout_add_seconds(PCMK_PROCESS_CHECK_INTERVAL,
+ check_active_before_startup_processes, NULL);
+ }
+
+ } else {
crm_notice("Respawning failed child process: %s", child->name);
start_child(child);
}
@@ -218,8 +251,13 @@ stop_child(pcmk_child_t * child, int signal)
signal = SIGTERM;
}
- if (child->command == NULL) {
- crm_debug("Nothing to do for child \"%s\"", child->name);
+ /* why to skip PID of 1?
+ - FreeBSD ~ how untrackable process behind IPC is masqueraded as
+ - elsewhere: how "init" task is designated; in particular, in systemd
+ arrangement of socket-based activation, this is pretty real */
+ if (child->command == NULL || child->pid == PCMK__SPECIAL_PID) {
+ crm_debug("Nothing to do for child \"%s\" (process %lld)",
+ child->name, (long long) PCMK__SPECIAL_PID_AS_0(child->pid));
return TRUE;
}
@@ -244,6 +282,11 @@ stop_child(pcmk_child_t * child, int signal)
static char *opts_default[] = { NULL, NULL };
static char *opts_vgrind[] = { NULL, NULL, NULL, NULL, NULL };
+/* TODO once libqb is taught to juggle with IPC end-points carried over as
+ bare file descriptor (https://github.com/ClusterLabs/libqb/issues/325)
+ it shall hand over these descriptors here if/once they are successfully
+ pre-opened in (presumably) pcmk_child_active, to avoid any remaining
+ room for races */
static gboolean
start_child(pcmk_child_t * child)
{
@@ -380,7 +423,10 @@ escalate_shutdown(gpointer data)
pcmk_child_t *child = data;
- if (child->pid) {
+ if (child->pid == PCMK__SPECIAL_PID) {
+ pcmk_process_exit(child);
+
+ } else if (child->pid) {
/* Use SIGSEGV instead of SIGKILL to create a core so we can see what it was up to */
crm_err("Child %s not terminating in a timely manner, forcing", child->name);
stop_child(child, SIGSEGV);
@@ -388,6 +434,8 @@ escalate_shutdown(gpointer data)
return FALSE;
}
+#define SHUTDOWN_ESCALATION_PERIOD 180000 /* 3m */
+
static gboolean
pcmk_shutdown_worker(gpointer user_data)
{
@@ -416,11 +464,24 @@ pcmk_shutdown_worker(gpointer user_data)
time_t now = time(NULL);
if (child->respawn) {
+ if (child->pid == PCMK__SPECIAL_PID) {
+ crm_warn("The process behind %s IPC cannot be"
+ " terminated, so either wait the graceful"
+ " period of %ld s for its native termination"
+ " if it vitally depends on some other daemons"
+ " going down in a controlled way already,"
+ " or locate and kill the correct %s process"
+ " on your own; set PCMK_fail_fast=1 to avoid"
+ " this altogether next time around",
+ child->name, (long) SHUTDOWN_ESCALATION_PERIOD,
+ child->command);
+ }
next_log = now + 30;
child->respawn = FALSE;
stop_child(child, SIGTERM);
if (phase < pcmk_children[PCMK_CHILD_CONTROLD].start_seq) {
- g_timeout_add(180000 /* 3m */ , escalate_shutdown, child);
+ g_timeout_add(SHUTDOWN_ESCALATION_PERIOD,
+ escalate_shutdown, child);
}
} else if (now >= next_log) {
@@ -702,7 +763,102 @@ mcp_chown(const char *path, uid_t uid, gid_t gid)
}
}
-#if SUPPORT_PROCFS
+/*!
+ * \internal
+ * \brief Check the liveness of the child based on IPC name and PID if tracked
+ *
+ * \param[inout] child Child tracked data
+ *
+ * \return 0 if no trace of child's liveness detected (while it is detectable
+ * to begin with, at least according to one of the two properties),
+ * 1 if everything is fine, 2 if it's up per PID, but not per IPC
+ * end-point (still starting?), -1 on error, and -2 when the child
+ * (its IPC) blocked with an unauthorized process (log message
+ * emitted in both latter cases)
+ *
+ * \note This function doesn't modify any of \p child members but \c pid,
+ * and is not actively toying with processes as such but invoking
+ * \c stop_child in one particular case (there's for some reason
+ * a different authentic holder of the IPC end-point).
+ */
+static int
+pcmk_child_active(pcmk_child_t *child) {
+ static uid_t cl_uid = 0;
+ static gid_t cl_gid = 0;
+ const uid_t root_uid = 0;
+ const gid_t root_gid = 0;
+ const uid_t *ref_uid;
+ const gid_t *ref_gid;
+ int ret = 0;
+ pid_t ipc_pid = 0;
+
+ if (child->endpoint == NULL
+ && (child->pid <= 0 || child->pid == PCMK__SPECIAL_PID)) {
+ crm_err("Cannot track child %s for missing both API end-point and PID",
+ child->name);
+ ret = -1; /* misuse of the function when child is not trackable */
+
+ } else if (child->endpoint != NULL) {
+
+ ref_uid = (child->uid != NULL) ? &cl_uid : &root_uid;
+ ref_gid = (child->uid != NULL) ? &cl_gid : &root_gid;
+
+ if (child->uid != NULL && !cl_uid && !cl_gid
+ && crm_user_lookup(CRM_DAEMON_USER, &cl_uid, &cl_gid) < 0) {
+ crm_err("Could not find user and group IDs for user %s",
+ CRM_DAEMON_USER);
+ ret = -1;
+ } else if ((ret = pcmk__ipc_is_authentic_process_active(child->endpoint,
+ *ref_uid, *ref_gid,
+ &ipc_pid)) < 0) {
+ /* game over */
+ } else if (child->pid <= 0) {
+ /* hit new child to be initialized, or reset to zero
+ and investigate further for ret == 0 */
+ child->pid = ipc_pid;
+ } else if (ipc_pid && child->pid != ipc_pid) {
+ /* ultimately strange for ret == 1; either way, investigate */
+ ret = 0;
+ }
+ }
+
+ if (!ret) {
+ /* when no IPC based liveness detected (incl. if ever a child without
+ IPC is tracked), or detected for a different _authentic_ process;
+ safe on FreeBSD since the only change possible from a proper child's
+ PID into "special" PID of 1 behind more loosely related process */
+ ret = crm_pid_active(child->pid, child->name);
+ if (ipc_pid && (ret != 1
+ || ipc_pid == PCMK__SPECIAL_PID
+ || crm_pid_active(ipc_pid, child->name) == 1)) {
+ if (ret == 1) {
+ /* assume there's no forking-while-retaining-IPC-socket
+ involved in the "children's" lifecycle, hence that the
+ tracking got out of sync purely because of some external
+ (esotheric?) forces (user initiated process "refresh" by
+ force? or intentionally racing on start-up, even?), and
+ that switching over to this other detected, authentic
+ instance with an IPC already in possession is a better
+ trade-off than "neutralizing" it first so as to give
+ either the original or possibly a new to-be-spawned
+ daemon process a leeway for operation, which would
+ otherwise have to be carried out */
+ /* not possessing IPC, afterall (what about corosync CPG?) */
+ stop_child(child, SIGKILL);
+ } else {
+ ret = 1;
+ }
+ child->pid = ipc_pid;
+ } else if (ret == 1) {
+ ret = 2; /* up per PID, but not per IPC (still starting?) */
+ } else if (!child->pid && ret == -1) {
+ ret = 0; /* correct -1 on FreeBSD from above back to 0 */
+ }
+ }
+
+ return ret;
+}
+
static gboolean
check_active_before_startup_processes(gpointer user_data)
{
@@ -719,12 +875,41 @@ check_active_before_startup_processes(gpointer user_data)
continue;
} else {
const char *name = pcmk_children[lpc].name;
-
- if (crm_pid_active(pcmk_children[lpc].pid, name) != 1) {
- crm_notice("Process %s terminated (pid=%d)",
- name, pcmk_children[lpc].pid);
- pcmk_process_exit(&(pcmk_children[lpc]));
- continue;
+ int ret;
+
+ switch ((ret = pcmk_child_active(&pcmk_children[lpc]))) {
+ case 1:
+ break;
+ case 0:
+ case 2: /* this very case: it was OK once already */
+ if (pcmk_children[lpc].respawn == TRUE) {
+ /* presumably after crash, hence critical */
+ crm_crit("Process %s terminated (pid=%lld)%s", \
+ name, (long long)
+ PCMK__SPECIAL_PID_AS_0(pcmk_children[lpc].pid),
+ ret ? ", at least per IPC end-point that went AWOL"
+ : "");
+ } else {
+ /* orderly shutdown */
+ crm_notice("Process %s terminated (pid=%lld)%s", \
+ name, (long long)
+ PCMK__SPECIAL_PID_AS_0(pcmk_children[lpc].pid),
+ ret ? ", at least per IPC end-point that went AWOL"
+ : "");
+ }
+ pcmk_process_exit(&(pcmk_children[lpc]));
+ continue;
+ default:
+ crm_crit("Unexpected value from pcmk_child_active:"
+ " %d (pid=%lld)", ret,
+ (long long) PCMK__SPECIAL_PID_AS_0(
+ pcmk_children[lpc].pid));
+ /* fall through */
+ case -1:
+ case -2:
+ /* message(s) already emitted */
+ crm_exit(CRM_EX_FATAL);
+ break; /* static analysis/noreturn */
}
}
/* at least one of the processes found at startup
@@ -733,57 +918,147 @@ check_active_before_startup_processes(gpointer user_data)
}
}
+ global_keep_tracking = keep_tracking;
return keep_tracking;
}
-#endif // SUPPORT_PROCFS
-static void
+/*!
+ * \internal
+ * \brief Initial one-off check of the pre-existing "child" processes
+ *
+ * With "child" process, we mean the subdaemon that defines an API end-point
+ * (all of them do as of the comment) -- the possible complement is skipped
+ * as it is deemed it has no such shared resources to cause conflicts about,
+ * hence it can presumably be started anew without hesitation.
+ * If that won't hold true in the future, the concept of a shared resource
+ * will have to be generalized beyond the API end-point.
+ *
+ * For boundary cases that the "child" is still starting (IPC end-point is yet
+ * to be witnessed), or more rarely (practically FreeBSD only), when there's
+ * a pre-existing "untrackable" authentic process, we give the situation some
+ * time to possibly unfold in the right direction, meaning that said socket
+ * will appear or the unattainable process will disappear per the observable
+ * IPC, respectively.
+ *
+ * \return 0 if no such "child" process found, positive number X when X
+ * "children" detected, -1 on an internal error, -2 when any
+ * would-be-used IPC is blocked with an unauthorized process
+ *
+ * \note Since this gets run at the very start, \c respawn_count fields
+ * for particular children get temporarily overloaded with "rounds
+ * of waiting" tracking, restored once we are about to finish with
+ * success (i.e. returning value >=0) and will remain unrestored
+ * otherwise. One way to suppress liveness detection logic for
+ * particular child is to set the said value to a negative number.
+ */
+#define WAIT_TRIES 4 /* together with interleaved sleeps, worst case ~ 1s */
+static int
find_and_track_existing_processes(void)
{
-#if SUPPORT_PROCFS
- DIR *dp;
- struct dirent *entry;
- bool start_tracker = FALSE;
- char entry_name[16];
-
- dp = opendir("/proc");
- if (!dp) {
- /* no proc directory to search through */
- crm_notice("Can not read /proc directory to track existing components");
- return;
- }
-
- while ((entry = readdir(dp)) != NULL) {
- int pid;
- int max = SIZEOF(pcmk_children);
- int i;
-
- if (crm_procfs_process_info(entry, entry_name, &pid) < 0) {
- continue;
- }
- for (i = 0; i < max; i++) {
- if ((pcmk_children[i].start_seq != 0)
- && !strncmp(entry_name, pcmk_children[i].name, 15)
- && (crm_pid_active(pid, NULL) == 1)) {
-
- crm_notice("Tracking existing %s process (pid=%d)",
- pcmk_children[i].name, pid);
- pcmk_children[i].pid = pid;
- pcmk_children[i].active_before_startup = TRUE;
- start_tracker = TRUE;
- break;
+ unsigned tracking = 0U;
+ bool wait_in_progress;
+ int cur;
+ size_t i, rounds;
+
+ for (rounds = 1; rounds <= WAIT_TRIES; rounds++) {
+ wait_in_progress = false;
+ for (i = 0; i < SIZEOF(pcmk_children); i++) {
+ if (!pcmk_children[i].endpoint
+ || pcmk_children[i].respawn_count < 0
+ || !(cur = pcmk_child_active(&pcmk_children[i]))) {
+ /* as a speculation, don't give up in the context of
+ pcmk_child_active check if there are more rounds to
+ come for other reasons, but don't artificially wait just
+ because of this, since we would preferably start ASAP */
+ continue;
+ }
+ pcmk_children[i].respawn_count = rounds;
+ switch (cur) {
+ case 1:
+ if (pcmk_children[i].pid == PCMK__SPECIAL_PID) {
+ if (crm_is_true(getenv("PCMK_fail_fast"))) {
+ crm_crit("Cannot reliably track pre-existing"
+ " authentic process behind %s IPC on this"
+ " platform and PCMK_fail_fast requested",
+ pcmk_children[i].endpoint);
+ return -1;
+ } else if (pcmk_children[i].respawn_count == WAIT_TRIES) {
+ crm_notice("Assuming pre-existing authentic, though"
+ " on this platform untrackable, process"
+ " behind %s IPC is stable (was in %d"
+ " previous samples) so rather than"
+ " bailing out (PCMK_fail_fast not"
+ " requested), we just switch to a less"
+ " optimal IPC liveness monitoring"
+ " (not very suitable for heavy load)",
+ pcmk_children[i].name, WAIT_TRIES - 1);
+ crm_warn("The process behind %s IPC cannot be"
+ " terminated, so the overall shutdown"
+ " will get delayed implicitly (%ld s),"
+ " which serves as a graceful period for"
+ " its native termination if it vitally"
+ " depends on some other daemons going"
+ " down in a controlled way already",
+ pcmk_children[i].name,
+ (long) SHUTDOWN_ESCALATION_PERIOD);
+ } else {
+ wait_in_progress = true;
+ crm_warn("Cannot reliably track pre-existing"
+ " authentic process behind %s IPC on this"
+ " platform, can still disappear in %d"
+ " attempt(s)", pcmk_children[i].endpoint,
+ WAIT_TRIES - pcmk_children[i].respawn_count);
+ continue;
+ }
+ }
+ crm_notice("Tracking existing %s process (pid=%lld)",
+ pcmk_children[i].name,
+ (long long) PCMK__SPECIAL_PID_AS_0(
+ pcmk_children[i].pid));
+ pcmk_children[i].respawn_count = -1; /* 0~keep watching */
+ pcmk_children[i].active_before_startup = TRUE;
+ tracking++;
+ break;
+ case 2:
+ if (pcmk_children[i].respawn_count == WAIT_TRIES) {
+ crm_crit("%s IPC end-point for existing authentic"
+ " process %lld did not (re)appear",
+ pcmk_children[i].endpoint,
+ (long long) PCMK__SPECIAL_PID_AS_0(
+ pcmk_children[i].pid));
+ return -1;
+ }
+ wait_in_progress = true;
+ crm_warn("Cannot find %s IPC end-point for existing"
+ " authentic process %lld, can still (re)appear"
+ " in %d attempts (?)",
+ pcmk_children[i].endpoint,
+ (long long) PCMK__SPECIAL_PID_AS_0(
+ pcmk_children[i].pid),
+ WAIT_TRIES - pcmk_children[i].respawn_count);
+ continue;
+ case -1:
+ case -2:
+ return cur; /* messages already emitted */
+ default:
+ crm_crit("Unexpected condition"CRM_XS"cur=%d", cur);
+ return -1; /* unexpected condition */
}
}
+ if (!wait_in_progress) {
+ break;
+ }
+ (void) poll(NULL, 0, 250); /* a bit for changes to possibly happen */
+ }
+ for (i = 0; i < SIZEOF(pcmk_children); i++) {
+ pcmk_children[i].respawn_count = 0; /* restore pristine state */
}
- if (start_tracker) {
- g_timeout_add_seconds(PCMK_PROCESS_CHECK_INTERVAL, check_active_before_startup_processes,
- NULL);
+ if (tracking) {
+ g_timeout_add_seconds(PCMK_PROCESS_CHECK_INTERVAL,
+ check_active_before_startup_processes, NULL);
}
- closedir(dp);
-#else
- crm_notice("No procfs support, so skipping check for existing components");
-#endif // SUPPORT_PROCFS
+ return (tracking > INT_MAX) ? INT_MAX : tracking;
}
static void
@@ -1091,7 +1366,16 @@ main(int argc, char **argv)
setenv("PCMK_watchdog", "false", 1);
}
- find_and_track_existing_processes();
+ switch (find_and_track_existing_processes()) {
+ case -1:
+ crm_crit("Internal fatality, see the log");
+ crm_exit(CRM_EX_FATAL);
+ case -2:
+ crm_crit("Blocked by foreign process, kill the offender");
+ crm_exit(CRM_EX_CANTCREAT);
+ default:
+ break;
+ };
cluster.destroy = mcp_cpg_destroy;
cluster.cpg.cpg_deliver_fn = mcp_cpg_deliver;
--
1.8.3.1
From 052e6045eea77685aabeed12c519c7c9eb9b5287 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
Date: Tue, 16 Apr 2019 00:13:31 +0200
Subject: [PATCH 5/7] High: pacemakerd vs. IPC/procfs confused deputy
authenticity issue (3/4)
[3/4: other daemons to authenticate IPC servers of fellow processes]
Now that CVE-2018-16877 issue alone is still only partially covered
based on the preceding commits in the set, put the server-by-client
authentication (enabled and 1/3 and partially sported in 2/3) into
practice widely amongst the communicating pacemaker child daemons and
towards CPG API provided by 3rd party but principally using the same
underlying IPC mechanism facilitated by libqb, and consequently close
the remaining "big gap".
As a small justification to introducing yet another "return
value" int variable, type-correctness is restored for those
that shall be cs_error_t to begin with.
---
daemons/pacemakerd/pcmkd_corosync.c | 61 +++++++++++-
lib/cluster/corosync.c | 178 ++++++++++++++++++++++++++++++------
lib/cluster/cpg.c | 81 +++++++++++++---
lib/common/ipc.c | 43 ++++++++-
4 files changed, 317 insertions(+), 46 deletions(-)
diff --git a/daemons/pacemakerd/pcmkd_corosync.c b/daemons/pacemakerd/pcmkd_corosync.c
index c73a1f5..65595fa 100644
--- a/daemons/pacemakerd/pcmkd_corosync.c
+++ b/daemons/pacemakerd/pcmkd_corosync.c
@@ -1,5 +1,7 @@
/*
- * Copyright 2010-2018 Andrew Beekhof <andrew@beekhof.net>
+ * Copyright 2010-2019 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
@@ -21,8 +23,11 @@
#include <corosync/cmap.h>
#include <crm/cluster/internal.h>
+#include <crm/common/ipc.h> /* for crm_ipc_is_authentic_process */
#include <crm/common/mainloop.h>
+#include <crm/common/ipc_internal.h> /* PCMK__SPECIAL_PID* */
+
enum cluster_type_e stack = pcmk_cluster_unknown;
static corosync_cfg_handle_t cfg_handle;
@@ -91,7 +96,10 @@ gboolean
cluster_connect_cfg(uint32_t * nodeid)
{
cs_error_t rc;
- int fd = 0, retries = 0;
+ int fd = -1, retries = 0, rv;
+ uid_t found_uid = 0;
+ gid_t found_gid = 0;
+ pid_t found_pid = 0;
static struct mainloop_fd_callbacks cfg_fd_callbacks = {
.dispatch = pcmk_cfg_dispatch,
@@ -101,13 +109,27 @@ cluster_connect_cfg(uint32_t * nodeid)
cs_repeat(retries, 30, rc = corosync_cfg_initialize(&cfg_handle, &cfg_callbacks));
if (rc != CS_OK) {
- crm_err("corosync cfg init error %d", rc);
+ crm_err("corosync cfg init: %s (%d)", cs_strerror(rc), rc);
return FALSE;
}
rc = corosync_cfg_fd_get(cfg_handle, &fd);
if (rc != CS_OK) {
- crm_err("corosync cfg fd_get error %d", rc);
+ crm_err("corosync cfg fd_get: %s (%d)", cs_strerror(rc), rc);
+ goto bail;
+ }
+
+ /* CFG provider run as root (in given user namespace, anyway)? */
+ if (!(rv = crm_ipc_is_authentic_process(fd, (uid_t) 0,(gid_t) 0, &found_pid,
+ &found_uid, &found_gid))) {
+ crm_err("CFG provider is not authentic:"
+ " process %lld (uid: %lld, gid: %lld)",
+ (long long) PCMK__SPECIAL_PID_AS_0(found_pid),
+ (long long) found_uid, (long long) found_gid);
+ goto bail;
+ } else if (rv < 0) {
+ crm_err("Could not verify authenticity of CFG provider: %s (%d)",
+ strerror(-rv), -rv);
goto bail;
}
@@ -152,10 +174,15 @@ get_config_opt(uint64_t unused, cmap_handle_t object_handle, const char *key, ch
gboolean
mcp_read_config(void)
{
- int rc = CS_OK;
+ cs_error_t rc = CS_OK;
int retries = 0;
cmap_handle_t local_handle;
uint64_t config = 0;
+ int fd = -1;
+ uid_t found_uid = 0;
+ gid_t found_gid = 0;
+ pid_t found_pid = 0;
+ int rv;
// There can be only one possibility
do {
@@ -178,6 +205,30 @@ mcp_read_config(void)
return FALSE;
}
+ rc = cmap_fd_get(local_handle, &fd);
+ if (rc != CS_OK) {
+ crm_err("Could not obtain the CMAP API connection: %s (%d)",
+ cs_strerror(rc), rc);
+ cmap_finalize(local_handle);
+ return FALSE;
+ }
+
+ /* CMAP provider run as root (in given user namespace, anyway)? */
+ if (!(rv = crm_ipc_is_authentic_process(fd, (uid_t) 0,(gid_t) 0, &found_pid,
+ &found_uid, &found_gid))) {
+ crm_err("CMAP provider is not authentic:"
+ " process %lld (uid: %lld, gid: %lld)",
+ (long long) PCMK__SPECIAL_PID_AS_0(found_pid),
+ (long long) found_uid, (long long) found_gid);
+ cmap_finalize(local_handle);
+ return FALSE;
+ } else if (rv < 0) {
+ crm_err("Could not verify authenticity of CMAP provider: %s (%d)",
+ strerror(-rv), -rv);
+ cmap_finalize(local_handle);
+ return FALSE;
+ }
+
stack = get_cluster_type();
crm_info("Reading configure for stack: %s", name_for_cluster_type(stack));
diff --git a/lib/cluster/corosync.c b/lib/cluster/corosync.c
index fcdfc0e..648c0d5 100644
--- a/lib/cluster/corosync.c
+++ b/lib/cluster/corosync.c
@@ -1,19 +1,10 @@
/*
- * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
+ * Copyright 2004-2019 the Pacemaker project contributors
*
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
+ * The version control history for this file may have further details.
*
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
@@ -43,6 +34,8 @@
#include <crm/msg_xml.h>
+#include <crm/common/ipc_internal.h> /* PCMK__SPECIAL_PID* */
+
quorum_handle_t pcmk_quorum_handle = 0;
gboolean(*quorum_app_callback) (unsigned long long seq, gboolean quorate) = NULL;
@@ -68,10 +61,15 @@ char *
corosync_node_name(uint64_t /*cmap_handle_t */ cmap_handle, uint32_t nodeid)
{
int lpc = 0;
- int rc = CS_OK;
+ cs_error_t rc = CS_OK;
int retries = 0;
char *name = NULL;
cmap_handle_t local_handle = 0;
+ int fd = -1;
+ uid_t found_uid = 0;
+ gid_t found_gid = 0;
+ pid_t found_pid = 0;
+ int rv;
if (nodeid == 0) {
nodeid = get_local_nodeid(0);
@@ -100,6 +98,27 @@ corosync_node_name(uint64_t /*cmap_handle_t */ cmap_handle, uint32_t nodeid)
if (cmap_handle == 0) {
cmap_handle = local_handle;
+
+ rc = cmap_fd_get(cmap_handle, &fd);
+ if (rc != CS_OK) {
+ crm_err("Could not obtain the CMAP API connection: %s (%d)",
+ cs_strerror(rc), rc);
+ goto bail;
+ }
+
+ /* CMAP provider run as root (in given user namespace, anyway)? */
+ if (!(rv = crm_ipc_is_authentic_process(fd, (uid_t) 0,(gid_t) 0, &found_pid,
+ &found_uid, &found_gid))) {
+ crm_err("CMAP provider is not authentic:"
+ " process %lld (uid: %lld, gid: %lld)",
+ (long long) PCMK__SPECIAL_PID_AS_0(found_pid),
+ (long long) found_uid, (long long) found_gid);
+ goto bail;
+ } else if (rv < 0) {
+ crm_err("Could not verify authenticity of CMAP provider: %s (%d)",
+ strerror(-rv), -rv);
+ goto bail;
+ }
}
while (name == NULL && cmap_handle != 0) {
@@ -140,6 +159,7 @@ corosync_node_name(uint64_t /*cmap_handle_t */ cmap_handle, uint32_t nodeid)
lpc++;
}
+bail:
if(local_handle) {
cmap_finalize(local_handle);
}
@@ -251,11 +271,15 @@ gboolean
cluster_connect_quorum(gboolean(*dispatch) (unsigned long long, gboolean),
void (*destroy) (gpointer))
{
- int rc = -1;
+ cs_error_t rc;
int fd = 0;
int quorate = 0;
uint32_t quorum_type = 0;
struct mainloop_fd_callbacks quorum_fd_callbacks;
+ uid_t found_uid = 0;
+ gid_t found_gid = 0;
+ pid_t found_pid = 0;
+ int rv;
quorum_fd_callbacks.dispatch = pcmk_quorum_dispatch;
quorum_fd_callbacks.destroy = destroy;
@@ -264,7 +288,8 @@ cluster_connect_quorum(gboolean(*dispatch) (unsigned long long, gboolean),
rc = quorum_initialize(&pcmk_quorum_handle, &quorum_callbacks, &quorum_type);
if (rc != CS_OK) {
- crm_err("Could not connect to the Quorum API: %d", rc);
+ crm_err("Could not connect to the Quorum API: %s (%d)",
+ cs_strerror(rc), rc);
goto bail;
} else if (quorum_type != QUORUM_SET) {
@@ -272,6 +297,29 @@ cluster_connect_quorum(gboolean(*dispatch) (unsigned long long, gboolean),
goto bail;
}
+ rc = quorum_fd_get(pcmk_quorum_handle, &fd);
+ if (rc != CS_OK) {
+ crm_err("Could not obtain the Quorum API connection: %s (%d)",
+ strerror(rc), rc);
+ goto bail;
+ }
+
+ /* Quorum provider run as root (in given user namespace, anyway)? */
+ if (!(rv = crm_ipc_is_authentic_process(fd, (uid_t) 0,(gid_t) 0, &found_pid,
+ &found_uid, &found_gid))) {
+ crm_err("Quorum provider is not authentic:"
+ " process %lld (uid: %lld, gid: %lld)",
+ (long long) PCMK__SPECIAL_PID_AS_0(found_pid),
+ (long long) found_uid, (long long) found_gid);
+ rc = CS_ERR_ACCESS;
+ goto bail;
+ } else if (rv < 0) {
+ crm_err("Could not verify authenticity of Quorum provider: %s (%d)",
+ strerror(-rv), -rv);
+ rc = CS_ERR_ACCESS;
+ goto bail;
+ }
+
rc = quorum_getquorate(pcmk_quorum_handle, &quorate);
if (rc != CS_OK) {
crm_err("Could not obtain the current Quorum API state: %d", rc);
@@ -292,12 +340,6 @@ cluster_connect_quorum(gboolean(*dispatch) (unsigned long long, gboolean),
goto bail;
}
- rc = quorum_fd_get(pcmk_quorum_handle, &fd);
- if (rc != CS_OK) {
- crm_err("Could not obtain the Quorum API connection: %d", rc);
- goto bail;
- }
-
mainloop_add_fd("quorum", G_PRIORITY_HIGH, fd, dispatch, &quorum_fd_callbacks);
corosync_initialize_nodelist(NULL, FALSE, NULL);
@@ -488,10 +530,15 @@ gboolean
corosync_initialize_nodelist(void *cluster, gboolean force_member, xmlNode * xml_parent)
{
int lpc = 0;
- int rc = CS_OK;
+ cs_error_t rc = CS_OK;
int retries = 0;
gboolean any = FALSE;
cmap_handle_t cmap_handle;
+ int fd = -1;
+ uid_t found_uid = 0;
+ gid_t found_gid = 0;
+ pid_t found_pid = 0;
+ int rv;
do {
rc = cmap_initialize(&cmap_handle);
@@ -509,6 +556,27 @@ corosync_initialize_nodelist(void *cluster, gboolean force_member, xmlNode * xml
return FALSE;
}
+ rc = cmap_fd_get(cmap_handle, &fd);
+ if (rc != CS_OK) {
+ crm_err("Could not obtain the CMAP API connection: %s (%d)",
+ cs_strerror(rc), rc);
+ goto bail;
+ }
+
+ /* CMAP provider run as root (in given user namespace, anyway)? */
+ if (!(rv = crm_ipc_is_authentic_process(fd, (uid_t) 0,(gid_t) 0, &found_pid,
+ &found_uid, &found_gid))) {
+ crm_err("CMAP provider is not authentic:"
+ " process %lld (uid: %lld, gid: %lld)",
+ (long long) PCMK__SPECIAL_PID_AS_0(found_pid),
+ (long long) found_uid, (long long) found_gid);
+ goto bail;
+ } else if (rv < 0) {
+ crm_err("Could not verify authenticity of CMAP provider: %s (%d)",
+ strerror(-rv), -rv);
+ goto bail;
+ }
+
crm_peer_init();
crm_trace("Initializing corosync nodelist");
for (lpc = 0; TRUE; lpc++) {
@@ -562,6 +630,7 @@ corosync_initialize_nodelist(void *cluster, gboolean force_member, xmlNode * xml
free(name);
}
+bail:
cmap_finalize(cmap_handle);
return any;
}
@@ -571,36 +640,68 @@ corosync_cluster_name(void)
{
cmap_handle_t handle;
char *cluster_name = NULL;
- int rc = CS_OK;
+ cs_error_t rc = CS_OK;
+ int fd = -1;
+ uid_t found_uid = 0;
+ gid_t found_gid = 0;
+ pid_t found_pid = 0;
+ int rv;
rc = cmap_initialize(&handle);
if (rc != CS_OK) {
- crm_info("Failed to initialize the cmap API: %s (%d)", ais_error2text(rc), rc);
+ crm_info("Failed to initialize the cmap API: %s (%d)",
+ cs_strerror(rc), rc);
return NULL;
}
+ rc = cmap_fd_get(handle, &fd);
+ if (rc != CS_OK) {
+ crm_err("Could not obtain the CMAP API connection: %s (%d)",
+ cs_strerror(rc), rc);
+ goto bail;
+ }
+
+ /* CMAP provider run as root (in given user namespace, anyway)? */
+ if (!(rv = crm_ipc_is_authentic_process(fd, (uid_t) 0,(gid_t) 0, &found_pid,
+ &found_uid, &found_gid))) {
+ crm_err("CMAP provider is not authentic:"
+ " process %lld (uid: %lld, gid: %lld)",
+ (long long) PCMK__SPECIAL_PID_AS_0(found_pid),
+ (long long) found_uid, (long long) found_gid);
+ goto bail;
+ } else if (rv < 0) {
+ crm_err("Could not verify authenticity of CMAP provider: %s (%d)",
+ strerror(-rv), -rv);
+ goto bail;
+ }
+
rc = cmap_get_string(handle, "totem.cluster_name", &cluster_name);
if (rc != CS_OK) {
- crm_info("Cannot get totem.cluster_name: %s (%d)", ais_error2text(rc), rc);
+ crm_info("Cannot get totem.cluster_name: %s (%d)", cs_strerror(rc), rc);
} else {
crm_debug("cmap totem.cluster_name = '%s'", cluster_name);
}
+bail:
cmap_finalize(handle);
-
return cluster_name;
}
int
corosync_cmap_has_config(const char *prefix)
{
- int rc = CS_OK;
+ cs_error_t rc = CS_OK;
int retries = 0;
static int found = -1;
cmap_handle_t cmap_handle;
cmap_iter_handle_t iter_handle;
char key_name[CMAP_KEYNAME_MAXLEN + 1];
+ int fd = -1;
+ uid_t found_uid = 0;
+ gid_t found_gid = 0;
+ pid_t found_pid = 0;
+ int rv;
if(found != -1) {
return found;
@@ -623,6 +724,27 @@ corosync_cmap_has_config(const char *prefix)
return -1;
}
+ rc = cmap_fd_get(cmap_handle, &fd);
+ if (rc != CS_OK) {
+ crm_err("Could not obtain the CMAP API connection: %s (%d)",
+ cs_strerror(rc), rc);
+ goto bail;
+ }
+
+ /* CMAP provider run as root (in given user namespace, anyway)? */
+ if (!(rv = crm_ipc_is_authentic_process(fd, (uid_t) 0,(gid_t) 0, &found_pid,
+ &found_uid, &found_gid))) {
+ crm_err("CMAP provider is not authentic:"
+ " process %lld (uid: %lld, gid: %lld)",
+ (long long) PCMK__SPECIAL_PID_AS_0(found_pid),
+ (long long) found_uid, (long long) found_gid);
+ goto bail;
+ } else if (rv < 0) {
+ crm_err("Could not verify authenticity of CMAP provider: %s (%d)",
+ strerror(-rv), -rv);
+ goto bail;
+ }
+
rc = cmap_iter_init(cmap_handle, prefix, &iter_handle);
if (rc != CS_OK) {
crm_warn("Failed to initialize iteration for corosync cmap '%s': %s (rc=%d)",
diff --git a/lib/cluster/cpg.c b/lib/cluster/cpg.c
index 75af131..430195c 100644
--- a/lib/cluster/cpg.c
+++ b/lib/cluster/cpg.c
@@ -1,5 +1,7 @@
/*
- * Copyright 2004-2018 Andrew Beekhof <andrew@beekhof.net>
+ * Copyright 2004-2019 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
@@ -27,6 +29,8 @@
#include <crm/msg_xml.h>
+#include <crm/common/ipc_internal.h> /* PCMK__SPECIAL_PID* */
+
cpg_handle_t pcmk_cpg_handle = 0; /* TODO: Remove, use cluster.cpg_handle */
static bool cpg_evicted = FALSE;
@@ -60,11 +64,16 @@ cluster_disconnect_cpg(crm_cluster_t *cluster)
uint32_t get_local_nodeid(cpg_handle_t handle)
{
- int rc = CS_OK;
+ cs_error_t rc = CS_OK;
int retries = 0;
static uint32_t local_nodeid = 0;
cpg_handle_t local_handle = handle;
cpg_callbacks_t cb = { };
+ int fd = -1;
+ uid_t found_uid = 0;
+ gid_t found_gid = 0;
+ pid_t found_pid = 0;
+ int rv;
if(local_nodeid != 0) {
return local_nodeid;
@@ -73,6 +82,32 @@ uint32_t get_local_nodeid(cpg_handle_t handle)
if(handle == 0) {
crm_trace("Creating connection");
cs_repeat(retries, 5, rc = cpg_initialize(&local_handle, &cb));
+ if (rc != CS_OK) {
+ crm_err("Could not connect to the CPG API: %s (%d)",
+ cs_strerror(rc), rc);
+ return 0;
+ }
+
+ rc = cpg_fd_get(local_handle, &fd);
+ if (rc != CS_OK) {
+ crm_err("Could not obtain the CPG API connection: %s (%d)",
+ cs_strerror(rc), rc);
+ goto bail;
+ }
+
+ /* CPG provider run as root (in given user namespace, anyway)? */
+ if (!(rv = crm_ipc_is_authentic_process(fd, (uid_t) 0,(gid_t) 0, &found_pid,
+ &found_uid, &found_gid))) {
+ crm_err("CPG provider is not authentic:"
+ " process %lld (uid: %lld, gid: %lld)",
+ (long long) PCMK__SPECIAL_PID_AS_0(found_pid),
+ (long long) found_uid, (long long) found_gid);
+ goto bail;
+ } else if (rv < 0) {
+ crm_err("Could not verify authenticity of CPG provider: %s (%d)",
+ strerror(-rv), -rv);
+ goto bail;
+ }
}
if (rc == CS_OK) {
@@ -84,6 +119,8 @@ uint32_t get_local_nodeid(cpg_handle_t handle)
if (rc != CS_OK) {
crm_err("Could not get local node id from the CPG API: %s (%d)", ais_error2text(rc), rc);
}
+
+bail:
if(handle == 0) {
crm_trace("Closing connection");
cpg_finalize(local_handle);
@@ -403,13 +440,17 @@ pcmk_cpg_membership(cpg_handle_t handle,
gboolean
cluster_connect_cpg(crm_cluster_t *cluster)
{
- int rc = -1;
- int fd = 0;
+ cs_error_t rc;
+ int fd = -1;
int retries = 0;
uint32_t id = 0;
crm_node_t *peer = NULL;
cpg_handle_t handle = 0;
const char *message_name = pcmk_message_name(crm_system_name);
+ uid_t found_uid = 0;
+ gid_t found_gid = 0;
+ pid_t found_pid = 0;
+ int rv;
struct mainloop_fd_callbacks cpg_fd_callbacks = {
.dispatch = pcmk_cpg_dispatch,
@@ -434,7 +475,31 @@ cluster_connect_cpg(crm_cluster_t *cluster)
cs_repeat(retries, 30, rc = cpg_initialize(&handle, &cpg_callbacks));
if (rc != CS_OK) {
- crm_err("Could not connect to the Cluster Process Group API: %d", rc);
+ crm_err("Could not connect to the CPG API: %s (%d)",
+ cs_strerror(rc), rc);
+ goto bail;
+ }
+
+ rc = cpg_fd_get(handle, &fd);
+ if (rc != CS_OK) {
+ crm_err("Could not obtain the CPG API connection: %s (%d)",
+ cs_strerror(rc), rc);
+ goto bail;
+ }
+
+ /* CPG provider run as root (in given user namespace, anyway)? */
+ if (!(rv = crm_ipc_is_authentic_process(fd, (uid_t) 0,(gid_t) 0, &found_pid,
+ &found_uid, &found_gid))) {
+ crm_err("CPG provider is not authentic:"
+ " process %lld (uid: %lld, gid: %lld)",
+ (long long) PCMK__SPECIAL_PID_AS_0(found_pid),
+ (long long) found_uid, (long long) found_gid);
+ rc = CS_ERR_ACCESS;
+ goto bail;
+ } else if (rv < 0) {
+ crm_err("Could not verify authenticity of CPG provider: %s (%d)",
+ strerror(-rv), -rv);
+ rc = CS_ERR_ACCESS;
goto bail;
}
@@ -453,12 +518,6 @@ cluster_connect_cpg(crm_cluster_t *cluster)
goto bail;
}
- rc = cpg_fd_get(handle, &fd);
- if (rc != CS_OK) {
- crm_err("Could not obtain the CPG API connection: %d", rc);
- goto bail;
- }
-
pcmk_cpg_handle = handle;
cluster->cpg_handle = handle;
mainloop_add_fd("corosync-cpg", G_PRIORITY_MEDIUM, fd, cluster, &cpg_fd_callbacks);
diff --git a/lib/common/ipc.c b/lib/common/ipc.c
index aa055d3..84d7cb9 100644
--- a/lib/common/ipc.c
+++ b/lib/common/ipc.c
@@ -947,11 +947,18 @@ crm_ipc_new(const char *name, size_t max_size)
*
* \param[in] client Connection instance obtained from crm_ipc_new()
*
- * \return TRUE on success, FALSE otherwise (in which case errno will be set)
+ * \return TRUE on success, FALSE otherwise (in which case errno will be set;
+ * specifically, in case of discovering the remote side is not
+ * authentic, its value is set to ECONNABORTED).
*/
bool
crm_ipc_connect(crm_ipc_t * client)
{
+ static uid_t cl_uid = 0;
+ static gid_t cl_gid = 0;
+ pid_t found_pid = 0; uid_t found_uid = 0; gid_t found_gid = 0;
+ int rv;
+
client->need_reply = FALSE;
client->ipc = qb_ipcc_connect(client->name, client->buf_size);
@@ -962,7 +969,39 @@ crm_ipc_connect(crm_ipc_t * client)
client->pfd.fd = crm_ipc_get_fd(client);
if (client->pfd.fd < 0) {
- crm_debug("Could not obtain file descriptor for %s connection: %s (%d)", client->name, pcmk_strerror(errno), errno);
+ rv = errno;
+ /* message already omitted */
+ crm_ipc_close(client);
+ errno = rv;
+ return FALSE;
+ }
+
+ if (!cl_uid && !cl_gid
+ && (rv = crm_user_lookup(CRM_DAEMON_USER, &cl_uid, &cl_gid)) < 0) {
+ errno = -rv;
+ /* message already omitted */
+ crm_ipc_close(client);
+ errno = -rv;
+ return FALSE;
+ }
+
+ if (!(rv = crm_ipc_is_authentic_process(client->pfd.fd, cl_uid, cl_gid,
+ &found_pid, &found_uid,
+ &found_gid))) {
+ crm_err("Daemon (IPC %s) is not authentic:"
+ " process %lld (uid: %lld, gid: %lld)",
+ client->name, (long long) PCMK__SPECIAL_PID_AS_0(found_pid),
+ (long long) found_uid, (long long) found_gid);
+ crm_ipc_close(client);
+ errno = ECONNABORTED;
+ return FALSE;
+
+ } else if (rv < 0) {
+ errno = -rv;
+ crm_perror(LOG_ERR, "Could not verify authenticity of daemon (IPC %s)",
+ client->name);
+ crm_ipc_close(client);
+ errno = -rv;
return FALSE;
}
--
1.8.3.1
From d324e407c0e2695f405974d567d79eb91d0ee69a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
Date: Tue, 16 Apr 2019 00:13:40 +0200
Subject: [PATCH 6/7] High: pacemakerd vs. IPC/procfs confused deputy
authenticity issue (4/4)
[4/4: CPG users to be careful about now-more-probable rival processes]
In essence, this comes down to pacemaker confusing at-node CPG members
with effectively the only plausible to co-exist at particular node,
which doesn't hold and asks for a wider reconciliation of this
reality-check.
However, in practical terms, since there are two factors lowering the
priority of doing so:
1/ possibly the only non-self-inflicted scenario is either that
some of the cluster stack processes fail -- this the problem
that shall rather be deferred to arranged node disarming/fencing
to stay on the safe side with 100% certainty, at the cost of
possibly long-lasting failover process at other nodes
(for other possibility, someone running some of these by accident
so they effectively become rival processes, it's like getting
hands cut when playing with a lawnmower in an unintended way)
2/ for state tracking of the peer nodes, it may possibly cause troubles
in case the process observed as left wasn't the last for the
particular node, even if presumably just temporary, since the
situation may eventually resolve with imposed serialization of
the rival processes via API end-point singleton restriction (this
is also the most likely cause of why such non-final leave gets
observed in the first place), except in one case -- the legitimate
API end-point carrier won't possibly acknowledged as returned
by its peers, at least not immediately, unless it tries to join
anew, which verges on undefined behaviour (at least per corosync
documentation)
we make do just with a light code change so as to
* limit 1/ some more with in-daemon self-check for pre-existing
end-point existence (this is to complement the checks already made in
the parent daemon prior to spawning new instances, only some moments
later; note that we don't have any lock file etc. mechanisms to
prevent parallel runs of the same daemons, and people could run these
on their own deliberation), and to
* guard against the interferences from the rivals at the same node
per 2/ with ignoring their non-final leave messages altogether.
Note that CPG at this point is already expected to be authenticity-safe.
Regarding now-more-probable part, we actually traded the inherently racy
procfs scanning for something (exactly that singleton mentioned above)
rather firm (and unfakeable), but we admittedly got lost track of
processes that are after CPG membership (that is, another form of
a shared state) prior to (or in non-deterministic order allowing for
the same) carring about publishing the end-point.
Big thanks is owed to Yan Gao of SUSE, for early discovery and reporting
this discrepancy arising from the earlier commits in the set.
---
daemons/attrd/pacemaker-attrd.c | 15 ++++++
daemons/based/pacemaker-based.c | 18 ++++++-
daemons/controld/pacemaker-controld.c | 18 ++++++-
daemons/fenced/pacemaker-fenced.c | 15 ++++++
lib/cluster/cpg.c | 91 ++++++++++++++++++++++++++++++++---
5 files changed, 146 insertions(+), 11 deletions(-)
diff --git a/daemons/attrd/pacemaker-attrd.c b/daemons/attrd/pacemaker-attrd.c
index 837459f..9ffe201 100644
--- a/daemons/attrd/pacemaker-attrd.c
+++ b/daemons/attrd/pacemaker-attrd.c
@@ -344,6 +344,7 @@ main(int argc, char **argv)
int flag = 0;
int index = 0;
int argerr = 0;
+ crm_ipc_t *old_instance = NULL;
attrd_init_mainloop();
crm_log_preinit(NULL, argc, argv);
@@ -380,6 +381,20 @@ main(int argc, char **argv)
crm_log_init(T_ATTRD, LOG_INFO, TRUE, FALSE, argc, argv, FALSE);
crm_info("Starting up");
+
+ old_instance = crm_ipc_new(T_ATTRD, 0);
+ if (crm_ipc_connect(old_instance)) {
+ /* IPC end-point already up */
+ crm_ipc_close(old_instance);
+ crm_ipc_destroy(old_instance);
+ crm_err("pacemaker-attrdd is already active, aborting startup");
+ crm_exit(CRM_EX_OK);
+ } else {
+ /* not up or not authentic, we'll proceed either way */
+ crm_ipc_destroy(old_instance);
+ old_instance = NULL;
+ }
+
attributes = g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, free_attribute);
/* Connect to the CIB before connecting to the cluster or listening for IPC.
diff --git a/daemons/based/pacemaker-based.c b/daemons/based/pacemaker-based.c
index bd98ca8..f004f671 100644
--- a/daemons/based/pacemaker-based.c
+++ b/daemons/based/pacemaker-based.c
@@ -90,13 +90,12 @@ main(int argc, char **argv)
int index = 0;
int argerr = 0;
struct passwd *pwentry = NULL;
+ crm_ipc_t *old_instance = NULL;
crm_log_preinit(NULL, argc, argv);
crm_set_options(NULL, "[options]",
long_options, "Daemon for storing and replicating the cluster configuration");
- crm_peer_init();
-
mainloop_add_signal(SIGTERM, cib_shutdown);
mainloop_add_signal(SIGPIPE, cib_enable_writes);
@@ -171,6 +170,19 @@ main(int argc, char **argv)
crm_log_init(NULL, LOG_INFO, TRUE, FALSE, argc, argv, FALSE);
+ old_instance = crm_ipc_new(CIB_CHANNEL_RO, 0);
+ if (crm_ipc_connect(old_instance)) {
+ /* IPC end-point already up */
+ crm_ipc_close(old_instance);
+ crm_ipc_destroy(old_instance);
+ crm_err("pacemaker-based is already active, aborting startup");
+ crm_exit(CRM_EX_OK);
+ } else {
+ /* not up or not authentic, we'll proceed either way */
+ crm_ipc_destroy(old_instance);
+ old_instance = NULL;
+ }
+
if (cib_root == NULL) {
cib_root = CRM_CONFIG_DIR;
} else {
@@ -185,6 +197,8 @@ main(int argc, char **argv)
return CRM_EX_FATAL;
}
+ crm_peer_init();
+
/* read local config file */
cib_init();
diff --git a/daemons/controld/pacemaker-controld.c b/daemons/controld/pacemaker-controld.c
index 11f909e..38842fb 100644
--- a/daemons/controld/pacemaker-controld.c
+++ b/daemons/controld/pacemaker-controld.c
@@ -1,5 +1,7 @@
/*
- * Copyright 2004-2018 Andrew Beekhof <andrew@beekhof.net>
+ * Copyright 2004-2019 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
@@ -50,6 +52,7 @@ main(int argc, char **argv)
int flag;
int index = 0;
int argerr = 0;
+ crm_ipc_t *old_instance = NULL;
crmd_mainloop = g_main_loop_new(NULL, FALSE);
crm_log_preinit(NULL, argc, argv);
@@ -93,6 +96,19 @@ main(int argc, char **argv)
crm_help('?', CRM_EX_USAGE);
}
+ old_instance = crm_ipc_new(CRM_SYSTEM_CRMD, 0);
+ if (crm_ipc_connect(old_instance)) {
+ /* IPC end-point already up */
+ crm_ipc_close(old_instance);
+ crm_ipc_destroy(old_instance);
+ crm_err("pacemaker-controld is already active, aborting startup");
+ crm_exit(CRM_EX_OK);
+ } else {
+ /* not up or not authentic, we'll proceed either way */
+ crm_ipc_destroy(old_instance);
+ old_instance = NULL;
+ }
+
if (pcmk__daemon_can_write(PE_STATE_DIR, NULL) == FALSE) {
crm_err("Terminating due to bad permissions on " PE_STATE_DIR);
fprintf(stderr,
diff --git a/daemons/fenced/pacemaker-fenced.c b/daemons/fenced/pacemaker-fenced.c
index 105012b..da0dce9 100644
--- a/daemons/fenced/pacemaker-fenced.c
+++ b/daemons/fenced/pacemaker-fenced.c
@@ -1262,6 +1262,7 @@ main(int argc, char **argv)
int option_index = 0;
crm_cluster_t cluster;
const char *actions[] = { "reboot", "off", "on", "list", "monitor", "status" };
+ crm_ipc_t *old_instance = NULL;
crm_log_preinit(NULL, argc, argv);
crm_set_options(NULL, "mode [options]", long_options,
@@ -1432,6 +1433,20 @@ main(int argc, char **argv)
}
crm_log_init(NULL, LOG_INFO, TRUE, FALSE, argc, argv, FALSE);
+
+ old_instance = crm_ipc_new("stonith-ng", 0);
+ if (crm_ipc_connect(old_instance)) {
+ /* IPC end-point already up */
+ crm_ipc_close(old_instance);
+ crm_ipc_destroy(old_instance);
+ crm_err("pacemaker-fenced is already active, aborting startup");
+ crm_exit(CRM_EX_OK);
+ } else {
+ /* not up or not authentic, we'll proceed either way */
+ crm_ipc_destroy(old_instance);
+ old_instance = NULL;
+ }
+
mainloop_add_signal(SIGTERM, stonith_shutdown);
crm_peer_init();
diff --git a/lib/cluster/cpg.c b/lib/cluster/cpg.c
index 430195c..2898c51 100644
--- a/lib/cluster/cpg.c
+++ b/lib/cluster/cpg.c
@@ -362,6 +362,20 @@ pcmk_message_common_cs(cpg_handle_t handle, uint32_t nodeid, uint32_t pid, void
#define PEER_NAME(peer) ((peer)? ((peer)->uname? (peer)->uname : "<unknown>") : "<none>")
+static int cmp_member_list_nodeid(const void *first,
+ const void *second)
+{
+ const struct cpg_address *const a = *((const struct cpg_address **) first),
+ *const b = *((const struct cpg_address **) second);
+ if (a->nodeid < b->nodeid) {
+ return -1;
+ } else if (a->nodeid > b->nodeid) {
+ return 1;
+ }
+ /* don't bother with "reason" nor "pid" */
+ return 0;
+}
+
void
pcmk_cpg_membership(cpg_handle_t handle,
const struct cpg_name *groupName,
@@ -373,29 +387,89 @@ pcmk_cpg_membership(cpg_handle_t handle,
gboolean found = FALSE;
static int counter = 0;
uint32_t local_nodeid = get_local_nodeid(handle);
+ const struct cpg_address *key, **rival, **sorted;
+
+ sorted = malloc(member_list_entries * sizeof(const struct cpg_address *));
+ CRM_ASSERT(sorted != NULL);
+
+ for (size_t iter = 0; iter < member_list_entries; iter++) {
+ sorted[iter] = member_list + iter;
+ }
+ /* so that the cross-matching multiply-subscribed nodes is then cheap */
+ qsort(sorted, member_list_entries, sizeof(const struct cpg_address *),
+ cmp_member_list_nodeid);
for (i = 0; i < left_list_entries; i++) {
crm_node_t *peer = crm_find_peer(left_list[i].nodeid, NULL);
- crm_info("Group event %s.%d: node %u (%s) left",
+ crm_info("Group event %s.%d: node %u (%s) left: %llu",
groupName->value, counter, left_list[i].nodeid,
- PEER_NAME(peer));
+ PEER_NAME(peer), (unsigned long long) left_list[i].pid);
+
+ /* in CPG world, NODE:PROCESS-IN-MEMBERSHIP-OF-G is an 1:N relation
+ and not playing by this rule may go wild in case of multiple
+ residual instances of the same pacemaker daemon at the same node
+ -- we must ensure that the possible local rival(s) won't make us
+ cry out and bail (e.g. when they quit themselves), since all the
+ surrounding logic denies this simple fact that the full membership
+ is discriminated also per the PID of the process beside mere node
+ ID (and implicitly, group ID); practically, this will be sound in
+ terms of not preventing progress, since all the CPG joiners are
+ also API end-point carriers, and that's what matters locally
+ (who's the winner);
+ remotely, we will just compare leave_list and member_list and if
+ the left process has it's node retained in member_list (under some
+ other PID, anyway) we will just ignore it as well
+ XXX: long-term fix is to establish in-out PID-aware tracking? */
if (peer) {
- crm_update_peer_proc(__FUNCTION__, peer, crm_proc_cpg, OFFLINESTATUS);
+ key = &left_list[i];
+ rival = bsearch(&key, sorted, member_list_entries,
+ sizeof(const struct cpg_address *),
+ cmp_member_list_nodeid);
+ if (rival == NULL) {
+ crm_update_peer_proc(__FUNCTION__, peer, crm_proc_cpg,
+ OFFLINESTATUS);
+ } else if (left_list[i].nodeid == local_nodeid) {
+ crm_info("Ignoring the above event %s.%d, comes from a local"
+ " rival process (presumably not us): %llu",
+ groupName->value, counter,
+ (unsigned long long) left_list[i].pid);
+ } else {
+ crm_info("Ignoring the above event %s.%d, comes from"
+ " a rival-rich node: %llu (e.g. %llu process"
+ " carries on)",
+ groupName->value, counter,
+ (unsigned long long) left_list[i].pid,
+ (unsigned long long) (*rival)->pid);
+ }
}
}
+ free(sorted);
+ sorted = NULL;
for (i = 0; i < joined_list_entries; i++) {
- crm_info("Group event %s.%d: node %u joined",
- groupName->value, counter, joined_list[i].nodeid);
+ crm_info("Group event %s.%d: node %u joined: %llu"
+ " (unchecked for rivals)",
+ groupName->value, counter, joined_list[i].nodeid,
+ (unsigned long long) joined_list[i].pid);
}
for (i = 0; i < member_list_entries; i++) {
crm_node_t *peer = crm_get_peer(member_list[i].nodeid, NULL);
- crm_info("Group event %s.%d: node %u (%s) is member",
+ crm_info("Group event %s.%d: node %u (%s) is member: %llu"
+ " (at least once)",
groupName->value, counter, member_list[i].nodeid,
- PEER_NAME(peer));
+ PEER_NAME(peer), member_list[i].pid);
+
+ if (member_list[i].nodeid == local_nodeid
+ && member_list[i].pid != getpid()) {
+ /* see the note above */
+ crm_info("Ignoring the above event %s.%d, comes from a local rival"
+ " process: %llu", groupName->value, counter,
+ (unsigned long long) member_list[i].pid);
+ continue;
+ }
/* If the caller left auto-reaping enabled, this will also update the
* state to member.
@@ -418,7 +492,8 @@ pcmk_cpg_membership(cpg_handle_t handle,
} else if (now > (peer->when_lost + 60)) {
// If it persists for more than a minute, update the state
- crm_warn("Node %u member of group %s but believed offline",
+ crm_warn("Node %u member of group %s but believed offline"
+ " (unchecked for rivals)",
member_list[i].nodeid, groupName->value);
crm_update_peer_state(__FUNCTION__, peer, CRM_NODE_MEMBER, 0);
}
--
1.8.3.1
From 3ad7b2509d78f95b5dfc8fffc4d9a91be1da5113 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
Date: Wed, 17 Apr 2019 15:04:21 +0200
Subject: [PATCH 7/7] Med: controld: fix possible NULL pointer dereference
This is now more likely triggerable once the problems related to
CVE-2018-16878 are avoided.
---
daemons/controld/controld_control.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/daemons/controld/controld_control.c b/daemons/controld/controld_control.c
index ee95698..0ac358c 100644
--- a/daemons/controld/controld_control.c
+++ b/daemons/controld/controld_control.c
@@ -77,12 +77,15 @@ do_ha_control(long long action,
registered = crm_connect_corosync(cluster);
#endif
}
- controld_election_init(cluster->uname);
- fsa_our_uname = cluster->uname;
- fsa_our_uuid = cluster->uuid;
- if(cluster->uuid == NULL) {
- crm_err("Could not obtain local uuid");
- registered = FALSE;
+
+ if (registered == TRUE) {
+ controld_election_init(cluster->uname);
+ fsa_our_uname = cluster->uname;
+ fsa_our_uuid = cluster->uuid;
+ if(cluster->uuid == NULL) {
+ crm_err("Could not obtain local uuid");
+ registered = FALSE;
+ }
}
if (registered == FALSE) {
--
1.8.3.1