Skip to content

Commit d492e58

Browse files
authored
Merge pull request #2707 from millenomi/59688482-5.2
2 parents 81023c5 + 79bc225 commit d492e58

File tree

2 files changed

+65
-15
lines changed

2 files changed

+65
-15
lines changed

Foundation/JSONSerialization.swift

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ extension JSONSerialization {
2828
}
2929
}
3030

31+
extension JSONSerialization {
32+
// Structures with container nesting deeper than this limit are not valid if passed in in-memory for validation, nor if they are read during deserialization.
33+
// This matches Darwin Foundation's validation behavior.
34+
fileprivate static let maximumRecursionDepth = 512
35+
}
36+
3137

3238
/* A class for converting JSON to Foundation/Swift objects and converting Foundation/Swift objects to JSON.
3339

@@ -48,8 +54,15 @@ open class JSONSerialization : NSObject {
4854
- returns: `true` if `obj` can be converted to JSON, otherwise `false`.
4955
*/
5056
open class func isValidJSONObject(_ obj: Any) -> Bool {
57+
var recursionDepth = 0
58+
5159
// TODO: - revisit this once bridging story gets fully figured out
5260
func isValidJSONObjectInternal(_ obj: Any?) -> Bool {
61+
// Match Darwin Foundation in not considering a deep object valid.
62+
guard recursionDepth < JSONSerialization.maximumRecursionDepth else { return false }
63+
recursionDepth += 1
64+
defer { recursionDepth -= 1 }
65+
5366
// Emulate the SE-0140 behavior bridging behavior for nils
5467
guard let obj = obj else {
5568
return true
@@ -173,13 +186,13 @@ open class JSONSerialization : NSObject {
173186

174187
let source = JSONReader.UnicodeSource(buffer: buffer, encoding: encoding)
175188
let reader = JSONReader(source: source)
176-
if let (object, _) = try reader.parseObject(0, options: opt) {
189+
if let (object, _) = try reader.parseObject(0, options: opt, recursionDepth: 0) {
177190
return object
178191
}
179-
else if let (array, _) = try reader.parseArray(0, options: opt) {
192+
else if let (array, _) = try reader.parseArray(0, options: opt, recursionDepth: 0) {
180193
return array
181194
}
182-
else if opt.contains(.allowFragments), let (value, _) = try reader.parseValue(0, options: opt) {
195+
else if opt.contains(.allowFragments), let (value, _) = try reader.parseValue(0, options: opt, recursionDepth: 0) {
183196
return value
184197
}
185198
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: [
@@ -922,9 +935,16 @@ private struct JSONReader {
922935
}
923936
return nil
924937
}
925-
926-
//MARK: - Value parsing
927-
func parseValue(_ input: Index, options opt: JSONSerialization.ReadingOptions) throws -> (Any, Index)? {
938+
939+
func parseValue(_ input: Index, options opt: JSONSerialization.ReadingOptions, recursionDepth: Int) throws -> (Any, Index)? {
940+
guard recursionDepth < JSONSerialization.maximumRecursionDepth else {
941+
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: [
942+
NSDebugDescriptionErrorKey: "Recursion depth exceeded during parsing"
943+
])
944+
}
945+
946+
let newDepth = recursionDepth + 1
947+
928948
if let (value, parser) = try parseString(input) {
929949
return (value, parser)
930950
}
@@ -937,10 +957,10 @@ private struct JSONReader {
937957
else if let parser = try consumeASCIISequence("null", input: input) {
938958
return (NSNull(), parser)
939959
}
940-
else if let (object, parser) = try parseObject(input, options: opt) {
960+
else if let (object, parser) = try parseObject(input, options: opt, recursionDepth: newDepth) {
941961
return (object, parser)
942962
}
943-
else if let (array, parser) = try parseArray(input, options: opt) {
963+
else if let (array, parser) = try parseArray(input, options: opt, recursionDepth: newDepth) {
944964
return (array, parser)
945965
}
946966
else if let (number, parser) = try parseNumber(input, options: opt) {
@@ -950,7 +970,7 @@ private struct JSONReader {
950970
}
951971

952972
//MARK: - Object parsing
953-
func parseObject(_ input: Index, options opt: JSONSerialization.ReadingOptions) throws -> ([String: Any], Index)? {
973+
func parseObject(_ input: Index, options opt: JSONSerialization.ReadingOptions, recursionDepth: Int) throws -> ([String: Any], Index)? {
954974
guard let beginIndex = try consumeStructure(Structure.BeginObject, input: input) else {
955975
return nil
956976
}
@@ -961,7 +981,7 @@ private struct JSONReader {
961981
return (output, finalIndex)
962982
}
963983

964-
if let (key, value, nextIndex) = try parseObjectMember(index, options: opt) {
984+
if let (key, value, nextIndex) = try parseObjectMember(index, options: opt, recursionDepth: recursionDepth) {
965985
output[key] = value
966986

967987
if let finalParser = try consumeStructure(Structure.EndObject, input: nextIndex) {
@@ -979,7 +999,7 @@ private struct JSONReader {
979999
}
9801000
}
9811001

982-
func parseObjectMember(_ input: Index, options opt: JSONSerialization.ReadingOptions) throws -> (String, Any, Index)? {
1002+
func parseObjectMember(_ input: Index, options opt: JSONSerialization.ReadingOptions, recursionDepth: Int) throws -> (String, Any, Index)? {
9831003
guard let (name, index) = try parseString(input) else {
9841004
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: [
9851005
NSDebugDescriptionErrorKey : "Missing object key at location \(source.distanceFromStart(input))"
@@ -990,7 +1010,7 @@ private struct JSONReader {
9901010
NSDebugDescriptionErrorKey : "Invalid separator at location \(source.distanceFromStart(index))"
9911011
])
9921012
}
993-
guard let (value, finalIndex) = try parseValue(separatorIndex, options: opt) else {
1013+
guard let (value, finalIndex) = try parseValue(separatorIndex, options: opt, recursionDepth: recursionDepth) else {
9941014
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: [
9951015
NSDebugDescriptionErrorKey : "Invalid value at location \(source.distanceFromStart(separatorIndex))"
9961016
])
@@ -1000,7 +1020,7 @@ private struct JSONReader {
10001020
}
10011021

10021022
//MARK: - Array parsing
1003-
func parseArray(_ input: Index, options opt: JSONSerialization.ReadingOptions) throws -> ([Any], Index)? {
1023+
func parseArray(_ input: Index, options opt: JSONSerialization.ReadingOptions, recursionDepth: Int) throws -> ([Any], Index)? {
10041024
guard let beginIndex = try consumeStructure(Structure.BeginArray, input: input) else {
10051025
return nil
10061026
}
@@ -1010,8 +1030,8 @@ private struct JSONReader {
10101030
if let finalIndex = try consumeStructure(Structure.EndArray, input: index) {
10111031
return (output, finalIndex)
10121032
}
1013-
1014-
if let (value, nextIndex) = try parseValue(index, options: opt) {
1033+
1034+
if let (value, nextIndex) = try parseValue(index, options: opt, recursionDepth: recursionDepth) {
10151035
output.append(value)
10161036

10171037
if let finalIndex = try consumeStructure(Structure.EndArray, input: nextIndex) {

TestFoundation/TestJSONSerialization.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ extension TestJSONSerialization {
149149
("test_deserialize_unicodeMissingTrailingSurrogate_withStream", test_deserialize_unicodeMissingTrailingSurrogate_withStream),
150150
("test_JSONObjectWithStream_withFile", test_JSONObjectWithStream_withFile),
151151
("test_JSONObjectWithStream_withURL", test_JSONObjectWithStream_withURL),
152+
153+
("test_bailOnDeepValidStructure", test_bailOnDeepValidStructure),
154+
("test_bailOnDeepInvalidStructure", test_bailOnDeepInvalidStructure),
152155
]
153156
}
154157

@@ -1540,6 +1543,33 @@ extension TestJSONSerialization {
15401543
let dictionary = ["key": 4]
15411544
XCTAssertEqual(try trySerialize(dictionary, options: .prettyPrinted), "{\n \"key\" : 4\n}")
15421545
}
1546+
1547+
func test_bailOnDeepValidStructure() {
1548+
let repetition = 8000
1549+
let testString = String(repeating: "[", count: repetition) + String(repeating: "]", count: repetition)
1550+
let data = testString.data(using: .utf8)!
1551+
do {
1552+
_ = try JSONSerialization.jsonObject(with: data, options: [])
1553+
}
1554+
catch let nativeError {
1555+
if let error = nativeError as? NSError {
1556+
XCTAssertEqual(error.domain, "NSCocoaErrorDomain")
1557+
XCTAssertEqual(error.code, 3840)
1558+
}
1559+
}
1560+
}
1561+
1562+
func test_bailOnDeepInvalidStructure() {
1563+
let repetition = 8000
1564+
let testString = String(repeating: "[", count: repetition)
1565+
let data = testString.data(using: .utf8)!
1566+
do {
1567+
_ = try JSONSerialization.jsonObject(with: data, options: [])
1568+
}
1569+
catch {
1570+
// expected case
1571+
}
1572+
}
15431573

15441574
fileprivate func createTestFile(_ path: String,_contents: Data) -> String? {
15451575
let tempDir = NSTemporaryDirectory() + "TestFoundation_Playground_" + NSUUID().uuidString + "/"

0 commit comments

Comments
 (0)