File tomcat-8.0.53-CVE-2021-30640.patch of Package tomcat.23111

Index: apache-tomcat-8.0.53-src/java/org/apache/catalina/realm/JNDIRealm.java
===================================================================
--- apache-tomcat-8.0.53-src.orig/java/org/apache/catalina/realm/JNDIRealm.java
+++ apache-tomcat-8.0.53-src/java/org/apache/catalina/realm/JNDIRealm.java
@@ -1626,8 +1626,11 @@ public class JNDIRealm extends RealmBase
         if (username == null || userPatternFormatArray[curUserPattern] == null)
             return null;
 
-        // Form the dn from the user pattern
-        String dn = userPatternFormatArray[curUserPattern].format(new String[] { username });
+        // Form the DistinguishedName from the user pattern.
+        // Escape in case username contains a character with special meaning in
+        // an attribute value.
+        String dn = userPatternFormatArray[curUserPattern].format(
+                new String[] { doAttributeValueEscaping(username) });
 
         try {
             user = getUserByPattern(context, username, attrIds, dn);
@@ -1668,7 +1671,9 @@ public class JNDIRealm extends RealmBase
             return null;
 
         // Form the search filter
-        String filter = userSearchFormat.format(new String[] { username });
+        // Escape in case username contains a character with special meaning in
+        // a search filter.
+        String filter = userSearchFormat.format(new String[] { doFilterEscaping(username) });
 
         // Set up the search controls
         SearchControls constraints = new SearchControls();
@@ -1959,8 +1964,13 @@ public class JNDIRealm extends RealmBase
         if ((roleFormat == null) || (roleName == null))
             return list;
 
-        // Set up parameters for an appropriate search
-        String filter = roleFormat.format(new String[] { doRFC2254Encoding(dn), username, userRoleId });
+        // Set up parameters for an appropriate search filter
+        // The dn is already attribute value escaped but the others are not
+        // This is a filter so all input will require filter escaping
+        String filter = roleFormat.format(new String[] {
+                doFilterEscaping(dn),
+                doFilterEscaping(doAttributeValueEscaping(username)),
+                doFilterEscaping(doAttributeValueEscaping(userRoleId)) });
         SearchControls controls = new SearchControls();
         if (roleSubtree)
             controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
@@ -1974,7 +1984,9 @@ public class JNDIRealm extends RealmBase
             Name name = np.parse(dn);
             String nameParts[] = new String[name.size()];
             for (int i = 0; i < name.size(); i++) {
-                nameParts[i] = name.get(i);
+                // May have been returned with \<char> escaping rather than
+                // \<hex><hex>. Make sure it is \<hex><hex>.
+                nameParts[i] =  convertToHexEscape(name.get(i));
             }
             base = roleBaseFormat.format(nameParts);
         } else {
@@ -1995,7 +2007,7 @@ public class JNDIRealm extends RealmBase
                 Attributes attrs = result.getAttributes();
                 if (attrs == null)
                     continue;
-                String dname = getDistinguishedName(context, roleBase, result);
+                String dname = getDistinguishedName(context, base, result);
                 String name = getAttributeValue(roleName, attrs);
                 if (name != null && dname != null) {
                     groupMap.put(dname, name);
@@ -2028,14 +2040,19 @@ public class JNDIRealm extends RealmBase
                 Map<String, String> newThisRound = new HashMap<>(); // Stores the groups we find in this iteration
 
                 for (Entry<String, String> group : newGroups.entrySet()) {
-                    filter = roleFormat.format(new String[] { group.getKey(), group.getValue(), group.getValue() });
+                    // Group key is already value escaped if required
+                    // Group value is not value escaped
+                    // Everything needs to be filter escaped
+                    filter = roleFormat.format(new String[] {
+                            doFilterEscaping(group.getKey()),
+                            doFilterEscaping(doAttributeValueEscaping(group.getValue())),
+                            doFilterEscaping(doAttributeValueEscaping(group.getValue())) });
 
                     if (containerLog.isTraceEnabled()) {
-                        containerLog.trace("Perform a nested group search with base "+ roleBase + " and filter " + filter);
+                        containerLog.trace("Perform a nested group search with base "+ roleBase +
+                                " and filter " + filter);
                     }
-
-                    results = searchAsUser(context, user, roleBase, filter, controls,
-                            isRoleSearchAsUser());
+                    results = searchAsUser(context, user, base, filter, controls, isRoleSearchAsUser());
 
                     try {
                         while (results.hasMore()) {
@@ -2477,12 +2494,14 @@ public class JNDIRealm extends RealmBase
                 sslContext = SSLContext.getDefault();
             }
             return sslContext.getSocketFactory();
-        } catch (NoSuchAlgorithmException | KeyManagementException e) {
-            List<String> allowedProtocols = Arrays
-                    .asList(getSupportedSslProtocols());
-            throw new IllegalArgumentException(
-                    sm.getString("jndiRealm.invalidSslProtocol", protocol,
-                            allowedProtocols), e);
+        } catch (NoSuchAlgorithmException e) {
+            List<String> allowedProtocols = Arrays.asList(getSupportedSslProtocols());
+            throw new IllegalArgumentException(sm.getString("jndiRealm.invalidSslProtocol",
+                    protocol, allowedProtocols), e);
+        } catch (KeyManagementException e) {
+            List<String> allowedProtocols = Arrays.asList(getSupportedSslProtocols());
+            throw new IllegalArgumentException(sm.getString("jndiRealm.invalidSslProtocol",
+                    protocol, allowedProtocols), e);
         }
     }
 
@@ -2698,6 +2717,27 @@ public class JNDIRealm extends RealmBase
      * @return String the escaped/encoded result
      */
     protected String doRFC2254Encoding(String inString) {
+        return doFilterEscaping(inString);
+    }
+
+
+    /**
+     * Given an LDAP search string, returns the string with certain characters
+     * escaped according to RFC 2254 guidelines.
+     * The character mapping is as follows:
+     *     char -&gt;  Replacement
+     *    ---------------------------
+     *     *  -&gt; \2a
+     *     (  -&gt; \28
+     *     )  -&gt; \29
+     *     \  -&gt; \5c
+     *     \0 -&gt; \00
+     *
+     * @param inString string to escape according to RFC 2254 guidelines
+     *
+     * @return String the escaped/encoded result
+     */
+    protected String doFilterEscaping(String inString) {
         StringBuilder buf = new StringBuilder(inString.length());
         for (int i = 0; i < inString.length(); i++) {
             char c = inString.charAt(i);
@@ -2782,6 +2822,148 @@ public class JNDIRealm extends RealmBase
         }
     }
 
+    /**
+     * Implements the necessary escaping to represent an attribute value as a
+     * String as per RFC 4514.
+     *
+     * @param input The original attribute value
+     * @return      The string representation of the attribute value
+     */
+    protected String doAttributeValueEscaping(String input) {
+        int len = input.length();
+        StringBuilder result = new StringBuilder();
+
+        for (int i = 0; i < len; i++) {
+            char c = input.charAt(i);
+            switch (c) {
+                case ' ': {
+                    if (i == 0 || i == (len -1)) {
+                        result.append("\\20");
+                    } else {
+                        result.append(c);
+                    }
+                    break;
+                }
+                case '#': {
+                    if (i == 0 ) {
+                        result.append("\\23");
+                    } else {
+                        result.append(c);
+                    }
+                    break;
+                }
+                case '\"': {
+                    result.append("\\22");
+                    break;
+                }
+                case '+': {
+                    result.append("\\2B");
+                    break;
+                }
+                case ',': {
+                    result.append("\\2C");
+                    break;
+                }
+                case ';': {
+                    result.append("\\3B");
+                    break;
+                }
+                case '<': {
+                    result.append("\\3C");
+                    break;
+                }
+                case '>': {
+                    result.append("\\3E");
+                    break;
+                }
+                case '\\': {
+                    result.append("\\5C");
+                    break;
+                }
+                case '\u0000': {
+                    result.append("\\00");
+                    break;
+                }
+                default:
+                    result.append(c);
+            }
+
+        }
+
+        return result.toString();
+    }
+
+
+    protected static String convertToHexEscape(String input) {
+        if (input.indexOf('\\') == -1) {
+            // No escaping present. Return original.
+            return input;
+        }
+        // +6 allows for 3 escaped characters by default
+        StringBuilder result = new StringBuilder(input.length() + 6);
+        boolean previousSlash = false;
+        for (int i = 0; i < input.length(); i++) {
+            char c = input.charAt(i);
+            if (previousSlash) {
+                switch (c) {
+                    case ' ': {
+                        result.append("\\20");
+                        break;
+                    }
+                    case '\"': {
+                        result.append("\\22");
+                        break;
+                    }
+                    case '#': {
+                        result.append("\\23");
+                        break;
+                    }
+                    case '+': {
+                        result.append("\\2B");
+                        break;
+                    }
+                    case ',': {
+                        result.append("\\2C");
+                        break;
+                    }
+                    case ';': {
+                        result.append("\\3B");
+                        break;
+                    }
+                    case '<': {
+                        result.append("\\3C");
+                        break;
+                    }
+                    case '=': {
+                        result.append("\\3D");
+                        break;
+                    }
+                    case '>': {
+                        result.append("\\3E");
+                        break;
+                    }
+                    case '\\': {
+                        result.append("\\5C");
+                        break;
+                    }
+                    default:
+                        result.append('\\');
+                        result.append(c);
+                }
+                previousSlash = false;
+            } else {
+                if (c == '\\') {
+                    previousSlash = true;
+                } else {
+                    result.append(c);
+                }
+            }
+        }
+        if (previousSlash) {
+            result.append('\\');
+        }
+        return result.toString();
+    }
 
     // ------------------------------------------------------ Private Classes
 
Index: apache-tomcat-8.0.53-src/webapps/docs/changelog.xml
===================================================================
--- apache-tomcat-8.0.53-src.orig/webapps/docs/changelog.xml
+++ apache-tomcat-8.0.53-src/webapps/docs/changelog.xml
@@ -146,6 +146,10 @@
         set using a canonical path which in turn meant resource URLs were not
         being constructed as expected. (markt)
       </fix>
+      <fix>
+        <bug>65224</bug>: Ensure the correct escaping of attribute values and
+        search filters in the JNDIRealm. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Coyote">
openSUSE Build Service is sponsored by