Skip to content

Commit

Permalink
High: pacemakerd vs. IPC/procfs confused deputy authenticity issue (1/4)
Browse files Browse the repository at this point in the history
[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 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!).
  • Loading branch information
jnpkrn committed Apr 16, 2019
1 parent ab44422 commit 6888aaf
Show file tree
Hide file tree
Showing 5 changed files with 303 additions and 29 deletions.
43 changes: 43 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,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
Expand Down Expand Up @@ -2179,3 +2221,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}])
7 changes: 5 additions & 2 deletions include/crm/common/Makefile.am
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#
# Copyright 2004-2019 Andrew Beekhof <[email protected]>
# 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.
Expand All @@ -11,7 +13,8 @@ headerdir=$(pkgincludedir)/crm/common

header_HEADERS = xml.h ipc.h util.h iso8601.h mainloop.h logging.h \
nvpair.h
noinst_HEADERS = ipcs.h internal.h remote_internal.h xml_internal.h
noinst_HEADERS = ipcs.h internal.h remote_internal.h xml_internal.h \
ipc_internal.h
if BUILD_CIBSECRETS
noinst_HEADERS += cib_secrets.h
endif
55 changes: 42 additions & 13 deletions include/crm/common/ipc.h
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
/*
* Copyright (C) 2013 Andrew Beekhof <[email protected]>
* Copyright 2013-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
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
* The version control history for this file may have further details.
*
* This software 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
* 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.
*/
#ifndef CRM_COMMON_IPC__H
# define CRM_COMMON_IPC__H
Expand Down Expand Up @@ -77,6 +68,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);
Expand Down
69 changes: 69 additions & 0 deletions include/crm/common/ipc_internal.h
Original file line number Diff line number Diff line change
@@ -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
158 changes: 144 additions & 14 deletions lib/common/ipc.c
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
/*
* Copyright (C) 2004 Andrew Beekhof <[email protected]>
* 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>

#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>
Expand All @@ -30,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) */
Expand Down Expand Up @@ -1375,6 +1379,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 *
Expand Down

0 comments on commit 6888aaf

Please sign in to comment.