| From 199f2d440d8659b42670c1b796220792b01a97bf Mon Sep 17 00:00:00 2001 |
| From: Daniel Stenberg <daniel@haxx.se> |
| Date: Mon, 24 Apr 2023 21:07:02 +0200 |
| Subject: [PATCH] hostcheck: fix host name wildcard checking |
| |
| The leftmost "label" of the host name can now only match against single |
| '*'. Like the browsers have worked for a long time. |
| |
| - extended unit test 1397 for this |
| - move some SOURCE variables from unit/Makefile.am to unit/Makefile.inc |
| |
| Reported-by: Hiroki Kurosawa |
| Closes #11018 |
| |
| CVE: CVE-2023-28321 |
| Upstream-Status: Backport [https://github.com/curl/curl/commit/199f2d440d8659b42] |
| Comments: Hunks removed as changes already exist |
| Removed hunks from files: |
| tests/unit/Makefile.am |
| tests/unit/Makefile.inc |
| Signed-off-by: Bhabu Bindu <bhabu.bindu@kpit.com> |
| --- |
| lib/vtls/hostcheck.c | 50 +++++++-------- |
| tests/data/test1397 | 10 ++- |
| tests/unit/Makefile.am | 94 ---------------------------- |
| tests/unit/Makefile.inc | 94 ++++++++++++++++++++++++++++ |
| tests/unit/unit1397.c | 134 ++++++++++++++++++++++++---------------- |
| 5 files changed, 202 insertions(+), 180 deletions(-) |
| |
| diff --git a/lib/vtls/hostcheck.c b/lib/vtls/hostcheck.c |
| index e827dc58f378c..d061c6356f97f 100644 |
| --- a/lib/vtls/hostcheck.c |
| +++ b/lib/vtls/hostcheck.c |
| @@ -71,7 +71,12 @@ static bool pmatch(const char *hostname, size_t hostlen, |
| * apparent distinction between a name and an IP. We need to detect the use of |
| * an IP address and not wildcard match on such names. |
| * |
| + * Only match on "*" being used for the leftmost label, not "a*", "a*b" nor |
| + * "*b". |
| + * |
| * Return TRUE on a match. FALSE if not. |
| + * |
| + * @unittest: 1397 |
| */ |
| |
| static bool hostmatch(const char *hostname, |
| @@ -79,53 +84,42 @@ static bool hostmatch(const char *hostname, |
| const char *pattern, |
| size_t patternlen) |
| { |
| - const char *pattern_label_end, *wildcard, *hostname_label_end; |
| - size_t prefixlen, suffixlen; |
| + const char *pattern_label_end; |
| |
| - /* normalize pattern and hostname by stripping off trailing dots */ |
| + DEBUGASSERT(pattern); |
| DEBUGASSERT(patternlen); |
| + DEBUGASSERT(hostname); |
| + DEBUGASSERT(hostlen); |
| + |
| + /* normalize pattern and hostname by stripping off trailing dots */ |
| if(hostname[hostlen-1]=='.') |
| hostlen--; |
| if(pattern[patternlen-1]=='.') |
| patternlen--; |
| |
| - wildcard = memchr(pattern, '*', patternlen); |
| - if(!wildcard) |
| + if(strncmp(pattern, "*.", 2)) |
| return pmatch(hostname, hostlen, pattern, patternlen); |
| |
| /* detect IP address as hostname and fail the match if so */ |
| - if(Curl_host_is_ipnum(hostname)) |
| + else if(Curl_host_is_ipnum(hostname)) |
| return FALSE; |
| |
| /* We require at least 2 dots in the pattern to avoid too wide wildcard |
| match. */ |
| pattern_label_end = memchr(pattern, '.', patternlen); |
| if(!pattern_label_end || |
| - (memrchr(pattern, '.', patternlen) == pattern_label_end) || |
| - strncasecompare(pattern, "xn--", 4)) |
| + (memrchr(pattern, '.', patternlen) == pattern_label_end)) |
| return pmatch(hostname, hostlen, pattern, patternlen); |
| - |
| - hostname_label_end = memchr(hostname, '.', hostlen); |
| - if(!hostname_label_end) |
| - return FALSE; |
| else { |
| - size_t skiphost = hostname_label_end - hostname; |
| - size_t skiplen = pattern_label_end - pattern; |
| - if(!pmatch(hostname_label_end, hostlen - skiphost, |
| - pattern_label_end, patternlen - skiplen)) |
| - return FALSE; |
| + const char *hostname_label_end = memchr(hostname, '.', hostlen); |
| + if(hostname_label_end) { |
| + size_t skiphost = hostname_label_end - hostname; |
| + size_t skiplen = pattern_label_end - pattern; |
| + return pmatch(hostname_label_end, hostlen - skiphost, |
| + pattern_label_end, patternlen - skiplen); |
| + } |
| } |
| - /* The wildcard must match at least one character, so the left-most |
| - label of the hostname is at least as large as the left-most label |
| - of the pattern. */ |
| - if(hostname_label_end - hostname < pattern_label_end - pattern) |
| - return FALSE; |
| - |
| - prefixlen = wildcard - pattern; |
| - suffixlen = pattern_label_end - (wildcard + 1); |
| - return strncasecompare(pattern, hostname, prefixlen) && |
| - strncasecompare(wildcard + 1, hostname_label_end - suffixlen, |
| - suffixlen) ? TRUE : FALSE; |
| + return FALSE; |
| } |
| |
| /* |
| diff --git a/tests/data/test1397 b/tests/data/test1397 |
| index 84f962abebee3..f31b2c2a3f330 100644 |
| --- a/tests/data/test1397 |
| +++ b/tests/data/test1397 |
| @@ -2,8 +2,7 @@ |
| <info> |
| <keywords> |
| unittest |
| -ssl |
| -wildcard |
| +Curl_cert_hostcheck |
| </keywords> |
| </info> |
| |
| @@ -16,9 +15,8 @@ none |
| <features> |
| unittest |
| </features> |
| - <name> |
| -Check wildcard certificate matching function Curl_cert_hostcheck |
| - </name> |
| +<name> |
| +Curl_cert_hostcheck unit tests |
| +</name> |
| </client> |
| - |
| </testcase> |
| diff --git a/tests/unit/unit1397.c b/tests/unit/unit1397.c |
| index 2f3d3aa4d09e1..3ae75618d5d10 100644 |
| --- a/tests/unit/unit1397.c |
| +++ b/tests/unit/unit1397.c |
| @@ -23,7 +23,6 @@ |
| ***************************************************************************/ |
| #include "curlcheck.h" |
| |
| -#include "vtls/hostcheck.h" /* from the lib dir */ |
| |
| static CURLcode unit_setup(void) |
| { |
| @@ -32,63 +31,94 @@ static CURLcode unit_setup(void) |
| |
| static void unit_stop(void) |
| { |
| - /* done before shutting down and exiting */ |
| } |
| |
| -UNITTEST_START |
| - |
| /* only these backends define the tested functions */ |
| -#if defined(USE_OPENSSL) || defined(USE_GSKIT) |
| - |
| - /* here you start doing things and checking that the results are good */ |
| +#if defined(USE_OPENSSL) || defined(USE_GSKIT) || defined(USE_SCHANNEL) |
| +#include "vtls/hostcheck.h" |
| +struct testcase { |
| + const char *host; |
| + const char *pattern; |
| + bool match; |
| +}; |
| |
| -fail_unless(Curl_cert_hostcheck(STRCONST("www.example.com"), |
| - STRCONST("www.example.com")), "good 1"); |
| -fail_unless(Curl_cert_hostcheck(STRCONST("*.example.com"), |
| - STRCONST("www.example.com")), |
| - "good 2"); |
| -fail_unless(Curl_cert_hostcheck(STRCONST("xxx*.example.com"), |
| - STRCONST("xxxwww.example.com")), "good 3"); |
| -fail_unless(Curl_cert_hostcheck(STRCONST("f*.example.com"), |
| - STRCONST("foo.example.com")), "good 4"); |
| -fail_unless(Curl_cert_hostcheck(STRCONST("192.168.0.0"), |
| - STRCONST("192.168.0.0")), "good 5"); |
| +static struct testcase tests[] = { |
| + {"", "", FALSE}, |
| + {"a", "", FALSE}, |
| + {"", "b", FALSE}, |
| + {"a", "b", FALSE}, |
| + {"aa", "bb", FALSE}, |
| + {"\xff", "\xff", TRUE}, |
| + {"aa.aa.aa", "aa.aa.bb", FALSE}, |
| + {"aa.aa.aa", "aa.aa.aa", TRUE}, |
| + {"aa.aa.aa", "*.aa.bb", FALSE}, |
| + {"aa.aa.aa", "*.aa.aa", TRUE}, |
| + {"192.168.0.1", "192.168.0.1", TRUE}, |
| + {"192.168.0.1", "*.168.0.1", FALSE}, |
| + {"192.168.0.1", "*.0.1", FALSE}, |
| + {"h.ello", "*.ello", FALSE}, |
| + {"h.ello.", "*.ello", FALSE}, |
| + {"h.ello", "*.ello.", FALSE}, |
| + {"h.e.llo", "*.e.llo", TRUE}, |
| + {"h.e.llo", " *.e.llo", FALSE}, |
| + {" h.e.llo", "*.e.llo", TRUE}, |
| + {"h.e.llo.", "*.e.llo", TRUE}, |
| + {"*.e.llo.", "*.e.llo", TRUE}, |
| + {"************.e.llo.", "*.e.llo", TRUE}, |
| + {"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" |
| + "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" |
| + "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" |
| + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" |
| + "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE" |
| + ".e.llo.", "*.e.llo", TRUE}, |
| + {"\xfe\xfe.e.llo.", "*.e.llo", TRUE}, |
| + {"h.e.llo.", "*.e.llo.", TRUE}, |
| + {"h.e.llo", "*.e.llo.", TRUE}, |
| + {".h.e.llo", "*.e.llo.", FALSE}, |
| + {"h.e.llo", "*.*.llo.", FALSE}, |
| + {"h.e.llo", "h.*.llo", FALSE}, |
| + {"h.e.llo", "h.e.*", FALSE}, |
| + {"hello", "*.ello", FALSE}, |
| + {"hello", "**llo", FALSE}, |
| + {"bar.foo.example.com", "*.example.com", FALSE}, |
| + {"foo.example.com", "*.example.com", TRUE}, |
| + {"baz.example.net", "b*z.example.net", FALSE}, |
| + {"foobaz.example.net", "*baz.example.net", FALSE}, |
| + {"xn--l8j.example.local", "x*.example.local", FALSE}, |
| + {"xn--l8j.example.net", "*.example.net", TRUE}, |
| + {"xn--l8j.example.net", "*j.example.net", FALSE}, |
| + {"xn--l8j.example.net", "xn--l8j.example.net", TRUE}, |
| + {"xn--l8j.example.net", "xn--l8j.*.net", FALSE}, |
| + {"xl8j.example.net", "*.example.net", TRUE}, |
| + {"fe80::3285:a9ff:fe46:b619", "*::3285:a9ff:fe46:b619", FALSE}, |
| + {"fe80::3285:a9ff:fe46:b619", "fe80::3285:a9ff:fe46:b619", TRUE}, |
| + {NULL, NULL, FALSE} |
| +}; |
| |
| -fail_if(Curl_cert_hostcheck(STRCONST("xxx.example.com"), |
| - STRCONST("www.example.com")), "bad 1"); |
| -fail_if(Curl_cert_hostcheck(STRCONST("*"), |
| - STRCONST("www.example.com")),"bad 2"); |
| -fail_if(Curl_cert_hostcheck(STRCONST("*.*.com"), |
| - STRCONST("www.example.com")), "bad 3"); |
| -fail_if(Curl_cert_hostcheck(STRCONST("*.example.com"), |
| - STRCONST("baa.foo.example.com")), "bad 4"); |
| -fail_if(Curl_cert_hostcheck(STRCONST("f*.example.com"), |
| - STRCONST("baa.example.com")), "bad 5"); |
| -fail_if(Curl_cert_hostcheck(STRCONST("*.com"), |
| - STRCONST("example.com")), "bad 6"); |
| -fail_if(Curl_cert_hostcheck(STRCONST("*fail.com"), |
| - STRCONST("example.com")), "bad 7"); |
| -fail_if(Curl_cert_hostcheck(STRCONST("*.example."), |
| - STRCONST("www.example.")), "bad 8"); |
| -fail_if(Curl_cert_hostcheck(STRCONST("*.example."), |
| - STRCONST("www.example")), "bad 9"); |
| -fail_if(Curl_cert_hostcheck(STRCONST(""), STRCONST("www")), "bad 10"); |
| -fail_if(Curl_cert_hostcheck(STRCONST("*"), STRCONST("www")), "bad 11"); |
| -fail_if(Curl_cert_hostcheck(STRCONST("*.168.0.0"), |
| - STRCONST("192.168.0.0")), "bad 12"); |
| -fail_if(Curl_cert_hostcheck(STRCONST("www.example.com"), |
| - STRCONST("192.168.0.0")), "bad 13"); |
| - |
| -#ifdef ENABLE_IPV6 |
| -fail_if(Curl_cert_hostcheck(STRCONST("*::3285:a9ff:fe46:b619"), |
| - STRCONST("fe80::3285:a9ff:fe46:b619")), "bad 14"); |
| -fail_unless(Curl_cert_hostcheck(STRCONST("fe80::3285:a9ff:fe46:b619"), |
| - STRCONST("fe80::3285:a9ff:fe46:b619")), |
| - "good 6"); |
| -#endif |
| +UNITTEST_START |
| +{ |
| + int i; |
| + for(i = 0; tests[i].host; i++) { |
| + if(tests[i].match != Curl_cert_hostcheck(tests[i].pattern, |
| + strlen(tests[i].pattern), |
| + tests[i].host, |
| + strlen(tests[i].host))) { |
| + fprintf(stderr, |
| + "HOST: %s\n" |
| + "PTRN: %s\n" |
| + "did %sMATCH\n", |
| + tests[i].host, |
| + tests[i].pattern, |
| + tests[i].match ? "NOT ": ""); |
| + unitfail++; |
| + } |
| + } |
| +} |
| |
| -#endif |
| +UNITTEST_STOP |
| +#else |
| |
| - /* you end the test code like this: */ |
| +UNITTEST_START |
| |
| UNITTEST_STOP |
| +#endif |