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") }
