diff mbox series

[meta-oe,wrynose] thrift: fix multiple CVEs

Message ID 20260707130919.112360-1-roland.kovacs@est.tech
State New
Headers show
Series [meta-oe,wrynose] thrift: fix multiple CVEs | expand

Commit Message

Roland Kovacs July 7, 2026, 1:09 p.m. UTC
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 <roland.kovacs@est.tech>
---
 .../thrift/thrift/CVE-2026-43868.patch        | 1381 +++++++++++++++++
 .../thrift/thrift/CVE-2026-43870.patch        |  134 ++
 .../thrift/thrift_0.22.0.bb                   |    4 +
 3 files changed, 1519 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 mbox series

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..de5b21ffa6
--- /dev/null
+++ b/meta-oe/recipes-connectivity/thrift/thrift/CVE-2026-43868.patch
@@ -0,0 +1,1381 @@ 
+From cbab9a3e41ef02ad762bfd2e07cbc8e07ef5cf11 Mon Sep 17 00:00:00 2001
+From: Hasnain Lakhani <m.hasnain.lakhani@gmail.com>
+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 <roland.kovacs@est.tech>
+---
+ 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 2f6018810..d3804eccc 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 4dc45cac2..7e1a7510e 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()
+@@ -227,6 +258,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)
+@@ -291,6 +335,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))
+         }
+     }
+@@ -309,6 +359,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>
+@@ -2573,14 +2647,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]
+@@ -2600,10 +2685,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);
+@@ -2632,14 +2716,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]
+@@ -2658,10 +2753,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);
+@@ -2725,10 +2819,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);
+@@ -2804,7 +2897,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();
+ 
+@@ -2814,6 +2907,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();
+@@ -2883,4 +2990,248 @@ mod tests {
+ 
+         assert_success!(i_prot.read_list_end());
+     }
++
++    #[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 d8a744872..cf959be8b 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.34.1
+
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..eae170fa86
--- /dev/null
+++ b/meta-oe/recipes-connectivity/thrift/thrift/CVE-2026-43870.patch
@@ -0,0 +1,134 @@ 
+From 86f647c6894360da7496efee6f6f5eb2601a5c3f Mon Sep 17 00:00:00 2001
+From: Jens Geyer <jensg@apache.org>
+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 <noreply@anthropic.com>
+
+This closes #3391
+
+CVE: CVE-2026-43870
+Upstream-Status: Backport [https://github.com/apache/thrift/commit/5e4f01d737e4d3845d07dde6da43981feee0cb9a]
+
+Signed-off-by: Roland Kovacs <roland.kovacs@est.tech>
+---
+ lib/nodejs/lib/thrift/web_server.js | 34 +++++++++++++++++++++--------
+ 1 file changed, 25 insertions(+), 9 deletions(-)
+
+diff --git a/lib/nodejs/lib/thrift/web_server.js b/lib/nodejs/lib/thrift/web_server.js
+index f49f654b1..ad3a34c5c 100644
+--- a/lib/nodejs/lib/thrift/web_server.js
++++ b/lib/nodejs/lib/thrift/web_server.js
+@@ -27,6 +27,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");
+@@ -84,7 +88,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 +
+@@ -171,19 +175,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
+@@ -450,7 +454,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;
+@@ -543,6 +548,14 @@ exports.createWebServer = function (options) {
+       }
+     })
+     .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 {
+@@ -557,6 +570,9 @@ exports.createWebServer = function (options) {
+         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" +
+@@ -565,11 +581,11 @@ exports.createWebServer = function (options) {
+           hash.digest("base64") +
+           "\r\n" +
+           "Sec-WebSocket-Origin: " +
+-          request.headers.origin +
++          origin +
+           "\r\n" +
+           "Sec-WebSocket-Location: ws://" +
+-          request.headers.host +
+-          request.url +
++          host +
++          reqUrl +
+           "\r\n" +
+           "\r\n",
+       );
+@@ -582,7 +598,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.34.1
+
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 8ca1d0085e..8d885dadc4 100644
--- a/meta-oe/recipes-connectivity/thrift/thrift_0.22.0.bb
+++ b/meta-oe/recipes-connectivity/thrift/thrift_0.22.0.bb
@@ -11,6 +11,8 @@  DEPENDS = "thrift-native boost flex-native bison-native openssl zlib"
 SRC_URI = "https://downloads.apache.org/${BPN}/${PV}/${BP}.tar.gz \
            file://0001-DefineInstallationPaths.cmake-Define-libdir-in-terms.patch \
            file://0001-support-reproducible-builds.patch \
+           file://CVE-2026-43868.patch \
+           file://CVE-2026-43870.patch \
            "
 SRC_URI[sha256sum] = "794a0e455787960d9f27ab92c38e34da27e8deeda7a5db0e59dc64a00df8a1e5"
 
@@ -18,6 +20,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