pacemaker/008-CVE-2026-10649.patch

220 lines
8.0 KiB
Diff

From 8e667ef87ac4bc66ab8c64599334b521655925f7 Mon Sep 17 00:00:00 2001
From: Chris Lumens <clumens@redhat.com>
Date: Thu, 14 May 2026 16:57:17 +0000
Subject: [PATCH] Fix CVE-2026-10649: bounds and overflow checks in remote
message handling
Squashed backport of the CVE-2026-10649 fixes from upstream
ClusterLabs/pacemaker, adapted to the 3.0.1 tree (which predates the
pcmk__err()/pcmk__trace() logging macro rename and the
pcmk__assert_alloc() argument order change):
8e667ef87ac4 Med: libcrmcommon: Fix checks in localized_remote_header.
1e1825bf2c28 High: libcrmcommon: Fix integer overflow in remote message code.
bb37829e00c7 High: libcrmcommon: Limit the max size of a remote message.
6dd7ce8e656b High: libcrmcommon: Fix an integer overflow in pcmk__remote_send_xml.
c49b26cb1e11 Refactor: libcib: Remove an unnecessary coverity suppression.
---
diff --git a/include/crm/common/remote_internal.h b/include/crm/common/remote_internal.h
--- a/include/crm/common/remote_internal.h
+++ b/include/crm/common/remote_internal.h
@@ -23,6 +23,9 @@
extern "C" {
#endif
+// 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/cib/cib_remote.c b/lib/cib/cib_remote.c
--- a/lib/cib/cib_remote.c
+++ b/lib/cib/cib_remote.c
@@ -239,7 +239,6 @@
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;
diff --git a/lib/common/remote.c b/lib/common/remote.c
--- a/lib/common/remote.c
+++ b/lib/common/remote.c
@@ -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>
@@ -96,11 +96,19 @@
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);
@@ -122,6 +130,41 @@
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;
}
@@ -266,6 +309,13 @@
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);
@@ -274,6 +324,7 @@
pcmk_rc_str(rc), rc);
}
+done:
free(iov[0].iov_base);
g_free((gchar *) iov[1].iov_base);
return rc;
@@ -299,16 +350,58 @@
}
/* 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;
+ size_t buffer_size = 0;
+
+#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;
+ }
+
+ 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 %d bytes into %d bytes",
- header->payload_compressed, size_u);
+ crm_trace("Decompressing message data %" PRIu32 " bytes into %u "
+ "bytes", header->payload_compressed, size_u);
- rc = BZ2_bzBuffToBuffDecompress(uncompressed + header->payload_offset, &size_u,
+ uncompressed = pcmk__assert_alloc(buffer_size, sizeof(char));
+
+ rc = BZ2_bzBuffToBuffDecompress(uncompressed + header->payload_offset,
+ &size_u,
remote->buffer + header->payload_offset,
header->payload_compressed, 1, 0);
rc = pcmk__bzlib2rc(rc);
@@ -443,6 +536,12 @@
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;