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

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


The following commit(s) were added to refs/heads/11.0.x by this push:
     new 2dd54f2caf Minor fixes from code review
2dd54f2caf is described below

commit 2dd54f2caf7450d729cdc30b8b6ee39850ded885
Author: remm <[email protected]>
AuthorDate: Mon Jul 27 15:39:58 2026 +0200

    Minor fixes from code review
    
    One validation for unexpected path traversal.
    Harmonize getCreation.
---
 java/org/apache/catalina/Wrapper.java              |  2 +-
 .../webresources/AbstractArchiveResourceSet.java   | 48 ++++++++++++----------
 .../catalina/webresources/ExtractingRoot.java      | 16 ++++++++
 .../apache/catalina/webresources/FileResource.java |  2 +-
 .../apache/catalina/webresources/JarContents.java  |  7 +---
 .../catalina/webresources/JarResourceRoot.java     | 13 +++++-
 .../catalina/webresources/JarWarResourceSet.java   | 10 +++++
 .../catalina/webresources/LocalStrings.properties  |  3 ++
 .../apache/catalina/webresources/StandardRoot.java | 12 +++++-
 .../webresources/war/WarURLConnection.java         |  4 +-
 10 files changed, 84 insertions(+), 33 deletions(-)

diff --git a/java/org/apache/catalina/Wrapper.java 
b/java/org/apache/catalina/Wrapper.java
index aadb824dd9..bd6f0debd1 100644
--- a/java/org/apache/catalina/Wrapper.java
+++ b/java/org/apache/catalina/Wrapper.java
@@ -132,7 +132,7 @@ public interface Wrapper extends Container {
     String[] getServletMethods() throws ServletException;
 
 
-   /**
+    /**
      * Returns whether this Servlet is currently unavailable.
      *
      * @return <code>true</code> if this Servlet is currently unavailable
diff --git 
a/java/org/apache/catalina/webresources/AbstractArchiveResourceSet.java 
b/java/org/apache/catalina/webresources/AbstractArchiveResourceSet.java
index ea1c43e4a8..410fb8eaa3 100644
--- a/java/org/apache/catalina/webresources/AbstractArchiveResourceSet.java
+++ b/java/org/apache/catalina/webresources/AbstractArchiveResourceSet.java
@@ -116,21 +116,24 @@ public abstract class AbstractArchiveResourceSet extends 
AbstractResourceSet {
             if (!pathInJar.isEmpty() && pathInJar.charAt(0) == '/') {
                 pathInJar = pathInJar.substring(1);
             }
-            for (String name : getArchiveEntries(false).keySet()) {
-                if (name.length() > pathInJar.length() && 
name.startsWith(pathInJar)) {
-                    if (name.charAt(name.length() - 1) == '/') {
-                        name = name.substring(pathInJar.length(), 
name.length() - 1);
-                    } else {
-                        name = name.substring(pathInJar.length());
-                    }
-                    if (name.isEmpty()) {
-                        continue;
-                    }
-                    if (name.charAt(0) == '/') {
-                        name = name.substring(1);
-                    }
-                    if (!name.isEmpty() && name.lastIndexOf('/') == -1) {
-                        result.add(name);
+            Map<String, JarEntry> archiveEntries = getArchiveEntries(false);
+            if (archiveEntries != null) {
+                for (String name : archiveEntries.keySet()) {
+                    if (name.length() > pathInJar.length() && 
name.startsWith(pathInJar)) {
+                        if (name.charAt(name.length() - 1) == '/') {
+                            name = name.substring(pathInJar.length(), 
name.length() - 1);
+                        } else {
+                            name = name.substring(pathInJar.length());
+                        }
+                        if (name.isEmpty()) {
+                            continue;
+                        }
+                        if (name.charAt(0) == '/') {
+                            name = name.substring(1);
+                        }
+                        if (!name.isEmpty() && name.lastIndexOf('/') == -1) {
+                            result.add(name);
+                        }
                     }
                 }
             }
@@ -170,13 +173,16 @@ public abstract class AbstractArchiveResourceSet extends 
AbstractResourceSet {
                 }
             }
 
-            for (String name : getArchiveEntries(false).keySet()) {
-                if (name.length() > pathInJar.length() && 
name.startsWith(pathInJar)) {
-                    int nextSlash = name.indexOf('/', pathInJar.length());
-                    if (nextSlash != -1 && nextSlash != name.length() - 1) {
-                        name = name.substring(0, nextSlash + 1);
+            Map<String, JarEntry> archiveEntries = getArchiveEntries(false);
+            if (archiveEntries != null) {
+                for (String name : archiveEntries.keySet()) {
+                    if (name.length() > pathInJar.length() && 
name.startsWith(pathInJar)) {
+                        int nextSlash = name.indexOf('/', pathInJar.length());
+                        if (nextSlash != -1 && nextSlash != name.length() - 1) 
{
+                            name = name.substring(0, nextSlash + 1);
+                        }
+                        result.add(webAppMount + '/' + 
name.substring(getInternalPath().length()));
                     }
-                    result.add(webAppMount + '/' + 
name.substring(getInternalPath().length()));
                 }
             }
         } else {
diff --git a/java/org/apache/catalina/webresources/ExtractingRoot.java 
b/java/org/apache/catalina/webresources/ExtractingRoot.java
index a98de40870..8163d3ce9f 100644
--- a/java/org/apache/catalina/webresources/ExtractingRoot.java
+++ b/java/org/apache/catalina/webresources/ExtractingRoot.java
@@ -70,6 +70,17 @@ public class ExtractingRoot extends StandardRoot {
                 try {
                     File dest = new File(expansionTarget, 
possibleJar.getName());
                     dest = dest.getCanonicalFile();
+                    try {
+                        String expansionCanonical = 
expansionTarget.getCanonicalPath();
+                        String destCanonical = dest.getCanonicalPath();
+                        if (!destCanonical.startsWith(expansionCanonical + 
File.separator) &&
+                                !destCanonical.equals(expansionCanonical)) {
+                            throw new LifecycleException(
+                                    
sm.getString("extractingRoot.pathTraversal", possibleJar.getName()));
+                        }
+                    } catch (IOException ioe) {
+                        throw new 
LifecycleException(sm.getString("extractingRoot.targetFailed", 
expansionTarget), ioe);
+                    }
                     try (InputStream sourceStream = 
possibleJar.getInputStream();
                             OutputStream destStream = new 
FileOutputStream(dest)) {
                         IOTools.flow(sourceStream, destStream);
@@ -89,6 +100,11 @@ public class ExtractingRoot extends StandardRoot {
     }
 
 
+    /**
+     * Always returns false because ExtractingRoot extracts JARs to the work
+     * directory, making the deployment behave as if it were exploded. Callers
+     * that check isPackedWarFile() should treat this as an exploded 
deployment.
+     */
     @Override
     protected boolean isPackedWarFile() {
         return false;
diff --git a/java/org/apache/catalina/webresources/FileResource.java 
b/java/org/apache/catalina/webresources/FileResource.java
index 1004e97f2b..839a08b437 100644
--- a/java/org/apache/catalina/webresources/FileResource.java
+++ b/java/org/apache/catalina/webresources/FileResource.java
@@ -296,7 +296,7 @@ public class FileResource extends AbstractResource {
             if (log.isDebugEnabled()) {
                 log.debug(sm.getString("fileResource.getCreationFail", 
resource.getPath()), ioe);
             }
-            return 0;
+            return resource.lastModified();
         }
     }
 
diff --git a/java/org/apache/catalina/webresources/JarContents.java 
b/java/org/apache/catalina/webresources/JarContents.java
index 31438bd88d..79045c7120 100644
--- a/java/org/apache/catalina/webresources/JarContents.java
+++ b/java/org/apache/catalina/webresources/JarContents.java
@@ -136,11 +136,8 @@ public final class JarContents {
         for (int i = startPos; i < endPos; i++) {
             h = hashPrime * h + content.charAt(i);
         }
-
-        if (h < 0) {
-            h = h * -1;
-        }
-        return h;
+        // Use bitwise AND to ensure non-negative result, handles 
Integer.MIN_VALUE
+        return h & Integer.MAX_VALUE;
     }
 
 
diff --git a/java/org/apache/catalina/webresources/JarResourceRoot.java 
b/java/org/apache/catalina/webresources/JarResourceRoot.java
index d6f1803ee3..8669bd617b 100644
--- a/java/org/apache/catalina/webresources/JarResourceRoot.java
+++ b/java/org/apache/catalina/webresources/JarResourceRoot.java
@@ -17,11 +17,14 @@
 package org.apache.catalina.webresources;
 
 import java.io.File;
+import java.io.IOException;
 import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.attribute.BasicFileAttributes;
 import java.security.cert.Certificate;
 import java.util.jar.Manifest;
 
@@ -128,7 +131,15 @@ public class JarResourceRoot extends AbstractResource {
 
     @Override
     public long getCreation() {
-        return base.lastModified();
+        try {
+            BasicFileAttributes attrs = Files.readAttributes(base.toPath(), 
BasicFileAttributes.class);
+            return attrs.creationTime().toMillis();
+        } catch (IOException ioe) {
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("fileResource.getCreationFail", 
base.getPath()), ioe);
+            }
+            return base.lastModified();
+        }
     }
 
     @Override
diff --git a/java/org/apache/catalina/webresources/JarWarResourceSet.java 
b/java/org/apache/catalina/webresources/JarWarResourceSet.java
index a6ab614058..3b88af6d46 100644
--- a/java/org/apache/catalina/webresources/JarWarResourceSet.java
+++ b/java/org/apache/catalina/webresources/JarWarResourceSet.java
@@ -96,6 +96,12 @@ public class JarWarResourceSet extends 
AbstractArchiveResourceSet {
                 try {
                     warFile = openJarFile();
                     JarEntry jarFileInWar = warFile.getJarEntry(archivePath);
+                    if (jarFileInWar == null) {
+                        // Should never happen
+                        archiveEntries = null;
+                        throw new IllegalStateException(
+                                sm.getString("jarWarResourceSet.jarNotFound", 
archivePath, getBase()));
+                    }
                     jarFileIs = warFile.getInputStream(jarFileInWar);
 
                     try (TomcatJarInputStream jarIs = new 
TomcatJarInputStream(jarFileIs)) {
@@ -251,6 +257,10 @@ public class JarWarResourceSet extends 
AbstractArchiveResourceSet {
 
         try (JarFile warFile = new JarFile(getBase())) {
             JarEntry jarFileInWar = warFile.getJarEntry(archivePath);
+            if (jarFileInWar == null) {
+                throw new LifecycleException(
+                        sm.getString("jarWarResourceSet.jarNotFound", 
archivePath, getBase()));
+            }
             InputStream jarFileIs = warFile.getInputStream(jarFileInWar);
 
             try (JarInputStream jarIs = new JarInputStream(jarFileIs)) {
diff --git a/java/org/apache/catalina/webresources/LocalStrings.properties 
b/java/org/apache/catalina/webresources/LocalStrings.properties
index d4a23d9363..3a0f8e854f 100644
--- a/java/org/apache/catalina/webresources/LocalStrings.properties
+++ b/java/org/apache/catalina/webresources/LocalStrings.properties
@@ -56,6 +56,7 @@ dirResourceSet.startFail=Failed to start the resource set
 dirResourceSet.writeNpe=The input stream may not be null
 
 extractingRoot.jarFailed=Failed to extract the JAR file [{0}]
+extractingRoot.pathTraversal=Detected path traversal attempt in JAR name [{0}]
 extractingRoot.targetFailed=Failed to create the directory [{0}] for extracted 
JAR files
 
 fileResource.getCanonicalPathFail=Unable to determine the canonical path for 
the resource [{0}]
@@ -72,6 +73,7 @@ jarResourceRoot.invalidWebAppPath=This resource always refers 
to a directory so
 jarWarResourceSet.archiveEntriesFail=Failed to read the archive entries from 
the inner JAR [{0}] located in [{1}]
 jarWarResourceSet.baseUrlFail=Failed to build the base URL for the WAR file 
[{0}]
 jarWarResourceSet.codingError=Coding error
+jarWarResourceSet.jarNotFound=The JAR file [{0}] was not found in the WAR file 
[{1}]
 jarWarResourceSet.manifestFail=Failed to read the manifest from the inner JAR 
[{0}] located in [{1}]
 jarWarResourceSet.startFail=Failed to start the resource set
 
@@ -83,6 +85,7 @@ standardRoot.invalidJarUrl=The JAR/WAR URL [{0}] has invalid 
syntax
 standardRoot.invalidPath=The resource path [{0}] is not valid
 standardRoot.invalidPathNormal=The resource path [{0}] has been normalized to 
[{1}] which is not valid
 standardRoot.lockedFile=The web application [{0}] failed to close the file 
[{1}] opened via the following stack trace
+standardRoot.missingSeparator=Missing separator for archive path in [{0}]
 standardRoot.noContext=A Context has not been configured for this 
WebResourceRoot
 standardRoot.startInvalidMain=The main resource set specified [{0}] is not a 
directory or war file, or is not readable (it does not exist or permissions to 
access it are missing)
 standardRoot.unsupportedProtocol=The URL protocol [{0}] is not supported by 
this web resources implementation
diff --git a/java/org/apache/catalina/webresources/StandardRoot.java 
b/java/org/apache/catalina/webresources/StandardRoot.java
index b9ddc14d9f..d472fcc450 100644
--- a/java/org/apache/catalina/webresources/StandardRoot.java
+++ b/java/org/apache/catalina/webresources/StandardRoot.java
@@ -807,7 +807,7 @@ public class StandardRoot extends LifecycleMBeanBase 
implements WebResourceRoot
             if (f.isDirectory()) {
                 mainResourceSet = new DirResourceSet(this, "/", 
f.getAbsolutePath(), "/");
                 mainResourceSet.setReadOnly(readOnly);
-            } else if (f.isFile() && docBase.endsWith(".war")) {
+            } else if (f.isFile() && 
docBase.toLowerCase(Locale.ENGLISH).endsWith(".war")) {
                 mainResourceSet = new WarResourceSet(this, "/", 
f.getAbsolutePath());
             } else {
                 throw new 
IllegalArgumentException(sm.getString("standardRoot.startInvalidMain", 
f.getAbsolutePath()));
@@ -885,6 +885,9 @@ public class StandardRoot extends LifecycleMBeanBase 
implements WebResourceRoot
                 } else {
                     endOfFileUrl = jarUrl.indexOf(UriUtil.getWarSeparator());
                 }
+                if (endOfFileUrl == -1) {
+                    throw new 
IllegalArgumentException(sm.getString("standardRoot.missingSeparator", jarUrl));
+                }
                 String fileUrl = jarUrl.substring(4, endOfFileUrl);
                 try {
                     f = new File(new URI(fileUrl));
@@ -892,7 +895,12 @@ public class StandardRoot extends LifecycleMBeanBase 
implements WebResourceRoot
                     throw new IllegalArgumentException(
                             sm.getString("standardRoot.invalidJarUrl", 
fileUrl), e);
                 }
-                int startOfArchivePath = endOfFileUrl + 2;
+                int startOfArchivePath;
+                if ("jar".equals(url.getProtocol())) {
+                    startOfArchivePath = endOfFileUrl + 2;  // "!/" is always 
2 chars
+                } else {
+                    startOfArchivePath = endOfFileUrl + 
UriUtil.getWarSeparator().length();
+                }
                 if (jarUrl.length() > startOfArchivePath) {
                     archivePath = jarUrl.substring(startOfArchivePath);
                 } else {
diff --git a/java/org/apache/catalina/webresources/war/WarURLConnection.java 
b/java/org/apache/catalina/webresources/war/WarURLConnection.java
index ed1096497d..3ed14fcc08 100644
--- a/java/org/apache/catalina/webresources/war/WarURLConnection.java
+++ b/java/org/apache/catalina/webresources/war/WarURLConnection.java
@@ -26,7 +26,7 @@ import org.apache.tomcat.util.buf.UriUtil;
 
 
 /**
- * URL connection for WAR resources that wraps a JAR URL connection.
+ * URL connection for WAR resources that internally wraps a JAR URL connection.
  */
 public class WarURLConnection extends URLConnection {
 
@@ -42,7 +42,7 @@ public class WarURLConnection extends URLConnection {
     /**
      * Constructs a new WarURLConnection.
      *
-     * @param url The URL to connect to
+     * @param url The URL to connect to, which must use the "war:" protocol
      *
      * @throws IOException If an I/O error occurs
      */


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

Reply via email to