Re: [oe] [meta-oe,scathgap][PATCH] thrift: fix multiple CVEs
Anuj Mittal <[email protected]>
| Newsgroups | org.openembedded.lists.openembedded-devel |
|---|---|
| Message-ID | <CA+s=J=wDXh38DZQO=3rmOQCTr1oXaFh5ow5a2TSPtcUeg5x5UA@mail.gmail.com> |
There's a typo in subject and says scathgap, so my filters had missed this. I've taken this now for testing but in such cases, please re-send with correct subject in future. Thanks, Anuj On Tue, Jul 7, 2026 at 9:09 PM Roland Kovács <[email protected]> wrote: > > CVE-2026-43868: > Memory Allocation with Excessive Size Value vulnerability in Apache Thrift. > > CVE-2026-43869: > Improper Validation of Certificate with Host Mismatch vulnerability in Apache Thrift. > This CVE only affects the Java client, which is not built by the recipe. Marked as > 'not-applicable-config'. > Upstream commit: > https://github.com/apache/thrift/commit/a30c552bd0808b7e19f35ad30212ba7a9aee8c66 > > CVE-2026-43870: > Origin Validation Error, Improper Limitation of a Pathname to a Restricted Directory > ('Path Traversal'), Improper Neutralization of CRLF Sequences in HTTP Headers > ('HTTP Request/Response Splitting'), Uncontrolled Resource Consumption vulnerability > in Apache Thrift. > > Signed-off-by: Roland Kovacs <[email protected]> > --- > .../thrift/thrift/CVE-2026-43868.patch | 1381 +++++++++++++++++ > .../thrift/thrift/CVE-2026-43870.patch | 127 ++ > .../thrift/thrift_0.20.0.bb | 4 + > 3 files changed, 1512 insertions(+) > create mode 100644 meta-oe/recipes-connectivity/thrift/thrift/CVE-2026-43868.patch > create mode 100644 meta-oe/recipes-connectivity/thrift/thrift/CVE-2026-43870.patch > > diff --git a/meta-oe/recipes-connectivity/thrift/thrift/CVE-2026-43868.patch b/meta-oe/recipes-connectivity/thrift/thrift/CVE-2026-43868.patch > new file mode 100644 > index 0000000000..a4f4aec09e > --- /dev/null > +++ b/meta-oe/recipes-connectivity/thrift/thrift/CVE-2026-43868.patch > @@ -0,0 +1,1381 @@ > +From ebb9e1ec3d7dea6ade9e98951b7f1dd58ba933a5 Mon Sep 17 00:00:00 2001 > +From: Hasnain Lakhani <[email protected]> > +Date: Thu, 17 Jul 2025 19:57:05 -0700 > +Subject: [PATCH] [THRIFT-5871] Add message / container size checking for Rust > + > +Bring the Rust implementation somewhat up to par with the other implementations. > +I tried 4-5 different ways to get the "perfect" check but since trait specialization is not yet stable, > +I was not able to arrive at a solution I'm happy with (code was either ugly, or had runtime overhead). > + > +So for now, we avoid full message size tracking / more precise limit checking, but this is a strong step > +in the right direction. > + > +CVE: CVE-2026-43868 > +Upstream-Status: Backport [https://github.com/apache/thrift/commit/42d0b717746a748a50a19e7e7842373488048dc3] > + > +Signed-off-by: Roland Kovacs <[email protected]> > +--- > + lib/rs/src/configuration.rs | 178 ++++++++++++++++ > + lib/rs/src/lib.rs | 12 +- > + lib/rs/src/protocol/binary.rs | 285 ++++++++++++++++++++++++- > + lib/rs/src/protocol/compact.rs | 373 ++++++++++++++++++++++++++++++++- > + lib/rs/src/protocol/mod.rs | 80 ++++++- > + lib/rs/src/transport/framed.rs | 27 ++- > + 6 files changed, 925 insertions(+), 30 deletions(-) > + create mode 100644 lib/rs/src/configuration.rs > + > +diff --git a/lib/rs/src/configuration.rs b/lib/rs/src/configuration.rs > +new file mode 100644 > +index 000000000..0f786f4ee > +--- /dev/null > ++++ b/lib/rs/src/configuration.rs > +@@ -0,0 +1,178 @@ > ++// 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. > ++ > ++/// Configuration for Thrift protocols. > ++#[derive(Debug, Clone)] > ++pub struct TConfiguration { > ++ max_message_size: Option<usize>, > ++ max_frame_size: Option<usize>, > ++ max_recursion_depth: Option<usize>, > ++ max_container_size: Option<usize>, > ++ max_string_size: Option<usize>, > ++} > ++ > ++impl TConfiguration { > ++ // this value is used consistently across all Thrift libraries > ++ pub const DEFAULT_MAX_MESSAGE_SIZE: usize = 100 * 1024 * 1024; > ++ > ++ // this value is used consistently across all Thrift libraries > ++ pub const DEFAULT_MAX_FRAME_SIZE: usize = 16_384_000; > ++ > ++ pub const DEFAULT_RECURSION_LIMIT: usize = 64; > ++ > ++ pub const DEFAULT_CONTAINER_LIMIT: Option<usize> = None; > ++ > ++ pub const DEFAULT_STRING_LIMIT: usize = 100 * 1024 * 1024; > ++ > ++ pub fn no_limits() -> Self { > ++ Self { > ++ max_message_size: None, > ++ max_frame_size: None, > ++ max_recursion_depth: None, > ++ max_container_size: None, > ++ max_string_size: None, > ++ } > ++ } > ++ > ++ pub fn max_message_size(&self) -> Option<usize> { > ++ self.max_message_size > ++ } > ++ > ++ pub fn max_frame_size(&self) -> Option<usize> { > ++ self.max_frame_size > ++ } > ++ > ++ pub fn max_recursion_depth(&self) -> Option<usize> { > ++ self.max_recursion_depth > ++ } > ++ > ++ pub fn max_container_size(&self) -> Option<usize> { > ++ self.max_container_size > ++ } > ++ > ++ pub fn max_string_size(&self) -> Option<usize> { > ++ self.max_string_size > ++ } > ++ > ++ pub fn builder() -> TConfigurationBuilder { > ++ TConfigurationBuilder::default() > ++ } > ++} > ++ > ++impl Default for TConfiguration { > ++ fn default() -> Self { > ++ Self { > ++ max_message_size: Some(Self::DEFAULT_MAX_MESSAGE_SIZE), > ++ max_frame_size: Some(Self::DEFAULT_MAX_FRAME_SIZE), > ++ max_recursion_depth: Some(Self::DEFAULT_RECURSION_LIMIT), > ++ max_container_size: Self::DEFAULT_CONTAINER_LIMIT, > ++ max_string_size: Some(Self::DEFAULT_STRING_LIMIT), > ++ } > ++ } > ++} > ++ > ++#[derive(Debug, Default)] > ++pub struct TConfigurationBuilder { > ++ config: TConfiguration, > ++} > ++ > ++impl TConfigurationBuilder { > ++ pub fn max_message_size(mut self, limit: Option<usize>) -> Self { > ++ self.config.max_message_size = limit; > ++ self > ++ } > ++ > ++ pub fn max_frame_size(mut self, limit: Option<usize>) -> Self { > ++ self.config.max_frame_size = limit; > ++ self > ++ } > ++ > ++ pub fn max_recursion_depth(mut self, limit: Option<usize>) -> Self { > ++ self.config.max_recursion_depth = limit; > ++ self > ++ } > ++ > ++ pub fn max_container_size(mut self, limit: Option<usize>) -> Self { > ++ self.config.max_container_size = limit; > ++ self > ++ } > ++ > ++ pub fn max_string_size(mut self, limit: Option<usize>) -> Self { > ++ self.config.max_string_size = limit; > ++ self > ++ } > ++ > ++ pub fn build(self) -> crate::Result<TConfiguration> { > ++ if let (Some(frame_size), Some(message_size)) = > ++ (self.config.max_frame_size, self.config.max_message_size) > ++ { > ++ if frame_size > message_size { > ++ // FIXME: This should probably be a different error type. > ++ return Err(crate::Error::Application(crate::ApplicationError::new( > ++ crate::ApplicationErrorKind::Unknown, > ++ format!( > ++ "Invalid configuration: max_frame_size ({}) cannot exceed max_message_size ({})", > ++ frame_size, message_size > ++ ), > ++ ))); > ++ } > ++ } > ++ > ++ Ok(self.config) > ++ } > ++} > ++ > ++#[cfg(test)] > ++mod tests { > ++ use super::*; > ++ > ++ #[test] > ++ fn test_custom_configuration_builder() { > ++ let config = TConfiguration::builder() > ++ .max_message_size(Some(1024)) > ++ .max_frame_size(Some(512)) > ++ .max_recursion_depth(Some(10)) > ++ .max_container_size(Some(100)) > ++ .max_string_size(Some(256)) > ++ .build() > ++ .unwrap(); > ++ > ++ assert_eq!(config.max_message_size(), Some(1024)); > ++ assert_eq!(config.max_frame_size(), Some(512)); > ++ assert_eq!(config.max_recursion_depth(), Some(10)); > ++ assert_eq!(config.max_container_size(), Some(100)); > ++ assert_eq!(config.max_string_size(), Some(256)); > ++ } > ++ > ++ #[test] > ++ fn test_invalid_configuration() { > ++ // Test that builder catches invalid configurations > ++ let result = TConfiguration::builder() > ++ .max_frame_size(Some(1000)) > ++ .max_message_size(Some(500)) // frame size > message size is invalid > ++ .build(); > ++ > ++ assert!(result.is_err()); > ++ match result { > ++ Err(crate::Error::Application(e)) => { > ++ assert!(e.message.contains("max_frame_size")); > ++ assert!(e.message.contains("cannot exceed max_message_size")); > ++ } > ++ _ => panic!("Expected Application error"), > ++ } > ++ } > ++} > +diff --git a/lib/rs/src/lib.rs b/lib/rs/src/lib.rs > +index 84c1f9b71..1b80d7cfe 100644 > +--- a/lib/rs/src/lib.rs > ++++ b/lib/rs/src/lib.rs > +@@ -21,10 +21,11 @@ > + //! Thrift server and client. It is divided into the following modules: > + //! > + //! 1. errors > +-//! 2. protocol > +-//! 3. transport > +-//! 4. server > +-//! 5. autogen > ++//! 2. configuration > ++//! 3. protocol > ++//! 4. transport > ++//! 5. server > ++//! 6. autogen > + //! > + //! The modules are layered as shown in the diagram below. The `autogen'd` > + //! layer is generated by the Thrift compiler's Rust plugin. It uses the > +@@ -82,6 +83,9 @@ pub use crate::errors::*; > + mod autogen; > + pub use crate::autogen::*; > + > ++mod configuration; > ++pub use crate::configuration::*; > ++ > + /// Result type returned by all runtime library functions. > + /// > + /// As is convention this is a typedef of `std::result::Result` > +diff --git a/lib/rs/src/protocol/binary.rs b/lib/rs/src/protocol/binary.rs > +index b4b51f682..596285fb9 100644 > +--- a/lib/rs/src/protocol/binary.rs > ++++ b/lib/rs/src/protocol/binary.rs > +@@ -24,7 +24,7 @@ use super::{ > + }; > + use super::{TOutputProtocol, TOutputProtocolFactory, TSetIdentifier, TStructIdentifier, TType}; > + use crate::transport::{TReadTransport, TWriteTransport}; > +-use crate::{ProtocolError, ProtocolErrorKind}; > ++use crate::{ProtocolError, ProtocolErrorKind, TConfiguration}; > + > + const BINARY_PROTOCOL_VERSION_1: u32 = 0x8001_0000; > + > +@@ -57,6 +57,8 @@ where > + { > + strict: bool, > + pub transport: T, // FIXME: shouldn't be public > ++ config: TConfiguration, > ++ recursion_depth: usize, > + } > + > + impl<T> TBinaryInputProtocol<T> > +@@ -67,8 +69,29 @@ where > + /// > + /// Set `strict` to `true` if all incoming messages contain the protocol > + /// version number in the protocol header. > +- pub fn new(transport: T, strict: bool) -> TBinaryInputProtocol<T> { > +- TBinaryInputProtocol { strict, transport } > ++ pub fn new(transport: T, strict: bool) -> Self { > ++ Self::with_config(transport, strict, TConfiguration::default()) > ++ } > ++ > ++ pub fn with_config(transport: T, strict: bool, config: TConfiguration) -> Self { > ++ TBinaryInputProtocol { > ++ strict, > ++ transport, > ++ config, > ++ recursion_depth: 0, > ++ } > ++ } > ++ > ++ fn check_recursion_depth(&self) -> crate::Result<()> { > ++ if let Some(limit) = self.config.max_recursion_depth() { > ++ if self.recursion_depth >= limit { > ++ return Err(crate::Error::Protocol(ProtocolError::new( > ++ ProtocolErrorKind::DepthLimit, > ++ format!("Maximum recursion depth {} exceeded", limit), > ++ ))); > ++ } > ++ } > ++ Ok(()) > + } > + } > + > +@@ -78,6 +101,7 @@ where > + { > + #[allow(clippy::collapsible_if)] > + fn read_message_begin(&mut self) -> crate::Result<TMessageIdentifier> { > ++ // TODO: Once specialization is stable, call the message size tracking here > + let mut first_bytes = vec![0; 4]; > + self.transport.read_exact(&mut first_bytes[..])?; > + > +@@ -130,10 +154,13 @@ where > + } > + > + fn read_struct_begin(&mut self) -> crate::Result<Option<TStructIdentifier>> { > ++ self.check_recursion_depth()?; > ++ self.recursion_depth += 1; > + Ok(None) > + } > + > + fn read_struct_end(&mut self) -> crate::Result<()> { > ++ self.recursion_depth -= 1; > + Ok(()) > + } > + > +@@ -154,8 +181,28 @@ where > + } > + > + fn read_bytes(&mut self) -> crate::Result<Vec<u8>> { > +- let num_bytes = self.transport.read_i32::<BigEndian>()? as usize; > +- let mut buf = vec![0u8; num_bytes]; > ++ let num_bytes = self.transport.read_i32::<BigEndian>()?; > ++ > ++ if num_bytes < 0 { > ++ return Err(crate::Error::Protocol(ProtocolError::new( > ++ ProtocolErrorKind::NegativeSize, > ++ format!("Negative byte array size: {}", num_bytes), > ++ ))); > ++ } > ++ > ++ if let Some(max_size) = self.config.max_string_size() { > ++ if num_bytes as usize > max_size { > ++ return Err(crate::Error::Protocol(ProtocolError::new( > ++ ProtocolErrorKind::SizeLimit, > ++ format!( > ++ "Byte array size {} exceeds maximum allowed size of {}", > ++ num_bytes, max_size > ++ ), > ++ ))); > ++ } > ++ } > ++ > ++ let mut buf = vec![0u8; num_bytes as usize]; > + self.transport > + .read_exact(&mut buf) > + .map(|_| buf) > +@@ -206,6 +253,8 @@ where > + fn read_list_begin(&mut self) -> crate::Result<TListIdentifier> { > + let element_type: TType = self.read_byte().and_then(field_type_from_u8)?; > + let size = self.read_i32()?; > ++ let min_element_size = self.min_serialized_size(element_type); > ++ super::check_container_size(&self.config, size, min_element_size)?; > + Ok(TListIdentifier::new(element_type, size)) > + } > + > +@@ -216,6 +265,8 @@ where > + fn read_set_begin(&mut self) -> crate::Result<TSetIdentifier> { > + let element_type: TType = self.read_byte().and_then(field_type_from_u8)?; > + let size = self.read_i32()?; > ++ let min_element_size = self.min_serialized_size(element_type); > ++ super::check_container_size(&self.config, size, min_element_size)?; > + Ok(TSetIdentifier::new(element_type, size)) > + } > + > +@@ -227,6 +278,12 @@ where > + let key_type: TType = self.read_byte().and_then(field_type_from_u8)?; > + let value_type: TType = self.read_byte().and_then(field_type_from_u8)?; > + let size = self.read_i32()?; > ++ > ++ let key_min_size = self.min_serialized_size(key_type); > ++ let value_min_size = self.min_serialized_size(value_type); > ++ let element_size = key_min_size + value_min_size; > ++ super::check_container_size(&self.config, size, element_size)?; > ++ > + Ok(TMapIdentifier::new(key_type, value_type, size)) > + } > + > +@@ -240,6 +297,26 @@ where > + fn read_byte(&mut self) -> crate::Result<u8> { > + self.transport.read_u8().map_err(From::from) > + } > ++ > ++ fn min_serialized_size(&self, field_type: TType) -> usize { > ++ match field_type { > ++ TType::Stop => 1, // 1 byte minimum > ++ TType::Void => 1, // 1 byte minimum > ++ TType::Bool => 1, // 1 byte > ++ TType::I08 => 1, // 1 byte > ++ TType::Double => 8, // 8 bytes > ++ TType::I16 => 2, // 2 bytes > ++ TType::I32 => 4, // 4 bytes > ++ TType::I64 => 8, // 8 bytes > ++ TType::String => 4, // 4 bytes for length prefix > ++ TType::Struct => 1, // 1 byte minimum (stop field) > ++ TType::Map => 4, // 4 bytes size > ++ TType::Set => 4, // 4 bytes size > ++ TType::List => 4, // 4 bytes size > ++ TType::Uuid => 16, // 16 bytes > ++ TType::Utf7 => 1, // 1 byte > ++ } > ++ } > + } > + > + /// Factory for creating instances of `TBinaryInputProtocol`. > +@@ -514,14 +591,13 @@ fn field_type_from_u8(b: u8) -> crate::Result<TType> { > + #[cfg(test)] > + mod tests { > + > ++ use super::*; > + use crate::protocol::{ > + TFieldIdentifier, TInputProtocol, TListIdentifier, TMapIdentifier, TMessageIdentifier, > + TMessageType, TOutputProtocol, TSetIdentifier, TStructIdentifier, TType, > + }; > + use crate::transport::{ReadHalf, TBufferChannel, TIoChannel, WriteHalf}; > + > +- use super::*; > +- > + #[test] > + fn must_write_strict_message_call_begin() { > + let (_, mut o_prot) = test_objects(true); > +@@ -759,13 +835,26 @@ mod tests { > + fn must_round_trip_list_begin() { > + let (mut i_prot, mut o_prot) = test_objects(true); > + > +- let ident = TListIdentifier::new(TType::List, 900); > ++ let ident = TListIdentifier::new(TType::I32, 4); > + assert!(o_prot.write_list_begin(&ident).is_ok()); > ++ assert!(o_prot.write_i32(10).is_ok()); > ++ assert!(o_prot.write_i32(20).is_ok()); > ++ assert!(o_prot.write_i32(30).is_ok()); > ++ assert!(o_prot.write_i32(40).is_ok()); > ++ > ++ assert!(o_prot.write_list_end().is_ok()); > + > + copy_write_buffer_to_read_buffer!(o_prot); > + > + let received_ident = assert_success!(i_prot.read_list_begin()); > + assert_eq!(&received_ident, &ident); > ++ > ++ assert_eq!(i_prot.read_i32().unwrap(), 10); > ++ assert_eq!(i_prot.read_i32().unwrap(), 20); > ++ assert_eq!(i_prot.read_i32().unwrap(), 30); > ++ assert_eq!(i_prot.read_i32().unwrap(), 40); > ++ > ++ assert!(i_prot.read_list_end().is_ok()); > + } > + > + #[test] > +@@ -789,14 +878,25 @@ mod tests { > + fn must_round_trip_set_begin() { > + let (mut i_prot, mut o_prot) = test_objects(true); > + > +- let ident = TSetIdentifier::new(TType::I64, 2000); > ++ let ident = TSetIdentifier::new(TType::I64, 3); > + assert!(o_prot.write_set_begin(&ident).is_ok()); > ++ assert!(o_prot.write_i64(123).is_ok()); > ++ assert!(o_prot.write_i64(456).is_ok()); > ++ assert!(o_prot.write_i64(789).is_ok()); > ++ > ++ assert!(o_prot.write_set_end().is_ok()); > + > + copy_write_buffer_to_read_buffer!(o_prot); > + > + let received_ident_result = i_prot.read_set_begin(); > + assert!(received_ident_result.is_ok()); > + assert_eq!(&received_ident_result.unwrap(), &ident); > ++ > ++ assert_eq!(i_prot.read_i64().unwrap(), 123); > ++ assert_eq!(i_prot.read_i64().unwrap(), 456); > ++ assert_eq!(i_prot.read_i64().unwrap(), 789); > ++ > ++ assert!(i_prot.read_set_end().is_ok()); > + } > + > + #[test] > +@@ -820,13 +920,26 @@ mod tests { > + fn must_round_trip_map_begin() { > + let (mut i_prot, mut o_prot) = test_objects(true); > + > +- let ident = TMapIdentifier::new(TType::Map, TType::Set, 100); > ++ let ident = TMapIdentifier::new(TType::String, TType::I32, 2); > + assert!(o_prot.write_map_begin(&ident).is_ok()); > ++ assert!(o_prot.write_string("key1").is_ok()); > ++ assert!(o_prot.write_i32(100).is_ok()); > ++ assert!(o_prot.write_string("key2").is_ok()); > ++ assert!(o_prot.write_i32(200).is_ok()); > ++ > ++ assert!(o_prot.write_map_end().is_ok()); > + > + copy_write_buffer_to_read_buffer!(o_prot); > + > + let received_ident = assert_success!(i_prot.read_map_begin()); > + assert_eq!(&received_ident, &ident); > ++ > ++ assert_eq!(i_prot.read_string().unwrap(), "key1"); > ++ assert_eq!(i_prot.read_i32().unwrap(), 100); > ++ assert_eq!(i_prot.read_string().unwrap(), "key2"); > ++ assert_eq!(i_prot.read_i32().unwrap(), 200); > ++ > ++ assert!(i_prot.read_map_end().is_ok()); > + } > + > + #[test] > +@@ -963,7 +1076,7 @@ mod tests { > + TBinaryInputProtocol<ReadHalf<TBufferChannel>>, > + TBinaryOutputProtocol<WriteHalf<TBufferChannel>>, > + ) { > +- let mem = TBufferChannel::with_capacity(40, 40); > ++ let mem = TBufferChannel::with_capacity(200, 200); > + > + let (r_mem, w_mem) = mem.split().unwrap(); > + > +@@ -981,4 +1094,154 @@ mod tests { > + assert!(write_fn(&mut o_prot).is_ok()); > + assert_eq!(o_prot.transport.write_bytes().len(), 0); > + } > ++ > ++ #[test] > ++ fn must_enforce_recursion_depth_limit() { > ++ let mem = TBufferChannel::with_capacity(40, 40); > ++ let (r_mem, _) = mem.split().unwrap(); > ++ > ++ let config = TConfiguration::builder() > ++ .max_recursion_depth(Some(2)) > ++ .build() > ++ .unwrap(); > ++ let mut i_prot = TBinaryInputProtocol::with_config(r_mem, true, config); > ++ > ++ assert!(i_prot.read_struct_begin().is_ok()); > ++ assert_eq!(i_prot.recursion_depth, 1); > ++ > ++ assert!(i_prot.read_struct_begin().is_ok()); > ++ assert_eq!(i_prot.recursion_depth, 2); > ++ > ++ let result = i_prot.read_struct_begin(); > ++ assert!(result.is_err()); > ++ match result { > ++ Err(crate::Error::Protocol(e)) => { > ++ assert_eq!(e.kind, ProtocolErrorKind::DepthLimit); > ++ } > ++ _ => panic!("Expected protocol error with DepthLimit"), > ++ } > ++ > ++ assert!(i_prot.read_struct_end().is_ok()); > ++ assert_eq!(i_prot.recursion_depth, 1); > ++ assert!(i_prot.read_struct_end().is_ok()); > ++ assert_eq!(i_prot.recursion_depth, 0); > ++ } > ++ > ++ #[test] > ++ fn must_reject_negative_container_sizes() { > ++ let mem = TBufferChannel::with_capacity(40, 40); > ++ let (r_mem, mut w_mem) = mem.split().unwrap(); > ++ > ++ let mut i_prot = TBinaryInputProtocol::new(r_mem, true); > ++ > ++ w_mem.set_readable_bytes(&[0x0F, 0xFF, 0xFF, 0xFF, 0xFF]); > ++ > ++ let result = i_prot.read_list_begin(); > ++ assert!(result.is_err()); > ++ match result { > ++ Err(crate::Error::Protocol(e)) => { > ++ assert_eq!(e.kind, ProtocolErrorKind::NegativeSize); > ++ } > ++ _ => panic!("Expected protocol error with NegativeSize"), > ++ } > ++ } > ++ > ++ #[test] > ++ fn must_enforce_container_size_limit() { > ++ let mem = TBufferChannel::with_capacity(40, 40); > ++ let (r_mem, mut w_mem) = mem.split().unwrap(); > ++ > ++ let config = TConfiguration::builder() > ++ .max_container_size(Some(100)) > ++ .build() > ++ .unwrap(); > ++ > ++ let mut i_prot = TBinaryInputProtocol::with_config(r_mem, true, config); > ++ > ++ w_mem.set_readable_bytes(&[0x0F, 0x00, 0x00, 0x00, 0xC8]); > ++ > ++ let result = i_prot.read_list_begin(); > ++ assert!(result.is_err()); > ++ match result { > ++ Err(crate::Error::Protocol(e)) => { > ++ assert_eq!(e.kind, ProtocolErrorKind::SizeLimit); > ++ assert!(e > ++ .message > ++ .contains("Container size 200 exceeds maximum allowed size of 100")); > ++ } > ++ _ => panic!("Expected protocol error with SizeLimit"), > ++ } > ++ } > ++ > ++ #[test] > ++ fn must_allow_containers_within_limit() { > ++ let mem = TBufferChannel::with_capacity(200, 200); > ++ let (r_mem, mut w_mem) = mem.split().unwrap(); > ++ > ++ // Create protocol with container limit of 100 > ++ let config = TConfiguration::builder() > ++ .max_container_size(Some(100)) > ++ .build() > ++ .unwrap(); > ++ let mut i_prot = TBinaryInputProtocol::with_config(r_mem, true, config); > ++ > ++ let mut data = vec![0x08]; // TType::I32 > ++ data.extend_from_slice(&5i32.to_be_bytes()); // size = 5 > ++ > ++ for i in 1i32..=5i32 { > ++ data.extend_from_slice(&(i * 10).to_be_bytes()); > ++ } > ++ > ++ w_mem.set_readable_bytes(&data); > ++ > ++ let result = i_prot.read_list_begin(); > ++ assert!(result.is_ok()); > ++ let list_ident = result.unwrap(); > ++ assert_eq!(list_ident.size, 5); > ++ assert_eq!(list_ident.element_type, TType::I32); > ++ } > ++ > ++ #[test] > ++ fn must_enforce_string_size_limit() { > ++ let mem = TBufferChannel::with_capacity(100, 100); > ++ let (r_mem, mut w_mem) = mem.split().unwrap(); > ++ > ++ let config = TConfiguration::builder() > ++ .max_string_size(Some(1000)) > ++ .build() > ++ .unwrap(); > ++ let mut i_prot = TBinaryInputProtocol::with_config(r_mem, true, config); > ++ > ++ w_mem.set_readable_bytes(&[0x00, 0x00, 0x07, 0xD0]); > ++ > ++ let result = i_prot.read_string(); > ++ assert!(result.is_err()); > ++ match result { > ++ Err(crate::Error::Protocol(e)) => { > ++ assert_eq!(e.kind, ProtocolErrorKind::SizeLimit); > ++ assert!(e > ++ .message > ++ .contains("Byte array size 2000 exceeds maximum allowed size of 1000")); > ++ } > ++ _ => panic!("Expected protocol error with SizeLimit"), > ++ } > ++ } > ++ > ++ #[test] > ++ fn must_allow_strings_within_limit() { > ++ let mem = TBufferChannel::with_capacity(100, 100); > ++ let (r_mem, mut w_mem) = mem.split().unwrap(); > ++ > ++ let config = TConfiguration::builder() > ++ .max_string_size(Some(1000)) > ++ .build() > ++ .unwrap(); > ++ let mut i_prot = TBinaryInputProtocol::with_config(r_mem, true, config); > ++ > ++ w_mem.set_readable_bytes(&[0x00, 0x00, 0x00, 0x05, b'h', b'e', b'l', b'l', b'o']); > ++ > ++ let result = i_prot.read_string(); > ++ assert!(result.is_ok()); > ++ assert_eq!(result.unwrap(), "hello"); > ++ } > + } > +diff --git a/lib/rs/src/protocol/compact.rs b/lib/rs/src/protocol/compact.rs > +index c0c43722e..a72243e30 100644 > +--- a/lib/rs/src/protocol/compact.rs > ++++ b/lib/rs/src/protocol/compact.rs > +@@ -26,6 +26,7 @@ use super::{ > + }; > + use super::{TOutputProtocol, TOutputProtocolFactory, TSetIdentifier, TStructIdentifier, TType}; > + use crate::transport::{TReadTransport, TWriteTransport}; > ++use crate::{ProtocolError, ProtocolErrorKind, TConfiguration}; > + > + const COMPACT_PROTOCOL_ID: u8 = 0x82; > + const COMPACT_VERSION: u8 = 0x01; > +@@ -64,6 +65,10 @@ where > + pending_read_bool_value: Option<bool>, > + // Underlying transport used for byte-level operations. > + transport: T, > ++ // Configuration > ++ config: TConfiguration, > ++ // Current recursion depth > ++ recursion_depth: usize, > + } > + > + impl<T> TCompactInputProtocol<T> > +@@ -72,11 +77,18 @@ where > + { > + /// Create a `TCompactInputProtocol` that reads bytes from `transport`. > + pub fn new(transport: T) -> TCompactInputProtocol<T> { > ++ Self::with_config(transport, TConfiguration::default()) > ++ } > ++ > ++ /// Create a `TCompactInputProtocol` with custom configuration. > ++ pub fn with_config(transport: T, config: TConfiguration) -> TCompactInputProtocol<T> { > + TCompactInputProtocol { > + last_read_field_id: 0, > + read_field_id_stack: Vec::new(), > + pending_read_bool_value: None, > + transport, > ++ config, > ++ recursion_depth: 0, > + } > + } > + > +@@ -92,8 +104,23 @@ where > + self.transport.read_varint::<u32>()? as i32 > + }; > + > ++ let min_element_size = self.min_serialized_size(element_type); > ++ super::check_container_size(&self.config, element_count, min_element_size)?; > ++ > + Ok((element_type, element_count)) > + } > ++ > ++ fn check_recursion_depth(&self) -> crate::Result<()> { > ++ if let Some(limit) = self.config.max_recursion_depth() { > ++ if self.recursion_depth >= limit { > ++ return Err(crate::Error::Protocol(ProtocolError::new( > ++ ProtocolErrorKind::DepthLimit, > ++ format!("Maximum recursion depth {} exceeded", limit), > ++ ))); > ++ } > ++ } > ++ Ok(()) > ++ } > + } > + > + impl<T> TInputProtocol for TCompactInputProtocol<T> > +@@ -101,6 +128,7 @@ where > + T: TReadTransport, > + { > + fn read_message_begin(&mut self) -> crate::Result<TMessageIdentifier> { > ++ // TODO: Once specialization is stable, call the message size tracking here > + let compact_id = self.read_byte()?; > + if compact_id != COMPACT_PROTOCOL_ID { > + Err(crate::Error::Protocol(crate::ProtocolError { > +@@ -145,12 +173,15 @@ where > + } > + > + fn read_struct_begin(&mut self) -> crate::Result<Option<TStructIdentifier>> { > ++ self.check_recursion_depth()?; > ++ self.recursion_depth += 1; > + self.read_field_id_stack.push(self.last_read_field_id); > + self.last_read_field_id = 0; > + Ok(None) > + } > + > + fn read_struct_end(&mut self) -> crate::Result<()> { > ++ self.recursion_depth -= 1; > + self.last_read_field_id = self > + .read_field_id_stack > + .pop() > +@@ -223,6 +254,19 @@ where > + > + fn read_bytes(&mut self) -> crate::Result<Vec<u8>> { > + let len = self.transport.read_varint::<u32>()?; > ++ > ++ if let Some(max_size) = self.config.max_string_size() { > ++ if len as usize > max_size { > ++ return Err(crate::Error::Protocol(ProtocolError::new( > ++ ProtocolErrorKind::SizeLimit, > ++ format!( > ++ "Byte array size {} exceeds maximum allowed size of {}", > ++ len, max_size > ++ ), > ++ ))); > ++ } > ++ } > ++ > + let mut buf = vec![0u8; len as usize]; > + self.transport > + .read_exact(&mut buf) > +@@ -287,6 +331,12 @@ where > + let type_header = self.read_byte()?; > + let key_type = collection_u8_to_type((type_header & 0xF0) >> 4)?; > + let val_type = collection_u8_to_type(type_header & 0x0F)?; > ++ > ++ let key_min_size = self.min_serialized_size(key_type); > ++ let value_min_size = self.min_serialized_size(val_type); > ++ let element_size = key_min_size + value_min_size; > ++ super::check_container_size(&self.config, element_count, element_size)?; > ++ > + Ok(TMapIdentifier::new(key_type, val_type, element_count)) > + } > + } > +@@ -305,6 +355,30 @@ where > + .map_err(From::from) > + .map(|_| buf[0]) > + } > ++ > ++ fn min_serialized_size(&self, field_type: TType) -> usize { > ++ compact_protocol_min_serialized_size(field_type) > ++ } > ++} > ++ > ++pub(crate) fn compact_protocol_min_serialized_size(field_type: TType) -> usize { > ++ match field_type { > ++ TType::Stop => 1, // 1 byte > ++ TType::Void => 1, // 1 byte > ++ TType::Bool => 1, // 1 byte > ++ TType::I08 => 1, // 1 byte > ++ TType::Double => 8, // 8 bytes (not varint encoded) > ++ TType::I16 => 1, // 1 byte minimum (varint) > ++ TType::I32 => 1, // 1 byte minimum (varint) > ++ TType::I64 => 1, // 1 byte minimum (varint) > ++ TType::String => 1, // 1 byte minimum for length (varint) > ++ TType::Struct => 1, // 1 byte minimum (stop field) > ++ TType::Map => 1, // 1 byte minimum > ++ TType::Set => 1, // 1 byte minimum > ++ TType::List => 1, // 1 byte minimum > ++ TType::Uuid => 16, // 16 bytes > ++ TType::Utf7 => 1, // 1 byte > ++ } > + } > + > + impl<T> io::Seek for TCompactInputProtocol<T> > +@@ -2567,14 +2641,25 @@ mod tests { > + fn must_round_trip_small_sized_list_begin() { > + let (mut i_prot, mut o_prot) = test_objects(); > + > +- let ident = TListIdentifier::new(TType::I08, 10); > +- > ++ let ident = TListIdentifier::new(TType::I32, 3); > + assert_success!(o_prot.write_list_begin(&ident)); > + > ++ assert_success!(o_prot.write_i32(100)); > ++ assert_success!(o_prot.write_i32(200)); > ++ assert_success!(o_prot.write_i32(300)); > ++ > ++ assert_success!(o_prot.write_list_end()); > ++ > + copy_write_buffer_to_read_buffer!(o_prot); > + > + let res = assert_success!(i_prot.read_list_begin()); > + assert_eq!(&res, &ident); > ++ > ++ assert_eq!(i_prot.read_i32().unwrap(), 100); > ++ assert_eq!(i_prot.read_i32().unwrap(), 200); > ++ assert_eq!(i_prot.read_i32().unwrap(), 300); > ++ > ++ assert_success!(i_prot.read_list_end()); > + } > + > + #[test] > +@@ -2594,10 +2679,9 @@ mod tests { > + > + #[test] > + fn must_round_trip_large_sized_list_begin() { > +- let (mut i_prot, mut o_prot) = test_objects(); > ++ let (mut i_prot, mut o_prot) = test_objects_no_limits(); > + > + let ident = TListIdentifier::new(TType::Set, 47381); > +- > + assert_success!(o_prot.write_list_begin(&ident)); > + > + copy_write_buffer_to_read_buffer!(o_prot); > +@@ -2626,14 +2710,25 @@ mod tests { > + fn must_round_trip_small_sized_set_begin() { > + let (mut i_prot, mut o_prot) = test_objects(); > + > +- let ident = TSetIdentifier::new(TType::I16, 7); > +- > ++ let ident = TSetIdentifier::new(TType::I16, 3); > + assert_success!(o_prot.write_set_begin(&ident)); > + > ++ assert_success!(o_prot.write_i16(111)); > ++ assert_success!(o_prot.write_i16(222)); > ++ assert_success!(o_prot.write_i16(333)); > ++ > ++ assert_success!(o_prot.write_set_end()); > ++ > + copy_write_buffer_to_read_buffer!(o_prot); > + > + let res = assert_success!(i_prot.read_set_begin()); > + assert_eq!(&res, &ident); > ++ > ++ assert_eq!(i_prot.read_i16().unwrap(), 111); > ++ assert_eq!(i_prot.read_i16().unwrap(), 222); > ++ assert_eq!(i_prot.read_i16().unwrap(), 333); > ++ > ++ assert_success!(i_prot.read_set_end()); > + } > + > + #[test] > +@@ -2652,10 +2747,9 @@ mod tests { > + > + #[test] > + fn must_round_trip_large_sized_set_begin() { > +- let (mut i_prot, mut o_prot) = test_objects(); > ++ let (mut i_prot, mut o_prot) = test_objects_no_limits(); > + > + let ident = TSetIdentifier::new(TType::Map, 3_928_429); > +- > + assert_success!(o_prot.write_set_begin(&ident)); > + > + copy_write_buffer_to_read_buffer!(o_prot); > +@@ -2719,10 +2813,9 @@ mod tests { > + > + #[test] > + fn must_round_trip_map_begin() { > +- let (mut i_prot, mut o_prot) = test_objects(); > ++ let (mut i_prot, mut o_prot) = test_objects_no_limits(); > + > + let ident = TMapIdentifier::new(TType::Map, TType::List, 1_928_349); > +- > + assert_success!(o_prot.write_map_begin(&ident)); > + > + copy_write_buffer_to_read_buffer!(o_prot); > +@@ -2798,7 +2891,7 @@ mod tests { > + TCompactInputProtocol<ReadHalf<TBufferChannel>>, > + TCompactOutputProtocol<WriteHalf<TBufferChannel>>, > + ) { > +- let mem = TBufferChannel::with_capacity(80, 80); > ++ let mem = TBufferChannel::with_capacity(200, 200); > + > + let (r_mem, w_mem) = mem.split().unwrap(); > + > +@@ -2808,6 +2901,20 @@ mod tests { > + (i_prot, o_prot) > + } > + > ++ fn test_objects_no_limits() -> ( > ++ TCompactInputProtocol<ReadHalf<TBufferChannel>>, > ++ TCompactOutputProtocol<WriteHalf<TBufferChannel>>, > ++ ) { > ++ let mem = TBufferChannel::with_capacity(200, 200); > ++ > ++ let (r_mem, w_mem) = mem.split().unwrap(); > ++ > ++ let i_prot = TCompactInputProtocol::with_config(r_mem, TConfiguration::no_limits()); > ++ let o_prot = TCompactOutputProtocol::new(w_mem); > ++ > ++ (i_prot, o_prot) > ++ } > ++ > + #[test] > + fn must_read_write_double() { > + let (mut i_prot, mut o_prot) = test_objects(); > +@@ -2841,4 +2948,248 @@ mod tests { > + assert!(write_fn(&mut o_prot).is_ok()); > + assert_eq!(o_prot.transport.write_bytes().len(), 0); > + } > ++ > ++ #[test] > ++ fn must_enforce_recursion_depth_limit() { > ++ let channel = TBufferChannel::with_capacity(100, 100); > ++ > ++ // Create a configuration with a small recursion limit > ++ let config = TConfiguration::builder() > ++ .max_recursion_depth(Some(2)) > ++ .build() > ++ .unwrap(); > ++ > ++ let mut protocol = TCompactInputProtocol::with_config(channel, config); > ++ > ++ // First struct - should succeed > ++ assert!(protocol.read_struct_begin().is_ok()); > ++ > ++ // Second struct - should succeed (at limit) > ++ assert!(protocol.read_struct_begin().is_ok()); > ++ > ++ // Third struct - should fail (exceeds limit) > ++ let result = protocol.read_struct_begin(); > ++ assert!(result.is_err()); > ++ match result { > ++ Err(crate::Error::Protocol(e)) => { > ++ assert_eq!(e.kind, ProtocolErrorKind::DepthLimit); > ++ } > ++ _ => panic!("Expected protocol error with DepthLimit"), > ++ } > ++ } > ++ > ++ #[test] > ++ fn must_check_container_size_overflow() { > ++ // Configure a small message size limit > ++ let config = TConfiguration::builder() > ++ .max_message_size(Some(1000)) > ++ .max_frame_size(Some(1000)) > ++ .build() > ++ .unwrap(); > ++ let transport = TBufferChannel::with_capacity(100, 0); > ++ let mut i_prot = TCompactInputProtocol::with_config(transport, config); > ++ > ++ // Write a list header that would require more memory than message size limit > ++ // List of 100 UUIDs (16 bytes each) = 1600 bytes > 1000 limit > ++ i_prot.transport.set_readable_bytes(&[ > ++ 0xFD, // element type UUID (0x0D) | count in next bytes (0xF0) > ++ 0x64, // varint 100 > ++ ]); > ++ > ++ let result = i_prot.read_list_begin(); > ++ assert!(result.is_err()); > ++ match result { > ++ Err(crate::Error::Protocol(e)) => { > ++ assert_eq!(e.kind, ProtocolErrorKind::SizeLimit); > ++ assert!(e > ++ .message > ++ .contains("1600 bytes, exceeding message size limit of 1000")); > ++ } > ++ _ => panic!("Expected protocol error with SizeLimit"), > ++ } > ++ } > ++ > ++ #[test] > ++ fn must_reject_negative_container_sizes() { > ++ let mut channel = TBufferChannel::with_capacity(100, 100); > ++ > ++ let mut protocol = TCompactInputProtocol::new(channel.clone()); > ++ > ++ // Write header with negative size when decoded > ++ // In compact protocol, lists/sets use a header byte followed by size > ++ // We'll use 0x0F for element type and then a varint-encoded negative number > ++ channel.set_readable_bytes(&[ > ++ 0xF0, // Header: 15 in upper nibble (triggers varint read), List type in lower > ++ 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, // Varint encoding of -1 > ++ ]); > ++ > ++ let result = protocol.read_list_begin(); > ++ assert!(result.is_err()); > ++ match result { > ++ Err(crate::Error::Protocol(e)) => { > ++ assert_eq!(e.kind, ProtocolErrorKind::NegativeSize); > ++ } > ++ _ => panic!("Expected protocol error with NegativeSize"), > ++ } > ++ } > ++ > ++ #[test] > ++ fn must_enforce_container_size_limit() { > ++ let channel = TBufferChannel::with_capacity(100, 100); > ++ let (r_channel, mut w_channel) = channel.split().unwrap(); > ++ > ++ // Create protocol with explicit container size limit > ++ let config = TConfiguration::builder() > ++ .max_container_size(Some(1000)) > ++ .build() > ++ .unwrap(); > ++ let mut protocol = TCompactInputProtocol::with_config(r_channel, config); > ++ > ++ // Write header with large size > ++ // Compact protocol: 0xF0 means size >= 15 is encoded as varint > ++ // Then we write a varint encoding 10000 (exceeds our limit of 1000) > ++ w_channel.set_readable_bytes(&[ > ++ 0xF0, // Header: 15 in upper nibble (triggers varint read), element type in lower > ++ 0x90, 0x4E, // Varint encoding of 10000 > ++ ]); > ++ > ++ let result = protocol.read_list_begin(); > ++ assert!(result.is_err()); > ++ match result { > ++ Err(crate::Error::Protocol(e)) => { > ++ assert_eq!(e.kind, ProtocolErrorKind::SizeLimit); > ++ assert!(e.message.contains("exceeds maximum allowed size")); > ++ } > ++ _ => panic!("Expected protocol error with SizeLimit"), > ++ } > ++ } > ++ > ++ #[test] > ++ fn must_handle_varint_size_overflow() { > ++ // Test that compact protocol properly handles varint-encoded sizes that would cause overflow > ++ let mut channel = TBufferChannel::with_capacity(100, 100); > ++ > ++ let mut protocol = TCompactInputProtocol::new(channel.clone()); > ++ > ++ // Create input that encodes a very large size using varint encoding > ++ // 0xFA = list header with size >= 15 (so size follows as varint) > ++ // Then multiple 0xFF bytes which in varint encoding create a very large number > ++ channel.set_readable_bytes(&[ > ++ 0xFA, // List header: size >= 15, element type = 0x0A > ++ 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, // Varint encoding of a huge number > ++ ]); > ++ > ++ let result = protocol.read_list_begin(); > ++ assert!(result.is_err()); > ++ match result { > ++ Err(crate::Error::Protocol(e)) => { > ++ // The varint decoder might interpret this as negative, which is also fine > ++ assert!( > ++ e.kind == ProtocolErrorKind::SizeLimit > ++ || e.kind == ProtocolErrorKind::NegativeSize, > ++ "Expected SizeLimit or NegativeSize but got {:?}", > ++ e.kind > ++ ); > ++ } > ++ _ => panic!("Expected protocol error"), > ++ } > ++ } > ++ > ++ #[test] > ++ fn must_enforce_string_size_limit() { > ++ let channel = TBufferChannel::with_capacity(100, 100); > ++ let (r_channel, mut w_channel) = channel.split().unwrap(); > ++ > ++ // Create protocol with string limit of 100 bytes > ++ let config = TConfiguration::builder() > ++ .max_string_size(Some(100)) > ++ .build() > ++ .unwrap(); > ++ let mut protocol = TCompactInputProtocol::with_config(r_channel, config); > ++ > ++ // Write a varint-encoded string size that exceeds the limit > ++ w_channel.set_readable_bytes(&[ > ++ 0xC8, 0x01, // Varint encoding of 200 > ++ ]); > ++ > ++ let result = protocol.read_string(); > ++ assert!(result.is_err()); > ++ match result { > ++ Err(crate::Error::Protocol(e)) => { > ++ assert_eq!(e.kind, ProtocolErrorKind::SizeLimit); > ++ assert!(e.message.contains("exceeds maximum allowed size")); > ++ } > ++ _ => panic!("Expected protocol error with SizeLimit"), > ++ } > ++ } > ++ > ++ #[test] > ++ fn must_allow_no_limit_configuration() { > ++ let channel = TBufferChannel::with_capacity(40, 40); > ++ > ++ let config = TConfiguration::no_limits(); > ++ let mut protocol = TCompactInputProtocol::with_config(channel, config); > ++ > ++ // Should be able to nest structs deeply without limit > ++ for _ in 0..100 { > ++ assert!(protocol.read_struct_begin().is_ok()); > ++ } > ++ > ++ for _ in 0..100 { > ++ assert!(protocol.read_struct_end().is_ok()); > ++ } > ++ } > ++ > ++ #[test] > ++ fn must_allow_containers_within_limit() { > ++ let channel = TBufferChannel::with_capacity(200, 200); > ++ let (r_channel, mut w_channel) = channel.split().unwrap(); > ++ > ++ // Create protocol with container limit of 100 > ++ let config = TConfiguration::builder() > ++ .max_container_size(Some(100)) > ++ .build() > ++ .unwrap(); > ++ let mut protocol = TCompactInputProtocol::with_config(r_channel, config); > ++ > ++ // Write a list with 5 i32 elements (well within limit of 100) > ++ // Compact protocol: size < 15 is encoded in header > ++ w_channel.set_readable_bytes(&[ > ++ 0x55, // Header: size=5, element type=5 (i32) > ++ // 5 varint-encoded i32 values > ++ 0x0A, // 10 > ++ 0x14, // 20 > ++ 0x1E, // 30 > ++ 0x28, // 40 > ++ 0x32, // 50 > ++ ]); > ++ > ++ let result = protocol.read_list_begin(); > ++ assert!(result.is_ok()); > ++ let list_ident = result.unwrap(); > ++ assert_eq!(list_ident.size, 5); > ++ assert_eq!(list_ident.element_type, TType::I32); > ++ } > ++ > ++ #[test] > ++ fn must_allow_strings_within_limit() { > ++ let channel = TBufferChannel::with_capacity(100, 100); > ++ let (r_channel, mut w_channel) = channel.split().unwrap(); > ++ > ++ let config = TConfiguration::builder() > ++ .max_string_size(Some(1000)) > ++ .build() > ++ .unwrap(); > ++ let mut protocol = TCompactInputProtocol::with_config(r_channel, config); > ++ > ++ // Write a string "hello" (5 bytes, well within limit) > ++ w_channel.set_readable_bytes(&[ > ++ 0x05, // Varint-encoded length: 5 > ++ b'h', b'e', b'l', b'l', b'o', > ++ ]); > ++ > ++ let result = protocol.read_string(); > ++ assert!(result.is_ok()); > ++ assert_eq!(result.unwrap(), "hello"); > ++ } > + } > +diff --git a/lib/rs/src/protocol/mod.rs b/lib/rs/src/protocol/mod.rs > +index 8bbbb3c7c..573978a75 100644 > +--- a/lib/rs/src/protocol/mod.rs > ++++ b/lib/rs/src/protocol/mod.rs > +@@ -62,7 +62,7 @@ use std::fmt; > + use std::fmt::{Display, Formatter}; > + > + use crate::transport::{TReadTransport, TWriteTransport}; > +-use crate::{ProtocolError, ProtocolErrorKind}; > ++use crate::{ProtocolError, ProtocolErrorKind, TConfiguration}; > + > + #[cfg(test)] > + macro_rules! assert_eq_written_bytes { > +@@ -262,6 +262,15 @@ pub trait TInputProtocol { > + /// > + /// This method should **never** be used in generated code. > + fn read_byte(&mut self) -> crate::Result<u8>; > ++ > ++ /// Get the minimum number of bytes a type will consume on the wire. > ++ /// This picks the minimum possible across all protocols (so currently matches the compact protocol). > ++ /// > ++ /// This is used for pre-allocation size checks. > ++ /// The actual data may be larger (e.g., for strings, lists, etc.). > ++ fn min_serialized_size(&self, field_type: TType) -> usize { > ++ self::compact::compact_protocol_min_serialized_size(field_type) > ++ } > + } > + > + /// Converts Thrift identifiers, primitives, containers or structs into a > +@@ -444,6 +453,10 @@ where > + fn read_byte(&mut self) -> crate::Result<u8> { > + (**self).read_byte() > + } > ++ > ++ fn min_serialized_size(&self, field_type: TType) -> usize { > ++ (**self).min_serialized_size(field_type) > ++ } > + } > + > + impl<P> TOutputProtocol for Box<P> > +@@ -565,7 +578,7 @@ where > + /// let protocol = factory.create(Box::new(channel)); > + /// ``` > + pub trait TInputProtocolFactory { > +- // Create a `TInputProtocol` that reads bytes from `transport`. > ++ /// Create a `TInputProtocol` that reads bytes from `transport`. > + fn create(&self, transport: Box<dyn TReadTransport + Send>) -> Box<dyn TInputProtocol + Send>; > + } > + > +@@ -920,6 +933,69 @@ pub fn verify_required_field_exists<T>(field_name: &str, field: &Option<T>) -> c > + } > + } > + > ++/// Common container size validation used by all protocols. > ++/// > ++/// Checks that: > ++/// - Container size is not negative > ++/// - Container size doesn't exceed configured maximum > ++/// - Container size * element size doesn't overflow > ++/// - Container memory requirements don't exceed message size limit > ++pub(crate) fn check_container_size( > ++ config: &TConfiguration, > ++ container_size: i32, > ++ element_size: usize, > ++) -> crate::Result<()> { > ++ // Check for negative size > ++ if container_size < 0 { > ++ return Err(crate::Error::Protocol(ProtocolError::new( > ++ ProtocolErrorKind::NegativeSize, > ++ format!("Negative container size: {}", container_size), > ++ ))); > ++ } > ++ > ++ let size_as_usize = container_size as usize; > ++ > ++ // Check against configured max container size > ++ if let Some(max_size) = config.max_container_size() { > ++ if size_as_usize > max_size { > ++ return Err(crate::Error::Protocol(ProtocolError::new( > ++ ProtocolErrorKind::SizeLimit, > ++ format!( > ++ "Container size {} exceeds maximum allowed size of {}", > ++ container_size, max_size > ++ ), > ++ ))); > ++ } > ++ } > ++ > ++ // Check for potential overflow > ++ if let Some(min_bytes_needed) = size_as_usize.checked_mul(element_size) { > ++ // TODO: When Rust trait specialization stabilizes, we can add more precise checks > ++ // for transports that track exact remaining bytes. For now, we use the message > ++ // size limit as a best-effort check. > ++ if let Some(max_message_size) = config.max_message_size() { > ++ if min_bytes_needed > max_message_size { > ++ return Err(crate::Error::Protocol(ProtocolError::new( > ++ ProtocolErrorKind::SizeLimit, > ++ format!( > ++ "Container would require {} bytes, exceeding message size limit of {}", > ++ min_bytes_needed, max_message_size > ++ ), > ++ ))); > ++ } > ++ } > ++ Ok(()) > ++ } else { > ++ Err(crate::Error::Protocol(ProtocolError::new( > ++ ProtocolErrorKind::SizeLimit, > ++ format!( > ++ "Container size {} with element size {} bytes would result in overflow", > ++ container_size, element_size > ++ ), > ++ ))) > ++ } > ++} > ++ > + /// Extract the field id from a Thrift field identifier. > + /// > + /// `field_ident` must *not* have `TFieldIdentifier.field_type` of type `TType::Stop`. > +diff --git a/lib/rs/src/transport/framed.rs b/lib/rs/src/transport/framed.rs > +index c30ccd9ed..a08a380f4 100644 > +--- a/lib/rs/src/transport/framed.rs > ++++ b/lib/rs/src/transport/framed.rs > +@@ -21,6 +21,7 @@ use std::io; > + use std::io::{Read, Write}; > + > + use super::{TReadTransport, TReadTransportFactory, TWriteTransport, TWriteTransportFactory}; > ++use crate::TConfiguration; > + > + /// Default capacity of the read buffer in bytes. > + const READ_CAPACITY: usize = 4096; > +@@ -61,6 +62,7 @@ where > + pos: usize, > + cap: usize, > + chan: C, > ++ config: TConfiguration, > + } > + > + impl<C> TFramedReadTransport<C> > +@@ -81,6 +83,7 @@ where > + pos: 0, > + cap: 0, > + chan: channel, > ++ config: TConfiguration::default(), > + } > + } > + } > +@@ -91,7 +94,28 @@ where > + { > + fn read(&mut self, b: &mut [u8]) -> io::Result<usize> { > + if self.cap - self.pos == 0 { > +- let message_size = self.chan.read_i32::<BigEndian>()? as usize; > ++ let frame_size_bytes = self.chan.read_i32::<BigEndian>()?; > ++ > ++ if frame_size_bytes < 0 { > ++ return Err(io::Error::new( > ++ io::ErrorKind::InvalidData, > ++ format!("Negative frame size: {}", frame_size_bytes), > ++ )); > ++ } > ++ > ++ let message_size = frame_size_bytes as usize; > ++ > ++ if let Some(max_frame) = self.config.max_frame_size() { > ++ if message_size > max_frame { > ++ return Err(io::Error::new( > ++ io::ErrorKind::InvalidData, > ++ format!( > ++ "Frame size {} exceeds maximum allowed size of {}", > ++ message_size, max_frame > ++ ), > ++ )); > ++ } > ++ } > + > + let buf_capacity = cmp::max(message_size, READ_CAPACITY); > + self.buf.resize(buf_capacity, 0); > +@@ -125,7 +149,6 @@ impl TReadTransportFactory for TFramedReadTransportFactory { > + Box::new(TFramedReadTransport::new(channel)) > + } > + } > +- > + /// Transport that writes framed messages. > + /// > + /// A `TFramedWriteTransport` maintains a fixed-size internal write buffer. All > +-- > +2.43.0 > + > diff --git a/meta-oe/recipes-connectivity/thrift/thrift/CVE-2026-43870.patch b/meta-oe/recipes-connectivity/thrift/thrift/CVE-2026-43870.patch > new file mode 100644 > index 0000000000..a4b5ef4835 > --- /dev/null > +++ b/meta-oe/recipes-connectivity/thrift/thrift/CVE-2026-43870.patch > @@ -0,0 +1,127 @@ > +From 0faaa79330a2864135948186858036676fdbc1b1 Mon Sep 17 00:00:00 2001 > +From: Jens Geyer <[email protected]> > +Date: Sat, 11 Apr 2026 12:29:16 +0200 > +Subject: [PATCH] Harden Node.js WebSocket server handling Client: nodejs > + > +- Validate origin on WebSocket upgrade using the same CORS rules > + as HTTP requests > +- Fix path containment check to use trailing separator > +- Replace deprecated new Buffer() with Buffer.alloc() > +- Sanitize interpolated header values in upgrade response > + > +(04) > + > +Co-Authored-By: Claude Opus 4.6 <[email protected]> > + > +This closes #3391 > + > +CVE: CVE-2026-43870 > +Upstream-Status: Backport [https://github.com/apache/thrift/commit/5e4f01d737e4d3845d07dde6da43981feee0cb9a] > + > +Signed-off-by: Roland Kovacs <[email protected]> > +--- > + lib/nodejs/lib/thrift/web_server.js | 32 +++++++++++++++++++++-------- > + 1 file changed, 24 insertions(+), 8 deletions(-) > + > +diff --git a/lib/nodejs/lib/thrift/web_server.js b/lib/nodejs/lib/thrift/web_server.js > +index a33f47aed..cf88bde1b 100644 > +--- a/lib/nodejs/lib/thrift/web_server.js > ++++ b/lib/nodejs/lib/thrift/web_server.js > +@@ -26,6 +26,10 @@ var log = require('./log'); > + > + var MultiplexedProcessor = require('./multiplexed_processor').MultiplexedProcessor; > + > ++function sanitizeHeader(value) { > ++ return (value || "").replace(/[\r\n]/g, ""); > ++} > ++ > + var TBufferedTransport = require('./buffered_transport'); > + var TBinaryProtocol = require('./binary_protocol'); > + var InputBufferUnderrunError = require('./input_buffer_underrun_error'); > +@@ -83,7 +87,7 @@ var wsFrame = { > + * @returns {Buffer} - The WebSocket frame, ready to send > + */ > + encode: function(data, mask, binEncoding) { > +- var frame = new Buffer(wsFrame.frameSizeFromData(data, mask)); > ++ var frame = Buffer.alloc(wsFrame.frameSizeFromData(data, mask)); > + //Byte 0 - FIN & OPCODE > + frame[0] = wsFrame.fin.FIN + > + (binEncoding ? wsFrame.frameOpCodes.BIN : wsFrame.frameOpCodes.TEXT); > +@@ -162,19 +166,19 @@ var wsFrame = { > + } > + //MASK > + if (wsFrame.mask.TO_SERVER == (frame[1] & wsFrame.mask.TO_SERVER)) { > +- result.mask = new Buffer(4); > ++ result.mask = Buffer.alloc(4); > + frame.copy(result.mask, 0, dataOffset, dataOffset + 4); > + dataOffset += 4; > + } > + //Payload > +- result.data = new Buffer(len); > ++ result.data = Buffer.alloc(len); > + frame.copy(result.data, 0, dataOffset, dataOffset+len); > + if (result.mask) { > + wsFrame.applyMask(result.data, result.mask); > + } > + //Next Frame > + if (frame.length > dataOffset+len) { > +- result.nextFrame = new Buffer(frame.length - (dataOffset+len)); > ++ result.nextFrame = Buffer.alloc(frame.length - (dataOffset+len)); > + frame.copy(result.nextFrame, 0, dataOffset+len, frame.length); > + } > + //Don't forward control frames > +@@ -418,7 +422,8 @@ exports.createWebServer = function(options) { > + var filename = path.resolve(path.join(baseDir, uri)); > + > + //Ensure the basedir path is not able to be escaped > +- if (filename.indexOf(baseDir) != 0) { > ++ var normalizedBase = baseDir.endsWith(path.sep) ? baseDir : baseDir + path.sep; > ++ if (filename !== baseDir && filename.indexOf(normalizedBase) !== 0) { > + response.writeHead(400, "Invalid request path", {}); > + response.end(); > + return; > +@@ -510,6 +515,14 @@ exports.createWebServer = function(options) { > + response.end(); > + } > + }).on('upgrade', function(request, socket, head) { > ++ //Verify CORS origin for WebSocket upgrades > ++ if (request.headers.origin && options.cors) { > ++ if (!options.cors["*"] && !options.cors[request.headers.origin]) { > ++ socket.write("HTTP/1.1 403 Origin not allowed\r\n\r\n"); > ++ socket.destroy(); > ++ return; > ++ } > ++ } > + //Lookup service > + var svc; > + try { > +@@ -521,12 +534,15 @@ exports.createWebServer = function(options) { > + //Perform upgrade > + var hash = crypto.createHash("sha1"); > + hash.update(request.headers['sec-websocket-key'] + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); > ++ var origin = sanitizeHeader(request.headers.origin); > ++ var host = sanitizeHeader(request.headers.host); > ++ var reqUrl = sanitizeHeader(request.url); > + socket.write("HTTP/1.1 101 Switching Protocols\r\n" + > + "Upgrade: websocket\r\n" + > + "Connection: Upgrade\r\n" + > + "Sec-WebSocket-Accept: " + hash.digest("base64") + "\r\n" + > +- "Sec-WebSocket-Origin: " + request.headers.origin + "\r\n" + > +- "Sec-WebSocket-Location: ws://" + request.headers.host + request.url + "\r\n" + > ++ "Sec-WebSocket-Origin: " + origin + "\r\n" + > ++ "Sec-WebSocket-Location: ws://" + host + reqUrl + "\r\n" + > + "\r\n"); > + //Handle WebSocket traffic > + var data = null; > +@@ -537,7 +553,7 @@ exports.createWebServer = function(options) { > + //Prepend any existing decoded data > + if (data) { > + if (result.data) { > +- var newData = new Buffer(data.length + result.data.length); > ++ var newData = Buffer.alloc(data.length + result.data.length); > + data.copy(newData); > + result.data.copy(newData, data.length); > + result.data = newData; > +-- > +2.43.0 > + > diff --git a/meta-oe/recipes-connectivity/thrift/thrift_0.20.0.bb b/meta-oe/recipes-connectivity/thrift/thrift_0.20.0.bb > index e4fd07198c..2922dc96e4 100644 > --- a/meta-oe/recipes-connectivity/thrift/thrift_0.20.0.bb > +++ b/meta-oe/recipes-connectivity/thrift/thrift_0.20.0.bb > @@ -12,6 +12,8 @@ SRC_URI = "https://archive.apache.org/dist/${BPN}/${PV}/${BP}.tar.gz \ > file://0001-DefineInstallationPaths.cmake-Define-libdir-in-terms.patch \ > file://0001-thrift-pr2755.patch \ > file://0001-THRIFT-5842-Add-missing-cstdint-include-for-int64_t-.patch \ > + file://CVE-2026-43868.patch \ > + file://CVE-2026-43870.patch \ > " > SRC_URI[sha256sum] = "b5d8311a779470e1502c027f428a1db542f5c051c8e1280ccd2163fa935ff2d6" > > @@ -19,6 +21,8 @@ BBCLASSEXTEND = "native nativesdk" > > CVE_PRODUCT = "apache:thrift" > > +CVE_STATUS[CVE-2026-43869] = "not-applicable-config: The issue is present in the Java client which is not built" > + > inherit pkgconfig cmake python3native > > export STAGING_INCDIR > -- > 2.34.1 > > > -=-=-=-=-=-=-=-=-=-=-=- > Links: You receive all messages sent to this group. > View/Reply Online (#128066): https://lists.openembedded.org/g/openembedded-devel/message/128066 > Mute This Topic: https://lists.openembedded.org/mt/120155673/3616702 > Group Owner: [email protected] > Unsubscribe: https://lists.openembedded.org/g/openembedded-devel/unsub [[email protected]] > -=-=-=-=-=-=-=-=-=-=-=- >