thrift: fix CVE-2026-48586

Improper Handling of Highly Compressed Data (Data Amplification)
vulnerability in Apache Thrift C++, Java, Python, Go, D, C/GLib
bindings. This issue affects Apache Thrift: before 0.24.0.

Backport patch to fix CVE-2026-48586.

Reference:
[https://nvd.nist.gov/vuln/detail/cve-2026-48586]

Upstream Patch:
[https://github.com/apache/thrift/commit/e3c8c534cacdbef039f4bb0bcf4b2744a982e3bf]
[https://github.com/apache/thrift/commit/702d00f9053133aba7186f2dbb242a91a1ddd491]

Signed-off-by: Abhishek Bachiphale <Abhishek.Bachiphale@windriver.com>
Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
This commit is contained in:
Abhishek Bachiphale
2026-09-15 10:13:24 +05:30
committed by Anuj Mittal
parent 29cdca4152
commit 2c673e4a4f
3 changed files with 189 additions and 0 deletions
@@ -0,0 +1,109 @@
From e3c8c534cacdbef039f4bb0bcf4b2744a982e3bf Mon Sep 17 00:00:00 2001
From: Jens Geyer <jensg@apache.org>
Date: Thu, 21 May 2026 14:24:28 +0200
Subject: [PATCH] Add decompressed byte tracking to C++ TZlibTransport Client:
cpp
TZlibTransport::read() called checkReadBytesAvailable() but never
called countConsumedMessageBytes(), so remainingMessageSize_ was never
decremented. Each read() call would pass the check regardless of how
many bytes had already been decompressed. Add countConsumedMessageBytes()
after each copy from the inflate buffer so that the TConfiguration
maxMessageSize limit is correctly enforced across the lifetime of a
decompression session.
Also adds test_message_size_limit() to ZlibTest to cover this path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CVE: CVE-2026-48586
Upstream-Status: Backport [https://github.com/apache/thrift/commit/e3c8c534cacdbef039f4bb0bcf4b2744a982e3bf]
Signed-off-by: Abhishek Bachiphale <Abhishek.Bachiphale@windriver.com>
---
.../src/thrift/transport/TZlibTransport.cpp | 1 +
lib/cpp/test/ZlibTest.cpp | 37 +++++++++++++++++++
2 files changed, 38 insertions(+)
diff --git a/lib/cpp/src/thrift/transport/TZlibTransport.cpp b/lib/cpp/src/thrift/transport/TZlibTransport.cpp
index 1a171109837..d6161a129d9 100644
--- a/lib/cpp/src/thrift/transport/TZlibTransport.cpp
+++ b/lib/cpp/src/thrift/transport/TZlibTransport.cpp
@@ -149,6 +149,7 @@ uint32_t TZlibTransport::read(uint8_t* buf, uint32_t len) {
need -= give;
buf += give;
urpos_ += give;
+ countConsumedMessageBytes(give);
// If they were satisfied, we are done.
if (need == 0) {
diff --git a/lib/cpp/test/ZlibTest.cpp b/lib/cpp/test/ZlibTest.cpp
index ea9c617f625..8f8f5a8e7cf 100644
--- a/lib/cpp/test/ZlibTest.cpp
+++ b/lib/cpp/test/ZlibTest.cpp
@@ -45,8 +45,10 @@
#include <thrift/transport/TBufferTransports.h>
#include <thrift/transport/TZlibTransport.h>
+#include <thrift/TConfiguration.h>
using namespace apache::thrift::transport;
+using apache::thrift::TConfiguration;
using std::shared_ptr;
using std::string;
@@ -338,6 +340,38 @@ void test_get_underlying_transport() {
BOOST_CHECK_EQUAL(membuf.get(), zlib_trans->getUnderlyingTransport().get());
}
+void test_message_size_limit() {
+ // Write 4 KB of compressible data, then read it back with a 1 KB limit.
+ const uint32_t write_len = 4096;
+ boost::shared_array<uint8_t> buf = gen_uniform_buffer(write_len, 'a');
+
+ shared_ptr<TMemoryBuffer> membuf(new TMemoryBuffer());
+ {
+ shared_ptr<TZlibTransport> writer(new TZlibTransport(membuf));
+ writer->write(buf.get(), write_len);
+ writer->finish();
+ }
+
+ auto config = std::make_shared<TConfiguration>();
+ config->setMaxMessageSize(1024);
+ shared_ptr<TZlibTransport> reader(new TZlibTransport(
+ membuf,
+ TZlibTransport::DEFAULT_URBUF_SIZE,
+ TZlibTransport::DEFAULT_CRBUF_SIZE,
+ TZlibTransport::DEFAULT_UWBUF_SIZE,
+ TZlibTransport::DEFAULT_CWBUF_SIZE,
+ Z_DEFAULT_COMPRESSION,
+ config));
+
+ boost::shared_array<uint8_t> mirror(new uint8_t[write_len]);
+ try {
+ reader->readAll(mirror.get(), write_len);
+ BOOST_ERROR("readAll() should have thrown when maxMessageSize is exceeded");
+ } catch (TTransportException& ex) {
+ BOOST_CHECK_EQUAL(ex.getType(), TTransportException::END_OF_FILE);
+ }
+}
+
/*
* Initialization
*/
@@ -441,6 +475,7 @@ bool init_unit_test_suite() {
suite->add(BOOST_TEST_CASE(test_no_write));
suite->add(BOOST_TEST_CASE(test_get_underlying_transport));
+ suite->add(BOOST_TEST_CASE(test_message_size_limit));
return true;
}
@@ -467,6 +502,8 @@ boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) {
add_tests(suite, gen_random_buffer(buf_len), buf_len, "random");
suite->add(BOOST_TEST_CASE(test_no_write));
+ suite->add(BOOST_TEST_CASE(test_get_underlying_transport));
+ suite->add(BOOST_TEST_CASE(test_message_size_limit));
return nullptr;
}
@@ -0,0 +1,78 @@
From 702d00f9053133aba7186f2dbb242a91a1ddd491 Mon Sep 17 00:00:00 2001
From: Jens Geyer <jensg@apache.org>
Date: Thu, 21 May 2026 22:29:35 +0200
Subject: [PATCH] Add consumed byte tracking to ThriftZlibTransport read
Client: c_glib
Call countConsumedMessageBytes() after each successful read so that
the pre-existing checkReadBytesAvailable() limit is decremented per
the transport's message size configuration.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CVE: CVE-2026-48586
Upstream-Status: Backport [https://github.com/apache/thrift/commit/702d00f9053133aba7186f2dbb242a91a1ddd491]
Signed-off-by: Abhishek Bachiphale <Abhishek.Bachiphale@windriver.com>
---
.../c_glib/transport/thrift_zlib_transport.c | 4 +++
lib/c_glib/test/testzlibtransport.c | 26 +++++++++++++++++++
2 files changed, 30 insertions(+)
diff --git a/lib/c_glib/src/thrift/c_glib/transport/thrift_zlib_transport.c b/lib/c_glib/src/thrift/c_glib/transport/thrift_zlib_transport.c
index 32f1ba27d74..459d08f79bc 100644
--- a/lib/c_glib/src/thrift/c_glib/transport/thrift_zlib_transport.c
+++ b/lib/c_glib/src/thrift/c_glib/transport/thrift_zlib_transport.c
@@ -238,6 +238,10 @@ thrift_zlib_transport_read (ThriftTransport *transport, gpointer buf,
break;
}
+ if (!ttc->countConsumedMessageBytes (transport, i, error)) {
+ return -1;
+ }
+
return len;
}
diff --git a/lib/c_glib/test/testzlibtransport.c b/lib/c_glib/test/testzlibtransport.c
index 04e368ffa93..531241acd7f 100644
--- a/lib/c_glib/test/testzlibtransport.c
+++ b/lib/c_glib/test/testzlibtransport.c
@@ -214,6 +214,31 @@ thrift_server (const int port)
g_object_unref (tsocket);
}
+static void
+test_message_size_limit(void)
+{
+ ThriftSocket *tsocket = NULL;
+ ThriftTransport *transport = NULL;
+ gchar readbuf[4096];
+ GError *err = NULL;
+ gint32 ret;
+
+ /* checkReadBytesAvailable fires before any I/O, so the underlying transport
+ * does not need to be connected or contain data. */
+ tsocket = g_object_new (THRIFT_TYPE_SOCKET, "hostname", "localhost",
+ "port", 9999, NULL);
+ transport = g_object_new (THRIFT_TYPE_ZLIB_TRANSPORT,
+ "transport", THRIFT_TRANSPORT (tsocket),
+ "remainingmessagesize", (glong) 1024,
+ NULL);
+
+ ret = thrift_zlib_transport_read (transport, readbuf, sizeof (readbuf), &err);
+ g_assert (ret < 0);
+ if (err) g_error_free (err);
+ g_object_unref (transport);
+ g_object_unref (tsocket);
+}
+
int
main(int argc, char *argv[])
{
@@ -226,6 +251,7 @@ main(int argc, char *argv[])
g_test_add_func ("/testzlibtransport/CreateAndDestroy", test_create_and_destroy);
g_test_add_func ("/testzlibtransport/OpenAndClose", test_open_and_close);
g_test_add_func ("/testzlibtransport/ReadAndWrite", test_read_and_write);
+ g_test_add_func ("/testzlibtransport/MessageSizeLimit", test_message_size_limit);
return g_test_run ();
}
@@ -22,6 +22,8 @@ SRC_URI = "https://downloads.apache.org/${BPN}/${PV}/${BP}.tar.gz \
file://CVE-2026-45112.patch \
file://CVE-2026-55969.patch \
file://CVE-2026-55970.patch \
file://CVE-2026-48586-01.patch \
file://CVE-2026-48586-02.patch \
"
SRC_URI[sha256sum] = "794a0e455787960d9f27ab92c38e34da27e8deeda7a5db0e59dc64a00df8a1e5"