This is an automated email from the ASF dual-hosted git repository.

rmaucher pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit 2db32e5b28c6d6ef9d4684b29f8a3272376f5899
Author: opencode <[email protected]>
AuthorDate: Wed Oct 7 15:10:56 2026 +0200

    Track StartTlsResponse per connection in JNDIRealm
    
    JNDIRealm stored the StartTlsResponse of the most recently negotiated
    TLS session in a single realm-wide, non-volatile field. With
    connectionPoolSize greater than one and useStartTls enabled, every
    open() overwrote the reference and close() of any connection closed
    whatever session the field happened to reference. Closing one pooled
    connection - pool overflow in release(), failover paths, or stop -
    therefore terminated the TLS session of a different, still active
    pooled connection, causing spurious authentication failures, while
    superseded sessions leaked. Concurrent open() calls also raced on the
    non-volatile field.
    
    Store the StartTlsResponse on the JNDIConnection that negotiated it
    and close only the connection's own response when that connection is
    closed. The response is now recorded only after negotiate() succeeds,
    so a failed negotiation cannot leave a stale reference behind.
    
    Include a changelog entry.
---
 java/org/apache/catalina/realm/JNDIRealm.java | 48 ++++++++++++++++-----------
 webapps/docs/changelog.xml                    |  7 ++++
 2 files changed, 36 insertions(+), 19 deletions(-)

diff --git a/java/org/apache/catalina/realm/JNDIRealm.java 
b/java/org/apache/catalina/realm/JNDIRealm.java
index e664bf6e03..e79a56ec8f 100644
--- a/java/org/apache/catalina/realm/JNDIRealm.java
+++ b/java/org/apache/catalina/realm/JNDIRealm.java
@@ -343,8 +343,6 @@ public class JNDIRealm extends RealmBase {
      */
     private boolean useStartTls = false;
 
-    private StartTlsResponse tls = null;
-
     /**
      * The list of enabled cipher suites used for establishing tls 
connections. <code>null</code> means to use the
      * default cipher suites.
@@ -2386,11 +2384,13 @@ public class JNDIRealm extends RealmBase {
         }
 
         // Close tls startResponse if used
-        if (tls != null) {
+        if (connection.tls != null) {
             try {
-                tls.close();
+                connection.tls.close();
             } catch (IOException ioe) {
                 containerLog.error(sm.getString("jndiRealm.tlsClose"), ioe);
+            } finally {
+                connection.tls = null;
             }
         }
         // Close our opened connection
@@ -2711,7 +2711,7 @@ public class JNDIRealm extends RealmBase {
     protected void open(JNDIConnection connection) throws NamingException {
         try {
             // Ensure that we have a directory context available
-            connection.context = 
createDirContext(getDirectoryContextEnvironment());
+            connection.context = createDirContext(connection, 
getDirectoryContextEnvironment());
         } catch (Exception e) {
             if (alternateURL == null || alternateURL.isEmpty()) {
                 // No alternate URL. Re-throw the exception.
@@ -2721,7 +2721,7 @@ public class JNDIRealm extends RealmBase {
             // log the first exception.
             containerLog.info(sm.getString("jndiRealm.exception.retry"), e);
             // Try connecting to the alternate url.
-            connection.context = 
createDirContext(getDirectoryContextEnvironment());
+            connection.context = createDirContext(connection, 
getDirectoryContextEnvironment());
         } finally {
             // reset it in case the connection times out.
             // the primary may come back.
@@ -2737,9 +2737,10 @@ public class JNDIRealm extends RealmBase {
     }
 
 
-    private DirContext createDirContext(Hashtable<String,String> env) throws 
NamingException {
+    private DirContext createDirContext(JNDIConnection connection, 
Hashtable<String,String> env)
+            throws NamingException {
         if (useStartTls) {
-            return createTlsDirContext(env);
+            return createTlsDirContext(connection, env);
         } else {
             return new InitialDirContext(env);
         }
@@ -2794,15 +2795,17 @@ public class JNDIRealm extends RealmBase {
 
 
     /**
-     * Create a tls enabled LdapContext and set the StartTlsResponse tls 
instance variable.
+     * Create a tls enabled LdapContext and set the StartTlsResponse tls 
instance field of the connection.
      *
-     * @param env Environment to use for context creation
+     * @param connection The directory server connection wrapper
+     * @param env        Environment to use for context creation
      *
      * @return configured {@link LdapContext}
      *
      * @throws NamingException when something goes wrong while negotiating the 
connection
      */
-    private DirContext createTlsDirContext(Hashtable<String,String> env) 
throws NamingException {
+    private DirContext createTlsDirContext(JNDIConnection connection, 
Hashtable<String,String> env)
+            throws NamingException {
         Map<String,Object> savedEnv = new HashMap<>();
         for (String key : Arrays.asList(Context.SECURITY_AUTHENTICATION, 
Context.SECURITY_CREDENTIALS,
                 Context.SECURITY_PRINCIPAL, Context.SECURITY_PROTOCOL)) {
@@ -2814,16 +2817,17 @@ public class JNDIRealm extends RealmBase {
         LdapContext result = null;
         try {
             result = new InitialLdapContext(env, null);
-            tls = (StartTlsResponse) result.extendedOperation(new 
StartTlsRequest());
-            if (getHostnameVerifier() != null) {
-                tls.setHostnameVerifier(getHostnameVerifier());
-            }
-            if (getCipherSuitesArray() != null) {
-                tls.setEnabledCipherSuites(getCipherSuitesArray());
-            }
+            StartTlsResponse tls = (StartTlsResponse) 
result.extendedOperation(new StartTlsRequest());
             try {
+                if (getHostnameVerifier() != null) {
+                    tls.setHostnameVerifier(getHostnameVerifier());
+                }
+                if (getCipherSuitesArray() != null) {
+                    tls.setEnabledCipherSuites(getCipherSuitesArray());
+                }
                 SSLSession negotiate = tls.negotiate(getSSLSocketFactory());
                 containerLog.debug(sm.getString("jndiRealm.negotiatedTls", 
negotiate.getProtocol()));
+                connection.tls = tls;
             } catch (IOException ioe) {
                 NamingException ne = new NamingException(ioe.getMessage());
                 ne.initCause(ioe);
@@ -3386,6 +3390,12 @@ public class JNDIRealm extends RealmBase {
         /**
          * The directory context linking us to our directory server.
          */
-        public DirContext context = null;
+        public volatile DirContext context = null;
+
+        /**
+         * The {@link StartTlsResponse} negotiated for this connection, if any.
+         */
+        public volatile StartTlsResponse tls = null;
+
     }
 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 3f62d69765..e4dc641b61 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -1886,6 +1886,13 @@
         Ensure URL encoding errors in the Rewrite Valve trigger an exception
         rather than silently using a replacement character. (markt)
       </fix>
+      <fix>
+        Track the <code>StartTlsResponse</code> used by <code>JNDIRealm</code>
+        per connection rather than in a single realm-wide field, so that
+        closing one pooled connection when <code>useStartTls</code> is enabled
+        no longer terminates the TLS session of another, still active pooled
+        connection. (remm)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Coyote">


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to