pacemaker/SOURCES/017-remote-overflow.patch
2026-07-14 13:21:03 -04:00

387 lines
14 KiB
Diff

From 2d9c031078a83796dcbe4faa0bad3579c4fcd1c3 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/5] 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 fe19296..f771f46 100644
--- a/lib/common/remote.c
+++ b/lib/common/remote.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2008-2023 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 43cadbc4ce424a881ec0b3258d65e4f94bda3175 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/5] 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 | 50 +++++++++++++++++++++++++++++++++++++++------
1 file changed, 44 insertions(+), 6 deletions(-)
diff --git a/lib/common/remote.c b/lib/common/remote.c
index f771f46..54620c2 100644
--- a/lib/common/remote.c
+++ b/lib/common/remote.c
@@ -593,15 +593,53 @@ 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 = calloc(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
- crm_trace("Decompressing message data %d bytes into %d bytes",
- header->payload_compressed, size_u);
+ /* @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);
+
+ uncompressed = calloc(1, header->payload_offset + size_u);
+ if (uncompressed == NULL) {
+ return NULL;
+ }
- 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 07f44f9db114ea7f609029410712835c1256cd55 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/5] 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 calloc, 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 | 16 +++++++++++++++-
2 files changed, 19 insertions(+), 2 deletions(-)
diff --git a/include/crm/common/remote_internal.h b/include/crm/common/remote_internal.h
index 030c7a4..c39fcef 100644
--- a/include/crm/common/remote_internal.h
+++ b/include/crm/common/remote_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2008-2023 the Pacemaker project contributors
+ * Copyright 2008-2026 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
@@ -10,6 +10,9 @@
#ifndef PCMK__REMOTE_INTERNAL__H
# define PCMK__REMOTE_INTERNAL__H
+// 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 54620c2..256ff11 100644
--- a/lib/common/remote.c
+++ b/lib/common/remote.c
@@ -597,6 +597,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) {
@@ -630,10 +631,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 = calloc(1, header->payload_offset + size_u);
+ uncompressed = calloc(1, buffer_size);
if (uncompressed == NULL) {
return NULL;
}
@@ -780,6 +788,12 @@ 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 3d9bf582dc28b94d59037ac32a3b4ee6498a3e35 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/5] 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 string, which is 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 256ff11..26f1917 100644
--- a/lib/common/remote.c
+++ b/lib/common/remote.c
@@ -560,6 +560,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);
@@ -568,6 +575,7 @@ pcmk__remote_send_xml(pcmk__remote_t *remote, const xmlNode *msg)
pcmk_rc_str(rc), rc);
}
+done:
free(iov[0].iov_base);
free(iov[1].iov_base);
return rc;
--
2.53.0
From d022a58012629a5d4ecc84dc5d2f325f01738adc Mon Sep 17 00:00:00 2001
From: Chris Lumens <clumens@redhat.com>
Date: Wed, 17 Jun 2026 15:55:28 -0400
Subject: [PATCH 5/5] 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 26f1917..fbcf0a0 100644
--- a/lib/common/remote.c
+++ b/lib/common/remote.c
@@ -594,6 +594,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) {
@@ -686,7 +687,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 = string2xml(remote->buffer + header->payload_offset);
if (xml == NULL && header->version > REMOTE_MSG_VERSION) {
--
2.53.0