Skip to content

Commit e1f6ac1

Browse files
authored
Bug fix to replace uncleanShutdown with handshakeFailed during TLS failure. (#287)
Motivation: To address #253 Modifications: When channelInactive is called during a handshake failure it currently defaults to uncleanShutdown. This bugfix and test is to report handshakeFailed instead of uncleanShutdown. Added the handshaking case to the channelInactive function to report handshakeFailed. Added a new test case, testChannelInactiveDuringHandshake, in UnwrappingTests to test this fix. Result: Fix of #253
1 parent 955f3d2 commit e1f6ac1

File tree

3 files changed

+87
-9
lines changed

3 files changed

+87
-9
lines changed

Sources/NIOSSL/NIOSSLHandler.swift

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,25 +98,35 @@ public class NIOSSLHandler : ChannelInboundHandler, ChannelOutboundHandler, Remo
9898
// keeping track of the state we're in properly before we do anything else.
9999
let oldState = state
100100
state = .closed
101+
let channelError: NIOSSLError
101102

102103
switch oldState {
103104
case .closed, .idle:
104105
// Nothing to do, but discard any buffered writes we still have.
105106
discardBufferedWrites(reason: ChannelError.ioOnClosedChannel)
107+
// Return early
108+
context.fireChannelInactive()
109+
return
110+
case .handshaking:
111+
// In this case the channel is going through the doHandshake steps and
112+
// a channelInactive is fired taking down the connection.
113+
// This case propogates a .handshakeFailed instead of an .uncleanShutdown.
114+
channelError = NIOSSLError.handshakeFailed(.sslError(BoringSSLError.buildErrorStack()))
106115
default:
107116
// This is a ragged EOF: we weren't sent a CLOSE_NOTIFY. We want to send a user
108117
// event to notify about this before we propagate channelInactive. We also want to fail all
109118
// these writes.
110-
let shutdownPromise = self.shutdownPromise
111-
self.shutdownPromise = nil
112-
let closePromise = self.closePromise
113-
self.closePromise = nil
114-
115-
shutdownPromise?.fail(NIOSSLError.uncleanShutdown)
116-
closePromise?.fail(NIOSSLError.uncleanShutdown)
117-
context.fireErrorCaught(NIOSSLError.uncleanShutdown)
118-
discardBufferedWrites(reason: NIOSSLError.uncleanShutdown)
119+
channelError = NIOSSLError.uncleanShutdown
119120
}
121+
let shutdownPromise = self.shutdownPromise
122+
self.shutdownPromise = nil
123+
let closePromise = self.closePromise
124+
self.closePromise = nil
125+
126+
shutdownPromise?.fail(channelError)
127+
closePromise?.fail(channelError)
128+
context.fireErrorCaught(channelError)
129+
discardBufferedWrites(reason: channelError)
120130

121131
context.fireChannelInactive()
122132
}

Tests/NIOSSLTests/UnwrappingTests+XCTest.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ extension UnwrappingTests {
4343
("testUnwrappingTimeout", testUnwrappingTimeout),
4444
("testSuccessfulUnwrapCancelsTimeout", testSuccessfulUnwrapCancelsTimeout),
4545
("testUnwrappingAndClosingShareATimeout", testUnwrappingAndClosingShareATimeout),
46+
("testChannelInactiveDuringHandshake", testChannelInactiveDuringHandshake),
4647
]
4748
}
4849
}

Tests/NIOSSLTests/UnwrappingTests.swift

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -963,4 +963,71 @@ final class UnwrappingTests: XCTestCase {
963963
XCTAssertTrue(serverClosed)
964964
XCTAssertTrue(unwrapped)
965965
}
966+
967+
func testChannelInactiveDuringHandshake() throws {
968+
969+
let serverChannel = EmbeddedChannel()
970+
let clientChannel = EmbeddedChannel()
971+
972+
var serverClosed = false
973+
var serverUnwrapped = false
974+
defer {
975+
// The errors here are expected
976+
XCTAssertThrowsError(try serverChannel.finish())
977+
XCTAssertThrowsError(try clientChannel.finish())
978+
}
979+
980+
let context = try assertNoThrowWithValue(configuredSSLContext())
981+
let serverHandler = try assertNoThrowWithValue(NIOSSLServerHandler(context: context))
982+
let clientHandler = try assertNoThrowWithValue(NIOSSLClientHandler(context: context, serverHostname: nil))
983+
XCTAssertNoThrow(try serverChannel.pipeline.addHandler(NIOSSLServerHandler(context: context)).wait())
984+
XCTAssertNoThrow(try clientChannel.pipeline.addHandler(clientHandler).wait())
985+
let handshakeHandler = HandshakeCompletedHandler()
986+
XCTAssertNoThrow(try clientChannel.pipeline.addHandler(handshakeHandler).wait())
987+
988+
serverChannel.closeFuture.whenComplete { _ in
989+
serverClosed = true
990+
}
991+
992+
// Place the guts of connectInMemory here to abruptly alter the handshake process
993+
let addr = try assertNoThrowWithValue(SocketAddress(unixDomainSocketPath: "/tmp/whatever2"))
994+
let _ = clientChannel.connect(to: addr)
995+
996+
XCTAssertFalse(serverClosed)
997+
998+
serverChannel.pipeline.fireChannelActive()
999+
clientChannel.pipeline.fireChannelActive()
1000+
// doHandshakeStep process should start here out in NIOSSLHandler before fireChannelInactive
1001+
serverChannel.pipeline.fireChannelInactive()
1002+
clientChannel.pipeline.fireChannelInactive()
1003+
1004+
// Need to test this error as a BoringSSLError because that means success instead of an uncleanShutdown
1005+
do {
1006+
try interactInMemory(clientChannel: clientChannel, serverChannel: serverChannel)
1007+
} catch {
1008+
switch error as? NIOSSLError {
1009+
case .some(.handshakeFailed):
1010+
// Expected to fall into .handshakeFailed
1011+
break
1012+
default:
1013+
XCTFail("Unexpected error: \(error)")
1014+
}
1015+
}
1016+
clientHandler.stopTLS(promise: nil)
1017+
1018+
// Go through the process of closing and verifying the close on the server side.
1019+
XCTAssertFalse(serverUnwrapped)
1020+
1021+
let serverStopPromise: EventLoopPromise<Void> = serverChannel.eventLoop.makePromise()
1022+
serverStopPromise.futureResult.whenComplete { _ in
1023+
serverUnwrapped = true
1024+
}
1025+
serverHandler.stopTLS(promise: serverStopPromise)
1026+
XCTAssertNoThrow(try interactInMemory(clientChannel: clientChannel, serverChannel: serverChannel))
1027+
1028+
(serverChannel.eventLoop as! EmbeddedEventLoop).run()
1029+
1030+
XCTAssertTrue(serverClosed)
1031+
XCTAssertTrue(serverUnwrapped)
1032+
}
9661033
}

0 commit comments

Comments
 (0)