CVE-2022-31019: Uncontrolled Recursion (CWE-674)
value: Vapor is a server-side Swift HTTP web framework. When using automatic content decoding an attacker can craft a request body that can make the server crash with the following request: `curl -d "array[_0][0][array][_0][0][array]$(for f in $(seq 1100); do echo -n '[_0][0][array]'; done)[string][_0]=hello%20world" http://localhost:8080/foo`. The issue is unbounded, attacker controlled stack growth which will at some point lead to a stack overflow and a process crash. This issue has been fixed in version 4.61.1.
CWE description: The product does not properly control the amount of recursion that takes place,  consuming excessive resources, such as allocated memory or the program stack.
CVSS v3 base score: 7.5
Published: 2022-06-09T13:15Z
Repository: https://github.com/vapor/vapor
Language: Swift
Files changed: Sources/Vapor/URLEncodedForm/URLEncodedFormData.swift, Sources/Vapor/URLEncodedForm/URLEncodedFormError.swift, Sources/Vapor/URLEncodedForm/URLEncodedFormParser.swift, Tests/VaporTests/URLEncodedFormTests.swift
Commit date: 2022-06-06 17:36:34 +0100
Commit message: Merge pull request from GHSA-qvxg-wjxc-r4gg

* Add test for crash

* The sensible option

* Crude fix

* Throw an error if we hit a nesting limit

* Catch error in test
Diff: diff --git a/Sources/Vapor/URLEncodedForm/URLEncodedFormData.swift b/Sources/Vapor/URLEncodedForm/URLEncodedFormData.swift
index cf2ba8566..47da5c56a 100644
--- a/Sources/Vapor/URLEncodedForm/URLEncodedFormData.swift
+++ b/Sources/Vapor/URLEncodedForm/URLEncodedFormData.swift
@@ -52,10 +52,11 @@ enum URLQueryFragment: ExpressibleByStringLiteral, Equatable {
 
 /// Represents application/x-www-form-urlencoded encoded data.
 internal struct URLEncodedFormData: ExpressibleByArrayLiteral, ExpressibleByStringLiteral, ExpressibleByDictionaryLiteral, Equatable {
     var values: [URLQueryFragment]
     var children: [String: URLEncodedFormData]
+    let maxRecursionDepth = 100
     
     var hasOnlyValues: Bool {
         return children.count == 0
     }
     
@@ -88,20 +89,23 @@ internal struct URLEncodedFormData: ExpressibleByArrayLiteral, ExpressibleByStri
     init(dictionaryLiteral: (String, URLEncodedFormData)...) {
         self.values = []
         self.children = Dictionary(uniqueKeysWithValues: dictionaryLiteral)
     }
         
-    mutating func set(value: URLQueryFragment, forPath path: [String]) {
+    mutating func set(value: URLQueryFragment, forPath path: [String], recursionDepth: Int) throws {
+        guard recursionDepth <= maxRecursionDepth else {
+            throw URLEncodedFormError.reachedNestingLimit
+        }
         guard let firstElement = path.first else {
             self.values.append(value)
             return
         }
         var child: URLEncodedFormData
         if let existingChild = self.children[firstElement] {
             child = existingChild
         } else {
             child = []
         }
-        child.set(value: value, forPath: Array(path[1...]))
+        try child.set(value: value, forPath: Array(path[1...]), recursionDepth: recursionDepth + 1)
         self.children[firstElement] = child
     }
 }
diff --git a/Sources/Vapor/URLEncodedForm/URLEncodedFormError.swift b/Sources/Vapor/URLEncodedForm/URLEncodedFormError.swift
index 4ab3a6ffa..4a73749ba 100644
--- a/Sources/Vapor/URLEncodedForm/URLEncodedFormError.swift
+++ b/Sources/Vapor/URLEncodedForm/URLEncodedFormError.swift
@@ -1,8 +1,9 @@
 /// Errors thrown while encoding/decoding `application/x-www-form-urlencoded` data.
 enum URLEncodedFormError: Error {
     case malformedKey(key: Substring)
+    case reachedNestingLimit
 }
 
 extension URLEncodedFormError: AbortError {
     var status: HTTPResponseStatus {
         .badRequest
@@ -10,8 +11,10 @@ extension URLEncodedFormError: AbortError {
 
     var reason: String {
         switch self {
         case .malformedKey(let path):
             return "Malformed form-urlencoded key encountered: \(path)"
+        case .reachedNestingLimit:
+            return "The data supplied is too nested"
         }
     }
 }
diff --git a/Sources/Vapor/URLEncodedForm/URLEncodedFormParser.swift b/Sources/Vapor/URLEncodedForm/URLEncodedFormParser.swift
index f604a1e0d..aa98b9c26 100644
--- a/Sources/Vapor/URLEncodedForm/URLEncodedFormParser.swift
+++ b/Sources/Vapor/URLEncodedForm/URLEncodedFormParser.swift
@@ -12,15 +12,15 @@ internal struct URLEncodedFormParser {
                 omittingEmptySubsequences: false
             )
             switch kv.count {
             case 1:
                 let value = String(kv[0])
-                result.set(value: .urlEncoded(value), forPath: [])
+                try result.set(value: .urlEncoded(value), forPath: [], recursionDepth: 0)
             case 2:
                 let key = kv[0]
                 let value = String(kv[1])
-                result.set(value: .urlEncoded(value), forPath: try parseKey(key: Substring(key)))
+                try result.set(value: .urlEncoded(value), forPath: try parseKey(key: Substring(key)), recursionDepth: 0)
             default:
                 //Empty `&&`
                 continue
             }
         }
diff --git a/Tests/VaporTests/URLEncodedFormTests.swift b/Tests/VaporTests/URLEncodedFormTests.swift
index 4192e6982..558743a38 100644
--- a/Tests/VaporTests/URLEncodedFormTests.swift
+++ b/Tests/VaporTests/URLEncodedFormTests.swift
@@ -572,10 +572,19 @@ final class URLEncodedFormTests: XCTestCase {
         let data = try URLEncodedFormSerializer().serialize([
             "test": "&;!$'(),/:=?@~"
         ])
         XCTAssertEqual(data, "test=%26%3B!$'(),/:%3D%3F@~")
     }
+    
+    func testHeavilyNestedArray() throws {
+        var body = "x"
+        body += String(repeating: "[]", count: 80000)
+        body += "=y"
+        struct Foo: Content {}
+        XCTAssertThrowsError(try URLEncodedFormDecoder().decode(Foo.self, from: body))
+        XCTAssert(true, "We should not have crashed")
+    }
 }
 
 private struct User: Codable, Equatable {
     var name: String
     var age: Int

Vulnerable code:     mutating func set(value: URLQueryFragment, forPath path: [String]) {
        child.set(value: value, forPath: Array(path[1...]))
                result.set(value: .urlEncoded(value), forPath: [])
                result.set(value: .urlEncoded(value), forPath: try parseKey(key: Substring(key)))
Fixed code:     let maxRecursionDepth = 100
    mutating func set(value: URLQueryFragment, forPath path: [String], recursionDepth: Int) throws {
        guard recursionDepth <= maxRecursionDepth else {
            throw URLEncodedFormError.reachedNestingLimit
        }
        try child.set(value: value, forPath: Array(path[1...]), recursionDepth: recursionDepth + 1)
    case reachedNestingLimit
        case .reachedNestingLimit:
            return "The data supplied is too nested"
                try result.set(value: .urlEncoded(value), forPath: [], recursionDepth: 0)
                try result.set(value: .urlEncoded(value), forPath: try parseKey(key: Substring(key)), recursionDepth: 0)
    
    func testHeavilyNestedArray() throws {
        var body = "x"
        body += String(repeating: "[]", count: 80000)
        body += "=y"
        struct Foo: Content {}
        XCTAssertThrowsError(try URLEncodedFormDecoder().decode(Foo.self, from: body))
        XCTAssert(true, "We should not have crashed")
    }