From 8e667ef87ac4bc66ab8c64599334b521655925f7 Mon Sep 17 00:00:00 2001 From: Chris Lumens 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 #include #include -#include // PRIx32 +#include // PRIu32, PRIx32 #include #include @@ -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;