-
Bug
-
Resolution: Unresolved
-
Major
-
None
-
2.0.30.Final
-
None
SslConduit interacts with its wrappedData field in a non-thread-safe manner. This can result in a NullPointerException due to wrappedData being set to null in closed() while it's being accessed in doWrap. We see the NPE occur intermittently in Spring Boot's test suite in tests that use SSL during the following sequence of events:
1. Start Undertow
2. Make an HTTP GET request using https
3. Receive the response
4. Stop Undertow
I believe the failure occurs because there's a race condition. SslConduit contains the following logic:
if (wrappedData.getBuffer().hasRemaining()) { sink.write(wrappedData.getBuffer()); } //if it was not a complete write we just return if (wrappedData.getBuffer().hasRemaining()) { return result.bytesConsumed(); }
When the call to sink.write was a complete write, the response is received by the client. If Undertow is then stopped, this stop processing races with the remainder of doWrap's processing. Crucially, if the stop processing manages to call close() on the conduit, before the second hasRemaining() check is performed, the NPE will occur. The stack of the call to close() is the following:
Thread [XNIO-1 I/O-6] (Suspended (breakpoint at line 1066 in SslConduit)) SslConduit.closed() line: 1066 SslConduit.close() line: 1153 UndertowSslConnection.closeAction() line: 149 UndertowSslConnection(Connection).writeClosed() line: 115 UndertowSslConnection.writeClosed() line: 145 SslConduit.notifyWriteClosed() line: 604 SslConduit.truncateWrites() line: 517 HttpResponseConduit.truncateWrites() line: 776 ServerFixedLengthStreamSinkConduit(AbstractSinkConduit<D>).truncateWrites() line: 82 ServerFixedLengthStreamSinkConduit(AbstractFixedLengthStreamSinkConduit).truncateWrites() line: 277 ConduitStreamSinkChannel.close() line: 186 IoUtils.safeClose(Closeable) line: 134 WriteReadyHandler$ChannelListenerHandler<C>.forceTermination() line: 57 SslConduit$SslWriteReadyHandler.forceTermination() line: 1270 NioSocketConduit.forceTermination() line: 107 WorkerThread.run() line: 494
This results in the following NullPointerException:
java.lang.NullPointerException: null at io.undertow.protocols.ssl.SslConduit.doWrap(SslConduit.java:942) ~[undertow-core-2.0.30.Final.jar:2.0.30.Final] at io.undertow.protocols.ssl.SslConduit.write(SslConduit.java:393) ~[undertow-core-2.0.30.Final.jar:2.0.30.Final] at io.undertow.server.protocol.http.HttpResponseConduit.processWrite(HttpResponseConduit.java:251) ~[undertow-core-2.0.30.Final.jar:2.0.30.Final] at io.undertow.server.protocol.http.HttpResponseConduit.write(HttpResponseConduit.java:598) ~[undertow-core-2.0.30.Final.jar:2.0.30.Final] at io.undertow.server.protocol.http.HttpResponseConduit.transferFrom(HttpResponseConduit.java:685) ~[undertow-core-2.0.30.Final.jar:2.0.30.Final] at io.undertow.conduits.AbstractFixedLengthStreamSinkConduit.transferFrom(AbstractFixedLengthStreamSinkConduit.java:193) ~[undertow-core-2.0.30.Final.jar:2.0.30.Final] at org.xnio.conduits.ConduitStreamSinkChannel.transferFrom(ConduitStreamSinkChannel.java:142) ~[xnio-api-3.3.8.Final.jar:3.3.8.Final] at io.undertow.channels.DetachableStreamSinkChannel.transferFrom(DetachableStreamSinkChannel.java:127) ~[undertow-core-2.0.30.Final.jar:2.0.30.Final] at io.undertow.server.HttpServerExchange$WriteDispatchChannel.transferFrom(HttpServerExchange.java:2054) ~[undertow-core-2.0.30.Final.jar:2.0.30.Final] at org.xnio.channels.Channels.transferBlocking(Channels.java:512) ~[xnio-api-3.3.8.Final.jar:3.3.8.Final] at io.undertow.servlet.spec.ServletOutputStreamImpl.transferFrom(ServletOutputStreamImpl.java:535) ~[undertow-servlet-2.0.30.Final.jar:2.0.30.Final] at io.undertow.io.BlockingSenderImpl.performTransfer(BlockingSenderImpl.java:187) ~[undertow-core-2.0.30.Final.jar:2.0.30.Final] at io.undertow.io.BlockingSenderImpl.transferFrom(BlockingSenderImpl.java:180) ~[undertow-core-2.0.30.Final.jar:2.0.30.Final] at io.undertow.server.handlers.resource.PathResource$1TransferTask.run(PathResource.java:228) ~[undertow-core-2.0.30.Final.jar:2.0.30.Final] at io.undertow.server.handlers.resource.PathResource.serveImpl(PathResource.java:258) ~[undertow-core-2.0.30.Final.jar:2.0.30.Final] at io.undertow.server.handlers.resource.PathResource.serve(PathResource.java:114) ~[undertow-core-2.0.30.Final.jar:2.0.30.Final] at io.undertow.servlet.handlers.DefaultServlet.serveFileBlocking(DefaultServlet.java:360) ~[undertow-servlet-2.0.30.Final.jar:2.0.30.Final] at io.undertow.servlet.handlers.DefaultServlet.doGet(DefaultServlet.java:201) ~[undertow-servlet-2.0.30.Final.jar:2.0.30.Final] at javax.servlet.http.HttpServlet.service(HttpServlet.java:645) ~[javax.servlet-api-4.0.1.jar:4.0.1] at javax.servlet.http.HttpServlet.service(HttpServlet.java:750) ~[javax.servlet-api-4.0.1.jar:4.0.1] at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74) ~[undertow-servlet-2.0.30.Final.jar:2.0.30.Final] at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62) ~[undertow-servlet-2.0.30.Final.jar:2.0.30.Final] at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68) ~[undertow-servlet-2.0.30.Final.jar:2.0.30.Final] at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) ~[undertow-servlet-2.0.30.Final.jar:2.0.30.Final] at io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68) ~[undertow-servlet-2.0.30.Final.jar:2.0.30.Final] at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132) ~[undertow-servlet-2.0.30.Final.jar:2.0.30.Final] at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57) ~[undertow-servlet-2.0.30.Final.jar:2.0.30.Final] at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) ~[undertow-core-2.0.30.Final.jar:2.0.30.Final] at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46) ~[undertow-core-2.0.30.Final.jar:2.0.30.Final] at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64) ~[undertow-servlet-2.0.30.Final.jar:2.0.30.Final] at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60) ~[undertow-core-2.0.30.Final.jar:2.0.30.Final] at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77) ~[undertow-servlet-2.0.30.Final.jar:2.0.30.Final] at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43) ~[undertow-core-2.0.30.Final.jar:2.0.30.Final] at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) ~[undertow-core-2.0.30.Final.jar:2.0.30.Final] at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) ~[undertow-core-2.0.30.Final.jar:2.0.30.Final] at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:269) [undertow-servlet-2.0.30.Final.jar:2.0.30.Final] at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:78) [undertow-servlet-2.0.30.Final.jar:2.0.30.Final] at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:133) [undertow-servlet-2.0.30.Final.jar:2.0.30.Final] at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:130) [undertow-servlet-2.0.30.Final.jar:2.0.30.Final] at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48) [undertow-servlet-2.0.30.Final.jar:2.0.30.Final] at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43) [undertow-servlet-2.0.30.Final.jar:2.0.30.Final] at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:249) [undertow-servlet-2.0.30.Final.jar:2.0.30.Final] at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:78) [undertow-servlet-2.0.30.Final.jar:2.0.30.Final] at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:99) [undertow-servlet-2.0.30.Final.jar:2.0.30.Final] at io.undertow.server.Connectors.executeRootHandler(Connectors.java:376) [undertow-core-2.0.30.Final.jar:2.0.30.Final] at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830) [undertow-core-2.0.30.Final.jar:2.0.30.Final] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_202] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_202] at java.lang.Thread.run(Thread.java:748) [?:1.8.0_202]