pacemaker/SOURCES/005-remote_overflow.patch
2026-07-20 04:26:34 -04:00

414 lines
15 KiB
Diff

From 264d94dcdb4c6739355f7251b4e9e81256938dbf Mon Sep 17 00:00:00 2001
From: Chris Lumens <clumens@redhat.com>
Date: Thu, 14 May 2026 12:57:17 -0400
Subject: [PATCH 1/6] Med: libcrmcommon: Add sanity checks to
localized_remote_header.
These checks are present in the main and 3.0 branches, and they make
sense here as well.
---
lib/common/remote.c | 52 ++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 47 insertions(+), 5 deletions(-)
diff --git a/lib/common/remote.c b/lib/common/remote.c
index b25e5ce..a4c8b8a 100644
--- a/lib/common/remote.c
+++ b/lib/common/remote.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2008-2024 the Pacemaker project contributors
+ * Copyright 2008-2026 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
@@ -23,7 +23,7 @@
#include <netdb.h>
#include <stdlib.h>
#include <errno.h>
-#include <inttypes.h> // PRIx32
+#include <inttypes.h> // PRIu32, PRIx32
#include <glib.h>
#include <bzlib.h>
@@ -97,11 +97,18 @@ struct remote_header_v0 {
static struct remote_header_v0 *
localized_remote_header(pcmk__remote_t *remote)
{
- struct remote_header_v0 *header = (struct remote_header_v0 *)remote->buffer;
- if(remote->buffer_offset < sizeof(struct remote_header_v0)) {
+ struct remote_header_v0 *header = NULL;
+ size_t expected_size = 0;
+
+ if ((remote == NULL) || (remote->buffer == NULL)
+ || (remote->buffer_offset < sizeof(struct remote_header_v0))) {
+
+ // Caller error or we haven't received the full header yet
return NULL;
+ }
- } else if(header->endian != ENDIAN_LOCAL) {
+ header = (struct remote_header_v0 *) remote->buffer;
+ if (header->endian != ENDIAN_LOCAL) {
uint32_t endian = __swab32(header->endian);
CRM_LOG_ASSERT(endian == ENDIAN_LOCAL);
@@ -123,6 +130,41 @@ localized_remote_header(pcmk__remote_t *remote)
header->payload_uncompressed = __swab32(header->payload_uncompressed);
}
+ // Sanity checks
+ if (header->payload_offset != sizeof(struct remote_header_v0)) {
+ crm_err("Header payload offset %" PRIu32 " does not have expected "
+ "size %zu", header->payload_offset,
+ sizeof(struct remote_header_v0));
+ return NULL;
+ }
+
+ if (header->payload_compressed != 0) {
+ if (header->payload_compressed > (SIZE_MAX - header->payload_offset)) {
+ crm_err("Header compressed size %" PRIu32 " is too large",
+ header->payload_compressed);
+ return NULL;
+ }
+
+ expected_size = (size_t) header->payload_offset
+ + header->payload_compressed;
+
+ } else {
+ if (header->payload_uncompressed > (SIZE_MAX - header->payload_offset)) {
+ crm_err("Header uncompressed size %" PRIu32 " is too large",
+ header->payload_uncompressed);
+ return NULL;
+ }
+
+ expected_size = (size_t) header->payload_offset
+ + header->payload_uncompressed;
+ }
+
+ if (expected_size != header->size_total) {
+ crm_err("Header total size %" PRIu32 " does not match calculated "
+ "size %zu", header->size_total, expected_size);
+ return NULL;
+ }
+
return header;
}
--
2.53.0
From 8d21d297629bcc8aebf71bb171c550bcc4532c3f Mon Sep 17 00:00:00 2001
From: Chris Lumens <clumens@redhat.com>
Date: Thu, 14 May 2026 13:06:57 -0400
Subject: [PATCH 2/6] High: libcrmcommon: Fix integer overflow in remote
message code.
It's possible for a client to send a specifically constructed header
packet that will cause our size checks in pcmk__remote_message_xml to
overflow, which could result in crashes (potentially leading to a node
being fenced) or memory corruption problems.
Such a header would have to indicate that the payload is compressed,
which nothing in pacemaker is doing. In fact, we haven't used any of
the remote message compression code since it was introduced in 2013
which explains why we haven't seen any problems with it. Thus, it would
require a malicious client to cause this to happen.
It would also require remote CIB administration to be enabled via
remote-clear-port or remote-tls-port, which we do not do by default.
Additionally, if remote-tls-port is in use, it requires the client to
complete the gnutls handshake procedure (pcmk__remote_message_xml is
only called in cib_remote_msg after the handshake but before the client
has authenticated).
Co-Authored-By: Reid Wahl <nrwahl@protonmail.com>
---
lib/common/remote.c | 49 ++++++++++++++++++++++++++++++++++++++-------
1 file changed, 42 insertions(+), 7 deletions(-)
diff --git a/lib/common/remote.c b/lib/common/remote.c
index a4c8b8a..aa8e762 100644
--- a/lib/common/remote.c
+++ b/lib/common/remote.c
@@ -619,16 +619,51 @@ pcmk__remote_message_xml(pcmk__remote_t *remote)
}
/* Support compression on the receiving end now, in case we ever want to add it later */
- if (header->payload_compressed) {
+ if (header->payload_compressed != 0) {
int rc = 0;
- unsigned int size_u = 1 + header->payload_uncompressed;
- char *uncompressed =
- pcmk__assert_alloc(1, header->payload_offset + size_u);
+ unsigned int size_u = 0;
+ char *uncompressed = NULL;
+
+#if (UINT32_MAX < UINT_MAX)
+ if (header->payload_uncompressed >= UINT_MAX) {
+ crm_err("Couldn't decompress message because uncompressed "
+ "payload size (%" PRIu32 ") is greater than UINT_MAX "
+ "(%u)", header->payload_uncompressed, UINT_MAX);
+ return NULL;
+ }
+#endif
+
+ /* @TODO Is the extra byte for the null terminator?
+ * pcmk__remote_send_xml() also adds one byte to the iov length.
+ * (However, we do need to account for the possibility of receiving a
+ * message from an untrusted sender.)
+ */
+ size_u = 1 + header->payload_uncompressed;
+
+ /* Header and uncompressed payload must fit in the destination buffer.
+ * We do not need to separately check the header size here since
+ * localized_remote_header will return NULL if it's incorrect.
+ */
+#if (UINT_MAX >= SIZE_MAX)
+ if ((size_u >= SIZE_MAX)
+ || (header->payload_offset > (SIZE_MAX - size_u))) {
+#else
+ if (header->payload_offset > (SIZE_MAX - size_u)) {
+#endif
+ crm_err("Couldn't decompress message because the required buffer "
+ "size (%" PRIu32 " + %u) is greater than SIZE_MAX (%zu)",
+ header->payload_offset, size_u, SIZE_MAX);
+ return NULL;
+ }
+
+ crm_trace("Decompressing message data %" PRIu32 " bytes into %u "
+ "bytes", header->payload_compressed, size_u);
- crm_trace("Decompressing message data %d bytes into %d bytes",
- header->payload_compressed, size_u);
+ uncompressed = pcmk__assert_alloc(header->payload_offset + size_u,
+ sizeof(char));
- rc = BZ2_bzBuffToBuffDecompress(uncompressed + header->payload_offset, &size_u,
+ rc = BZ2_bzBuffToBuffDecompress(uncompressed + header->payload_offset,
+ &size_u,
remote->buffer + header->payload_offset,
header->payload_compressed, 1, 0);
rc = pcmk__bzlib2rc(rc);
--
2.53.0
From 77a530d1bb867264fa0d42752da44d75ee063830 Mon Sep 17 00:00:00 2001
From: Chris Lumens <clumens@redhat.com>
Date: Thu, 14 May 2026 13:26:07 -0400
Subject: [PATCH 3/6] High: libcrmcommon: Limit the max size of a remote
message.
Previously, we were just taking the sizes straight from the message
header and allocating however much memory was asked for without
checking. It's possible for a malicious client to ask for a very large
amount of memory (say, 4 GB since that'll fit in the uint32_t value for
header->size_total).
In that case, due to the use of pcmk__assert_alloc, we'll crash due to
an out of memory error and the node could potentially be fenced.
The fix is to simply check that the client isn't asking for too much
memory. This does unfortunately impose a size restriction on the
message. It's doubly unfortunate given how much work we've recently put
into removing restrictions on IPC messages allowing for much larger
clusters.
I think the biggest thing these messages will ever contain is a CIB, so
for now I'm going with a limit of 20 MB. It is not my intention that
this become a tuneable parameter that is exposed to the user.
---
include/crm/common/remote_internal.h | 5 ++++-
lib/common/remote.c | 17 +++++++++++++++--
2 files changed, 19 insertions(+), 3 deletions(-)
diff --git a/include/crm/common/remote_internal.h b/include/crm/common/remote_internal.h
index f8c0bea..00f5918 100644
--- a/include/crm/common/remote_internal.h
+++ b/include/crm/common/remote_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2008-2024 the Pacemaker project contributors
+ * Copyright 2008-2026 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
@@ -15,6 +15,9 @@
#include <crm/common/nodes.h> // pcmk_node_variant_remote
#include <crm/common/scheduler_types.h> // pcmk_node_t
+// The maximum payload size for a remote message (in bytes)
+#define PCMK__REMOTE_MSG_MAX_SIZE (20 * 1024 * 1024)
+
// internal functions from remote.c
typedef struct pcmk__remote_s pcmk__remote_t;
diff --git a/lib/common/remote.c b/lib/common/remote.c
index aa8e762..08b6988 100644
--- a/lib/common/remote.c
+++ b/lib/common/remote.c
@@ -623,6 +623,7 @@ pcmk__remote_message_xml(pcmk__remote_t *remote)
int rc = 0;
unsigned int size_u = 0;
char *uncompressed = NULL;
+ size_t buffer_size = 0;
#if (UINT32_MAX < UINT_MAX)
if (header->payload_uncompressed >= UINT_MAX) {
@@ -656,11 +657,17 @@ pcmk__remote_message_xml(pcmk__remote_t *remote)
return NULL;
}
+ buffer_size = (size_t) header->payload_offset + size_u;
+ if (buffer_size > PCMK__REMOTE_MSG_MAX_SIZE) {
+ crm_err("Message size %zu is larger than max allowed %u bytes",
+ buffer_size, PCMK__REMOTE_MSG_MAX_SIZE);
+ return NULL;
+ }
+
crm_trace("Decompressing message data %" PRIu32 " bytes into %u "
"bytes", header->payload_compressed, size_u);
- uncompressed = pcmk__assert_alloc(header->payload_offset + size_u,
- sizeof(char));
+ uncompressed = pcmk__assert_alloc(buffer_size, sizeof(char));
rc = BZ2_bzBuffToBuffDecompress(uncompressed + header->payload_offset,
&size_u,
@@ -805,6 +812,12 @@ pcmk__read_available_remote_data(pcmk__remote_t *remote)
read_len = header->size_total;
}
+ if (read_len > PCMK__REMOTE_MSG_MAX_SIZE) {
+ crm_err("Message size %zu is larger than max allowed %u bytes",
+ read_len, PCMK__REMOTE_MSG_MAX_SIZE);
+ return EINVAL;
+ }
+
/* automatically grow the buffer when needed */
if(remote->buffer_size < read_len) {
remote->buffer_size = 2 * read_len;
--
2.53.0
From eaae2d00a2d29c0187832a55a81cf42b39d93a25 Mon Sep 17 00:00:00 2001
From: Chris Lumens <clumens@redhat.com>
Date: Fri, 15 May 2026 10:19:33 -0400
Subject: [PATCH 4/6] High: libcrmcommon: Fix an integer overflow in
pcmk__remote_send_xml.
iov[0].iov_len is a size_t. iov[1].iov_len is also a size_t (which
comes from the length of a GString, which is a gsize, which is also a
size_t). We're adding those together and storing them in a uint32_t, so
make sure the result will fit.
Fixes RHEL-181155
---
lib/common/remote.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/lib/common/remote.c b/lib/common/remote.c
index 08b6988..83f050d 100644
--- a/lib/common/remote.c
+++ b/lib/common/remote.c
@@ -586,6 +586,13 @@ pcmk__remote_send_xml(pcmk__remote_t *remote, const xmlNode *msg)
header->version = REMOTE_MSG_VERSION;
header->payload_offset = iov[0].iov_len;
header->payload_uncompressed = iov[1].iov_len;
+
+ if ((UINT32_MAX - iov[0].iov_len) < iov[1].iov_len) {
+ crm_err("Remote message size %zu + %zu exceeds maximum of %" PRIu32,
+ iov[0].iov_len, iov[1].iov_len, UINT32_MAX);
+ goto done;
+ }
+
header->size_total = iov[0].iov_len + iov[1].iov_len;
rc = remote_send_iovs(remote, iov, 2);
@@ -594,6 +601,7 @@ pcmk__remote_send_xml(pcmk__remote_t *remote, const xmlNode *msg)
pcmk_rc_str(rc), rc);
}
+done:
free(iov[0].iov_base);
g_free((gchar *) iov[1].iov_base);
return rc;
--
2.53.0
From 2e097d03f1d93f95162a37a5c21ec46d911af176 Mon Sep 17 00:00:00 2001
From: Chris Lumens <clumens@redhat.com>
Date: Thu, 28 May 2026 12:56:57 -0400
Subject: [PATCH 5/6] Refactor: libcib: Remove an unnecessary coverity
suppression.
With all the previous changes, this suppression is no longer necessary
and can be removed.
---
lib/cib/cib_remote.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/lib/cib/cib_remote.c b/lib/cib/cib_remote.c
index baed17b..a64ff95 100644
--- a/lib/cib/cib_remote.c
+++ b/lib/cib/cib_remote.c
@@ -243,7 +243,6 @@ cib_remote_callback_dispatch(gpointer user_data)
return -1;
}
- // coverity[tainted_data] This can't easily be changed right now
msg = pcmk__remote_message_xml(&private->callback);
if (msg == NULL) {
private->start_time = 0;
--
2.53.0
From 2a3c713c3ceab8f5aded9693d09203d18b33bf55 Mon Sep 17 00:00:00 2001
From: Chris Lumens <clumens@redhat.com>
Date: Wed, 17 Jun 2026 15:55:28 -0400
Subject: [PATCH 6/6] Med: libcrmcommon: Add additional checks to
pcmk__remote_message_xml.
coverity flags the existing code for using a tainted expression as an
index to a pointer. While we're fixing other range errors and overflows
in this code, we should fix this one as well by porting the existing
code from the main branch.
---
lib/common/remote.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/lib/common/remote.c b/lib/common/remote.c
index 83f050d..9ad4b17 100644
--- a/lib/common/remote.c
+++ b/lib/common/remote.c
@@ -620,6 +620,7 @@ xmlNode *
pcmk__remote_message_xml(pcmk__remote_t *remote)
{
xmlNode *xml = NULL;
+ size_t data_size = 0;
struct remote_header_v0 *header = localized_remote_header(remote);
if (header == NULL) {
@@ -709,7 +710,18 @@ pcmk__remote_message_xml(pcmk__remote_t *remote)
/* take ownership of the buffer */
remote->buffer_offset = 0;
- CRM_LOG_ASSERT(remote->buffer[sizeof(struct remote_header_v0) + header->payload_uncompressed - 1] == 0);
+ data_size = (size_t) header->payload_offset + header->payload_uncompressed;
+
+ // Ensure the buffer is as big as it should be
+ CRM_CHECK(remote->buffer_size >= data_size, return NULL);
+
+ /* Ensure the buffer is null-terminated (see
+ * pcmk__read_available_remote_data()).
+ *
+ * Note that payload_uncompressed contains the payload size including the
+ * null byte (see pcmk__remote_send_xml()).
+ */
+ CRM_CHECK(remote->buffer[data_size] == '\0', return NULL);
xml = pcmk__xml_parse(remote->buffer + header->payload_offset);
if (xml == NULL && header->version > REMOTE_MSG_VERSION) {
--
2.53.0