- Resolves: RHEL-192806 HTTP/2 request headers not validated (CVE-2026-41293)

- Resolves: RHEL-219561 Security constraint bypass via improper URL encoding in rewrite valve (CVE-2026-59083)
- Resolves: RHEL-219581 Insufficient documentation for EncryptInterceptor may lead to insecure configurations (CVE-2026-59084)
- Resolves: RHEL-238206 tomcat: Information disclosure due to HTTP Authentication Header exposure during WebSocket authentication (CVE-2026-42498)
- Resolves: RHEL-238213 tomcat: Improper Handling of Case Sensitivity in LockOutRealm (CVE-2026-43513)
- Resolves: RHEL-238260 tomcat: Improper Authorization allows security bypass (CVE-2026-43515)
- Resolves: RHEL-238294 tomcat: Authentication bypass via digest authentication (CVE-2026-43512)
- Resolves: RHEL-239018 tomcat: Apache Tomcat: Authentication bypass via missing critical step in JNDIRealm GSSAPI configuration (CVE-2026-55957)
This commit is contained in:
pmeloni 2026-08-05 19:33:39 +02:00
parent 634ffb6138
commit 88d152f2d4
7 changed files with 104 additions and 619 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@
/tomcat-9.0.87.redhat-00011-src.zip
/tomcat-9.0.87.redhat-00012-src.zip
/tomcat-9.0.87.redhat-00013-src.zip
/apache-tomcat-9.0.120-src.tar.gz

View File

@ -1,40 +0,0 @@
diff --git a/java/org/apache/catalina/mbeans/JmxRemoteLifecycleListener.java b/java/org/apache/catalina/mbeans/JmxRemoteLifecycleListener.java
index f62f8d1..db19960 100644
--- a/java/org/apache/catalina/mbeans/JmxRemoteLifecycleListener.java
+++ b/java/org/apache/catalina/mbeans/JmxRemoteLifecycleListener.java
@@ -611,34 +611,28 @@ public class JmxRemoteLifecycleListener extends SSLHostConfig implements Lifecyc
* Better to use the internal API than re-invent the wheel.
*/
@SuppressWarnings("restriction")
- private static class JmxRegistry extends sun.rmi.registry.RegistryImpl {
+ private static class JmxRegistry {
private static final long serialVersionUID = -3772054804656428217L;
private final String jmxName;
private final Remote jmxServer;
public JmxRegistry(int port, RMIClientSocketFactory csf,
RMIServerSocketFactory ssf, String jmxName, Remote jmxServer) throws RemoteException {
- super(port, csf, ssf);
this.jmxName = jmxName;
this.jmxServer = jmxServer;
}
- @Override
public Remote lookup(String name)
throws RemoteException, NotBoundException {
return (jmxName.equals(name)) ? jmxServer : null;
}
- @Override
public void bind(String name, Remote obj)
throws RemoteException, AlreadyBoundException, AccessException {
}
- @Override
public void unbind(String name)
throws RemoteException, NotBoundException, AccessException {
}
- @Override
public void rebind(String name, Remote obj)
throws RemoteException, AccessException {
}
- @Override
public String[] list() throws RemoteException {
return new String[] { jmxName };
}

View File

@ -1,74 +1,69 @@
diff --git a/build.xml b/build.xml
index 1a4b255..ea50aeb 100644
--- a/build.xml
+++ b/build.xml
@@ -3296,7 +3296,7 @@ asf.ldap.username=${release.asfusername}
@@ -3755,7 +3755,7 @@
<target name="download-compile"
description="Download components necessary to compile"
- depends="setup-bnd">
+ >
<!-- Download Commons Daemon -->
<antcall target="downloadgz-2">
diff --git a/java/org/apache/el/ExpressionFactoryImpl.java b/java/org/apache/el/ExpressionFactoryImpl.java
index 3a6690a..03a2afe 100644
--- a/java/org/apache/el/ExpressionFactoryImpl.java
+++ b/java/org/apache/el/ExpressionFactoryImpl.java
@@ -34,7 +34,7 @@ import org.apache.el.util.MessageFactory;
*
* @author Jacob Hookom [jacob@hookom.net]
@@ -31,7 +31,7 @@
/**
* Implementation of {@link javax.el.ExpressionFactory}.
*/
-@aQute.bnd.annotation.spi.ServiceProvider(value=ExpressionFactory.class)
+//@aQute.bnd.annotation.spi.ServiceProvider(value=ExpressionFactory.class)
-@aQute.bnd.annotation.spi.ServiceProvider(value = ExpressionFactory.class)
+//@aQute.bnd.annotation.spi.ServiceProvider(value = ExpressionFactory.class)
public class ExpressionFactoryImpl extends ExpressionFactory {
static {
/**
diff --git a/java/org/apache/juli/logging/LogFactory.java b/java/org/apache/juli/logging/LogFactory.java
index bfc4238..acf989a 100644
--- a/java/org/apache/juli/logging/LogFactory.java
+++ b/java/org/apache/juli/logging/LogFactory.java
@@ -21,7 +21,7 @@ import java.nio.file.FileSystems;
@@ -21,7 +21,7 @@
import java.util.ServiceLoader;
import java.util.logging.LogManager;
-import aQute.bnd.annotation.spi.ServiceConsumer;
+//import aQute.bnd.annotation.spi.ServiceConsumer;
/**
* This is a modified LogFactory that uses a simple {@link ServiceLoader} based
@@ -63,7 +63,7 @@ import aQute.bnd.annotation.spi.ServiceConsumer;
* @author Costin Manolache
* @author Richard A. Sitze
* This is a modified LogFactory that uses a simple {@link ServiceLoader} based discovery mechanism with a default of
@@ -50,7 +50,7 @@
* <strong>IMPLEMENTATION NOTE</strong> - This implementation is heavily based on the SAXParserFactory and
* DocumentBuilderFactory implementations (corresponding to the JAXP pluggability APIs) found in Apache Xerces.
*/
-@ServiceConsumer(value=Log.class)
+//@ServiceConsumer(value=Log.class)
-@ServiceConsumer(value = Log.class)
+//@ServiceConsumer(value = Log.class)
public class LogFactory {
private static final LogFactory singleton = new LogFactory();
diff --git a/java/org/apache/tomcat/websocket/WsContainerProvider.java b/java/org/apache/tomcat/websocket/WsContainerProvider.java
index 4b0577c..e383290 100644
--- a/java/org/apache/tomcat/websocket/WsContainerProvider.java
+++ b/java/org/apache/tomcat/websocket/WsContainerProvider.java
@@ -19,7 +19,7 @@ package org.apache.tomcat.websocket;
import javax.websocket.ContainerProvider;
import javax.websocket.WebSocketContainer;
@@ -22,7 +22,7 @@
/**
* WebSocket container provider for Tomcat.
*/
-@aQute.bnd.annotation.spi.ServiceProvider(value = ContainerProvider.class)
+//@aQute.bnd.annotation.spi.ServiceProvider(value = ContainerProvider.class)
public class WsContainerProvider extends ContainerProvider {
@Override
/**
diff --git a/java/org/apache/tomcat/websocket/server/DefaultServerEndpointConfigurator.java b/java/org/apache/tomcat/websocket/server/DefaultServerEndpointConfigurator.java
index 00f492e..fe5c34d 100644
--- a/java/org/apache/tomcat/websocket/server/DefaultServerEndpointConfigurator.java
+++ b/java/org/apache/tomcat/websocket/server/DefaultServerEndpointConfigurator.java
@@ -26,7 +26,7 @@ import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
@@ -30,7 +30,7 @@
* Default implementation of {@link ServerEndpointConfig.Configurator} for WebSocket server endpoints.
*/
@SuppressWarnings("javadoc") // Eclipse bug - the Javadoc is fine
-@aQute.bnd.annotation.spi.ServiceProvider(value = ServerEndpointConfig.Configurator.class)
+//@aQute.bnd.annotation.spi.ServiceProvider(value = ServerEndpointConfig.Configurator.class)
public class DefaultServerEndpointConfigurator extends ServerEndpointConfig.Configurator {
@Override
/**

View File

@ -1,22 +1,21 @@
diff -up ./build.xml.orig ./build.xml
--- build.xml.orig 2021-07-07 10:53:55.493742841 +0800
+++ build.xml 2021-07-07 11:09:43.107968515 +0800
@@ -1020,7 +1020,7 @@
--- build.xml.orig 2026-02-11 15:17:18.947314996 -0500
+++ build.xml 2026-02-11 15:17:23.675329041 -0500
@@ -1116,7 +1116,7 @@
filesDir="${tomcat.classes}"
filesId="files.annotations-api"
manifest="${tomcat.manifests}/annotations-api.jar.manifest"
- addOSGi="true" />
+ addOSGi="false" />
<!-- Servlet Implementation JAR File -->
<jarIt jarfile="${servlet-api.jar}"
@@ -1029,41 +1029,41 @@
@@ -1125,41 +1125,41 @@
manifest="${tomcat.manifests}/servlet-api.jar.manifest"
notice="${tomcat.manifests}/servlet-api.jar.notice"
license="${tomcat.manifests}/servlet-api.jar.license"
- addOSGi="true" />
+ addOSGi="false" />
<!-- EL Implementation JAR File -->
<jarIt jarfile="${el-api.jar}"
filesDir="${tomcat.classes}"
@ -24,7 +23,7 @@ diff -up ./build.xml.orig ./build.xml
manifest="${tomcat.manifests}/el-api.jar.manifest"
- addOSGi="true" />
+ addOSGi="false" />
<!-- JSP Implementation JAR File -->
<jarIt jarfile="${jsp-api.jar}"
filesDir="${tomcat.classes}"
@ -32,7 +31,7 @@ diff -up ./build.xml.orig ./build.xml
manifest="${tomcat.manifests}/jsp-api.jar.manifest"
- addOSGi="true" />
+ addOSGi="false" />
<!-- WebSocket API JAR File -->
<jarIt jarfile="${websocket-api.jar}"
filesDir="${tomcat.classes}"
@ -40,7 +39,7 @@ diff -up ./build.xml.orig ./build.xml
manifest="${tomcat.manifests}/websocket-api.jar.manifest"
- addOSGi="true" />
+ addOSGi="false" />
<!-- JASPIC API JAR File -->
<jarIt jarfile="${jaspic-api.jar}"
filesDir="${tomcat.classes}"
@ -48,50 +47,58 @@ diff -up ./build.xml.orig ./build.xml
manifest="${tomcat.manifests}/jaspic-api.jar.manifest"
- addOSGi="true" />
+ addOSGi="false" />
<!-- Tomcat-juli JAR File -->
<jarIt jarfile="${tomcat-juli.jar}"
filesDir="${tomcat.classes}"
filesId="files.tomcat-juli"
- addOSGi="true" />
+ addOSGi="false" />
<!-- Bootstrap JAR File -->
<jarIt jarfile="${bootstrap.jar}"
@@ -1075,61 +1075,61 @@
@@ -1171,68 +1171,68 @@
<jarIt jarfile="${tomcat-util.jar}"
filesDir="${tomcat.classes}"
filesId="files.tomcat-util"
- addOSGi="true" />
+ addOSGi="false" />
<!-- Tomcat API JAR File -->
<jarIt jarfile="${tomcat-api.jar}"
filesDir="${tomcat.classes}"
filesId="files.tomcat-api"
- addOSGi="true" />
+ addOSGi="false" />
<!-- Tomcat Util Scan JAR File -->
<jarIt jarfile="${tomcat-util-scan.jar}"
filesDir="${tomcat.classes}"
filesId="files.tomcat-util-scan"
- addOSGi="true" />
+ addOSGi="false" />
<jarIt jarfile="${tomcat-jni.jar}"
filesDir="${tomcat.classes}"
filesId="files.tomcat-jni"
- addOSGi="true" />
+ addOSGi="false" />
<!-- Protocol handlers - Coyote -->
<jarIt jarfile="${tomcat-coyote.jar}"
filesDir="${tomcat.classes}"
filesId="files.tomcat-coyote"
- addOSGi="true" />
+ addOSGi="false" />
<!-- OpenSSL FFM - Coyote -->
<jarIt jarfile="${tomcat-coyote-ffm.jar}"
filesDir="${tomcat.classes}"
filesId="files.tomcat-coyote-ffm"
manifest="${tomcat.manifests}/tomcat-coyote-ffm.jar.manifest"
- addOSGi="true" />
+ addOSGi="false" />
<!-- WebSocket implementation JAR File -->
<jarIt jarfile="${tomcat-websocket.jar}"
filesDir="${tomcat.classes}"
@ -99,44 +106,44 @@ diff -up ./build.xml.orig ./build.xml
meta-inf="${tomcat.manifests}/tomcat-websocket.jar"
- addOSGi="true" />
+ addOSGi="false" />
<!-- Catalina GroupCom/Tribes JAR File -->
<jarIt jarfile="${catalina-tribes.jar}"
filesDir="${tomcat.classes}"
filesId="files.catalina-tribes"
- addOSGi="true" />
+ addOSGi="false" />
<!-- Catalina Main JAR File -->
<jarIt jarfile="${catalina.jar}"
filesDir="${tomcat.classes}"
filesId="files.catalina"
- addOSGi="true" />
+ addOSGi="false" />
<!-- Catalina Cluster/HA JAR File -->
<jarIt jarfile="${catalina-ha.jar}"
filesDir="${tomcat.classes}"
filesId="files.catalina-ha"
- addOSGi="true" />
+ addOSGi="false" />
<!-- Server-Side Includes (SSI) -->
<jarIt jarfile="${catalina-ssi.jar}"
filesDir="${tomcat.classes}"
filesId="files.catalina-ssi"
- addOSGi="true" />
+ addOSGi="false" />
<!-- Catalina Ant Tasks JAR File -->
<jarIt jarfile="${catalina-ant.jar}"
@@ -1140,27 +1140,27 @@
@@ -1243,27 +1243,27 @@
<jarIt jarfile="${catalina-storeconfig.jar}"
filesDir="${tomcat.classes}"
filesId="files.catalina-storeconfig"
- addOSGi="true" />
+ addOSGi="false" />
<!-- Jasper EL Implementation JAR File -->
<jarIt jarfile="${jasper-el.jar}"
filesDir="${tomcat.classes}"
@ -144,7 +151,7 @@ diff -up ./build.xml.orig ./build.xml
meta-inf="${tomcat.manifests}/jasper-el.jar"
- addOSGi="true" />
+ addOSGi="false" />
<!-- Jasper Implementation JAR File -->
<jarIt jarfile="${jasper.jar}"
filesDir="${tomcat.classes}"
@ -152,17 +159,17 @@ diff -up ./build.xml.orig ./build.xml
meta-inf="${tomcat.manifests}/jasper.jar"
- addOSGi="true" />
+ addOSGi="false" />
<!-- Re-packaged Apache Commons DBCP 2-->
<jarIt jarfile="${tomcat-dbcp.jar}"
filesDir="${tomcat.classes}"
filesId="files.tomcat-dbcp"
- addOSGi="true" />
+ addOSGi="false" />
<!-- i18n JARs -->
<jar jarfile="${tomcat.build}/lib/tomcat-i18n-cs.jar"
@@ -1620,7 +1620,7 @@
@@ -1716,7 +1716,7 @@
filesId="files.tomcat-embed-core"
notice="${tomcat.manifests}/servlet-api.jar.notice"
license="${tomcat.manifests}/servlet-api.jar.license"
@ -171,7 +178,7 @@ diff -up ./build.xml.orig ./build.xml
addGraal="true"
graalPrefix="org.apache.tomcat.embed/tomcat-embed-core"
graalFiles="res/graal/tomcat-embed-core/native-image"
@@ -1628,7 +1628,7 @@
@@ -1724,7 +1724,7 @@
<jarIt jarfile="${tomcat-embed-el.jar}"
filesDir="${tomcat.classes}"
filesId="files.tomcat-embed-el"
@ -180,7 +187,7 @@ diff -up ./build.xml.orig ./build.xml
addGraal="true"
graalPrefix="org.apache.tomcat.embed/tomcat-embed-el"
graalFiles="res/graal/tomcat-embed-el/native-image"
@@ -1637,7 +1637,7 @@
@@ -1733,7 +1733,7 @@
filesDir="${tomcat.classes}"
filesId="files.tomcat-embed-jasper"
meta-inf="${tomcat.manifests}/jasper.jar"
@ -189,7 +196,7 @@ diff -up ./build.xml.orig ./build.xml
addGraal="true"
graalPrefix="org.apache.tomcat.embed/tomcat-embed-jasper"
graalFiles="res/graal/tomcat-embed-jasper/native-image"
@@ -1646,7 +1646,7 @@
@@ -1742,7 +1742,7 @@
filesDir="${tomcat.classes}"
filesId="files.tomcat-embed-websocket"
meta-inf="${tomcat.manifests}/tomcat-websocket.jar"

View File

@ -1,494 +0,0 @@
diff --git a/java/org/apache/catalina/storeconfig/LocalStrings.properties b/java/org/apache/catalina/storeconfig/LocalStrings.properties
index 2facbe157c..b257859ea3 100644
--- a/java/org/apache/catalina/storeconfig/LocalStrings.properties
+++ b/java/org/apache/catalina/storeconfig/LocalStrings.properties
@@ -25,8 +25,11 @@ factory.storeTag=store tag [{0}] ( Object: [{1}] )
globalNamingResourcesSF.noFactory=Cannot find NamingResources store factory
globalNamingResourcesSF.wrongElement=Wrong element [{0}]
+registry.interfacesLoaded=Loaded [{0}] interface classes for registry
registry.loadClassFailed=Failed to load class [{0}]
registry.noDescriptor=Can't find descriptor for key [{0}]
+registry.optionalClassLoaded=Loaded optional class [{0}]
+registry.optionalClassNotFound=Optional class [{0}] not found, skipping
standardContextSF.cannotWriteFile=Cannot write file at [{0}]
standardContextSF.canonicalPathError=Failed to obtain the canonical path of the configuration file [{0}]
diff --git a/java/org/apache/catalina/storeconfig/StandardEngineSF.java b/java/org/apache/catalina/storeconfig/StandardEngineSF.java
index 8963cc76af..bec6f6a789 100644
--- a/java/org/apache/catalina/storeconfig/StandardEngineSF.java
+++ b/java/org/apache/catalina/storeconfig/StandardEngineSF.java
@@ -26,13 +26,23 @@ import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Realm;
import org.apache.catalina.Valve;
import org.apache.catalina.core.StandardEngine;
-import org.apache.catalina.ha.ClusterValve;
/**
* Store server.xml Element Engine
*/
public class StandardEngineSF extends StoreFactoryBase {
+ private static final Class<?> clusterValveClass;
+ static {
+ Class<?> clazz = null;
+ try {
+ clazz = Class.forName("org.apache.catalina.ha.ClusterValve");
+ } catch (ClassNotFoundException e) {
+ // Expected when clustering JARs are not present
+ }
+ clusterValveClass = clazz;
+ }
+
/**
* Store the specified Engine properties.
*
@@ -72,7 +82,7 @@ public class StandardEngineSF extends StoreFactoryBase {
if(valves != null && valves.length > 0 ) {
List<Valve> engineValves = new ArrayList<>() ;
for (Valve valve : valves) {
- if (!(valve instanceof ClusterValve)) {
+ if (clusterValveClass == null || !clusterValveClass.isInstance(valve)) {
engineValves.add(valve);
}
}
diff --git a/java/org/apache/catalina/storeconfig/StandardHostSF.java b/java/org/apache/catalina/storeconfig/StandardHostSF.java
index 54d2aeed42..5c511a5256 100644
--- a/java/org/apache/catalina/storeconfig/StandardHostSF.java
+++ b/java/org/apache/catalina/storeconfig/StandardHostSF.java
@@ -26,13 +26,23 @@ import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Realm;
import org.apache.catalina.Valve;
import org.apache.catalina.core.StandardHost;
-import org.apache.catalina.ha.ClusterValve;
/**
* Store server.xml Element Host
*/
public class StandardHostSF extends StoreFactoryBase {
+ private static final Class<?> clusterValveClass;
+ static {
+ Class<?> clazz = null;
+ try {
+ clazz = Class.forName("org.apache.catalina.ha.ClusterValve");
+ } catch (ClassNotFoundException e) {
+ // Expected when clustering JARs are not present
+ }
+ clusterValveClass = clazz;
+ }
+
/**
* Store the specified Host properties and children
* (Listener,Alias,Realm,Valve,Cluster, Context)
@@ -78,7 +88,7 @@ public class StandardHostSF extends StoreFactoryBase {
if(valves != null && valves.length > 0 ) {
List<Valve> hostValves = new ArrayList<>() ;
for (Valve valve : valves) {
- if (!(valve instanceof ClusterValve)) {
+ if (clusterValveClass == null || !clusterValveClass.isInstance(valve)) {
hostValves.add(valve);
}
}
diff --git a/java/org/apache/catalina/storeconfig/StoreRegistry.java b/java/org/apache/catalina/storeconfig/StoreRegistry.java
index 4865171d14..1c1b21112e 100644
--- a/java/org/apache/catalina/storeconfig/StoreRegistry.java
+++ b/java/org/apache/catalina/storeconfig/StoreRegistry.java
@@ -16,7 +16,9 @@
*/
package org.apache.catalina.storeconfig;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import javax.naming.directory.DirContext;
@@ -28,17 +30,6 @@ import org.apache.catalina.Realm;
import org.apache.catalina.Valve;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.WebResourceSet;
-import org.apache.catalina.ha.CatalinaCluster;
-import org.apache.catalina.ha.ClusterDeployer;
-import org.apache.catalina.ha.ClusterListener;
-import org.apache.catalina.tribes.Channel;
-import org.apache.catalina.tribes.ChannelInterceptor;
-import org.apache.catalina.tribes.ChannelReceiver;
-import org.apache.catalina.tribes.ChannelSender;
-import org.apache.catalina.tribes.Member;
-import org.apache.catalina.tribes.MembershipService;
-import org.apache.catalina.tribes.MessageListener;
-import org.apache.catalina.tribes.transport.DataSender;
import org.apache.coyote.UpgradeProtocol;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
@@ -49,10 +40,16 @@ import org.apache.tomcat.util.res.StringManager;
* Central StoreRegistry for all server.xml elements
*/
public class StoreRegistry {
- private static Log log = LogFactory.getLog(StoreRegistry.class);
- private static StringManager sm = StringManager.getManager(StoreRegistry.class);
+ /**
+ * Constructs a new StoreRegistry with default settings.
+ */
+ public StoreRegistry() {
+ }
- private Map<String, StoreDescription> descriptors = new HashMap<>();
+ private static final Log log = LogFactory.getLog(StoreRegistry.class);
+ private static final StringManager sm = StringManager.getManager(StoreRegistry.class);
+
+ private final Map<String,StoreDescription> descriptors = new HashMap<>();
private String encoding = "UTF-8";
@@ -61,49 +58,116 @@ public class StoreRegistry {
private String version;
// Access Information
- private static Class<?> interfaces[] = { CatalinaCluster.class,
- ChannelSender.class, ChannelReceiver.class, Channel.class,
- MembershipService.class, ClusterDeployer.class, Realm.class,
- Manager.class, DirContext.class, LifecycleListener.class,
- Valve.class, ClusterListener.class, MessageListener.class,
- DataSender.class, ChannelInterceptor.class, Member.class,
- WebResourceRoot.class, WebResourceSet.class,
- CredentialHandler.class, UpgradeProtocol.class,
- CookieProcessor.class };
+ // Lazily initialized to gracefully handle optional features like clustering
+ private static volatile Class<?>[] interfaces = null;
+
+ /**
+ * Initialize the interfaces array with all available classes.
+ * Uses dynamic loading for optional classes (e.g., clustering) to avoid
+ * ClassNotFoundException when those JARs are not present. This approach
+ * is consistent with how Catalina.addClusterRuleSet() handles clustering.
+ */
+ private static Class<?>[] getInterfaces() {
+ if (interfaces == null) {
+ synchronized (StoreRegistry.class) {
+ if (interfaces == null) {
+ // Required interfaces - always present
+ List<Class<?>> list = new ArrayList<>();
+ list.add(Realm.class);
+ list.add(Manager.class);
+ list.add(DirContext.class);
+ list.add(LifecycleListener.class);
+ list.add(Valve.class);
+ list.add(WebResourceRoot.class);
+ list.add(WebResourceSet.class);
+ list.add(CredentialHandler.class);
+ list.add(UpgradeProtocol.class);
+ list.add(CookieProcessor.class);
+
+ // Optional clustering interfaces - load dynamically to support
+ // deployments where clustering JARs may not be present
+ tryAddClass(list, "org.apache.catalina.ha.CatalinaCluster");
+ tryAddClass(list, "org.apache.catalina.tribes.ChannelSender");
+ tryAddClass(list, "org.apache.catalina.tribes.ChannelReceiver");
+ tryAddClass(list, "org.apache.catalina.tribes.Channel");
+ tryAddClass(list, "org.apache.catalina.tribes.MembershipService");
+ tryAddClass(list, "org.apache.catalina.ha.ClusterDeployer");
+ tryAddClass(list, "org.apache.catalina.ha.ClusterListener");
+ tryAddClass(list, "org.apache.catalina.tribes.MessageListener");
+ tryAddClass(list, "org.apache.catalina.tribes.transport.DataSender");
+ tryAddClass(list, "org.apache.catalina.tribes.ChannelInterceptor");
+ tryAddClass(list, "org.apache.catalina.tribes.Member");
+
+ interfaces = list.toArray(new Class<?>[0]);
+
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("registry.interfacesLoaded", Integer.valueOf(interfaces.length)));
+ }
+ }
+ }
+ }
+ return interfaces;
+ }
+
+ /**
+ * Try to load a class by name and add it to the list if successful.
+ * Logs at TRACE level if the class is not available.
+ */
+ private static void tryAddClass(List<Class<?>> list, String className) {
+ try {
+ Class<?> clazz = Class.forName(className, false, StoreRegistry.class.getClassLoader());
+ list.add(clazz);
+ if (log.isTraceEnabled()) {
+ log.trace(sm.getString("registry.optionalClassLoaded", className));
+ }
+ } catch (ClassNotFoundException | NoClassDefFoundError e) {
+ if (log.isTraceEnabled()) {
+ log.trace(sm.getString("registry.optionalClassNotFound", className));
+ }
+ }
+ }
/**
- * @return the name
+ * Returns the name of this registry.
+ *
+ * @return the registry name
*/
public String getName() {
return name;
}
/**
- * @param name The name to set.
+ * Sets the name of this registry.
+ *
+ * @param name the registry name
*/
public void setName(String name) {
this.name = name;
}
/**
- * @return the version
+ * Returns the version of this registry.
+ *
+ * @return the registry version
*/
public String getVersion() {
return version;
}
/**
- * @param version The version to set
+ * Sets the version of this registry.
+ *
+ * @param version the registry version
*/
public void setVersion(String version) {
this.version = version;
}
/**
- * Find a description for id. Handle interface search when no direct match
- * found.
+ * Find a description for id. Handle interface search when no direct match found.
*
* @param id The class name
+ *
* @return the description
*/
public StoreDescription findDescription(String id) {
@@ -120,17 +184,17 @@ public class StoreRegistry {
}
if (aClass != null) {
desc = descriptors.get(aClass.getName());
- for (int i = 0; desc == null && i < interfaces.length; i++) {
- if (interfaces[i].isAssignableFrom(aClass)) {
- desc = descriptors.get(interfaces[i].getName());
+ Class<?>[] availableInterfaces = getInterfaces();
+ for (int i = 0; desc == null && i < availableInterfaces.length; i++) {
+ if (availableInterfaces[i].isAssignableFrom(aClass)) {
+ desc = descriptors.get(availableInterfaces[i].getName());
}
}
}
}
if (log.isDebugEnabled()) {
if (desc != null) {
- log.trace("find descriptor " + id + "#" + desc.getTag() + "#"
- + desc.getStoreFactoryClass());
+ log.trace("find descriptor " + id + "#" + desc.getTag() + "#" + desc.getStoreFactoryClass());
} else {
log.debug(sm.getString("registry.noDescriptor", id));
}
@@ -142,6 +206,7 @@ public class StoreRegistry {
* Find Description by class.
*
* @param aClass The class
+ *
* @return the description
*/
public StoreDescription findDescription(Class<?> aClass) {
@@ -152,6 +217,7 @@ public class StoreRegistry {
* Find factory from class name.
*
* @param aClassName The class name
+ *
* @return the factory
*/
public IStoreFactory findStoreFactory(String aClassName) {
@@ -168,6 +234,7 @@ public class StoreRegistry {
* Find factory from class.
*
* @param aClass The class
+ *
* @return the factory
*/
public IStoreFactory findStoreFactory(Class<?> aClass) {
@@ -186,8 +253,7 @@ public class StoreRegistry {
}
descriptors.put(key, desc);
if (log.isTraceEnabled()) {
- log.trace("register store descriptor " + key + "#" + desc.getTag()
- + "#" + desc.getTagClass());
+ log.trace("register store descriptor " + key + "#" + desc.getTag() + "#" + desc.getTagClass());
}
}
@@ -195,11 +261,12 @@ public class StoreRegistry {
* Unregister a description.
*
* @param desc The description
+ *
* @return the description, or <code>null</code> if it was not registered
*/
public StoreDescription unregisterDescription(StoreDescription desc) {
String key = desc.getId();
- if (key == null || "".equals(key)) {
+ if (key == null || key.isEmpty()) {
key = desc.getTagClass();
}
return descriptors.remove(key);
@@ -208,7 +275,9 @@ public class StoreRegistry {
// Attributes
/**
- * @return the encoding
+ * Returns the character encoding used when writing configuration files.
+ *
+ * @return the character encoding
*/
public String getEncoding() {
return encoding;
@@ -216,6 +285,7 @@ public class StoreRegistry {
/**
* Set the encoding to use when writing the configuration files.
+ *
* @param string The encoding
*/
public void setEncoding(String string) {
diff --git a/test/org/apache/catalina/storeconfig/TestStoreRegistry.java b/test/org/apache/catalina/storeconfig/TestStoreRegistry.java
new file mode 100644
index 0000000000..e6869d3642
--- /dev/null
+++ b/test/org/apache/catalina/storeconfig/TestStoreRegistry.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.storeconfig;
+
+import java.lang.reflect.Method;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.catalina.Manager;
+import org.apache.catalina.Realm;
+import org.apache.catalina.Valve;
+
+/**
+ * Test StoreRegistry behavior, particularly dynamic loading of optional classes like clustering.
+ *
+ * Verifies StoreRegistry uses the same dynamic loading pattern.
+ */
+public class TestStoreRegistry {
+
+ /**
+ * Test that clustering classes are dynamically loaded like other Tomcat components.
+ *
+ * StoreRegistry should initialize successfully whether clustering is available or not.
+ * This matches the pattern used in Catalina.addClusterRuleSet().
+ */
+ @Test
+ public void testClusteringClassesOptional() throws Exception {
+ // Verify StoreRegistry initializes successfully with dynamic class loading
+ StoreRegistry registry = new StoreRegistry();
+ Assert.assertNotNull("Registry should initialize with dynamic loading", registry);
+
+ // Trigger lazy loading of interfaces array
+ Method getInterfacesMethod = StoreRegistry.class.getDeclaredMethod("getInterfaces");
+ getInterfacesMethod.setAccessible(true);
+
+ Class<?>[] interfaces = (Class<?>[]) getInterfacesMethod.invoke(null);
+ Assert.assertNotNull("Interfaces should load dynamically", interfaces);
+
+ // Test passes if we get here without ClassNotFoundException.
+ // The actual number of interfaces loaded depends on whether clustering is available,
+ // but we should always have at least the core 10 interfaces.
+ Assert.assertTrue("Should have at least 10 core interfaces",
+ interfaces.length >= 10);
+
+ // Verify required core interfaces are always present
+ boolean hasRealm = false;
+ boolean hasManager = false;
+ boolean hasValve = false;
+
+ for (Class<?> iface : interfaces) {
+ if (iface.equals(Realm.class)) {
+ hasRealm = true;
+ }
+ if (iface.equals(Manager.class)) {
+ hasManager = true;
+ }
+ if (iface.equals(Valve.class)) {
+ hasValve = true;
+ }
+ }
+
+ Assert.assertTrue("Should contain Realm interface", hasRealm);
+ Assert.assertTrue("Should contain Manager interface", hasManager);
+ Assert.assertTrue("Should contain Valve interface", hasValve);
+ }
+
+ /**
+ * Test that findDescription works with interface inheritance and
+ * dynamically loaded interfaces.
+ */
+ @Test
+ public void testFindDescriptionWithDynamicInterfaces() throws Exception {
+ StoreRegistry registry = new StoreRegistry();
+
+ // Register a description for the Valve interface
+ StoreDescription valveDesc = new StoreDescription();
+ valveDesc.setId(Valve.class.getName());
+ valveDesc.setTag("Valve");
+ valveDesc.setTagClass(Valve.class.getName());
+ registry.registerDescription(valveDesc);
+
+ // AccessLogValve implements Valve interface - should find via dynamic interface matching
+ String accessLogValveClass = "org.apache.catalina.valves.AccessLogValve";
+ StoreDescription foundDesc = registry.findDescription(accessLogValveClass);
+
+ Assert.assertNotNull("Should find description via interface matching", foundDesc);
+ Assert.assertEquals("Should match Valve descriptor", "Valve", foundDesc.getTag());
+ }
+}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 012b607f18..17f639d669 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -138,6 +138,13 @@
The rewrite valve should not do a rewrite if the output is identical
to the input. (remm)
</fix>
+ <fix>
+ Update <code>StoreRegistry</code> to dynamically load optional clustering
+ classes rather than statically referencing them. This matches the pattern
+ used in <code>Catalina.addClusterRuleSet()</code> and prevents
+ <code>NoClassDefFoundError</code> when <code>StoreConfigLifecycleListener</code>
+ is configured but clustering classes are not available. (csutherl)
+ </fix>
<update>
Add a new <code>valveSkip</code> (or <code>VS</code>) rule flag to the
rewrite valve to allow skipping over the next valve in the Catalina

View File

@ -1 +1 @@
SHA512 (tomcat-9.0.87.redhat-00013-src.zip) = 1049d86d4bbdfd251a3f0cc72840cb6535a5637a76becaec8cb9c6532430dabaefd007af285fa1ac8d6a2a70f1d0378f6a1e908f7e7c5aff7c2bbedcd521cc9d
SHA512 (apache-tomcat-9.0.120-src.tar.gz) = db8230bb08b1c33ea710b71fbba1de3d0c84c6416a784921333c03ef6526c1b022b0b2ad18686b734babba538500da795e46bf5bbbd789d8a7a251ffb3665a59

View File

@ -31,8 +31,8 @@
%global jspspec 2.3
%global major_version 9
%global minor_version 0
%global micro_version 87
%global packdname %{name}-%{major_version}.%{minor_version}.%{micro_version}.redhat-00013-src
%global micro_version 120
%global packdname apache-%{name}-%{major_version}.%{minor_version}.%{micro_version}-src
%global servletspec 4.0
%global elspec 3.0
%global tcuid 53
@ -56,12 +56,12 @@
Name: tomcat
Epoch: 1
Version: %{major_version}.%{minor_version}.%{micro_version}
Release: 2%{?dist}
Release: 1%{?dist}
Summary: Apache Servlet/JSP Engine, RI for Servlet %{servletspec}/JSP %{jspspec} API
License: ASL 2.0
URL: http://tomcat.apache.org/
Source0: %{packdname}.zip
Source0: %{packdname}.tar.gz
Source1: %{name}-%{major_version}.%{minor_version}.conf
Source3: %{name}-%{major_version}.%{minor_version}.sysconfig
Source4: %{name}-%{major_version}.%{minor_version}.wrapper
@ -82,19 +82,20 @@ Patch3: %{name}-%{major_version}.%{minor_version}-catalina-policy.patch
Patch4: rhbz-1857043.patch
# remove bnd dependency which version is too low on rhel8
Patch6: remove-bnd-annotation.patch
Patch7: JmxRemoteLifecycleListener.patch
Patch8: rhel-168577.patch
BuildArch: noarch
BuildRequires: ant
BuildRequires: ecj
BuildRequires: ecj >= 1:4.10
BuildRequires: findutils
BuildRequires: java-17-openjdk-devel
BuildRequires: javapackages-local
BuildRequires: aqute-bnd
BuildRequires: aqute-bndlib
BuildRequires: systemd
Requires: (java-headless >= 1:1.8 or java-1.8.0-headless or java-11-headless or java-17-headless or java >= 1:1.8)
Requires: (java-headless >= 1:1.8 or java-1.8.0-headless or java-11-headless or java-17-headless or java-21-headless or java >= 1:1.8)
Requires: javapackages-tools
Requires: %{name}-lib = %{epoch}:%{version}-%{release}
%if 0%{?fedora} || 0%{?rhel} > 7
@ -185,7 +186,7 @@ Requires: %{name} = %{epoch}:%{version}-%{release}
The ROOT web application for Apache Tomcat.
%prep
%setup -q -n apache-%{packdname}
%setup -q -n %{packdname}
# remove pre-built binaries and windows files
find . -type f \( -name "*.bat" -o -name "*.class" -o -name Thumbs.db -o -name "*.gz" -o \
-name "*.jar" -o -name "*.war" -o -name "*.zip" \) -delete
@ -196,8 +197,6 @@ find . -type f \( -name "*.bat" -o -name "*.class" -o -name Thumbs.db -o -name "
%patch -P3 -p0
%patch -P4 -p0
%patch -P6 -p1
%patch -P7 -p1
%patch -P8 -p1
# Remove webservices naming resources as it's generally unused
%{__rm} -rf java/org/apache/naming/factory/webservices
@ -219,8 +218,12 @@ export OPT_JAR_LIST="xalan-j2-serializer"
# so just create a dummy file for later removal
touch HACK
# Adding JAVA_HOME to always compile with java-17 instead of autodetecting
export JAVA_HOME=%{_jvmdir}/java-17-openjdk
export PATH=$JAVA_HOME/bin:$PATH
# who needs a build.properties file anyway
%{ant} -Dbase.path="." \
ant -Dbase.path="." \
-Dbuild.compiler="modern" \
-Dcommons-daemon.jar="HACK" \
-Dcommons-daemon.native.src.tgz="HACK" \
@ -238,6 +241,9 @@ touch HACK
# remove some jars that we'll replace with symlinks later
%{__rm} output/build/lib/ecj.jar
# Cleanup commons-daemon.jar that somehow appeared since last build, but is unnecessary
%{__rm} -rf output/build/bin/commons-daemon.jar
# Remove the example webapps per Apache Tomcat Security Considerations
# see https://tomcat.apache.org/tomcat-9.0-doc/security-howto.html
%{__rm} -rf output/build/webapps/examples
@ -403,7 +409,6 @@ popd
%mvn_artifact res/maven/tomcat-websocket-api.pom ${RPM_BUILD_ROOT}%{libdir}/websocket-api.jar
%mvn_artifact res/maven/tomcat-websocket.pom ${RPM_BUILD_ROOT}%{libdir}/tomcat-websocket.jar
%mvn_artifact res/maven/tomcat.pom
%mvn_install
%pre
@ -439,7 +444,7 @@ exit 0
%systemd_preun %{name}.service
%postun
%systemd_postun_with_restart %{name}.service
%systemd_postun_with_restart %{name}.service
%postun jsp-%{jspspec}-api
if [ "$1" = "0" ]; then
@ -459,7 +464,7 @@ if [ "$1" = "0" ]; then
%{_javadir}/%{name}-el-%{elspec}-api.jar
fi
%files
%files
%defattr(0664,root,tomcat,0755)
%doc {LICENSE,NOTICE,RELEASE*}
%attr(0755,root,root) %{_bindir}/%{name}-digest
@ -558,7 +563,18 @@ fi
%{appdir}/ROOT
%changelog
* Fri Jul 31 2026 Pietro Meloni <pmeloni@redhat.com> - 1:9.0.120-1
- Resolves: RHEL-192806 HTTP/2 request headers not validated (CVE-2026-41293)
- Resolves: RHEL-219561 Security constraint bypass via improper URL encoding in rewrite valve (CVE-2026-59083)
- Resolves: RHEL-219581 Insufficient documentation for EncryptInterceptor may lead to insecure configurations (CVE-2026-59084)
- Resolves: RHEL-238206 tomcat: Information disclosure due to HTTP Authentication Header exposure during WebSocket authentication (CVE-2026-42498)
- Resolves: RHEL-238213 tomcat: Improper Handling of Case Sensitivity in LockOutRealm (CVE-2026-43513)
- Resolves: RHEL-238260 tomcat: Improper Authorization allows security bypass (CVE-2026-43515)
- Resolves: RHEL-238294 tomcat: Authentication bypass via digest authentication (CVE-2026-43512)
- Resolves: RHEL-239018 tomcat: Apache Tomcat: Authentication bypass via missing critical step in JNDIRealm GSSAPI configuration (CVE-2026-55957)
* Thu Jun 4 2026 Pietro Meloni <pmeloni@redhat.com> - 1:9.0.87-2
- Resolves: RHEL-183993 Remove tomcat clustering JAR from RPM builds