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

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

commit 413d82fab675d43653bda5513ae7cfc777c80c2c
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Thu Feb 20 16:45:02 2025 +0000

    Improve CVE-2024-56337 protection
---
 bin/catalina.bat                                  |   1 +
 bin/catalina.sh                                   |   1 +
 java/org/apache/tomcat/util/compat/JreCompat.java | 119 ++++++++++++++++------
 webapps/docs/changelog.xml                        |   6 +-
 4 files changed, 95 insertions(+), 32 deletions(-)

diff --git a/bin/catalina.bat b/bin/catalina.bat
index 78a7eb9d9f..6c7f1baced 100755
--- a/bin/catalina.bat
+++ b/bin/catalina.bat
@@ -215,6 +215,7 @@ set 
LOGGING_MANAGER=-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogMa
 
 rem Configure module start-up parameters
 set "JAVA_OPTS=%JAVA_OPTS% --add-opens=java.base/java.lang=ALL-UNNAMED"
+set "JAVA_OPTS=%JAVA_OPTS% --add-opens=java.base/java.lang.reflect=ALL-UNNAMED"
 set "JAVA_OPTS=%JAVA_OPTS% --add-opens=java.base/java.io=ALL-UNNAMED"
 set "JAVA_OPTS=%JAVA_OPTS% --add-opens=java.base/java.util=ALL-UNNAMED"
 set "JAVA_OPTS=%JAVA_OPTS% 
--add-opens=java.base/java.util.concurrent=ALL-UNNAMED"
diff --git a/bin/catalina.sh b/bin/catalina.sh
index ee679ad0c6..885aaf721d 100755
--- a/bin/catalina.sh
+++ b/bin/catalina.sh
@@ -288,6 +288,7 @@ fi
 
 # Add the module start-up parameters required by Tomcat
 JAVA_OPTS="$JAVA_OPTS --add-opens=java.base/java.lang=ALL-UNNAMED"
+JAVA_OPTS="$JAVA_OPTS --add-opens=java.base/java.lang.reflect=ALL-UNNAMED"
 JAVA_OPTS="$JAVA_OPTS --add-opens=java.base/java.io=ALL-UNNAMED"
 JAVA_OPTS="$JAVA_OPTS --add-opens=java.base/java.util=ALL-UNNAMED"
 JAVA_OPTS="$JAVA_OPTS --add-opens=java.base/java.util.concurrent=ALL-UNNAMED"
diff --git a/java/org/apache/tomcat/util/compat/JreCompat.java 
b/java/org/apache/tomcat/util/compat/JreCompat.java
index adbd6b6d92..d40a6413c9 100644
--- a/java/org/apache/tomcat/util/compat/JreCompat.java
+++ b/java/org/apache/tomcat/util/compat/JreCompat.java
@@ -16,9 +16,16 @@
  */
 package org.apache.tomcat.util.compat;
 
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.VarHandle;
+import java.lang.management.ManagementFactory;
 import java.lang.reflect.Field;
 import java.lang.reflect.InaccessibleObjectException;
+import java.lang.reflect.Modifier;
 import java.security.PrivilegedExceptionAction;
+import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CompletionException;
 
@@ -43,7 +50,11 @@ public class JreCompat {
     private static final boolean jre21Available;
     private static final boolean jre22Available;
 
-    private static final Field useCanonCachesField;
+    private static final String USE_CANON_CACHES_CMD_ARG = 
"-Dsun.io.useCanonCaches=";
+    private static volatile Boolean canonCachesDisabled;
+    private static final Object canonCachesDisabledLock = new Object();
+    private static volatile Optional<Field> useCanonCachesField;
+    private static final Object useCanonCachesFieldLock = new Object();
 
     static {
         boolean result = false;
@@ -80,21 +91,6 @@ public class JreCompat {
             jre21Available = false;
             jre19Available = false;
         }
-
-        Field f1 = null;
-        try {
-            Class<?> clazz = Class.forName("java.io.FileSystem");
-            f1 = clazz.getDeclaredField("useCanonCaches");
-            f1.setAccessible(true);
-        } catch (InaccessibleObjectException | ReflectiveOperationException | 
IllegalArgumentException e) {
-            /*
-             * Log at debug level as this will only be an issue if the field 
needs to be accessed and most
-             * configurations will not need to do so. Appropriate warnings 
will be logged if an attempt is made to use
-             * the field when it could not be found/accessed.
-             */
-            log.debug(sm.getString("jreCompat.useCanonCaches.init"), e);
-        }
-        useCanonCachesField = f1;
     }
 
 
@@ -234,28 +230,43 @@ public class JreCompat {
 
 
     /*
-     * The behaviour of the canonical file cache varies by Java version.
+     * The behaviour of the canonical file name cache varies by Java version.
      *
      * The cache was removed in Java 21 so these methods and the associated 
code can be removed once the minimum Java
      * version is 21.
      *
-     * For 12 <= Java <= 20, the cache was present but disabled by default. 
Since the user may have changed the default
-     * Tomcat has to assume the cache is enabled unless proven otherwise.
+     * For 12 <= Java <= 20, the cache was present but disabled by default.
      *
-     * For Java < 12, the cache was enabled by default. Tomcat assumes the 
cache is enabled unless proven otherwise.
+     * Tomcat 11 has a minimum Java version of 17.
+     *
+     * The static field in java.io.FileSystem will be set before any 
application code gets a chance to run. Therefore,
+     * the value of that field can be determined by looking at the command 
line arguments. This enables us to determine
+     * the status without having using reflection.
      */
     public boolean isCanonCachesDisabled() {
-        if (useCanonCachesField == null) {
-            // No need to log a warning. The warning will be logged when 
trying to disable the cache.
-            return false;
+        if (canonCachesDisabled != null) {
+            return canonCachesDisabled.booleanValue();
         }
-        boolean result = false;
-        try {
-            result = !((Boolean) useCanonCachesField.get(null)).booleanValue();
-        } catch (ReflectiveOperationException e) {
-            // No need to log a warning. The warning will be logged when 
trying to disable the cache.
+        synchronized (canonCachesDisabledLock) {
+            if (canonCachesDisabled != null) {
+                return canonCachesDisabled.booleanValue();
+            }
+
+            List<String> args = 
ManagementFactory.getRuntimeMXBean().getInputArguments();
+            for (String arg : args) {
+                // If any command line argument attempts to enable the cache, 
assume it is enabled.
+                if (arg.startsWith(USE_CANON_CACHES_CMD_ARG)) {
+                    String value = 
arg.substring(USE_CANON_CACHES_CMD_ARG.length());
+                    boolean cacheEnabled = 
Boolean.valueOf(value).booleanValue();
+                    if (cacheEnabled) {
+                        canonCachesDisabled = Boolean.FALSE;
+                        return false;
+                    }
+                }
+            }
+            canonCachesDisabled = Boolean.TRUE;
+            return true;
         }
-        return result;
     }
 
 
@@ -266,16 +277,62 @@ public class JreCompat {
      *             as a result of this call, otherwise {@code false}
      */
     public boolean disableCanonCaches() {
-        if (useCanonCachesField == null) {
+        ensureUseCanonCachesFieldIsPopulated();
+        if (useCanonCachesField.isEmpty()) {
             log.warn(sm.getString("jreCompat.useCanonCaches.none"));
             return false;
         }
         try {
-            useCanonCachesField.set(null, Boolean.FALSE);
+            useCanonCachesField.get().set(null, Boolean.FALSE);
         } catch (ReflectiveOperationException | IllegalArgumentException e) {
             log.warn(sm.getString("jreCompat.useCanonCaches.failed"), e);
             return false;
         }
+        synchronized (canonCachesDisabledLock) {
+            canonCachesDisabled = Boolean.TRUE;
+        }
         return true;
     }
+
+
+    private void ensureUseCanonCachesFieldIsPopulated() {
+        if (useCanonCachesField != null) {
+            return;
+        }
+        synchronized (useCanonCachesFieldLock) {
+            if (useCanonCachesField != null) {
+                return;
+            }
+
+            Field f = null;
+            try {
+                Class<?> clazz = Class.forName("java.io.FileSystem");
+                f = clazz.getDeclaredField("useCanonCaches");
+                // Need this because the 'useCanonCaches' field is private
+                f.setAccessible(true);
+
+                /*
+                 * Need this in Java 17 (and it only works in Java 17) because 
the 'useCanonCaches' field is final.
+                 *
+                 * This will fail in Java 18 to 20 but since those versions 
are no longer supported it is acceptable for
+                 * the attempt to set the 'useCanonCaches' field to fail. 
Users that really want to use Java 18 to 20
+                 * will have to ensure that they do not explicitly enable the 
canonical file name cache.
+                 */
+                VarHandle modifiers;
+                Lookup lookup = MethodHandles.privateLookupIn(Field.class, 
MethodHandles.lookup());
+                modifiers = lookup.findVarHandle(Field.class, "modifiers", 
int.class);
+                modifiers.set(f, f.getModifiers() & ~Modifier.FINAL);
+            } catch (InaccessibleObjectException | 
ReflectiveOperationException | IllegalArgumentException e) {
+                // Make sure field is not set.
+                f = null;
+                log.warn(sm.getString("jreCompat.useCanonCaches.init"), e);
+            }
+
+            if (f == null) {
+                useCanonCachesField = Optional.empty();
+            } else {
+                useCanonCachesField = Optional.of(f);
+            }
+        }
+    }
 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index ca0514f489..40e64be3aa 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -125,6 +125,10 @@
         <code>HttpServletRequest.login(String username, String password)</code>
         when the realm is configured to use GSSAPI authentication. (markt)
       </fix>
+      <fix>
+        Improve the checks for exposure to and protection against 
CVE-2024-56337
+        so that reflection is not used unless required. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Coyote">
@@ -151,7 +155,7 @@
       <fix>
         <bug>69576</bug>: Avoid possible failure intializing
         <code>JreCompat</code> due to uncaught exception introduced for the
-        check for CVE-2004-56337. (remm)
+        check for CVE-2024-56337. (remm)
       </fix>
     </changelog>
   </subsection>


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to