File bsc#1131353-bsc#1131356-0003-High-pacemakerd-vs.-IPC-procfs-confused-deputy-authe.patch of Package pacemaker.12602
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
Index: pacemaker-1.1.18+20180430.b12c320f5/configure.ac
===================================================================
--- pacemaker-1.1.18+20180430.b12c320f5.orig/configure.ac
+++ pacemaker-1.1.18+20180430.b12c320f5/configure.ac
@@ -392,6 +392,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
@@ -1817,3 +1859,4 @@ AC_MSG_RESULT([ 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}])
Index: pacemaker-1.1.18+20180430.b12c320f5/include/crm/common/Makefile.am
===================================================================
--- pacemaker-1.1.18+20180430.b12c320f5.orig/include/crm/common/Makefile.am
+++ pacemaker-1.1.18+20180430.b12c320f5/include/crm/common/Makefile.am
@@ -22,4 +22,5 @@ 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 xml_internal.h
+ iso8601_internal.h xml_internal.h \
+ ipc_internal.h
Index: pacemaker-1.1.18+20180430.b12c320f5/include/crm/common/ipc.h
===================================================================
--- pacemaker-1.1.18+20180430.b12c320f5.orig/include/crm/common/ipc.h
+++ pacemaker-1.1.18+20180430.b12c320f5/include/crm/common/ipc.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 Andrew Beekhof <andrew@beekhof.net>
+ * Copyright 2004-2019 the Pacemaker project contributors
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -81,6 +81,44 @@ uint32_t crm_ipc_buffer_flags(crm_ipc_t
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);
Index: pacemaker-1.1.18+20180430.b12c320f5/include/crm/common/ipc_internal.h
===================================================================
--- /dev/null
+++ pacemaker-1.1.18+20180430.b12c320f5/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
Index: pacemaker-1.1.18+20180430.b12c320f5/lib/common/ipc.c
===================================================================
--- pacemaker-1.1.18+20180430.b12c320f5.orig/lib/common/ipc.c
+++ pacemaker-1.1.18+20180430.b12c320f5/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) */
@@ -1338,6 +1353,132 @@ crm_ipc_send(crm_ipc_t * client, xmlNode
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 *