From fc50eb84ec94750a0e0860ca1c696055b5509596 Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Wed, 3 Feb 2021 12:53:25 +0100 Subject: Fix #80672 Null Dereference in SoapClient CVE-2021-21702 better fix for #77423 --- php-bug77423.patch | 223 +++++++++++++++++++++++++++++++++++++++++++++++++ php-bug80672.patch | 239 +++++++++++++++++++++++++++++++++++++++++++++++++++++ php56.spec | 9 +- 3 files changed, 470 insertions(+), 1 deletion(-) create mode 100644 php-bug80672.patch diff --git a/php-bug77423.patch b/php-bug77423.patch index 668adac..cfd971f 100644 --- a/php-bug77423.patch +++ b/php-bug77423.patch @@ -207,3 +207,226 @@ index 7ca1c46721..43e3b8faf3 100644 -- 2.29.2 +From b837c01d4cd290d87d2dd4c2d1195e9f209fe749 Mon Sep 17 00:00:00 2001 +From: "Christoph M. Becker" +Date: Tue, 19 Jan 2021 11:23:25 +0100 +Subject: [PATCH] Alternative fix for bug 77423 + +That bug report originally was about `parse_url()` misbehaving, but the +security aspect was actually only regarding `FILTER_VALIDATE_URL`. +Since the changes to `parse_url_ex()` apparently affect userland code +which is relying on the sloppy URL parsing[1], this alternative +restores the old parsing behavior, but ensures that the userinfo is +checked for correctness for `FILTER_VALIDATE_URL`. + +[1] + +(cherry picked from commit 4a89e726bd4d0571991dc22a9a1ad4509e8fe347) +(cherry picked from commit 9c673083cd46ee2a954a62156acbe4b6e657c048) +(cherry picked from commit 356f7008f36da60ec9794d48c55d117f1dd31903) +(cherry picked from commit b5d4f109bab648c0d07273d2a52a5f2560e7832b) +(cherry picked from commit efb6c49f08314aca84733b0e83d72cd20c8e0015) +--- + ext/filter/logical_filters.c | 25 +++++++++++++++++++ + .../tests/url => filter/tests}/bug77423.phpt | 15 ----------- + ext/standard/tests/strings/url_t.phpt | 6 +++-- + .../tests/url/parse_url_basic_001.phpt | 6 +++-- + .../tests/url/parse_url_basic_003.phpt | 2 +- + .../tests/url/parse_url_basic_005.phpt | 2 +- + ext/standard/url.c | 21 ---------------- + 7 files changed, 35 insertions(+), 42 deletions(-) + rename ext/{standard/tests/url => filter/tests}/bug77423.phpt (53%) + +diff --git a/ext/filter/logical_filters.c b/ext/filter/logical_filters.c +index 39a035f3af..9e1daffaab 100644 +--- a/ext/filter/logical_filters.c ++++ b/ext/filter/logical_filters.c +@@ -445,6 +445,24 @@ void php_filter_validate_regexp(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ + } + /* }}} */ + ++static int is_userinfo_valid(char *str) ++{ ++ const char *valid = "-._~!$&'()*+,;=:"; ++ const char *p = str; ++ size_t len = strlen(str); ++ ++ while (p - str < len) { ++ if (isalpha(*p) || isdigit(*p) || strchr(valid, *p)) { ++ p++; ++ } else if (*p == '%' && p - str <= len - 3 && isdigit(*(p+1)) && isxdigit(*(p+2))) { ++ p += 3; ++ } else { ++ return 0; ++ } ++ } ++ return 1; ++} ++ + void php_filter_validate_url(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ + { + php_url *url; +@@ -496,6 +514,13 @@ bad_url: + php_url_free(url); + RETURN_VALIDATION_FAILED + } ++ ++ if (url->user != NULL && !is_userinfo_valid(url->user)) { ++ php_url_free(url); ++ RETURN_VALIDATION_FAILED ++ ++ } ++ + php_url_free(url); + } + /* }}} */ +diff --git a/ext/standard/tests/url/bug77423.phpt b/ext/filter/tests/bug77423.phpt +similarity index 53% +rename from ext/standard/tests/url/bug77423.phpt +rename to ext/filter/tests/bug77423.phpt +index be03fe95e2..761c7c359a 100644 +--- a/ext/standard/tests/url/bug77423.phpt ++++ b/ext/filter/tests/bug77423.phpt +@@ -8,23 +8,8 @@ $urls = array( + ); + foreach ($urls as $url) { + var_dump(filter_var($url, FILTER_VALIDATE_URL)); +- var_dump(parse_url($url)); + } + ?> + --EXPECT-- + bool(false) +-array(3) { +- ["scheme"]=> +- string(4) "http" +- ["host"]=> +- string(19) "php.net\@aliyun.com" +- ["path"]=> +- string(7) "/aaa.do" +-} + bool(false) +-array(2) { +- ["scheme"]=> +- string(5) "https" +- ["host"]=> +- string(26) "example.com\uFF03@bing.com" +-} +diff --git a/ext/standard/tests/strings/url_t.phpt b/ext/standard/tests/strings/url_t.phpt +index 80e164a08e..e172061ec2 100644 +--- a/ext/standard/tests/strings/url_t.phpt ++++ b/ext/standard/tests/strings/url_t.phpt +@@ -575,13 +575,15 @@ $sample_urls = array ( + string(16) "some_page_ref123" + } + +---> http://secret@hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(6) { ++--> http://secret@hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(7) { + ["scheme"]=> + string(4) "http" + ["host"]=> +- string(26) "secret@hideout@www.php.net" ++ string(11) "www.php.net" + ["port"]=> + int(80) ++ ["user"]=> ++ string(14) "secret@hideout" + ["path"]=> + string(10) "/index.php" + ["query"]=> +diff --git a/ext/standard/tests/url/parse_url_basic_001.phpt b/ext/standard/tests/url/parse_url_basic_001.phpt +index c9e9d32de0..e468066a42 100644 +--- a/ext/standard/tests/url/parse_url_basic_001.phpt ++++ b/ext/standard/tests/url/parse_url_basic_001.phpt +@@ -507,13 +507,15 @@ echo "Done"; + string(16) "some_page_ref123" + } + +---> http://secret@hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(6) { ++--> http://secret@hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123: array(7) { + ["scheme"]=> + string(4) "http" + ["host"]=> +- string(26) "secret@hideout@www.php.net" ++ string(11) "www.php.net" + ["port"]=> + int(80) ++ ["user"]=> ++ string(14) "secret@hideout" + ["path"]=> + string(10) "/index.php" + ["query"]=> +diff --git a/ext/standard/tests/url/parse_url_basic_003.phpt b/ext/standard/tests/url/parse_url_basic_003.phpt +index 431de27009..70dc4bb90b 100644 +--- a/ext/standard/tests/url/parse_url_basic_003.phpt ++++ b/ext/standard/tests/url/parse_url_basic_003.phpt +@@ -68,7 +68,7 @@ echo "Done"; + --> http://secret:@www.php.net/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : string(11) "www.php.net" + --> http://:hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : string(11) "www.php.net" + --> http://secret:hideout@www.php.net/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : string(11) "www.php.net" +---> http://secret@hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : string(26) "secret@hideout@www.php.net" ++--> http://secret@hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : string(11) "www.php.net" + --> http://secret:hid:out@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : string(11) "www.php.net" + --> nntp://news.php.net : string(12) "news.php.net" + --> ftp://ftp.gnu.org/gnu/glic/glibc.tar.gz : string(11) "ftp.gnu.org" +diff --git a/ext/standard/tests/url/parse_url_basic_005.phpt b/ext/standard/tests/url/parse_url_basic_005.phpt +index b2c1a1d6dd..b2ca06ff96 100644 +--- a/ext/standard/tests/url/parse_url_basic_005.phpt ++++ b/ext/standard/tests/url/parse_url_basic_005.phpt +@@ -68,7 +68,7 @@ echo "Done"; + --> http://secret:@www.php.net/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : string(6) "secret" + --> http://:hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : string(0) "" + --> http://secret:hideout@www.php.net/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : string(6) "secret" +---> http://secret@hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : NULL ++--> http://secret@hideout@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : string(14) "secret@hideout" + --> http://secret:hid:out@www.php.net:80/index.php?test=1&test2=char&test3=mixesCI#some_page_ref123 : string(6) "secret" + --> nntp://news.php.net : NULL + --> ftp://ftp.gnu.org/gnu/glic/glibc.tar.gz : NULL +diff --git a/ext/standard/url.c b/ext/standard/url.c +index 8da9da3d6a..0278bd47e8 100644 +--- a/ext/standard/url.c ++++ b/ext/standard/url.c +@@ -92,22 +92,6 @@ PHPAPI php_url *php_url_parse(char const *str) + return php_url_parse_ex(str, strlen(str)); + } + +-static int is_userinfo_valid(const char *str, size_t len) +-{ +- char *valid = "-._~!$&'()*+,;=:"; +- char *p = str; +- while (p - str < len) { +- if (isalpha(*p) || isdigit(*p) || strchr(valid, *p)) { +- p++; +- } else if (*p == '%' && p - str <= len - 3 && isdigit(*(p+1)) && isxdigit(*(p+2))) { +- p += 3; +- } else { +- return 0; +- } +- } +- return 1; +-} +- + /* {{{ php_url_parse + */ + PHPAPI php_url *php_url_parse_ex(char const *str, int length) +@@ -246,18 +230,13 @@ PHPAPI php_url *php_url_parse_ex(char const *str, int length) + ret->pass = estrndup(pp, (p-pp)); + php_replace_controlchars_ex(ret->pass, (p-pp)); + } else { +- if (!is_userinfo_valid(s, p-s)) { +- goto check_port; +- } + ret->user = estrndup(s, (p-s)); + php_replace_controlchars_ex(ret->user, (p-s)); +- + } + + s = p + 1; + } + +-check_port: + /* check for port */ + if (s < ue && *s == '[' && *(e-1) == ']') { + /* Short circuit portscan, +-- +2.29.2 + diff --git a/php-bug80672.patch b/php-bug80672.patch new file mode 100644 index 0000000..bc94bd3 --- /dev/null +++ b/php-bug80672.patch @@ -0,0 +1,239 @@ +From d0bc0dbb20e906324e874197fa71d7b4583e59b6 Mon Sep 17 00:00:00 2001 +From: Stanislav Malyshev +Date: Sun, 31 Jan 2021 21:15:23 -0800 +Subject: [PATCH 1/2] Fix bug #80672 - Null Dereference in SoapClient + +(cherry picked from commit 3c939e3f69955d087e0bb671868f7267dfb2a502) +(cherry picked from commit f1e2cfa008d1596251968d13eb9a8539dba6879f) +--- + NEWS | 5 +++++ + ext/soap/php_sdl.c | 26 ++++++++++++++------------ + ext/soap/php_xml.c | 4 ++-- + ext/soap/tests/bug80672.phpt | 15 +++++++++++++++ + ext/soap/tests/bug80672.xml | 6 ++++++ + 5 files changed, 42 insertions(+), 14 deletions(-) + create mode 100644 ext/soap/tests/bug80672.phpt + create mode 100644 ext/soap/tests/bug80672.xml + +diff --git a/NEWS b/NEWS +index 43e3b8faf3..8e9bd9648e 100644 +--- a/NEWS ++++ b/NEWS +@@ -1,6 +1,11 @@ + PHP NEWS + ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| + ++Backported from 7.3.27 ++ ++- SOAP: ++ . Fixed bug #80672 (Null Dereference in SoapClient). (CVE-2021-21702) (cmb, Stas) ++ + Backported from 7.3.26 + + - Standard: +diff --git a/ext/soap/php_sdl.c b/ext/soap/php_sdl.c +index 51aea0021e..49f61374cb 100644 +--- a/ext/soap/php_sdl.c ++++ b/ext/soap/php_sdl.c +@@ -318,6 +318,8 @@ void sdl_restore_uri_credentials(sdlCtx *ctx TSRMLS_DC) + ctx->context = NULL; + } + ++#define SAFE_STR(a) ((a)?a:"") ++ + static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, int include TSRMLS_DC) + { + sdlPtr tmpsdl = ctx->sdl; +@@ -379,7 +381,7 @@ static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, int include + if (node_is_equal_ex(trav2, "schema", XSD_NAMESPACE)) { + load_schema(ctx, trav2 TSRMLS_CC); + } else if (is_wsdl_element(trav2) && !node_is_equal(trav2,"documentation")) { +- soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav2->name); ++ soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav2->name)); + } + trav2 = trav2->next; + } +@@ -440,7 +442,7 @@ static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, int include + soap_error0(E_ERROR, "Parsing WSDL: has no name attribute"); + } + } else if (!node_is_equal(trav,"documentation")) { +- soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); ++ soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); + } + trav = trav->next; + } +@@ -550,7 +552,7 @@ static sdlSoapBindingFunctionHeaderPtr wsdl_soap_binding_header(sdlCtx* ctx, xml + } + smart_str_free(&key); + } else if (is_wsdl_element(trav) && !node_is_equal(trav,"documentation")) { +- soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); ++ soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); + } + trav = trav->next; + } +@@ -655,7 +657,7 @@ static void wsdl_soap_binding_body(sdlCtx* ctx, xmlNodePtr node, char* wsdl_soap + } + smart_str_free(&key); + } else if (is_wsdl_element(trav) && !node_is_equal(trav,"documentation")) { +- soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); ++ soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); + } + trav = trav->next; + } +@@ -687,14 +689,14 @@ static HashTable* wsdl_message(sdlCtx *ctx, xmlChar* message_name) + sdlParamPtr param; + + if (trav->ns != NULL && strcmp((char*)trav->ns->href, WSDL_NAMESPACE) != 0) { +- soap_error1(E_ERROR, "Parsing WSDL: Unexpected extensibility element <%s>", trav->name); ++ soap_error1(E_ERROR, "Parsing WSDL: Unexpected extensibility element <%s>", SAFE_STR(trav->name)); + } + if (node_is_equal(trav,"documentation")) { + trav = trav->next; + continue; + } + if (!node_is_equal(trav,"part")) { +- soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); ++ soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); + } + part = trav; + param = emalloc(sizeof(sdlParam)); +@@ -703,7 +705,7 @@ static HashTable* wsdl_message(sdlCtx *ctx, xmlChar* message_name) + + name = get_attribute(part->properties, "name"); + if (name == NULL) { +- soap_error1(E_ERROR, "Parsing WSDL: No name associated with '%s'", message->name); ++ soap_error1(E_ERROR, "Parsing WSDL: No name associated with '%s'", SAFE_STR(message->name)); + } + + param->paramName = estrdup((char*)name->children->content); +@@ -773,7 +775,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri TSRMLS_DC) + continue; + } + if (!node_is_equal(trav,"port")) { +- soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); ++ soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); + } + + port = trav; +@@ -812,7 +814,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri TSRMLS_DC) + } + } + if (trav2 != address && is_wsdl_element(trav2) && !node_is_equal(trav2,"documentation")) { +- soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav2->name); ++ soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav2->name)); + } + trav2 = trav2->next; + } +@@ -914,7 +916,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri TSRMLS_DC) + continue; + } + if (!node_is_equal(trav2,"operation")) { +- soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav2->name); ++ soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav2->name)); + } + + operation = trav2; +@@ -933,7 +935,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri TSRMLS_DC) + !node_is_equal(trav3,"output") && + !node_is_equal(trav3,"fault") && + !node_is_equal(trav3,"documentation")) { +- soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav3->name); ++ soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav3->name)); + } + trav3 = trav3->next; + } +@@ -1111,7 +1113,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri TSRMLS_DC) + } + } + } else if (is_wsdl_element(trav) && !node_is_equal(trav,"documentation")) { +- soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); ++ soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); + } + trav = trav->next; + } +diff --git a/ext/soap/php_xml.c b/ext/soap/php_xml.c +index f3b49dfd41..4694b4e05d 100644 +--- a/ext/soap/php_xml.c ++++ b/ext/soap/php_xml.c +@@ -205,7 +205,7 @@ xmlNsPtr node_find_ns(xmlNodePtr node) + + int attr_is_equal_ex(xmlAttrPtr node, char *name, char *ns) + { +- if (name == NULL || strcmp((char*)node->name, name) == 0) { ++ if (name == NULL || ((node->name) && strcmp((char*)node->name, name) == 0)) { + if (ns) { + xmlNsPtr nsPtr = attr_find_ns(node); + if (nsPtr) { +@@ -221,7 +221,7 @@ int attr_is_equal_ex(xmlAttrPtr node, char *name, char *ns) + + int node_is_equal_ex(xmlNodePtr node, char *name, char *ns) + { +- if (name == NULL || strcmp((char*)node->name, name) == 0) { ++ if (name == NULL || ((node->name) && strcmp((char*)node->name, name) == 0)) { + if (ns) { + xmlNsPtr nsPtr = node_find_ns(node); + if (nsPtr) { +diff --git a/ext/soap/tests/bug80672.phpt b/ext/soap/tests/bug80672.phpt +new file mode 100644 +index 0000000000..71e2b1d841 +--- /dev/null ++++ b/ext/soap/tests/bug80672.phpt +@@ -0,0 +1,15 @@ ++--TEST-- ++Bug #80672 Null Dereference in SoapClient ++--SKIPIF-- ++ ++--FILE-- ++query(array('sXML' => 'something')); ++} catch(SoapFault $e) { ++ print $e->getMessage(); ++} ++?> ++--EXPECTF-- ++SOAP-ERROR: Parsing WSDL: Unexpected WSDL element <> +\ No newline at end of file +diff --git a/ext/soap/tests/bug80672.xml b/ext/soap/tests/bug80672.xml +new file mode 100644 +index 0000000000..0fa185bf1e +--- /dev/null ++++ b/ext/soap/tests/bug80672.xml +@@ -0,0 +1,6 @@ ++ ++ ++ ++ +-- +2.29.2 + +From 0373255df92545ded6df1a8b1af58ee63bae1754 Mon Sep 17 00:00:00 2001 +From: Nikita Popov +Date: Mon, 1 Feb 2021 09:46:17 +0100 +Subject: [PATCH 2/2] Fix build + +(cherry picked from commit e5d767d27f94895e09f0321562fd3774d4656164) +(cherry picked from commit 02352d5acc1896756dcb4645f54689ffdcc4ca52) +--- + ext/soap/php_sdl.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/ext/soap/php_sdl.c b/ext/soap/php_sdl.c +index 49f61374cb..4a3a2fab52 100644 +--- a/ext/soap/php_sdl.c ++++ b/ext/soap/php_sdl.c +@@ -318,7 +318,7 @@ void sdl_restore_uri_credentials(sdlCtx *ctx TSRMLS_DC) + ctx->context = NULL; + } + +-#define SAFE_STR(a) ((a)?a:"") ++#define SAFE_STR(a) ((a)?((const char *)a):"") + + static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, int include TSRMLS_DC) + { +-- +2.29.2 + diff --git a/php56.spec b/php56.spec index ef2d74f..9e2ed9d 100644 --- a/php56.spec +++ b/php56.spec @@ -157,7 +157,7 @@ Summary: PHP scripting language for creating dynamic web sites Name: php Version: 5.6.40 -Release: 24%{?dist} +Release: 25%{?dist} # All files licensed under PHP version 3.01, except # Zend is licensed under Zend # TSRM is licensed under BSD @@ -259,6 +259,7 @@ Patch243: php-bug79797.patch Patch244: php-bug79877.patch Patch246: php-bug79699.patch Patch247: php-bug77423.patch +Patch248: php-bug80672.patch # Fixes for tests (300+) # Factory is droped from system tzdata @@ -1068,6 +1069,7 @@ echo CIBLE = %{name}-%{version}-%{release} oci8=%{with_oci8} libzip=%{with_libzi %patch244 -p1 -b .bug79877 %patch246 -p1 -b .bug79699 %patch247 -p1 -b .bug77423 +%patch248 -p1 -b .bug80672 # Fixes for tests %patch300 -p1 -b .datetests @@ -2124,6 +2126,11 @@ EOF %changelog +* Wed Feb 3 2021 Remi Collet - 5.6.40-25 +- Fix #80672 Null Dereference in SoapClient + CVE-2021-21702 +- better fix for #77423 + * Mon Jan 4 2021 Remi Collet - 5.6.40-24 - Fix #77423 FILTER_VALIDATE_URL accepts URLs with invalid userinfo CVE-2020-7071 -- cgit