220 lines
8.0 KiB
Diff
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;
|