From 244521fbe623d7b0b1032162298f84bc6a306e32 Mon Sep 17 00:00:00 2001 From: Adarsh Jagadish Kamini Date: Mon, 7 Sep 2026 09:42:18 +0200 Subject: [PATCH] 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 Signed-off-by: Anuj Mittal --- .../thrift/thrift/CVE-2026-45112.patch | 306 ++++++++++++++++++ .../thrift/thrift_0.22.0.bb | 1 + 2 files changed, 307 insertions(+) create mode 100644 meta-oe/recipes-connectivity/thrift/thrift/CVE-2026-45112.patch diff --git a/meta-oe/recipes-connectivity/thrift/thrift/CVE-2026-45112.patch b/meta-oe/recipes-connectivity/thrift/thrift/CVE-2026-45112.patch new file mode 100644 index 0000000000..7e4fb78911 --- /dev/null +++ b/meta-oe/recipes-connectivity/thrift/thrift/CVE-2026-45112.patch @@ -0,0 +1,306 @@ +From 4d68f215f05905a19dd43865fb97d170b4fc1a3b Mon Sep 17 00:00:00 2001 +From: Jens Geyer +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 + +CVE: CVE-2026-45112 +Upstream-Status: Backport [https://github.com/apache/thrift/commit/16b3673827498d1f4b05f8026d5d580b8e8f5aa2] + +Signed-off-by: Adarsh Jagadish Kamini +--- + .../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"); ++ } ++} diff --git a/meta-oe/recipes-connectivity/thrift/thrift_0.22.0.bb b/meta-oe/recipes-connectivity/thrift/thrift_0.22.0.bb index 6d76771ab2..60465b3950 100644 --- a/meta-oe/recipes-connectivity/thrift/thrift_0.22.0.bb +++ b/meta-oe/recipes-connectivity/thrift/thrift_0.22.0.bb @@ -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"