thrift: fix CVE-2026-45112

Backport patch to fix CVE-2026-45112.

References:
  https://nvd.nist.gov/vuln/detail/CVE-2026-45112

Upstream fix:
  https://github.com/apache/thrift/commit/16b3673827498d1f4b05f8026d5d580b8e8f5aa2

Signed-off-by: Adarsh Jagadish Kamini <adarsh.jagadish.kamini@est.tech>
Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
This commit is contained in:
Adarsh Jagadish Kamini
2026-09-15 07:52:15 +05:30
committed by Anuj Mittal
parent 50b2a64565
commit 244521fbe6
2 changed files with 307 additions and 0 deletions
@@ -0,0 +1,306 @@
From 4d68f215f05905a19dd43865fb97d170b4fc1a3b Mon Sep 17 00:00:00 2001
From: Jens Geyer <jensg@apache.org>
Date: Fri, 8 May 2026 21:24:30 +0200
Subject: [PATCH] Add message byte tracking to consumeBuffer() in Java
transports Client: java
- consumeBuffer() in TMemoryInputTransport and AutoExpandingBufferReadTransport
now decrements remainingMessageSize inline so fast-path buffer reads are
counted toward the per-message size limit
- AutoExpandingBufferReadTransport.fill() resets remainingMessageSize at the
start of each frame, matching TMemoryInputTransport.reset() behavior in
TFramedTransport
- TBinaryProtocol.readString() now checks stringLengthLimit before the
fast-path branch
- Add TestMessageSizeLimits covering all fixed code paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CVE: CVE-2026-45112
Upstream-Status: Backport [https://github.com/apache/thrift/commit/16b3673827498d1f4b05f8026d5d580b8e8f5aa2]
Signed-off-by: Adarsh Jagadish Kamini <adarsh.jagadish.kamini@est.tech>
---
.../thrift/protocol/TBinaryProtocol.java | 6 +-
.../AutoExpandingBufferReadTransport.java | 6 +
.../transport/TMemoryInputTransport.java | 7 +-
.../protocol/TestMessageSizeLimits.java | 196 ++++++++++++++++++
4 files changed, 213 insertions(+), 2 deletions(-)
create mode 100644 lib/java/src/test/java/org/apache/thrift/protocol/TestMessageSizeLimits.java
diff --git a/lib/java/src/main/java/org/apache/thrift/protocol/TBinaryProtocol.java b/lib/java/src/main/java/org/apache/thrift/protocol/TBinaryProtocol.java
index 99c3e9302..49e82ee8a 100644
--- a/lib/java/src/main/java/org/apache/thrift/protocol/TBinaryProtocol.java
+++ b/lib/java/src/main/java/org/apache/thrift/protocol/TBinaryProtocol.java
@@ -458,6 +458,8 @@ public class TBinaryProtocol extends TProtocol {
public String readString() throws TException {
int size = readI32();
+ checkStringReadLength(size);
+
if (trans_.getBytesRemainingInBuffer() >= size) {
String s =
new String(trans_.getBuffer(), trans_.getBufferPosition(), size, StandardCharsets.UTF_8);
@@ -465,7 +467,9 @@ public class TBinaryProtocol extends TProtocol {
return s;
}
- return readStringBody(size);
+ byte[] buf = new byte[size];
+ trans_.readAll(buf, 0, size);
+ return new String(buf, StandardCharsets.UTF_8);
}
public String readStringBody(int size) throws TException {
diff --git a/lib/java/src/main/java/org/apache/thrift/transport/AutoExpandingBufferReadTransport.java b/lib/java/src/main/java/org/apache/thrift/transport/AutoExpandingBufferReadTransport.java
index d59c657db..53b35c207 100644
--- a/lib/java/src/main/java/org/apache/thrift/transport/AutoExpandingBufferReadTransport.java
+++ b/lib/java/src/main/java/org/apache/thrift/transport/AutoExpandingBufferReadTransport.java
@@ -39,6 +39,7 @@ public class AutoExpandingBufferReadTransport extends TEndpointTransport {
inTrans.readAll(buf.array(), 0, length);
pos = 0;
limit = length;
+ resetConsumedMessageSize(-1);
}
@Override
@@ -70,6 +71,11 @@ public class AutoExpandingBufferReadTransport extends TEndpointTransport {
@Override
public final void consumeBuffer(int len) {
pos += len;
+ if (remainingMessageSize >= len) {
+ remainingMessageSize -= len;
+ } else {
+ remainingMessageSize = 0;
+ }
}
@Override
diff --git a/lib/java/src/main/java/org/apache/thrift/transport/TMemoryInputTransport.java b/lib/java/src/main/java/org/apache/thrift/transport/TMemoryInputTransport.java
index 375e2b72a..2fff0dacc 100644
--- a/lib/java/src/main/java/org/apache/thrift/transport/TMemoryInputTransport.java
+++ b/lib/java/src/main/java/org/apache/thrift/transport/TMemoryInputTransport.java
@@ -96,7 +96,6 @@ public final class TMemoryInputTransport extends TEndpointTransport {
if (amtToRead > 0) {
System.arraycopy(buf_, pos_, buf, off, amtToRead);
consumeBuffer(amtToRead);
- countConsumedMessageBytes(amtToRead);
}
return amtToRead;
}
@@ -119,7 +118,13 @@ public final class TMemoryInputTransport extends TEndpointTransport {
return endPos_ - pos_;
}
+ @Override
public void consumeBuffer(int len) {
pos_ += len;
+ if (remainingMessageSize >= len) {
+ remainingMessageSize -= len;
+ } else {
+ remainingMessageSize = 0;
+ }
}
}
diff --git a/lib/java/src/test/java/org/apache/thrift/protocol/TestMessageSizeLimits.java b/lib/java/src/test/java/org/apache/thrift/protocol/TestMessageSizeLimits.java
new file mode 100644
index 000000000..ab12a0f70
--- /dev/null
+++ b/lib/java/src/test/java/org/apache/thrift/protocol/TestMessageSizeLimits.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.thrift.protocol;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.Arrays;
+import org.apache.thrift.TConfiguration;
+import org.apache.thrift.transport.AutoExpandingBufferReadTransport;
+import org.apache.thrift.transport.TMemoryInputTransport;
+import org.apache.thrift.transport.TTransportException;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests that message size limits are enforced in the fast-path buffer reads of
+ * TBinaryProtocol/TCompactProtocol and that consumeBuffer() correctly tracks consumed bytes.
+ */
+public class TestMessageSizeLimits {
+
+ /** Build TBinaryProtocol-encoded bytes for a string: 4-byte big-endian length + raw bytes. */
+ private byte[] encodeBinaryString(String s) {
+ byte[] data = s.getBytes(java.nio.charset.StandardCharsets.UTF_8);
+ byte[] out = new byte[4 + data.length];
+ int len = data.length;
+ out[0] = (byte) (len >> 24);
+ out[1] = (byte) (len >> 16);
+ out[2] = (byte) (len >> 8);
+ out[3] = (byte) len;
+ System.arraycopy(data, 0, out, 4, data.length);
+ return out;
+ }
+
+ /**
+ * Build TCompactProtocol-encoded bytes for a string: varint length + raw bytes. Assumes length <
+ * 128 so it fits in one varint byte.
+ */
+ private byte[] encodeCompactString(String s) {
+ byte[] data = s.getBytes(java.nio.charset.StandardCharsets.UTF_8);
+ assert data.length < 128 : "test helper only handles single-byte varints";
+ byte[] out = new byte[1 + data.length];
+ out[0] = (byte) data.length;
+ System.arraycopy(data, 0, out, 1, data.length);
+ return out;
+ }
+
+ private static String repeat(char c, int n) {
+ char[] chars = new char[n];
+ Arrays.fill(chars, c);
+ return new String(chars);
+ }
+
+ @Test
+ public void testBinaryProtocol_stringLengthLimitEnforcedInFastPath() throws Exception {
+ // 100-char string encoded for TBinaryProtocol - all bytes in transport so fast path fires
+ byte[] buf = encodeBinaryString(repeat('A', 100));
+ TMemoryInputTransport transport = new TMemoryInputTransport(buf);
+
+ // Protocol limited to 10 bytes per string
+ TBinaryProtocol proto = new TBinaryProtocol(transport, 10L, -1L, false, true);
+
+ assertThrows(
+ TProtocolException.class,
+ proto::readString,
+ "stringLengthLimit must be enforced even when the fast path is taken");
+ }
+
+ @Test
+ public void testBinaryProtocol_stringLengthLimitAllowsValidString() throws Exception {
+ byte[] buf = encodeBinaryString("Hello");
+ TMemoryInputTransport transport = new TMemoryInputTransport(buf);
+
+ TBinaryProtocol proto = new TBinaryProtocol(transport, 10L, -1L, false, true);
+
+ assertDoesNotThrow(
+ proto::readString, "string within stringLengthLimit must be readable via fast path");
+ }
+
+ @Test
+ public void testCompactProtocol_stringLengthLimitEnforcedInFastPath() throws Exception {
+ byte[] buf = encodeCompactString(repeat('A', 100));
+ TMemoryInputTransport transport = new TMemoryInputTransport(buf);
+
+ TCompactProtocol proto = new TCompactProtocol(transport, 10L, -1L);
+
+ assertThrows(
+ TProtocolException.class,
+ proto::readString,
+ "TCompactProtocol stringLengthLimit must reject oversized strings in fast path");
+ }
+
+ @Test
+ public void testConsumeBuffer_decrementsRemainingMessageSize() throws Exception {
+ // 20-byte transport; updateKnownMessageSize sets remainingMessageSize = 20
+ byte[] buf = new byte[20];
+ TMemoryInputTransport transport = new TMemoryInputTransport(buf);
+
+ transport.consumeBuffer(15);
+
+ // 5 bytes remain; requesting 5 must succeed
+ assertDoesNotThrow(() -> transport.checkReadBytesAvailable(5));
+
+ // requesting 6 must fail
+ assertThrows(
+ TTransportException.class,
+ () -> transport.checkReadBytesAvailable(6),
+ "checkReadBytesAvailable must reflect bytes consumed via consumeBuffer");
+ }
+
+ @Test
+ public void testBinaryProtocol_fastPathReadsDrainRemainingMessageSize() throws Exception {
+ // 10 i32 values = 40 bytes; maxMessageSize exactly 40
+ byte[] buf = new byte[40];
+ TConfiguration config = TConfiguration.custom().setMaxMessageSize(40).build();
+ TMemoryInputTransport transport = new TMemoryInputTransport(config, buf);
+ TBinaryProtocol proto = new TBinaryProtocol(transport);
+
+ // Reading 8 i32 values via fast path consumes 32 bytes
+ for (int i = 0; i < 8; i++) {
+ proto.readI32();
+ }
+
+ // 8 bytes remain — requesting exactly 8 must succeed
+ assertDoesNotThrow(
+ () -> transport.checkReadBytesAvailable(8),
+ "8 bytes should still be available after 32 consumed via fast path");
+
+ // requesting 9 must fail — size limit is properly tracked
+ assertThrows(
+ TTransportException.class,
+ () -> transport.checkReadBytesAvailable(9),
+ "fast-path reads must decrement remaining message size so limits can be enforced");
+ }
+
+ @Test
+ public void testRead_doesNotDoubleCountConsumedBytes() throws Exception {
+ // If read() double-counted, consuming 10 bytes via read() on a 20-byte transport
+ // would wrongly decrement remainingMessageSize by 20 instead of 10.
+ byte[] buf = new byte[20];
+ TMemoryInputTransport transport = new TMemoryInputTransport(buf);
+ byte[] dest = new byte[10];
+ transport.read(dest, 0, 10);
+
+ // 10 bytes remain; requesting 10 must succeed
+ assertDoesNotThrow(() -> transport.checkReadBytesAvailable(10));
+
+ // requesting 11 must fail
+ assertThrows(
+ TTransportException.class,
+ () -> transport.checkReadBytesAvailable(11),
+ "read() must not double-count consumed bytes after consumeBuffer fix");
+ }
+
+ @Test
+ public void testAutoExpandingBufferReadTransport_fillResetsMessageSizePerFrame()
+ throws Exception {
+ // fill() must reset remainingMessageSize so that consumption across frames on a long-lived
+ // TFastFramedTransport connection does not accumulate toward the per-message limit.
+ TConfiguration config = TConfiguration.custom().setMaxMessageSize(80).build();
+ AutoExpandingBufferReadTransport readBuf = new AutoExpandingBufferReadTransport(config, 100);
+ TMemoryInputTransport source = new TMemoryInputTransport(new byte[200]);
+
+ // Frame 1: fill and consume 70 bytes
+ readBuf.fill(source, 70);
+ readBuf.consumeBuffer(70);
+
+ // Frame 2: fill resets remainingMessageSize to maxMessageSize (80) before new frame
+ readBuf.fill(source, 70);
+ readBuf.consumeBuffer(70);
+
+ // After two frames of 70 bytes each, 10 bytes of budget must still be available
+ assertDoesNotThrow(
+ () -> readBuf.checkReadBytesAvailable(10),
+ "fill() must reset remainingMessageSize so multi-frame connections stay functional");
+ assertThrows(
+ TTransportException.class,
+ () -> readBuf.checkReadBytesAvailable(11),
+ "remaining budget after fill-reset and 70-byte consume should be exactly 10");
+ }
+}
@@ -19,6 +19,7 @@ SRC_URI = "https://downloads.apache.org/${BPN}/${PV}/${BP}.tar.gz \
file://CVE-2026-58389.patch \
file://CVE-2026-58662.patch \
file://CVE-2026-41608.patch \
file://CVE-2026-45112.patch \
"
SRC_URI[sha256sum] = "794a0e455787960d9f27ab92c38e34da27e8deeda7a5db0e59dc64a00df8a1e5"