mirror of
https://github.com/openembedded/meta-openembedded.git
synced 2026-10-15 06:17:16 +00:00
thrift: fix CVE-2026-41608
Backport patch to fix CVE-2026-41608. References: https://nvd.nist.gov/vuln/detail/CVE-2026-41608 Upstream fix: https://github.com/apache/thrift/commit/8c605880ea4ef050a689c859de3b702ad8d4ce7b 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:
1 parent
194a8339ce
commit
50b2a64565
2 files changed
+177
No files matched your search
@@ -0,0 +1,176 @@
|
||||
From d6ef4cd811666ce8c72dd5417d95debce52f490b Mon Sep 17 00:00:00 2001
|
||||
From: Jens Geyer <jensg@apache.org>
|
||||
Date: Sun, 19 Apr 2026 10:10:35 +0200
|
||||
Subject: [PATCH] Add decompressed payload size limit to Python
|
||||
THeaderTransport Client: py
|
||||
|
||||
Adds _max_decompressed_size (default: HARD_MAX_FRAME_SIZE) and
|
||||
set_max_decompressed_size() to THeaderTransport. ZLIB decompression
|
||||
now uses decompressobj with a size cap instead of unbounded
|
||||
zlib.decompress(), consistent with the C++ implementation.
|
||||
|
||||
Refactor THeaderTransport read-transform dispatch into _apply_read_transform
|
||||
|
||||
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||||
|
||||
CVE: CVE-2026-41608
|
||||
Upstream-Status: Backport [https://github.com/apache/thrift/commit/8c605880ea4ef050a689c859de3b702ad8d4ce7b]
|
||||
|
||||
Signed-off-by: Adarsh Jagadish Kamini <adarsh.jagadish.kamini@est.tech>
|
||||
---
|
||||
lib/py/src/transport/THeaderTransport.py | 26 +++++++---
|
||||
lib/py/test/thrift_transport.py | 65 ++++++++++++++++++++++++
|
||||
2 files changed, 85 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/lib/py/src/transport/THeaderTransport.py b/lib/py/src/transport/THeaderTransport.py
|
||||
index 4fb203430..51bc43cf7 100644
|
||||
--- a/lib/py/src/transport/THeaderTransport.py
|
||||
+++ b/lib/py/src/transport/THeaderTransport.py
|
||||
@@ -60,9 +60,7 @@ class THeaderTransformID(object):
|
||||
ZLIB = 0x01
|
||||
|
||||
|
||||
-READ_TRANSFORMS_BY_ID = {
|
||||
- THeaderTransformID.ZLIB: zlib.decompress,
|
||||
-}
|
||||
+KNOWN_READ_TRANSFORM_IDS = frozenset({THeaderTransformID.ZLIB})
|
||||
|
||||
|
||||
WRITE_TRANSFORMS_BY_ID = {
|
||||
@@ -102,6 +100,7 @@ class THeaderTransport(TTransportBase, CReadableTransport):
|
||||
self.sequence_id = 0
|
||||
self._protocol_id = default_protocol
|
||||
self._max_frame_size = HARD_MAX_FRAME_SIZE
|
||||
+ self._max_decompressed_size = HARD_MAX_FRAME_SIZE
|
||||
|
||||
def isOpen(self):
|
||||
return self._transport.isOpen()
|
||||
@@ -135,6 +134,22 @@ class THeaderTransport(TTransportBase, CReadableTransport):
|
||||
raise ValueError("maximum frame size should be < %d and > 0" % HARD_MAX_FRAME_SIZE)
|
||||
self._max_frame_size = size
|
||||
|
||||
+ def set_max_decompressed_size(self, size):
|
||||
+ if not 0 < size <= HARD_MAX_FRAME_SIZE:
|
||||
+ raise ValueError("maximum decompressed size should be <= %d and > 0" % HARD_MAX_FRAME_SIZE)
|
||||
+ self._max_decompressed_size = size
|
||||
+
|
||||
+ def _apply_read_transform(self, transform_id, payload):
|
||||
+ if transform_id == THeaderTransformID.ZLIB:
|
||||
+ decompressor = zlib.decompressobj()
|
||||
+ payload = decompressor.decompress(payload, self._max_decompressed_size)
|
||||
+ if decompressor.unconsumed_tail:
|
||||
+ raise TTransportException(
|
||||
+ TTransportException.SIZE_LIMIT,
|
||||
+ "Decompressed payload exceeds maximum allowed size.",
|
||||
+ )
|
||||
+ return payload
|
||||
+
|
||||
@property
|
||||
def protocol_id(self):
|
||||
if self._client_type == THeaderClientType.HEADERS:
|
||||
@@ -250,7 +265,7 @@ class THeaderTransport(TTransportBase, CReadableTransport):
|
||||
transform_count = readVarint(buffer_transport)
|
||||
for _ in range(transform_count):
|
||||
transform_id = readVarint(buffer_transport)
|
||||
- if transform_id not in READ_TRANSFORMS_BY_ID:
|
||||
+ if transform_id not in KNOWN_READ_TRANSFORM_IDS:
|
||||
raise TApplicationException(
|
||||
TApplicationException.INVALID_TRANSFORM,
|
||||
"Unknown transform: %d" % transform_id,
|
||||
@@ -276,8 +291,7 @@ class THeaderTransport(TTransportBase, CReadableTransport):
|
||||
|
||||
payload = buffer.read()
|
||||
for transform_id in transforms:
|
||||
- transform_fn = READ_TRANSFORMS_BY_ID[transform_id]
|
||||
- payload = transform_fn(payload)
|
||||
+ payload = self._apply_read_transform(transform_id, payload)
|
||||
return BytesIO(payload)
|
||||
|
||||
def write(self, buf):
|
||||
diff --git a/lib/py/test/thrift_transport.py b/lib/py/test/thrift_transport.py
|
||||
index cb1bb0ce7..cb0d815cb 100644
|
||||
--- a/lib/py/test/thrift_transport.py
|
||||
+++ b/lib/py/test/thrift_transport.py
|
||||
@@ -17,11 +17,17 @@
|
||||
# under the License.
|
||||
#
|
||||
|
||||
+import struct
|
||||
import unittest
|
||||
+import zlib
|
||||
import os
|
||||
|
||||
import _import_local_thrift # noqa
|
||||
from thrift.transport import TTransport
|
||||
+from thrift.transport.THeaderTransport import (
|
||||
+ THeaderTransport, THeaderClientType, THeaderTransformID, HEADER_MAGIC,
|
||||
+)
|
||||
+from thrift.transport.TTransport import TTransportException
|
||||
|
||||
|
||||
class TestTFileObjectTransport(unittest.TestCase):
|
||||
@@ -66,5 +72,64 @@ class TestMemoryBuffer(unittest.TestCase):
|
||||
buffer_r.close()
|
||||
|
||||
|
||||
+def _craft_zlib_header_frame(plain_payload):
|
||||
+ """Return a complete THeaderTransport frame with a ZLIB-compressed payload."""
|
||||
+ compressed = zlib.compress(plain_payload)
|
||||
+ # proto_id=0, 1 transform; values <128 encode as single varint bytes
|
||||
+ hdr_content = bytes([0, 1, THeaderTransformID.ZLIB])
|
||||
+ padding = (4 - len(hdr_content) % 4) % 4
|
||||
+ hdr_bytes = hdr_content + b'\x00' * padding
|
||||
+ header_len_u16 = len(hdr_bytes) // 4
|
||||
+ # 10 fixed bytes: 2(magic)+2(flags)+4(seqid)+2(hdr_len_u16)
|
||||
+ frame_size = 10 + len(hdr_bytes) + len(compressed)
|
||||
+ frame = struct.pack('!i', frame_size)
|
||||
+ frame += struct.pack('!H', HEADER_MAGIC)
|
||||
+ frame += struct.pack('!H', 0) # flags
|
||||
+ frame += struct.pack('!i', 0) # sequence_id
|
||||
+ frame += struct.pack('!H', header_len_u16)
|
||||
+ frame += hdr_bytes
|
||||
+ frame += compressed
|
||||
+ return frame
|
||||
+
|
||||
+
|
||||
+class TestTHeaderTransportDecompressionLimit(unittest.TestCase):
|
||||
+
|
||||
+ def _make_transport(self, payload, max_decompressed_size=None):
|
||||
+ frame = _craft_zlib_header_frame(payload)
|
||||
+ inner = TTransport.TMemoryBuffer(frame)
|
||||
+ transport = THeaderTransport(inner, [THeaderClientType.HEADERS])
|
||||
+ if max_decompressed_size is not None:
|
||||
+ transport.set_max_decompressed_size(max_decompressed_size)
|
||||
+ return transport
|
||||
+
|
||||
+ def test_zlib_roundtrip(self):
|
||||
+ payload = b'Hello Thrift! ' * 100
|
||||
+ t = self._make_transport(payload)
|
||||
+ t.readFrame(1)
|
||||
+ self.assertEqual(t._read_buffer.read(), payload)
|
||||
+
|
||||
+ def test_decompression_bomb_blocked(self):
|
||||
+ # 10,000 bytes decompressed; limit set to 1,000
|
||||
+ payload = b'A' * 10000
|
||||
+ t = self._make_transport(payload, max_decompressed_size=1000)
|
||||
+ with self.assertRaises(TTransportException) as ctx:
|
||||
+ t.readFrame(1)
|
||||
+ self.assertEqual(ctx.exception.type, TTransportException.SIZE_LIMIT)
|
||||
+
|
||||
+ def test_payload_at_limit_passes(self):
|
||||
+ payload = b'B' * 5000
|
||||
+ t = self._make_transport(payload, max_decompressed_size=5000)
|
||||
+ t.readFrame(1)
|
||||
+ self.assertEqual(t._read_buffer.read(), payload)
|
||||
+
|
||||
+ def test_set_max_decompressed_size_rejects_invalid(self):
|
||||
+ inner = TTransport.TMemoryBuffer(b'')
|
||||
+ t = THeaderTransport(inner, [THeaderClientType.HEADERS])
|
||||
+ with self.assertRaises(ValueError):
|
||||
+ t.set_max_decompressed_size(0)
|
||||
+ with self.assertRaises(ValueError):
|
||||
+ t.set_max_decompressed_size(-1)
|
||||
+
|
||||
+
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -18,6 +18,7 @@ SRC_URI = "https://downloads.apache.org/${BPN}/${PV}/${BP}.tar.gz \
|
||||
file://CVE-2026-48144.patch \
|
||||
file://CVE-2026-58389.patch \
|
||||
file://CVE-2026-58662.patch \
|
||||
file://CVE-2026-41608.patch \
|
||||
"
|
||||
SRC_URI[sha256sum] = "794a0e455787960d9f27ab92c38e34da27e8deeda7a5db0e59dc64a00df8a1e5"
|
||||
|
||||
|
||||
Reference in new issue
Block a user