Apache Tomcat: Information disclosure
Description
The simplified implementation of blocking reads and writes introduced in Tomcat 10 and back-ported to Tomcat 9.0.47 onwards exposed a long standing (but extremely hard to trigger) concurrency bug in Apache Tomcat 10.1.0 to 10.1.0-M12, 10.0.0-M1 to 10.0.18, 9.0.0-M1 to 9.0.60 and 8.5.0 to 8.5.77 that could cause client connections to share an Http11Processor instance resulting in responses, or part responses, to be received by the wrong client.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A concurrency bug in Apache Tomcat's simplified blocking I/O can cause client connections to share an Http11Processor, leading to responses being sent to wrong clients.
Vulnerability
Overview
CVE-2021-43980 is a concurrency bug in Apache Tomcat that affects versions 10.1.0 to 10.1.0-M12, 10.0.0-M1 to 10.0.18, 9.0.0-M1 to 9.0.60, and 8.5.0 to 8.5.77. The vulnerability was exposed by a simplified implementation of blocking reads and writes introduced in Tomcat 10 and backported to Tomcat 9.0.47 onwards. This simplification revealed a long-standing but extremely hard to trigger concurrency flaw that could cause client connections to share an Http11Processor instance [4].
Exploitation
The bug is triggered under specific timing conditions during concurrent handling of blocking I/O operations. No authentication or special privileges are required, as the vulnerability exists in the core request processing loop. An attacker would need to be able to send HTTP requests to the server, and the attack would rely on precise timing to exploit the race condition that leads to processor sharing.
Impact
Successful exploitation can result in responses—or partial responses—being delivered to the wrong client connection. This could lead to information disclosure, as sensitive data from one user's session might be exposed to another user. The impact is limited to response misrouting; the attacker does not gain code execution or direct access to server resources.
Mitigation
Patches are available in Tomcat 10.1.1, 10.0.19, 9.0.61, and 8.5.78. Users should upgrade to these or later versions. No workaround is documented, and the vulnerability is not known to be exploited in the wild as of publication [1][3][4].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.apache.tomcat:tomcatMaven | >= 8.5.0, < 8.5.78 | 8.5.78 |
org.apache.tomcat:tomcatMaven | >= 9.0.0-M1, < 9.0.62 | 9.0.62 |
org.apache.tomcat:tomcatMaven | >= 10.0.0-M1, < 10.0.20 | 10.0.20 |
org.apache.tomcat:tomcatMaven | >= 10.1.0-M1, < 10.1.0-M14 | 10.1.0-M14 |
Affected products
19- osv-coords18 versionspkg:bitnami/tomcatpkg:maven/org.apache.tomcat/tomcatpkg:rpm/opensuse/tomcat10&distro=openSUSE%20Tumbleweedpkg:rpm/opensuse/tomcat&distro=openSUSE%20Tumbleweedpkg:rpm/suse/tomcat&distro=SUSE%20Enterprise%20Storage%206pkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP1-ESPOSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP1-LTSSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015-ESPOSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015-LTSSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%2012%20SP5pkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%2012%20SP5-LTSSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%2015%20SP1-BCLpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%2015%20SP1-LTSSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%2015-LTSSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2012%20SP5pkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2015pkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2015%20SP1pkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%20LTSS%20Extended%20Security%2012%20SP5
>= 8.5.0, < 8.5.78+ 17 more
- (no CPE)range: >= 8.5.0, < 8.5.78
- (no CPE)range: >= 8.5.0, < 8.5.78
- (no CPE)range: < 10.1.14-1.1
- (no CPE)range: < 9.0.43-11.1
- (no CPE)range: < 9.0.36-150100.4.81.1
- (no CPE)range: < 9.0.36-150100.4.81.1
- (no CPE)range: < 9.0.36-150100.4.81.1
- (no CPE)range: < 9.0.36-150000.3.101.2
- (no CPE)range: < 9.0.36-150000.3.101.2
- (no CPE)range: < 9.0.36-3.90.1
- (no CPE)range: < 9.0.115-3.160.1
- (no CPE)range: < 9.0.36-150100.4.81.1
- (no CPE)range: < 9.0.36-150100.4.81.1
- (no CPE)range: < 9.0.36-150000.3.101.2
- (no CPE)range: < 9.0.36-3.90.1
- (no CPE)range: < 9.0.36-150000.3.101.2
- (no CPE)range: < 9.0.36-150100.4.81.1
- (no CPE)range: < 9.0.115-3.160.1
- Apache Software Foundation/Apache Tomcatv5Range: 10.1.0-M1 to 10.1.0-M12
Patches
4170e0f792bd1Improve the recycling of Processor objects to make it more robust.
3 files changed · +33 −20
java/org/apache/coyote/AbstractProtocol.java+17 −15 modified@@ -797,7 +797,11 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { S socket = wrapper.getSocket(); - Processor processor = (Processor) wrapper.getCurrentProcessor(); + // We take complete ownership of the Processor inside of this method to ensure + // no other thread can release it while we're using it. Whatever processor is + // held by this variable will be associated with the SocketWrapper before this + // method returns. + Processor processor = (Processor) wrapper.takeCurrentProcessor(); if (getLog().isDebugEnabled()) { getLog().debug(sm.getString("abstractConnectionHandler.connectionsGet", processor, socket)); @@ -881,9 +885,6 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { processor.setSslSupport( wrapper.getSslSupport(getProtocol().getClientCertProvider())); - // Associate the processor with the connection - wrapper.setCurrentProcessor(processor); - SocketState state = SocketState.CLOSED; do { state = processor.process(wrapper, status); @@ -903,8 +904,6 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { release(processor); // Create the upgrade processor processor = upgradeProtocol.getProcessor(wrapper, getProtocol().getAdapter()); - // Associate with the processor with the connection - wrapper.setCurrentProcessor(processor); } else { if (getLog().isDebugEnabled()) { getLog().debug(sm.getString( @@ -924,8 +923,6 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { getLog().debug(sm.getString("abstractConnectionHandler.upgradeCreate", processor, wrapper)); } - // Associate with the processor with the connection - wrapper.setCurrentProcessor(processor); // Initialise the upgrade handler (which may trigger // some IO using the new protocol which is why the lines // above are necessary) @@ -963,8 +960,8 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { } else if (state == SocketState.OPEN) { // In keep-alive but between requests. OK to recycle // processor. Continue to poll for the next request. - wrapper.setCurrentProcessor(null); release(processor); + processor = null; wrapper.registerReadInterest(); } else if (state == SocketState.SENDFILE) { // Sendfile in progress. If it fails, the socket will be @@ -989,8 +986,7 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { // Connection closed. OK to recycle the processor. // Processors handling upgrades require additional clean-up // before release. - wrapper.setCurrentProcessor(null); - if (processor.isUpgrade()) { + if (processor != null && processor.isUpgrade()) { UpgradeToken upgradeToken = processor.getUpgradeToken(); HttpUpgradeHandler httpUpgradeHandler = upgradeToken.getHttpUpgradeHandler(); InstanceManager instanceManager = upgradeToken.getInstanceManager(); @@ -1011,7 +1007,13 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { } } } + release(processor); + processor = null; + } + + if (processor != null) { + wrapper.setCurrentProcessor(processor); } return state; } catch(java.net.SocketException e) { @@ -1047,7 +1049,6 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { // Make sure socket/processor is removed from the list of current // connections - wrapper.setCurrentProcessor(null); release(processor); return SocketState.CLOSED; } @@ -1081,7 +1082,9 @@ public Set<S> getOpenSockets() { /** * Expected to be used by the handler once the processor is no longer - * required. + * required. Care must be taken to ensure that this method is only + * called once per processor, after the request processing has + * completed. * * @param processor Processor being released (that was associated with * the socket) @@ -1119,8 +1122,7 @@ private void release(Processor processor) { */ @Override public void release(SocketWrapperBase<S> socketWrapper) { - Processor processor = (Processor) socketWrapper.getCurrentProcessor(); - socketWrapper.setCurrentProcessor(null); + Processor processor = (Processor) socketWrapper.takeCurrentProcessor(); release(processor); }
java/org/apache/tomcat/util/net/SocketWrapperBase.java+12 −5 modified@@ -29,6 +29,7 @@ import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; @@ -106,10 +107,12 @@ public abstract class SocketWrapperBase<E> { protected volatile OperationState<?> writeOperation = null; /** - * The org.apache.coyote.Processor instance currently associated - * with the wrapper. + * The org.apache.coyote.Processor instance currently associated with the + * wrapper. Only populated when required to maintain wrapper<->Processor + * mapping between calls to + * {@link AbstractEndpoint.Handler#process(SocketWrapperBase, SocketEvent)}. */ - protected Object currentProcessor = null; + private final AtomicReference<Object> currentProcessor = new AtomicReference<>(); public SocketWrapperBase(E socket, AbstractEndpoint<E,?> endpoint) { this.socket = socket; @@ -136,11 +139,15 @@ protected AbstractEndpoint<E,?> getEndpoint() { } public Object getCurrentProcessor() { - return currentProcessor; + return currentProcessor.get(); } public void setCurrentProcessor(Object currentProcessor) { - this.currentProcessor = currentProcessor; + this.currentProcessor.set(currentProcessor); + } + + public Object takeCurrentProcessor() { + return currentProcessor.getAndSet(null); } /**
webapps/docs/changelog.xml+4 −0 modified@@ -138,6 +138,10 @@ with TLS 1.3 but the JSSE TLS 1.3 implementation does not support PHA. (markt) </add> + <fix> + Improve the recycling of Processor objects to make it more robust. + (markt) + </fix> </changelog> </subsection> <subsection name="Jasper">
17f177eeb7dfImprove the recycling of Processor objects to make it more robust.
3 files changed · +33 −20
java/org/apache/coyote/AbstractProtocol.java+17 −15 modified@@ -767,7 +767,11 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { S socket = wrapper.getSocket(); - Processor processor = (Processor) wrapper.getCurrentProcessor(); + // We take complete ownership of the Processor inside of this method to ensure + // no other thread can release it while we're using it. Whatever processor is + // held by this variable will be associated with the SocketWrapper before this + // method returns. + Processor processor = (Processor) wrapper.takeCurrentProcessor(); if (getLog().isDebugEnabled()) { getLog().debug(sm.getString("abstractConnectionHandler.connectionsGet", processor, socket)); @@ -849,9 +853,6 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { processor.setSslSupport(wrapper.getSslSupport()); - // Associate the processor with the connection - wrapper.setCurrentProcessor(processor); - SocketState state = SocketState.CLOSED; do { state = processor.process(wrapper, status); @@ -871,8 +872,6 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { release(processor); // Create the upgrade processor processor = upgradeProtocol.getProcessor(wrapper, getProtocol().getAdapter()); - // Associate with the processor with the connection - wrapper.setCurrentProcessor(processor); } else { if (getLog().isDebugEnabled()) { getLog().debug(sm.getString( @@ -892,8 +891,6 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { getLog().debug(sm.getString("abstractConnectionHandler.upgradeCreate", processor, wrapper)); } - // Associate with the processor with the connection - wrapper.setCurrentProcessor(processor); // Initialise the upgrade handler (which may trigger // some IO using the new protocol which is why the lines // above are necessary) @@ -931,8 +928,8 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { } else if (state == SocketState.OPEN) { // In keep-alive but between requests. OK to recycle // processor. Continue to poll for the next request. - wrapper.setCurrentProcessor(null); release(processor); + processor = null; wrapper.registerReadInterest(); } else if (state == SocketState.SENDFILE) { // Sendfile in progress. If it fails, the socket will be @@ -957,8 +954,7 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { // Connection closed. OK to recycle the processor. // Processors handling upgrades require additional clean-up // before release. - wrapper.setCurrentProcessor(null); - if (processor.isUpgrade()) { + if (processor != null && processor.isUpgrade()) { UpgradeToken upgradeToken = processor.getUpgradeToken(); HttpUpgradeHandler httpUpgradeHandler = upgradeToken.getHttpUpgradeHandler(); InstanceManager instanceManager = upgradeToken.getInstanceManager(); @@ -979,7 +975,13 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { } } } + release(processor); + processor = null; + } + + if (processor != null) { + wrapper.setCurrentProcessor(processor); } return state; } catch(java.net.SocketException e) { @@ -1015,7 +1017,6 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { // Make sure socket/processor is removed from the list of current // connections - wrapper.setCurrentProcessor(null); release(processor); return SocketState.CLOSED; } @@ -1035,7 +1036,9 @@ protected void longPoll(SocketWrapperBase<?> socket, Processor processor) { /** * Expected to be used by the handler once the processor is no longer - * required. + * required. Care must be taken to ensure that this method is only + * called once per processor, after the request processing has + * completed. * * @param processor Processor being released (that was associated with * the socket) @@ -1073,8 +1076,7 @@ private void release(Processor processor) { */ @Override public void release(SocketWrapperBase<S> socketWrapper) { - Processor processor = (Processor) socketWrapper.getCurrentProcessor(); - socketWrapper.setCurrentProcessor(null); + Processor processor = (Processor) socketWrapper.takeCurrentProcessor(); release(processor); }
java/org/apache/tomcat/util/net/SocketWrapperBase.java+12 −5 modified@@ -30,6 +30,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; import jakarta.servlet.ServletConnection; @@ -122,10 +123,12 @@ public abstract class SocketWrapperBase<E> { protected volatile OperationState<?> writeOperation = null; /** - * The org.apache.coyote.Processor instance currently associated - * with the wrapper. + * The org.apache.coyote.Processor instance currently associated with the + * wrapper. Only populated when required to maintain wrapper<->Processor + * mapping between calls to + * {@link AbstractEndpoint.Handler#process(SocketWrapperBase, SocketEvent)}. */ - protected Object currentProcessor = null; + private final AtomicReference<Object> currentProcessor = new AtomicReference<>(); public SocketWrapperBase(E socket, AbstractEndpoint<E,?> endpoint) { this.socket = socket; @@ -153,11 +156,15 @@ protected AbstractEndpoint<E,?> getEndpoint() { } public Object getCurrentProcessor() { - return currentProcessor; + return currentProcessor.get(); } public void setCurrentProcessor(Object currentProcessor) { - this.currentProcessor = currentProcessor; + this.currentProcessor.set(currentProcessor); + } + + public Object takeCurrentProcessor() { + return currentProcessor.getAndSet(null); } /**
webapps/docs/changelog.xml+4 −0 modified@@ -143,6 +143,10 @@ with TLS 1.3 but the JSSE TLS 1.3 implementation does not support PHA. (markt) </add> + <fix> + Improve the recycling of Processor objects to make it more robust. + (markt) + </fix> </changelog> </subsection> <subsection name="Jasper">
9651b83a1d04Improve the recycling of Processor objects to make it more robust.
3 files changed · +33 −20
java/org/apache/coyote/AbstractProtocol.java+17 −15 modified@@ -774,7 +774,11 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { S socket = wrapper.getSocket(); - Processor processor = (Processor) wrapper.getCurrentProcessor(); + // We take complete ownership of the Processor inside of this method to ensure + // no other thread can release it while we're using it. Whatever processor is + // held by this variable will be associated with the SocketWrapper before this + // method returns. + Processor processor = (Processor) wrapper.takeCurrentProcessor(); if (getLog().isDebugEnabled()) { getLog().debug(sm.getString("abstractConnectionHandler.connectionsGet", processor, socket)); @@ -858,9 +862,6 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { processor.setSslSupport( wrapper.getSslSupport(getProtocol().getClientCertProvider())); - // Associate the processor with the connection - wrapper.setCurrentProcessor(processor); - SocketState state = SocketState.CLOSED; do { state = processor.process(wrapper, status); @@ -880,8 +881,6 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { release(processor); // Create the upgrade processor processor = upgradeProtocol.getProcessor(wrapper, getProtocol().getAdapter()); - // Associate with the processor with the connection - wrapper.setCurrentProcessor(processor); } else { if (getLog().isDebugEnabled()) { getLog().debug(sm.getString( @@ -901,8 +900,6 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { getLog().debug(sm.getString("abstractConnectionHandler.upgradeCreate", processor, wrapper)); } - // Associate with the processor with the connection - wrapper.setCurrentProcessor(processor); // Initialise the upgrade handler (which may trigger // some IO using the new protocol which is why the lines // above are necessary) @@ -940,8 +937,8 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { } else if (state == SocketState.OPEN) { // In keep-alive but between requests. OK to recycle // processor. Continue to poll for the next request. - wrapper.setCurrentProcessor(null); release(processor); + processor = null; wrapper.registerReadInterest(); } else if (state == SocketState.SENDFILE) { // Sendfile in progress. If it fails, the socket will be @@ -966,8 +963,7 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { // Connection closed. OK to recycle the processor. // Processors handling upgrades require additional clean-up // before release. - wrapper.setCurrentProcessor(null); - if (processor.isUpgrade()) { + if (processor != null && processor.isUpgrade()) { UpgradeToken upgradeToken = processor.getUpgradeToken(); HttpUpgradeHandler httpUpgradeHandler = upgradeToken.getHttpUpgradeHandler(); InstanceManager instanceManager = upgradeToken.getInstanceManager(); @@ -988,7 +984,13 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { } } } + release(processor); + processor = null; + } + + if (processor != null) { + wrapper.setCurrentProcessor(processor); } return state; } catch(java.net.SocketException e) { @@ -1024,7 +1026,6 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { // Make sure socket/processor is removed from the list of current // connections - wrapper.setCurrentProcessor(null); release(processor); return SocketState.CLOSED; } @@ -1044,7 +1045,9 @@ protected void longPoll(SocketWrapperBase<?> socket, Processor processor) { /** * Expected to be used by the handler once the processor is no longer - * required. + * required. Care must be taken to ensure that this method is only + * called once per processor, after the request processing has + * completed. * * @param processor Processor being released (that was associated with * the socket) @@ -1082,8 +1085,7 @@ private void release(Processor processor) { */ @Override public void release(SocketWrapperBase<S> socketWrapper) { - Processor processor = (Processor) socketWrapper.getCurrentProcessor(); - socketWrapper.setCurrentProcessor(null); + Processor processor = (Processor) socketWrapper.takeCurrentProcessor(); release(processor); }
java/org/apache/tomcat/util/net/SocketWrapperBase.java+12 −5 modified@@ -29,6 +29,7 @@ import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; @@ -104,10 +105,12 @@ public abstract class SocketWrapperBase<E> { protected volatile OperationState<?> writeOperation = null; /** - * The org.apache.coyote.Processor instance currently associated - * with the wrapper. + * The org.apache.coyote.Processor instance currently associated with the + * wrapper. Only populated when required to maintain wrapper<->Processor + * mapping between calls to + * {@link AbstractEndpoint.Handler#process(SocketWrapperBase, SocketEvent)}. */ - protected Object currentProcessor = null; + private final AtomicReference<Object> currentProcessor = new AtomicReference<>(); public SocketWrapperBase(E socket, AbstractEndpoint<E,?> endpoint) { this.socket = socket; @@ -134,11 +137,15 @@ protected AbstractEndpoint<E,?> getEndpoint() { } public Object getCurrentProcessor() { - return currentProcessor; + return currentProcessor.get(); } public void setCurrentProcessor(Object currentProcessor) { - this.currentProcessor = currentProcessor; + this.currentProcessor.set(currentProcessor); + } + + public Object takeCurrentProcessor() { + return currentProcessor.getAndSet(null); } /**
webapps/docs/changelog.xml+4 −0 modified@@ -138,6 +138,10 @@ with TLS 1.3 but the JSSE TLS 1.3 implementation does not support PHA. (markt) </add> + <fix> + Improve the recycling of Processor objects to make it more robust. + (markt) + </fix> </changelog> </subsection> <subsection name="Jasper">
4a00b0c08905Improve the recycling of Processor objects to make it more robust.
3 files changed · +33 −20
java/org/apache/coyote/AbstractProtocol.java+17 −15 modified@@ -790,7 +790,11 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { S socket = wrapper.getSocket(); - Processor processor = (Processor) wrapper.getCurrentProcessor(); + // We take complete ownership of the Processor inside of this method to ensure + // no other thread can release it while we're using it. Whatever processor is + // held by this variable will be associated with the SocketWrapper before this + // method returns. + Processor processor = (Processor) wrapper.takeCurrentProcessor(); if (getLog().isDebugEnabled()) { getLog().debug(sm.getString("abstractConnectionHandler.connectionsGet", processor, socket)); @@ -873,9 +877,6 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { processor.setSslSupport( wrapper.getSslSupport(getProtocol().getClientCertProvider())); - // Associate the processor with the connection - wrapper.setCurrentProcessor(processor); - SocketState state = SocketState.CLOSED; do { state = processor.process(wrapper, status); @@ -895,8 +896,6 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { release(processor); // Create the upgrade processor processor = upgradeProtocol.getProcessor(wrapper, getProtocol().getAdapter()); - // Associate with the processor with the connection - wrapper.setCurrentProcessor(processor); } else { if (getLog().isDebugEnabled()) { getLog().debug(sm.getString( @@ -916,8 +915,6 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { getLog().debug(sm.getString("abstractConnectionHandler.upgradeCreate", processor, wrapper)); } - // Associate with the processor with the connection - wrapper.setCurrentProcessor(processor); // Initialise the upgrade handler (which may trigger // some IO using the new protocol which is why the lines // above are necessary) @@ -949,8 +946,8 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { } else if (state == SocketState.OPEN) { // In keep-alive but between requests. OK to recycle // processor. Continue to poll for the next request. - wrapper.setCurrentProcessor(null); release(processor); + processor = null; wrapper.registerReadInterest(); } else if (state == SocketState.SENDFILE) { // Sendfile in progress. If it fails, the socket will be @@ -975,8 +972,7 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { // Connection closed. OK to recycle the processor. // Processors handling upgrades require additional clean-up // before release. - wrapper.setCurrentProcessor(null); - if (processor.isUpgrade()) { + if (processor != null && processor.isUpgrade()) { UpgradeToken upgradeToken = processor.getUpgradeToken(); HttpUpgradeHandler httpUpgradeHandler = upgradeToken.getHttpUpgradeHandler(); InstanceManager instanceManager = upgradeToken.getInstanceManager(); @@ -997,7 +993,13 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { } } } + release(processor); + processor = null; + } + + if (processor != null) { + wrapper.setCurrentProcessor(processor); } return state; } catch(java.net.SocketException e) { @@ -1033,7 +1035,6 @@ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { // Make sure socket/processor is removed from the list of current // connections - wrapper.setCurrentProcessor(null); release(processor); return SocketState.CLOSED; } @@ -1067,7 +1068,9 @@ public Set<S> getOpenSockets() { /** * Expected to be used by the handler once the processor is no longer - * required. + * required. Care must be taken to ensure that this method is only + * called once per processor, after the request processing has + * completed. * * @param processor Processor being released (that was associated with * the socket) @@ -1105,8 +1108,7 @@ private void release(Processor processor) { */ @Override public void release(SocketWrapperBase<S> socketWrapper) { - Processor processor = (Processor) socketWrapper.getCurrentProcessor(); - socketWrapper.setCurrentProcessor(null); + Processor processor = (Processor) socketWrapper.takeCurrentProcessor(); release(processor); }
java/org/apache/tomcat/util/net/SocketWrapperBase.java+12 −5 modified@@ -29,6 +29,7 @@ import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; @@ -106,10 +107,12 @@ public abstract class SocketWrapperBase<E> { protected volatile OperationState<?> writeOperation = null; /** - * The org.apache.coyote.Processor instance currently associated - * with the wrapper. + * The org.apache.coyote.Processor instance currently associated with the + * wrapper. Only populated when required to maintain wrapper<->Processor + * mapping between calls to + * {@link AbstractEndpoint.Handler#process(SocketWrapperBase, SocketEvent)}. */ - protected Object currentProcessor = null; + private final AtomicReference<Object> currentProcessor = new AtomicReference<>(); public SocketWrapperBase(E socket, AbstractEndpoint<E,?> endpoint) { this.socket = socket; @@ -136,11 +139,15 @@ public AbstractEndpoint<E,?> getEndpoint() { } public Object getCurrentProcessor() { - return currentProcessor; + return currentProcessor.get(); } public void setCurrentProcessor(Object currentProcessor) { - this.currentProcessor = currentProcessor; + this.currentProcessor.set(currentProcessor); + } + + public Object takeCurrentProcessor() { + return currentProcessor.getAndSet(null); } /**
webapps/docs/changelog.xml+4 −0 modified@@ -138,6 +138,10 @@ with TLS 1.3 but the JSSE TLS 1.3 implementation does not support PHA. (markt) </add> + <fix> + Improve the recycling of Processor objects to make it more robust. + (markt) + </fix> </changelog> </subsection> <subsection name="Jasper">
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
13- github.com/advisories/GHSA-jx7c-7mj5-9438ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-43980ghsaADVISORY
- www.debian.org/security/2022/dsa-5265ghsavendor-advisoryWEB
- www.openwall.com/lists/oss-security/2022/09/28/1ghsamailing-listWEB
- github.com/apache/tomcat/commit/170e0f792bd18ff031677890ba2fe50eb7a376c1ghsaWEB
- github.com/apache/tomcat/commit/17f177eeb7df5938f67ef9ea580411b120195f13ghsaWEB
- github.com/apache/tomcat/commit/4a00b0c0890538b9d3107eef8f2e0afadd119bebghsaWEB
- github.com/apache/tomcat/commit/9651b83a1d04583791525e5f0c4c9089f678d9fcghsaWEB
- lists.apache.org/thread/3jjqbsp6j88b198x5rmg99b1qr8ht3g3ghsaWEB
- lists.debian.org/debian-lts-announce/2022/10/msg00029.htmlghsamailing-listWEB
- tomcat.apache.org/security-10.htmlghsaWEB
- tomcat.apache.org/security-8.htmlghsaWEB
- tomcat.apache.org/security-9.htmlghsaWEB
News mentions
0No linked articles in our index yet.