Uploaded image for project: 'Undertow'
  1. Undertow
  2. UNDERTOW-1705

SslConduit is not thread-safe, resulting in NPE when server is stopped immediately after a response is sent

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: Major Major
    • None
    • 2.0.30.Final
    • SSL
    • 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]
      

              flaviarnn Flavia Rainone
              ankinson Andy Wilkinson (Inactive)
              Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

                Created:
                Updated: