Skip to content

Commit 2df54bc

Browse files
authored
Merge pull request from GHSA-9cfh-vx93-84vv
* Ensure empty incoming buffer when TLS negotiation starts * cleanup * Use real nio version
1 parent dd1b740 commit 2df54bc

File tree

7 files changed

+74
-28
lines changed

7 files changed

+74
-28
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ let package = Package(
1414
],
1515
dependencies: [
1616
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.1.0"),
17-
.package(url: "https://github.com/apple/swift-nio.git", from: "2.51.1"),
17+
.package(url: "https://github.com/apple/swift-nio.git", from: "2.52.0"),
1818
.package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.16.0"),
1919
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.23.1"),
2020
.package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "3.0.0"),

Sources/PostgresNIO/New/Connection State Machine/ConnectionStateMachine.swift

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,12 @@ struct ConnectionStateMachine {
228228
}
229229
}
230230

231-
mutating func sslSupportedReceived() -> ConnectionAction {
231+
mutating func sslSupportedReceived(unprocessedBytes: Int) -> ConnectionAction {
232232
switch self.state {
233233
case .sslRequestSent:
234+
if unprocessedBytes > 0 {
235+
return self.closeConnectionAndCleanup(.receivedUnencryptedDataAfterSSLRequest)
236+
}
234237
self.state = .sslNegotiated
235238
return .establishSSLConnection
236239

@@ -1079,9 +1082,18 @@ extension ConnectionStateMachine {
10791082
extension ConnectionStateMachine {
10801083
func shouldCloseConnection(reason error: PSQLError) -> Bool {
10811084
switch error.code.base {
1082-
case .sslUnsupported:
1083-
return true
1084-
case .failedToAddSSLHandler:
1085+
case .failedToAddSSLHandler,
1086+
.receivedUnencryptedDataAfterSSLRequest,
1087+
.sslUnsupported,
1088+
.messageDecodingFailure,
1089+
.unexpectedBackendMessage,
1090+
.unsupportedAuthMechanism,
1091+
.authMechanismRequiresPassword,
1092+
.saslError,
1093+
.tooManyParameters,
1094+
.invalidCommandTag,
1095+
.connectionError,
1096+
.uncleanShutdown:
10851097
return true
10861098
case .queryCancelled:
10871099
return false
@@ -1097,28 +1109,10 @@ extension ConnectionStateMachine {
10971109
}
10981110

10991111
return false
1100-
case .messageDecodingFailure:
1101-
return true
1102-
case .unexpectedBackendMessage:
1103-
return true
1104-
case .unsupportedAuthMechanism:
1105-
return true
1106-
case .authMechanismRequiresPassword:
1107-
return true
1108-
case .saslError:
1109-
return true
1110-
case .tooManyParameters:
1111-
return true
1112-
case .invalidCommandTag:
1113-
return true
11141112
case .connectionQuiescing:
11151113
preconditionFailure("Pure client error, that is thrown directly in PostgresConnection")
11161114
case .connectionClosed:
11171115
preconditionFailure("Pure client error, that is thrown directly and should never ")
1118-
case .connectionError:
1119-
return true
1120-
case .uncleanShutdown:
1121-
return true
11221116
}
11231117
}
11241118

Sources/PostgresNIO/New/PSQLError.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public struct PSQLError: Error {
77
enum Base: Sendable, Hashable {
88
case sslUnsupported
99
case failedToAddSSLHandler
10+
case receivedUnencryptedDataAfterSSLRequest
1011
case server
1112
case messageDecodingFailure
1213
case unexpectedBackendMessage
@@ -31,6 +32,7 @@ public struct PSQLError: Error {
3132

3233
public static let sslUnsupported = Self.init(.sslUnsupported)
3334
public static let failedToAddSSLHandler = Self(.failedToAddSSLHandler)
35+
public static let receivedUnencryptedDataAfterSSLRequest = Self(.receivedUnencryptedDataAfterSSLRequest)
3436
public static let server = Self(.server)
3537
public static let messageDecodingFailure = Self(.messageDecodingFailure)
3638
public static let unexpectedBackendMessage = Self(.unexpectedBackendMessage)
@@ -51,6 +53,8 @@ public struct PSQLError: Error {
5153
return "sslUnsupported"
5254
case .failedToAddSSLHandler:
5355
return "failedToAddSSLHandler"
56+
case .receivedUnencryptedDataAfterSSLRequest:
57+
return "receivedUnencryptedDataAfterSSLRequest"
5458
case .server:
5559
return "server"
5660
case .messageDecodingFailure:
@@ -343,6 +347,8 @@ public struct PSQLError: Error {
343347

344348
static var uncleanShutdown: PSQLError { PSQLError(code: .uncleanShutdown) }
345349

350+
static var receivedUnencryptedDataAfterSSLRequest: PSQLError { PSQLError(code: .receivedUnencryptedDataAfterSSLRequest) }
351+
346352
static func server(_ response: PostgresBackendMessage.ErrorResponse) -> PSQLError {
347353
var error = PSQLError(code: .server)
348354
error.serverInfo = .init(response)

Sources/PostgresNIO/New/PostgresChannelHandler.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ final class PostgresChannelHandler: ChannelDuplexHandler {
139139
case .rowDescription(let rowDescription):
140140
action = self.state.rowDescriptionReceived(rowDescription)
141141
case .sslSupported:
142-
action = self.state.sslSupportedReceived()
142+
action = self.state.sslSupportedReceived(unprocessedBytes: self.decoder.unprocessedBytes)
143143
case .sslUnsupported:
144144
action = self.state.sslUnsupportedReceived()
145145
}

Sources/PostgresNIO/Postgres+PSQLCompat.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ extension PSQLError {
3131
return PostgresError.protocol("Unsupported auth scheme: \(message)")
3232
case .authMechanismRequiresPassword:
3333
return PostgresError.protocol("Unable to authenticate without password")
34+
case .receivedUnencryptedDataAfterSSLRequest:
35+
return PostgresError.protocol("Received unencrypted data after SSL request")
3436
case .saslError:
3537
return self.underlying ?? self
3638
case .tooManyParameters, .invalidCommandTag:

Tests/PostgresNIOTests/New/Connection State Machine/ConnectionStateMachineTests.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,27 @@ class ConnectionStateMachineTests: XCTestCase {
1919
let authContext = AuthContext(username: "test", password: "abc123", database: "test")
2020
var state = ConnectionStateMachine(requireBackendKeyData: true)
2121
XCTAssertEqual(state.connected(tls: .require), .sendSSLRequest)
22-
XCTAssertEqual(state.sslSupportedReceived(), .establishSSLConnection)
22+
XCTAssertEqual(state.sslSupportedReceived(unprocessedBytes: 0), .establishSSLConnection)
2323
XCTAssertEqual(state.sslHandlerAdded(), .wait)
2424
XCTAssertEqual(state.sslEstablished(), .provideAuthenticationContext)
2525
XCTAssertEqual(state.provideAuthenticationContext(authContext), .sendStartupMessage(authContext))
2626
let salt: (UInt8, UInt8, UInt8, UInt8) = (0,1,2,3)
2727
XCTAssertEqual(state.authenticationMessageReceived(.md5(salt: salt)), .sendPasswordMessage(.md5(salt: salt), authContext))
2828
}
29-
29+
30+
func testSSLStartupFailureTooManyBytesRemaining() {
31+
var state = ConnectionStateMachine(requireBackendKeyData: true)
32+
XCTAssertEqual(state.connected(tls: .require), .sendSSLRequest)
33+
let failError = PSQLError.receivedUnencryptedDataAfterSSLRequest
34+
XCTAssertEqual(state.sslSupportedReceived(unprocessedBytes: 1), .closeConnectionAndCleanup(.init(action: .close, tasks: [], error: failError, closePromise: nil)))
35+
}
36+
3037
func testSSLStartupFailHandler() {
3138
struct SSLHandlerAddError: Error, Equatable {}
3239

3340
var state = ConnectionStateMachine(requireBackendKeyData: true)
3441
XCTAssertEqual(state.connected(tls: .require), .sendSSLRequest)
35-
XCTAssertEqual(state.sslSupportedReceived(), .establishSSLConnection)
42+
XCTAssertEqual(state.sslSupportedReceived(unprocessedBytes: 0), .establishSSLConnection)
3643
let failError = PSQLError.failedToAddSSLHandler(underlying: SSLHandlerAddError())
3744
XCTAssertEqual(state.errorHappened(failError), .closeConnectionAndCleanup(.init(action: .close, tasks: [], error: failError, closePromise: nil)))
3845
}

Tests/PostgresNIOTests/New/PostgresChannelHandlerTests.swift

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,44 @@ class PostgresChannelHandlerTests: XCTestCase {
7777
XCTAssertEqual(startupMessage.parameters.database, config.database)
7878
XCTAssertEqual(startupMessage.parameters.replication, .false)
7979
}
80-
80+
81+
func testEstablishSSLCallbackIsNotCalledIfSSLIsSupportedButAnotherMEssageIsSentAsWell() {
82+
var config = self.testConnectionConfiguration()
83+
XCTAssertNoThrow(config.tls = .require(try NIOSSLContext(configuration: .makeClientConfiguration())))
84+
var addSSLCallbackIsHit = false
85+
let handler = PostgresChannelHandler(configuration: config) { channel in
86+
addSSLCallbackIsHit = true
87+
}
88+
let eventHandler = TestEventHandler()
89+
let embedded = EmbeddedChannel(handlers: [
90+
ReverseByteToMessageHandler(PSQLFrontendMessageDecoder()),
91+
handler,
92+
eventHandler
93+
])
94+
95+
var maybeMessage: PostgresFrontendMessage?
96+
XCTAssertNoThrow(embedded.connect(to: try .init(ipAddress: "0.0.0.0", port: 5432), promise: nil))
97+
XCTAssertNoThrow(maybeMessage = try embedded.readOutbound(as: PostgresFrontendMessage.self))
98+
guard case .sslRequest(let request) = maybeMessage else {
99+
return XCTFail("Unexpected message")
100+
}
101+
102+
XCTAssertEqual(request.code, 80877103)
103+
104+
var responseBuffer = ByteBuffer()
105+
responseBuffer.writeInteger(UInt8(ascii: "S"))
106+
responseBuffer.writeInteger(UInt8(ascii: "1"))
107+
XCTAssertNoThrow(try embedded.writeInbound(responseBuffer))
108+
109+
XCTAssertFalse(addSSLCallbackIsHit)
110+
111+
// the event handler should have seen an error
112+
XCTAssertEqual(eventHandler.errors.count, 1)
113+
114+
// the connections should be closed
115+
XCTAssertFalse(embedded.isActive)
116+
}
117+
81118
func testSSLUnsupportedClosesConnection() throws {
82119
let config = self.testConnectionConfiguration(tls: .require(try NIOSSLContext(configuration: .makeClientConfiguration())))
83120

0 commit comments

Comments
 (0)