This is an automated email from the ASF dual-hosted git repository.

jensg pushed a commit to branch 0.23.0
in repository https://gitbox.apache.org/repos/asf/thrift.git

commit e242889231df48f8791372598052862c28f5be83
Author: Jens Geyer <[email protected]>
AuthorDate: Sat Apr 11 12:29:23 2026 +0200

    Add input validation to Swift protocol layer
    Client: swift
    
    - Add recursion depth limit (64) and negative size checks to
      skip() in TProtocol for map, set, and list types
    - Add range validation for fieldId before UInt8 cast in
      TCompactProtocol.readFieldBegin()
    
    (07, 08)
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    This closes #3392
---
 lib/swift/Sources/TCompactProtocol.swift |  4 ++++
 lib/swift/Sources/TProtocol.swift        | 41 ++++++++++++++++++++++----------
 2 files changed, 33 insertions(+), 12 deletions(-)

diff --git a/lib/swift/Sources/TCompactProtocol.swift 
b/lib/swift/Sources/TCompactProtocol.swift
index 812df25c1..acdb91b79 100644
--- a/lib/swift/Sources/TCompactProtocol.swift
+++ b/lib/swift/Sources/TCompactProtocol.swift
@@ -313,6 +313,10 @@ public class TCompactProtocol: TProtocol {
       booleanValue = type == .boolean_TRUE
     }
     
+    guard fieldId >= 0 && fieldId <= Int16(UInt8.max) else {
+      throw TProtocolError(error: .invalidData,
+                           message: "Field id out of range: \(fieldId)")
+    }
     // push the new field onto the field stack so we can keep the deltas going
     lastFieldId = UInt8(fieldId)
     return ("", fieldType, Int32(fieldId))
diff --git a/lib/swift/Sources/TProtocol.swift 
b/lib/swift/Sources/TProtocol.swift
index ed6db6fa3..e3a7a0ec4 100644
--- a/lib/swift/Sources/TProtocol.swift
+++ b/lib/swift/Sources/TProtocol.swift
@@ -133,6 +133,14 @@ public extension TProtocol {
   }
   
   func skip(type: TType) throws {
+    try skip(type: type, depth: 0)
+  }
+
+  private func skip(type: TType, depth: Int) throws {
+    let nextDepth = depth + 1
+    if nextDepth > 64 {
+      throw TProtocolError(error: .depthLimit, message: "Maximum skip depth 
exceeded")
+    }
     switch type {
     case .bool:   _ = try read() as Bool
     case .i8:   _ = try read() as Int8
@@ -142,7 +150,7 @@ public extension TProtocol {
     case .double: _ = try read() as Double
     case .string: _ = try read() as String
     case .uuid: _ = try read() as UUID
-      
+
     case .struct:
       _ = try readStructBegin()
       while true {
@@ -150,35 +158,44 @@ public extension TProtocol {
         if fieldType == .stop {
           break
         }
-        try skip(type: fieldType)
+        try skip(type: fieldType, depth: nextDepth)
         try readFieldEnd()
       }
       try readStructEnd()
-      
-      
+
+
     case .map:
       let (keyType, valueType, size) = try readMapBegin()
+      if size < 0 {
+        throw TProtocolError(error: .negativeSize, message: "Negative map 
size: \(size)")
+      }
       for _ in 0..<size {
-        try skip(type: keyType)
-        try skip(type: valueType)
+        try skip(type: keyType, depth: nextDepth)
+        try skip(type: valueType, depth: nextDepth)
       }
       try readMapEnd()
-      
-      
+
+
     case .set:
       let (elemType, size) = try readSetBegin()
+      if size < 0 {
+        throw TProtocolError(error: .negativeSize, message: "Negative set 
size: \(size)")
+      }
       for _ in 0..<size {
-        try skip(type: elemType)
+        try skip(type: elemType, depth: nextDepth)
       }
       try readSetEnd()
-      
+
     case .list:
       let (elemType, size) = try readListBegin()
+      if size < 0 {
+        throw TProtocolError(error: .negativeSize, message: "Negative list 
size: \(size)")
+      }
       for _ in 0..<size {
-        try skip(type: elemType)
+        try skip(type: elemType, depth: nextDepth)
       }
       try readListEnd()
-      
+
     default:
       throw TProtocolError(error: .invalidData, message: "Invalid data")
     }

Reply via email to