Skip to content

Commit acdd3b4

Browse files
authored
Add a decompression limit (#729)
Motivation: Compression can be an attack vector for maclicious users. Specifically, bad actors may send 'zip-bombs' which have very high compression ratios which may consume a large amount of memory when decompressed. Modifications: - Intoruce a `DecompressionLimit` which has two options, an `absolute` (an absolute size limit for a decompressed message), and `ratio` (based on the compression ratio of the message). - Add decompression limits to server and client compression configuration - Explicitly make compression `enabled` or `disabled` Result: Easier to protect against zip-bombs
1 parent 6124259 commit acdd3b4

26 files changed

+766
-261
lines changed

Package.resolved

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
"repositoryURL": "https://github.com/apple/swift-nio.git",
1616
"state": {
1717
"branch": null,
18-
"revision": "4409b57d4c0c40d41ac2b320fccf02e4d451e3db",
19-
"version": "2.13.0"
18+
"revision": "16ab4d657e1ad4e77bd5f8b94af8538561643053",
19+
"version": "2.14.0"
2020
}
2121
},
2222
{
@@ -33,8 +33,8 @@
3333
"repositoryURL": "https://github.com/apple/swift-nio-ssl.git",
3434
"state": {
3535
"branch": null,
36-
"revision": "cf54f5c1db1c3740a6c7d662dc8569c150c3846c",
37-
"version": "2.6.0"
36+
"revision": "af46d9b58fafbb76f9b01177568d435a1b024f99",
37+
"version": "2.6.2"
3838
}
3939
},
4040
{

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ let package = Package(
3030
dependencies: [
3131
// GRPC dependencies:
3232
// Main SwiftNIO package
33-
.package(url: "https://github.com/apple/swift-nio.git", from: "2.13.0"),
33+
.package(url: "https://github.com/apple/swift-nio.git", from: "2.14.0"),
3434
// HTTP2 via SwiftNIO
3535
.package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.8.0"),
3636
// TLS via SwiftNIO
37-
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.6.0"),
37+
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.6.2"),
3838
// Support for Network.framework where possible.
3939
.package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.3.0"),
4040

Sources/GRPC/ClientOptions.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public struct CallOptions {
3636
///
3737
/// Note that enabling `compression` via the `sendMessage` or `sendMessages` methods only applies
3838
/// if encoding has been specified in these options.
39-
public var messageEncoding: MessageEncoding
39+
public var messageEncoding: ClientMessageEncoding
4040

4141
/// Whether the call is cacheable.
4242
public var cacheable: Bool
@@ -61,7 +61,7 @@ public struct CallOptions {
6161
public init(
6262
customMetadata: HPACKHeaders = HPACKHeaders(),
6363
timeout: GRPCTimeout = GRPCTimeout.infinite,
64-
messageEncoding: MessageEncoding = .none,
64+
messageEncoding: ClientMessageEncoding = .disabled,
6565
requestIDProvider: RequestIDProvider = .autogenerated,
6666
requestIDHeader: String? = nil,
6767
cacheable: Bool = false
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2020, gRPC Authors All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
public struct DecompressionLimit: Equatable {
18+
private enum Limit: Equatable {
19+
case ratio(Int)
20+
case absolute(Int)
21+
}
22+
private let limit: Limit
23+
24+
/// Limits decompressed payloads to be no larger than the product of the compressed size
25+
/// and `ratio`.
26+
///
27+
/// - Parameter ratio: The decompression ratio.
28+
/// - Precondition: `ratio` must be greater than zero.
29+
public static func ratio(_ ratio: Int) -> DecompressionLimit {
30+
precondition(ratio > 0, "ratio must be greater than zero")
31+
return DecompressionLimit(limit: .ratio(ratio))
32+
}
33+
34+
/// Limits decompressed payloads to be no larger than the given `size`.
35+
///
36+
/// - Parameter size: The absolute size limit of decompressed payloads.
37+
/// - Precondition: `size` must not be negative.
38+
public static func absolute(_ size: Int) -> DecompressionLimit {
39+
precondition(size >= 0, "absolute size must be non-negative")
40+
return DecompressionLimit(limit: .absolute(size))
41+
}
42+
}
43+
44+
extension DecompressionLimit {
45+
/// The largest allowed decompressed size for this limit.
46+
func maximumDecompressedSize(compressedSize: Int) -> Int {
47+
switch self.limit {
48+
case .ratio(let ratio):
49+
return ratio * compressedSize
50+
case .absolute(let size):
51+
return size
52+
}
53+
}
54+
}

Sources/GRPC/Compression/MessageEncoding.swift

Lines changed: 64 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
* limitations under the License.
1515
*/
1616

17-
1817
/// Whether compression should be enabled for the message.
1918
public enum Compression {
2019
/// Enable compression. Note that this will be ignored if compression has not been enabled or is
@@ -42,14 +41,32 @@ extension Compression {
4241
}
4342
}
4443

45-
extension CallOptions {
46-
public struct MessageEncoding {
44+
public enum ClientMessageEncoding {
45+
case enabled(Configuration)
46+
case disabled
47+
}
48+
49+
extension ClientMessageEncoding {
50+
var enabledForRequests: Bool {
51+
switch self {
52+
case .enabled(let configuration):
53+
return configuration.outbound != nil
54+
case .disabled:
55+
return false
56+
}
57+
}
58+
}
59+
60+
extension ClientMessageEncoding {
61+
public struct Configuration {
4762
public init(
4863
forRequests outbound: CompressionAlgorithm?,
49-
acceptableForResponses inbound: [CompressionAlgorithm] = CompressionAlgorithm.all
64+
acceptableForResponses inbound: [CompressionAlgorithm] = CompressionAlgorithm.all,
65+
decompressionLimit: DecompressionLimit
5066
) {
5167
self.outbound = outbound
5268
self.inbound = inbound
69+
self.decompressionLimit = decompressionLimit
5370
}
5471

5572
/// The compression algorithm used for outbound messages.
@@ -58,47 +75,57 @@ extension CallOptions {
5875
/// The set of compression algorithms advertised to the remote peer that they may use.
5976
public var inbound: [CompressionAlgorithm]
6077

61-
/// No compression.
62-
public static let none = MessageEncoding(
63-
forRequests: nil,
64-
acceptableForResponses: []
65-
)
78+
/// The decompression limit acceptable for responses. RPCs which receive a message whose
79+
/// decompressed size exceeds the limit will be cancelled.
80+
public var decompressionLimit: DecompressionLimit
6681

6782
/// Accept all supported compression on responses, do not compress requests.
68-
public static let responsesOnly = MessageEncoding(
69-
forRequests: .identity,
70-
acceptableForResponses: CompressionAlgorithm.all
71-
)
72-
73-
/// Whether compression is enabled for requests.
74-
internal var enabledForRequests: Bool {
75-
return self.outbound != nil
83+
public static func responsesOnly(
84+
acceptable: [CompressionAlgorithm] = CompressionAlgorithm.all,
85+
decompressionLimit: DecompressionLimit
86+
) -> Configuration {
87+
return Configuration(
88+
forRequests: .identity,
89+
acceptableForResponses: acceptable,
90+
decompressionLimit: decompressionLimit
91+
)
7692
}
77-
}
78-
}
7993

80-
extension CallOptions.MessageEncoding {
81-
var acceptEncodingHeader: String {
82-
return self.inbound.map { $0.name }.joined(separator: ",")
94+
internal var acceptEncodingHeader: String {
95+
return self.inbound.map { $0.name }.joined(separator: ",")
96+
}
8397
}
8498
}
8599

86-
extension Server.Configuration {
87-
public struct MessageEncoding {
88-
/// The set of compression algorithms advertised that we will accept from clients. Note that
89-
/// clients may send us messages compressed with algorithms not included in this list; if we
90-
/// support it then we still accept the message.
91-
public var enabled: [CompressionAlgorithm]
100+
public enum ServerMessageEncoding {
101+
case enabled(Configuration)
102+
case disabled
103+
}
92104

93-
public init(enabled: [CompressionAlgorithm]) {
94-
self.enabled = enabled
105+
extension ServerMessageEncoding {
106+
public struct Configuration {
107+
/// The set of compression algorithms advertised that we will accept from clients for requests.
108+
/// Note that clients may send us messages compressed with algorithms not included in this list;
109+
/// if we support it then we still accept the message.
110+
///
111+
/// All cases of `CompressionAlgorithm` are supported.
112+
public var enabledAlgorithms: [CompressionAlgorithm]
113+
114+
/// The decompression limit acceptable for requests. RPCs which receive a message whose
115+
/// decompressed size exceeds the limit will be cancelled.
116+
public var decompressionLimit: DecompressionLimit
117+
118+
/// Create a configuration for server message encoding.
119+
///
120+
/// - Parameters:
121+
/// - enabledAlgorithms: The list of algorithms which are enabled.
122+
/// - decompressionLimit: Decompression limit acceptable for requests.
123+
public init(
124+
enabledAlgorithms: [CompressionAlgorithm] = CompressionAlgorithm.all,
125+
decompressionLimit: DecompressionLimit
126+
) {
127+
self.enabledAlgorithms = enabledAlgorithms
128+
self.decompressionLimit = decompressionLimit
95129
}
96-
97-
// All supported algorithms are enabled.
98-
public static let enabled = MessageEncoding(enabled: CompressionAlgorithm.all)
99-
100-
/// No compression.
101-
public static let none = MessageEncoding(enabled: [.identity])
102130
}
103-
104131
}

0 commit comments

Comments
 (0)