From 7610527d756cb024215533950a41a3813e9b79e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Tue, 18 Nov 2025 17:30:51 +0100 Subject: [PATCH 1/5] lexbor: Cherry pick "URL: fixed "use-after-poison" for an empty path entry." see lexbor/lexbor@9259b169e3cdaed9c61622dab92abb457bb8ddf5 Fixes php/php-src#20502 Fixes php/php-src#20521 --- NEWS | 2 ++ ext/lexbor/lexbor/url/url.c | 27 +++++++++++++++++---------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/NEWS b/NEWS index 469edebbb67a..daf490e95f4c 100644 --- a/NEWS +++ b/NEWS @@ -25,6 +25,8 @@ PHP NEWS - Lexbor: . Fixed bug GH-20501 (\Uri\WhatWg\Url lose host after calling withPath() or withQuery()). (lexborisov) + . Fixed bug GH-20502 (\Uri\WhatWg\Url crashes (SEGV) when parsing + malformed URL due to Lexbor memory corruption). (lexborisov) - Opcache: . Fixed bug GH-20329 (opcache.file_cache broken with full interned string diff --git a/ext/lexbor/lexbor/url/url.c b/ext/lexbor/lexbor/url/url.c index 99ba809b05fe..3483013eeaaf 100644 --- a/ext/lexbor/lexbor/url/url.c +++ b/ext/lexbor/lexbor/url/url.c @@ -1029,27 +1029,34 @@ lxb_url_path_append_wo_slash(lxb_url_t *url, static lxb_status_t lxb_url_path_append(lxb_url_t *url, const lxb_char_t *data, size_t length) { - size_t len; - lxb_char_t *p; + lxb_char_t *p, *begin; lexbor_str_t *str; str = &url->path.str; if (str->data == NULL) { p = lexbor_str_init(str, url->mraw, length + 1); - if (p == NULL) { - return LXB_STATUS_ERROR_MEMORY_ALLOCATION; - } + } + else { + /* + 2 == begin '/' and end '\0' */ + p = lexbor_str_check_size(str, url->mraw, length + 2); } - len = str->length; - str->length += 1; + if (p == NULL) { + return LXB_STATUS_ERROR_MEMORY_ALLOCATION; + } - p = lexbor_str_append(&url->path.str, url->mraw, data, length); + begin = &str->data[str->length]; + begin[0] = '/'; - str->data[len] = '/'; + if (length > 0) { + memcpy(&begin[1], data, sizeof(lxb_char_t) * length); + } - return (p != NULL) ? LXB_STATUS_OK : LXB_STATUS_ERROR_MEMORY_ALLOCATION; + str->length += length + 1; + str->data[str->length] = '\0'; + + return LXB_STATUS_OK; } static lxb_status_t From 4a12745705bcc863949f9b42abc0981f47052725 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Tue, 18 Nov 2025 09:49:13 -0800 Subject: [PATCH 2/5] xml: Optimize attribute array construction (#20515) Attributes can't be numeric strings by the definition of the grammar, so don't bother with the symbol table stuff. --- ext/xml/xml.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/xml/xml.c b/ext/xml/xml.c index 0b46aa6b2fe2..89c0dcd0dc4a 100644 --- a/ext/xml/xml.c +++ b/ext/xml/xml.c @@ -648,7 +648,7 @@ void xml_startElementHandler(void *userData, const XML_Char *name, const XML_Cha val = xml_utf8_decode(attributes[1], strlen((char *)attributes[1]), parser->target_encoding); ZVAL_STR(&tmp, val); - zend_symtable_update(Z_ARRVAL(args[2]), att, &tmp); + zend_hash_update(Z_ARRVAL(args[2]), att, &tmp); attributes += 2; @@ -688,7 +688,7 @@ void xml_startElementHandler(void *userData, const XML_Char *name, const XML_Cha val = xml_utf8_decode(attributes[1], strlen((char *)attributes[1]), parser->target_encoding); ZVAL_STR(&tmp, val); - zend_symtable_update(Z_ARRVAL(atr), att, &tmp); + zend_hash_update(Z_ARRVAL(atr), att, &tmp); atcnt++; attributes += 2; From 6054a900ff90a214df18e20e2544151a1dc11b4a Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Mon, 17 Nov 2025 20:54:44 +0100 Subject: [PATCH 3/5] libxml: Fix some deprecations regarding input buffer/parser handling Closes GH-20514. --- NEWS | 4 ++++ ext/libxml/libxml.c | 16 +++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index cacfdc28892d..0ef238afe42d 100644 --- a/NEWS +++ b/NEWS @@ -22,6 +22,10 @@ PHP NEWS . Fixed bug GH-20483 (ASAN stack overflow with fiber.stack_size INI small value). (David Carlier) +- LibXML: + . Fix some deprecations on newer libxml versions regarding input + buffer/parser handling. (ndossche) + - Opcache: . Fixed bug GH-20329 (opcache.file_cache broken with full interned string buffer). (Arnaud) diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c index d5009889549b..f70fa5bd8c85 100644 --- a/ext/libxml/libxml.c +++ b/ext/libxml/libxml.c @@ -644,11 +644,10 @@ php_libxml_output_buffer_create_filename(const char *URI, } /* Allocate the Output buffer front-end. */ - ret = xmlAllocOutputBuffer(encoder); - if (ret != NULL) { - ret->context = context; - ret->writecallback = php_libxml_streams_IO_write; - ret->closecallback = php_libxml_streams_IO_close; + ret = xmlOutputBufferCreateIO(php_libxml_streams_IO_write, php_libxml_streams_IO_close, context, encoder); + if (ret == NULL) { + php_libxml_streams_IO_close(context); + goto err; } return(ret); @@ -820,6 +819,7 @@ static xmlParserInputPtr _php_libxml_external_entity_loader(const char *URL, zend_string_release(callable_name); zval_ptr_dtor(&callable); } else { +#if LIBXML_VERSION < 21400 /* TODO: allow storing the encoding in the stream context? */ xmlCharEncoding enc = XML_CHAR_ENCODING_NONE; xmlParserInputBufferPtr pib = xmlAllocParserInputBuffer(enc); @@ -838,6 +838,12 @@ static xmlParserInputPtr _php_libxml_external_entity_loader(const char *URL, xmlFreeParserInputBuffer(pib); } } +#else + /* make stream not being closed when the zval is freed */ + GC_ADDREF(stream->res); + ret = xmlNewInputFromIO(NULL, php_libxml_streams_IO_read, php_libxml_streams_IO_close, stream, 0); + /* Note: if ret == NULL, the close operation will be executed, so don't DELREF stream->res upon failure! */ +#endif } } else if (Z_TYPE(retval) != IS_NULL) { /* retval not string nor resource nor null; convert to string */ From e929602ed06b33be834984acc072fb2c1f236eab Mon Sep 17 00:00:00 2001 From: David CARLIER Date: Tue, 18 Nov 2025 18:32:59 +0000 Subject: [PATCH 4/5] ext/soap: HTTP request micro optimisations. (#20516) smart string is a bit overkill for the cookie id here. --- ext/soap/php_http.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ext/soap/php_http.c b/ext/soap/php_http.c index 48305ad62725..63c0093eb05c 100644 --- a/ext/soap/php_http.c +++ b/ext/soap/php_http.c @@ -1014,8 +1014,7 @@ int make_http_soap_request( char *eqpos = strstr(cookie, "="); char *sempos = strstr(cookie, ";"); if (eqpos != NULL && (sempos == NULL || sempos > eqpos)) { - smart_str name = {0}; - int cookie_len; + size_t cookie_len; zval zcookie; if (sempos != NULL) { @@ -1024,8 +1023,7 @@ int make_http_soap_request( cookie_len = strlen(cookie)-(eqpos-cookie)-1; } - smart_str_appendl(&name, cookie, eqpos - cookie); - smart_str_0(&name); + zend_string *name = zend_string_init(cookie, eqpos - cookie, false); array_init(&zcookie); add_index_stringl(&zcookie, 0, eqpos + 1, cookie_len); @@ -1063,8 +1061,8 @@ int make_http_soap_request( GC_ADDREF(uri->host); } - zend_symtable_update(Z_ARRVAL_P(cookies), name.s, &zcookie); - smart_str_free(&name); + zend_symtable_update(Z_ARRVAL_P(cookies), name, &zcookie); + zend_string_release_ex(name, false); } cookie_itt = cookie_itt + cookie_len; From 2f05830a5fb8d8b00c9496fa64906c8a92be90aa Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Sun, 16 Nov 2025 23:01:44 +0100 Subject: [PATCH 5/5] zip: Don't truncate return value of zip_fread() with user sizes The return type has been zip_int64_t since 2009, so we shouldn't truncate to an int because the user may have requested a size that won't fit in an int. Closes GH-20509. --- NEWS | 1 + ext/zip/php_zip.c | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index 0ef238afe42d..516f6abd462a 100644 --- a/NEWS +++ b/NEWS @@ -52,6 +52,7 @@ PHP NEWS - Zip: . Fix crash in property existence test. (ndossche) + . Don't truncate return value of zip_fread() with user sizes. (ndossche) - Zlib: . Fix assertion failures resulting in crashes with stream filter diff --git a/ext/zip/php_zip.c b/ext/zip/php_zip.c index 15f55cba7125..f1630192e601 100644 --- a/ext/zip/php_zip.c +++ b/ext/zip/php_zip.c @@ -1332,7 +1332,6 @@ PHP_FUNCTION(zip_entry_read) zend_long len = 0; zip_read_rsrc * zr_rsrc; zend_string *buffer; - int n = 0; if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|l", &zip_entry, &len) == FAILURE) { RETURN_THROWS(); @@ -1348,7 +1347,7 @@ PHP_FUNCTION(zip_entry_read) if (zr_rsrc->zf) { buffer = zend_string_safe_alloc(1, len, 0, 0); - n = zip_fread(zr_rsrc->zf, ZSTR_VAL(buffer), ZSTR_LEN(buffer)); + zip_int64_t n = zip_fread(zr_rsrc->zf, ZSTR_VAL(buffer), ZSTR_LEN(buffer)); if (n > 0) { ZSTR_VAL(buffer)[n] = '\0'; ZSTR_LEN(buffer) = n; @@ -2910,8 +2909,6 @@ static void php_zip_get_from(INTERNAL_FUNCTION_PARAMETERS, int type) /* {{{ */ zend_string *filename; zend_string *buffer; - int n = 0; - if (type == 1) { if (zend_parse_parameters(ZEND_NUM_ARGS(), "P|ll", &filename, &len, &flags) == FAILURE) { RETURN_THROWS(); @@ -2948,7 +2945,7 @@ static void php_zip_get_from(INTERNAL_FUNCTION_PARAMETERS, int type) /* {{{ */ } buffer = zend_string_safe_alloc(1, len, 0, 0); - n = zip_fread(zf, ZSTR_VAL(buffer), ZSTR_LEN(buffer)); + zip_int64_t n = zip_fread(zf, ZSTR_VAL(buffer), ZSTR_LEN(buffer)); if (n < 1) { zend_string_efree(buffer); RETURN_EMPTY_STRING();