[Git][java-team/tomcat8][jessie] 2 commits: Import Debian changes 8.0.14-1+deb8u16

Markus Koschany gitlab at salsa.debian.org
Wed Jul 15 22:16:30 BST 2020



Markus Koschany pushed to branch jessie at Debian Java Maintainers / tomcat8


Commits:
fb38f8de by Abhijith PA at 2020-07-15T23:15:39+02:00
Import Debian changes 8.0.14-1+deb8u16

tomcat8 (8.0.14-1+deb8u16) jessie-security; urgency=medium
..
  * Non-maintainer upload by the Debian LTS Team.
  * Fix CVE-2019-12418: manipulate the RMI registry to perform a
    man-in-the-middle attack via JMX Remote Lifecycle Listener

- - - - -
42ca7330 by Markus Koschany at 2020-07-15T23:15:58+02:00
Import Debian changes 8.0.14-1+deb8u17

tomcat8 (8.0.14-1+deb8u17) jessie-security; urgency=high
..
  * Non-maintainer upload by the LTS team.
..
  * WARNING: The fix for CVE-2020-1938 may disrupt services that rely on a
    working AJP configuration. The option secretRequired defaults to true now.
    You should define a secret in your server.xml or you can revert back by
    setting secretRequired to false.
..
  * Fix CVE-2019-17563:
    When using FORM authentication with Apache Tomcat there was a narrow window
    where an attacker could perform a session fixation attack. The window was
    considered too narrow for an exploit to be practical but, erring on the
    side of caution, this issue has been treated as a security vulnerability.
  * Fix CVE-2020-1935:
    In Apache Tomcat the HTTP header parsing code used an approach to
    end-of-line parsing that allowed some invalid HTTP headers to be parsed as
    valid. This led to a possibility of HTTP Request Smuggling if Tomcat was
    located behind a reverse proxy that incorrectly handled the invalid
    Transfer-Encoding header in a particular manner. Such a reverse proxy is
    considered unlikely.
  * Fix CVE-2020-1938:
    When using the Apache JServ Protocol (AJP), care must be taken when
    trusting incoming connections to Apache Tomcat. Tomcat treats AJP
    connections as having higher trust than, for example, a similar HTTP
    connection. If such connections are available to an attacker, they can be
    exploited in ways that may be surprising. Previously Tomcat shipped with an
    AJP Connector enabled by default that listened on all configured IP
    addresses. It was expected (and recommended in the security guide) that
    this Connector would be disabled if not required.
    .
    Note that Debian already disabled the AJP connector by default.
    Mitigation is only required if the AJP port was made accessible to
    untrusted users.
  * Fix CVE-2020-9484:
    When using Apache Tomcat and an attacker is able to control the contents
    and name of a file on the server; and b) the server is configured to use
    the PersistenceManager with a FileStore; and c) the PersistenceManager is
    configured with sessionAttributeValueClassNameFilter="null" (the default
    unless a SecurityManager is used) or a sufficiently lax filter to allow the
    attacker provided object to be deserialized; and d) the attacker knows the
    relative file path from the storage location used by FileStore to the file
    the attacker has control over; then, using a specifically crafted request,
    the attacker will be able to trigger remote code execution via
    deserialization of the file under their control. Note that all of
    conditions a) to d) must be true for the attack to succeed.

- - - - -


7 changed files:

- debian/changelog
- + debian/patches/CVE-2019-12418.patch
- + debian/patches/CVE-2019-17563.patch
- + debian/patches/CVE-2020-1935.patch
- + debian/patches/CVE-2020-1938.patch
- + debian/patches/CVE-2020-9484.patch
- debian/patches/series


Changes:

=====================================
debian/changelog
=====================================
@@ -1,3 +1,60 @@
+tomcat8 (8.0.14-1+deb8u17) jessie-security; urgency=high
+
+  * Non-maintainer upload by the LTS team.
+
+  * WARNING: The fix for CVE-2020-1938 may disrupt services that rely on a
+    working AJP configuration. The option secretRequired defaults to true now.
+    You should define a secret in your server.xml or you can revert back by
+    setting secretRequired to false.
+
+  * Fix CVE-2019-17563:
+    When using FORM authentication with Apache Tomcat there was a narrow window
+    where an attacker could perform a session fixation attack. The window was
+    considered too narrow for an exploit to be practical but, erring on the
+    side of caution, this issue has been treated as a security vulnerability.
+  * Fix CVE-2020-1935:
+    In Apache Tomcat the HTTP header parsing code used an approach to
+    end-of-line parsing that allowed some invalid HTTP headers to be parsed as
+    valid. This led to a possibility of HTTP Request Smuggling if Tomcat was
+    located behind a reverse proxy that incorrectly handled the invalid
+    Transfer-Encoding header in a particular manner. Such a reverse proxy is
+    considered unlikely.
+  * Fix CVE-2020-1938:
+    When using the Apache JServ Protocol (AJP), care must be taken when
+    trusting incoming connections to Apache Tomcat. Tomcat treats AJP
+    connections as having higher trust than, for example, a similar HTTP
+    connection. If such connections are available to an attacker, they can be
+    exploited in ways that may be surprising. Previously Tomcat shipped with an
+    AJP Connector enabled by default that listened on all configured IP
+    addresses. It was expected (and recommended in the security guide) that
+    this Connector would be disabled if not required.
+    .
+    Note that Debian already disabled the AJP connector by default.
+    Mitigation is only required if the AJP port was made accessible to
+    untrusted users.
+  * Fix CVE-2020-9484:
+    When using Apache Tomcat and an attacker is able to control the contents
+    and name of a file on the server; and b) the server is configured to use
+    the PersistenceManager with a FileStore; and c) the PersistenceManager is
+    configured with sessionAttributeValueClassNameFilter="null" (the default
+    unless a SecurityManager is used) or a sufficiently lax filter to allow the
+    attacker provided object to be deserialized; and d) the attacker knows the
+    relative file path from the storage location used by FileStore to the file
+    the attacker has control over; then, using a specifically crafted request,
+    the attacker will be able to trigger remote code execution via
+    deserialization of the file under their control. Note that all of
+    conditions a) to d) must be true for the attack to succeed.
+
+ -- Markus Koschany <apo at debian.org>  Thu, 28 May 2020 18:08:54 +0200
+
+tomcat8 (8.0.14-1+deb8u16) jessie-security; urgency=medium
+
+  * Non-maintainer upload by the Debian LTS Team.
+  * Fix CVE-2019-12418: manipulate the RMI registry to perform a
+    man-in-the-middle attack via JMX Remote Lifecycle Listener
+
+ -- Abhijith PA <abhijith at debian.org>  Mon, 23 Mar 2020 22:58:21 +0530
+
 tomcat8 (8.0.14-1+deb8u15) jessie-security; urgency=high
 
   * Non-maintainer upload by the LTS team.


=====================================
debian/patches/CVE-2019-12418.patch
=====================================
@@ -0,0 +1,113 @@
+Description: CVE-2019-12418
+ tomcat is configured with the JMX Remote Lifecycle Listener, a local
+ attacker without access to the Tomcat process or configuration files
+ is able to manipulate the RMI registry to perform a
+ man-in-the-middle attack to capture user names and passwords used to
+ access the JMX interface. The attacker can then use these
+ credentials to access the JMX interface and gain complete control
+ over the Tomcat instance.
+
+--
+Origin: https://github.com/apache/tomcat/commit/a91d7db4047d372b2f12999d3cf2bc3254c20d00
+Author: Abhijith PA <abhijith at debian.org>
+
+diff --git a/java/org/apache/catalina/mbeans/JmxRemoteLifecycleListener.java b/java/org/apache/catalina/mbeans/JmxRemoteLifecycleListener.java
+index d16d18cbdaeeb3512e563936deca498f8531b41f..2ccd9ce55cd8cdf8e2e1e7f132c60cd189e8b813 100644
+--- a/java/org/apache/catalina/mbeans/JmxRemoteLifecycleListener.java
++++ b/java/org/apache/catalina/mbeans/JmxRemoteLifecycleListener.java
+@@ -25,10 +25,11 @@ import java.net.MalformedURLException;
+ import java.net.ServerSocket;
+ import java.net.Socket;
+ import java.net.UnknownHostException;
++import java.rmi.AccessException;
+ import java.rmi.AlreadyBoundException;
++import java.rmi.NotBoundException;
++import java.rmi.Remote;
+ import java.rmi.RemoteException;
+-import java.rmi.registry.LocateRegistry;
+-import java.rmi.registry.Registry;
+ import java.rmi.server.RMIClientSocketFactory;
+ import java.rmi.server.RMIServerSocketFactory;
+ import java.security.NoSuchAlgorithmException;
+@@ -300,17 +301,6 @@ public class JmxRemoteLifecycleListener implements LifecycleListener {
+             RMIClientSocketFactory registryCsf, RMIServerSocketFactory registrySsf,
+             RMIClientSocketFactory serverCsf, RMIServerSocketFactory serverSsf) {
+ 
+-        // Create the RMI registry
+-        Registry registry;
+-        try {
+-            registry = LocateRegistry.createRegistry(
+-                    theRmiRegistryPort, registryCsf, registrySsf);
+-        } catch (RemoteException e) {
+-            log.error(sm.getString(
+-                    "jmxRemoteLifecycleListener.createRegistryFailed",
+-                    serverName, Integer.toString(theRmiRegistryPort)), e);
+-            return null;
+-        }
+ 
+         if (bindAddress == null) {
+             bindAddress = "localhost";
+@@ -332,11 +322,20 @@ public class JmxRemoteLifecycleListener implements LifecycleListener {
+             cs = new RMIConnectorServer(serviceUrl, theEnv, server,
+                     ManagementFactory.getPlatformMBeanServer());
+             cs.start();
+-            registry.bind("jmxrmi", server.toStub());
++            Remote jmxServer = server.toStub();
++            // Create the RMI registry
++            try {
++                new JmxRegistry(theRmiRegistryPort, registryCsf, registrySsf, "jmxrmi", jmxServer);
++            } catch (RemoteException e) {
++                log.error(sm.getString(
++                        "jmxRemoteLifecycleListener.createRegistryFailed",
++                        serverName, Integer.toString(theRmiRegistryPort)), e);
++                return null;
++            }
+             log.info(sm.getString("jmxRemoteLifecycleListener.start",
+                     Integer.toString(theRmiRegistryPort),
+                     Integer.toString(theRmiServerPort), serverName));
+-        } catch (IOException | AlreadyBoundException e) {
++       } catch (IOException e) {
+             log.error(sm.getString(
+                     "jmxRemoteLifecycleListener.createServerFailed",
+                     serverName), e);
+@@ -465,4 +464,39 @@ public class JmxRemoteLifecycleListener implements LifecycleListener {
+             return sslServerSocket;
+         }
+     }
++
++
++private static class JmxRegistry extends sun.rmi.registry.RegistryImpl {
++        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 };
++        }
++    }
++
+ }
+


=====================================
debian/patches/CVE-2019-17563.patch
=====================================
@@ -0,0 +1,193 @@
+From: Markus Koschany <apo at debian.org>
+Date: Sat, 11 Apr 2020 21:02:11 +0200
+Subject: CVE-2019-17563
+
+Origin: https://github.com/apache/tomcat/commit/e19a202ee43b6e2a538be5515ae0ab32d8ef112c
+---
+ .../catalina/authenticator/AuthenticatorBase.java  |  5 +-
+ .../apache/catalina/authenticator/Constants.java   |  3 +
+ .../catalina/authenticator/FormAuthenticator.java  | 94 +++++++++-------------
+ 3 files changed, 43 insertions(+), 59 deletions(-)
+
+diff --git a/java/org/apache/catalina/authenticator/AuthenticatorBase.java b/java/org/apache/catalina/authenticator/AuthenticatorBase.java
+index 880dd16..a0d700e 100644
+--- a/java/org/apache/catalina/authenticator/AuthenticatorBase.java
++++ b/java/org/apache/catalina/authenticator/AuthenticatorBase.java
+@@ -764,10 +764,11 @@ public abstract class AuthenticatorBase extends ValveBase
+         }
+ 
+         // Cache the authentication information in our session, if any
+-        if (cache) {
+-            if (session != null) {
++        if (session != null) {
++            if (cache) {
+                 session.setAuthType(authType);
+                 session.setPrincipal(principal);
++            } else {
+                 if (username != null) {
+                     session.setNote(Constants.SESS_USERNAME_NOTE, username);
+                 } else {
+diff --git a/java/org/apache/catalina/authenticator/Constants.java b/java/org/apache/catalina/authenticator/Constants.java
+index 1f35b52..c8b51f6 100644
+--- a/java/org/apache/catalina/authenticator/Constants.java
++++ b/java/org/apache/catalina/authenticator/Constants.java
+@@ -94,7 +94,10 @@ public class Constants {
+ 
+     /**
+      * The previously authenticated principal (if caching is disabled).
++     *
++     * @deprecated Unused. Will be removed in Tomcat 10.
+      */
++      @Deprecated
+     public static final String FORM_PRINCIPAL_NOTE =
+         "org.apache.catalina.authenticator.PRINCIPAL";
+ 
+diff --git a/java/org/apache/catalina/authenticator/FormAuthenticator.java b/java/org/apache/catalina/authenticator/FormAuthenticator.java
+index d3343b4..a0dd72b 100644
+--- a/java/org/apache/catalina/authenticator/FormAuthenticator.java
++++ b/java/org/apache/catalina/authenticator/FormAuthenticator.java
+@@ -127,40 +127,9 @@ public class FormAuthenticator
+ 
+         // References to objects we will need later
+         Session session = null;
++        Principal principal = null;
+ 
+-        // Have we already authenticated someone?
+-        Principal principal = request.getUserPrincipal();
+-        String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
+-        if (principal != null) {
+-            if (log.isDebugEnabled()) {
+-                log.debug("Already authenticated '" +
+-                    principal.getName() + "'");
+-            }
+-            // Associate the session with any existing SSO session
+-            if (ssoId != null) {
+-                associate(ssoId, request.getSessionInternal(true));
+-            }
+-            return true;
+-        }
+-
+-        // Is there an SSO session against which we can try to reauthenticate?
+-        if (ssoId != null) {
+-            if (log.isDebugEnabled()) {
+-                log.debug("SSO Id " + ssoId + " set; attempting " +
+-                          "reauthentication");
+-            }
+-            // Try to reauthenticate using data cached by SSO.  If this fails,
+-            // either the original SSO logon was of DIGEST or SSL (which
+-            // we can't reauthenticate ourselves because there is no
+-            // cached username and password), or the realm denied
+-            // the user's reauthentication for some reason.
+-            // In either case we have to prompt the user for a logon */
+-            if (reauthenticateFromSSO(ssoId, request)) {
+-                return true;
+-            }
+-        }
+-
+-        // Have we authenticated this user before but have caching disabled?
++         // Have we authenticated this user before but have caching disabled?
+         if (!cache) {
+             session = request.getSessionInternal(true);
+             if (log.isDebugEnabled()) {
+@@ -177,11 +146,8 @@ public class FormAuthenticator
+                 principal =
+                     context.getRealm().authenticate(username, password);
+                 if (principal != null) {
+-                    session.setNote(Constants.FORM_PRINCIPAL_NOTE, principal);
++                   register(request, response, principal, HttpServletRequest.FORM_AUTH, username, password);
+                     if (!matchRequest(request)) {
+-                        register(request, response, principal,
+-                                HttpServletRequest.FORM_AUTH,
+-                                username, password);
+                         return true;
+                     }
+                 }
+@@ -200,17 +166,6 @@ public class FormAuthenticator
+                           + session.getIdInternal()
+                           + "'");
+             }
+-            principal = (Principal)
+-                session.getNote(Constants.FORM_PRINCIPAL_NOTE);
+-            register(request, response, principal, HttpServletRequest.FORM_AUTH,
+-                     (String) session.getNote(Constants.SESS_USERNAME_NOTE),
+-                     (String) session.getNote(Constants.SESS_PASSWORD_NOTE));
+-            // If we're caching principals we no longer need the username
+-            // and password in the session, so remove them
+-            if (cache) {
+-                session.removeNote(Constants.SESS_USERNAME_NOTE);
+-                session.removeNote(Constants.SESS_PASSWORD_NOTE);
+-            }
+             if (restoreRequest(request, session)) {
+                 if (log.isDebugEnabled()) {
+                     log.debug("Proceed to restored request");
+@@ -225,6 +180,38 @@ public class FormAuthenticator
+             }
+         }
+ 
++       // Have we already authenticated someone?
++        principal = request.getUserPrincipal();
++        String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
++        if (principal != null) {
++            if (log.isDebugEnabled()) {
++                log.debug("Already authenticated '" +
++                    principal.getName() + "'");
++            }
++            // Associate the session with any existing SSO session
++            if (ssoId != null) {
++                associate(ssoId, request.getSessionInternal(true));
++            }
++            return true;
++        }
++
++        // Is there an SSO session against which we can try to reauthenticate?
++        if (ssoId != null) {
++            if (log.isDebugEnabled()) {
++                log.debug("SSO Id " + ssoId + " set; attempting " +
++                          "reauthentication");
++            }
++            // Try to reauthenticate using data cached by SSO.  If this fails,
++            // either the original SSO logon was of DIGEST or SSL (which
++            // we can't reauthenticate ourselves because there is no
++            // cached username and password), or the realm denied
++            // the user's reauthentication for some reason.
++            // In either case we have to prompt the user for a logon */
++            if (reauthenticateFromSSO(ssoId, request)) {
++                return true;
++            }
++        }
++
+         // Acquire references to objects we will need to evaluate
+         MessageBytes uriMB = MessageBytes.newInstance();
+         CharChunk uriCC = uriMB.getCharChunk();
+@@ -319,13 +306,7 @@ public class FormAuthenticator
+             return false;
+         }
+ 
+-        // Save the authenticated Principal in our session
+-        session.setNote(Constants.FORM_PRINCIPAL_NOTE, principal);
+-
+-        // Save the username and password as well
+-        session.setNote(Constants.SESS_USERNAME_NOTE, username);
+-        session.setNote(Constants.SESS_PASSWORD_NOTE, password);
+-
++        register(request, response, principal, HttpServletRequest.FORM_AUTH, username, password);
+         // Redirect the user to the original request URI (which will cause
+         // the original request to be restored)
+         requestURI = savedRequestURL(session);
+@@ -503,7 +484,7 @@ public class FormAuthenticator
+         }
+ 
+         // Is there a saved principal?
+-        if (session.getNote(Constants.FORM_PRINCIPAL_NOTE) == null) {
++        if (cache && session.getPrincipal() == null || !cache && request.getPrincipal() == null) {
+             return false;
+         }
+ 
+@@ -532,7 +513,6 @@ public class FormAuthenticator
+         SavedRequest saved = (SavedRequest)
+             session.getNote(Constants.FORM_REQUEST_NOTE);
+         session.removeNote(Constants.FORM_REQUEST_NOTE);
+-        session.removeNote(Constants.FORM_PRINCIPAL_NOTE);
+         if (saved == null) {
+             return false;
+         }


=====================================
debian/patches/CVE-2020-1935.patch
=====================================
@@ -0,0 +1,549 @@
+From: Markus Koschany <apo at debian.org>
+Date: Sat, 11 Apr 2020 16:53:39 +0200
+Subject: CVE-2020-1935
+
+Origin: https://github.com/apache/tomcat/commit/8fbe2e962f0ea138d92361921643fe5abe0c4f56
+---
+ .../coyote/http11/AbstractNioInputBuffer.java      | 38 ++++++++--
+ .../coyote/http11/InternalAprInputBuffer.java      | 55 ++++++++++----
+ .../apache/coyote/http11/InternalInputBuffer.java  | 59 +++++++++++----
+ java/org/apache/tomcat/util/http/MimeHeaders.java  |  2 +-
+ .../apache/tomcat/util/http/parser/HttpParser.java | 10 +++
+ .../coyote/http11/TestInternalInputBuffer.java     | 87 ++++++++++++++++++++--
+ webapps/docs/changelog.xml                         |  5 ++
+ 7 files changed, 212 insertions(+), 44 deletions(-)
+
+diff --git a/java/org/apache/coyote/http11/AbstractNioInputBuffer.java b/java/org/apache/coyote/http11/AbstractNioInputBuffer.java
+index 1a60b8c..85b1c3f 100644
+--- a/java/org/apache/coyote/http11/AbstractNioInputBuffer.java
++++ b/java/org/apache/coyote/http11/AbstractNioInputBuffer.java
+@@ -421,6 +421,8 @@ public abstract class AbstractNioInputBuffer<S> extends AbstractInputBuffer<S> {
+         //
+ 
+         byte chr = 0;
++        byte prevChr = 0;
++
+         while (headerParsePos == HeaderParsePosition.HEADER_START) {
+ 
+             // Read new bytes if needed
+@@ -431,14 +433,19 @@ public abstract class AbstractNioInputBuffer<S> extends AbstractInputBuffer<S> {
+                 }
+             }
+ 
++            prevChr = chr;
+             chr = buf[pos];
+ 
+-            if (chr == Constants.CR) {
+-                // Skip
+-            } else if (chr == Constants.LF) {
++            if (chr == Constants.CR && prevChr != Constants.CR) {
++                // Possible start of CRLF - process the next byte.
++            } else if (prevChr == Constants.CR && chr == Constants.LF) {
+                 pos++;
+                 return HeaderParseStatus.DONE;
+             } else {
++                if (prevChr == Constants.CR) {
++                    // Must have read two bytes (first was CR, second was not LF)
++                    pos--;
++                }
+                 break;
+             }
+ 
+@@ -537,11 +544,22 @@ public abstract class AbstractNioInputBuffer<S> extends AbstractInputBuffer<S> {
+                         }
+                     }
+ 
++                    prevChr = chr;
+                     chr = buf[pos];
+                     if (chr == Constants.CR) {
+-                        // Skip
+-                    } else if (chr == Constants.LF) {
++                        // Possible start of CRLF - process the next byte.
++                    } else if (prevChr == Constants.CR && chr == Constants.LF) {
+                         eol = true;
++                    } else if (prevChr == Constants.CR) {
++                        // Invalid value
++                        // Delete the header (it will be the most recent one)
++                        headers.removeHeader(headers.size() - 1);
++                        return skipLine();
++                    } else if (chr != Constants.HT && HttpParser.isControl(chr)) {
++                        // Invalid value
++                        // Delete the header (it will be the most recent one)
++                        headers.removeHeader(headers.size() - 1);
++                        return skipLine();
+                     } else if (chr == Constants.SP || chr == Constants.HT) {
+                         buf[headerData.realPos] = chr;
+                         headerData.realPos++;
+@@ -598,6 +616,9 @@ public abstract class AbstractNioInputBuffer<S> extends AbstractInputBuffer<S> {
+         headerParsePos = HeaderParsePosition.HEADER_SKIPLINE;
+         boolean eol = false;
+ 
++        byte chr = 0;
++        byte prevChr = 0;
++
+         // Reading bytes until the end of the line
+         while (!eol) {
+ 
+@@ -608,9 +629,12 @@ public abstract class AbstractNioInputBuffer<S> extends AbstractInputBuffer<S> {
+                 }
+             }
+ 
+-            if (buf[pos] == Constants.CR) {
++            prevChr = chr;
++            chr = buf[pos];
++
++            if (chr == Constants.CR) {
+                 // Skip
+-            } else if (buf[pos] == Constants.LF) {
++            } else if (prevChr == Constants.CR && chr == Constants.LF) {
+                 eol = true;
+             } else {
+                 headerData.lastSignificantChar = pos;
+diff --git a/java/org/apache/coyote/http11/InternalAprInputBuffer.java b/java/org/apache/coyote/http11/InternalAprInputBuffer.java
+index 56a9a6f..cf44250 100644
+--- a/java/org/apache/coyote/http11/InternalAprInputBuffer.java
++++ b/java/org/apache/coyote/http11/InternalAprInputBuffer.java
+@@ -345,6 +345,8 @@ public class InternalAprInputBuffer extends AbstractInputBuffer<Long> {
+         //
+ 
+         byte chr = 0;
++        byte prevChr = 0;
++
+         while (true) {
+ 
+             // Read new bytes if needed
+@@ -353,23 +355,28 @@ public class InternalAprInputBuffer extends AbstractInputBuffer<Long> {
+                     throw new EOFException(sm.getString("iib.eof.error"));
+             }
+ 
++            prevChr = chr;
+             chr = buf[pos];
+ 
+-            if (chr == Constants.CR) {
+-                // Skip
+-            } else if (chr == Constants.LF) {
+-                pos++;
++            if (chr == Constants.CR && prevChr != Constants.CR) {
++                // Possible start of CRLF - process the next byte.
++            } else if (prevChr == Constants.CR && chr == Constants.LF) {
++                    pos++;
+                 return false;
+             } else {
++                if (prevChr == Constants.CR) {
++                    // Must have read two bytes (first was CR, second was not LF)
++                    pos--;
++                }
+                 break;
+             }
+ 
+             pos++;
+-
+         }
+ 
+         // Mark the current buffer position
+         int start = pos;
++        int lineStart = start;
+ 
+         //
+         // Reading the header name
+@@ -393,7 +400,7 @@ public class InternalAprInputBuffer extends AbstractInputBuffer<Long> {
+             } else if (!HttpParser.isToken(buf[pos])) {
+                 // If a non-token header is detected, skip the line and
+                 // ignore the header
+-                skipLine(start);
++                skipLine(lineStart, start);
+                 return true;
+             }
+             chr = buf[pos];
+@@ -448,10 +455,24 @@ public class InternalAprInputBuffer extends AbstractInputBuffer<Long> {
+                         throw new EOFException(sm.getString("iib.eof.error"));
+                 }
+ 
+-                if (buf[pos] == Constants.CR) {
+-                    // Skip
+-                } else if (buf[pos] == Constants.LF) {
++                prevChr = chr;
++                chr = buf[pos];
++                if (chr == Constants.CR) {
++                    // Possible start of CRLF - process the next byte.
++                } else if (prevChr == Constants.CR && chr == Constants.LF) {
+                     eol = true;
++                } else if (prevChr == Constants.CR) {
++                    // Invalid value
++                    // Delete the header (it will be the most recent one)
++                    headers.removeHeader(headers.size() - 1);
++                    skipLine(lineStart, start);
++                    return true;
++                } else if (chr != Constants.HT && HttpParser.isControl(chr)) {
++                    // Invalid value
++                    // Delete the header (it will be the most recent one)
++                    headers.removeHeader(headers.size() - 1);
++                    skipLine(lineStart, start);
++                    return true;
+                 } else if (buf[pos] == Constants.SP) {
+                     buf[realPos] = buf[pos];
+                     realPos++;
+@@ -497,13 +518,16 @@ public class InternalAprInputBuffer extends AbstractInputBuffer<Long> {
+     }
+ 
+ 
+-    private void skipLine(int start) throws IOException {
++    private void skipLine(int lineStart, int start) throws IOException {
+         boolean eol = false;
+         int lastRealByte = start;
+         if (pos - 1 > start) {
+             lastRealByte = pos - 1;
+         }
+ 
++        byte chr = 0;
++        byte prevChr = 0;
++
+         while (!eol) {
+ 
+             // Read new bytes if needed
+@@ -512,9 +536,12 @@ public class InternalAprInputBuffer extends AbstractInputBuffer<Long> {
+                     throw new EOFException(sm.getString("iib.eof.error"));
+             }
+ 
+-            if (buf[pos] == Constants.CR) {
++            prevChr = chr;
++            chr = buf[pos];
++
++            if (chr == Constants.CR) {
+                 // Skip
+-            } else if (buf[pos] == Constants.LF) {
++            } else if (prevChr == Constants.CR && chr == Constants.LF) {
+                 eol = true;
+             } else {
+                 lastRealByte = pos;
+@@ -523,8 +550,8 @@ public class InternalAprInputBuffer extends AbstractInputBuffer<Long> {
+         }
+ 
+         if (log.isDebugEnabled()) {
+-            log.debug(sm.getString("iib.invalidheader", new String(buf, start,
+-                    lastRealByte - start + 1, StandardCharsets.ISO_8859_1)));
++            log.debug(sm.getString("iib.invalidheader", new String(buf, lineStart,
++                    lastRealByte - lineStart + 1, StandardCharsets.ISO_8859_1)));
+         }
+     }
+ 
+diff --git a/java/org/apache/coyote/http11/InternalInputBuffer.java b/java/org/apache/coyote/http11/InternalInputBuffer.java
+index ac8dbfb..02d7215 100644
+--- a/java/org/apache/coyote/http11/InternalInputBuffer.java
++++ b/java/org/apache/coyote/http11/InternalInputBuffer.java
+@@ -306,6 +306,8 @@ public class InternalInputBuffer extends AbstractInputBuffer<Socket> {
+         //
+ 
+         byte chr = 0;
++        byte prevChr = 0;
++
+         while (true) {
+ 
+             // Read new bytes if needed
+@@ -314,23 +316,28 @@ public class InternalInputBuffer extends AbstractInputBuffer<Socket> {
+                     throw new EOFException(sm.getString("iib.eof.error"));
+             }
+ 
++            prevChr = chr;
+             chr = buf[pos];
+ 
+-            if (chr == Constants.CR) {
+-                // Skip
+-            } else if (chr == Constants.LF) {
++            if (chr == Constants.CR && prevChr != Constants.CR) {
++                // Possible start of CRLF - process the next byte.
++            } else if (prevChr == Constants.CR && chr == Constants.LF) {
+                 pos++;
+                 return false;
+             } else {
++                if (prevChr == Constants.CR) {
++                    // Must have read two bytes (first was CR, second was not LF)
++                    pos--;
++                }
+                 break;
+             }
+ 
+             pos++;
+-
+         }
+ 
+         // Mark the current buffer position
+         int start = pos;
++        int lineStart = start;
+ 
+         //
+         // Reading the header name
+@@ -354,7 +361,7 @@ public class InternalInputBuffer extends AbstractInputBuffer<Socket> {
+             } else if (!HttpParser.isToken(buf[pos])) {
+                 // If a non-token header is detected, skip the line and
+                 // ignore the header
+-                skipLine(start);
++                skipLine(lineStart, start);
+                 return true;
+             }
+ 
+@@ -410,15 +417,29 @@ public class InternalInputBuffer extends AbstractInputBuffer<Socket> {
+                         throw new EOFException(sm.getString("iib.eof.error"));
+                 }
+ 
+-                if (buf[pos] == Constants.CR) {
+-                    // Skip
+-                } else if (buf[pos] == Constants.LF) {
++                prevChr = chr;
++                chr = buf[pos];
++                if (chr == Constants.CR) {
++                    // Possible start of CRLF - process the next byte.
++                } else if (prevChr == Constants.CR && chr == Constants.LF) {
+                     eol = true;
+-                } else if (buf[pos] == Constants.SP) {
+-                    buf[realPos] = buf[pos];
++                } else if (prevChr == Constants.CR) {
++                    // Invalid value
++                    // Delete the header (it will be the most recent one)
++                    headers.removeHeader(headers.size() - 1);
++                    skipLine(lineStart, start);
++                    return true;
++                } else if (chr != Constants.HT && HttpParser.isControl(chr)) {
++                    // Invalid value
++                    // Delete the header (it will be the most recent one)
++                    headers.removeHeader(headers.size() - 1);
++                    skipLine(lineStart, start);
++                    return true;
++                } else if (chr == Constants.SP) {
++                    buf[realPos] = chr;
+                     realPos++;
+                 } else {
+-                    buf[realPos] = buf[pos];
++                    buf[realPos] = chr;
+                     realPos++;
+                     lastSignificantChar = realPos;
+                 }
+@@ -477,13 +498,16 @@ public class InternalInputBuffer extends AbstractInputBuffer<Socket> {
+ 
+ 
+ 
+-    private void skipLine(int start) throws IOException {
++    private void skipLine(int lineStart, int start) throws IOException {
+         boolean eol = false;
+         int lastRealByte = start;
+         if (pos - 1 > start) {
+             lastRealByte = pos - 1;
+         }
+ 
++        byte chr = 0;
++        byte prevChr = 0;
++
+         while (!eol) {
+ 
+             // Read new bytes if needed
+@@ -492,9 +516,12 @@ public class InternalInputBuffer extends AbstractInputBuffer<Socket> {
+                     throw new EOFException(sm.getString("iib.eof.error"));
+             }
+ 
+-            if (buf[pos] == Constants.CR) {
++            prevChr = chr;
++            chr = buf[pos];
++
++            if (chr == Constants.CR) {
+                 // Skip
+-            } else if (buf[pos] == Constants.LF) {
++            } else if (prevChr == Constants.CR && chr == Constants.LF) {
+                 eol = true;
+             } else {
+                 lastRealByte = pos;
+@@ -503,8 +530,8 @@ public class InternalInputBuffer extends AbstractInputBuffer<Socket> {
+         }
+ 
+         if (log.isDebugEnabled()) {
+-            log.debug(sm.getString("iib.invalidheader", new String(buf, start,
+-                    lastRealByte - start + 1, StandardCharsets.ISO_8859_1)));
++            log.debug(sm.getString("iib.invalidheader", new String(buf, lineStart,
++                    lastRealByte - lineStart + 1, StandardCharsets.ISO_8859_1)));
+         }
+     }
+ 
+diff --git a/java/org/apache/tomcat/util/http/MimeHeaders.java b/java/org/apache/tomcat/util/http/MimeHeaders.java
+index b1e2bdb..9414a4a 100644
+--- a/java/org/apache/tomcat/util/http/MimeHeaders.java
++++ b/java/org/apache/tomcat/util/http/MimeHeaders.java
+@@ -366,7 +366,7 @@ public class MimeHeaders {
+      * reset and swap with last header
+      * @param idx the index of the header to remove.
+      */
+-    private void removeHeader(int idx) {
++    public void removeHeader(int idx) {
+         MimeHeaderField mh = headers[idx];
+ 
+         mh.recycle();
+diff --git a/java/org/apache/tomcat/util/http/parser/HttpParser.java b/java/org/apache/tomcat/util/http/parser/HttpParser.java
+index 037064b..9270ca5 100644
+--- a/java/org/apache/tomcat/util/http/parser/HttpParser.java
++++ b/java/org/apache/tomcat/util/http/parser/HttpParser.java
+@@ -143,6 +143,16 @@ public class HttpParser {
+         }
+     }
+ 
++    public static boolean isControl(int c) {
++        // Fast for valid control characters, slower for some incorrect
++        // ones
++        try {
++            return IS_CONTROL[c];
++        } catch (ArrayIndexOutOfBoundsException ex) {
++            return false;
++        }
++    }
++
+     // Skip any LWS and return the next char
+     static int skipLws(StringReader input, boolean withReset) throws IOException {
+ 
+diff --git a/test/org/apache/coyote/http11/TestInternalInputBuffer.java b/test/org/apache/coyote/http11/TestInternalInputBuffer.java
+index 4822d08..1338429 100644
+--- a/test/org/apache/coyote/http11/TestInternalInputBuffer.java
++++ b/test/org/apache/coyote/http11/TestInternalInputBuffer.java
+@@ -31,6 +31,7 @@ import static org.junit.Assert.assertFalse;
+ import static org.junit.Assert.assertTrue;
+ 
+ import org.junit.Test;
++import org.junit.Assert;
+ 
+ import org.apache.catalina.Context;
+ import org.apache.catalina.startup.SimpleHttpClient;
+@@ -142,13 +143,13 @@ public class TestInternalInputBuffer extends TomcatBaseTest {
+ 
+ 
+     @Test
+-    public void testBug51557Separators() throws Exception {
++    public void testBug51557SeparatorsInName() throws Exception {
+         char httpSeparators[] = new char[] {
+                 '\t', ' ', '\"', '(', ')', ',', '/', ':', ';', '<',
+                 '=', '>', '?', '@', '[', '\\', ']', '{', '}' };
+ 
+         for (char s : httpSeparators) {
+-            doTestBug51557Char(s);
++            doTestBug51557CharInName(s);
+             tearDown();
+             setUp();
+         }
+@@ -156,13 +157,38 @@ public class TestInternalInputBuffer extends TomcatBaseTest {
+ 
+ 
+     @Test
+-    public void testBug51557Ctl() throws Exception {
++    public void testBug51557CtlInName() throws Exception {
+         for (int i = 0; i < 31; i++) {
+-            doTestBug51557Char((char) i);
++            doTestBug51557CharInName((char) i);
++            tearDown();
++            setUp();
++        }
++        doTestBug51557CharInName((char) 127);
++    }
++
++
++    @Test
++    public void testBug51557CtlInValue() throws Exception {
++        for (int i = 0; i < 31; i++) {
++            if (i == '\t') {
++                // TAB is allowed
++                continue;
++            }
++            doTestBug51557InvalidCharInValue((char) i);
++            tearDown();
++            setUp();
++        }
++        doTestBug51557InvalidCharInValue((char) 127);
++    }
++
++
++    @Test
++    public void testBug51557ObsTextInValue() throws Exception {
++        for (int i = 128; i < 255; i++) {
++            doTestBug51557ValidCharInValue((char) i);
+             tearDown();
+             setUp();
+         }
+-        doTestBug51557Char((char) 127);
+     }
+ 
+ 
+@@ -205,7 +231,33 @@ public class TestInternalInputBuffer extends TomcatBaseTest {
+     }
+ 
+ 
+-    private void doTestBug51557Char(char s) {
++    @Test
++    public void testBug51557CRStartName() {
++
++        Bug51557Client client = new Bug51557Client("\rName=",
++                "invalid");
++
++        client.doRequest();
++        Assert.assertTrue(client.isResponse200());
++        Assert.assertEquals("abcd", client.getResponseBody());
++        Assert.assertTrue(client.isResponseBodyOK());
++    }
++
++
++    @Test
++    public void testBug51557CR2StartName() {
++
++        Bug51557Client client = new Bug51557Client("\r\rName=",
++                "invalid");
++
++        client.doRequest();
++        Assert.assertTrue(client.isResponse200());
++        Assert.assertEquals("abcd", client.getResponseBody());
++        Assert.assertTrue(client.isResponseBodyOK());
++    }
++
++
++    private void doTestBug51557CharInName(char s) {
+         Bug51557Client client =
+             new Bug51557Client("X-Bug" + s + "51557", "invalid");
+ 
+@@ -215,6 +267,29 @@ public class TestInternalInputBuffer extends TomcatBaseTest {
+         assertTrue(client.isResponseBodyOK());
+     }
+ 
++
++    private void doTestBug51557InvalidCharInValue(char s) {
++        Bug51557Client client =
++            new Bug51557Client("X-Bug51557-Invalid", "invalid" + s + "invalid");
++
++        client.doRequest();
++        Assert.assertTrue("Testing [" + (int) s + "]", client.isResponse200());
++        Assert.assertEquals("Testing [" + (int) s + "]", "abcd", client.getResponseBody());
++        Assert.assertTrue(client.isResponseBodyOK());
++    }
++
++
++    private void doTestBug51557ValidCharInValue(char s) {
++        Bug51557Client client =
++            new Bug51557Client("X-Bug51557-Valid", "valid" + s + "valid");
++
++        client.doRequest();
++        Assert.assertTrue("Testing [" + (int) s + "]", client.isResponse200());
++        Assert.assertEquals("Testing [" + (int) s + "]", "valid" + s + "validabcd", client.getResponseBody());
++        Assert.assertTrue(client.isResponseBodyOK());
++    }
++
++
+     /**
+      * Bug 51557 test client.
+      */
+diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
+index 288a947..505e357 100644
+--- a/webapps/docs/changelog.xml
++++ b/webapps/docs/changelog.xml
+@@ -172,6 +172,11 @@
+           <bug>58809</bug>: Correctly recycle cookies when mapping requests for
+                parallel deployment. (markt)
+       </fix>
++      <fix>
++        Rename the HTTP Connector attribute <code>rejectIllegalHeaderName</code>
++        to <code>rejectIllegalHeader</code> and expand the underlying
++        implementation to include header values as well as names. (markt)
++      </fix>
+     </changelog>
+   </subsection>
+   <subsection name="Coyote">


=====================================
debian/patches/CVE-2020-1938.patch
=====================================
@@ -0,0 +1,441 @@
+From: Markus Koschany <apo at debian.org>
+Date: Sat, 11 Apr 2020 23:03:56 +0200
+Subject: CVE-2020-1938
+
+Origin: https://github.com/apache/tomcat/commit/69c56080fb3355507e1b55d014ec0ee6767a6150
+Origin: https://github.com/apache/tomcat/commit/b962835f98b905286b78c414d5aaec2d0e711f75
+Origin: https://github.com/apache/tomcat/commit/9be57601efb8a81e3832feb0dd60b1eb9d2b61d5
+Origin: https://github.com/apache/tomcat/commit/64159aa1d7cdc2c118fcb5eac098e70129d54a19
+Origin: https://github.com/apache/tomcat/commit/03c436126db6794db5277a3b3d871016fb9a3f23
+---
+ .../apache/coyote/ajp/AbstractAjpProcessor.java    | 63 +++++++++++++++++---
+ .../org/apache/coyote/ajp/AbstractAjpProtocol.java | 68 +++++++++++++++++++++-
+ java/org/apache/coyote/ajp/AjpAprProtocol.java     |  2 +
+ java/org/apache/coyote/ajp/AjpNioProtocol.java     |  2 +
+ java/org/apache/coyote/ajp/AjpProtocol.java        |  2 +
+ java/org/apache/coyote/ajp/LocalStrings.properties |  2 +-
+ .../coyote/ajp/TestAbstractAjpProcessor.java       | 12 ++++
+ webapps/docs/changelog.xml                         | 17 ++++++
+ webapps/docs/config/ajp.xml                        | 36 ++++++++++--
+ 9 files changed, 188 insertions(+), 16 deletions(-)
+
+diff --git a/java/org/apache/coyote/ajp/AbstractAjpProcessor.java b/java/org/apache/coyote/ajp/AbstractAjpProcessor.java
+index 0df48ce..afa8fd7 100644
+--- a/java/org/apache/coyote/ajp/AbstractAjpProcessor.java
++++ b/java/org/apache/coyote/ajp/AbstractAjpProcessor.java
+@@ -56,6 +56,12 @@ import org.apache.tomcat.util.net.SocketStatus;
+ import org.apache.tomcat.util.net.SocketWrapper;
+ import org.apache.tomcat.util.res.StringManager;
+ 
++import java.util.Collections;
++import java.util.HashSet;
++import java.util.Set;
++import java.util.regex.Matcher;
++import java.util.regex.Pattern;
++
+ /**
+  * Base class for AJP Processor implementations.
+  */
+@@ -87,6 +93,9 @@ public abstract class AbstractAjpProcessor<S> extends AbstractProcessor<S> {
+     protected static final byte[] pongMessageArray;
+ 
+ 
++    private static final Set<String> javaxAttributes;
++
++
+     static {
+         // Allocate the end message array
+         AjpMessage endMessage = new AjpMessage(16);
+@@ -127,6 +136,14 @@ public abstract class AbstractAjpProcessor<S> extends AbstractProcessor<S> {
+         pongMessageArray = new byte[pongMessage.getLen()];
+         System.arraycopy(pongMessage.getBuffer(), 0, pongMessageArray,
+                 0, pongMessage.getLen());
++
++        // Build the Set of javax attributes
++        Set<String> s = new HashSet<String>();
++        s.add("javax.servlet.request.cipher_suite");
++        s.add("javax.servlet.request.key_size");
++        s.add("javax.servlet.request.ssl_session");
++        s.add("javax.servlet.request.X509Certificate");
++        javaxAttributes= Collections.unmodifiableSet(s);
+     }
+ 
+ 
+@@ -320,9 +337,16 @@ public abstract class AbstractAjpProcessor<S> extends AbstractProcessor<S> {
+     /**
+      * Required secret.
+      */
++    @Deprecated
+     protected String requiredSecret = null;
++    protected String secret = null;
++    public void setSecret(String secret) {
++        this.secret = secret;
++        this.requiredSecret = secret;
++    }
++    @Deprecated
+     public void setRequiredSecret(String requiredSecret) {
+-        this.requiredSecret = requiredSecret;
++        setSecret(requiredSecret);
+     }
+ 
+ 
+@@ -339,9 +363,15 @@ public abstract class AbstractAjpProcessor<S> extends AbstractProcessor<S> {
+     public String getClientCertProvider() { return clientCertProvider; }
+     public void setClientCertProvider(String s) { this.clientCertProvider = s; }
+ 
+-    // --------------------------------------------------------- Public Methods
++
++    private Pattern allowedRequestAttributesPatternPattern;
++    public void setAllowedRequestAttributesPatternPattern(Pattern allowedRequestAttributesPatternPattern) {
++        this.allowedRequestAttributesPatternPattern = allowedRequestAttributesPatternPattern;
++    }
+ 
+ 
++    // --------------------------------------------------------- Public Methods
++
+     /**
+      * Send an action to the connector.
+      *
+@@ -1218,7 +1248,7 @@ public abstract class AbstractAjpProcessor<S> extends AbstractProcessor<S> {
+         }
+ 
+         // Decode extra attributes
+-        boolean secret = false;
++        boolean secretPresentInRequest = false;
+         byte attributeCode;
+         while ((attributeCode = requestHeaderMessage.getByte())
+                 != Constants.SC_A_ARE_DONE) {
+@@ -1245,8 +1275,25 @@ public abstract class AbstractAjpProcessor<S> extends AbstractProcessor<S> {
+                     } catch (NumberFormatException nfe) {
+                         // Ignore invalid value
+                     }
++                } else if (n.equals("JK_LB_ACTIVATION")) {
++                    request.setAttribute(n, v);
++                } else if (javaxAttributes.contains(n)) {
++                    request.setAttribute(n, v);
+                 } else {
+-                    request.setAttribute(n, v );
++                    // All 'known' attributes will be processed by the previous
++                    // blocks. Any remaining attribute is an 'arbitrary' one.
++                    if (allowedRequestAttributesPatternPattern == null) {
++                        response.setStatus(403);
++                        setErrorState(ErrorState.CLOSE_CLEAN, null);
++                    } else {
++                        Matcher m = allowedRequestAttributesPatternPattern.matcher(n);
++                        if (m.matches()) {
++                            request.setAttribute(n, v);
++                        } else {
++                            response.setStatus(403);
++                            setErrorState(ErrorState.CLOSE_CLEAN, null);
++                        }
++                    }
+                 }
+                 break;
+ 
+@@ -1314,9 +1361,9 @@ public abstract class AbstractAjpProcessor<S> extends AbstractProcessor<S> {
+ 
+             case Constants.SC_A_SECRET:
+                 requestHeaderMessage.getBytes(tmpMB);
+-                if (requiredSecret != null) {
+-                    secret = true;
+-                    if (!tmpMB.equals(requiredSecret)) {
++                if (secret != null) {
++                    secretPresentInRequest = true;
++                    if (!tmpMB.equals(secret)) {
+                         response.setStatus(403);
+                         setErrorState(ErrorState.CLOSE_CLEAN, null);
+                     }
+@@ -1332,7 +1379,7 @@ public abstract class AbstractAjpProcessor<S> extends AbstractProcessor<S> {
+         }
+ 
+         // Check if secret was submitted if required
+-        if ((requiredSecret != null) && !secret) {
++        if ((secret != null) && !secretPresentInRequest) {
+             response.setStatus(403);
+             setErrorState(ErrorState.CLOSE_CLEAN, null);
+         }
+diff --git a/java/org/apache/coyote/ajp/AbstractAjpProtocol.java b/java/org/apache/coyote/ajp/AbstractAjpProtocol.java
+index d163be9..361b5f9 100644
+--- a/java/org/apache/coyote/ajp/AbstractAjpProtocol.java
++++ b/java/org/apache/coyote/ajp/AbstractAjpProtocol.java
+@@ -18,6 +18,8 @@ package org.apache.coyote.ajp;
+ 
+ import javax.servlet.http.HttpUpgradeHandler;
+ 
++import java.util.regex.Pattern;
++
+ import org.apache.coyote.AbstractProtocol;
+ import org.apache.coyote.Processor;
+ import org.apache.tomcat.util.net.SocketWrapper;
+@@ -57,8 +59,57 @@ public abstract class AbstractAjpProtocol<S> extends AbstractProtocol<S> {
+      * Required secret.
+      */
+     protected String requiredSecret = null;
++
++    private String secret = null;
++    /**
++     * Set the secret that must be included with every request.
++     *
++     * @param secret The required secret
++     */
++    public void setSecret(String secret) {
++        this.secret = secret;
++        this.requiredSecret = secret;
++    }
++    protected String getSecret() {
++        return secret;
++    }
++
++    @Deprecated
+     public void setRequiredSecret(String requiredSecret) {
+-        this.requiredSecret = requiredSecret;
++        setSecret(requiredSecret);
++    }
++    /**
++     * @return The current secret
++     *
++     * @deprecated Replaced by {@link #getSecret()}.
++     *             Will be removed in Tomcat 11 onwards
++     */
++    @Deprecated
++    protected String getRequiredSecret() {
++        return getSecret();
++    }
++
++
++    private boolean secretRequired = true;
++    public void setSecretRequired(boolean secretRequired) {
++        this.secretRequired = secretRequired;
++    }
++
++    public boolean getSecretRequired() {
++        return secretRequired;
++
++    }
++
++
++    private Pattern allowedRequestAttributesPatternPattern;
++    public void setAllowedRequestAttributesPattern(String allowedRequestAttributesPattern) {
++        this.allowedRequestAttributesPatternPattern = Pattern.compile(allowedRequestAttributesPattern);
++    }
++    public String getAllowedRequestAttributesPattern() {
++        return allowedRequestAttributesPatternPattern.pattern();
++    }
++    protected Pattern getAllowedRequestAttributesPatternPattern() {
++        return allowedRequestAttributesPatternPattern;
+     }
+ 
+ 
+@@ -78,9 +129,10 @@ public abstract class AbstractAjpProtocol<S> extends AbstractProtocol<S> {
+     protected void configureProcessor(AbstractAjpProcessor<S> processor) {
+         processor.setAdapter(getAdapter());
+         processor.setTomcatAuthentication(getTomcatAuthentication());
+-        processor.setRequiredSecret(requiredSecret);
++        processor.setSecret(getSecret());
+         processor.setKeepAliveTimeout(getKeepAliveTimeout());
+         processor.setClientCertProvider(getClientCertProvider());
++        processor.setAllowedRequestAttributesPatternPattern(getAllowedRequestAttributesPatternPattern());
+     }
+ 
+     protected abstract static class AbstractAjpConnectionHandler<S,P extends AbstractAjpProcessor<S>>
+@@ -105,4 +157,16 @@ public abstract class AbstractAjpProtocol<S> extends AbstractProtocol<S> {
+             return null;
+         }
+     }
++
++
++    @Override
++    public void start() throws Exception {
++        if (getSecretRequired()) {
++            String secret = getSecret();
++            if (secret == null || secret.length() == 0) {
++                throw new IllegalArgumentException(sm.getString("ajpprotocol.nosecret"));
++            }
++        }
++        super.start();
++    }
+ }
+diff --git a/java/org/apache/coyote/ajp/AjpAprProtocol.java b/java/org/apache/coyote/ajp/AjpAprProtocol.java
+index 1a3ecb6..776bd83 100644
+--- a/java/org/apache/coyote/ajp/AjpAprProtocol.java
++++ b/java/org/apache/coyote/ajp/AjpAprProtocol.java
+@@ -24,6 +24,7 @@ import org.apache.tomcat.util.net.AbstractEndpoint;
+ import org.apache.tomcat.util.net.AprEndpoint;
+ import org.apache.tomcat.util.net.AprEndpoint.Handler;
+ import org.apache.tomcat.util.net.SocketWrapper;
++import java.net.InetAddress;
+ 
+ 
+ /**
+@@ -61,6 +62,7 @@ public class AjpAprProtocol extends AbstractAjpProtocol<Long> {
+ 
+     public AjpAprProtocol() {
+         endpoint = new AprEndpoint();
++        endpoint.setAddress(InetAddress.getLoopbackAddress());
+         cHandler = new AjpConnectionHandler(this);
+         ((AprEndpoint) endpoint).setHandler(cHandler);
+         setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
+diff --git a/java/org/apache/coyote/ajp/AjpNioProtocol.java b/java/org/apache/coyote/ajp/AjpNioProtocol.java
+index 35a239a..a4fd9bd 100644
+--- a/java/org/apache/coyote/ajp/AjpNioProtocol.java
++++ b/java/org/apache/coyote/ajp/AjpNioProtocol.java
+@@ -31,6 +31,7 @@ import org.apache.tomcat.util.net.NioEndpoint;
+ import org.apache.tomcat.util.net.NioEndpoint.Handler;
+ import org.apache.tomcat.util.net.SSLImplementation;
+ import org.apache.tomcat.util.net.SocketWrapper;
++import java.net.InetAddress;
+ 
+ 
+ /**
+@@ -58,6 +59,7 @@ public class AjpNioProtocol extends AbstractAjpProtocol<NioChannel> {
+ 
+     public AjpNioProtocol() {
+         endpoint = new NioEndpoint();
++        endpoint.setAddress(InetAddress.getLoopbackAddress());
+         cHandler = new AjpConnectionHandler(this);
+         ((NioEndpoint) endpoint).setHandler(cHandler);
+         setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
+diff --git a/java/org/apache/coyote/ajp/AjpProtocol.java b/java/org/apache/coyote/ajp/AjpProtocol.java
+index 8cf946b..2ffc70c 100644
+--- a/java/org/apache/coyote/ajp/AjpProtocol.java
++++ b/java/org/apache/coyote/ajp/AjpProtocol.java
+@@ -27,6 +27,7 @@ import org.apache.tomcat.util.net.JIoEndpoint;
+ import org.apache.tomcat.util.net.JIoEndpoint.Handler;
+ import org.apache.tomcat.util.net.SSLImplementation;
+ import org.apache.tomcat.util.net.SocketWrapper;
++import java.net.InetAddress;
+ 
+ 
+ /**
+@@ -57,6 +58,7 @@ public class AjpProtocol extends AbstractAjpProtocol<Socket> {
+ 
+     public AjpProtocol() {
+         endpoint = new JIoEndpoint();
++        endpoint.setAddress(InetAddress.getLoopbackAddress());
+         cHandler = new AjpConnectionHandler(this);
+         ((JIoEndpoint) endpoint).setHandler(cHandler);
+         setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
+diff --git a/java/org/apache/coyote/ajp/LocalStrings.properties b/java/org/apache/coyote/ajp/LocalStrings.properties
+index fc7b387..9f50b7f 100644
+--- a/java/org/apache/coyote/ajp/LocalStrings.properties
++++ b/java/org/apache/coyote/ajp/LocalStrings.properties
+@@ -33,4 +33,4 @@ ajpmessage.overflow=Overflow error for buffer adding {0} bytes at position {1}
+ ajpmessage.invalid=Invalid message received with signature {0}
+ ajpmessage.invalidLength=Invalid message received with length {0}
+ ajpMessage.invalidPos=Requested read of bytes at position [{0}] which is beyond the end of the AJP message
+-
++ajpprotocol.nosecret=The AJP Connector is configured with secretRequired="true" but the secret attribute is either null or "". This combination is not valid.
+diff --git a/test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java b/test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java
+index f777dcf..2ed59f8 100644
+--- a/test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java
++++ b/test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java
+@@ -30,14 +30,26 @@ import javax.servlet.http.HttpServletRequest;
+ import javax.servlet.http.HttpServletResponse;
+ 
+ import org.junit.Assert;
++import org.junit.Before;
+ import org.junit.Test;
+ 
+ import org.apache.catalina.Context;
++import org.apache.catalina.connector.Connector;
+ import org.apache.catalina.startup.Tomcat;
+ import org.apache.catalina.startup.TomcatBaseTest;
+ 
+ public class TestAbstractAjpProcessor extends TomcatBaseTest {
+ 
++    @Before
++    @Override
++    public void setUp() throws Exception {
++        super.setUp();
++
++        Connector c = getTomcatInstance().getConnector();
++        c.setProperty("secretRequired", "false");
++        c.setProperty("allowedRequestAttributesPattern", "MYATTRIBUTE.*");
++    }
++
+     @Override
+     protected String getProtocol() {
+         /*
+diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
+index 505e357..aa30a77 100644
+--- a/webapps/docs/changelog.xml
++++ b/webapps/docs/changelog.xml
+@@ -1863,6 +1863,23 @@
+         granted the necessary permissions to communicate with the database.
+         (markt)
+       </update>
++      <update>
++        Change the default bind address for the AJP/1.3 connector to be the
++        loopback address. (markt)
++      </update>
++      <add>
++        Rename the <code>requiredSecret</code> attribute of the AJP/1.3
++        Connector to <code>secret</code> and add a new attribute
++        <code>secretRequired</code> that defaults to <code>true</code>. When
++        <code>secretRequired</code> is <code>true</code> the AJP/1.3 Connector
++        will not start unless the <code>secret</code> attribute is configured to
++        a non-null, non-zero length String. (markt)
++      </add>
++      <add>
++        Add a new attribute, <code>allowedRequestAttributesPattern</code> to
++        the AJP/1.3 Connector. Requests with unreconised attributes will be
++        blocked with a 403. (markt)
++      </add>
+     </changelog>
+   </subsection>
+ </section>
+diff --git a/webapps/docs/config/ajp.xml b/webapps/docs/config/ajp.xml
+index 0f56996..ab1b59b 100644
+--- a/webapps/docs/config/ajp.xml
++++ b/webapps/docs/config/ajp.xml
+@@ -294,10 +294,26 @@
+     <attribute name="address" required="false">
+       <p>For servers with more than one IP address, this attribute
+       specifies which address will be used for listening on the specified
+-      port.  By default, this port will be used on all IP addresses
+-      associated with the server. A value of <code>127.0.0.1</code>
+-      indicates that the Connector will only listen on the loopback
+-      interface.</p>
++      port. By default, the loopback address will be used.</p>
++    </attribute>
++
++    <attribute name="allowedRequestAttributesPattern" required="false">
++      <p>The AJP protocol passes some information from the reverse proxy to the
++      AJP connector using request attributes. These attributes are:</p>
++      <ul>
++        <li>javax.servlet.request.cipher_suite</li>
++        <li>javax.servlet.request.key_size</li>
++        <li>javax.servlet.request.ssl_session</li>
++        <li>javax.servlet.request.X509Certificate</li>
++        <li>AJP_LOCAL_ADDR</li>
++        <li>AJP_REMOTE_PORT</li>
++        <li>AJP_SSL_PROTOCOL</li>
++        <li>JK_LB_ACTIVATION</li>
++      </ul>
++      <p>The AJP protocol supports the passing of arbitrary request attributes.
++      Requests containing arbitrary request attributes will be rejected with a
++      403 response unless the entire attribute name matches this regular
++      expression. If not specified, the default value is <code>null</code>.</p>
+     </attribute>
+ 
+     <attribute name="bindOnInit" required="false">
+@@ -410,8 +426,18 @@
+       expected concurrent requests (synchronous and asynchronous).</p>
+     </attribute>
+ 
+-    <attribute name="requiredSecret" required="false">
++    <attribute name="secret" required="false">
+       <p>Only requests from workers with this secret keyword will be accepted.
++      The default value is <code>null</code>. This attrbute must be specified
++      with a non-null, non-zero length value unless
++      <strong>secretRequired</strong> is explicitly configured to be
++      <code>false</code>.</p>
++    </attribute>
++
++    <attribute name="secretRequired" required="false">
++      <p>If this attribute is <code>true</code>, the AJP Connector will only
++      start if the <strong>secret</strong> attribute is configured with a
++      non-null, non-zero length value. The default value is <code>true</code>.
+       </p>
+     </attribute>
+ 


=====================================
debian/patches/CVE-2020-9484.patch
=====================================
@@ -0,0 +1,122 @@
+From: Markus Koschany <apo at debian.org>
+Date: Mon, 25 May 2020 11:25:14 +0200
+Subject: CVE-2020-9484
+
+Origin: https://github.com/apache/tomcat/commit/ec08af18d0f9ddca3f2d800ef66fe7fd20afef2f
+---
+ java/org/apache/catalina/session/FileStore.java    | 25 ++++++++++++++++++----
+ .../catalina/session/LocalStrings.properties       |  3 ++-
+ java/org/apache/tomcat/util/res/StringManager.java | 15 +++++++++++++
+ webapps/docs/changelog.xml                         |  3 +++
+ 4 files changed, 41 insertions(+), 5 deletions(-)
+
+diff --git a/java/org/apache/catalina/session/FileStore.java b/java/org/apache/catalina/session/FileStore.java
+index d34c39f..cdc333f 100644
+--- a/java/org/apache/catalina/session/FileStore.java
++++ b/java/org/apache/catalina/session/FileStore.java
+@@ -36,6 +36,9 @@ import org.apache.catalina.Context;
+ import org.apache.catalina.Loader;
+ import org.apache.catalina.Session;
+ import org.apache.catalina.util.CustomObjectInputStream;
++import org.apache.juli.logging.Log;
++import org.apache.juli.logging.LogFactory;
++import org.apache.tomcat.util.res.StringManager;
+ 
+ 
+ /**
+@@ -48,6 +51,10 @@ import org.apache.catalina.util.CustomObjectInputStream;
+ public final class FileStore extends StoreBase {
+ 
+ 
++    private static final Log log = LogFactory.getLog(FileStore.class);
++    private static final StringManager sm = StringManager.getManager(FileStore.class);
++
++
+     // ----------------------------------------------------- Constants
+ 
+ 
+@@ -392,11 +399,21 @@ public final class FileStore extends StoreBase {
+      */
+     private File file(String id) throws IOException {
+ 
+-        if (this.directory == null) {
+-            return (null);
+-        }
++        File storageDir = directory();
++            if (storageDir == null) {
++                 return (null);
++            }
++
+         String filename = id + FILE_EXT;
+-        File file = new File(directory(), filename);
++
++        File file = new File(storageDir, filename);
++
++        // Check the file is within the storage directory
++        if (!file.getCanonicalPath().startsWith(storageDir.getCanonicalPath())) {
++            log.warn(sm.getString("fileStore.invalid", file.getPath(), id));
++            return null;
++        }
++
+         return (file);
+ 
+     }
+diff --git a/java/org/apache/catalina/session/LocalStrings.properties b/java/org/apache/catalina/session/LocalStrings.properties
+index 67eb04e..a071dbd 100644
+--- a/java/org/apache/catalina/session/LocalStrings.properties
++++ b/java/org/apache/catalina/session/LocalStrings.properties
+@@ -18,6 +18,7 @@ fileStore.loading=Loading Session {0} from file {1}
+ fileStore.removing=Removing Session {0} at file {1}
+ fileStore.deleteFailed=Unable to delete file [{0}] which is preventing the creation of the session storage location
+ fileStore.createFailed=Unable to create directory [{0}] for the storage of session data
++fileStore.invalid=Invalid persistence file [{0}] for session ID [{1}]
+ JDBCStore.close=Exception closing database connection {0}
+ JDBCStore.saving=Saving Session {0} to database {1}
+ JDBCStore.loading=Loading Session {0} from database {1}
+@@ -72,4 +73,4 @@ persistentManager.swapIn=Swapping session {0} in from Store
+ persistentManager.swapInException=Exception in the Store during swapIn: {0}
+ persistentManager.swapInInvalid=Swapped session {0} is invalid
+ persistentManager.storeKeysException=Unable to determine the list of session IDs for sessions in the session store, assuming that the store is empty
+-persistentManager.storeSizeException=Unable to determine the number of sessions in the session store, assuming that the store is empty
+\ No newline at end of file
++persistentManager.storeSizeException=Unable to determine the number of sessions in the session store, assuming that the store is empty
+diff --git a/java/org/apache/tomcat/util/res/StringManager.java b/java/org/apache/tomcat/util/res/StringManager.java
+index 034fdb5..febe2a6 100644
+--- a/java/org/apache/tomcat/util/res/StringManager.java
++++ b/java/org/apache/tomcat/util/res/StringManager.java
+@@ -173,6 +173,21 @@ public class StringManager {
+     private static final Map<String, Map<Locale,StringManager>> managers =
+             new Hashtable<>();
+ 
++
++     /**
++     * Get the StringManager for a given class. The StringManager will be
++     * returned for the package in which the class is located. If a manager for
++     * that package already exists, it will be reused, else a new
++     * StringManager will be created and returned.
++     *
++     * @param clazz The class for which to retrieve the StringManager
++     *
++     * @return The instance associated with the package of the provide class
++     */
++    public static final StringManager getManager(Class<?> clazz) {
++        return getManager(clazz.getPackage().getName());
++    }
++
+     /**
+      * Get the StringManager for a particular package. If a manager for
+      * a package already exists, it will be reused, else a new
+diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
+index 288a947..ecf4b55 100644
+--- a/webapps/docs/changelog.xml
++++ b/webapps/docs/changelog.xml
+@@ -52,6 +52,9 @@
+         Windows service and the Apache Tomcat Windows service monitor
+         application are now digitally signed. (markt)
+       </fix>
++      <add>
++        Improve validation of storage location when using FileStore. (markt)
++      </add>
+     </changelog>
+   </subsection>
+ </section>


=====================================
debian/patches/series
=====================================
@@ -49,3 +49,8 @@ CVE-2018-11784.patch
 CVE-2019-0221.patch
 CVE-2018-8014.patch
 CVE-2016-5388.patch
+CVE-2019-12418.patch
+CVE-2019-17563.patch
+CVE-2020-9484.patch
+CVE-2020-1935.patch
+CVE-2020-1938.patch



View it on GitLab: https://salsa.debian.org/java-team/tomcat8/-/compare/56b840e5ad5f76d04b9e881395b33e7781255a8a...42ca7330b98bc605d69cc8b8bce578c8769d633e

-- 
View it on GitLab: https://salsa.debian.org/java-team/tomcat8/-/compare/56b840e5ad5f76d04b9e881395b33e7781255a8a...42ca7330b98bc605d69cc8b8bce578c8769d633e
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20200715/cffbd869/attachment.html>


More information about the pkg-java-commits mailing list