VYPR
High severityNVD Advisory· Published May 31, 2022· Updated Apr 22, 2025

Integer Overflow in Vapor's HTTP Range Request

CVE-2022-31005

Description

Vapor FileMiddleware with integer overflow in HTTP range parsing leads to denial of service; fixed in 4.60.3.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Vapor FileMiddleware with integer overflow in HTTP range parsing leads to denial of service; fixed in 4.60.3.

Vulnerability

Vapor versions prior to 4.60.3 with FileMiddleware enabled contain an integer overflow vulnerability when processing HTTP Range headers [1][4]. Crafted range values such as bytes=0-9223372036854775807 cause an integer overflow, crashing the application [2][4]. This affects all Vapor apps using FileMiddleware without the patch.

Exploitation

An attacker only needs network access to send a single HTTP request with a malicious Range header [4]. The request can target any file served by FileMiddleware. No authentication or user interaction is required. The server then crashes as the range parsing overflow leads to undefined behavior [2].

Impact

Successful exploitation causes a denial of service (DoS) by crashing the application process [1][4]. The application becomes unavailable until restarted. No data integrity or confidentiality is compromised; the impact is limited to availability.

Mitigation

Vapor version 4.60.3 patches the vulnerability by properly validating range headers and returning HTTP 400 for invalid ranges [2][3]. Users should upgrade immediately. As a workaround, disable FileMiddleware and serve static files via a Content Delivery Network [1][4].

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/vapor/vaporSwiftURL
< 4.60.34.60.3

Affected products

2

Patches

1
953a349b539b

Merge pull request from GHSA-vj2m-9f5j-mpr5

https://github.com/vapor/vaporTim CondonMay 31, 2022via ghsa
2 files changed · +82 3
  • Sources/Vapor/Utilities/FileIO.swift+31 3 modified
    @@ -1,4 +1,5 @@
     import NIO
    +import Logging
     
     extension Request {
         public var fileio: FileIO {
    @@ -138,6 +139,11 @@ public struct FileIO {
                 } else {
                     contentRange = nil
                 }
    +        } else if request.headers.contains(name: .range) {
    +            // Range header was supplied but could not be parsed i.e. it was invalid
    +            request.logger.debug("Range header was provided in request but was invalid")
    +            let response = Response(status: .badRequest)
    +            return response
             } else {
                 contentRange = nil
             }
    @@ -163,7 +169,12 @@ public struct FileIO {
                 if let firstRange = contentRange.ranges.first {
                     let range = firstRange.asResponseContentRange(limit: fileSize)
                     response.headers.contentRange = HTTPHeaders.ContentRange(unit: contentRange.unit, range: range)
    -                (offset, byteCount) = firstRange.asByteBufferBounds(withMaxSize: fileSize)
    +                do {
    +                    (offset, byteCount) = try firstRange.asByteBufferBounds(withMaxSize: fileSize, logger: request.logger)
    +                } catch {
    +                    let response = Response(status: .badRequest)
    +                    return response
    +                }
                 } else {
                     offset = 0
                     byteCount = fileSize
    @@ -252,14 +263,31 @@ public struct FileIO {
     
     extension HTTPHeaders.Range.Value {
         
    -    fileprivate func asByteBufferBounds(withMaxSize size: Int) -> (offset: Int64, byteCount: Int) {
    +    fileprivate func asByteBufferBounds(withMaxSize size: Int, logger: Logger) throws -> (offset: Int64, byteCount: Int) {
             switch self {
                 case .start(let value):
    +                guard value <= size, value >= 0 else {
    +                    logger.debug("Requested range start was invalid: \(value)")
    +                    throw Abort(.badRequest)
    +                }
                     return (offset: numericCast(value), byteCount: size - value)
                 case .tail(let value):
    +                guard value <= size, value >= 0 else {
    +                    logger.debug("Requested range end was invalid: \(value)")
    +                    throw Abort(.badRequest)
    +                }
                     return (offset: numericCast(size - value), byteCount: value)
                 case .within(let start, let end):
    -                return (offset: numericCast(start), byteCount: end - start + 1)
    +                guard start >= 0, end >= 0, start < end else {
    +                    logger.debug("Requested range was invalid: \(start)-\(end)")
    +                    throw Abort(.badRequest)
    +                }
    +                let (byteCount, overflow) =  (end - start).addingReportingOverflow(1)
    +                guard !overflow else {
    +                    logger.debug("Requested range was invalid: \(start)-\(end)")
    +                    throw Abort(.badRequest)
    +                }
    +                return (offset: numericCast(start), byteCount: byteCount)
             }
         }
     }
    
  • Tests/VaporTests/FileTests.swift+51 0 modified
    @@ -246,4 +246,55 @@ final class FileTests: XCTestCase {
                 XCTAssertEqual(res.status, .notFound)
             }
         }
    +    
    +    // https://github.com/vapor/vapor/security/advisories/GHSA-vj2m-9f5j-mpr5
    +    func testInvalidRangeHeaderDoesNotCrash() throws {
    +        let app = Application(.testing)
    +        defer { app.shutdown() }
    +
    +        app.get("file-stream") { req in
    +            return req.fileio.streamFile(at: #file)
    +        }
    +
    +        var headers = HTTPHeaders()
    +        headers.replaceOrAdd(name: .range, value: "bytes=0-9223372036854775807")
    +        try app.testable(method: .running).test(.GET, "/file-stream", headers: headers) { res in
    +            XCTAssertEqual(res.status, .badRequest)
    +        }
    +        
    +        headers.replaceOrAdd(name: .range, value: "bytes=-1-10")
    +        try app.testable(method: .running).test(.GET, "/file-stream", headers: headers) { res in
    +            XCTAssertEqual(res.status, .badRequest)
    +        }
    +        
    +        headers.replaceOrAdd(name: .range, value: "bytes=100-10")
    +        try app.testable(method: .running).test(.GET, "/file-stream", headers: headers) { res in
    +            XCTAssertEqual(res.status, .badRequest)
    +        }
    +        
    +        headers.replaceOrAdd(name: .range, value: "bytes=10--100")
    +        try app.testable(method: .running).test(.GET, "/file-stream", headers: headers) { res in
    +            XCTAssertEqual(res.status, .badRequest)
    +        }
    +        
    +        headers.replaceOrAdd(name: .range, value: "bytes=9223372036854775808-")
    +        try app.testable(method: .running).test(.GET, "/file-stream", headers: headers) { res in
    +            XCTAssertEqual(res.status, .badRequest)
    +        }
    +        
    +        headers.replaceOrAdd(name: .range, value: "bytes=922337203-")
    +        try app.testable(method: .running).test(.GET, "/file-stream", headers: headers) { res in
    +            XCTAssertEqual(res.status, .badRequest)
    +        }
    +        
    +        headers.replaceOrAdd(name: .range, value: "bytes=-922337203")
    +        try app.testable(method: .running).test(.GET, "/file-stream", headers: headers) { res in
    +            XCTAssertEqual(res.status, .badRequest)
    +        }
    +        
    +        headers.replaceOrAdd(name: .range, value: "bytes=-9223372036854775808")
    +        try app.testable(method: .running).test(.GET, "/file-stream", headers: headers) { res in
    +            XCTAssertEqual(res.status, .badRequest)
    +        }
    +    }
     }
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.