Blame SOURCES/squid-3.5.20-CVE-2023-46724.patch

e2987f
From 792ef23e6e1c05780fe17f733859eef6eb8c8be3 Mon Sep 17 00:00:00 2001
e2987f
From: Andreas Weigel <andreas.weigel@securepoint.de>
e2987f
Date: Wed, 18 Oct 2023 04:14:31 +0000
e2987f
Subject: [PATCH] Fix validation of certificates with CN=* (#1523)
e2987f
e2987f
The bug was discovered and detailed by Joshua Rogers at
e2987f
https://megamansec.github.io/Squid-Security-Audit/
e2987f
where it was filed as "Buffer UnderRead in SSL CN Parsing".
e2987f
e2987f
Additionally includes upstream commit abbd782505c760279a8bd86a45c6095428f10752.
e2987f
---
e2987f
 src/URL.h             | 39 ++++++++++++++++++++++++---------------
e2987f
 src/acl/ServerName.cc |  2 +-
e2987f
 src/ssl/support.cc    |  9 ++++++---
e2987f
 src/url.cc            | 37 ++++++++++++++++++++++++++++++++-----
e2987f
 4 files changed, 63 insertions(+), 24 deletions(-)
e2987f
e2987f
diff --git a/src/URL.h b/src/URL.h
e2987f
index 7076958..9a6c091 100644
e2987f
--- a/src/URL.h
e2987f
+++ b/src/URL.h
e2987f
@@ -73,23 +73,29 @@ char *urlMakeAbsolute(const HttpRequest *, const char *);
e2987f
 char *urlRInternal(const char *host, unsigned short port, const char *dir, const char *name);
e2987f
 char *urlInternal(const char *dir, const char *name);
e2987f
 
e2987f
+enum MatchDomainNameFlags {
e2987f
+    mdnNone = 0,
e2987f
+    mdnHonorWildcards = 1 << 0,
e2987f
+    mdnRejectSubsubDomains = 1 << 1
e2987f
+};
e2987f
+
e2987f
 /**
e2987f
- * matchDomainName() compares a hostname (usually extracted from traffic)
e2987f
- * with a domainname (usually from an ACL) according to the following rules:
e2987f
+ * matchDomainName() matches a hostname (usually extracted from traffic)
e2987f
+ * with a domainname when mdnNone or mdnRejectSubsubDomains flags are used
e2987f
+ * according to the following rules:
e2987f
  *
e2987f
- *    HOST      |   DOMAIN    |   MATCH?
e2987f
- * -------------|-------------|------
e2987f
- *    foo.com   |   foo.com   |     YES
e2987f
- *   .foo.com   |   foo.com   |     YES
e2987f
- *  x.foo.com   |   foo.com   |     NO
e2987f
- *    foo.com   |  .foo.com   |     YES
e2987f
- *   .foo.com   |  .foo.com   |     YES
e2987f
- *  x.foo.com   |  .foo.com   |     YES
e2987f
+ *    HOST      |   DOMAIN    |   mdnNone | mdnRejectSubsubDomains
e2987f
+ * -------------|-------------|-----------|-----------------------
e2987f
+ *      foo.com |   foo.com   |     YES   |   YES
e2987f
+ *     .foo.com |   foo.com   |     YES   |   YES
e2987f
+ *    x.foo.com |   foo.com   |     NO    |   NO
e2987f
+ *      foo.com |  .foo.com   |     YES   |   YES
e2987f
+ *     .foo.com |  .foo.com   |     YES   |   YES
e2987f
+ *    x.foo.com |  .foo.com   |     YES   |   YES
e2987f
+ *   .x.foo.com |  .foo.com   |     YES   |   NO
e2987f
+ *  y.x.foo.com |  .foo.com   |     YES   |   NO
e2987f
  *
e2987f
- *  We strip leading dots on hosts (but not domains!) so that
e2987f
- *  ".foo.com" is always the same as "foo.com".
e2987f
- *
e2987f
- * if honorWildcards is true then the matchDomainName() also accepts
e2987f
+ * if mdnHonorWildcards flag is set then the matchDomainName() also accepts
e2987f
  * optional wildcards on hostname:
e2987f
  *
e2987f
  *    HOST      |    DOMAIN    |  MATCH?
e2987f
@@ -99,11 +105,14 @@ char *urlInternal(const char *dir, const char *name);
e2987f
  *    *.foo.com |    .foo.com  |   YES
e2987f
  *    *.foo.com |     foo.com  |   NO
e2987f
  *
e2987f
+ * The combination of mdnHonorWildcards and mdnRejectSubsubDomains flags is
e2987f
+ * supported.
e2987f
+ *
e2987f
  * \retval 0 means the host matches the domain
e2987f
  * \retval 1 means the host is greater than the domain
e2987f
  * \retval -1 means the host is less than the domain
e2987f
  */
e2987f
-int matchDomainName(const char *host, const char *domain, bool honorWildcards = false);
e2987f
+int matchDomainName(const char *host, const char *domain, uint flags = mdnNone);
e2987f
 int urlCheckRequest(const HttpRequest *);
e2987f
 int urlDefaultPort(AnyP::ProtocolType p);
e2987f
 char *urlHostname(const char *url);
e2987f
diff --git a/src/acl/ServerName.cc b/src/acl/ServerName.cc
e2987f
index 9cc08b2..f49209b 100644
e2987f
--- a/src/acl/ServerName.cc
e2987f
+++ b/src/acl/ServerName.cc
e2987f
@@ -30,7 +30,7 @@ aclHostDomainCompare( char *const &a, char * const &b)
e2987f
     const char *h = static_cast<const char *>(a);
e2987f
     const char *d = static_cast<const char *>(b);
e2987f
     debugs(28, 7, "Match:" << h << " <>  " << d);
e2987f
-    return matchDomainName(h, d, true);
e2987f
+    return matchDomainName(h, d, mdnHonorWildcards);
e2987f
 }
e2987f
 
e2987f
 bool
e2987f
diff --git a/src/ssl/support.cc b/src/ssl/support.cc
e2987f
index 6f01a94..5e33447 100644
e2987f
--- a/src/ssl/support.cc
e2987f
+++ b/src/ssl/support.cc
e2987f
@@ -199,9 +199,12 @@ static int check_domain( void *check_data, ASN1_STRING *cn_data)
e2987f
     char cn[1024];
e2987f
     const char *server = (const char *)check_data;
e2987f
 
e2987f
-    if (cn_data->length > (int)sizeof(cn) - 1) {
e2987f
+    if (cn_data->length == 0)
e2987f
+        return 1; // zero length cn, ignore
e2987f
+
e2987f
+    if (cn_data->length > (int)sizeof(cn) - 1)
e2987f
         return 1; //if does not fit our buffer just ignore
e2987f
-    }
e2987f
+
e2987f
     char *s = reinterpret_cast<char*>(cn_data->data);
e2987f
     char *d = cn;
e2987f
     for (int i = 0; i < cn_data->length; ++i, ++d, ++s) {
e2987f
@@ -211,7 +214,7 @@ static int check_domain( void *check_data, ASN1_STRING *cn_data)
e2987f
     }
e2987f
     cn[cn_data->length] = '\0';
e2987f
     debugs(83, 4, "Verifying server domain " << server << " to certificate name/subjectAltName " << cn);
e2987f
-    return matchDomainName(server, cn[0] == '*' ? cn + 1 : cn);
e2987f
+    return matchDomainName(server, (cn[0] == '*' ? cn + 1 : cn), mdnRejectSubsubDomains);
e2987f
 }
e2987f
 
e2987f
 bool Ssl::checkX509ServerValidity(X509 *cert, const char *server)
e2987f
diff --git a/src/url.cc b/src/url.cc
e2987f
index 1e14d05..0bddd0e 100644
e2987f
--- a/src/url.cc
e2987f
+++ b/src/url.cc
e2987f
@@ -54,6 +54,7 @@ urlInitialize(void)
e2987f
     assert(0 == matchDomainName("foo.com", ".foo.com"));
e2987f
     assert(0 == matchDomainName(".foo.com", ".foo.com"));
e2987f
     assert(0 == matchDomainName("x.foo.com", ".foo.com"));
e2987f
+    assert(0 == matchDomainName("y.x.foo.com", ".foo.com"));
e2987f
     assert(0 != matchDomainName("x.foo.com", "foo.com"));
e2987f
     assert(0 != matchDomainName("foo.com", "x.foo.com"));
e2987f
     assert(0 != matchDomainName("bar.com", "foo.com"));
e2987f
@@ -66,6 +67,17 @@ urlInitialize(void)
e2987f
     assert(0 < matchDomainName("bfoo.com", "afoo.com"));
e2987f
     assert(0 > matchDomainName("afoo.com", "bfoo.com"));
e2987f
     assert(0 < matchDomainName("x-foo.com", ".foo.com"));
e2987f
+
e2987f
+    assert(0 == matchDomainName(".foo.com", ".foo.com", mdnRejectSubsubDomains));
e2987f
+    assert(0 == matchDomainName("x.foo.com", ".foo.com", mdnRejectSubsubDomains));
e2987f
+    assert(0 != matchDomainName("y.x.foo.com", ".foo.com", mdnRejectSubsubDomains));
e2987f
+    assert(0 != matchDomainName(".x.foo.com", ".foo.com", mdnRejectSubsubDomains));
e2987f
+
e2987f
+    assert(0 == matchDomainName("*.foo.com", "x.foo.com", mdnHonorWildcards));
e2987f
+    assert(0 == matchDomainName("*.foo.com", ".x.foo.com", mdnHonorWildcards));
e2987f
+    assert(0 == matchDomainName("*.foo.com", ".foo.com", mdnHonorWildcards));
e2987f
+    assert(0 != matchDomainName("*.foo.com", "foo.com", mdnHonorWildcards));
e2987f
+
e2987f
     /* more cases? */
e2987f
 }
e2987f
 
e2987f
@@ -680,16 +692,20 @@ urlMakeAbsolute(const HttpRequest * req, const char *relUrl)
e2987f
 }
e2987f
 
e2987f
 int
e2987f
-matchDomainName(const char *h, const char *d, bool honorWildcards)
e2987f
+matchDomainName(const char *h, const char *d, uint flags)
e2987f
 {
e2987f
     int dl;
e2987f
     int hl;
e2987f
 
e2987f
+    const bool hostIncludesSubdomains = (*h == '.');
e2987f
     while ('.' == *h)
e2987f
         ++h;
e2987f
 
e2987f
     hl = strlen(h);
e2987f
 
e2987f
+    if (hl == 0)
e2987f
+        return -1;
e2987f
+
e2987f
     dl = strlen(d);
e2987f
 
e2987f
     /*
e2987f
@@ -727,9 +743,20 @@ matchDomainName(const char *h, const char *d, bool honorWildcards)
e2987f
              * is a leading '.'.
e2987f
              */
e2987f
 
e2987f
-            if ('.' == d[0])
e2987f
-                return 0;
e2987f
-            else
e2987f
+            if ('.' == d[0]) {
e2987f
+                if (flags & mdnRejectSubsubDomains) {
e2987f
+                    // Check for sub-sub domain and reject
e2987f
+                    while(--hl >= 0 && h[hl] != '.');
e2987f
+                    if (hl < 0) {
e2987f
+                        // No sub-sub domain found, but reject if there is a
e2987f
+                        // leading dot in given host string (which is removed
e2987f
+                        // before the check is started).
e2987f
+                        return hostIncludesSubdomains ? 1 : 0;
e2987f
+                    } else
e2987f
+                        return 1; // sub-sub domain, reject
e2987f
+                } else
e2987f
+                    return 0;
e2987f
+            } else
e2987f
                 return 1;
e2987f
         }
e2987f
     }
e2987f
@@ -741,7 +768,7 @@ matchDomainName(const char *h, const char *d, bool honorWildcards)
e2987f
     // If the h has a form of "*.foo.com" and d has a form of "x.foo.com"
e2987f
     // then the h[hl] points to '*', h[hl+1] to '.' and d[dl] to 'x'
e2987f
     // The following checks are safe, the "h[hl + 1]" in the worst case is '\0'.
e2987f
-    if (honorWildcards && h[hl] == '*' && h[hl + 1] == '.')
e2987f
+    if ((flags & mdnHonorWildcards) && h[hl] == '*' && h[hl + 1] == '.')
e2987f
         return 0;
e2987f
 
e2987f
     /*
e2987f
-- 
e2987f
2.43.0
e2987f