summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--failed.txt31
-rw-r--r--php-7.0.7-curl.patch15
-rw-r--r--php-7.4.33-icu.patch35
-rw-r--r--php-7.4.33-pcretests.patch43
-rw-r--r--php-7.4.33-proto.patch392
-rw-r--r--php-cve-2024-11233.patch68
-rw-r--r--php-cve-2024-11234.patch95
-rw-r--r--php-cve-2024-11236.patch119
-rw-r--r--php-cve-2024-8929.patch2714
-rw-r--r--php-cve-2024-8932.patch139
-rw-r--r--php-cve-2025-1217.patch917
-rw-r--r--php-cve-2025-1219.patch1906
-rw-r--r--php-cve-2025-1220.patch154
-rw-r--r--php-cve-2025-1734.patch314
-rw-r--r--php-cve-2025-1735.patch492
-rw-r--r--php-cve-2025-1736.patch242
-rw-r--r--php-cve-2025-1861.patch349
-rw-r--r--php-cve-2025-6491.patch103
-rw-r--r--php-fpm.service2
-rw-r--r--php-ghsa-4w77-75f9-2c8w.patch135
-rw-r--r--php.spec129
21 files changed, 8332 insertions, 62 deletions
diff --git a/failed.txt b/failed.txt
index 4cf5e72..41e59fd 100644
--- a/failed.txt
+++ b/failed.txt
@@ -1,26 +1,21 @@
-===== 7.4.33-18 (2024-09-26)
+===== 7.4.33-24 (2025-07-03)
$ grep -ar 'Tests failed' /var/lib/mock/*/build.log
-/var/lib/mock/scl74el8a/build.log:Tests failed : 3
-/var/lib/mock/scl74el8x/build.log:Tests failed : 3
-/var/lib/mock/scl74el9a/build.log:Tests failed : 1
-/var/lib/mock/scl74el9x/build.log:Tests failed : 1
-/var/lib/mock/scl74fc39a/build.log:Tests failed : 1
-/var/lib/mock/scl74fc39x/build.log:Tests failed : 1
-/var/lib/mock/scl80fc40a/build.log:Tests failed : 2
-/var/lib/mock/scl80fc40x/build.log:Tests failed : 2
-/var/lib/mock/scl80fc41a/build.log:Tests failed : 2
-/var/lib/mock/scl80fc41x/build.log:Tests failed : 2
+/var/lib/mock/scl74el8a/build.log:Tests failed : 0
+/var/lib/mock/scl74el8x/build.log:Tests failed : 0
+/var/lib/mock/scl74el9a/build.log:Tests failed : 0
+/var/lib/mock/scl74el9x/build.log:Tests failed : 0
+/var/lib/mock/scl74el10a/build.log:Tests failed : 0
+/var/lib/mock/scl74el10x/build.log:Tests failed : 0
+/var/lib/mock/scl80fc40a/build.log:Tests failed : 0
+/var/lib/mock/scl80fc40x/build.log:Tests failed : 0
+/var/lib/mock/scl80fc41a/build.log:Tests failed : 0
+/var/lib/mock/scl80fc41x/build.log:Tests failed : 0
+/var/lib/mock/scl74fc42a/build.log:Tests failed : 0
+/var/lib/mock/scl74fc42x/build.log:Tests failed : 0
-el8:
- 3 openssl_error_string() tests [ext/openssl/tests/openssl_error_string_basic.phpt]
- 3 openssl_open() tests [ext/openssl/tests/openssl_open_basic.phpt]
-all:
- 3 openssl_private_decrypt() tests [ext/openssl/tests/openssl_private_decrypt_basic.phpt]
-fc40, fc41:
- 3 openssl_x509_parse() tests [ext/openssl/tests/openssl_x509_parse_basic.phpt]
(1) proc_open give erratic test results :(
diff --git a/php-7.0.7-curl.patch b/php-7.0.7-curl.patch
deleted file mode 100644
index 218db98..0000000
--- a/php-7.0.7-curl.patch
+++ /dev/null
@@ -1,15 +0,0 @@
-diff -up php-7.0.7RC1/ext/curl/interface.c.curltls php-7.0.7RC1/ext/curl/interface.c
---- php-7.0.7RC1/ext/curl/interface.c.curltls 2016-05-10 17:28:33.000000000 +0200
-+++ php-7.0.7RC1/ext/curl/interface.c 2016-05-12 07:43:00.900419946 +0200
-@@ -1257,7 +1257,11 @@ PHP_MINIT_FUNCTION(curl)
-
- #if LIBCURL_VERSION_NUM >= 0x072200 /* Available since 7.34.0 */
- REGISTER_CURL_CONSTANT(CURLOPT_LOGIN_OPTIONS);
-+#endif
-
-+#if LIBCURL_VERSION_NUM >= 0x071300 /* Available since 7.19.0 (in upstream curl 7.34)
-+ backported in RHEL-7 curl-7.29.0-16.el7 rhbz#1012136
-+ backported in RHEL-6 curl-7.19.7-43.el6 rhbz#1036789 */
- REGISTER_CURL_CONSTANT(CURL_SSLVERSION_TLSv1_0);
- REGISTER_CURL_CONSTANT(CURL_SSLVERSION_TLSv1_1);
- REGISTER_CURL_CONSTANT(CURL_SSLVERSION_TLSv1_2);
diff --git a/php-7.4.33-icu.patch b/php-7.4.33-icu.patch
new file mode 100644
index 0000000..48940fd
--- /dev/null
+++ b/php-7.4.33-icu.patch
@@ -0,0 +1,35 @@
+From cc46a4e6b5a413bab3e264c1dcaaf7052f54fbc4 Mon Sep 17 00:00:00 2001
+From: David Carlier <devnexen@gmail.com>
+Date: Sat, 17 Feb 2024 21:38:21 +0000
+Subject: [PATCH] ext/intl: level up c++ runtime std for icu 74 and onwards.
+
+to align with what is required to build icu 74 itself.
+
+Close GH-14002
+---
+ NEWS | 3 +++
+ ext/intl/config.m4 | 11 ++++++++++-
+ 2 files changed, 13 insertions(+), 1 deletion(-)
+
+diff --git a/ext/intl/config.m4 b/ext/intl/config.m4
+index dd687bcd97de3..48f5147ca7bbf 100644
+--- a/ext/intl/config.m4
++++ b/ext/intl/config.m4
+@@ -83,7 +83,16 @@ if test "$PHP_INTL" != "no"; then
+ breakiterator/codepointiterator_methods.cpp"
+
+ PHP_REQUIRE_CXX()
+- PHP_CXX_COMPILE_STDCXX(11, mandatory, PHP_INTL_STDCXX)
++
++ AC_MSG_CHECKING([if intl requires -std=gnu++17])
++ AS_IF([test "$PKG_CONFIG icu-uc --atleast-version=74"],[
++ AC_MSG_RESULT([yes])
++ PHP_CXX_COMPILE_STDCXX(17, mandatory, PHP_INTL_STDCXX)
++ ],[
++ AC_MSG_RESULT([no])
++ PHP_CXX_COMPILE_STDCXX(11, mandatory, PHP_INTL_STDCXX)
++ ])
++
+ PHP_INTL_CXX_FLAGS="$INTL_COMMON_FLAGS $PHP_INTL_STDCXX $ICU_CXXFLAGS"
+ if test "$ext_shared" = "no"; then
+ PHP_ADD_SOURCES(PHP_EXT_DIR(intl), $PHP_INTL_CXX_SOURCES, $PHP_INTL_CXX_FLAGS)
diff --git a/php-7.4.33-pcretests.patch b/php-7.4.33-pcretests.patch
new file mode 100644
index 0000000..c226661
--- /dev/null
+++ b/php-7.4.33-pcretests.patch
@@ -0,0 +1,43 @@
+From c3150fcc89825f50d476b1b1971870aeb71f167d Mon Sep 17 00:00:00 2001
+From: Remi Collet <remi@remirepo.net>
+Date: Wed, 12 Mar 2025 07:48:05 +0100
+Subject: [PATCH 1/2] Relax test expectation for pcre2lib 10.45 Using
+ e92848789acd8aa5cf32fedb519ba9378ac64e02
+
+---
+ ext/pcre/tests/bug75457.phpt | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/ext/pcre/tests/bug75457.phpt b/ext/pcre/tests/bug75457.phpt
+index ee5ab162f8a6c..87dc12a1ad056 100644
+--- a/ext/pcre/tests/bug75457.phpt
++++ b/ext/pcre/tests/bug75457.phpt
+@@ -6,5 +6,5 @@ $pattern = "/(((?(?C)0?=))(?!()0|.(?0)0)())/";
+ var_dump(preg_match($pattern, "hello"));
+ ?>
+ --EXPECTF--
+-Warning: preg_match(): Compilation failed: assertion expected after (?( or (?(?C) at offset 8 in %sbug75457.php on line %d
++Warning: preg_match(): Compilation failed: %r(atomic|)%r assertion expected after (?( or (?(?C) at offset 8 in %sbug75457.php on line %d
+ bool(false)
+
+From 126095700a02b9aa1f33764a63c93a70e8373ad8 Mon Sep 17 00:00:00 2001
+From: Remi Collet <remi@famillecollet.com>
+Date: Wed, 12 Mar 2025 09:36:33 +0100
+Subject: [PATCH 2/2] Update ext/pcre/tests/bug75457.phpt
+
+Co-authored-by: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
+---
+ ext/pcre/tests/bug75457.phpt | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/ext/pcre/tests/bug75457.phpt b/ext/pcre/tests/bug75457.phpt
+index 87dc12a1ad056..1401b25ff6fb7 100644
+--- a/ext/pcre/tests/bug75457.phpt
++++ b/ext/pcre/tests/bug75457.phpt
+@@ -6,5 +6,5 @@ $pattern = "/(((?(?C)0?=))(?!()0|.(?0)0)())/";
+ var_dump(preg_match($pattern, "hello"));
+ ?>
+ --EXPECTF--
+-Warning: preg_match(): Compilation failed: %r(atomic|)%r assertion expected after (?( or (?(?C) at offset 8 in %sbug75457.php on line %d
++Warning: preg_match(): Compilation failed:%r( atomic|)%r assertion expected after (?( or (?(?C) at offset 8 in %sbug75457.php on line %d
+ bool(false)
diff --git a/php-7.4.33-proto.patch b/php-7.4.33-proto.patch
new file mode 100644
index 0000000..2e20717
--- /dev/null
+++ b/php-7.4.33-proto.patch
@@ -0,0 +1,392 @@
+From f566cba0bb6bd53b1d44d5097e68201412b00f7a Mon Sep 17 00:00:00 2001
+From: Remi Collet <remi@php.net>
+Date: Thu, 25 Nov 2021 13:16:26 +0100
+Subject: [PATCH] fix [-Wstrict-prototypes] build warnings in ext/gd
+
+---
+ ext/gd/config.m4 | 2 --
+ ext/gd/gd.c | 58 ++++++++++++++++++++++++------------------------
+ 2 files changed, 29 insertions(+), 31 deletions(-)
+
+diff -up a/ext/gd/gd.c.proto b/ext/gd/gd.c
+--- a/ext/gd/gd.c.proto 2022-10-31 11:36:07.000000000 +0100
++++ b/ext/gd/gd.c 2025-02-13 12:04:07.860118321 +0100
+@@ -138,9 +138,9 @@ static void php_image_filter_pixelate(IN
+ static void php_image_filter_scatter(INTERNAL_FUNCTION_PARAMETERS);
+
+ /* End Section filters declarations */
+-static gdImagePtr _php_image_create_from_string (zval *Data, char *tn, gdImagePtr (*ioctx_func_p)());
+-static void _php_image_create_from(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn, gdImagePtr (*func_p)(), gdImagePtr (*ioctx_func_p)());
+-static void _php_image_output(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn, void (*func_p)());
++static gdImagePtr _php_image_create_from_string (zval *Data, char *tn, gdImagePtr (*ioctx_func_p)(gdIOCtxPtr));
++static void _php_image_create_from(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn, gdImagePtr (*func_p)(FILE *), gdImagePtr (*ioctx_func_p)(gdIOCtxPtr));
++static void _php_image_output(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn);
+ static int _php_image_type(char data[12]);
+ static void _php_image_convert(INTERNAL_FUNCTION_PARAMETERS, int image_type);
+
+@@ -2330,7 +2330,7 @@ static int _php_image_type (char data[12
+
+ /* {{{ _php_image_create_from_string
+ */
+-gdImagePtr _php_image_create_from_string(zval *data, char *tn, gdImagePtr (*ioctx_func_p)())
++gdImagePtr _php_image_create_from_string(zval *data, char *tn, gdImagePtr (*ioctx_func_p)(gdIOCtxPtr))
+ {
+ gdImagePtr im;
+ gdIOCtx *io_ctx;
+@@ -2440,7 +2440,7 @@ PHP_FUNCTION(imagecreatefromstring)
+
+ /* {{{ _php_image_create_from
+ */
+-static void _php_image_create_from(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn, gdImagePtr (*func_p)(), gdImagePtr (*ioctx_func_p)())
++static void _php_image_create_from(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn, gdImagePtr (*func_p)(FILE *), gdImagePtr (*ioctx_func_p)(gdIOCtxPtr))
+ {
+ char *file;
+ size_t file_len;
+@@ -2477,7 +2477,7 @@ static void _php_image_create_from(INTER
+ if (FAILURE == php_stream_cast(stream, PHP_STREAM_AS_STDIO, (void**)&fp, REPORT_ERRORS)) {
+ goto out_err;
+ }
+- } else if (ioctx_func_p) {
++ } else if (ioctx_func_p || image_type == PHP_GDIMG_TYPE_GD2PART) {
+ /* we can create an io context */
+ gdIOCtx* io_ctx;
+ zend_string *buff;
+@@ -2501,7 +2501,7 @@ static void _php_image_create_from(INTER
+ }
+
+ if (image_type == PHP_GDIMG_TYPE_GD2PART) {
+- im = (*ioctx_func_p)(io_ctx, srcx, srcy, width, height);
++ im = gdImageCreateFromGd2PartCtx(io_ctx, srcx, srcy, width, height);
+ } else {
+ im = (*ioctx_func_p)(io_ctx);
+ }
+@@ -2519,7 +2519,7 @@ static void _php_image_create_from(INTER
+ if (!im && fp) {
+ switch (image_type) {
+ case PHP_GDIMG_TYPE_GD2PART:
+- im = (*func_p)(fp, srcx, srcy, width, height);
++ im = gdImageCreateFromGd2Part(fp, srcx, srcy, width, height);
+ break;
+ #if defined(HAVE_GD_XPM)
+ case PHP_GDIMG_TYPE_XPM:
+@@ -2608,7 +2608,7 @@ PHP_FUNCTION(imagecreatefromxbm)
+ Create a new image from XPM file or URL */
+ PHP_FUNCTION(imagecreatefromxpm)
+ {
+- _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_XPM, "XPM", gdImageCreateFromXpm, NULL);
++ _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_XPM, "XPM", NULL, NULL);
+ }
+ /* }}} */
+ #endif
+@@ -2641,7 +2641,7 @@ PHP_FUNCTION(imagecreatefromgd2)
+ Create a new image from a given part of GD2 file or URL */
+ PHP_FUNCTION(imagecreatefromgd2part)
+ {
+- _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD2PART, "GD2", gdImageCreateFromGd2Part, gdImageCreateFromGd2PartCtx);
++ _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD2PART, "GD2", NULL, NULL);
+ }
+ /* }}} */
+
+@@ -2667,7 +2667,7 @@ PHP_FUNCTION(imagecreatefromtga)
+
+ /* {{{ _php_image_output
+ */
+-static void _php_image_output(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn, void (*func_p)())
++static void _php_image_output(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn)
+ {
+ zval *imgind;
+ char *file = NULL;
+@@ -2720,13 +2720,13 @@ static void _php_image_output(INTERNAL_F
+ gdImageWBMP(im, q, fp);
+ break;
+ case PHP_GDIMG_TYPE_GD:
+- (*func_p)(im, fp);
++ gdImageGd(im, fp);
+ break;
+ case PHP_GDIMG_TYPE_GD2:
+ if (q == -1) {
+ q = 128;
+ }
+- (*func_p)(im, fp, q, t);
++ gdImageGd2(im, fp, q, t);
+ break;
+ default:
+ ZEND_ASSERT(0);
+@@ -2756,13 +2756,13 @@ static void _php_image_output(INTERNAL_F
+ gdImageWBMP(im, q, tmp);
+ break;
+ case PHP_GDIMG_TYPE_GD:
+- (*func_p)(im, tmp);
++ gdImageGd(im, tmp);
+ break;
+ case PHP_GDIMG_TYPE_GD2:
+ if (q == -1) {
+ q = 128;
+ }
+- (*func_p)(im, tmp, q, t);
++ gdImageGd2(im, tmp, q, t);
+ break;
+ default:
+ ZEND_ASSERT(0);
+@@ -2786,7 +2786,7 @@ static void _php_image_output(INTERNAL_F
+ Output XBM image to browser or file */
+ PHP_FUNCTION(imagexbm)
+ {
+- _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_XBM, "XBM", gdImageXbmCtx);
++ _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_XBM, "XBM");
+ }
+ /* }}} */
+
+@@ -2794,7 +2794,7 @@ PHP_FUNCTION(imagexbm)
+ Output GIF image to browser or file */
+ PHP_FUNCTION(imagegif)
+ {
+- _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GIF, "GIF", gdImageGifCtx);
++ _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GIF, "GIF");
+ }
+ /* }}} */
+
+@@ -2803,7 +2803,7 @@ PHP_FUNCTION(imagegif)
+ Output PNG image to browser or file */
+ PHP_FUNCTION(imagepng)
+ {
+- _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_PNG, "PNG", gdImagePngCtxEx);
++ _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_PNG, "PNG");
+ }
+ /* }}} */
+ #endif /* HAVE_GD_PNG */
+@@ -2814,7 +2814,7 @@ PHP_FUNCTION(imagepng)
+ Output WEBP image to browser or file */
+ PHP_FUNCTION(imagewebp)
+ {
+- _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_WEBP, "WEBP", gdImageWebpCtx);
++ _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_WEBP, "WEBP");
+ }
+ /* }}} */
+ #endif /* HAVE_GD_WEBP */
+@@ -2825,7 +2825,7 @@ PHP_FUNCTION(imagewebp)
+ Output JPEG image to browser or file */
+ PHP_FUNCTION(imagejpeg)
+ {
+- _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_JPG, "JPEG", gdImageJpegCtx);
++ _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_JPG, "JPEG");
+ }
+ /* }}} */
+ #endif /* HAVE_GD_JPG */
+@@ -2834,7 +2834,7 @@ PHP_FUNCTION(imagejpeg)
+ Output WBMP image to browser or file */
+ PHP_FUNCTION(imagewbmp)
+ {
+- _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_WBM, "WBMP", gdImageWBMPCtx);
++ _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_WBM, "WBMP");
+ }
+ /* }}} */
+
+@@ -2842,7 +2842,7 @@ PHP_FUNCTION(imagewbmp)
+ Output GD image to browser or file */
+ PHP_FUNCTION(imagegd)
+ {
+- _php_image_output(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD, "GD", gdImageGd);
++ _php_image_output(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD, "GD");
+ }
+ /* }}} */
+
+@@ -2850,7 +2850,7 @@ PHP_FUNCTION(imagegd)
+ Output GD2 image to browser or file */
+ PHP_FUNCTION(imagegd2)
+ {
+- _php_image_output(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD2, "GD2", gdImageGd2);
++ _php_image_output(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD2, "GD2");
+ }
+ /* }}} */
+
+@@ -2859,7 +2859,7 @@ PHP_FUNCTION(imagegd2)
+ Output BMP image to browser or file */
+ PHP_FUNCTION(imagebmp)
+ {
+- _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_BMP, "BMP", gdImageBmpCtx);
++ _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_BMP, "BMP");
+ }
+ /* }}} */
+ #endif
+@@ -4146,7 +4146,7 @@ static void php_imagettftext_common(INTE
+ Output WBMP image to browser or file */
+ PHP_FUNCTION(image2wbmp)
+ {
+- _php_image_output(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_CONVERT_WBM, "WBMP", NULL);
++ _php_image_output(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_CONVERT_WBM, "WBMP");
+ }
+ /* }}} */
+
+diff -up a/ext/gd/gd_ctx.c.proto b/ext/gd/gd_ctx.c
+--- a/ext/gd/gd_ctx.c.proto 2025-02-13 11:42:48.478248591 +0100
++++ b/ext/gd/gd_ctx.c 2025-02-13 11:52:48.325740296 +0100
+@@ -77,7 +77,7 @@ static void _php_image_stream_ctxfreeand
+ } /* }}} */
+
+ /* {{{ _php_image_output_ctx */
+-static void _php_image_output_ctx(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn, void (*func_p)())
++static void _php_image_output_ctx(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn)
+ {
+ zval *imgind;
+ char *file = NULL;
+@@ -177,16 +177,20 @@ static void _php_image_output_ctx(INTERN
+
+ switch(image_type) {
+ case PHP_GDIMG_TYPE_JPG:
+- (*func_p)(im, ctx, q);
++ gdImageJpegCtx(im, ctx, q);
+ break;
+ case PHP_GDIMG_TYPE_WEBP:
+ if (q == -1) {
+ q = 80;
+ }
+- (*func_p)(im, ctx, q);
++ gdImageWebpCtx(im, ctx, q);
+ break;
+ case PHP_GDIMG_TYPE_PNG:
+- (*func_p)(im, ctx, q, f);
++#ifdef HAVE_GD_BUNDLED
++ gdImagePngCtxEx(im, ctx, q, f);
++#else
++ gdImagePngCtxEx(im, ctx, q);
++#endif
+ break;
+ case PHP_GDIMG_TYPE_XBM:
+ case PHP_GDIMG_TYPE_WBM:
+@@ -197,16 +201,16 @@ static void _php_image_output_ctx(INTERN
+ q = i;
+ }
+ if (image_type == PHP_GDIMG_TYPE_XBM) {
+- (*func_p)(im, file ? file : "", q, ctx);
++ gdImageXbmCtx(im, file ? file : "", q, ctx);
+ } else {
+- (*func_p)(im, q, ctx);
++ gdImageWBMPCtx(im, q, ctx);
+ }
+ break;
+ case PHP_GDIMG_TYPE_BMP:
+- (*func_p)(im, ctx, (int) compressed);
++ gdImageBmpCtx(im, ctx, (int) compressed);
+ break;
+- default:
+- (*func_p)(im, ctx);
++ case PHP_GDIMG_TYPE_GIF:
++ gdImageGifCtx(im, ctx);
+ break;
+ }
+
+From b7356692f69f4ac0a07ea54e83debdd04b426dcb Mon Sep 17 00:00:00 2001
+From: George Peter Banyard <girgias@php.net>
+Date: Wed, 12 May 2021 14:41:11 +0100
+Subject: [PATCH] Specify function pointer signature for scanf implementation
+
+Fix [-Wstrict-prototypes] warnings in standard/scanf.c
+---
+ ext/standard/scanf.c | 14 ++++++++------
+ 1 file changed, 8 insertions(+), 6 deletions(-)
+
+diff --git a/ext/standard/scanf.c b/ext/standard/scanf.c
+index f58b4195cc599..78ecc1642cf92 100644
+--- a/ext/standard/scanf.c
++++ b/ext/standard/scanf.c
+@@ -108,6 +108,8 @@ typedef struct CharSet {
+ } *ranges;
+ } CharSet;
+
++typedef zend_long (*int_string_formater)(const char*, char**, int);
++
+ /*
+ * Declarations for functions used only in this file.
+ */
+@@ -585,7 +587,7 @@ PHPAPI int php_sscanf_internal( char *string, char *format,
+ int base = 0;
+ int underflow = 0;
+ size_t width;
+- zend_long (*fn)() = NULL;
++ int_string_formater fn = NULL;
+ char *ch, sch;
+ int flags;
+ char buf[64]; /* Temporary buffer to hold scanned number
+@@ -750,29 +752,29 @@ PHPAPI int php_sscanf_internal( char *string, char *format,
+ case 'D':
+ op = 'i';
+ base = 10;
+- fn = (zend_long (*)())ZEND_STRTOL_PTR;
++ fn = (int_string_formater)ZEND_STRTOL_PTR;
+ break;
+ case 'i':
+ op = 'i';
+ base = 0;
+- fn = (zend_long (*)())ZEND_STRTOL_PTR;
++ fn = (int_string_formater)ZEND_STRTOL_PTR;
+ break;
+ case 'o':
+ op = 'i';
+ base = 8;
+- fn = (zend_long (*)())ZEND_STRTOL_PTR;
++ fn = (int_string_formater)ZEND_STRTOL_PTR;
+ break;
+ case 'x':
+ case 'X':
+ op = 'i';
+ base = 16;
+- fn = (zend_long (*)())ZEND_STRTOL_PTR;
++ fn = (int_string_formater)ZEND_STRTOL_PTR;
+ break;
+ case 'u':
+ op = 'i';
+ base = 10;
+ flags |= SCAN_UNSIGNED;
+- fn = (zend_long (*)())ZEND_STRTOUL_PTR;
++ fn = (int_string_formater)ZEND_STRTOUL_PTR;
+ break;
+
+ case 'f':
+From 2068d230d981d7b06b41b87ebc37ab2581b79852 Mon Sep 17 00:00:00 2001
+From: George Peter Banyard <girgias@php.net>
+Date: Wed, 12 May 2021 18:54:57 +0100
+Subject: [PATCH] Fix [-Wstrict-prototypes] warning in PCNTL extension
+
+To achieve this we need to introduce a new wrapper function with
+dummy arguments which calls pcntl_signal_dispatch() to respect
+the function pointer signature for a tick function.
+---
+ ext/pcntl/pcntl.c | 10 ++++++++--
+ 1 file changed, 8 insertions(+), 2 deletions(-)
+
+diff --git a/ext/pcntl/pcntl.c b/ext/pcntl/pcntl.c
+index 1e8690ae75144..c116eff7d034a 100644
+--- a/ext/pcntl/pcntl.c
++++ b/ext/pcntl/pcntl.c
+@@ -252,7 +252,8 @@ static void pcntl_siginfo_to_zval(int, s
+ #else
+ static void pcntl_signal_handler(int);
+ #endif
+-static void pcntl_signal_dispatch();
++static void pcntl_signal_dispatch(void);
++static void pcntl_signal_dispatch_tick_function(int dummy_int, void *dummy_pointer);
+ static void pcntl_interrupt_function(zend_execute_data *execute_data);
+
+ void php_register_signal_constants(INIT_FUNC_ARGS)
+@@ -587,7 +588,7 @@ static PHP_GINIT_FUNCTION(pcntl)
+
+ PHP_RINIT_FUNCTION(pcntl)
+ {
+- php_add_tick_function(pcntl_signal_dispatch, NULL);
++ php_add_tick_function(pcntl_signal_dispatch_tick_function, NULL);
+ zend_hash_init(&PCNTL_G(php_signal_table), 16, NULL, ZVAL_PTR_DTOR, 0);
+ PCNTL_G(head) = PCNTL_G(tail) = PCNTL_G(spares) = NULL;
+ PCNTL_G(async_signals) = 0;
+@@ -1549,6 +1550,11 @@ void pcntl_signal_dispatch()
+ sigprocmask(SIG_SETMASK, &old_mask, NULL);
+ }
+
++static void pcntl_signal_dispatch_tick_function(int dummy_int, void *dummy_pointer)
++{
++ return pcntl_signal_dispatch();
++}
++
+ /* {{{ proto bool pcntl_async_signals([bool on[)
+ Enable/disable asynchronous signal handling and return the old setting. */
+ PHP_FUNCTION(pcntl_async_signals)
diff --git a/php-cve-2024-11233.patch b/php-cve-2024-11233.patch
new file mode 100644
index 0000000..d6c29ae
--- /dev/null
+++ b/php-cve-2024-11233.patch
@@ -0,0 +1,68 @@
+From 44a5975f83a02eb8169d12af912e6222b28216d0 Mon Sep 17 00:00:00 2001
+From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
+Date: Fri, 8 Nov 2024 22:04:21 +0100
+Subject: [PATCH 5/7] Fix GHSA-r977-prxv-hc43
+
+Move the bound check upwards. Since this doesn't generate output we can
+check the bound first.
+
+(cherry picked from commit 81030c9bbb5cd2e740b8398bb7212df9709f0274)
+(cherry picked from commit 2cee10a1206f5bc7724232d3988be2cfcb0bc9df)
+---
+ ext/standard/filters.c | 7 ++++---
+ ext/standard/tests/filters/ghsa-r977-prxv-hc43.phpt | 12 ++++++++++++
+ 2 files changed, 16 insertions(+), 3 deletions(-)
+ create mode 100644 ext/standard/tests/filters/ghsa-r977-prxv-hc43.phpt
+
+diff --git a/ext/standard/filters.c b/ext/standard/filters.c
+index 018270c730d..5d5745c6bec 100644
+--- a/ext/standard/filters.c
++++ b/ext/standard/filters.c
+@@ -1128,6 +1128,9 @@ static php_conv_err_t php_conv_qprint_decode_convert(php_conv_qprint_decode *ins
+ } break;
+
+ case 5: {
++ if (icnt == 0) {
++ goto out;
++ }
+ if (!inst->lbchars && lb_cnt == 1 && *ps == '\n') {
+ /* auto-detect soft line breaks, found network line break */
+ lb_cnt = lb_ptr = 0;
+@@ -1141,15 +1144,13 @@ static php_conv_err_t php_conv_qprint_decode_convert(php_conv_qprint_decode *ins
+ /* soft line break */
+ lb_cnt = lb_ptr = 0;
+ scan_stat = 0;
+- } else if (icnt > 0) {
++ } else {
+ if (*ps == (unsigned char)inst->lbchars[lb_cnt]) {
+ lb_cnt++;
+ ps++, icnt--;
+ } else {
+ scan_stat = 6; /* no break for short-cut */
+ }
+- } else {
+- goto out;
+ }
+ } break;
+
+diff --git a/ext/standard/tests/filters/ghsa-r977-prxv-hc43.phpt b/ext/standard/tests/filters/ghsa-r977-prxv-hc43.phpt
+new file mode 100644
+index 00000000000..8fdcce8ff22
+--- /dev/null
++++ b/ext/standard/tests/filters/ghsa-r977-prxv-hc43.phpt
+@@ -0,0 +1,12 @@
++--TEST--
++GHSA-r977-prxv-hc43: Single byte overread with convert.quoted-printable-decode filter
++--FILE--
++<?php
++
++$input_data = str_repeat('A', 8189)."X=\r";
++$filter_url = "php://filter/convert.quoted-printable-decode/resource=data:," . urlencode($input_data);
++var_dump(file_get_contents($filter_url));
++
++?>
++--EXPECT--
++string(8190) "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAX"
+--
+2.47.0
+
diff --git a/php-cve-2024-11234.patch b/php-cve-2024-11234.patch
new file mode 100644
index 0000000..0fd31f3
--- /dev/null
+++ b/php-cve-2024-11234.patch
@@ -0,0 +1,95 @@
+From 494de65139592da0e5e5b6fdf198c2f9c762f4d6 Mon Sep 17 00:00:00 2001
+From: Jakub Zelenka <bukka@php.net>
+Date: Fri, 8 Nov 2024 23:43:47 +0100
+Subject: [PATCH 3/7] Fix GHSA-c5f2-jwm7-mmq2: stream HTTP fulluri CRLF
+ injection
+
+(cherry picked from commit 426a6d4539ebee34879ac5de857036bb6ff0e732)
+(cherry picked from commit bc1f192102dd8cbda028e40aa31604c4885d387c)
+(cherry picked from commit 8d130e16fbfda7d154fedfa0f1ff1d5ad5e26815)
+---
+ ext/standard/http_fopen_wrapper.c | 18 ++++++++----
+ .../tests/http/ghsa-c5f2-jwm7-mmq2.phpt | 28 +++++++++++++++++++
+ 2 files changed, 40 insertions(+), 6 deletions(-)
+ create mode 100644 ext/standard/tests/http/ghsa-c5f2-jwm7-mmq2.phpt
+
+diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
+index 4d918b21e65..aeeb438f0f9 100644
+--- a/ext/standard/http_fopen_wrapper.c
++++ b/ext/standard/http_fopen_wrapper.c
+@@ -186,6 +186,11 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
+ return NULL;
+ }
+
++ /* Should we send the entire path in the request line, default to no. */
++ if (context && (tmpzval = php_stream_context_get_option(context, "http", "request_fulluri")) != NULL) {
++ request_fulluri = zend_is_true(tmpzval);
++ }
++
+ use_ssl = resource->scheme && (ZSTR_LEN(resource->scheme) > 4) && ZSTR_VAL(resource->scheme)[4] == 's';
+ /* choose default ports */
+ if (use_ssl && resource->port == 0)
+@@ -205,6 +210,13 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
+ }
+ }
+
++ if (request_fulluri && (strchr(path, '\n') != NULL || strchr(path, '\r') != NULL)) {
++ php_stream_wrapper_log_error(wrapper, options, "HTTP wrapper full URI path does not allow CR or LF characters");
++ php_url_free(resource);
++ efree(transport_string);
++ return NULL;
++ }
++
+ if (context && (tmpzval = php_stream_context_get_option(context, wrapper->wops->label, "timeout")) != NULL) {
+ double d = zval_get_double(tmpzval);
+ #ifndef PHP_WIN32
+@@ -385,12 +397,6 @@ finish:
+ smart_str_appends(&req_buf, "GET ");
+ }
+
+- /* Should we send the entire path in the request line, default to no. */
+- if (!request_fulluri && context &&
+- (tmpzval = php_stream_context_get_option(context, "http", "request_fulluri")) != NULL) {
+- request_fulluri = zend_is_true(tmpzval);
+- }
+-
+ if (request_fulluri) {
+ /* Ask for everything */
+ smart_str_appends(&req_buf, path);
+diff --git a/ext/standard/tests/http/ghsa-c5f2-jwm7-mmq2.phpt b/ext/standard/tests/http/ghsa-c5f2-jwm7-mmq2.phpt
+new file mode 100644
+index 00000000000..5b2e04f94f2
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-c5f2-jwm7-mmq2.phpt
+@@ -0,0 +1,28 @@
++--TEST--
++GHSA-c5f2-jwm7-mmq2 (Configuring a proxy in a stream context might allow for CRLF injection in URIs)
++--INI--
++allow_url_fopen=1
++--CONFLICTS--
++server
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++echo $_SERVER['REQUEST_URI'];
++CODE;
++
++include __DIR__."/../../../../sapi/cli/tests/php_cli_server.inc";
++php_cli_server_start($serverCode, null, []);
++
++$host = PHP_CLI_SERVER_ADDRESS;
++$userinput = "index.php HTTP/1.1\r\nHost: $host\r\n\r\nGET /index2.php HTTP/1.1\r\nHost: $host\r\n\r\nGET /index.php";
++$context = stream_context_create(['http' => ['proxy' => 'tcp://' . $host, 'request_fulluri' => true]]);
++echo file_get_contents("http://$host/$userinput", false, $context);
++?>
++--EXPECTF--
++Warning: file_get_contents(http://localhost:%d/index.php HTTP/1.1
++Host: localhost:%d
++
++GET /index2.php HTTP/1.1
++Host: localhost:%d
++
++GET /index.php): failed to open stream: HTTP wrapper full URI path does not allow CR or LF characters in %s on line %d
+--
+2.47.0
+
diff --git a/php-cve-2024-11236.patch b/php-cve-2024-11236.patch
new file mode 100644
index 0000000..e917cfc
--- /dev/null
+++ b/php-cve-2024-11236.patch
@@ -0,0 +1,119 @@
+From 97546df8d6900b115536c17af9213f1da837b82e Mon Sep 17 00:00:00 2001
+From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
+Date: Thu, 24 Oct 2024 22:02:17 +0200
+Subject: [PATCH 1/7] Fix GHSA-5hqh-c84r-qjcv: Integer overflow in the dblib
+ quoter causing OOB writes
+
+(cherry picked from commit d9baa9fed8c3ba692a36b388c0c7762e5102e2e0)
+(cherry picked from commit 5d9e54065ed18c51e4f25d8900635f90810c7394)
+---
+ ext/pdo_dblib/dblib_driver.c | 8 ++++++-
+ ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt | 24 ++++++++++++++++++++
+ 2 files changed, 31 insertions(+), 1 deletion(-)
+ create mode 100644 ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt
+
+diff --git a/ext/pdo_dblib/dblib_driver.c b/ext/pdo_dblib/dblib_driver.c
+index f36451afeeb..1dc75a4d2e3 100644
+--- a/ext/pdo_dblib/dblib_driver.c
++++ b/ext/pdo_dblib/dblib_driver.c
+@@ -154,6 +154,7 @@ static int dblib_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unqu
+
+ size_t i;
+ char * q;
++ size_t extralen = 0;
+ *quotedlen = 0;
+
+ if (H->assume_national_character_set_strings) {
+@@ -168,7 +169,7 @@ static int dblib_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unqu
+
+ /* Detect quoted length, adding extra char for doubled single quotes */
+ for (i = 0; i < unquotedlen; i++) {
+- if (unquoted[i] == '\'') ++*quotedlen;
++ if (unquoted[i] == '\'') ++extralen;
+ ++*quotedlen;
+ }
+
+@@ -176,6 +177,11 @@ static int dblib_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unqu
+ if (use_national_character_set) {
+ ++*quotedlen; /* N prefix */
+ }
++ if (UNEXPECTED(*quotedlen > ZSTR_MAX_LEN - extralen)) {
++ return 0;
++ }
++
++ *quotedlen += extralen;
+ q = *quoted = emalloc(*quotedlen + 1); /* Add byte for terminal null */
+ if (use_national_character_set) {
+ *q++ = 'N';
+diff --git a/ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt b/ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt
+new file mode 100644
+index 00000000000..431c61951ee
+--- /dev/null
++++ b/ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt
+@@ -0,0 +1,24 @@
++--TEST--
++GHSA-5hqh-c84r-qjcv (Integer overflow in the dblib quoter causing OOB writes)
++--EXTENSIONS--
++pdo_dblib
++--SKIPIF--
++<?php
++if (PHP_INT_SIZE != 4) die("skip for 32bit platforms only");
++if (PHP_OS_FAMILY === "Windows") die("skip not for Windows because the virtual address space for application is only 2GiB");
++if (getenv("SKIP_SLOW_TESTS")) die("skip slow test");
++require __DIR__ . '/config.inc';
++getDbConnection();
++?>
++--INI--
++memory_limit=-1
++--FILE--
++<?php
++
++require __DIR__ . '/config.inc';
++$db = getDbConnection();
++var_dump($db->quote(str_repeat("'", 2147483646)));
++
++?>
++--EXPECT--
++bool(false)
+--
+2.47.0
+
+From 0530cbfe5c3044537de52d8382eba5d69dbac726 Mon Sep 17 00:00:00 2001
+From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
+Date: Thu, 24 Oct 2024 22:02:36 +0200
+Subject: [PATCH 2/7] Fix GHSA-5hqh-c84r-qjcv: Integer overflow in the firebird
+ quoter causing OOB writes
+
+(cherry picked from commit 69c5f68fdc3deed9ebce2cc44b4bf5e0c47cd28f)
+(cherry picked from commit b4f73be75dbdde970a18cc7a636898b10400fb3f)
+---
+ ext/pdo_firebird/firebird_driver.c | 6 +++++-
+ 1 file changed, 5 insertions(+), 1 deletion(-)
+
+diff --git a/ext/pdo_firebird/firebird_driver.c b/ext/pdo_firebird/firebird_driver.c
+index 3e403afd368..5b74290abcc 100644
+--- a/ext/pdo_firebird/firebird_driver.c
++++ b/ext/pdo_firebird/firebird_driver.c
+@@ -243,7 +243,7 @@ free_statement:
+ static int firebird_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unquotedlen, /* {{{ */
+ char **quoted, size_t *quotedlen, enum pdo_param_type paramtype)
+ {
+- int qcount = 0;
++ size_t qcount = 0;
+ char const *co, *l, *r;
+ char *c;
+
+@@ -258,6 +258,10 @@ static int firebird_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t u
+ /* count the number of ' characters */
+ for (co = unquoted; (co = strchr(co,'\'')); qcount++, co++);
+
++ if (UNEXPECTED(unquotedlen + 2 > ZSTR_MAX_LEN - qcount)) {
++ return 0;
++ }
++
+ *quotedlen = unquotedlen + qcount + 2;
+ *quoted = c = emalloc(*quotedlen+1);
+ *c++ = '\'';
+--
+2.47.0
+
diff --git a/php-cve-2024-8929.patch b/php-cve-2024-8929.patch
new file mode 100644
index 0000000..fdc844d
--- /dev/null
+++ b/php-cve-2024-8929.patch
@@ -0,0 +1,2714 @@
+From e8bc357123ea19c4e2390374f088c9d4941f19e6 Mon Sep 17 00:00:00 2001
+From: Jakub Zelenka <bukka@php.net>
+Date: Tue, 8 Oct 2024 16:17:53 +0100
+Subject: [PATCH 1/6] Fix GHSA-h35g-vwh6-m678: Mysqlnd - various heap buffer
+ over-reads
+
+This fixes issues causing buffer over-read that leak heap content:
+- RESP packet field default left over for COM_LIST
+- RESP packet upsert filename
+- OK packet message
+- RESP packet for stmt row data
+ - ps_fetch_from_1_to_8_bytes
+ - ps_fetch_float
+ - ps_fetch_double
+ - ps_fetch_time
+ - ps_fetch_date
+ - ps_fetch_datetime
+ - ps_fetch_string
+ - ps_fetch_bit
+- RESP packet for query row data (just possible overflow on 32bit)
+
+It also adds various protocol tests using a new fake server.
+
+(cherry picked from commit 2f5aa9f9d150ca56e356f3ca9acf9d530108cb08)
+(cherry picked from commit 0d3ccf4cc54d3844bc9d1c8f6bdcd36180752a2c)
+
+adapt for 7.x
+---
+ ext/mysqli/tests/fake_server.inc | 856 ++++++++++++++++++
+ .../ghsa-h35g-vwh6-m678-auth-message.phpt | 38 +
+ ext/mysqli/tests/ghsa-h35g-vwh6-m678-def.phpt | 47 +
+ .../tests/ghsa-h35g-vwh6-m678-filename.phpt | 43 +
+ ...hsa-h35g-vwh6-m678-query-len-overflow.phpt | 48 +
+ .../ghsa-h35g-vwh6-m678-stmt-row-bit.phpt | 53 ++
+ .../ghsa-h35g-vwh6-m678-stmt-row-date.phpt | 53 ++
+ ...ghsa-h35g-vwh6-m678-stmt-row-datetime.phpt | 53 ++
+ .../ghsa-h35g-vwh6-m678-stmt-row-double.phpt | 53 ++
+ .../ghsa-h35g-vwh6-m678-stmt-row-float.phpt | 53 ++
+ .../ghsa-h35g-vwh6-m678-stmt-row-int.phpt | 53 ++
+ ...ghsa-h35g-vwh6-m678-stmt-row-no-space.phpt | 53 ++
+ .../ghsa-h35g-vwh6-m678-stmt-row-string.phpt | 53 ++
+ .../ghsa-h35g-vwh6-m678-stmt-row-time.phpt | 53 ++
+ .../tests/protocol_query_row_fetch_data.phpt | 74 ++
+ .../tests/protocol_stmt_row_fetch_data.phpt | 91 ++
+ ext/mysqlnd/mysqlnd_ps_codec.c | 69 ++
+ ext/mysqlnd/mysqlnd_result.c | 2 +-
+ ext/mysqlnd/mysqlnd_wireprotocol.c | 71 +-
+ 19 files changed, 1794 insertions(+), 22 deletions(-)
+ create mode 100644 ext/mysqli/tests/fake_server.inc
+ create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt
+ create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-def.phpt
+ create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-filename.phpt
+ create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-query-len-overflow.phpt
+ create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-bit.phpt
+ create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-date.phpt
+ create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-datetime.phpt
+ create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-double.phpt
+ create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-float.phpt
+ create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-int.phpt
+ create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-no-space.phpt
+ create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-string.phpt
+ create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-time.phpt
+ create mode 100644 ext/mysqli/tests/protocol_query_row_fetch_data.phpt
+ create mode 100644 ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt
+
+diff --git a/ext/mysqli/tests/fake_server.inc b/ext/mysqli/tests/fake_server.inc
+new file mode 100644
+index 00000000000..b02fabc584c
+--- /dev/null
++++ b/ext/mysqli/tests/fake_server.inc
+@@ -0,0 +1,856 @@
++<?php
++
++function my_mysqli_data_fields(): array
++{
++ return [
++ 'intval' => [
++ 'type' => '03',
++ 'charset' => '3f00',
++ 'length' => '0b000000',
++ 'flags' => '0110',
++ 'decimal' => '00',
++ 'query_data_packet_length' => '080000',
++ 'query_data_value' => '023134',
++ 'stmt_data_packet_length' => '0b0000',
++ 'stmt_data_value' => '0e000000'
++ ],
++ 'fltval' => [
++ 'type' => '04',
++ 'charset' => '3f00',
++ 'length' => '0c000000',
++ 'flags' => '0110',
++ 'decimal' => '1f',
++ 'query_data_packet_length' => '090000',
++ 'query_data_value' => '03322e33',
++ 'stmt_data_packet_length' => '0b0000',
++ 'stmt_data_value' => '33331340',
++ ],
++ 'dblval' => [
++ 'type' => '05',
++ 'charset' => '3f00',
++ 'length' => '16000000',
++ 'flags' => '0110',
++ 'decimal' => '1f',
++ 'query_data_packet_length' => '090000',
++ 'query_data_value' => '03312e32',
++ 'stmt_data_packet_length' => '0f0000',
++ 'stmt_data_value' => '333333333333f33f'
++ ],
++ 'datval' => [
++ 'type' => '0a',
++ 'charset' => '3f00',
++ 'length' => '0a000000',
++ 'flags' => '8110',
++ 'decimal' => '00',
++ 'query_data_packet_length' => '100000',
++ 'query_data_value' => '0a323031342d31322d3135',
++ 'stmt_data_packet_length' => '0c0000',
++ 'stmt_data_value' => '04de070c0f'
++ ],
++ 'timval' => [
++ 'type' => '0b',
++ 'charset' => '3f00',
++ 'length' => '0a000000',
++ 'flags' => '8110',
++ 'decimal' => '00',
++ 'query_data_packet_length' => '0e0000',
++ 'query_data_value' => '0831333a30303a3032',
++ 'stmt_data_packet_length' => '100000',
++ 'stmt_data_value' => '080000000000150801'
++ ],
++ 'dtival' => [
++ 'type' => '0c',
++ 'charset' => '3f00',
++ 'length' => '13000000',
++ 'flags' => '8110',
++ 'decimal' => '00',
++ 'query_data_packet_length' => '190000',
++ 'query_data_value' => '13323031342d31322d31362031333a30303a3031',
++ 'stmt_data_packet_length' => '0f0000',
++ 'stmt_data_value' => '07de070c100d0001'
++ ],
++ 'bitval' => [
++ 'type' => '10',
++ 'charset' => '3f00',
++ 'length' => '40000000',
++ 'flags' => '2110',
++ 'decimal' => '00',
++ 'query_data_packet_length' => '0e0000',
++ 'query_data_value' => '080808080808080808',
++ 'stmt_data_packet_length' => '100000',
++ 'stmt_data_value' => '080808080808080808'
++ ],
++ 'strval' => [
++ 'type' => 'fd',
++ 'charset' => 'e000',
++ 'length' => 'c8000000',
++ 'flags' => '0110',
++ 'decimal' => '00',
++ 'query_data_packet_length' => '0a0000',
++ 'query_data_value' => '0474657374',
++ 'stmt_data_packet_length' => '0c0000',
++ 'stmt_data_value' => '0474657374'
++ ],
++ ];
++}
++
++function my_mysqli_data_field(string $field): array
++{
++ $fields = my_mysqli_data_fields();
++ if (!isset($fields[$field])) {
++ throw new Exception("Unknown field $field");
++ }
++ return $fields[$field];
++}
++
++
++
++class my_mysqli_fake_packet_item
++{
++ public function __construct(public string|null $name, public string $value, public bool $is_hex = true)
++ {
++ }
++}
++
++class my_mysqli_fake_packet
++{
++ private array $data = array();
++
++ public function __get(string $name)
++ {
++ foreach ($this->data as $item) {
++ if ($item->name === $name) {
++ return $item->value;
++ }
++ }
++ return null;
++ }
++
++ public function __set(string $name, string|my_mysqli_fake_packet_item $value)
++ {
++ if ($value instanceof my_mysqli_fake_packet_item) {
++ if ($value->name === null) {
++ $value->name = $name;
++ }
++ } else {
++ $value = new my_mysqli_fake_packet_item($name, $value, true);
++ }
++
++ for ($i = 0; $i < count($this->data); $i++) {
++ if ($this->data[$i]->name === $name) {
++ $this->data[$i] = $value;
++ return;
++ }
++ }
++
++ $this->data[] = $value;
++ }
++
++ public function to_bytes(): string
++ {
++ $bytes = '';
++ foreach ($this->data as $item) {
++ $bytes .= $item->is_hex ? hex2bin($item->value) : $item->value;
++ }
++ return $bytes;
++ }
++}
++
++class my_mysqli_fake_packet_generator
++{
++ public static function create_packet_item(int|string $value, bool $is_hex = false, string $format = 'v'): my_mysqli_fake_packet_item
++ {
++ if (is_string($value)) {
++ $packed_value = $value;
++ } else {
++ $packed_value = pack($format, $value);
++ }
++ return new my_mysqli_fake_packet_item(null, $packed_value, $is_hex);
++ }
++
++ public function server_ok(): my_mysqli_fake_packet
++ {
++ $packet = new my_mysqli_fake_packet();
++ $packet->packet_length = "070000";
++ $packet->packet_number = "02";
++ $packet->header = "00"; // OK
++ $packet->affected_rows = "00";
++ $packet->last_insert_id = "00";
++ $packet->server_status = "0200";
++ $packet->warning_count = "0000";
++ return $packet;
++ }
++
++ public function server_greetings(): my_mysqli_fake_packet
++ {
++ $packet = new my_mysqli_fake_packet();
++ $packet->packet_length = "580000";
++ $packet->packet_number = "00";
++ $packet->proto_version = "0a";
++ $packet->version = self::create_packet_item('5.5.5-10.5.18-MariaDB' . chr(0));
++ $packet->thread_id = "03000000";
++ $packet->salt = "473e3f6047257c67";
++ $packet->filler = "00";
++ $packet->server_capabilities = self::create_packet_item(0b1111011111111110);
++ $packet->server_character_set = "08";
++ $packet->server_status = self::create_packet_item(0b000000000000010);
++ $packet->extended_server_capabilities = self::create_packet_item(0b1000000111111111);
++ $packet->auth_plugin = "15";
++ $packet->unused = "000000000000";
++ $packet->mariadb_extended_server_capabilities = self::create_packet_item(0b1111, false, 'V');
++ $packet->mariadb_extended_server_capabilities_salt = "6c6b55463f49335f686c643100";
++ $packet->mariadb_extended_server_capabilities_auth_plugin = self::create_packet_item('mysql_native_password');
++
++ return $packet;
++ }
++
++ public function server_tabular_query_response(): array
++ {
++ $qr1 = new my_mysqli_fake_packet();
++ $qr1->packet_length = "010000";
++ $qr1->packet_number = "01";
++ $qr1->field_count = "01";
++
++ $qr2 = new my_mysqli_fake_packet();
++ $qr2->packet_length = "190000";
++ $qr2->packet_number = "02";
++ $qr2->catalog_length_plus_name = "0164";
++ $qr2->db_length_plus_name = "0164";
++ $qr2->table_length_plus_name = "0164";
++ $qr2->original_t = "0164";
++ $qr2->name_length_plus_name = "0164";
++ $qr2->original_n = "0164";
++ $qr2->canary = "0c";
++ $qr2->charset = "3f00";
++ $qr2->length = "0b000000";
++ $qr2->type = "03";
++ $qr2->flags = "0350";
++ $qr2->decimals = "000000";
++
++ $qr3 = new my_mysqli_fake_packet();
++ $qr3->full = "05000003fe00002200";
++
++ $qr4 = new my_mysqli_fake_packet();
++ $qr4->full = "0400000401350174";
++
++ $qr5 = new my_mysqli_fake_packet();
++ $qr5->full = "05000005fe00002200";
++
++ return [$qr1, $qr2, $qr3, $qr4, $qr5];
++ }
++
++ public function server_upsert_query_response(): array
++ {
++ $qr1 = new my_mysqli_fake_packet();
++ $qr1->packet_length = "010000";
++ $qr1->packet_number = "01";
++ $qr1->field_count = "00"; // UPSERT
++ $qr1->affected_rows = "00";
++ $qr1->affected_rows = "00";
++ $qr1->last_insert_id = "00";
++ $qr1->server_status = "0000";
++ $qr1->warning_count = "0000";
++ $qr1->len = "01";
++ $qr1->filename = "65";
++ $qr1->packet_length = sprintf("%02x0000", strlen($qr1->to_bytes())-4);
++
++ return [$qr1];
++ }
++
++ public function server_stmt_prepare_response_start($num_field): my_mysqli_fake_packet
++ {
++ $pr1 = new my_mysqli_fake_packet();
++ $pr1->packet_length = "0c0000";
++ $pr1->packet_number = "01";
++ $pr1->response_code = '00'; // OK
++ $pr1->statement_id = '01000000';
++ $pr1->num_fields = $num_field;
++ $pr1->num_params = '0000';
++ $pr1->filler = '00';
++ $pr1->warnings = '0000';
++
++ return $pr1;
++ }
++
++ public function server_stmt_prepare_response_end($packer_number): my_mysqli_fake_packet
++ {
++ $pr3 = new my_mysqli_fake_packet();
++ $pr3->packet_length = "050000";
++ $pr3->packet_number = $packer_number;
++ $pr3->packet_type = 'fe'; // EOF
++ $pr3->warnings = '0000';
++ $pr3->server_status = '0200';
++
++ return $pr3;
++ }
++
++ public function server_stmt_prepare_items_response(): array
++ {
++ $pr1 = $this->server_stmt_prepare_response_start('0100');
++
++ $pr2 = new my_mysqli_fake_packet();
++ $pr2->packet_length = "300000";
++ $pr2->packet_number = "02";
++ $pr2->catalogue_len = '03';
++ $pr2->catalogue = '646566'; // def
++ $pr2->db_len = '08';
++ $pr2->db = '7068705f74657374'; // php_test
++ $pr2->table_len = '05';
++ $pr2->table = '6974656d73'; // items
++ $pr2->orig_table_len = '05';
++ $pr2->orig_table = '6974656d73'; // items
++ $pr2->name_len = '04';
++ $pr2->name = '6974656d';
++ $pr2->orig_name_len = '04';
++ $pr2->orig_name = '6974656d';
++ $pr2->something = '0c';
++ $pr2->charset = 'e000';
++ $pr2->length = 'c8000000';
++ $pr2->field_type = 'fd'; // FIELD_TYPE_VAR_STRING
++ $pr2->flags = '0110';
++ $pr2->decimal = '00';
++ $pr2->padding = '0000';
++
++ $pr3 = $this->server_stmt_prepare_response_end('03');
++
++ return [$pr1, $pr2, $pr3];
++ }
++
++ public function server_stmt_prepare_data_response_field($packet_number, $field_name): my_mysqli_fake_packet
++ {
++ if (strlen($field_name) != 6) {
++ throw new Exception("Invalid field length - only 6 is allowed");
++ }
++
++ $field = my_mysqli_data_field($field_name);
++
++ $pr = new my_mysqli_fake_packet();
++ $pr->packet_length = "320000";
++ $pr->packet_number = $packet_number;
++ $pr->catalogue_len = '03';
++ $pr->catalogue = bin2hex('def');
++ $pr->db_len = '08';
++ $pr->db = bin2hex('php_test');
++ $pr->table_len = '04';
++ $pr->table = bin2hex('data');
++ $pr->orig_table_len = '04';
++ $pr->orig_table = bin2hex('data');
++ $pr->name_len = '06';
++ $pr->name = bin2hex($field_name);
++ $pr->orig_name_len = '06';
++ $pr->orig_name = bin2hex($field_name);
++ $pr->something = '0c';
++ $pr->charset = $field['charset'];
++ $pr->length = $field['length'];
++ $pr->field_type = $field['type'];
++ $pr->flags = $field['flags'];
++ $pr->decimal = $field['decimal'];
++ $pr->padding = '0000';
++
++ return $pr;
++ }
++
++ public function server_stmt_prepare_data_response(string $field_name): array
++ {
++ $pr1 = $this->server_stmt_prepare_response_start('0200');
++
++ $pr2 = $this->server_stmt_prepare_data_response_field('02', 'strval');
++ $pr3 = $this->server_stmt_prepare_data_response_field('03', $field_name);
++
++ $pr4 = $this->server_stmt_prepare_response_end('04');
++
++ return [$pr1, $pr2, $pr3, $pr4];
++ }
++
++ public function server_stmt_execute_items_response(): array
++ {
++ $pr1 = new my_mysqli_fake_packet();
++ $pr1->packet_length = "010000";
++ $pr1->packet_number = "01";
++ $pr1->num_fields = '01';
++
++ $pr2 = new my_mysqli_fake_packet();
++ $pr2->packet_length = "300000";
++ $pr2->packet_number = "02";
++ $pr2->catalogue_len = '03';
++ $pr2->catalogue = '646566'; // def
++ $pr2->db_len = '08';
++ $pr2->db = '7068705f74657374'; // php_test
++ $pr2->table_len = '05';
++ $pr2->table = '6974656d73'; // items
++ $pr2->orig_table_len = '05';
++ $pr2->orig_table = '6974656d73'; // items
++ $pr2->name_len = '04';
++ $pr2->name = '6974656d';
++ $pr2->orig_name_len = '04';
++ $pr2->orig_name = '6974656d';
++ $pr2->something = '0c';
++ $pr2->charset = 'e000';
++ $pr2->length = 'c8000000';
++ $pr2->field_type = 'fd'; // FIELD_TYPE_VAR_STRING
++ $pr2->flags = '0110';
++ $pr2->decimal = '00';
++ $pr2->padding = '0000';
++
++ $pr3 = new my_mysqli_fake_packet();
++ $pr3->packet_length = "050000";
++ $pr3->packet_number = "03";
++ $pr3->packet_type = 'fe'; // EOF
++ $pr3->warnings = '0000';
++ $pr3->server_status = '2200';
++
++ $pr4 = new my_mysqli_fake_packet();
++ $pr4->packet_length = "070000";
++ $pr4->packet_number = "04";
++ $pr4->packet_type = '00'; // OK
++ $pr4->affected_rows = '00';
++ $pr4->row_data_len = '04';
++ $pr4->row_data = '74657374'; // item
++
++ $pr5 = new my_mysqli_fake_packet();
++ $pr5->full = '05000005fe00002200';
++
++ return [$pr1, $pr2, $pr3, $pr4, $pr5];
++ }
++
++ private function server_execute_data_response_start(string $field_name): array
++ {
++ $pr1 = new my_mysqli_fake_packet();
++ $pr1->packet_length = "010000";
++ $pr1->packet_number = "01";
++ $pr1->num_fields = '02';
++
++ $pr2 = new my_mysqli_fake_packet();
++ $pr2->packet_length = "320000";
++ $pr2->packet_number = "02";
++ $pr2->catalogue_len = '03';
++ $pr2->catalogue = '646566'; // def
++ $pr2->db_len = '08';
++ $pr2->db = '7068705f74657374'; // php_test
++ $pr2->table_len = '04';
++ $pr2->table = bin2hex('data');
++ $pr2->orig_table_len = '04';
++ $pr2->orig_table = bin2hex('data');
++ $pr2->name_len = '06';
++ $pr2->name = bin2hex('strval');
++ $pr2->orig_name_len = '06';
++ $pr2->orig_name = bin2hex('strval');
++ $pr2->something = '0c';
++ $pr2->charset = 'e000';
++ $pr2->length = 'c8000000';
++ $pr2->field_type = 'fd'; // FIELD_TYPE_VAR_STRING
++ $pr2->flags = '0110';
++ $pr2->decimal = '00';
++ $pr2->padding = '0000';
++
++ $field = my_mysqli_data_field($field_name);
++
++ $pr3 = new my_mysqli_fake_packet();
++ $pr3->packet_length = "320000";
++ $pr3->packet_number = "03";
++ $pr3->catalogue_len = '03';
++ $pr3->catalogue = '646566'; // def
++ $pr3->db_len = '08';
++ $pr3->db = '7068705f74657374'; // php_test
++ $pr3->table_len = '04';
++ $pr3->table = bin2hex('data');
++ $pr3->orig_table_len = '04';
++ $pr3->orig_table = bin2hex('data');
++ $pr3->name_len = '06';
++ $pr3->name = bin2hex($field_name);
++ $pr3->orig_name_len = '06';
++ $pr3->orig_name = bin2hex($field_name);
++ $pr3->something = '0c';
++ $pr3->charset = $field['charset'];
++ $pr3->length = $field['length'];
++ $pr3->field_type = $field['type'];
++ $pr3->flags = $field['flags'];
++ $pr3->decimal = $field['decimal'];
++ $pr3->padding = '0000';
++
++ $pr4 = new my_mysqli_fake_packet();
++ $pr4->packet_length = "050000";
++ $pr4->packet_number = "04";
++ $pr4->packet_type = 'fe'; // EOF
++ $pr4->warnings = '0000';
++ $pr4->server_status = '2200';
++
++ return [$field, $pr1, $pr2, $pr3, $pr4];
++ }
++
++ private function server_execute_data_response_end(): my_mysqli_fake_packet
++ {
++ $pr6 = new my_mysqli_fake_packet();
++ $pr6->packet_length = '050000';
++ $pr6->packet_number = "06";
++ $pr6->packet_type = 'fe'; // EOF
++ $pr6->warnings = '0000';
++ $pr6->server_status = '2200';
++
++ return $pr6;
++ }
++
++ public function server_stmt_execute_data_response(string $field_name): array
++ {
++ [$field, $pr1, $pr2, $pr3, $pr4] = $this->server_execute_data_response_start($field_name);
++
++ $pr5 = new my_mysqli_fake_packet();
++ $pr5->packet_length = $field['stmt_data_packet_length'];
++ $pr5->packet_number = "05";
++ $pr5->packet_type = '00'; // OK
++ $pr5->affected_rows = '00';
++ $pr5->row_field1_len = '04';
++ $pr5->row_field1_data = '74657374'; // test
++ $pr5->row_field2 = $field['stmt_data_value'];
++
++ return [$pr1, $pr2, $pr3, $pr4, $pr5, $this->server_execute_data_response_end()];
++ }
++
++ public function server_query_execute_data_response(string $field_name): array
++ {
++ [$field, $pr1, $pr2, $pr3, $pr4] = $this->server_execute_data_response_start($field_name);
++
++ $pr5 = new my_mysqli_fake_packet();
++ $pr5->packet_length = $field['query_data_packet_length'];
++ $pr5->packet_number = "05";
++ $pr5->row_field1_len = '04';
++ $pr5->row_field1_data = '74657374'; // test
++ $pr5->row_field2 = $field['query_data_value'];
++
++ return [$pr1, $pr2, $pr3, $pr4, $pr5, $this->server_execute_data_response_end()];
++ }
++}
++
++class my_mysqli_fake_server_conn
++{
++ private $conn;
++ public $packet_generator;
++
++ public function __construct($socket)
++ {
++ $this->packet_generator = new my_mysqli_fake_packet_generator();
++ $this->conn = stream_socket_accept($socket);
++ if ($this->conn) {
++ fprintf(STDERR, "[*] Connection established\n");
++ } else {
++ fprintf(STDERR, "[*] Failed to establish connection\n");
++ }
++ }
++
++ public function packets_to_bytes(array $packets): string
++ {
++ return implode('', array_map(fn($s) => $s->to_bytes(), $packets));
++ }
++
++ public function send($payload, $message = null): void
++ {
++ if ($message) {
++ fprintf(STDERR, "[*] Sending - %s: %s\n", $message, bin2hex($payload));
++ }
++ fwrite($this->conn, $payload);
++ }
++
++ public function read($bytes_len = 1024)
++ {
++ // wait 10ms to fill the buffer
++ usleep(10000);
++ $data = fread($this->conn, $bytes_len);
++ if ($data) {
++ fprintf(STDERR, "[*] Received: %s\n", bin2hex($data));
++ }
++ }
++
++ public function close()
++ {
++ fclose($this->conn);
++ }
++
++ public function send_server_greetings()
++ {
++ $this->send($this->packet_generator->server_greetings()->to_bytes(), "Server Greeting");
++ }
++
++ public function send_server_ok()
++ {
++ $this->send($this->packet_generator->server_ok()->to_bytes(), "Server OK");
++ }
++
++ public function send_server_tabular_query_response(): void
++ {
++ $packets = $this->packet_generator->server_tabular_query_response();
++ $this->send($this->packets_to_bytes($packets), "Tabular response");
++ }
++
++ public function send_server_stmt_prepare_items_response(): void
++ {
++ $packets = $this->packet_generator->server_stmt_prepare_items_response();
++ $this->send($this->packets_to_bytes($packets), "Stmt prepare items");
++ }
++
++
++ public function send_server_stmt_prepare_data_response(string $field_name): void
++ {
++ $packets = $this->packet_generator->server_stmt_prepare_data_response($field_name);
++ $this->send($this->packets_to_bytes($packets), "Stmt prepare data $field_name");
++ }
++
++ public function send_server_stmt_execute_items_response(): void
++ {
++ $packets = $this->packet_generator->server_stmt_execute_items_response();
++ $this->send($this->packets_to_bytes($packets), "Stmt execute items");
++ }
++
++ public function send_server_stmt_execute_data_response(string $field_name): void
++ {
++ $packets = $this->packet_generator->server_stmt_execute_data_response($field_name);
++ $this->send($this->packets_to_bytes($packets), "Stmt execute data $field_name");
++ }
++
++ public function send_server_query_execute_data_response(string $field_name): void
++ {
++ $packets = $this->packet_generator->server_query_execute_data_response($field_name);
++ $this->send($this->packets_to_bytes($packets), "Query execute data $field_name");
++ }
++}
++
++class my_mysqli_fake_server_process
++{
++ public function __construct(private $process, private array $pipes) {}
++
++ public function terminate(bool $wait = false)
++ {
++ if ($wait) {
++ $this->wait();
++ }
++ proc_terminate($this->process);
++ }
++
++ public function wait()
++ {
++ echo fgets($this->pipes[1]);
++ }
++}
++
++function my_mysqli_test_tabular_response_def_over_read(my_mysqli_fake_server_conn $conn): void
++{
++ $rh = $conn->packet_generator->server_tabular_query_response();
++
++ // Length of the packet is modified to include the next added data
++ $rh[1]->packet_length = "1e0000";
++
++ // We add a length field encoded on 4 bytes which evaluates to 65536. If the process crashes because
++ // the heap has been overread, lower this value.
++ $rh[1]->extra_def_size = "fd000001"; # 65536
++
++ // Filler
++ $rh[1]->extra_def_data = "aa";
++
++ $trrh = $conn->packets_to_bytes($rh);
++
++ $conn->send_server_greetings();
++ $conn->read();
++ $conn->send_server_ok();
++ $conn->read();
++ $conn->send($trrh, "Malicious Tabular Response [Extract heap through buffer over-read]");
++ $conn->read(65536);
++}
++
++function my_mysqli_test_upsert_response_filename_over_read(my_mysqli_fake_server_conn $conn): void
++{
++ $rh = $conn->packet_generator->server_upsert_query_response();
++
++ // Set extra length to overread
++ $rh[0]->len = "fa";
++
++ $trrh = $conn->packets_to_bytes($rh);
++
++ $conn->send_server_greetings();
++ $conn->read();
++ $conn->send_server_ok();
++ $conn->read();
++ $conn->send($trrh, "Malicious Tabular Response [Extract heap through buffer over-read]");
++ $conn->read(65536);
++}
++
++function my_mysqli_test_auth_response_message_over_read(my_mysqli_fake_server_conn $conn): void
++{
++ $p = $conn->packet_generator->server_ok();
++ $p->packet_length = "090000";
++ $p->message_len = "fcff";
++
++ $conn->send_server_greetings();
++ $conn->read();
++ $conn->send($p->to_bytes(), "Malicious OK Auth Response [Extract heap through buffer over-read]");
++ $conn->read();
++}
++
++function my_mysqli_test_stmt_response_row_over_read_string(my_mysqli_fake_server_conn $conn): void
++{
++ $rh = $conn->packet_generator->server_stmt_execute_items_response();
++
++ // Set extra length to overread
++ $rh[3]->row_data_len = "fa";
++
++ $conn->send_server_greetings();
++ $conn->read();
++ $conn->send_server_ok();
++ $conn->read();
++ $conn->send_server_stmt_prepare_items_response();
++ $conn->read();
++ $conn->send($conn->packets_to_bytes($rh), "Malicious Stmt Response for items [Extract heap through buffer over-read]");
++ $conn->read(65536);
++}
++
++function my_mysqli_test_stmt_response_row_over_read_two_fields(
++ my_mysqli_fake_server_conn $conn,
++ string $field_name,
++ string $row_field1_len = '06'
++): void {
++ $rh = $conn->packet_generator->server_stmt_execute_data_response($field_name);
++
++ // Set extra length to overread by two bytes
++ $rh[4]->row_field1_len = $row_field1_len;
++
++ $conn->send_server_greetings();
++ $conn->read();
++ $conn->send_server_ok();
++ $conn->read();
++ $conn->send_server_stmt_prepare_data_response($field_name);
++ $conn->read();
++ $conn->send(
++ $conn->packets_to_bytes($rh),
++ "Malicious Stmt Response for data $field_name [Extract heap through buffer over-read]"
++ );
++ $conn->read(65536);
++}
++
++function my_mysqli_test_stmt_response_row_over_read_int(my_mysqli_fake_server_conn $conn): void
++{
++ my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'intval');
++}
++
++function my_mysqli_test_stmt_response_row_over_read_float(my_mysqli_fake_server_conn $conn): void
++{
++ my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'fltval');
++}
++
++function my_mysqli_test_stmt_response_row_over_read_double(my_mysqli_fake_server_conn $conn): void
++{
++ my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'dblval');
++}
++
++function my_mysqli_test_stmt_response_row_over_read_date(my_mysqli_fake_server_conn $conn): void
++{
++ my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'datval');
++}
++
++function my_mysqli_test_stmt_response_row_over_read_time(my_mysqli_fake_server_conn $conn): void
++{
++ my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'timval', '0c');
++}
++
++function my_mysqli_test_stmt_response_row_over_read_datetime(my_mysqli_fake_server_conn $conn): void
++{
++ my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'dtival');
++}
++
++function my_mysqli_test_stmt_response_row_no_space(my_mysqli_fake_server_conn $conn): void
++{
++ my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'strval', '09');
++}
++
++function my_mysqli_test_stmt_response_row_over_read_bit(my_mysqli_fake_server_conn $conn): void
++{
++ my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'bitval');
++}
++
++function my_mysqli_test_stmt_response_row_read_two_fields(my_mysqli_fake_server_conn $conn): void
++{
++ $conn->send_server_greetings();
++ $conn->read();
++ $conn->send_server_ok();
++ $conn->read();
++ $field_names = array_keys(my_mysqli_data_fields());
++ foreach ($field_names as $field_name) {
++ $conn->send_server_stmt_prepare_data_response($field_name);
++ $conn->read(65536);
++ $conn->send_server_stmt_execute_data_response($field_name);
++ $conn->read(65536);
++ }
++}
++
++function my_mysqli_test_query_response_row_length_overflow(my_mysqli_fake_server_conn $conn): void
++{
++ $rh = $conn->packet_generator->server_query_execute_data_response('strval');
++
++ // Set extra length to overread by two bytes
++ $rh[4]->row_field2 = 'fefefefefe';
++
++ $conn->send_server_greetings();
++ $conn->read();
++ $conn->send_server_ok();
++ $conn->read();
++ $conn->send($conn->packets_to_bytes($rh), "Malicious Query Response for data strval field [length overflow]");
++ $conn->read(65536);
++}
++
++function my_mysqli_test_query_response_row_read_two_fields(my_mysqli_fake_server_conn $conn): void
++{
++ $conn->send_server_greetings();
++ $conn->read();
++ $conn->send_server_ok();
++ $conn->read();
++ $field_names = array_keys(my_mysqli_data_fields());
++ foreach ($field_names as $field_name) {
++ $conn->send_server_query_execute_data_response($field_name);
++ $conn->read();
++ }
++}
++
++function run_fake_server(string $test_function, $port = 33305): void
++{
++ $address = '127.0.0.1';
++
++ $socket = @stream_socket_server("tcp://$address:$port", $errno, $errstr);
++ if (!$socket) {
++ die("Failed to create socket: $errstr ($errno)\n");
++ }
++ echo "[*] Server started\n";
++
++ try {
++ $conn = new my_mysqli_fake_server_conn($socket);
++ $test_function_name = 'my_mysqli_test_' . $test_function;
++ call_user_func($test_function_name, $conn);
++ $conn->close();
++ } catch (Exception $e) {
++ fprintf(STDERR, "[!] Exception: " . $e->getMessage() . "\n");
++ }
++
++ fclose($socket);
++
++ echo "[*] Server finished\n";
++}
++
++
++function run_fake_server_in_background($test_function, $port = 33305): my_mysqli_fake_server_process
++{
++ $command = [PHP_BINARY, '-n', __FILE__, 'mysqli_fake_server', $test_function, $port];
++
++ $descriptorspec = array(
++ 0 => array("pipe", "r"),
++ 1 => array("pipe", "w"),
++ 2 => STDERR,
++ );
++
++ $process = proc_open($command, $descriptorspec, $pipes);
++
++ if (is_resource($process)) {
++ return new my_mysqli_fake_server_process($process, $pipes);
++ } else {
++ throw new Exception("Failed to start server process");
++ }
++}
++
++if (isset($argv) && $argc > 2 && $argv[1] == 'mysqli_fake_server') {
++ run_fake_server($argv[2], $argv[3] ?? '33305');
++}
+diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt
+new file mode 100644
+index 00000000000..db54a6c0177
+--- /dev/null
++++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt
+@@ -0,0 +1,38 @@
++--TEST--
++GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - auth message buffer over-read)
++--EXTENSIONS--
++mysqli
++--FILE--
++<?php
++require_once 'fake_server.inc';
++
++$port = 50001;
++$servername = "127.0.0.1";
++$username = "root";
++$password = "";
++
++$process = run_fake_server_in_background('auth_response_message_over_read', $port);
++$process->wait();
++
++try {
++ $conn = new mysqli( $servername, $username, $password, "", $port );
++ $info = mysqli_info($conn);
++ var_dump($info);
++} catch (Exception $e) {
++ echo $e->getMessage() . PHP_EOL;
++}
++
++$process->terminate();
++
++print "done!";
++?>
++--EXPECTF--
++[*] Server started
++[*] Connection established
++[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
++[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
++[*] Sending - Malicious OK Auth Response [Extract heap through buffer over-read]: 0900000200000002000000fcff
++
++Warning: mysqli::__construct(): OK packet message length is past the packet size in %s on line %d
++Unknown error while trying to connect via tcp://127.0.0.1:50001
++done!
+diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-def.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-def.phpt
+new file mode 100644
+index 00000000000..77f2232eca6
+--- /dev/null
++++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-def.phpt
+@@ -0,0 +1,47 @@
++--TEST--
++GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - tabular default)
++--EXTENSIONS--
++mysqli
++--FILE--
++<?php
++require_once 'fake_server.inc';
++
++
++$port = 33305;
++$servername = "127.0.0.1";
++$username = "root";
++$password = "";
++
++$process = run_fake_server_in_background('tabular_response_def_over_read', $port);
++$process->wait();
++
++$conn = new mysqli($servername, $username, $password, "", $port);
++
++echo "[*] Running query on the fake server...\n";
++
++$result = $conn->query("SELECT * from users");
++
++if ($result) {
++ $all_fields = $result->fetch_fields();
++ var_dump($result->fetch_all(MYSQLI_ASSOC));
++ var_dump(get_object_vars($all_fields[0])["def"]);
++}
++
++$conn->close();
++
++$process->terminate();
++
++print "done!";
++?>
++--EXPECTF--
++[*] Server started
++[*] Connection established
++[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
++[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
++[*] Sending - Server OK: 0700000200000002000000
++[*] Running query on the fake server...
++[*] Received: 140000000353454c454354202a2066726f6d207573657273
++[*] Sending - Malicious Tabular Response [Extract heap through buffer over-read]: 01000001011e0000020164016401640164016401640c3f000b000000030350000000fd000001aa05000003fe00002200040000040135017405000005fe00002200
++
++Warning: mysqli::query(): Protocol error. Server sent default for unsupported field list (mysqlnd_wireprotocol.c:%d) in %s on line %d
++done!
+diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-filename.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-filename.phpt
+new file mode 100644
+index 00000000000..0b4db8ccece
+--- /dev/null
++++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-filename.phpt
+@@ -0,0 +1,43 @@
++--TEST--
++GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - upsert filename buffer over-read)
++--EXTENSIONS--
++mysqli
++--FILE--
++<?php
++require_once 'fake_server.inc';
++
++$port = 33305;
++$servername = "127.0.0.1";
++$username = "root";
++$password = "";
++
++$process = run_fake_server_in_background('upsert_response_filename_over_read', $port);
++$process->wait();
++
++$conn = new mysqli($servername, $username, $password, "", $port);
++echo "[*] Running query on the fake server...\n";
++
++$result = $conn->query("SELECT * from users");
++$info = mysqli_info($conn);
++
++var_dump($info);
++
++$process->terminate();
++
++print "done!";
++?>
++--EXPECTF--
++[*] Server started
++[*] Connection established
++[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
++[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
++[*] Sending - Server OK: 0700000200000002000000
++[*] Running query on the fake server...
++[*] Received: 140000000353454c454354202a2066726f6d207573657273
++[*] Sending - Malicious Tabular Response [Extract heap through buffer over-read]: 0900000100000000000000fa65
++
++Warning: mysqli::query(): RSET_HEADER packet additional data length is past 249 bytes the packet size in %s on line %d
++
++Warning: mysqli::query(): Error reading result set's header in %s on line %d
++NULL
++done!
+diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-query-len-overflow.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-query-len-overflow.phpt
+new file mode 100644
+index 00000000000..f141a79bdaa
+--- /dev/null
++++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-query-len-overflow.phpt
+@@ -0,0 +1,48 @@
++--TEST--
++GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row no space for the field)
++--EXTENSIONS--
++mysqli
++--FILE--
++<?php
++require_once 'fake_server.inc';
++
++$port = 33305;
++$servername = "127.0.0.1";
++$username = "root";
++$password = "";
++
++$process = run_fake_server_in_background('query_response_row_length_overflow', $port);
++$process->wait();
++
++$conn = new mysqli($servername, $username, $password, "", $port);
++
++echo "[*] Query the fake server...\n";
++$sql = "SELECT strval, strval FROM data";
++
++$result = $conn->query($sql);
++
++if ($result->num_rows > 0) {
++ while ($row = $result->fetch_assoc()) {
++ var_dump($row['strval']);
++ }
++}
++$conn->close();
++
++$process->terminate(true);
++
++print "done!";
++?>
++--EXPECTF--
++[*] Server started
++[*] Connection established
++[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
++[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
++[*] Sending - Server OK: 0700000200000002000000
++[*] Query the fake server...
++[*] Received: 200000000353454c4543542073747276616c2c2073747276616c2046524f4d2064617461
++[*] Sending - Malicious Query Response for data strval field [length overflow]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe000022000a0000050474657374fefefefefe05000006fe00002200
++
++Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after end of packet in %s on line %d
++[*] Received: 0100000001
++[*] Server finished
++done!
+diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-bit.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-bit.phpt
+new file mode 100644
+index 00000000000..e43518217eb
+--- /dev/null
++++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-bit.phpt
+@@ -0,0 +1,53 @@
++--TEST--
++GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row bit buffer over-read)
++--EXTENSIONS--
++mysqli
++--FILE--
++<?php
++require_once 'fake_server.inc';
++
++$port = 33305;
++$servername = "127.0.0.1";
++$username = "root";
++$password = "";
++
++$process = run_fake_server_in_background('stmt_response_row_over_read_bit', $port);
++$process->wait();
++
++$conn = new mysqli($servername, $username, $password, "", $port);
++
++echo "[*] Preparing statement on the fake server...\n";
++$stmt = $conn->prepare("SELECT bitval, timval FROM data");
++
++$stmt->execute();
++$result = $stmt->get_result();
++
++// Fetch and display the results
++if ($result->num_rows > 0) {
++ while ($row = $result->fetch_assoc()) {
++ var_dump($row["bitval"]);
++ }
++}
++$stmt->close();
++$conn->close();
++
++$process->terminate(true);
++
++print "done!";
++?>
++--EXPECTF--
++[*] Server started
++[*] Connection established
++[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
++[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
++[*] Sending - Server OK: 0700000200000002000000
++[*] Preparing statement on the fake server...
++[*] Received: 200000001653454c4543542062697476616c2c2074696d76616c2046524f4d2064617461
++[*] Sending - Stmt prepare data bitval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe00000200
++[*] Received: 0a00000017010000000001000000
++[*] Sending - Malicious Stmt Response for data bitval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe00002200100000050000067465737408080808080808080805000006fe00002200
++
++Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d
++[*] Received: 0500000019010000000100000001
++[*] Server finished
++done!
+diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-date.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-date.phpt
+new file mode 100644
+index 00000000000..76158e940d0
+--- /dev/null
++++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-date.phpt
+@@ -0,0 +1,53 @@
++--TEST--
++GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row date buffer over-read)
++--EXTENSIONS--
++mysqli
++--FILE--
++<?php
++require_once 'fake_server.inc';
++
++$port = 33305;
++$servername = "127.0.0.1";
++$username = "root";
++$password = "";
++
++$process = run_fake_server_in_background('stmt_response_row_over_read_date', $port);
++$process->wait();
++
++$conn = new mysqli($servername, $username, $password, "", $port);
++
++echo "[*] Preparing statement on the fake server...\n";
++$stmt = $conn->prepare("SELECT strval, datval FROM data");
++
++$stmt->execute();
++$result = $stmt->get_result();
++
++// Fetch and display the results
++if ($result->num_rows > 0) {
++ while ($row = $result->fetch_assoc()) {
++ var_dump($row["datval"]);
++ }
++}
++$stmt->close();
++$conn->close();
++
++$process->terminate(true);
++
++print "done!";
++?>
++--EXPECTF--
++[*] Server started
++[*] Connection established
++[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
++[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
++[*] Sending - Server OK: 0700000200000002000000
++[*] Preparing statement on the fake server...
++[*] Received: 200000001653454c4543542073747276616c2c2064617476616c2046524f4d2064617461
++[*] Sending - Stmt prepare data datval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664617476616c0664617476616c0c3f000a0000000a811000000005000004fe00000200
++[*] Received: 0a00000017010000000001000000
++[*] Sending - Malicious Stmt Response for data datval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664617476616c0664617476616c0c3f000a0000000a811000000005000004fe000022000c0000050000067465737404de070c0f05000006fe00002200
++
++Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d
++[*] Received: 0500000019010000000100000001
++[*] Server finished
++done!
+diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-datetime.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-datetime.phpt
+new file mode 100644
+index 00000000000..f53d5b83bd4
+--- /dev/null
++++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-datetime.phpt
+@@ -0,0 +1,53 @@
++--TEST--
++GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row datetime buffer over-read)
++--EXTENSIONS--
++mysqli
++--FILE--
++<?php
++require_once 'fake_server.inc';
++
++$port = 33305;
++$servername = "127.0.0.1";
++$username = "root";
++$password = "";
++
++$process = run_fake_server_in_background('stmt_response_row_over_read_datetime', $port);
++$process->wait();
++
++$conn = new mysqli($servername, $username, $password, "", $port);
++
++echo "[*] Preparing statement on the fake server...\n";
++$stmt = $conn->prepare("SELECT strval, dtival FROM data");
++
++$stmt->execute();
++$result = $stmt->get_result();
++
++// Fetch and display the results
++if ($result->num_rows > 0) {
++ while ($row = $result->fetch_assoc()) {
++ var_dump($row["dtival"]);
++ }
++}
++$stmt->close();
++$conn->close();
++
++$process->terminate(true);
++
++print "done!";
++?>
++--EXPECTF--
++[*] Server started
++[*] Connection established
++[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
++[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
++[*] Sending - Server OK: 0700000200000002000000
++[*] Preparing statement on the fake server...
++[*] Received: 200000001653454c4543542073747276616c2c2064746976616c2046524f4d2064617461
++[*] Sending - Stmt prepare data dtival: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664746976616c0664746976616c0c3f00130000000c811000000005000004fe00000200
++[*] Received: 0a00000017010000000001000000
++[*] Sending - Malicious Stmt Response for data dtival [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664746976616c0664746976616c0c3f00130000000c811000000005000004fe000022000f0000050000067465737407de070c100d000105000006fe00002200
++
++Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d
++[*] Received: 0500000019010000000100000001
++[*] Server finished
++done!
+diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-double.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-double.phpt
+new file mode 100644
+index 00000000000..03c9b045d73
+--- /dev/null
++++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-double.phpt
+@@ -0,0 +1,53 @@
++--TEST--
++GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row double buffer over-read)
++--EXTENSIONS--
++mysqli
++--FILE--
++<?php
++require_once 'fake_server.inc';
++
++$port = 33305;
++$servername = "127.0.0.1";
++$username = "root";
++$password = "";
++
++$process = run_fake_server_in_background('stmt_response_row_over_read_double', $port);
++$process->wait();
++
++$conn = new mysqli($servername, $username, $password, "", $port);
++
++echo "[*] Preparing statement on the fake server...\n";
++$stmt = $conn->prepare("SELECT strval, dblval FROM data");
++
++$stmt->execute();
++$result = $stmt->get_result();
++
++// Fetch and display the results
++if ($result->num_rows > 0) {
++ while ($row = $result->fetch_assoc()) {
++ var_dump($row["dblval"]);
++ }
++}
++$stmt->close();
++$conn->close();
++
++$process->terminate(true);
++
++print "done!";
++?>
++--EXPECTF--
++[*] Server started
++[*] Connection established
++[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
++[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
++[*] Sending - Server OK: 0700000200000002000000
++[*] Preparing statement on the fake server...
++[*] Received: 200000001653454c4543542073747276616c2c2064626c76616c2046524f4d2064617461
++[*] Sending - Stmt prepare data dblval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664626c76616c0664626c76616c0c3f00160000000501101f000005000004fe00000200
++[*] Received: 0a00000017010000000001000000
++[*] Sending - Malicious Stmt Response for data dblval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664626c76616c0664626c76616c0c3f00160000000501101f000005000004fe000022000f00000500000674657374333333333333f33f05000006fe00002200
++
++Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d
++[*] Received: 0500000019010000000100000001
++[*] Server finished
++done!
+diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-float.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-float.phpt
+new file mode 100644
+index 00000000000..b1ec9aa51ec
+--- /dev/null
++++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-float.phpt
+@@ -0,0 +1,53 @@
++--TEST--
++GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row int buffer over-read)
++--EXTENSIONS--
++mysqli
++--FILE--
++<?php
++require_once 'fake_server.inc';
++
++$port = 33305;
++$servername = "127.0.0.1";
++$username = "root";
++$password = "";
++
++$process = run_fake_server_in_background('stmt_response_row_over_read_float', $port);
++$process->wait();
++
++$conn = new mysqli($servername, $username, $password, "", $port);
++
++echo "[*] Preparing statement on the fake server...\n";
++$stmt = $conn->prepare("SELECT strval, fltval FROM data");
++
++$stmt->execute();
++$result = $stmt->get_result();
++
++// Fetch and display the results
++if ($result->num_rows > 0) {
++ while ($row = $result->fetch_assoc()) {
++ var_dump($row["fltval"]);
++ }
++}
++$stmt->close();
++$conn->close();
++
++$process->terminate(true);
++
++print "done!";
++?>
++--EXPECTF--
++[*] Server started
++[*] Connection established
++[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
++[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
++[*] Sending - Server OK: 0700000200000002000000
++[*] Preparing statement on the fake server...
++[*] Received: 200000001653454c4543542073747276616c2c20666c7476616c2046524f4d2064617461
++[*] Sending - Stmt prepare data fltval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106666c7476616c06666c7476616c0c3f000c0000000401101f000005000004fe00000200
++[*] Received: 0a00000017010000000001000000
++[*] Sending - Malicious Stmt Response for data fltval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106666c7476616c06666c7476616c0c3f000c0000000401101f000005000004fe000022000b000005000006746573743333134005000006fe00002200
++
++Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d
++[*] Received: 0500000019010000000100000001
++[*] Server finished
++done!
+diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-int.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-int.phpt
+new file mode 100644
+index 00000000000..426d9ea7b3f
+--- /dev/null
++++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-int.phpt
+@@ -0,0 +1,53 @@
++--TEST--
++GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row int buffer over-read)
++--EXTENSIONS--
++mysqli
++--FILE--
++<?php
++require_once 'fake_server.inc';
++
++$port = 33305;
++$servername = "127.0.0.1";
++$username = "root";
++$password = "";
++
++$process = run_fake_server_in_background('stmt_response_row_over_read_int', $port);
++$process->wait();
++
++$conn = new mysqli($servername, $username, $password, "", $port);
++
++echo "[*] Preparing statement on the fake server...\n";
++$stmt = $conn->prepare("SELECT strval, intval FROM data");
++
++$stmt->execute();
++$result = $stmt->get_result();
++
++// Fetch and display the results
++if ($result->num_rows > 0) {
++ while ($row = $result->fetch_assoc()) {
++ var_dump($row["intval"]);
++ }
++}
++$stmt->close();
++$conn->close();
++
++$process->terminate(true);
++
++print "done!";
++?>
++--EXPECTF--
++[*] Server started
++[*] Connection established
++[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
++[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
++[*] Sending - Server OK: 0700000200000002000000
++[*] Preparing statement on the fake server...
++[*] Received: 200000001653454c4543542073747276616c2c20696e7476616c2046524f4d2064617461
++[*] Sending - Stmt prepare data intval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106696e7476616c06696e7476616c0c3f000b00000003011000000005000004fe00000200
++[*] Received: 0a00000017010000000001000000
++[*] Sending - Malicious Stmt Response for data intval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106696e7476616c06696e7476616c0c3f000b00000003011000000005000004fe000022000b000005000006746573740e00000005000006fe00002200
++
++Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d
++[*] Received: 0500000019010000000100000001
++[*] Server finished
++done!
+diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-no-space.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-no-space.phpt
+new file mode 100644
+index 00000000000..6db6952d42a
+--- /dev/null
++++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-no-space.phpt
+@@ -0,0 +1,53 @@
++--TEST--
++GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row no space for the field)
++--EXTENSIONS--
++mysqli
++--FILE--
++<?php
++require_once 'fake_server.inc';
++
++$port = 33305;
++$servername = "127.0.0.1";
++$username = "root";
++$password = "";
++
++$process = run_fake_server_in_background('stmt_response_row_no_space', $port);
++$process->wait();
++
++$conn = new mysqli($servername, $username, $password, "", $port);
++
++echo "[*] Preparing statement on the fake server...\n";
++$stmt = $conn->prepare("SELECT strval, strval FROM data");
++
++$stmt->execute();
++$result = $stmt->get_result();
++
++// Fetch and display the results
++if ($result->num_rows > 0) {
++ while ($row = $result->fetch_assoc()) {
++ var_dump($row["strval"]);
++ }
++}
++$stmt->close();
++$conn->close();
++
++$process->terminate(true);
++
++print "done!";
++?>
++--EXPECTF--
++[*] Server started
++[*] Connection established
++[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
++[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
++[*] Sending - Server OK: 0700000200000002000000
++[*] Preparing statement on the fake server...
++[*] Received: 200000001653454c4543542073747276616c2c2073747276616c2046524f4d2064617461
++[*] Sending - Stmt prepare data strval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe00000200
++[*] Received: 0a00000017010000000001000000
++[*] Sending - Malicious Stmt Response for data strval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe000022000c00000500000974657374047465737405000006fe00002200
++
++Warning: mysqli_result::fetch_assoc(): Malformed server packet. No packet space left for the field in %s on line %d
++[*] Received: 0500000019010000000100000001
++[*] Server finished
++done!
+diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-string.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-string.phpt
+new file mode 100644
+index 00000000000..55bad4cc544
+--- /dev/null
++++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-string.phpt
+@@ -0,0 +1,53 @@
++--TEST--
++GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row string buffer over-read)
++--EXTENSIONS--
++mysqli
++--FILE--
++<?php
++require_once 'fake_server.inc';
++
++$port = 33305;
++$servername = "127.0.0.1";
++$username = "root";
++$password = "";
++
++$process = run_fake_server_in_background('stmt_response_row_over_read_string', $port);
++$process->wait();
++
++$conn = new mysqli($servername, $username, $password, "", $port);
++
++echo "[*] Preparing statement on the fake server...\n";
++$stmt = $conn->prepare("SELECT item FROM items");
++
++$stmt->execute();
++$result = $stmt->get_result();
++
++// Fetch and display the results
++if ($result->num_rows > 0) {
++ while ($row = $result->fetch_assoc()) {
++ var_dump($row["item"]);
++ }
++}
++$stmt->close();
++$conn->close();
++
++$process->terminate(true);
++
++print "done!";
++?>
++--EXPECTF--
++[*] Server started
++[*] Connection established
++[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
++[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
++[*] Sending - Server OK: 0700000200000002000000
++[*] Preparing statement on the fake server...
++[*] Received: 170000001653454c454354206974656d2046524f4d206974656d73
++[*] Sending - Stmt prepare items: 0c0000010001000000010000000000003000000203646566087068705f74657374056974656d73056974656d73046974656d046974656d0ce000c8000000fd011000000005000003fe00000200
++[*] Received: 0a00000017010000000001000000
++[*] Sending - Malicious Stmt Response for items [Extract heap through buffer over-read]: 01000001013000000203646566087068705f74657374056974656d73056974656d73046974656d046974656d0ce000c8000000fd011000000005000003fe00002200070000040000fa7465737405000005fe00002200
++
++Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d
++[*] Received: 0500000019010000000100000001
++[*] Server finished
++done!
+diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-time.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-time.phpt
+new file mode 100644
+index 00000000000..06918c375f3
+--- /dev/null
++++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-time.phpt
+@@ -0,0 +1,53 @@
++--TEST--
++GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row time buffer over-read)
++--EXTENSIONS--
++mysqli
++--FILE--
++<?php
++require_once 'fake_server.inc';
++
++$port = 33305;
++$servername = "127.0.0.1";
++$username = "root";
++$password = "";
++
++$process = run_fake_server_in_background('stmt_response_row_over_read_time', $port);
++$process->wait();
++
++$conn = new mysqli($servername, $username, $password, "", $port);
++
++echo "[*] Preparing statement on the fake server...\n";
++$stmt = $conn->prepare("SELECT strval, timval FROM data");
++
++$stmt->execute();
++$result = $stmt->get_result();
++
++// Fetch and display the results
++if ($result->num_rows > 0) {
++ while ($row = $result->fetch_assoc()) {
++ var_dump($row["timval"]);
++ }
++}
++$stmt->close();
++$conn->close();
++
++$process->terminate(true);
++
++print "done!";
++?>
++--EXPECTF--
++[*] Server started
++[*] Connection established
++[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
++[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
++[*] Sending - Server OK: 0700000200000002000000
++[*] Preparing statement on the fake server...
++[*] Received: 200000001653454c4543542073747276616c2c2074696d76616c2046524f4d2064617461
++[*] Sending - Stmt prepare data timval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610674696d76616c0674696d76616c0c3f000a0000000b811000000005000004fe00000200
++[*] Received: 0a00000017010000000001000000
++[*] Sending - Malicious Stmt Response for data timval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610674696d76616c0674696d76616c0c3f000a0000000b811000000005000004fe000022001000000500000c7465737408000000000015080105000006fe00002200
++
++Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d
++[*] Received: 0500000019010000000100000001
++[*] Server finished
++done!
+diff --git a/ext/mysqli/tests/protocol_query_row_fetch_data.phpt b/ext/mysqli/tests/protocol_query_row_fetch_data.phpt
+new file mode 100644
+index 00000000000..524fe5e587c
+--- /dev/null
++++ b/ext/mysqli/tests/protocol_query_row_fetch_data.phpt
+@@ -0,0 +1,74 @@
++--TEST--
++MySQL protocol - statement row data fetch)
++--EXTENSIONS--
++mysqli
++--FILE--
++<?php
++require_once 'fake_server.inc';
++
++$port = 33305;
++$servername = "127.0.0.1";
++$username = "root";
++$password = "";
++
++$process = run_fake_server_in_background('query_response_row_read_two_fields', $port);
++$process->wait();
++
++$conn = new mysqli($servername, $username, $password, "", $port);
++
++function my_query($conn, $field)
++{
++ $sql = "SELECT strval, $field FROM data";
++
++ $result = $conn->query($sql);
++
++ if ($result->num_rows > 0) {
++ while ($row = $result->fetch_assoc()) {
++ var_dump($row[$field]);
++ }
++ }
++}
++
++foreach (my_mysqli_data_fields() as $field_name => $field) {
++ my_query($conn, $field_name);
++}
++
++$conn->close();
++
++$process->terminate(true);
++
++print "done!";
++?>
++--EXPECT--
++[*] Server started
++[*] Connection established
++[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
++[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
++[*] Sending - Server OK: 0700000200000002000000
++[*] Received: 200000000353454c4543542073747276616c2c20696e7476616c2046524f4d2064617461
++[*] Sending - Query execute data intval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106696e7476616c06696e7476616c0c3f000b00000003011000000005000004fe0000220008000005047465737402313405000006fe00002200
++string(2) "14"
++[*] Received: 200000000353454c4543542073747276616c2c20666c7476616c2046524f4d2064617461
++[*] Sending - Query execute data fltval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106666c7476616c06666c7476616c0c3f000c0000000401101f000005000004fe0000220009000005047465737403322e3305000006fe00002200
++string(3) "2.3"
++[*] Received: 200000000353454c4543542073747276616c2c2064626c76616c2046524f4d2064617461
++[*] Sending - Query execute data dblval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664626c76616c0664626c76616c0c3f00160000000501101f000005000004fe0000220009000005047465737403312e3205000006fe00002200
++string(3) "1.2"
++[*] Received: 200000000353454c4543542073747276616c2c2064617476616c2046524f4d2064617461
++[*] Sending - Query execute data datval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664617476616c0664617476616c0c3f000a0000000a811000000005000004fe000022001000000504746573740a323031342d31322d313505000006fe00002200
++string(10) "2014-12-15"
++[*] Received: 200000000353454c4543542073747276616c2c2074696d76616c2046524f4d2064617461
++[*] Sending - Query execute data timval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610674696d76616c0674696d76616c0c3f000a0000000b811000000005000004fe000022000e00000504746573740831333a30303a303205000006fe00002200
++string(8) "13:00:02"
++[*] Received: 200000000353454c4543542073747276616c2c2064746976616c2046524f4d2064617461
++[*] Sending - Query execute data dtival: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664746976616c0664746976616c0c3f00130000000c811000000005000004fe0000220019000005047465737413323031342d31322d31362031333a30303a303105000006fe00002200
++string(19) "2014-12-16 13:00:01"
++[*] Received: 200000000353454c4543542073747276616c2c2062697476616c2046524f4d2064617461
++[*] Sending - Query execute data bitval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe000022000e000005047465737408080808080808080805000006fe00002200
++string(18) "578721382704613384"
++[*] Received: 200000000353454c4543542073747276616c2c2073747276616c2046524f4d2064617461
++[*] Sending - Query execute data strval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe000022000a0000050474657374047465737405000006fe00002200
++string(4) "test"
++[*] Received: 0100000001
++[*] Server finished
++done!
+diff --git a/ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt b/ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt
+new file mode 100644
+index 00000000000..d461ec24b8c
+--- /dev/null
++++ b/ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt
+@@ -0,0 +1,91 @@
++--TEST--
++MySQL protocol - statement row data fetch)
++--EXTENSIONS--
++mysqli
++--FILE--
++<?php
++require_once 'fake_server.inc';
++
++$port = 33305;
++$servername = "127.0.0.1";
++$username = "root";
++$password = "";
++
++$process = run_fake_server_in_background('stmt_response_row_read_two_fields', $port);
++$process->wait();
++
++$conn = new mysqli($servername, $username, $password, "", $port);
++
++function my_query($conn, $field)
++{
++ $stmt = $conn->prepare("SELECT strval, $field FROM data");
++
++ $stmt->execute();
++ $result = $stmt->get_result();
++
++ if ($result->num_rows > 0) {
++ while ($row = $result->fetch_assoc()) {
++ var_dump($row[$field]);
++ }
++ }
++}
++
++foreach (my_mysqli_data_fields() as $field_name => $field) {
++ my_query($conn, $field_name);
++}
++
++$conn->close();
++
++$process->terminate(true);
++
++print "done!";
++?>
++--EXPECT--
++[*] Server started
++[*] Connection established
++[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
++[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
++[*] Sending - Server OK: 0700000200000002000000
++[*] Received: 200000001653454c4543542073747276616c2c20696e7476616c2046524f4d2064617461
++[*] Sending - Stmt prepare data intval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106696e7476616c06696e7476616c0c3f000b00000003011000000005000004fe00000200
++[*] Received: 0a00000017010000000001000000
++[*] Sending - Stmt execute data intval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106696e7476616c06696e7476616c0c3f000b00000003011000000005000004fe000022000b000005000004746573740e00000005000006fe00002200
++int(14)
++[*] Received: 050000001901000000200000001653454c4543542073747276616c2c20666c7476616c2046524f4d2064617461
++[*] Sending - Stmt prepare data fltval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106666c7476616c06666c7476616c0c3f000c0000000401101f000005000004fe00000200
++[*] Received: 0a00000017010000000001000000
++[*] Sending - Stmt execute data fltval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106666c7476616c06666c7476616c0c3f000c0000000401101f000005000004fe000022000b000005000004746573743333134005000006fe00002200
++float(2.3)
++[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2064626c76616c2046524f4d2064617461
++[*] Sending - Stmt prepare data dblval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664626c76616c0664626c76616c0c3f00160000000501101f000005000004fe00000200
++[*] Received: 0a00000017010000000001000000
++[*] Sending - Stmt execute data dblval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664626c76616c0664626c76616c0c3f00160000000501101f000005000004fe000022000f00000500000474657374333333333333f33f05000006fe00002200
++float(1.2)
++[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2064617476616c2046524f4d2064617461
++[*] Sending - Stmt prepare data datval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664617476616c0664617476616c0c3f000a0000000a811000000005000004fe00000200
++[*] Received: 0a00000017010000000001000000
++[*] Sending - Stmt execute data datval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664617476616c0664617476616c0c3f000a0000000a811000000005000004fe000022000c0000050000047465737404de070c0f05000006fe00002200
++string(10) "2014-12-15"
++[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2074696d76616c2046524f4d2064617461
++[*] Sending - Stmt prepare data timval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610674696d76616c0674696d76616c0c3f000a0000000b811000000005000004fe00000200
++[*] Received: 0a00000017010000000001000000
++[*] Sending - Stmt execute data timval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610674696d76616c0674696d76616c0c3f000a0000000b811000000005000004fe00002200100000050000047465737408000000000015080105000006fe00002200
++string(8) "21:08:01"
++[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2064746976616c2046524f4d2064617461
++[*] Sending - Stmt prepare data dtival: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664746976616c0664746976616c0c3f00130000000c811000000005000004fe00000200
++[*] Received: 0a00000017010000000001000000
++[*] Sending - Stmt execute data dtival: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664746976616c0664746976616c0c3f00130000000c811000000005000004fe000022000f0000050000047465737407de070c100d000105000006fe00002200
++string(19) "2014-12-16 13:00:01"
++[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2062697476616c2046524f4d2064617461
++[*] Sending - Stmt prepare data bitval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe00000200
++[*] Received: 0a00000017010000000001000000
++[*] Sending - Stmt execute data bitval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe00002200100000050000047465737408080808080808080805000006fe00002200
++int(578721382704613384)
++[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2073747276616c2046524f4d2064617461
++[*] Sending - Stmt prepare data strval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe00000200
++[*] Received: 0a00000017010000000001000000
++[*] Sending - Stmt execute data strval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe000022000c00000500000474657374047465737405000006fe00002200
++string(4) "test"
++[*] Received: 0500000019010000000100000001
++[*] Server finished
++done!
+diff --git a/ext/mysqlnd/mysqlnd_ps_codec.c b/ext/mysqlnd/mysqlnd_ps_codec.c
+index 12edd9a2849..976143cb471 100644
+--- a/ext/mysqlnd/mysqlnd_ps_codec.c
++++ b/ext/mysqlnd/mysqlnd_ps_codec.c
+@@ -52,6 +52,37 @@ struct st_mysqlnd_perm_bind mysqlnd_ps_fetch_functions[MYSQL_TYPE_LAST + 1];
+ #define MYSQLND_PS_SKIP_RESULT_W_LEN -1
+ #define MYSQLND_PS_SKIP_RESULT_STR -2
+
++static inline void ps_fetch_over_read_error(const zend_uchar ** row)
++{
++ php_error_docref(NULL, E_WARNING, "Malformed server packet. Field length pointing after the end of packet");
++ *row = NULL;
++}
++
++static inline zend_bool ps_fetch_is_packet_over_read_with_variable_length(const unsigned int pack_len,
++ const zend_uchar ** row, const zend_uchar *p, unsigned int length)
++{
++ if (pack_len == 0) {
++ return 0;
++ }
++ size_t length_len = *row - p;
++ if (length_len > pack_len || length > pack_len - length_len) {
++ ps_fetch_over_read_error(row);
++ return 1;
++ }
++ return 0;
++}
++
++static inline zend_bool ps_fetch_is_packet_over_read_with_static_length(const unsigned int pack_len,
++ const zend_uchar ** row, unsigned int length)
++{
++ if (pack_len > 0 && length > pack_len) {
++ ps_fetch_over_read_error(row);
++ return 1;
++ }
++ return 0;
++}
++
++
+ /* {{{ ps_fetch_from_1_to_8_bytes */
+ void
+ ps_fetch_from_1_to_8_bytes(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len,
+@@ -60,6 +91,11 @@ ps_fetch_from_1_to_8_bytes(zval * zv, const MYSQLND_FIELD * const field, const u
+ char tmp[22];
+ size_t tmp_len = 0;
+ zend_bool is_bit = field->type == MYSQL_TYPE_BIT;
++
++ if (UNEXPECTED(ps_fetch_is_packet_over_read_with_static_length(pack_len, row, byte_count))) {
++ return;
++ }
++
+ DBG_ENTER("ps_fetch_from_1_to_8_bytes");
+ DBG_INF_FMT("zv=%p byte_count=%u", zv, byte_count);
+ if (field->flags & UNSIGNED_FLAG) {
+@@ -178,6 +214,11 @@ ps_fetch_float(zval * zv, const MYSQLND_FIELD * const field, const unsigned int
+ float fval;
+ double dval;
+ DBG_ENTER("ps_fetch_float");
++
++ if (UNEXPECTED(ps_fetch_is_packet_over_read_with_static_length(pack_len, row, 4))) {
++ return;
++ }
++
+ float4get(fval, *row);
+ (*row)+= 4;
+ DBG_INF_FMT("value=%f", fval);
+@@ -200,6 +241,11 @@ ps_fetch_double(zval * zv, const MYSQLND_FIELD * const field, const unsigned int
+ {
+ double value;
+ DBG_ENTER("ps_fetch_double");
++
++ if (UNEXPECTED(ps_fetch_is_packet_over_read_with_static_length(pack_len, row, 8))) {
++ return;
++ }
++
+ float8get(value, *row);
+ ZVAL_DOUBLE(zv, value);
+ (*row)+= 8;
+@@ -216,9 +262,14 @@ ps_fetch_time(zval * zv, const MYSQLND_FIELD * const field, const unsigned int p
+ struct st_mysqlnd_time t;
+ zend_ulong length; /* First byte encodes the length*/
+ char * value;
++ const zend_uchar *p = *row;
+ DBG_ENTER("ps_fetch_time");
+
+ if ((length = php_mysqlnd_net_field_length(row))) {
++ if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) {
++ return;
++ }
++
+ const zend_uchar * to = *row;
+
+ t.time_type = MYSQLND_TIMESTAMP_TIME;
+@@ -273,9 +324,14 @@ ps_fetch_date(zval * zv, const MYSQLND_FIELD * const field, const unsigned int p
+ struct st_mysqlnd_time t = {0};
+ zend_ulong length; /* First byte encodes the length*/
+ char * value;
++ const zend_uchar *p = *row;
+ DBG_ENTER("ps_fetch_date");
+
+ if ((length = php_mysqlnd_net_field_length(row))) {
++ if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) {
++ return;
++ }
++
+ const zend_uchar * to = *row;
+
+ t.time_type = MYSQLND_TIMESTAMP_DATE;
+@@ -310,9 +366,14 @@ ps_fetch_datetime(zval * zv, const MYSQLND_FIELD * const field, const unsigned i
+ struct st_mysqlnd_time t;
+ zend_ulong length; /* First byte encodes the length*/
+ char * value;
++ const zend_uchar *p = *row;
+ DBG_ENTER("ps_fetch_datetime");
+
+ if ((length = php_mysqlnd_net_field_length(row))) {
++ if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) {
++ return;
++ }
++
+ const zend_uchar * to = *row;
+
+ t.time_type = MYSQLND_TIMESTAMP_DATETIME;
+@@ -371,7 +432,11 @@ ps_fetch_string(zval * zv, const MYSQLND_FIELD * const field, const unsigned int
+ For now just copy, before we make it possible
+ to write \0 to the row buffer
+ */
++ const zend_uchar *p = *row;
+ const zend_ulong length = php_mysqlnd_net_field_length(row);
++ if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) {
++ return;
++ }
+ DBG_ENTER("ps_fetch_string");
+ DBG_INF_FMT("len = %lu", length);
+ DBG_INF("copying from the row buffer");
+@@ -387,7 +452,11 @@ ps_fetch_string(zval * zv, const MYSQLND_FIELD * const field, const unsigned int
+ static void
+ ps_fetch_bit(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len, const zend_uchar ** row)
+ {
++ const zend_uchar *p = *row;
+ const zend_ulong length = php_mysqlnd_net_field_length(row);
++ if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) {
++ return;
++ }
+ ps_fetch_from_1_to_8_bytes(zv, field, pack_len, row, length);
+ }
+ /* }}} */
+diff --git a/ext/mysqlnd/mysqlnd_result.c b/ext/mysqlnd/mysqlnd_result.c
+index 4dcaf121fb7..c0f65f854ac 100644
+--- a/ext/mysqlnd/mysqlnd_result.c
++++ b/ext/mysqlnd/mysqlnd_result.c
+@@ -505,7 +505,7 @@ mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn, MYSQLND_STMT * s)
+ if (FAIL == (ret = result->m.read_result_metadata(result, conn))) {
+ /* For PS, we leave them in Prepared state */
+ if (!stmt && conn->current_result) {
+- mnd_efree(conn->current_result);
++ conn->current_result->m.free_result(conn->current_result, TRUE);
+ conn->current_result = NULL;
+ }
+ DBG_ERR("Error occurred while reading metadata");
+diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c
+index e4a298adaea..844b90c5704 100644
+--- a/ext/mysqlnd/mysqlnd_wireprotocol.c
++++ b/ext/mysqlnd/mysqlnd_wireprotocol.c
+@@ -715,7 +715,14 @@ php_mysqlnd_auth_response_read(MYSQLND_CONN_DATA * conn, void * _packet)
+
+ /* There is a message */
+ if (packet->header.size > (size_t) (p - buf) && (net_len = php_mysqlnd_net_field_length(&p))) {
+- packet->message_len = MIN(net_len, buf_len - (p - begin));
++ /* p can get past packet size when getting field length so it needs to be checked first
++ * and after that it can be checked that the net_len is not greater than the packet size */
++ if ((p - buf) > packet->header.size || packet->header.size - (p - buf) < net_len) {
++ DBG_ERR_FMT("OK packet message length is past the packet size");
++ php_error_docref(NULL, E_WARNING, "OK packet message length is past the packet size");
++ DBG_RETURN(FAIL);
++ }
++ packet->message_len = net_len;
+ packet->message = mnd_pestrndup((char *)p, packet->message_len, FALSE);
+ } else {
+ packet->message = NULL;
+@@ -1113,6 +1120,17 @@ php_mysqlnd_rset_header_read(MYSQLND_CONN_DATA * conn, void * _packet)
+ BAIL_IF_NO_MORE_DATA;
+ /* Check for additional textual data */
+ if (packet->header.size > (size_t) (p - buf) && (len = php_mysqlnd_net_field_length(&p))) {
++ /* p can get past packet size when getting field length so it needs to be checked first
++ * and after that it can be checked that the len is not greater than the packet size */
++ if ((p - buf) > packet->header.size || packet->header.size - (p - buf) < len) {
++ size_t local_file_name_over_read = ((p - buf) - packet->header.size) + len;
++ DBG_ERR_FMT("RSET_HEADER packet additional data length is past %zu bytes the packet size",
++ local_file_name_over_read);
++ php_error_docref(NULL, E_WARNING,
++ "RSET_HEADER packet additional data length is past %zu bytes the packet size",
++ local_file_name_over_read);
++ DBG_RETURN(FAIL);
++ }
+ packet->info_or_local_file.s = mnd_emalloc(len + 1);
+ if (packet->info_or_local_file.s) {
+ memcpy(packet->info_or_local_file.s, p, len);
+@@ -1271,23 +1289,16 @@ php_mysqlnd_rset_field_read(MYSQLND_CONN_DATA * conn, void * _packet)
+ meta->flags |= NUM_FLAG;
+ }
+
+-
+- /*
+- def could be empty, thus don't allocate on the root.
+- NULL_LENGTH (0xFB) comes from COM_FIELD_LIST when the default value is NULL.
+- Otherwise the string is length encoded.
+- */
++ /* COM_FIELD_LIST is no longer supported so def should not be present */
+ if (packet->header.size > (size_t) (p - buf) &&
+ (len = php_mysqlnd_net_field_length(&p)) &&
+ len != MYSQLND_NULL_LENGTH)
+ {
+- BAIL_IF_NO_MORE_DATA;
+- DBG_INF_FMT("Def found, length %lu", len);
+- meta->def = packet->memory_pool->get_chunk(packet->memory_pool, len + 1);
+- memcpy(meta->def, p, len);
+- meta->def[len] = '\0';
+- meta->def_length = len;
+- p += len;
++ DBG_ERR_FMT("Protocol error. Server sent default for unsupported field list");
++ php_error_docref(NULL, E_WARNING,
++ "Protocol error. Server sent default for unsupported field list (mysqlnd_wireprotocol.c:%u)",
++ __LINE__);
++ DBG_RETURN(FAIL);
+ }
+
+ root_ptr = meta->root = packet->memory_pool->get_chunk(packet->memory_pool, total_len);
+@@ -1462,8 +1473,10 @@ php_mysqlnd_rowp_read_binary_protocol(MYSQLND_ROW_BUFFER * row_buffer, zval * fi
+ const unsigned int field_count, const MYSQLND_FIELD * const fields_metadata,
+ const zend_bool as_int_or_float, MYSQLND_STATS * const stats)
+ {
+- unsigned int i;
+- const zend_uchar * p = row_buffer->ptr;
++ unsigned int i, j;
++ size_t rbs = row_buffer->size;
++ const zend_uchar * rbp = row_buffer->ptr;
++ const zend_uchar * p = rbp;
+ const zend_uchar * null_ptr;
+ zend_uchar bit;
+ zval *current_field, *end_field, *start_field;
+@@ -1496,7 +1509,21 @@ php_mysqlnd_rowp_read_binary_protocol(MYSQLND_ROW_BUFFER * row_buffer, zval * fi
+ statistic = STAT_BINARY_TYPE_FETCHED_NULL;
+ } else {
+ enum_mysqlnd_field_types type = fields_metadata[i].type;
+- mysqlnd_ps_fetch_functions[type].func(current_field, &fields_metadata[i], 0, &p);
++ size_t row_position = p - rbp;
++ if (rbs <= row_position) {
++ for (j = 0, current_field = start_field; j < i; current_field++, j++) {
++ zval_ptr_dtor(current_field);
++ }
++ php_error_docref(NULL, E_WARNING, "Malformed server packet. No packet space left for the field");
++ DBG_RETURN(FAIL);
++ }
++ mysqlnd_ps_fetch_functions[type].func(current_field, &fields_metadata[i], rbs - row_position, &p);
++ if (p == NULL) {
++ for (j = 0, current_field = start_field; j < i; current_field++, j++) {
++ zval_ptr_dtor(current_field);
++ }
++ DBG_RETURN(FAIL);
++ }
+
+ if (MYSQLND_G(collect_statistics)) {
+ switch (fields_metadata[i].type) {
+@@ -1553,7 +1580,7 @@ php_mysqlnd_rowp_read_text_protocol_aux(MYSQLND_ROW_BUFFER * row_buffer, zval *
+ unsigned int field_count, const MYSQLND_FIELD * fields_metadata,
+ zend_bool as_int_or_float, MYSQLND_STATS * stats)
+ {
+- unsigned int i;
++ unsigned int i, j;
+ zval *current_field, *end_field, *start_field;
+ zend_uchar * p = row_buffer->ptr;
+ const size_t data_size = row_buffer->size;
+@@ -1574,9 +1601,11 @@ php_mysqlnd_rowp_read_text_protocol_aux(MYSQLND_ROW_BUFFER * row_buffer, zval *
+ /* NULL or NOT NULL, this is the question! */
+ if (len == MYSQLND_NULL_LENGTH) {
+ ZVAL_NULL(current_field);
+- } else if ((p + len) > packet_end) {
+- php_error_docref(NULL, E_WARNING, "Malformed server packet. Field length pointing "MYSQLND_SZ_T_SPEC
+- " bytes after end of packet", (p + len) - packet_end - 1);
++ } else if (p > packet_end || len > packet_end - p) {
++ php_error_docref(NULL, E_WARNING, "Malformed server packet. Field length pointing after end of packet");
++ for (j = 0, current_field = start_field; j < i; current_field++, j++) {
++ zval_ptr_dtor(current_field);
++ }
+ DBG_RETURN(FAIL);
+ } else {
+ #if defined(MYSQLND_STRING_TO_INT_CONVERSION)
+--
+2.47.0
+
+From aaeb9549a1bdfa787fc3d3a2d499b418d09a5387 Mon Sep 17 00:00:00 2001
+From: Jakub Zelenka <bukka@php.net>
+Date: Mon, 18 Nov 2024 15:54:30 +0100
+Subject: [PATCH 2/6] Fix MySQLnd possible buffer over read in auth_protocol
+
+(cherry picked from commit 32f905f1d689aaa8eacd6331a18c0dd45972c3c1)
+(cherry picked from commit d5f9da0d6af72ae21b0a9f4c94c59dfdd409e3e2)
+---
+ ext/mysqlnd/mysqlnd_wireprotocol.c | 27 +++++++++++++++++++++++++--
+ 1 file changed, 25 insertions(+), 2 deletions(-)
+
+diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c
+index 844b90c5704..e75c859bab7 100644
+--- a/ext/mysqlnd/mysqlnd_wireprotocol.c
++++ b/ext/mysqlnd/mysqlnd_wireprotocol.c
+@@ -441,8 +441,31 @@ php_mysqlnd_greet_read(MYSQLND_CONN_DATA * conn, void * _packet)
+ if (packet->server_capabilities & CLIENT_PLUGIN_AUTH) {
+ BAIL_IF_NO_MORE_DATA;
+ /* The server is 5.5.x and supports authentication plugins */
+- packet->auth_protocol = estrdup((char *)p);
+- p+= strlen(packet->auth_protocol) + 1; /* eat the '\0' */
++ size_t remaining_size = packet->header.size - (size_t)(p - buf);
++ if (remaining_size == 0) {
++ /* Might be better to fail but this will fail anyway */
++ packet->auth_protocol = estrdup("");
++ } else {
++ /* Check if NUL present */
++ char *null_terminator = memchr(p, '\0', remaining_size);
++ size_t auth_protocol_len;
++ if (null_terminator) {
++ /* If present, do basically estrdup */
++ auth_protocol_len = null_terminator - (char *)p;
++ } else {
++ /* If not present, copy the rest of the buffer */
++ auth_protocol_len = remaining_size;
++ }
++ char *auth_protocol = emalloc(auth_protocol_len + 1);
++ memcpy(auth_protocol, p, auth_protocol_len);
++ auth_protocol[auth_protocol_len] = '\0';
++ packet->auth_protocol = auth_protocol;
++
++ p += auth_protocol_len;
++ if (null_terminator) {
++ p++;
++ }
++ }
+ }
+
+ DBG_INF_FMT("proto=%u server=%s thread_id=%u",
+--
+2.47.0
+
+From 83a0d005d51a44bbe77a178c387e2c9f042a335d Mon Sep 17 00:00:00 2001
+From: Remi Collet <remi@remirepo.net>
+Date: Wed, 27 Nov 2024 10:54:10 +0100
+Subject: [PATCH 3/6] Avoid using uninitialised struct
+
+ (cherry picked from commit 7e7817bc2f82570bbc510a2bf5e4e0ec09dbc774)
+
+(cherry picked from commit 69853e12b73a989e2383452356cdc07172427ae3)
+---
+ ext/mysqlnd/mysqlnd_result.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/ext/mysqlnd/mysqlnd_result.c b/ext/mysqlnd/mysqlnd_result.c
+index c0f65f854ac..9f19adb0ce9 100644
+--- a/ext/mysqlnd/mysqlnd_result.c
++++ b/ext/mysqlnd/mysqlnd_result.c
+@@ -549,8 +549,8 @@ mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn, MYSQLND_STMT * s)
+ }
+ MYSQLND_INC_CONN_STATISTIC(conn->stats, statistic);
+ }
++ PACKET_FREE(&fields_eof);
+ } while (0);
+- PACKET_FREE(&fields_eof);
+ break; /* switch break */
+ }
+ } while (0);
+--
+2.47.0
+
+From 606322b7f3475fb5980f7785789adfb9c381abbc Mon Sep 17 00:00:00 2001
+From: Jakub Zelenka <bukka@php.net>
+Date: Sun, 24 Nov 2024 20:13:47 +0100
+Subject: [PATCH 4/6] Change port for mysqli fake server auth message test
+
+(cherry picked from commit 51f5539914ae62ef8568ea1ed302dceda897c439)
+(cherry picked from commit 7e6af9c78d84d15880cfbc7867501f25ab982f5f)
+---
+ ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt
+index db54a6c0177..279aec6a2cb 100644
+--- a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt
++++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt
+@@ -6,7 +6,7 @@ mysqli
+ <?php
+ require_once 'fake_server.inc';
+
+-$port = 50001;
++$port = 33305;
+ $servername = "127.0.0.1";
+ $username = "root";
+ $password = "";
+@@ -34,5 +34,5 @@ print "done!";
+ [*] Sending - Malicious OK Auth Response [Extract heap through buffer over-read]: 0900000200000002000000fcff
+
+ Warning: mysqli::__construct(): OK packet message length is past the packet size in %s on line %d
+-Unknown error while trying to connect via tcp://127.0.0.1:50001
++Unknown error while trying to connect via tcp://127.0.0.1:33305
+ done!
+--
+2.47.0
+
+From c308c94eefdbddb041ed3cf502ef5dd6969e14f1 Mon Sep 17 00:00:00 2001
+From: Jakub Zelenka <bukka@php.net>
+Date: Sun, 24 Nov 2024 23:48:27 +0100
+Subject: [PATCH 5/6] Increase MySQLi fake server read timeout for ASAN job
+
+(cherry picked from commit eb951b3d11109aa16982a2132f8d1fd5129edc9e)
+(cherry picked from commit cae38b1c749d27dc3a65f7d65fdf238439e2676c)
+---
+ ext/mysqli/tests/fake_server.inc | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/ext/mysqli/tests/fake_server.inc b/ext/mysqli/tests/fake_server.inc
+index b02fabc584c..1127f6c00e3 100644
+--- a/ext/mysqli/tests/fake_server.inc
++++ b/ext/mysqli/tests/fake_server.inc
+@@ -552,8 +552,8 @@ class my_mysqli_fake_server_conn
+
+ public function read($bytes_len = 1024)
+ {
+- // wait 10ms to fill the buffer
+- usleep(10000);
++ // wait 20ms to fill the buffer
++ usleep(20000);
+ $data = fread($this->conn, $bytes_len);
+ if ($data) {
+ fprintf(STDERR, "[*] Received: %s\n", bin2hex($data));
+--
+2.47.0
+
+From 016ffd6131a6174fe5ca5f4af3c66ad9f59ed879 Mon Sep 17 00:00:00 2001
+From: Remi Collet <remi@remirepo.net>
+Date: Wed, 27 Nov 2024 11:17:48 +0100
+Subject: [PATCH 6/6] adapt test + NEWS
+
+---
+ NEWS | 4 +
+ ext/mysqli/tests/fake_server.inc | 99 ++++++++++---------
+ .../ghsa-h35g-vwh6-m678-auth-message.phpt | 3 +-
+ ext/mysqli/tests/mysqli_change_user_new.phpt | 3 +
+ 4 files changed, 61 insertions(+), 48 deletions(-)
+
+diff --git a/NEWS b/NEWS
+index f600d6aea65..09cf2cfa0bb 100644
+--- a/NEWS
++++ b/NEWS
+@@ -11,6 +11,10 @@ Backported from 8.1.31
+ . Fixed bug GHSA-g665-fm4p-vhff (OOB access in ldap_escape). (CVE-2024-8932)
+ (nielsdos)
+
++- MySQLnd:
++ . Fixed bug GHSA-h35g-vwh6-m678 (Leak partial content of the heap through
++ heap buffer over-read). (CVE-2024-8929) (Jakub Zelenka)
++
+ - PDO DBLIB:
+ . Fixed bug GHSA-5hqh-c84r-qjcv (Integer overflow in the dblib quoter causing
+ OOB writes). (CVE-2024-11236) (nielsdos)
+diff --git a/ext/mysqli/tests/fake_server.inc b/ext/mysqli/tests/fake_server.inc
+index 1127f6c00e3..18b77cb1a76 100644
+--- a/ext/mysqli/tests/fake_server.inc
++++ b/ext/mysqli/tests/fake_server.inc
+@@ -1,6 +1,6 @@
+ <?php
+
+-function my_mysqli_data_fields(): array
++function my_mysqli_data_fields()
+ {
+ return [
+ 'intval' => [
+@@ -107,8 +107,11 @@ function my_mysqli_data_field(string $field): array
+
+ class my_mysqli_fake_packet_item
+ {
+- public function __construct(public string|null $name, public string $value, public bool $is_hex = true)
++ public function __construct($name, string $value, bool $is_hex = true)
+ {
++ $this->name = $name;
++ $this->value = $value;
++ $this->is_hex = $is_hex;
+ }
+ }
+
+@@ -126,7 +129,7 @@ class my_mysqli_fake_packet
+ return null;
+ }
+
+- public function __set(string $name, string|my_mysqli_fake_packet_item $value)
++ public function __set(string $name, $value)
+ {
+ if ($value instanceof my_mysqli_fake_packet_item) {
+ if ($value->name === null) {
+@@ -146,7 +149,7 @@ class my_mysqli_fake_packet
+ $this->data[] = $value;
+ }
+
+- public function to_bytes(): string
++ public function to_bytes()
+ {
+ $bytes = '';
+ foreach ($this->data as $item) {
+@@ -158,7 +161,7 @@ class my_mysqli_fake_packet
+
+ class my_mysqli_fake_packet_generator
+ {
+- public static function create_packet_item(int|string $value, bool $is_hex = false, string $format = 'v'): my_mysqli_fake_packet_item
++ public static function create_packet_item($value, bool $is_hex = false, string $format = 'v')
+ {
+ if (is_string($value)) {
+ $packed_value = $value;
+@@ -168,7 +171,7 @@ class my_mysqli_fake_packet_generator
+ return new my_mysqli_fake_packet_item(null, $packed_value, $is_hex);
+ }
+
+- public function server_ok(): my_mysqli_fake_packet
++ public function server_ok()
+ {
+ $packet = new my_mysqli_fake_packet();
+ $packet->packet_length = "070000";
+@@ -181,7 +184,7 @@ class my_mysqli_fake_packet_generator
+ return $packet;
+ }
+
+- public function server_greetings(): my_mysqli_fake_packet
++ public function server_greetings()
+ {
+ $packet = new my_mysqli_fake_packet();
+ $packet->packet_length = "580000";
+@@ -204,7 +207,7 @@ class my_mysqli_fake_packet_generator
+ return $packet;
+ }
+
+- public function server_tabular_query_response(): array
++ public function server_tabular_query_response()
+ {
+ $qr1 = new my_mysqli_fake_packet();
+ $qr1->packet_length = "010000";
+@@ -239,7 +242,7 @@ class my_mysqli_fake_packet_generator
+ return [$qr1, $qr2, $qr3, $qr4, $qr5];
+ }
+
+- public function server_upsert_query_response(): array
++ public function server_upsert_query_response()
+ {
+ $qr1 = new my_mysqli_fake_packet();
+ $qr1->packet_length = "010000";
+@@ -257,7 +260,7 @@ class my_mysqli_fake_packet_generator
+ return [$qr1];
+ }
+
+- public function server_stmt_prepare_response_start($num_field): my_mysqli_fake_packet
++ public function server_stmt_prepare_response_start($num_field)
+ {
+ $pr1 = new my_mysqli_fake_packet();
+ $pr1->packet_length = "0c0000";
+@@ -272,7 +275,7 @@ class my_mysqli_fake_packet_generator
+ return $pr1;
+ }
+
+- public function server_stmt_prepare_response_end($packer_number): my_mysqli_fake_packet
++ public function server_stmt_prepare_response_end($packer_number)
+ {
+ $pr3 = new my_mysqli_fake_packet();
+ $pr3->packet_length = "050000";
+@@ -284,7 +287,7 @@ class my_mysqli_fake_packet_generator
+ return $pr3;
+ }
+
+- public function server_stmt_prepare_items_response(): array
++ public function server_stmt_prepare_items_response()
+ {
+ $pr1 = $this->server_stmt_prepare_response_start('0100');
+
+@@ -316,7 +319,7 @@ class my_mysqli_fake_packet_generator
+ return [$pr1, $pr2, $pr3];
+ }
+
+- public function server_stmt_prepare_data_response_field($packet_number, $field_name): my_mysqli_fake_packet
++ public function server_stmt_prepare_data_response_field($packet_number, $field_name)
+ {
+ if (strlen($field_name) != 6) {
+ throw new Exception("Invalid field length - only 6 is allowed");
+@@ -350,7 +353,7 @@ class my_mysqli_fake_packet_generator
+ return $pr;
+ }
+
+- public function server_stmt_prepare_data_response(string $field_name): array
++ public function server_stmt_prepare_data_response(string $field_name)
+ {
+ $pr1 = $this->server_stmt_prepare_response_start('0200');
+
+@@ -362,7 +365,7 @@ class my_mysqli_fake_packet_generator
+ return [$pr1, $pr2, $pr3, $pr4];
+ }
+
+- public function server_stmt_execute_items_response(): array
++ public function server_stmt_execute_items_response()
+ {
+ $pr1 = new my_mysqli_fake_packet();
+ $pr1->packet_length = "010000";
+@@ -413,7 +416,7 @@ class my_mysqli_fake_packet_generator
+ return [$pr1, $pr2, $pr3, $pr4, $pr5];
+ }
+
+- private function server_execute_data_response_start(string $field_name): array
++ private function server_execute_data_response_start(string $field_name)
+ {
+ $pr1 = new my_mysqli_fake_packet();
+ $pr1->packet_length = "010000";
+@@ -478,7 +481,7 @@ class my_mysqli_fake_packet_generator
+ return [$field, $pr1, $pr2, $pr3, $pr4];
+ }
+
+- private function server_execute_data_response_end(): my_mysqli_fake_packet
++ private function server_execute_data_response_end()
+ {
+ $pr6 = new my_mysqli_fake_packet();
+ $pr6->packet_length = '050000';
+@@ -490,7 +493,7 @@ class my_mysqli_fake_packet_generator
+ return $pr6;
+ }
+
+- public function server_stmt_execute_data_response(string $field_name): array
++ public function server_stmt_execute_data_response(string $field_name)
+ {
+ [$field, $pr1, $pr2, $pr3, $pr4] = $this->server_execute_data_response_start($field_name);
+
+@@ -506,7 +509,7 @@ class my_mysqli_fake_packet_generator
+ return [$pr1, $pr2, $pr3, $pr4, $pr5, $this->server_execute_data_response_end()];
+ }
+
+- public function server_query_execute_data_response(string $field_name): array
++ public function server_query_execute_data_response(string $field_name)
+ {
+ [$field, $pr1, $pr2, $pr3, $pr4] = $this->server_execute_data_response_start($field_name);
+
+@@ -537,12 +540,12 @@ class my_mysqli_fake_server_conn
+ }
+ }
+
+- public function packets_to_bytes(array $packets): string
++ public function packets_to_bytes(array $packets)
+ {
+ return implode('', array_map(fn($s) => $s->to_bytes(), $packets));
+ }
+
+- public function send($payload, $message = null): void
++ public function send($payload, $message = null)
+ {
+ if ($message) {
+ fprintf(STDERR, "[*] Sending - %s: %s\n", $message, bin2hex($payload));
+@@ -575,38 +578,38 @@ class my_mysqli_fake_server_conn
+ $this->send($this->packet_generator->server_ok()->to_bytes(), "Server OK");
+ }
+
+- public function send_server_tabular_query_response(): void
++ public function send_server_tabular_query_response()
+ {
+ $packets = $this->packet_generator->server_tabular_query_response();
+ $this->send($this->packets_to_bytes($packets), "Tabular response");
+ }
+
+- public function send_server_stmt_prepare_items_response(): void
++ public function send_server_stmt_prepare_items_response()
+ {
+ $packets = $this->packet_generator->server_stmt_prepare_items_response();
+ $this->send($this->packets_to_bytes($packets), "Stmt prepare items");
+ }
+
+
+- public function send_server_stmt_prepare_data_response(string $field_name): void
++ public function send_server_stmt_prepare_data_response(string $field_name)
+ {
+ $packets = $this->packet_generator->server_stmt_prepare_data_response($field_name);
+ $this->send($this->packets_to_bytes($packets), "Stmt prepare data $field_name");
+ }
+
+- public function send_server_stmt_execute_items_response(): void
++ public function send_server_stmt_execute_items_response()
+ {
+ $packets = $this->packet_generator->server_stmt_execute_items_response();
+ $this->send($this->packets_to_bytes($packets), "Stmt execute items");
+ }
+
+- public function send_server_stmt_execute_data_response(string $field_name): void
++ public function send_server_stmt_execute_data_response(string $field_name)
+ {
+ $packets = $this->packet_generator->server_stmt_execute_data_response($field_name);
+ $this->send($this->packets_to_bytes($packets), "Stmt execute data $field_name");
+ }
+
+- public function send_server_query_execute_data_response(string $field_name): void
++ public function send_server_query_execute_data_response(string $field_name)
+ {
+ $packets = $this->packet_generator->server_query_execute_data_response($field_name);
+ $this->send($this->packets_to_bytes($packets), "Query execute data $field_name");
+@@ -615,7 +618,11 @@ class my_mysqli_fake_server_conn
+
+ class my_mysqli_fake_server_process
+ {
+- public function __construct(private $process, private array $pipes) {}
++ public function __construct($process, array $pipes)
++ {
++ $this->process = $process;
++ $this->pipes = $pipes;
++ }
+
+ public function terminate(bool $wait = false)
+ {
+@@ -631,7 +638,7 @@ class my_mysqli_fake_server_process
+ }
+ }
+
+-function my_mysqli_test_tabular_response_def_over_read(my_mysqli_fake_server_conn $conn): void
++function my_mysqli_test_tabular_response_def_over_read(my_mysqli_fake_server_conn $conn)
+ {
+ $rh = $conn->packet_generator->server_tabular_query_response();
+
+@@ -655,7 +662,7 @@ function my_mysqli_test_tabular_response_def_over_read(my_mysqli_fake_server_con
+ $conn->read(65536);
+ }
+
+-function my_mysqli_test_upsert_response_filename_over_read(my_mysqli_fake_server_conn $conn): void
++function my_mysqli_test_upsert_response_filename_over_read(my_mysqli_fake_server_conn $conn)
+ {
+ $rh = $conn->packet_generator->server_upsert_query_response();
+
+@@ -672,7 +679,7 @@ function my_mysqli_test_upsert_response_filename_over_read(my_mysqli_fake_server
+ $conn->read(65536);
+ }
+
+-function my_mysqli_test_auth_response_message_over_read(my_mysqli_fake_server_conn $conn): void
++function my_mysqli_test_auth_response_message_over_read(my_mysqli_fake_server_conn $conn)
+ {
+ $p = $conn->packet_generator->server_ok();
+ $p->packet_length = "090000";
+@@ -684,7 +691,7 @@ function my_mysqli_test_auth_response_message_over_read(my_mysqli_fake_server_co
+ $conn->read();
+ }
+
+-function my_mysqli_test_stmt_response_row_over_read_string(my_mysqli_fake_server_conn $conn): void
++function my_mysqli_test_stmt_response_row_over_read_string(my_mysqli_fake_server_conn $conn)
+ {
+ $rh = $conn->packet_generator->server_stmt_execute_items_response();
+
+@@ -705,7 +712,7 @@ function my_mysqli_test_stmt_response_row_over_read_two_fields(
+ my_mysqli_fake_server_conn $conn,
+ string $field_name,
+ string $row_field1_len = '06'
+-): void {
++) {
+ $rh = $conn->packet_generator->server_stmt_execute_data_response($field_name);
+
+ // Set extra length to overread by two bytes
+@@ -724,47 +731,47 @@ function my_mysqli_test_stmt_response_row_over_read_two_fields(
+ $conn->read(65536);
+ }
+
+-function my_mysqli_test_stmt_response_row_over_read_int(my_mysqli_fake_server_conn $conn): void
++function my_mysqli_test_stmt_response_row_over_read_int(my_mysqli_fake_server_conn $conn)
+ {
+ my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'intval');
+ }
+
+-function my_mysqli_test_stmt_response_row_over_read_float(my_mysqli_fake_server_conn $conn): void
++function my_mysqli_test_stmt_response_row_over_read_float(my_mysqli_fake_server_conn $conn)
+ {
+ my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'fltval');
+ }
+
+-function my_mysqli_test_stmt_response_row_over_read_double(my_mysqli_fake_server_conn $conn): void
++function my_mysqli_test_stmt_response_row_over_read_double(my_mysqli_fake_server_conn $conn)
+ {
+ my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'dblval');
+ }
+
+-function my_mysqli_test_stmt_response_row_over_read_date(my_mysqli_fake_server_conn $conn): void
++function my_mysqli_test_stmt_response_row_over_read_date(my_mysqli_fake_server_conn $conn)
+ {
+ my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'datval');
+ }
+
+-function my_mysqli_test_stmt_response_row_over_read_time(my_mysqli_fake_server_conn $conn): void
++function my_mysqli_test_stmt_response_row_over_read_time(my_mysqli_fake_server_conn $conn)
+ {
+ my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'timval', '0c');
+ }
+
+-function my_mysqli_test_stmt_response_row_over_read_datetime(my_mysqli_fake_server_conn $conn): void
++function my_mysqli_test_stmt_response_row_over_read_datetime(my_mysqli_fake_server_conn $conn)
+ {
+ my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'dtival');
+ }
+
+-function my_mysqli_test_stmt_response_row_no_space(my_mysqli_fake_server_conn $conn): void
++function my_mysqli_test_stmt_response_row_no_space(my_mysqli_fake_server_conn $conn)
+ {
+ my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'strval', '09');
+ }
+
+-function my_mysqli_test_stmt_response_row_over_read_bit(my_mysqli_fake_server_conn $conn): void
++function my_mysqli_test_stmt_response_row_over_read_bit(my_mysqli_fake_server_conn $conn)
+ {
+ my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'bitval');
+ }
+
+-function my_mysqli_test_stmt_response_row_read_two_fields(my_mysqli_fake_server_conn $conn): void
++function my_mysqli_test_stmt_response_row_read_two_fields(my_mysqli_fake_server_conn $conn)
+ {
+ $conn->send_server_greetings();
+ $conn->read();
+@@ -779,7 +786,7 @@ function my_mysqli_test_stmt_response_row_read_two_fields(my_mysqli_fake_server_
+ }
+ }
+
+-function my_mysqli_test_query_response_row_length_overflow(my_mysqli_fake_server_conn $conn): void
++function my_mysqli_test_query_response_row_length_overflow(my_mysqli_fake_server_conn $conn)
+ {
+ $rh = $conn->packet_generator->server_query_execute_data_response('strval');
+
+@@ -794,7 +801,7 @@ function my_mysqli_test_query_response_row_length_overflow(my_mysqli_fake_server
+ $conn->read(65536);
+ }
+
+-function my_mysqli_test_query_response_row_read_two_fields(my_mysqli_fake_server_conn $conn): void
++function my_mysqli_test_query_response_row_read_two_fields(my_mysqli_fake_server_conn $conn)
+ {
+ $conn->send_server_greetings();
+ $conn->read();
+@@ -807,7 +814,7 @@ function my_mysqli_test_query_response_row_read_two_fields(my_mysqli_fake_server
+ }
+ }
+
+-function run_fake_server(string $test_function, $port = 33305): void
++function run_fake_server(string $test_function, $port = 33305)
+ {
+ $address = '127.0.0.1';
+
+@@ -832,7 +839,7 @@ function run_fake_server(string $test_function, $port = 33305): void
+ }
+
+
+-function run_fake_server_in_background($test_function, $port = 33305): my_mysqli_fake_server_process
++function run_fake_server_in_background($test_function, $port = 33305)
+ {
+ $command = [PHP_BINARY, '-n', __FILE__, 'mysqli_fake_server', $test_function, $port];
+
+diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt
+index 279aec6a2cb..161c9a5b8e6 100644
+--- a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt
++++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt
+@@ -34,5 +34,4 @@ print "done!";
+ [*] Sending - Malicious OK Auth Response [Extract heap through buffer over-read]: 0900000200000002000000fcff
+
+ Warning: mysqli::__construct(): OK packet message length is past the packet size in %s on line %d
+-Unknown error while trying to connect via tcp://127.0.0.1:33305
+-done!
++%A
+diff --git a/ext/mysqli/tests/mysqli_change_user_new.phpt b/ext/mysqli/tests/mysqli_change_user_new.phpt
+index 2b8993fc20d..d8fed6dfaee 100644
+--- a/ext/mysqli/tests/mysqli_change_user_new.phpt
++++ b/ext/mysqli/tests/mysqli_change_user_new.phpt
+@@ -12,6 +12,9 @@ if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket))
+
+ if (mysqli_get_server_version($link) < 50600)
+ die("SKIP For MySQL >= 5.6.0");
++
++if (mysqli_get_server_version($link) >= 10_00_00)
++ die("SKIP Not applicable for MariaDB");
+ ?>
+ --FILE--
+ <?php
+--
+2.47.0
+
diff --git a/php-cve-2024-8932.patch b/php-cve-2024-8932.patch
new file mode 100644
index 0000000..1efcff9
--- /dev/null
+++ b/php-cve-2024-8932.patch
@@ -0,0 +1,139 @@
+From 50e9e72530a4805980384b8ea6672877af816145 Mon Sep 17 00:00:00 2001
+From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
+Date: Thu, 26 Sep 2024 22:22:27 +0200
+Subject: [PATCH 4/7] Fix GHSA-g665-fm4p-vhff: OOB access in ldap_escape
+
+(cherry picked from commit f9ecf90070a11dad09ca7671a712f81cc2a7d52f)
+(cherry picked from commit 9f367d847989b339c33369737daf573e30bab5f1)
+---
+ ext/ldap/ldap.c | 21 ++++++++++++++--
+ ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt | 28 ++++++++++++++++++++++
+ ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt | 29 +++++++++++++++++++++++
+ 3 files changed, 76 insertions(+), 2 deletions(-)
+ create mode 100644 ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt
+ create mode 100644 ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt
+
+diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c
+index 72a39bd93df..75adf1b5df2 100644
+--- a/ext/ldap/ldap.c
++++ b/ext/ldap/ldap.c
+@@ -49,6 +49,7 @@
+
+ #include "ext/standard/php_string.h"
+ #include "ext/standard/info.h"
++#include "Zend/zend_exceptions.h"
+
+ #ifdef HAVE_LDAP_SASL
+ #include <sasl/sasl.h>
+@@ -3836,13 +3837,23 @@ static zend_string* php_ldap_do_escape(const zend_bool *map, const char *value,
+ zend_string *ret;
+
+ for (i = 0; i < valuelen; i++) {
+- len += (map[(unsigned char) value[i]]) ? 3 : 1;
++ size_t addend = (map[(unsigned char) value[i]]) ? 3 : 1;
++ if (len > ZSTR_MAX_LEN - addend) {
++ return NULL;
++ }
++ len += addend;
+ }
+ /* Per RFC 4514, a leading and trailing space must be escaped */
+ if ((flags & PHP_LDAP_ESCAPE_DN) && (value[0] == ' ')) {
++ if (len > ZSTR_MAX_LEN - 2) {
++ return NULL;
++ }
+ len += 2;
+ }
+ if ((flags & PHP_LDAP_ESCAPE_DN) && ((valuelen > 1) && (value[valuelen - 1] == ' '))) {
++ if (len > ZSTR_MAX_LEN - 2) {
++ return NULL;
++ }
+ len += 2;
+ }
+
+@@ -3909,7 +3920,13 @@ PHP_FUNCTION(ldap_escape)
+ php_ldap_escape_map_set_chars(map, ignores, ignoreslen, 0);
+ }
+
+- RETURN_NEW_STR(php_ldap_do_escape(map, value, valuelen, flags));
++ zend_string *result = php_ldap_do_escape(map, value, valuelen, flags);
++ if (UNEXPECTED(!result)) {
++ zend_throw_exception(NULL, "Argument #1 ($value) is too long", 0);
++ return;
++ }
++
++ RETURN_NEW_STR(result);
+ }
+
+ #ifdef STR_TRANSLATION
+diff --git a/ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt b/ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt
+new file mode 100644
+index 00000000000..734bbe91d42
+--- /dev/null
++++ b/ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt
+@@ -0,0 +1,28 @@
++--TEST--
++GHSA-g665-fm4p-vhff (OOB access in ldap_escape)
++--EXTENSIONS--
++ldap
++--INI--
++memory_limit=-1
++--SKIPIF--
++<?php
++if (PHP_INT_SIZE !== 4) die("skip only for 32-bit");
++if (getenv("SKIP_SLOW_TESTS")) die("skip slow test");
++?>
++--FILE--
++<?php
++try {
++ ldap_escape(' '.str_repeat("#", 1431655758), "", LDAP_ESCAPE_DN);
++} catch (Exception $e) {
++ echo $e->getMessage(), "\n";
++}
++
++try {
++ ldap_escape(str_repeat("#", 1431655758).' ', "", LDAP_ESCAPE_DN);
++} catch (Exception $e) {
++ echo $e->getMessage(), "\n";
++}
++?>
++--EXPECT--
++ldap_escape(): Argument #1 ($value) is too long
++ldap_escape(): Argument #1 ($value) is too long
+diff --git a/ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt b/ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt
+new file mode 100644
+index 00000000000..5c1b0fb6611
+--- /dev/null
++++ b/ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt
+@@ -0,0 +1,29 @@
++--TEST--
++GHSA-g665-fm4p-vhff (OOB access in ldap_escape)
++--EXTENSIONS--
++ldap
++--INI--
++memory_limit=-1
++--SKIPIF--
++<?php
++if (PHP_INT_SIZE !== 4) die("skip only for 32-bit");
++if (getenv("SKIP_SLOW_TESTS")) die("skip slow test");
++?>
++--FILE--
++<?php
++try {
++ ldap_escape(str_repeat("*", 1431655759), "", LDAP_ESCAPE_FILTER);
++} catch (Exception $e) {
++ echo $e->getMessage(), "\n";
++}
++
++// would allocate a string of length 2
++try {
++ ldap_escape(str_repeat("*", 1431655766), "", LDAP_ESCAPE_FILTER);
++} catch (Exception $e) {
++ echo $e->getMessage(), "\n";
++}
++?>
++--EXPECT--
++ldap_escape(): Argument #1 ($value) is too long
++ldap_escape(): Argument #1 ($value) is too long
+--
+2.47.0
+
diff --git a/php-cve-2025-1217.patch b/php-cve-2025-1217.patch
new file mode 100644
index 0000000..23d8b04
--- /dev/null
+++ b/php-cve-2025-1217.patch
@@ -0,0 +1,917 @@
+From bf4a8df2b3972118c87b05450e9062d3926f6be8 Mon Sep 17 00:00:00 2001
+From: Jakub Zelenka <bukka@php.net>
+Date: Tue, 31 Dec 2024 18:57:02 +0100
+Subject: [PATCH 01/11] Fix GHSA-ghsa-v8xr-gpvj-cx9g: http header folding
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This adds HTTP header folding support for HTTP wrapper response
+headers.
+
+Reviewed-by: Tim Düsterhus <tim@tideways-gmbh.com>
+(cherry picked from commit d20b4c97a9f883b62b65b82d939c5af9a2028ef1)
+(cherry picked from commit 4fec08542748c25573063ffc53ea89cd5de1edf0)
+---
+ ext/openssl/tests/ServerClientTestCase.inc | 65 +++-
+ ext/standard/http_fopen_wrapper.c | 347 ++++++++++++------
+ .../tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt | 49 +++
+ .../tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt | 51 +++
+ .../tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt | 49 +++
+ .../tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt | 48 +++
+ .../tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt | 48 +++
+ .../tests/http/http_response_header_05.phpt | 35 --
+ 8 files changed, 537 insertions(+), 155 deletions(-)
+ create mode 100644 ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt
+ create mode 100644 ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt
+ create mode 100644 ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt
+ create mode 100644 ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt
+ create mode 100644 ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt
+ delete mode 100644 ext/standard/tests/http/http_response_header_05.phpt
+
+diff --git a/ext/openssl/tests/ServerClientTestCase.inc b/ext/openssl/tests/ServerClientTestCase.inc
+index 753366df6f4..c74da444102 100644
+--- a/ext/openssl/tests/ServerClientTestCase.inc
++++ b/ext/openssl/tests/ServerClientTestCase.inc
+@@ -4,14 +4,19 @@ const WORKER_ARGV_VALUE = 'RUN_WORKER';
+
+ const WORKER_DEFAULT_NAME = 'server';
+
+-function phpt_notify($worker = WORKER_DEFAULT_NAME)
++function phpt_notify(string $worker = WORKER_DEFAULT_NAME, string $message = ""): void
+ {
+- ServerClientTestCase::getInstance()->notify($worker);
++ ServerClientTestCase::getInstance()->notify($worker, $message);
+ }
+
+-function phpt_wait($worker = WORKER_DEFAULT_NAME, $timeout = null)
++function phpt_wait($worker = WORKER_DEFAULT_NAME, $timeout = null): ?string
+ {
+- ServerClientTestCase::getInstance()->wait($worker, $timeout);
++ return ServerClientTestCase::getInstance()->wait($worker, $timeout);
++}
++
++function phpt_notify_server_start($server): void
++{
++ ServerClientTestCase::getInstance()->notify_server_start($server);
+ }
+
+ function phpt_has_sslv3() {
+@@ -119,43 +124,73 @@ class ServerClientTestCase
+ eval($code);
+ }
+
+- public function run($masterCode, $workerCode)
++ /**
++ * Run client and all workers
++ *
++ * @param string $clientCode The client PHP code
++ * @param string|array $workerCode
++ * @param bool $ephemeral Select whether automatic port selection and automatic awaiting is used
++ * @return void
++ * @throws Exception
++ */
++ public function run(string $clientCode, $workerCode, bool $ephemeral = true): void
+ {
+ if (!is_array($workerCode)) {
+ $workerCode = [WORKER_DEFAULT_NAME => $workerCode];
+ }
+- foreach ($workerCode as $worker => $code) {
++ reset($workerCode);
++ $code = current($workerCode);
++ $worker = key($workerCode);
++ while ($worker != null) {
+ $this->spawnWorkerProcess($worker, $this->stripPhpTagsFromCode($code));
++ $code = next($workerCode);
++ if ($ephemeral) {
++ $addr = trim($this->wait($worker));
++ if (empty($addr)) {
++ throw new \Exception("Failed server start");
++ }
++ if ($code === false) {
++ $clientCode = preg_replace('/{{\s*ADDR\s*}}/', $addr, $clientCode);
++ } else {
++ $code = preg_replace('/{{\s*ADDR\s*}}/', $addr, $code);
++ }
++ }
++ $worker = key($workerCode);
+ }
+- eval($this->stripPhpTagsFromCode($masterCode));
++
++ eval($this->stripPhpTagsFromCode($clientCode));
+ foreach ($workerCode as $worker => $code) {
+ $this->cleanupWorkerProcess($worker);
+ }
+ }
+
+- public function wait($worker, $timeout = null)
++ public function wait($worker, $timeout = null): ?string
+ {
+ $handle = $this->isWorker ? STDIN : $this->workerStdOut[$worker];
+ if ($timeout === null) {
+- fgets($handle);
+- return true;
++ return fgets($handle);
+ }
+
+ stream_set_blocking($handle, false);
+ $read = [$handle];
+ $result = stream_select($read, $write, $except, $timeout);
+ if (!$result) {
+- return false;
++ return null;
+ }
+
+- fgets($handle);
++ $result = fgets($handle);
+ stream_set_blocking($handle, true);
+- return true;
++ return $result;
++ }
++
++ public function notify(string $worker, string $message = ""): void
++ {
++ fwrite($this->isWorker ? STDOUT : $this->workerStdIn[$worker], "$message\n");
+ }
+
+- public function notify($worker)
++ public function notify_server_start($server): void
+ {
+- fwrite($this->isWorker ? STDOUT : $this->workerStdIn[$worker], "\n");
++ echo stream_socket_get_name($server, false) . "\n";
+ }
+ }
+
+diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
+index aeeb438f0f9..08386cfafcd 100644
+--- a/ext/standard/http_fopen_wrapper.c
++++ b/ext/standard/http_fopen_wrapper.c
+@@ -116,6 +116,172 @@ static zend_bool check_has_header(const char *headers, const char *header) {
+ return 0;
+ }
+
++typedef struct _php_stream_http_response_header_info {
++ php_stream_filter *transfer_encoding;
++ size_t file_size;
++ zend_bool follow_location;
++ char location[HTTP_HEADER_BLOCK_SIZE];
++} php_stream_http_response_header_info;
++
++static void php_stream_http_response_header_info_init(
++ php_stream_http_response_header_info *header_info)
++{
++ header_info->transfer_encoding = NULL;
++ header_info->file_size = 0;
++ header_info->follow_location = 1;
++ header_info->location[0] = '\0';
++}
++
++/* Trim white spaces from response header line and update its length */
++static zend_bool php_stream_http_response_header_trim(char *http_header_line,
++ size_t *http_header_line_length)
++{
++ char *http_header_line_end = http_header_line + *http_header_line_length - 1;
++ while (http_header_line_end >= http_header_line &&
++ (*http_header_line_end == '\n' || *http_header_line_end == '\r')) {
++ http_header_line_end--;
++ }
++
++ /* The primary definition of an HTTP header in RFC 7230 states:
++ * > Each header field consists of a case-insensitive field name followed
++ * > by a colon (":"), optional leading whitespace, the field value, and
++ * > optional trailing whitespace. */
++
++ /* Strip trailing whitespace */
++ zend_bool space_trim = (*http_header_line_end == ' ' || *http_header_line_end == '\t');
++ if (space_trim) {
++ do {
++ http_header_line_end--;
++ } while (http_header_line_end >= http_header_line &&
++ (*http_header_line_end == ' ' || *http_header_line_end == '\t'));
++ }
++ http_header_line_end++;
++ *http_header_line_end = '\0';
++ *http_header_line_length = http_header_line_end - http_header_line;
++
++ return space_trim;
++}
++
++/* Process folding headers of the current line and if there are none, parse last full response
++ * header line. It returns NULL if the last header is finished, otherwise it returns updated
++ * last header line. */
++static zend_string *php_stream_http_response_headers_parse(php_stream *stream,
++ php_stream_context *context, int options, zend_string *last_header_line_str,
++ char *header_line, size_t *header_line_length, int response_code,
++ zval *response_header, php_stream_http_response_header_info *header_info)
++{
++ char *last_header_line = ZSTR_VAL(last_header_line_str);
++ size_t last_header_line_length = ZSTR_LEN(last_header_line_str);
++ char *last_header_line_end = ZSTR_VAL(last_header_line_str) + ZSTR_LEN(last_header_line_str) - 1;
++
++ /* Process non empty header line. */
++ if (header_line && (*header_line != '\n' && *header_line != '\r')) {
++ /* Removing trailing white spaces. */
++ if (php_stream_http_response_header_trim(header_line, header_line_length) &&
++ *header_line_length == 0) {
++ /* Only spaces so treat as an empty folding header. */
++ return last_header_line_str;
++ }
++
++ /* Process folding headers if starting with a space or a tab. */
++ if (header_line && (*header_line == ' ' || *header_line == '\t')) {
++ char *http_folded_header_line = header_line;
++ size_t http_folded_header_line_length = *header_line_length;
++ /* Remove the leading white spaces. */
++ while (*http_folded_header_line == ' ' || *http_folded_header_line == '\t') {
++ http_folded_header_line++;
++ http_folded_header_line_length--;
++ }
++ /* It has to have some characters because it would get returned after the call
++ * php_stream_http_response_header_trim above. */
++ ZEND_ASSERT(http_folded_header_line_length > 0);
++ /* Concatenate last header line, space and current header line. */
++ zend_string *extended_header_str = zend_string_alloc(last_header_line_length + 1 + http_folded_header_line_length, 0);
++ memcpy(ZSTR_VAL(extended_header_str), last_header_line, last_header_line_length);
++ ZSTR_VAL(extended_header_str)[last_header_line_length] = ' ';
++ memcpy(ZSTR_VAL(extended_header_str) + last_header_line_length + 1, http_folded_header_line, http_folded_header_line_length);
++ ZSTR_VAL(extended_header_str)[ZSTR_LEN(extended_header_str)] = 0;
++ zend_string_efree(last_header_line_str);
++ last_header_line_str = extended_header_str;
++ /* Return new header line. */
++ return last_header_line_str;
++ }
++ }
++
++ /* Find header separator position. */
++ char *last_header_value = memchr(last_header_line, ':', last_header_line_length);
++ if (last_header_value) {
++ last_header_value++; /* Skip ':'. */
++
++ /* Strip leading whitespace. */
++ while (last_header_value < last_header_line_end
++ && (*last_header_value == ' ' || *last_header_value == '\t')) {
++ last_header_value++;
++ }
++ } else {
++ /* There is no colon. Set the value to the end of the header line, which is effectively
++ * an empty string. */
++ last_header_value = last_header_line_end;
++ }
++
++ zend_bool store_header = 1;
++ zval *tmpzval = NULL;
++
++ if (!strncasecmp(last_header_line, "Location:", sizeof("Location:")-1)) {
++ /* Check if the location should be followed. */
++ if (context && (tmpzval = php_stream_context_get_option(context, "http", "follow_location")) != NULL) {
++ header_info->follow_location = zval_is_true(tmpzval);
++ } else if (!((response_code >= 300 && response_code < 304)
++ || 307 == response_code || 308 == response_code)) {
++ /* The redirection should not be automatic if follow_location is not set and
++ * response_code not in (300, 301, 302, 303 and 307)
++ * see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.1
++ * RFC 7238 defines 308: http://tools.ietf.org/html/rfc7238 */
++ header_info->follow_location = 0;
++ }
++ strlcpy(header_info->location, last_header_value, sizeof(header_info->location));
++ } else if (!strncasecmp(last_header_line, "Content-Type:", sizeof("Content-Type:")-1)) {
++ php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, last_header_value, 0);
++ } else if (!strncasecmp(last_header_line, "Content-Length:", sizeof("Content-Length:")-1)) {
++ header_info->file_size = atoi(last_header_value);
++ php_stream_notify_file_size(context, header_info->file_size, last_header_line, 0);
++ } else if (
++ !strncasecmp(last_header_line, "Transfer-Encoding:", sizeof("Transfer-Encoding:")-1)
++ && !strncasecmp(last_header_value, "Chunked", sizeof("Chunked")-1)
++ ) {
++ /* Create filter to decode response body. */
++ if (!(options & STREAM_ONLY_GET_HEADERS)) {
++ zend_long decode = 1;
++
++ if (context && (tmpzval = php_stream_context_get_option(context, "http", "auto_decode")) != NULL) {
++ decode = zend_is_true(tmpzval);
++ }
++ if (decode) {
++ if (header_info->transfer_encoding != NULL) {
++ /* Prevent a memory leak in case there are more transfer-encoding headers. */
++ php_stream_filter_free(header_info->transfer_encoding);
++ }
++ header_info->transfer_encoding = php_stream_filter_create(
++ "dechunk", NULL, php_stream_is_persistent(stream));
++ if (header_info->transfer_encoding != NULL) {
++ /* Do not store transfer-encoding header. */
++ store_header = 0;
++ }
++ }
++ }
++ }
++
++ if (store_header) {
++ zval http_header;
++ ZVAL_NEW_STR(&http_header, last_header_line_str);
++ zend_hash_next_index_insert(Z_ARRVAL_P(response_header), &http_header);
++ } else {
++ zend_string_efree(last_header_line_str);
++ }
++
++ return NULL;
++}
++
+ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
+ const char *path, const char *mode, int options, zend_string **opened_path,
+ php_stream_context *context, int redirect_max, int flags,
+@@ -128,11 +294,12 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
+ zend_string *tmp = NULL;
+ char *ua_str = NULL;
+ zval *ua_zval = NULL, *tmpzval = NULL, ssl_proxy_peer_name;
+- char location[HTTP_HEADER_BLOCK_SIZE];
+ int reqok = 0;
+ char *http_header_line = NULL;
++ zend_string *last_header_line_str = NULL;
++ php_stream_http_response_header_info header_info;
+ char tmp_line[128];
+- size_t chunk_size = 0, file_size = 0;
++ size_t chunk_size = 0;
+ int eol_detect = 0;
+ char *transport_string;
+ zend_string *errstr = NULL;
+@@ -143,8 +310,6 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
+ char *user_headers = NULL;
+ int header_init = ((flags & HTTP_WRAPPER_HEADER_INIT) != 0);
+ int redirected = ((flags & HTTP_WRAPPER_REDIRECTED) != 0);
+- zend_bool follow_location = 1;
+- php_stream_filter *transfer_encoding = NULL;
+ int response_code;
+ smart_str req_buf = {0};
+ zend_bool custom_request_method;
+@@ -657,8 +822,6 @@ finish:
+ /* send it */
+ php_stream_write(stream, ZSTR_VAL(req_buf.s), ZSTR_LEN(req_buf.s));
+
+- location[0] = '\0';
+-
+ if (Z_ISUNDEF_P(response_header)) {
+ array_init(response_header);
+ }
+@@ -736,125 +899,101 @@ finish:
+ }
+ }
+
+- /* read past HTTP headers */
++ php_stream_http_response_header_info_init(&header_info);
+
++ /* read past HTTP headers */
+ while (!php_stream_eof(stream)) {
+ size_t http_header_line_length;
+
+ if (http_header_line != NULL) {
+ efree(http_header_line);
+ }
+- if ((http_header_line = php_stream_get_line(stream, NULL, 0, &http_header_line_length)) && *http_header_line != '\n' && *http_header_line != '\r') {
+- char *e = http_header_line + http_header_line_length - 1;
+- char *http_header_value;
+-
+- while (e >= http_header_line && (*e == '\n' || *e == '\r')) {
+- e--;
+- }
+-
+- /* The primary definition of an HTTP header in RFC 7230 states:
+- * > Each header field consists of a case-insensitive field name followed
+- * > by a colon (":"), optional leading whitespace, the field value, and
+- * > optional trailing whitespace. */
+-
+- /* Strip trailing whitespace */
+- while (e >= http_header_line && (*e == ' ' || *e == '\t')) {
+- e--;
+- }
+-
+- /* Terminate header line */
+- e++;
+- *e = '\0';
+- http_header_line_length = e - http_header_line;
+-
+- http_header_value = memchr(http_header_line, ':', http_header_line_length);
+- if (http_header_value) {
+- http_header_value++; /* Skip ':' */
+-
+- /* Strip leading whitespace */
+- while (http_header_value < e
+- && (*http_header_value == ' ' || *http_header_value == '\t')) {
+- http_header_value++;
++ if ((http_header_line = php_stream_get_line(stream, NULL, 0, &http_header_line_length))) {
++ zend_bool last_line;
++ if (*http_header_line == '\r') {
++ if (http_header_line[1] != '\n') {
++ php_stream_close(stream);
++ stream = NULL;
++ php_stream_wrapper_log_error(wrapper, options,
++ "HTTP invalid header name (cannot start with CR character)!");
++ goto out;
+ }
++ last_line = 1;
++ } else if (*http_header_line == '\n') {
++ last_line = 1;
+ } else {
+- /* There is no colon. Set the value to the end of the header line, which is
+- * effectively an empty string. */
+- http_header_value = e;
++ last_line = 0;
+ }
+-
+- if (!strncasecmp(http_header_line, "Location:", sizeof("Location:")-1)) {
+- if (context && (tmpzval = php_stream_context_get_option(context, "http", "follow_location")) != NULL) {
+- follow_location = zval_is_true(tmpzval);
+- } else if (!((response_code >= 300 && response_code < 304)
+- || 307 == response_code || 308 == response_code)) {
+- /* we shouldn't redirect automatically
+- if follow_location isn't set and response_code not in (300, 301, 302, 303 and 307)
+- see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.1
+- RFC 7238 defines 308: http://tools.ietf.org/html/rfc7238 */
+- follow_location = 0;
++
++ if (last_header_line_str != NULL) {
++ /* Parse last header line. */
++ last_header_line_str = php_stream_http_response_headers_parse(stream, context,
++ options, last_header_line_str, http_header_line, &http_header_line_length,
++ response_code, response_header, &header_info);
++ if (last_header_line_str != NULL) {
++ /* Folding header present so continue. */
++ continue;
+ }
+- strlcpy(location, http_header_value, sizeof(location));
+- } else if (!strncasecmp(http_header_line, "Content-Type:", sizeof("Content-Type:")-1)) {
+- php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, http_header_value, 0);
+- } else if (!strncasecmp(http_header_line, "Content-Length:", sizeof("Content-Length:")-1)) {
+- file_size = atoi(http_header_value);
+- php_stream_notify_file_size(context, file_size, http_header_line, 0);
+- } else if (
+- !strncasecmp(http_header_line, "Transfer-Encoding:", sizeof("Transfer-Encoding:")-1)
+- && !strncasecmp(http_header_value, "Chunked", sizeof("Chunked")-1)
+- ) {
+-
+- /* create filter to decode response body */
+- if (!(options & STREAM_ONLY_GET_HEADERS)) {
+- zend_long decode = 1;
+-
+- if (context && (tmpzval = php_stream_context_get_option(context, "http", "auto_decode")) != NULL) {
+- decode = zend_is_true(tmpzval);
+- }
+- if (decode) {
+- transfer_encoding = php_stream_filter_create("dechunk", NULL, php_stream_is_persistent(stream));
+- if (transfer_encoding) {
+- /* don't store transfer-encodeing header */
+- continue;
+- }
+- }
++ } else if (!last_line) {
++ /* The first line cannot start with spaces. */
++ if (*http_header_line == ' ' || *http_header_line == '\t') {
++ php_stream_close(stream);
++ stream = NULL;
++ php_stream_wrapper_log_error(wrapper, options,
++ "HTTP invalid response format (folding header at the start)!");
++ goto out;
+ }
++ /* Trim the first line if it is not the last line. */
++ php_stream_http_response_header_trim(http_header_line, &http_header_line_length);
+ }
+-
+- {
+- zval http_header;
+- ZVAL_STRINGL(&http_header, http_header_line, http_header_line_length);
+- zend_hash_next_index_insert(Z_ARRVAL_P(response_header), &http_header);
++ if (last_line) {
++ /* For the last line the last header line must be NULL. */
++ ZEND_ASSERT(last_header_line_str == NULL);
++ break;
+ }
++ /* Save current line as the last line so it gets parsed in the next round. */
++ last_header_line_str = zend_string_init(http_header_line, http_header_line_length, 0);
+ } else {
+ break;
+ }
+ }
+
+- if (!reqok || (location[0] != '\0' && follow_location)) {
+- if (!follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) {
++ /* If the stream was closed early, we still want to process the last line to keep BC. */
++ if (last_header_line_str != NULL) {
++ php_stream_http_response_headers_parse(stream, context, options, last_header_line_str,
++ NULL, NULL, response_code, response_header, &header_info);
++ }
++
++ if (!reqok || (header_info.location[0] != '\0' && header_info.follow_location)) {
++ if (!header_info.follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) {
+ goto out;
+ }
+
+- if (location[0] != '\0')
+- php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, location, 0);
++ if (header_info.location[0] != '\0')
++ php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, header_info.location, 0);
+
+ php_stream_close(stream);
+ stream = NULL;
+
+- if (location[0] != '\0') {
++ if (header_info.transfer_encoding) {
++ php_stream_filter_free(header_info.transfer_encoding);
++ header_info.transfer_encoding = NULL;
++ }
++
++ if (header_info.location[0] != '\0') {
+
+ char new_path[HTTP_HEADER_BLOCK_SIZE];
+ char loc_path[HTTP_HEADER_BLOCK_SIZE];
+
+ *new_path='\0';
+- if (strlen(location)<8 || (strncasecmp(location, "http://", sizeof("http://")-1) &&
+- strncasecmp(location, "https://", sizeof("https://")-1) &&
+- strncasecmp(location, "ftp://", sizeof("ftp://")-1) &&
+- strncasecmp(location, "ftps://", sizeof("ftps://")-1)))
++ if (strlen(header_info.location) < 8 ||
++ (strncasecmp(header_info.location, "http://", sizeof("http://")-1) &&
++ strncasecmp(header_info.location, "https://", sizeof("https://")-1) &&
++ strncasecmp(header_info.location, "ftp://", sizeof("ftp://")-1) &&
++ strncasecmp(header_info.location, "ftps://", sizeof("ftps://")-1)))
+ {
+- if (*location != '/') {
+- if (*(location+1) != '\0' && resource->path) {
++ if (*header_info.location != '/') {
++ if (*(header_info.location+1) != '\0' && resource->path) {
+ char *s = strrchr(ZSTR_VAL(resource->path), '/');
+ if (!s) {
+ s = ZSTR_VAL(resource->path);
+@@ -870,15 +1009,17 @@ finish:
+ if (resource->path &&
+ ZSTR_VAL(resource->path)[0] == '/' &&
+ ZSTR_VAL(resource->path)[1] == '\0') {
+- snprintf(loc_path, sizeof(loc_path) - 1, "%s%s", ZSTR_VAL(resource->path), location);
++ snprintf(loc_path, sizeof(loc_path) - 1, "%s%s",
++ ZSTR_VAL(resource->path), header_info.location);
+ } else {
+- snprintf(loc_path, sizeof(loc_path) - 1, "%s/%s", ZSTR_VAL(resource->path), location);
++ snprintf(loc_path, sizeof(loc_path) - 1, "%s/%s",
++ ZSTR_VAL(resource->path), header_info.location);
+ }
+ } else {
+- snprintf(loc_path, sizeof(loc_path) - 1, "/%s", location);
++ snprintf(loc_path, sizeof(loc_path) - 1, "/%s", header_info.location);
+ }
+ } else {
+- strlcpy(loc_path, location, sizeof(loc_path));
++ strlcpy(loc_path, header_info.location, sizeof(loc_path));
+ }
+ if ((use_ssl && resource->port != 443) || (!use_ssl && resource->port != 80)) {
+ snprintf(new_path, sizeof(new_path) - 1, "%s://%s:%d%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), resource->port, loc_path);
+@@ -886,7 +1027,7 @@ finish:
+ snprintf(new_path, sizeof(new_path) - 1, "%s://%s%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), loc_path);
+ }
+ } else {
+- strlcpy(new_path, location, sizeof(new_path));
++ strlcpy(new_path, header_info.location, sizeof(new_path));
+ }
+
+ php_url_free(resource);
+@@ -939,7 +1080,7 @@ out:
+ if (header_init) {
+ ZVAL_COPY(&stream->wrapperdata, response_header);
+ }
+- php_stream_notify_progress_init(context, 0, file_size);
++ php_stream_notify_progress_init(context, 0, header_info.file_size);
+
+ /* Restore original chunk size now that we're done with headers */
+ if (options & STREAM_WILL_CAST)
+@@ -955,12 +1096,8 @@ out:
+ /* restore mode */
+ strlcpy(stream->mode, mode, sizeof(stream->mode));
+
+- if (transfer_encoding) {
+- php_stream_filter_append(&stream->readfilters, transfer_encoding);
+- }
+- } else {
+- if (transfer_encoding) {
+- php_stream_filter_free(transfer_encoding);
++ if (header_info.transfer_encoding) {
++ php_stream_filter_append(&stream->readfilters, header_info.transfer_encoding);
+ }
+ }
+
+diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt
+new file mode 100644
+index 00000000000..64904bfcd1d
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt
+@@ -0,0 +1,49 @@
++--TEST--
++GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (single)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++ $ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
++
++ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html;\r\n charset=utf-8\r\n\r\nbody\r\n");
++CODE;
++
++$clientCode = <<<'CODE'
++ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
++ switch($notification_code) {
++ case STREAM_NOTIFY_MIME_TYPE_IS:
++ echo "Found the mime-type: ", $message, PHP_EOL;
++ break;
++ }
++ }
++
++ $ctx = stream_context_create();
++ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
++ var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx)));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++Found the mime-type: text/html; charset=utf-8
++string(4) "body"
++array(2) {
++ [0]=>
++ string(15) "HTTP/1.0 200 Ok"
++ [1]=>
++ string(38) "Content-Type: text/html; charset=utf-8"
++}
+diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt
+new file mode 100644
+index 00000000000..a6d9d00fd58
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt
+@@ -0,0 +1,51 @@
++--TEST--
++GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (multiple)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++ $ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
++
++ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html;\r\nCustom-Header: somevalue;\r\n param1=value1; \r\n param2=value2\r\n\r\nbody\r\n");
++CODE;
++
++$clientCode = <<<'CODE'
++ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
++ switch($notification_code) {
++ case STREAM_NOTIFY_MIME_TYPE_IS:
++ echo "Found the mime-type: ", $message, PHP_EOL;
++ break;
++ }
++ }
++
++ $ctx = stream_context_create();
++ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
++ var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx)));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++Found the mime-type: text/html;
++string(4) "body"
++array(3) {
++ [0]=>
++ string(15) "HTTP/1.0 200 Ok"
++ [1]=>
++ string(24) "Content-Type: text/html;"
++ [2]=>
++ string(54) "Custom-Header: somevalue; param1=value1; param2=value2"
++}
+diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt
+new file mode 100644
+index 00000000000..4eff7fc63f3
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt
+@@ -0,0 +1,49 @@
++--TEST--
++GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (empty)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++ $ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
++
++ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html;\r\n \r\n charset=utf-8\r\n\r\nbody\r\n");
++CODE;
++
++$clientCode = <<<'CODE'
++ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
++ switch($notification_code) {
++ case STREAM_NOTIFY_MIME_TYPE_IS:
++ echo "Found the mime-type: ", $message, PHP_EOL;
++ break;
++ }
++ }
++
++ $ctx = stream_context_create();
++ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
++ var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx)));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++Found the mime-type: text/html; charset=utf-8
++string(4) "body"
++array(2) {
++ [0]=>
++ string(15) "HTTP/1.0 200 Ok"
++ [1]=>
++ string(38) "Content-Type: text/html; charset=utf-8"
++}
+diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt
+new file mode 100644
+index 00000000000..71aed2fa2e8
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt
+@@ -0,0 +1,48 @@
++--TEST--
++GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (first line)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++ $ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
++
++ fwrite($conn, "HTTP/1.0 200 Ok\r\n Content-Type: text/html;\r\n \r\n charset=utf-8\r\n\r\nbody\r\n");
++CODE;
++
++$clientCode = <<<'CODE'
++ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
++ switch($notification_code) {
++ case STREAM_NOTIFY_MIME_TYPE_IS:
++ echo "Found the mime-type: ", $message, PHP_EOL;
++ break;
++ }
++ }
++
++ $ctx = stream_context_create();
++ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
++ var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++
++Warning: file_get_contents(http://127.0.0.1:%d): failed to open stream: HTTP invalid response format (folding header at the start)! in %s
++bool(false)
++array(1) {
++ [0]=>
++ string(15) "HTTP/1.0 200 Ok"
++}
+diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt
+new file mode 100644
+index 00000000000..49d845d84b4
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt
+@@ -0,0 +1,48 @@
++--TEST--
++GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (CR before header name)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++ $ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
++
++ fwrite($conn, "HTTP/1.0 200 Ok\r\n\rIgnored: ignored\r\n\r\nbody\r\n");
++CODE;
++
++$clientCode = <<<'CODE'
++ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
++ switch($notification_code) {
++ case STREAM_NOTIFY_MIME_TYPE_IS:
++ echo "Found the mime-type: ", $message, PHP_EOL;
++ break;
++ }
++ }
++
++ $ctx = stream_context_create();
++ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
++ var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++
++Warning: file_get_contents(http://127.0.0.1:%d): failed to open stream: HTTP invalid header name (cannot start with CR character)! in %s
++bool(false)
++array(1) {
++ [0]=>
++ string(15) "HTTP/1.0 200 Ok"
++}
+diff --git a/ext/standard/tests/http/http_response_header_05.phpt b/ext/standard/tests/http/http_response_header_05.phpt
+deleted file mode 100644
+index dbdd7b8b1a0..00000000000
+--- a/ext/standard/tests/http/http_response_header_05.phpt
++++ /dev/null
+@@ -1,35 +0,0 @@
+---TEST--
+-$http_reponse_header (whitespace-only "header")
+---SKIPIF--
+-<?php require 'server.inc'; http_server_skipif('tcp://127.0.0.1:22350'); ?>
+---INI--
+-allow_url_fopen=1
+---FILE--
+-<?php
+-require 'server.inc';
+-
+-$responses = array(
+- "data://text/plain,HTTP/1.0 200 Ok\r\n \r\n\r\nBody",
+-);
+-
+-$pid = http_server("tcp://127.0.0.1:22350", $responses, $output);
+-
+-function test() {
+- $f = file_get_contents('http://127.0.0.1:22350/');
+- var_dump($f);
+- var_dump($http_response_header);
+-}
+-test();
+-
+-http_server_kill($pid);
+-?>
+-==DONE==
+---EXPECT--
+-string(4) "Body"
+-array(2) {
+- [0]=>
+- string(15) "HTTP/1.0 200 Ok"
+- [1]=>
+- string(0) ""
+-}
+-==DONE==
+--
+2.48.1
+
diff --git a/php-cve-2025-1219.patch b/php-cve-2025-1219.patch
new file mode 100644
index 0000000..5d164b6
--- /dev/null
+++ b/php-cve-2025-1219.patch
@@ -0,0 +1,1906 @@
+From 8a2195d2c64e0323d05656238c5c72414e8ad340 Mon Sep 17 00:00:00 2001
+From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
+Date: Sat, 29 Apr 2023 21:07:50 +0200
+Subject: [PATCH 05/11] Fix GH-11160: Few tests failed building with new libxml
+ 2.11.0
+
+It's possible to categorise the failures into 2 categories:
+ - Changed error message. In this case we either duplicate the test and
+ modify the error message. Or if the change in error message is
+ small, we use the EXPECTF matchers to make the test compatible with both
+ old and new versions of libxml2.
+ - Missing warnings. This is caused by a change in libxml2 where the
+ parser started using SAX APIs internally [1]. In this case the
+ error_type passed to php_libxml_internal_error_handler() changed from
+ PHP_LIBXML_ERROR to PHP_LIBXML_CTX_WARNING because it internally
+ started to use the SAX handlers instead of the generic handlers.
+ However, for the SAX handlers the current input stack is empty, so
+ nothing is actually printed. I fixed this by falling back to a
+ regular warning without a filename & line number reference, which
+ mimicks the old behaviour. Furthermore, this change now also shows
+ an additional warning in a test which was previously hidden.
+
+[1] https://gitlab.gnome.org/GNOME/libxml2/-/commit/9a82b94a94bd310db426edd453b0f38c6c8f69f5
+
+Closes GH-11162.
+
+(cherry picked from commit 7c0dfc5cf58d3c445b935fa14ea8f5f13568c419)
+(cherry picked from commit 78ae0886bd1a3e42c53c9ba65764b6e6357640b5)
+---
+ .../DOMDocument_loadXML_error2_gte2_11.phpt | 34 +++
+ ...> DOMDocument_loadXML_error2_pre2_11.phpt} | 7 +-
+ .../DOMDocument_load_error2_gte2_11.phpt | 34 +++
+ ...t => DOMDocument_load_error2_pre2_11.phpt} | 7 +-
+ ext/libxml/libxml.c | 2 +
+ ext/libxml/tests/bug61367-read_2.phpt | 2 +-
+ .../tests/libxml_disable_entity_loader_2.phpt | 2 +-
+ ...xml_set_external_entity_loader_error1.phpt | 2 +
+ ...set_external_entity_loader_variation2.phpt | 2 +
+ ext/openssl/tests/ServerClientTestCase.inc | 65 ++----
+ .../tests/http/ServerClientTestCase.inc | 199 ++++++++++++++++++
+ .../tests/http/ghsa-52jp-hrpf-2jff-001.phpt | 2 +-
+ .../tests/http/ghsa-52jp-hrpf-2jff-002.phpt | 2 +-
+ .../tests/http/ghsa-hgf5-96fm-v528-001.phpt | 2 +-
+ .../tests/http/ghsa-hgf5-96fm-v528-002.phpt | 2 +-
+ .../tests/http/ghsa-hgf5-96fm-v528-003.phpt | 2 +-
+ .../tests/http/ghsa-pcmh-g36c-qc44-001.phpt | 2 +-
+ .../tests/http/ghsa-pcmh-g36c-qc44-002.phpt | 2 +-
+ .../tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt | 2 +-
+ .../tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt | 2 +-
+ .../tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt | 2 +-
+ .../tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt | 2 +-
+ .../tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt | 2 +-
+ ext/xml/tests/bug26614_libxml_gte2_11.phpt | 95 +++++++++
+ ...bxml.phpt => bug26614_libxml_pre2_11.phpt} | 1 +
+ 25 files changed, 408 insertions(+), 68 deletions(-)
+ create mode 100644 ext/dom/tests/DOMDocument_loadXML_error2_gte2_11.phpt
+ rename ext/dom/tests/{DOMDocument_loadXML_error2.phpt => DOMDocument_loadXML_error2_pre2_11.phpt} (89%)
+ create mode 100644 ext/dom/tests/DOMDocument_load_error2_gte2_11.phpt
+ rename ext/dom/tests/{DOMDocument_load_error2.phpt => DOMDocument_load_error2_pre2_11.phpt} (89%)
+ create mode 100644 ext/standard/tests/http/ServerClientTestCase.inc
+ create mode 100644 ext/xml/tests/bug26614_libxml_gte2_11.phpt
+ rename ext/xml/tests/{bug26614_libxml.phpt => bug26614_libxml_pre2_11.phpt} (96%)
+
+diff --git a/ext/dom/tests/DOMDocument_loadXML_error2_gte2_11.phpt b/ext/dom/tests/DOMDocument_loadXML_error2_gte2_11.phpt
+new file mode 100644
+index 00000000000..ff5ceb3fbed
+--- /dev/null
++++ b/ext/dom/tests/DOMDocument_loadXML_error2_gte2_11.phpt
+@@ -0,0 +1,34 @@
++--TEST--
++Test DOMDocument::loadXML() detects not-well formed XML
++--SKIPIF--
++<?php
++if (LIBXML_VERSION < 21100) die('skip libxml2 test variant for version >= 2.11');
++?>
++--DESCRIPTION--
++This test verifies the method detects attributes values not closed between " or '
++Environment variables used in the test:
++- XML_FILE: the xml file to load
++- LOAD_OPTIONS: the second parameter to pass to the method
++- EXPECTED_RESULT: the expected result
++--CREDITS--
++Antonio Diaz Ruiz <dejalatele@gmail.com>
++--INI--
++assert.bail=true
++--EXTENSIONS--
++dom
++--ENV--
++XML_FILE=/not_well_formed2.xml
++LOAD_OPTIONS=0
++EXPECTED_RESULT=0
++--FILE_EXTERNAL--
++domdocumentloadxml_test_method.inc
++--EXPECTF--
++Warning: DOMDocument::loadXML(): AttValue: " or ' expected in Entity, line: 4 in %s on line %d
++
++Warning: DOMDocument::loadXML(): internal error: xmlParseStartTag: problem parsing attributes in Entity, line: 4 in %s on line %d
++
++Warning: DOMDocument::loadXML(): Couldn't find end of Start Tag book line 4 in Entity, line: 4 in %s on line %d
++
++Warning: DOMDocument::loadXML(): Opening and ending tag mismatch: books line 3 and book in Entity, line: 7 in %s on line %d
++
++Warning: DOMDocument::loadXML(): Extra content at the end of the document in Entity, line: 8 in %s on line %d
+diff --git a/ext/dom/tests/DOMDocument_loadXML_error2.phpt b/ext/dom/tests/DOMDocument_loadXML_error2_pre2_11.phpt
+similarity index 89%
+rename from ext/dom/tests/DOMDocument_loadXML_error2.phpt
+rename to ext/dom/tests/DOMDocument_loadXML_error2_pre2_11.phpt
+index 6d56a317ed7..7e10771fdb7 100644
+--- a/ext/dom/tests/DOMDocument_loadXML_error2.phpt
++++ b/ext/dom/tests/DOMDocument_loadXML_error2_pre2_11.phpt
+@@ -1,5 +1,10 @@
+ --TEST--
+ Test DOMDocument::loadXML() detects not-well formed XML
++--SKIPIF--
++<?php
++include('skipif.inc');
++if (LIBXML_VERSION >= 21100) die('skip libxml2 test variant for version < 2.11');
++?>
+ --DESCRIPTION--
+ This test verifies the method detects attributes values not closed between " or '
+ Environment variables used in the test:
+@@ -10,8 +15,6 @@ Environment variables used in the test:
+ Antonio Diaz Ruiz <dejalatele@gmail.com>
+ --INI--
+ assert.bail=true
+---SKIPIF--
+-<?php include('skipif.inc'); ?>
+ --ENV--
+ XML_FILE=/not_well_formed2.xml
+ LOAD_OPTIONS=0
+diff --git a/ext/dom/tests/DOMDocument_load_error2_gte2_11.phpt b/ext/dom/tests/DOMDocument_load_error2_gte2_11.phpt
+new file mode 100644
+index 00000000000..32b6bf16114
+--- /dev/null
++++ b/ext/dom/tests/DOMDocument_load_error2_gte2_11.phpt
+@@ -0,0 +1,34 @@
++--TEST--
++Test DOMDocument::load() detects not-well formed
++--SKIPIF--
++<?php
++if (LIBXML_VERSION < 21100) die('skip libxml2 test variant for version >= 2.11');
++?>
++--DESCRIPTION--
++This test verifies the method detects attributes values not closed between " or '
++Environment variables used in the test:
++- XML_FILE: the xml file to load
++- LOAD_OPTIONS: the second parameter to pass to the method
++- EXPECTED_RESULT: the expected result
++--CREDITS--
++Antonio Diaz Ruiz <dejalatele@gmail.com>
++--INI--
++assert.bail=true
++--EXTENSIONS--
++dom
++--ENV--
++XML_FILE=/not_well_formed2.xml
++LOAD_OPTIONS=0
++EXPECTED_RESULT=0
++--FILE_EXTERNAL--
++domdocumentload_test_method.inc
++--EXPECTF--
++Warning: DOMDocument::load(): AttValue: " or ' expected in %s on line %d
++
++Warning: DOMDocument::load(): internal error: xmlParseStartTag: problem parsing attributes in %s on line %d
++
++Warning: DOMDocument::load(): Couldn't find end of Start Tag book line 4 in %s on line %d
++
++Warning: DOMDocument::load(): Opening and ending tag mismatch: books line 3 and book in %s on line %d
++
++Warning: DOMDocument::load(): Extra content at the end of the document in %s on line %d
+diff --git a/ext/dom/tests/DOMDocument_load_error2.phpt b/ext/dom/tests/DOMDocument_load_error2_pre2_11.phpt
+similarity index 89%
+rename from ext/dom/tests/DOMDocument_load_error2.phpt
+rename to ext/dom/tests/DOMDocument_load_error2_pre2_11.phpt
+index f450cf16545..74b20c171e0 100644
+--- a/ext/dom/tests/DOMDocument_load_error2.phpt
++++ b/ext/dom/tests/DOMDocument_load_error2_pre2_11.phpt
+@@ -1,5 +1,10 @@
+ --TEST--
+ Test DOMDocument::load() detects not-well formed XML
++--SKIPIF--
++<?php
++include('skipif.inc');
++if (LIBXML_VERSION >= 21100) die('skip libxml2 test variant for version < 2.11');
++?>
+ --DESCRIPTION--
+ This test verifies the method detects attributes values not closed between " or '
+ Environment variables used in the test:
+@@ -10,8 +15,6 @@ Environment variables used in the test:
+ Antonio Diaz Ruiz <dejalatele@gmail.com>
+ --INI--
+ assert.bail=true
+---SKIPIF--
+-<?php include('skipif.inc'); ?>
+ --ENV--
+ XML_FILE=/not_well_formed2.xml
+ LOAD_OPTIONS=0
+diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c
+index d343135b98d..5d9c23e0d7e 100644
+--- a/ext/libxml/libxml.c
++++ b/ext/libxml/libxml.c
+@@ -574,6 +574,8 @@ static void php_libxml_ctx_error_level(int level, void *ctx, const char *msg)
+ } else {
+ php_error_docref(NULL, level, "%s in Entity, line: %d", msg, parser->input->line);
+ }
++ } else {
++ php_error_docref(NULL, E_WARNING, "%s", msg);
+ }
+ }
+
+diff --git a/ext/libxml/tests/bug61367-read_2.phpt b/ext/libxml/tests/bug61367-read_2.phpt
+index 8cc0b50144c..12743adab1c 100644
+--- a/ext/libxml/tests/bug61367-read_2.phpt
++++ b/ext/libxml/tests/bug61367-read_2.phpt
+@@ -55,6 +55,6 @@ bool(true)
+ int(4)
+ bool(true)
+
+-Warning: DOMDocument::loadXML(): I/O warning : failed to load external entity "file:///%s/test_bug_61367-read/bad" in %s on line %d
++Warning: DOMDocument::loadXML(): %Sfailed to load external entity "file:///%s/test_bug_61367-read/bad" in %s on line %d
+
+ Notice: Trying to get property 'nodeValue' of non-object in %s on line %d
+diff --git a/ext/libxml/tests/libxml_disable_entity_loader_2.phpt b/ext/libxml/tests/libxml_disable_entity_loader_2.phpt
+index 845bd4bbe3c..55d8e61ee09 100644
+--- a/ext/libxml/tests/libxml_disable_entity_loader_2.phpt
++++ b/ext/libxml/tests/libxml_disable_entity_loader_2.phpt
+@@ -36,6 +36,6 @@ echo "Done\n";
+ bool(true)
+ bool(false)
+
+-Warning: DOMDocument::loadXML(): I/O warning : failed to load external entity "%s" in %s on line %d
++Warning: DOMDocument::loadXML(): %Sfailed to load external entity "%s" in %s on line %d
+ bool(true)
+ Done
+diff --git a/ext/libxml/tests/libxml_set_external_entity_loader_error1.phpt b/ext/libxml/tests/libxml_set_external_entity_loader_error1.phpt
+index 40b31ea85d3..00e06eb8a25 100644
+--- a/ext/libxml/tests/libxml_set_external_entity_loader_error1.phpt
++++ b/ext/libxml/tests/libxml_set_external_entity_loader_error1.phpt
+@@ -35,6 +35,8 @@ Warning: libxml_set_external_entity_loader() expects exactly 1 parameter, 2 give
+ NULL
+ bool(true)
+
++Warning: DOMDocument::validate(): Call to user entity loader callback %s
++
+ Warning: DOMDocument::validate(): Could not load the external subset "http://example.com/foobar" in %s on line %d
+ Exception: Too few arguments to function {closure}(), 3 passed and exactly 4 expected
+ Done.
+diff --git a/ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt b/ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt
+index e51869cf47f..0664de1ea6b 100644
+--- a/ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt
++++ b/ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt
+@@ -38,6 +38,8 @@ echo "Done.\n";
+ string(10) "-//FOO/BAR"
+ string(%d) "%sfoobar.dtd"
+
++Warning: DOMDocument::validate(): Failed to load external entity "-//FOO/BAR" in %s on line %d
++
+ Warning: DOMDocument::validate(): Could not load the external subset "foobar.dtd" in %s on line %d
+ bool(false)
+ bool(true)
+diff --git a/ext/openssl/tests/ServerClientTestCase.inc b/ext/openssl/tests/ServerClientTestCase.inc
+index c74da444102..753366df6f4 100644
+--- a/ext/openssl/tests/ServerClientTestCase.inc
++++ b/ext/openssl/tests/ServerClientTestCase.inc
+@@ -4,19 +4,14 @@ const WORKER_ARGV_VALUE = 'RUN_WORKER';
+
+ const WORKER_DEFAULT_NAME = 'server';
+
+-function phpt_notify(string $worker = WORKER_DEFAULT_NAME, string $message = ""): void
++function phpt_notify($worker = WORKER_DEFAULT_NAME)
+ {
+- ServerClientTestCase::getInstance()->notify($worker, $message);
++ ServerClientTestCase::getInstance()->notify($worker);
+ }
+
+-function phpt_wait($worker = WORKER_DEFAULT_NAME, $timeout = null): ?string
++function phpt_wait($worker = WORKER_DEFAULT_NAME, $timeout = null)
+ {
+- return ServerClientTestCase::getInstance()->wait($worker, $timeout);
+-}
+-
+-function phpt_notify_server_start($server): void
+-{
+- ServerClientTestCase::getInstance()->notify_server_start($server);
++ ServerClientTestCase::getInstance()->wait($worker, $timeout);
+ }
+
+ function phpt_has_sslv3() {
+@@ -124,73 +119,43 @@ class ServerClientTestCase
+ eval($code);
+ }
+
+- /**
+- * Run client and all workers
+- *
+- * @param string $clientCode The client PHP code
+- * @param string|array $workerCode
+- * @param bool $ephemeral Select whether automatic port selection and automatic awaiting is used
+- * @return void
+- * @throws Exception
+- */
+- public function run(string $clientCode, $workerCode, bool $ephemeral = true): void
++ public function run($masterCode, $workerCode)
+ {
+ if (!is_array($workerCode)) {
+ $workerCode = [WORKER_DEFAULT_NAME => $workerCode];
+ }
+- reset($workerCode);
+- $code = current($workerCode);
+- $worker = key($workerCode);
+- while ($worker != null) {
++ foreach ($workerCode as $worker => $code) {
+ $this->spawnWorkerProcess($worker, $this->stripPhpTagsFromCode($code));
+- $code = next($workerCode);
+- if ($ephemeral) {
+- $addr = trim($this->wait($worker));
+- if (empty($addr)) {
+- throw new \Exception("Failed server start");
+- }
+- if ($code === false) {
+- $clientCode = preg_replace('/{{\s*ADDR\s*}}/', $addr, $clientCode);
+- } else {
+- $code = preg_replace('/{{\s*ADDR\s*}}/', $addr, $code);
+- }
+- }
+- $worker = key($workerCode);
+ }
+-
+- eval($this->stripPhpTagsFromCode($clientCode));
++ eval($this->stripPhpTagsFromCode($masterCode));
+ foreach ($workerCode as $worker => $code) {
+ $this->cleanupWorkerProcess($worker);
+ }
+ }
+
+- public function wait($worker, $timeout = null): ?string
++ public function wait($worker, $timeout = null)
+ {
+ $handle = $this->isWorker ? STDIN : $this->workerStdOut[$worker];
+ if ($timeout === null) {
+- return fgets($handle);
++ fgets($handle);
++ return true;
+ }
+
+ stream_set_blocking($handle, false);
+ $read = [$handle];
+ $result = stream_select($read, $write, $except, $timeout);
+ if (!$result) {
+- return null;
++ return false;
+ }
+
+- $result = fgets($handle);
++ fgets($handle);
+ stream_set_blocking($handle, true);
+- return $result;
+- }
+-
+- public function notify(string $worker, string $message = ""): void
+- {
+- fwrite($this->isWorker ? STDOUT : $this->workerStdIn[$worker], "$message\n");
++ return true;
+ }
+
+- public function notify_server_start($server): void
++ public function notify($worker)
+ {
+- echo stream_socket_get_name($server, false) . "\n";
++ fwrite($this->isWorker ? STDOUT : $this->workerStdIn[$worker], "\n");
+ }
+ }
+
+diff --git a/ext/standard/tests/http/ServerClientTestCase.inc b/ext/standard/tests/http/ServerClientTestCase.inc
+new file mode 100644
+index 00000000000..c74da444102
+--- /dev/null
++++ b/ext/standard/tests/http/ServerClientTestCase.inc
+@@ -0,0 +1,199 @@
++<?php
++
++const WORKER_ARGV_VALUE = 'RUN_WORKER';
++
++const WORKER_DEFAULT_NAME = 'server';
++
++function phpt_notify(string $worker = WORKER_DEFAULT_NAME, string $message = ""): void
++{
++ ServerClientTestCase::getInstance()->notify($worker, $message);
++}
++
++function phpt_wait($worker = WORKER_DEFAULT_NAME, $timeout = null): ?string
++{
++ return ServerClientTestCase::getInstance()->wait($worker, $timeout);
++}
++
++function phpt_notify_server_start($server): void
++{
++ ServerClientTestCase::getInstance()->notify_server_start($server);
++}
++
++function phpt_has_sslv3() {
++ static $result = null;
++ if (!is_null($result)) {
++ return $result;
++ }
++ $server = @stream_socket_server('sslv3://127.0.0.1:10013');
++ if ($result = !!$server) {
++ fclose($server);
++ }
++ return $result;
++}
++
++/**
++ * This is a singleton to let the wait/notify functions work
++ * I know it's horrible, but it's a means to an end
++ */
++class ServerClientTestCase
++{
++ private $isWorker = false;
++
++ private $workerHandle = [];
++
++ private $workerStdIn = [];
++
++ private $workerStdOut = [];
++
++ private static $instance;
++
++ public static function getInstance($isWorker = false)
++ {
++ if (!isset(self::$instance)) {
++ self::$instance = new self($isWorker);
++ }
++
++ return self::$instance;
++ }
++
++ public function __construct($isWorker = false)
++ {
++ if (!isset(self::$instance)) {
++ self::$instance = $this;
++ }
++
++ $this->isWorker = $isWorker;
++ }
++
++ private function spawnWorkerProcess($worker, $code)
++ {
++ if (defined("PHP_WINDOWS_VERSION_MAJOR")) {
++ $ini = php_ini_loaded_file();
++ $cmd = sprintf(
++ '%s %s "%s" %s',
++ PHP_BINARY, $ini ? "-n -c $ini" : "",
++ __FILE__,
++ WORKER_ARGV_VALUE
++ );
++ } else {
++ $cmd = sprintf(
++ '%s "%s" %s %s',
++ PHP_BINARY,
++ __FILE__,
++ WORKER_ARGV_VALUE,
++ $worker
++ );
++ }
++ $this->workerHandle[$worker] = proc_open(
++ $cmd,
++ [['pipe', 'r'], ['pipe', 'w'], STDERR],
++ $pipes
++ );
++ $this->workerStdIn[$worker] = $pipes[0];
++ $this->workerStdOut[$worker] = $pipes[1];
++
++ fwrite($this->workerStdIn[$worker], $code . "\n---\n");
++ }
++
++ private function cleanupWorkerProcess($worker)
++ {
++ fclose($this->workerStdIn[$worker]);
++ fclose($this->workerStdOut[$worker]);
++ proc_close($this->workerHandle[$worker]);
++ }
++
++ private function stripPhpTagsFromCode($code)
++ {
++ return preg_replace('/^\s*<\?(?:php)?|\?>\s*$/i', '', $code);
++ }
++
++ public function runWorker()
++ {
++ $code = '';
++
++ while (1) {
++ $line = fgets(STDIN);
++
++ if (trim($line) === "---") {
++ break;
++ }
++
++ $code .= $line;
++ }
++
++ eval($code);
++ }
++
++ /**
++ * Run client and all workers
++ *
++ * @param string $clientCode The client PHP code
++ * @param string|array $workerCode
++ * @param bool $ephemeral Select whether automatic port selection and automatic awaiting is used
++ * @return void
++ * @throws Exception
++ */
++ public function run(string $clientCode, $workerCode, bool $ephemeral = true): void
++ {
++ if (!is_array($workerCode)) {
++ $workerCode = [WORKER_DEFAULT_NAME => $workerCode];
++ }
++ reset($workerCode);
++ $code = current($workerCode);
++ $worker = key($workerCode);
++ while ($worker != null) {
++ $this->spawnWorkerProcess($worker, $this->stripPhpTagsFromCode($code));
++ $code = next($workerCode);
++ if ($ephemeral) {
++ $addr = trim($this->wait($worker));
++ if (empty($addr)) {
++ throw new \Exception("Failed server start");
++ }
++ if ($code === false) {
++ $clientCode = preg_replace('/{{\s*ADDR\s*}}/', $addr, $clientCode);
++ } else {
++ $code = preg_replace('/{{\s*ADDR\s*}}/', $addr, $code);
++ }
++ }
++ $worker = key($workerCode);
++ }
++
++ eval($this->stripPhpTagsFromCode($clientCode));
++ foreach ($workerCode as $worker => $code) {
++ $this->cleanupWorkerProcess($worker);
++ }
++ }
++
++ public function wait($worker, $timeout = null): ?string
++ {
++ $handle = $this->isWorker ? STDIN : $this->workerStdOut[$worker];
++ if ($timeout === null) {
++ return fgets($handle);
++ }
++
++ stream_set_blocking($handle, false);
++ $read = [$handle];
++ $result = stream_select($read, $write, $except, $timeout);
++ if (!$result) {
++ return null;
++ }
++
++ $result = fgets($handle);
++ stream_set_blocking($handle, true);
++ return $result;
++ }
++
++ public function notify(string $worker, string $message = ""): void
++ {
++ fwrite($this->isWorker ? STDOUT : $this->workerStdIn[$worker], "$message\n");
++ }
++
++ public function notify_server_start($server): void
++ {
++ echo stream_socket_get_name($server, false) . "\n";
++ }
++}
++
++if (isset($argv[1]) && $argv[1] === WORKER_ARGV_VALUE) {
++ ServerClientTestCase::getInstance(true)->runWorker();
++}
+diff --git a/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt
+index 46d77ec4aff..3475a03beed 100644
+--- a/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt
++++ b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt
+@@ -39,7 +39,7 @@ $clientCode = <<<'CODE'
+ var_dump($http_response_header);
+ CODE;
+
+-include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++include sprintf("%s/ServerClientTestCase.inc", __DIR__);
+ ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+ ?>
+ --EXPECTF--
+diff --git a/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt
+index d25c89d06e5..706a85f410b 100644
+--- a/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt
++++ b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt
+@@ -39,7 +39,7 @@ $clientCode = <<<'CODE'
+ var_dump($http_response_header);
+ CODE;
+
+-include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++include sprintf("%s/ServerClientTestCase.inc", __DIR__);
+ ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+ ?>
+ --EXPECTF--
+diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt
+index c8dcd47a4a4..121f077c9f5 100644
+--- a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt
++++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt
+@@ -36,7 +36,7 @@ $clientCode = <<<'CODE'
+ var_dump($http_response_header);
+ CODE;
+
+-include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++include sprintf("%s/ServerClientTestCase.inc", __DIR__);
+ ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+ ?>
+ --EXPECTF--
+diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt
+index ca8f75f0327..0d141f93af3 100644
+--- a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt
++++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt
+@@ -36,7 +36,7 @@ $clientCode = <<<'CODE'
+ var_dump($http_response_header);
+ CODE;
+
+-include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++include sprintf("%s/ServerClientTestCase.inc", __DIR__);
+ ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+ ?>
+ --EXPECTF--
+diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt
+index 4cfbc7ee804..8041487d044 100644
+--- a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt
++++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt
+@@ -36,7 +36,7 @@ $clientCode = <<<'CODE'
+ var_dump($http_response_header);
+ CODE;
+
+-include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++include sprintf("%s/ServerClientTestCase.inc", __DIR__);
+ ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+ ?>
+ --EXPECTF--
+diff --git a/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt
+index 53baa1c92d6..f491acfae27 100644
+--- a/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt
++++ b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt
+@@ -35,7 +35,7 @@ $clientCode = <<<'CODE'
+ var_dump($http_response_header);
+ CODE;
+
+-include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++include sprintf("%s/ServerClientTestCase.inc", __DIR__);
+ ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+ ?>
+ --EXPECTF--
+diff --git a/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt
+index 5aa0ee00618..4320b17b97d 100644
+--- a/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt
++++ b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt
+@@ -35,7 +35,7 @@ $clientCode = <<<'CODE'
+ var_dump($http_response_header);
+ CODE;
+
+-include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++include sprintf("%s/ServerClientTestCase.inc", __DIR__);
+ ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+ ?>
+ --EXPECTF--
+diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt
+index 64904bfcd1d..3f1cc79bd9c 100644
+--- a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt
++++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt
+@@ -35,7 +35,7 @@ $clientCode = <<<'CODE'
+ var_dump($http_response_header);
+ CODE;
+
+-include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++include sprintf("%s/ServerClientTestCase.inc", __DIR__);
+ ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+ ?>
+ --EXPECTF--
+diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt
+index a6d9d00fd58..c7c13877fef 100644
+--- a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt
++++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt
+@@ -35,7 +35,7 @@ $clientCode = <<<'CODE'
+ var_dump($http_response_header);
+ CODE;
+
+-include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++include sprintf("%s/ServerClientTestCase.inc", __DIR__);
+ ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+ ?>
+ --EXPECTF--
+diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt
+index 4eff7fc63f3..c67663b65f7 100644
+--- a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt
++++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt
+@@ -35,7 +35,7 @@ $clientCode = <<<'CODE'
+ var_dump($http_response_header);
+ CODE;
+
+-include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++include sprintf("%s/ServerClientTestCase.inc", __DIR__);
+ ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+ ?>
+ --EXPECTF--
+diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt
+index 71aed2fa2e8..7a59e2688fd 100644
+--- a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt
++++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt
+@@ -35,7 +35,7 @@ $clientCode = <<<'CODE'
+ var_dump($http_response_header);
+ CODE;
+
+-include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++include sprintf("%s/ServerClientTestCase.inc", __DIR__);
+ ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+ ?>
+ --EXPECTF--
+diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt
+index 49d845d84b4..f097762ef9e 100644
+--- a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt
++++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt
+@@ -35,7 +35,7 @@ $clientCode = <<<'CODE'
+ var_dump($http_response_header);
+ CODE;
+
+-include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++include sprintf("%s/ServerClientTestCase.inc", __DIR__);
+ ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+ ?>
+ --EXPECTF--
+diff --git a/ext/xml/tests/bug26614_libxml_gte2_11.phpt b/ext/xml/tests/bug26614_libxml_gte2_11.phpt
+new file mode 100644
+index 00000000000..9a81b67686d
+--- /dev/null
++++ b/ext/xml/tests/bug26614_libxml_gte2_11.phpt
+@@ -0,0 +1,95 @@
++--TEST--
++Bug #26614 (CDATA sections skipped on line count)
++--EXTENSIONS--
++xml
++--SKIPIF--
++<?php
++if (!defined("LIBXML_VERSION")) die('skip libxml2 test');
++if (LIBXML_VERSION < 21100) die('skip libxml2 test variant for version >= 2.11');
++?>
++--FILE--
++<?php
++/*
++this test works fine with Expat but fails with libxml
++which we now use as default
++
++further investigation has shown that not only line count
++is skipped on CDATA sections but that libxml does also
++show different column numbers and byte positions depending
++on context and in opposition to what one would expect to
++see and what good old Expat reported just fine ...
++*/
++
++$xmls = array();
++
++// Case 1: CDATA Sections
++$xmls["CDATA"] ='<?xml version="1.0" encoding="iso-8859-1" ?>
++<data>
++<![CDATA[
++multi
++line
++CDATA
++block
++]]>
++</data>';
++
++// Case 2: replace some characters so that we get comments instead
++$xmls["Comment"] ='<?xml version="1.0" encoding="iso-8859-1" ?>
++<data>
++<!-- ATA[
++multi
++line
++CDATA
++block
++-->
++</data>';
++
++// Case 3: replace even more characters so that only textual data is left
++$xmls["Text"] ='<?xml version="1.0" encoding="iso-8859-1" ?>
++<data>
++-!-- ATA[
++multi
++line
++CDATA
++block
++---
++</data>';
++
++function startElement($parser, $name, $attrs) {
++ printf("<$name> at line %d, col %d (byte %d)\n",
++ xml_get_current_line_number($parser),
++ xml_get_current_column_number($parser),
++ xml_get_current_byte_index($parser));
++}
++
++function endElement($parser, $name) {
++ printf("</$name> at line %d, col %d (byte %d)\n",
++ xml_get_current_line_number($parser),
++ xml_get_current_column_number($parser),
++ xml_get_current_byte_index($parser));
++}
++
++function characterData($parser, $data) {
++ // dummy
++}
++
++foreach ($xmls as $desc => $xml) {
++ echo "$desc\n";
++ $xml_parser = xml_parser_create();
++ xml_set_element_handler($xml_parser, "startElement", "endElement");
++ xml_set_character_data_handler($xml_parser, "characterData");
++ if (!xml_parse($xml_parser, $xml, true))
++ echo "Error: ".xml_error_string(xml_get_error_code($xml_parser))."\n";
++ xml_parser_free($xml_parser);
++}
++?>
++--EXPECTF--
++CDATA
++<DATA> at line 2, col %d (byte 50)
++</DATA> at line 9, col %d (byte 96)
++Comment
++<DATA> at line 2, col %d (byte 50)
++</DATA> at line 9, col %d (byte 96)
++Text
++<DATA> at line 2, col %d (byte 50)
++</DATA> at line 9, col %d (byte 96)
+diff --git a/ext/xml/tests/bug26614_libxml.phpt b/ext/xml/tests/bug26614_libxml_pre2_11.phpt
+similarity index 96%
+rename from ext/xml/tests/bug26614_libxml.phpt
+rename to ext/xml/tests/bug26614_libxml_pre2_11.phpt
+index 3ddd35ed0ea..afacaa1c59a 100644
+--- a/ext/xml/tests/bug26614_libxml.phpt
++++ b/ext/xml/tests/bug26614_libxml_pre2_11.phpt
+@@ -4,6 +4,7 @@ Bug #26614 (CDATA sections skipped on line count)
+ <?php
+ require_once("skipif.inc");
+ if (!defined("LIBXML_VERSION")) die('skip libxml2 test');
++if (LIBXML_VERSION >= 21100) die('skip libxml2 test variant for version < 2.11');
+ ?>
+ --FILE--
+ <?php
+--
+2.48.1
+
+From 087e974d74efc977dfbd18fd3cd568b60fb7675d Mon Sep 17 00:00:00 2001
+From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
+Date: Fri, 1 Dec 2023 18:03:35 +0100
+Subject: [PATCH 06/11] Backport 0a39890c: Fix libxml2 2.12 build due to API
+ breaks
+
+See https://github.com/php/php-src/actions/runs/7062192818/job/19225478601
+
+(cherry picked from commit fa6a0f80f644932506666beb7c85e4041c4a4646)
+(cherry picked from commit 6e8e9f558aa0903e9650dd166a0a53c359d9e9e0)
+---
+ ext/libxml/libxml.c | 14 ++++++++++----
+ ext/soap/php_sdl.c | 2 +-
+ 2 files changed, 11 insertions(+), 5 deletions(-)
+
+diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c
+index 5d9c23e0d7e..7917f636a9e 100644
+--- a/ext/libxml/libxml.c
++++ b/ext/libxml/libxml.c
+@@ -530,7 +530,11 @@ static int _php_libxml_free_error(xmlErrorPtr error)
+ return 1;
+ }
+
+-static void _php_list_set_error_structure(xmlErrorPtr error, const char *msg)
++#if LIBXML_VERSION >= 21200
++static void _php_list_set_error_structure(const xmlError *error, const char *msg)
++#else
++static void _php_list_set_error_structure(xmlError *error, const char *msg)
++#endif
+ {
+ xmlError error_copy;
+ int ret;
+@@ -784,7 +788,11 @@ PHP_LIBXML_API void php_libxml_ctx_warning(void *ctx, const char *msg, ...)
+ va_end(args);
+ }
+
++#if LIBXML_VERSION >= 21200
++PHP_LIBXML_API void php_libxml_structured_error_handler(void *userData, const xmlError *error)
++#else
+ PHP_LIBXML_API void php_libxml_structured_error_handler(void *userData, xmlErrorPtr error)
++#endif
+ {
+ _php_list_set_error_structure(error, NULL);
+
+@@ -1063,9 +1071,7 @@ static PHP_FUNCTION(libxml_use_internal_errors)
+ Retrieve last error from libxml */
+ static PHP_FUNCTION(libxml_get_last_error)
+ {
+- xmlErrorPtr error;
+-
+- error = xmlGetLastError();
++ const xmlError *error = xmlGetLastError();
+
+ if (error) {
+ object_init_ex(return_value, libxmlerror_class_entry);
+diff --git a/ext/soap/php_sdl.c b/ext/soap/php_sdl.c
+index 26a23f57db2..3df532a2d65 100644
+--- a/ext/soap/php_sdl.c
++++ b/ext/soap/php_sdl.c
+@@ -333,7 +333,7 @@ static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, int include)
+ sdl_restore_uri_credentials(ctx);
+
+ if (!wsdl) {
+- xmlErrorPtr xmlErrorPtr = xmlGetLastError();
++ const xmlError *xmlErrorPtr = xmlGetLastError();
+
+ if (xmlErrorPtr) {
+ soap_error2(E_ERROR, "Parsing WSDL: Couldn't load from '%s' : %s", struri, xmlErrorPtr->message);
+--
+2.48.1
+
+From aa8817ab42f758c988dfd3158f705da770238a88 Mon Sep 17 00:00:00 2001
+From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
+Date: Thu, 4 Jul 2024 06:29:50 -0700
+Subject: [PATCH 07/11] Backport 4fe82131: Backport libxml2 2.13.2 fixes
+ (#14816)
+
+Backproted from https://github.com/php/php-src/pull/14789
+
+(cherry picked from commit bb46b4b799b583528025a775af45308133bfd4c1)
+(cherry picked from commit 6cb68826aaf68ffe8c70c8782450c38970236040)
+---
+ ext/dom/document.c | 6 ++--
+ .../DOMDocument_loadHTMLfile_error1.phpt | 2 +-
+ .../DOMDocument_relaxNGValidate_error2.phpt | 2 +-
+ .../tests/DOMDocument_saveHTMLFile_basic.phpt | 1 +
+ ...DOMDocument_saveHTMLFile_formatOutput.phpt | 1 +
+ ...nt_saveHTMLFile_formatOutput_gte_2_13.phpt | 32 +++++++++++++++++++
+ .../DOMDocument_saveHTML_basic_gte_2_13.phpt | 31 ++++++++++++++++++
+ .../DOMDocument_schemaValidate_error5.phpt | 2 +-
+ ext/dom/tests/dom_create_element.phpt | 14 +++-----
+ ext/libxml/libxml.c | 4 ++-
+ ext/simplexml/tests/bug79971_1.phpt | 2 +-
+ ext/soap/php_encoding.c | 9 ++++--
+ ext/soap/php_xml.c | 4 ++-
+ ext/soap/tests/bugs/bug42151.phpt | 4 +--
+ ext/xml/compat.c | 3 +-
+ ext/xmlwriter/php_xmlwriter.c | 3 +-
+ 16 files changed, 95 insertions(+), 25 deletions(-)
+ create mode 100644 ext/dom/tests/DOMDocument_saveHTMLFile_formatOutput_gte_2_13.phpt
+ create mode 100644 ext/dom/tests/DOMDocument_saveHTML_basic_gte_2_13.phpt
+
+diff --git a/ext/dom/document.c b/ext/dom/document.c
+index 989b5b3dd24..af06fb41240 100644
+--- a/ext/dom/document.c
++++ b/ext/dom/document.c
+@@ -1457,11 +1457,13 @@ static xmlDocPtr dom_document_parser(zval *id, int mode, char *source, size_t so
+ if (keep_blanks == 0 && ! (options & XML_PARSE_NOBLANKS)) {
+ options |= XML_PARSE_NOBLANKS;
+ }
++ if (recover) {
++ options |= XML_PARSE_RECOVER;
++ }
+
+ php_libxml_sanitize_parse_ctxt_options(ctxt);
+ xmlCtxtUseOptions(ctxt, options);
+
+- ctxt->recovery = recover;
+ if (recover) {
+ old_error_reporting = EG(error_reporting);
+ EG(error_reporting) = old_error_reporting | E_WARNING;
+@@ -1471,7 +1473,7 @@ static xmlDocPtr dom_document_parser(zval *id, int mode, char *source, size_t so
+
+ if (ctxt->wellFormed || recover) {
+ ret = ctxt->myDoc;
+- if (ctxt->recovery) {
++ if (recover) {
+ EG(error_reporting) = old_error_reporting;
+ }
+ /* If loading from memory, set the base reference uri for the document */
+diff --git a/ext/dom/tests/DOMDocument_loadHTMLfile_error1.phpt b/ext/dom/tests/DOMDocument_loadHTMLfile_error1.phpt
+index cfb41686e87..fc78273c85f 100644
+--- a/ext/dom/tests/DOMDocument_loadHTMLfile_error1.phpt
++++ b/ext/dom/tests/DOMDocument_loadHTMLfile_error1.phpt
+@@ -15,4 +15,4 @@ $result = $doc->loadHTMLFile(__DIR__ . "/ffff/test.html");
+ assert($result === false);
+ ?>
+ --EXPECTF--
+-%r(PHP ){0,1}%rWarning: DOMDocument::loadHTMLFile(): I/O warning : failed to load external entity %s
++%r(PHP ){0,1}%rWarning: DOMDocument::loadHTMLFile(): I/O %s
+diff --git a/ext/dom/tests/DOMDocument_relaxNGValidate_error2.phpt b/ext/dom/tests/DOMDocument_relaxNGValidate_error2.phpt
+index cdd6e64194c..19bb4dce2d6 100644
+--- a/ext/dom/tests/DOMDocument_relaxNGValidate_error2.phpt
++++ b/ext/dom/tests/DOMDocument_relaxNGValidate_error2.phpt
+@@ -22,7 +22,7 @@ $result = $doc->relaxNGValidate($rng);
+ var_dump($result);
+ ?>
+ --EXPECTF--
+-Warning: DOMDocument::relaxNGValidate(): I/O warning : failed to load external entity "%s/foo.rng" in %s on line %d
++Warning: DOMDocument::relaxNGValidate(): I/O %s : failed to load %s
+
+ Warning: DOMDocument::relaxNGValidate(): xmlRelaxNGParse: could not load %s/foo.rng in %s on line %d
+
+diff --git a/ext/dom/tests/DOMDocument_saveHTMLFile_basic.phpt b/ext/dom/tests/DOMDocument_saveHTMLFile_basic.phpt
+index f71db0c32a3..c51852e120c 100644
+--- a/ext/dom/tests/DOMDocument_saveHTMLFile_basic.phpt
++++ b/ext/dom/tests/DOMDocument_saveHTMLFile_basic.phpt
+@@ -6,6 +6,7 @@ Knut Urdalen <knut@php.net>
+ --SKIPIF--
+ <?php
+ require_once __DIR__ .'/skipif.inc';
++if (LIBXML_VERSION >= 21300) die("skip see https://gitlab.gnome.org/GNOME/libxml2/-/issues/756");
+ ?>
+ --FILE--
+ <?php
+diff --git a/ext/dom/tests/DOMDocument_saveHTMLFile_formatOutput.phpt b/ext/dom/tests/DOMDocument_saveHTMLFile_formatOutput.phpt
+index 376c9a8e323..8d7baa7b7e8 100644
+--- a/ext/dom/tests/DOMDocument_saveHTMLFile_formatOutput.phpt
++++ b/ext/dom/tests/DOMDocument_saveHTMLFile_formatOutput.phpt
+@@ -6,6 +6,7 @@ Knut Urdalen <knut@php.net>
+ --SKIPIF--
+ <?php
+ require_once __DIR__ .'/skipif.inc';
++if (LIBXML_VERSION >= 21300) die("skip see https://gitlab.gnome.org/GNOME/libxml2/-/issues/756");
+ ?>
+ --FILE--
+ <?php
+diff --git a/ext/dom/tests/DOMDocument_saveHTMLFile_formatOutput_gte_2_13.phpt b/ext/dom/tests/DOMDocument_saveHTMLFile_formatOutput_gte_2_13.phpt
+new file mode 100644
+index 00000000000..3477edfcf5f
+--- /dev/null
++++ b/ext/dom/tests/DOMDocument_saveHTMLFile_formatOutput_gte_2_13.phpt
+@@ -0,0 +1,32 @@
++--TEST--
++DOMDocument::saveHTMLFile() should format output on demand
++--CREDITS--
++Knut Urdalen <knut@php.net>
++#PHPTestFest2009 Norway 2009-06-09 \o/
++--EXTENSIONS--
++dom
++--SKIPIF--
++<?php
++if (LIBXML_VERSION < 21300) die("skip see https://gitlab.gnome.org/GNOME/libxml2/-/issues/756");
++?>
++--FILE--
++<?php
++$filename = __DIR__."/DOMDocument_saveHTMLFile_formatOutput_gte_2_13.html";
++$doc = new DOMDocument('1.0');
++$doc->formatOutput = true;
++$root = $doc->createElement('html');
++$root = $doc->appendChild($root);
++$head = $doc->createElement('head');
++$head = $root->appendChild($head);
++$title = $doc->createElement('title');
++$title = $head->appendChild($title);
++$text = $doc->createTextNode('This is the title');
++$text = $title->appendChild($text);
++$bytes = $doc->saveHTMLFile($filename);
++var_dump($bytes);
++echo file_get_contents($filename);
++unlink($filename);
++?>
++--EXPECT--
++int(59)
++<html><head><title>This is the title</title></head></html>
+diff --git a/ext/dom/tests/DOMDocument_saveHTML_basic_gte_2_13.phpt b/ext/dom/tests/DOMDocument_saveHTML_basic_gte_2_13.phpt
+new file mode 100644
+index 00000000000..c0be105253d
+--- /dev/null
++++ b/ext/dom/tests/DOMDocument_saveHTML_basic_gte_2_13.phpt
+@@ -0,0 +1,31 @@
++--TEST--
++DOMDocument::saveHTMLFile() should dump the internal document into a file using HTML formatting
++--CREDITS--
++Knut Urdalen <knut@php.net>
++#PHPTestFest2009 Norway 2009-06-09 \o/
++--EXTENSIONS--
++dom
++--SKIPIF--
++<?php
++if (LIBXML_VERSION < 21300) die("skip see https://gitlab.gnome.org/GNOME/libxml2/-/issues/756");
++?>
++--FILE--
++<?php
++$filename = __DIR__."/DOMDocument_saveHTMLFile_basic_gte_2_13.html";
++$doc = new DOMDocument('1.0');
++$root = $doc->createElement('html');
++$root = $doc->appendChild($root);
++$head = $doc->createElement('head');
++$head = $root->appendChild($head);
++$title = $doc->createElement('title');
++$title = $head->appendChild($title);
++$text = $doc->createTextNode('This is the title');
++$text = $title->appendChild($text);
++$bytes = $doc->saveHTMLFile($filename);
++var_dump($bytes);
++echo file_get_contents($filename);
++unlink($filename);
++?>
++--EXPECT--
++int(59)
++<html><head><title>This is the title</title></head></html>
+diff --git a/ext/dom/tests/DOMDocument_schemaValidate_error5.phpt b/ext/dom/tests/DOMDocument_schemaValidate_error5.phpt
+index cb57b55b41a..44ea52c2d06 100644
+--- a/ext/dom/tests/DOMDocument_schemaValidate_error5.phpt
++++ b/ext/dom/tests/DOMDocument_schemaValidate_error5.phpt
+@@ -17,7 +17,7 @@ var_dump($result);
+
+ ?>
+ --EXPECTF--
+-Warning: DOMDocument::schemaValidate(): I/O warning : failed to load external entity "%snon-existent-file" in %s.php on line %d
++Warning: DOMDocument::schemaValidate(): I/O %s : failed to load %s
+
+ Warning: DOMDocument::schemaValidate(): Failed to locate the main schema resource at '%s/non-existent-file'. in %s.php on line %d
+
+diff --git a/ext/dom/tests/dom_create_element.phpt b/ext/dom/tests/dom_create_element.phpt
+index bd2c8f11dae..70ae54a11bb 100644
+--- a/ext/dom/tests/dom_create_element.phpt
++++ b/ext/dom/tests/dom_create_element.phpt
+@@ -251,14 +251,10 @@ try {
+ print $e->getMessage() . "\n";
+ }
+
+-/* This isn't because the xml namespace isn't there and we can't create it */
+-print "29 DOMElement::__construct('xml:valid', '', 'http://www.w3.org/XML/1998/namespace')\n";
+-try {
+- $element = new DomElement('xml:valid', '', 'http://www.w3.org/XML/1998/namespace');
+- print "valid\n";
+-} catch (Exception $e) {
+- print $e->getMessage() . "\n";
+-}
++/* There used to be a 29 here that tested DOMElement::__construct('xml:valid', '', 'http://www.w3.org/XML/1998/namespace').
++ * In libxml2 version 2.12 or prior this didn't work because the xml namespace isn't there and you can't create it without
++ * a document. Starting from libxml2 version 2.13 it does actually work because the XML namespace is statically defined.
++ * The behaviour from version 2.13 is actually the desired behaviour anyway. */
+
+
+ /* the qualifiedName or its prefix is "xmlns" and the namespaceURI is
+@@ -378,8 +374,6 @@ Namespace Error
+ Namespace Error
+ 28 DOMDocument::createElementNS('http://www.w3.org/XML/1998/namespace', 'xml:valid')
+ valid
+-29 DOMElement::__construct('xml:valid', '', 'http://www.w3.org/XML/1998/namespace')
+-Namespace Error
+ 30 DOMDocument::createElementNS('http://wrong.namespaceURI.com', 'xmlns:valid')
+ Namespace Error
+ 31 DOMElement::__construct('xmlns:valid', '', 'http://wrong.namespaceURI.com')
+diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c
+index 7917f636a9e..4b9e6a918d4 100644
+--- a/ext/libxml/libxml.c
++++ b/ext/libxml/libxml.c
+@@ -476,8 +476,10 @@ php_libxml_input_buffer_create_filename(const char *URI, xmlCharEncoding enc)
+ static xmlOutputBufferPtr
+ php_libxml_output_buffer_create_filename(const char *URI,
+ xmlCharEncodingHandlerPtr encoder,
+- int compression ATTRIBUTE_UNUSED)
++ int compression)
+ {
++ ZEND_IGNORE_VALUE(compression);
++
+ xmlOutputBufferPtr ret;
+ xmlURIPtr puri;
+ void *context = NULL;
+diff --git a/ext/simplexml/tests/bug79971_1.phpt b/ext/simplexml/tests/bug79971_1.phpt
+index 197776d82d3..2ee24e89f12 100644
+--- a/ext/simplexml/tests/bug79971_1.phpt
++++ b/ext/simplexml/tests/bug79971_1.phpt
+@@ -20,7 +20,7 @@ var_dump($sxe->asXML("$uri.out%00foo"));
+ --EXPECTF--
+ Warning: simplexml_load_file(): URI must not contain percent-encoded NUL bytes in %s on line %d
+
+-Warning: simplexml_load_file(): I/O warning : failed to load external entity "%s/bug79971_1.xml%00foo" in %s on line %d
++Warning: simplexml_load_file(): I/O warning : failed to load %s
+ bool(false)
+
+ Warning: SimpleXMLElement::asXML(): URI must not contain percent-encoded NUL bytes in %s on line %d
+diff --git a/ext/soap/php_encoding.c b/ext/soap/php_encoding.c
+index e0cf63dd1da..0a6edbf5a41 100644
+--- a/ext/soap/php_encoding.c
++++ b/ext/soap/php_encoding.c
+@@ -3381,7 +3381,6 @@ xmlNsPtr encode_add_ns(xmlNodePtr node, const char* ns)
+ } else {
+ smart_str prefix = {0};
+ int num = ++SOAP_GLOBAL(cur_uniq_ns);
+- xmlChar *enc_ns;
+
+ while (1) {
+ smart_str_appendl(&prefix, "ns", 2);
+@@ -3395,9 +3394,15 @@ xmlNsPtr encode_add_ns(xmlNodePtr node, const char* ns)
+ num = ++SOAP_GLOBAL(cur_uniq_ns);
+ }
+
+- enc_ns = xmlEncodeSpecialChars(node->doc, BAD_CAST(ns));
++ /* Starting with libxml 2.13, we don't have to do this workaround anymore, otherwise we get double-encoded
++ * entities. See libxml2 commit f506ec66547ef9bac97a2bf306d368ecea8c0c9e. */
++#if LIBXML_VERSION < 21300
++ xmlChar *enc_ns = xmlEncodeSpecialChars(node->doc, BAD_CAST(ns));
+ xmlns = xmlNewNs(node->doc->children, enc_ns, BAD_CAST(prefix.s ? ZSTR_VAL(prefix.s) : ""));
+ xmlFree(enc_ns);
++#else
++ xmlns = xmlNewNs(node->doc->children, BAD_CAST(ns), BAD_CAST(prefix.s ? ZSTR_VAL(prefix.s) : ""));
++#endif
+ smart_str_free(&prefix);
+ }
+ }
+diff --git a/ext/soap/php_xml.c b/ext/soap/php_xml.c
+index 1bb7fa00a37..446017eb5c8 100644
+--- a/ext/soap/php_xml.c
++++ b/ext/soap/php_xml.c
+@@ -94,13 +94,14 @@ xmlDocPtr soap_xmlParseFile(const char *filename)
+ zend_bool old;
+
+ php_libxml_sanitize_parse_ctxt_options(ctxt);
++ /* TODO: In libxml2 2.14.0 change this to the new options API so we don't rely on deprecated APIs. */
+ ctxt->keepBlanks = 0;
++ ctxt->options |= XML_PARSE_HUGE;
+ ctxt->sax->ignorableWhitespace = soap_ignorableWhitespace;
+ ctxt->sax->comment = soap_Comment;
+ ctxt->sax->warning = NULL;
+ ctxt->sax->error = NULL;
+ /*ctxt->sax->fatalError = NULL;*/
+- ctxt->options |= XML_PARSE_HUGE;
+ old = php_libxml_disable_entity_loader(1);
+ xmlParseDocument(ctxt);
+ php_libxml_disable_entity_loader(old);
+@@ -148,6 +149,7 @@ xmlDocPtr soap_xmlParseMemory(const void *buf, size_t buf_size)
+ ctxt->sax->warning = NULL;
+ ctxt->sax->error = NULL;
+ /*ctxt->sax->fatalError = NULL;*/
++ /* TODO: In libxml2 2.14.0 change this to the new options API so we don't rely on deprecated APIs. */
+ ctxt->options |= XML_PARSE_HUGE;
+ old = php_libxml_disable_entity_loader(1);
+ xmlParseDocument(ctxt);
+diff --git a/ext/soap/tests/bugs/bug42151.phpt b/ext/soap/tests/bugs/bug42151.phpt
+index ee53e6d525d..d1bcae83364 100644
+--- a/ext/soap/tests/bugs/bug42151.phpt
++++ b/ext/soap/tests/bugs/bug42151.phpt
+@@ -25,8 +25,8 @@ try {
+ }
+ echo "ok\n";
+ ?>
+---EXPECT--
+-SOAP-ERROR: Parsing WSDL: Couldn't load from 'httpx://' : failed to load external entity "httpx://"
++--EXPECTF--
++SOAP-ERROR: Parsing WSDL: Couldn't load from 'httpx://' : failed to load %s
+
+ ok
+ I don't get executed either.
+diff --git a/ext/xml/compat.c b/ext/xml/compat.c
+index 57eb00dd429..ea1fd835059 100644
+--- a/ext/xml/compat.c
++++ b/ext/xml/compat.c
+@@ -716,8 +716,7 @@ XML_GetCurrentByteCount(XML_Parser parser)
+ {
+ /* WARNING: this is identical to ByteIndex; it should probably
+ * be different */
+- return parser->parser->input->consumed +
+- (parser->parser->input->cur - parser->parser->input->base);
++ return XML_GetCurrentByteIndex(parser);
+ }
+
+ PHP_XML_API const XML_Char *XML_ExpatVersion(void)
+diff --git a/ext/xmlwriter/php_xmlwriter.c b/ext/xmlwriter/php_xmlwriter.c
+index 5cb141dad39..55874420f3b 100644
+--- a/ext/xmlwriter/php_xmlwriter.c
++++ b/ext/xmlwriter/php_xmlwriter.c
+@@ -1785,7 +1785,8 @@ static void php_xmlwriter_flush(INTERNAL_FUNCTION_PARAMETERS, int force_string)
+ }
+ output_bytes = xmlTextWriterFlush(ptr);
+ if (buffer) {
+- RETVAL_STRING((char *) buffer->content);
++ const xmlChar *content = xmlBufferContent(buffer);
++ RETVAL_STRING((const char *) content);
+ if (empty) {
+ xmlBufferEmpty(buffer);
+ }
+--
+2.48.1
+
+From 238d5f0aeaedc9c355f1bc1159b01e357bdaf344 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= <tim@tideways-gmbh.com>
+Date: Wed, 20 Nov 2024 10:47:27 +0100
+Subject: [PATCH 08/11] Fix GHSA-p3x9-6h7p-cgfc: libxml streams wrong
+ `content-type` on redirect
+
+libxml streams use wrong content-type header when requesting a
+redirected resource.
+
+(cherry picked from commit b6004a043c16b211d462218fbb3f72db68ec2b18)
+(cherry picked from commit 1196e566681a34564c02173ba234b5a42587ff07)
+---
+ ext/dom/tests/ghsa-p3x9-6h7p-cgfc_001.phpt | 60 ++++++++++
+ ext/dom/tests/ghsa-p3x9-6h7p-cgfc_002.phpt | 60 ++++++++++
+ ext/dom/tests/ghsa-p3x9-6h7p-cgfc_003.phpt | 60 ++++++++++
+ ext/libxml/libxml.c | 77 +++++++------
+ ext/standard/tests/http/newserver.inc | 124 +++++++++++++++++++++
+ 5 files changed, 348 insertions(+), 33 deletions(-)
+ create mode 100644 ext/dom/tests/ghsa-p3x9-6h7p-cgfc_001.phpt
+ create mode 100644 ext/dom/tests/ghsa-p3x9-6h7p-cgfc_002.phpt
+ create mode 100644 ext/dom/tests/ghsa-p3x9-6h7p-cgfc_003.phpt
+ create mode 100644 ext/standard/tests/http/newserver.inc
+
+diff --git a/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_001.phpt b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_001.phpt
+new file mode 100644
+index 00000000000..87cb2aa0b1f
+--- /dev/null
++++ b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_001.phpt
+@@ -0,0 +1,60 @@
++--TEST--
++GHSA-p3x9-6h7p-cgfc: libxml streams use wrong `content-type` header when requesting a redirected resource (Basic)
++--EXTENSIONS--
++dom
++--SKIPIF--
++<?php
++if (@!include "./ext/standard/tests/http/newserver.inc") die('skip server.inc not available');
++http_server_skipif();
++?>
++--FILE--
++<?php
++require "./ext/standard/tests/http/newserver.inc";
++
++function genResponses($server) {
++ $uri = 'http://' . stream_socket_get_name($server, false);
++ yield "data://text/plain,HTTP/1.1 302 Moved Temporarily\r\nLocation: $uri/document.xml\r\nContent-Type: text/html;charset=utf-16\r\n\r\n";
++ $xml = <<<'EOT'
++ <!doctype html>
++ <html>
++ <head>
++ <title>GHSA-p3x9-6h7p-cgfc</title>
++
++ <meta charset="utf-8" />
++ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
++ </head>
++
++ <body>
++ <h1>GHSA-p3x9-6h7p-cgfc</h1>
++ </body>
++ </html>
++ EOT;
++ // Intentionally using non-standard casing for content-type to verify it is matched not case sensitively.
++ yield "data://text/plain,HTTP/1.1 200 OK\r\nconteNt-tyPe: text/html; charset=utf-8\r\n\r\n{$xml}";
++}
++
++['pid' => $pid, 'uri' => $uri] = http_server('genResponses', $output);
++$document = new \DOMDocument();
++$document->loadHTMLFile($uri);
++
++$h1 = $document->getElementsByTagName('h1');
++var_dump($h1->length);
++var_dump($document->saveHTML());
++http_server_kill($pid);
++?>
++--EXPECT--
++int(1)
++string(266) "<!DOCTYPE html>
++<html>
++ <head>
++ <title>GHSA-p3x9-6h7p-cgfc</title>
++
++ <meta charset="utf-8">
++ <meta http-equiv="Content-type" content="text/html; charset=utf-8">
++ </head>
++
++ <body>
++ <h1>GHSA-p3x9-6h7p-cgfc</h1>
++ </body>
++</html>
++"
+diff --git a/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_002.phpt b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_002.phpt
+new file mode 100644
+index 00000000000..1ce468c3b19
+--- /dev/null
++++ b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_002.phpt
+@@ -0,0 +1,60 @@
++--TEST--
++GHSA-p3x9-6h7p-cgfc: libxml streams use wrong `content-type` header when requesting a redirected resource (Missing content-type)
++--EXTENSIONS--
++dom
++--SKIPIF--
++<?php
++if (@!include "./ext/standard/tests/http/newserver.inc") die('skip server.inc not available');
++http_server_skipif();
++?>
++--FILE--
++<?php
++require "./ext/standard/tests/http/newserver.inc";
++
++function genResponses($server) {
++ $uri = 'http://' . stream_socket_get_name($server, false);
++ yield "data://text/plain,HTTP/1.1 302 Moved Temporarily\r\nLocation: $uri/document.xml\r\nContent-Type: text/html;charset=utf-16\r\n\r\n";
++ $xml = <<<'EOT'
++ <!doctype html>
++ <html>
++ <head>
++ <title>GHSA-p3x9-6h7p-cgfc</title>
++
++ <meta charset="utf-8" />
++ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
++ </head>
++
++ <body>
++ <h1>GHSA-p3x9-6h7p-cgfc</h1>
++ </body>
++ </html>
++ EOT;
++ // Missing content-type in actual response.
++ yield "data://text/plain,HTTP/1.1 200 OK\r\n\r\n{$xml}";
++}
++
++['pid' => $pid, 'uri' => $uri] = http_server('genResponses', $output);
++$document = new \DOMDocument();
++$document->loadHTMLFile($uri);
++
++$h1 = $document->getElementsByTagName('h1');
++var_dump($h1->length);
++var_dump($document->saveHTML());
++http_server_kill($pid);
++?>
++--EXPECT--
++int(1)
++string(266) "<!DOCTYPE html>
++<html>
++ <head>
++ <title>GHSA-p3x9-6h7p-cgfc</title>
++
++ <meta charset="utf-8">
++ <meta http-equiv="Content-type" content="text/html; charset=utf-8">
++ </head>
++
++ <body>
++ <h1>GHSA-p3x9-6h7p-cgfc</h1>
++ </body>
++</html>
++"
+diff --git a/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_003.phpt b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_003.phpt
+new file mode 100644
+index 00000000000..b8cac7e3247
+--- /dev/null
++++ b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_003.phpt
+@@ -0,0 +1,60 @@
++--TEST--
++GHSA-p3x9-6h7p-cgfc: libxml streams use wrong `content-type` header when requesting a redirected resource (Reason with colon)
++--EXTENSIONS--
++dom
++--SKIPIF--
++<?php
++if (@!include "./ext/standard/tests/http/newserver.inc") die('skip server.inc not available');
++http_server_skipif();
++?>
++--FILE--
++<?php
++require "./ext/standard/tests/http/newserver.inc";
++
++function genResponses($server) {
++ $uri = 'http://' . stream_socket_get_name($server, false);
++ yield "data://text/plain,HTTP/1.1 302 Moved Temporarily\r\nLocation: $uri/document.xml\r\nContent-Type: text/html;charset=utf-16\r\n\r\n";
++ $xml = <<<'EOT'
++ <!doctype html>
++ <html>
++ <head>
++ <title>GHSA-p3x9-6h7p-cgfc</title>
++
++ <meta charset="utf-8" />
++ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
++ </head>
++
++ <body>
++ <h1>GHSA-p3x9-6h7p-cgfc</h1>
++ </body>
++ </html>
++ EOT;
++ // Missing content-type in actual response.
++ yield "data://text/plain,HTTP/1.1 200 OK: This is fine\r\n\r\n{$xml}";
++}
++
++['pid' => $pid, 'uri' => $uri] = http_server('genResponses', $output);
++$document = new \DOMDocument();
++$document->loadHTMLFile($uri);
++
++$h1 = $document->getElementsByTagName('h1');
++var_dump($h1->length);
++var_dump($document->saveHTML());
++http_server_kill($pid);
++?>
++--EXPECT--
++int(1)
++string(266) "<!DOCTYPE html>
++<html>
++ <head>
++ <title>GHSA-p3x9-6h7p-cgfc</title>
++
++ <meta charset="utf-8">
++ <meta http-equiv="Content-type" content="text/html; charset=utf-8">
++ </head>
++
++ <body>
++ <h1>GHSA-p3x9-6h7p-cgfc</h1>
++ </body>
++</html>
++"
+diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c
+index 4b9e6a918d4..1866b7b21f4 100644
+--- a/ext/libxml/libxml.c
++++ b/ext/libxml/libxml.c
+@@ -420,42 +420,53 @@ php_libxml_input_buffer_create_filename(const char *URI, xmlCharEncoding enc)
+ if (Z_TYPE(s->wrapperdata) == IS_ARRAY) {
+ zval *header;
+
+- ZEND_HASH_FOREACH_VAL_IND(Z_ARRVAL(s->wrapperdata), header) {
++ /* Scan backwards: The header array might contain the headers for multiple responses, if
++ * a redirect was followed.
++ */
++ ZEND_HASH_REVERSE_FOREACH_VAL_IND(Z_ARRVAL(s->wrapperdata), header) {
+ const char buf[] = "Content-Type:";
+- if (Z_TYPE_P(header) == IS_STRING &&
+- !zend_binary_strncasecmp(Z_STRVAL_P(header), Z_STRLEN_P(header), buf, sizeof(buf)-1, sizeof(buf)-1)) {
+- char *needle = estrdup("charset=");
+- char *haystack = estrndup(Z_STRVAL_P(header), Z_STRLEN_P(header));
+- char *encoding = php_stristr(haystack, needle, Z_STRLEN_P(header), sizeof("charset=")-1);
+-
+- if (encoding) {
+- char *end;
+-
+- encoding += sizeof("charset=")-1;
+- if (*encoding == '"') {
+- encoding++;
+- }
+- end = strchr(encoding, ';');
+- if (end == NULL) {
+- end = encoding + strlen(encoding);
+- }
+- end--; /* end == encoding-1 isn't a buffer underrun */
+- while (*end == ' ' || *end == '\t') {
+- end--;
+- }
+- if (*end == '"') {
+- end--;
+- }
+- if (encoding >= end) continue;
+- *(end+1) = '\0';
+- enc = xmlParseCharEncoding(encoding);
+- if (enc <= XML_CHAR_ENCODING_NONE) {
+- enc = XML_CHAR_ENCODING_NONE;
++ if (Z_TYPE_P(header) == IS_STRING) {
++ /* If no colon is found in the header, we assume it's the HTTP status line and bail out. */
++ char *colon = memchr(Z_STRVAL_P(header), ':', Z_STRLEN_P(header));
++ char *space = memchr(Z_STRVAL_P(header), ' ', Z_STRLEN_P(header));
++ if (colon == NULL || space < colon) {
++ break;
++ }
++
++ if (!zend_binary_strncasecmp(Z_STRVAL_P(header), Z_STRLEN_P(header), buf, sizeof(buf)-1, sizeof(buf)-1)) {
++ char *needle = estrdup("charset=");
++ char *haystack = estrndup(Z_STRVAL_P(header), Z_STRLEN_P(header));
++ char *encoding = php_stristr(haystack, needle, Z_STRLEN_P(header), sizeof("charset=")-1);
++
++ if (encoding) {
++ char *end;
++
++ encoding += sizeof("charset=")-1;
++ if (*encoding == '"') {
++ encoding++;
++ }
++ end = strchr(encoding, ';');
++ if (end == NULL) {
++ end = encoding + strlen(encoding);
++ }
++ end--; /* end == encoding-1 isn't a buffer underrun */
++ while (*end == ' ' || *end == '\t') {
++ end--;
++ }
++ if (*end == '"') {
++ end--;
++ }
++ if (encoding >= end) continue;
++ *(end+1) = '\0';
++ enc = xmlParseCharEncoding(encoding);
++ if (enc <= XML_CHAR_ENCODING_NONE) {
++ enc = XML_CHAR_ENCODING_NONE;
++ }
+ }
++ efree(haystack);
++ efree(needle);
++ break; /* found content-type */
+ }
+- efree(haystack);
+- efree(needle);
+- break; /* found content-type */
+ }
+ } ZEND_HASH_FOREACH_END();
+ }
+diff --git a/ext/standard/tests/http/newserver.inc b/ext/standard/tests/http/newserver.inc
+new file mode 100644
+index 00000000000..5c636705e8c
+--- /dev/null
++++ b/ext/standard/tests/http/newserver.inc
+@@ -0,0 +1,124 @@
++<?php declare(strict_types=1);
++
++function http_server_skipif() {
++
++ if (!function_exists('pcntl_fork')) die('skip pcntl_fork() not available');
++ if (!function_exists('posix_kill')) die('skip posix_kill() not available');
++ if (!stream_socket_server('tcp://localhost:0')) die('skip stream_socket_server() failed');
++}
++
++function http_server_init(&$output = null) {
++ pcntl_alarm(60);
++
++ $server = stream_socket_server('tcp://localhost:0', $errno, $errstr);
++ if (!$server) {
++ return false;
++ }
++
++ if ($output === null) {
++ $output = tmpfile();
++ if ($output === false) {
++ return false;
++ }
++ }
++
++ $pid = pcntl_fork();
++ if ($pid == -1) {
++ die('could not fork');
++ } else if ($pid) {
++ return [
++ 'pid' => $pid,
++ 'uri' => 'http://' . stream_socket_get_name($server, false),
++ ];
++ }
++
++ return $server;
++}
++
++/* Minimal HTTP server with predefined responses.
++ *
++ * $socket_string is the socket to create and listen on (e.g. tcp://127.0.0.1:1234)
++ * $files is an iterable of files or callable generator yielding files.
++ * containing N responses for N expected requests. Server dies after N requests.
++ * $output is a stream on which everything sent by clients is written to
++ */
++function http_server($files, &$output = null) {
++
++ if (!is_resource($server = http_server_init($output))) {
++ return $server;
++ }
++
++ if (is_callable($files)) {
++ $files = $files($server);
++ }
++
++ foreach($files as $file) {
++
++ $sock = stream_socket_accept($server);
++ if (!$sock) {
++ exit(1);
++ }
++
++ // read headers
++
++ $content_length = 0;
++
++ stream_set_blocking($sock, false);
++ while (!feof($sock)) {
++
++ list($r, $w, $e) = array(array($sock), null, null);
++ if (!stream_select($r, $w, $e, 1)) continue;
++
++ $line = stream_get_line($sock, 8192, "\r\n");
++ if ($line === '') {
++ fwrite($output, "\r\n");
++ break;
++ } else if ($line !== false) {
++ fwrite($output, "$line\r\n");
++
++ if (preg_match('#^Content-Length\s*:\s*([[:digit:]]+)\s*$#i', $line, $matches)) {
++ $content_length = (int) $matches[1];
++ }
++ }
++ }
++ stream_set_blocking($sock, true);
++
++ // read content
++
++ if ($content_length > 0) {
++ stream_copy_to_stream($sock, $output, $content_length);
++ }
++
++ // send response
++
++ $fd = fopen($file, 'rb');
++ stream_copy_to_stream($fd, $sock);
++
++ fclose($sock);
++ }
++
++ exit(0);
++}
++
++function http_server_sleep($micro_seconds = 500000)
++{
++ if (!is_resource($server = http_server_init($output))) {
++ return $server;
++ }
++
++ $sock = stream_socket_accept($server);
++ if (!$sock) {
++ exit(1);
++ }
++
++ usleep($micro_seconds);
++
++ fclose($sock);
++
++ exit(0);
++}
++
++function http_server_kill(int $pid) {
++ posix_kill($pid, SIGTERM);
++ pcntl_waitpid($pid, $status);
++}
+--
+2.48.1
+
+From c5e836c5f98c6a01778595d448bb6a5b84eccec1 Mon Sep 17 00:00:00 2001
+From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
+Date: Wed, 18 Dec 2024 18:44:05 +0100
+Subject: [PATCH 09/11] Fix GHSA-wg4p-4hqh-c3g9
+
+(cherry picked from commit 0e715e71d945b68f8ccedd62c5960df747af6625)
+(cherry picked from commit 294140ee981fda6a38244215e4b16e53b7f5b2a6)
+---
+ ext/xml/tests/toffset_bounds.phpt | 42 +++++++++++++++++++++++++++++++
+ ext/xml/xml.c | 12 ++++++---
+ 2 files changed, 50 insertions(+), 4 deletions(-)
+ create mode 100644 ext/xml/tests/toffset_bounds.phpt
+
+diff --git a/ext/xml/tests/toffset_bounds.phpt b/ext/xml/tests/toffset_bounds.phpt
+new file mode 100644
+index 00000000000..5a3fd22f86c
+--- /dev/null
++++ b/ext/xml/tests/toffset_bounds.phpt
+@@ -0,0 +1,42 @@
++--TEST--
++XML_OPTION_SKIP_TAGSTART bounds
++--EXTENSIONS--
++xml
++--FILE--
++<?php
++$sample = "<?xml version=\"1.0\"?><test><child/></test>";
++$parser = xml_parser_create();
++xml_parser_set_option($parser, XML_OPTION_SKIP_TAGSTART, 100);
++$res = xml_parse_into_struct($parser,$sample,$vals,$index);
++var_dump($vals);
++?>
++--EXPECT--
++array(3) {
++ [0]=>
++ array(3) {
++ ["tag"]=>
++ string(0) ""
++ ["type"]=>
++ string(4) "open"
++ ["level"]=>
++ int(1)
++ }
++ [1]=>
++ array(3) {
++ ["tag"]=>
++ string(0) ""
++ ["type"]=>
++ string(8) "complete"
++ ["level"]=>
++ int(2)
++ }
++ [2]=>
++ array(3) {
++ ["tag"]=>
++ string(0) ""
++ ["type"]=>
++ string(5) "close"
++ ["level"]=>
++ int(1)
++ }
++}
+diff --git a/ext/xml/xml.c b/ext/xml/xml.c
+index 6fe6151c7a1..b56bf79f55d 100644
+--- a/ext/xml/xml.c
++++ b/ext/xml/xml.c
+@@ -773,9 +773,11 @@ void _xml_startElementHandler(void *userData, const XML_Char *name, const XML_Ch
+ array_init(&tag);
+ array_init(&atr);
+
+- _xml_add_to_info(parser, ZSTR_VAL(tag_name) + parser->toffset);
++ char *skipped_tag_name = SKIP_TAGSTART(ZSTR_VAL(tag_name));
+
+- add_assoc_string(&tag, "tag", SKIP_TAGSTART(ZSTR_VAL(tag_name))); /* cast to avoid gcc-warning */
++ _xml_add_to_info(parser, skipped_tag_name);
++
++ add_assoc_string(&tag, "tag", skipped_tag_name);
+ add_assoc_string(&tag, "type", "open");
+ add_assoc_long(&tag, "level", parser->level);
+
+@@ -842,9 +844,11 @@ void _xml_endElementHandler(void *userData, const XML_Char *name)
+ } else {
+ array_init(&tag);
+
+- _xml_add_to_info(parser, ZSTR_VAL(tag_name) + parser->toffset);
++ char *skipped_tag_name = SKIP_TAGSTART(ZSTR_VAL(tag_name));
++
++ _xml_add_to_info(parser, skipped_tag_name);
+
+- add_assoc_string(&tag, "tag", SKIP_TAGSTART(ZSTR_VAL(tag_name))); /* cast to avoid gcc-warning */
++ add_assoc_string(&tag, "tag", skipped_tag_name);
+ add_assoc_string(&tag, "type", "close");
+ add_assoc_long(&tag, "level", parser->level);
+
+--
+2.48.1
+
+From 3faf7b2017ccd1e7347c30cf64cddcb684300cba Mon Sep 17 00:00:00 2001
+From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
+Date: Fri, 17 Nov 2023 19:45:40 +0100
+Subject: [PATCH 10/11] Fix GH-12702: libxml2 2.12.0 issue building from src
+
+Fixes GH-12702.
+
+Co-authored-by: nono303 <github@nono303.net>
+(cherry picked from commit 6a76e5d0a2dcf46b4ab74cc3ffcbfeb860c4fdb3)
+(cherry picked from commit d7ab2bb9856d938fca7989575695c14c25892589)
+---
+ ext/dom/document.c | 1 +
+ ext/libxml/php_libxml.h | 1 +
+ 2 files changed, 2 insertions(+)
+
+diff --git a/ext/dom/document.c b/ext/dom/document.c
+index af06fb41240..f8071774b92 100644
+--- a/ext/dom/document.c
++++ b/ext/dom/document.c
+@@ -25,6 +25,7 @@
+ #if HAVE_LIBXML && HAVE_DOM
+ #include "php_dom.h"
+ #include <libxml/SAX.h>
++#include <libxml/xmlsave.h>
+ #ifdef LIBXML_SCHEMAS_ENABLED
+ #include <libxml/relaxng.h>
+ #include <libxml/xmlschemas.h>
+diff --git a/ext/libxml/php_libxml.h b/ext/libxml/php_libxml.h
+index 92028d5703e..6f3295b5241 100644
+--- a/ext/libxml/php_libxml.h
++++ b/ext/libxml/php_libxml.h
+@@ -37,6 +37,7 @@ extern zend_module_entry libxml_module_entry;
+
+ #include "zend_smart_str.h"
+ #include <libxml/tree.h>
++#include <libxml/parser.h>
+
+ #define LIBXML_SAVE_NOEMPTYTAG 1<<2
+
+--
+2.48.1
+
+From 8ab957ca87b42b808aec7fd472fbc4063073a119 Mon Sep 17 00:00:00 2001
+From: Remi Collet <remi@remirepo.net>
+Date: Thu, 13 Mar 2025 09:39:19 +0100
+Subject: [PATCH 11/11] NEWS
+
+(cherry picked from commit adae2b8de8963ac6f92103803bf91a5174172f88)
+---
+ NEWS | 17 +++++++++++++++++
+ 1 file changed, 17 insertions(+)
+
+diff --git a/NEWS b/NEWS
+index 09cf2cfa0bb..fda646c7010 100644
+--- a/NEWS
++++ b/NEWS
+@@ -1,6 +1,23 @@
+ PHP NEWS
+ |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
++Backported from 8.1.32
++
++- LibXML:
++ . Fixed GHSA-wg4p-4hqh-c3g9 (Reocurrence of #72714). (nielsdos)
++ . Fixed GHSA-p3x9-6h7p-cgfc (libxml streams use wrong `content-type` header
++ when requesting a redirected resource). (CVE-2025-1219) (timwolla)
++
++- Streams:
++ . Fixed GHSA-hgf54-96fm-v528 (Stream HTTP wrapper header check might omit
++ basic auth header). (CVE-2025-1736) (Jakub Zelenka)
++ . Fixed GHSA-52jp-hrpf-2jff (Stream HTTP wrapper truncate redirect location
++ to 1024 bytes). (CVE-2025-1861) (Jakub Zelenka)
++ . Fixed GHSA-pcmh-g36c-qc44 (Streams HTTP wrapper does not fail for headers
++ without colon). (CVE-2025-1734) (Jakub Zelenka)
++ . Fixed GHSA-v8xr-gpvj-cx9g (Header parser of `http` stream wrapper does not
++ handle folded headers). (CVE-2025-1217) (Jakub Zelenka)
++
+ Backported from 8.1.31
+
+ - CLI:
+--
+2.48.1
+
diff --git a/php-cve-2025-1220.patch b/php-cve-2025-1220.patch
new file mode 100644
index 0000000..25f3b61
--- /dev/null
+++ b/php-cve-2025-1220.patch
@@ -0,0 +1,154 @@
+From d407d8a8735ebf43bee3e6b49fb013b8aa4b6bfc Mon Sep 17 00:00:00 2001
+From: Jakub Zelenka <bukka@php.net>
+Date: Thu, 10 Apr 2025 15:15:36 +0200
+Subject: [PATCH 2/4] Fix GHSA-3cr5-j632-f35r: Null byte in hostnames
+
+This fixes stream_socket_client() and fsockopen().
+
+Specifically it adds a check to parse_ip_address_ex and it also makes
+sure that the \0 is not ignored in fsockopen() hostname formatting.
+
+(cherry picked from commit cac8f7f1cf4939f55f06b68120040f057682d89c)
+(cherry picked from commit 36150278addd8686a9899559241296094bd57282)
+---
+ ext/standard/fsock.c | 27 +++++++++++++++++--
+ .../tests/network/ghsa-3cr5-j632-f35r.phpt | 21 +++++++++++++++
+ .../tests/streams/ghsa-3cr5-j632-f35r.phpt | 26 ++++++++++++++++++
+ main/streams/xp_socket.c | 9 ++++---
+ 4 files changed, 78 insertions(+), 5 deletions(-)
+ create mode 100644 ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt
+ create mode 100644 ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt
+
+diff --git a/ext/standard/fsock.c b/ext/standard/fsock.c
+index fe8fbea85ca..df6a74b078f 100644
+--- a/ext/standard/fsock.c
++++ b/ext/standard/fsock.c
+@@ -25,6 +25,28 @@
+ #include "php_network.h"
+ #include "file.h"
+
++static size_t php_fsockopen_format_host_port(char **message, const char *prefix, size_t prefix_len,
++ const char *host, size_t host_len, zend_long port)
++{
++ char portbuf[32];
++ int portlen = snprintf(portbuf, sizeof(portbuf), ":" ZEND_LONG_FMT, port);
++ size_t total_len = prefix_len + host_len + portlen;
++
++ char *result = emalloc(total_len + 1);
++
++ if (prefix_len > 0) {
++ memcpy(result, prefix, prefix_len);
++ }
++ memcpy(result + prefix_len, host, host_len);
++ memcpy(result + prefix_len + host_len, portbuf, portlen);
++
++ result[total_len] = '\0';
++
++ *message = result;
++
++ return total_len;
++}
++
+ /* {{{ php_fsockopen() */
+
+ static void php_fsockopen_stream(INTERNAL_FUNCTION_PARAMETERS, int persistent)
+@@ -59,11 +81,12 @@ static void php_fsockopen_stream(INTERNAL_FUNCTION_PARAMETERS, int persistent)
+ ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
+
+ if (persistent) {
+- spprintf(&hashkey, 0, "pfsockopen__%s:" ZEND_LONG_FMT, host, port);
++ php_fsockopen_format_host_port(&hashkey, "pfsockopen__", strlen("pfsockopen__"), host,
++ host_len, port);
+ }
+
+ if (port > 0) {
+- hostname_len = spprintf(&hostname, 0, "%s:" ZEND_LONG_FMT, host, port);
++ hostname_len = php_fsockopen_format_host_port(&hostname, "", 0, host, host_len, port);
+ } else {
+ hostname_len = host_len;
+ hostname = host;
+diff --git a/ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt b/ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt
+new file mode 100644
+index 00000000000..e16d3fa9060
+--- /dev/null
++++ b/ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt
+@@ -0,0 +1,21 @@
++--TEST--
++GHSA-3cr5-j632-f35r: Null byte termination in fsockopen()
++--FILE--
++<?php
++
++$server = stream_socket_server("tcp://localhost:0");
++
++if (preg_match('/:(\d+)$/', stream_socket_get_name($server, false), $m)) {
++ $client = fsockopen("localhost\0.example.com", intval($m[1]));
++ var_dump($client);
++ if ($client) {
++ fclose($client);
++ }
++}
++fclose($server);
++
++?>
++--EXPECTF--
++
++Warning: fsockopen(): unable to connect to localhost:%d (The hostname must not contain null bytes) in %s
++bool(false)
+diff --git a/ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt b/ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt
+new file mode 100644
+index 00000000000..bc1f34eaf58
+--- /dev/null
++++ b/ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt
+@@ -0,0 +1,26 @@
++--TEST--
++GHSA-3cr5-j632-f35r: Null byte termination in stream_socket_client()
++--FILE--
++<?php
++
++$server = stream_socket_server("tcp://localhost:0");
++$socket_name = stream_socket_get_name($server, false);
++
++if (preg_match('/:(\d+)$/', $socket_name, $m)) {
++ $port = $m[1];
++ $client = stream_socket_client("tcp://localhost\0.example.com:$port");
++ var_dump($client);
++ if ($client) {
++ fclose($client);
++ }
++} else {
++ echo "Could not extract port from socket name: $socket_name\n";
++}
++
++fclose($server);
++
++?>
++--EXPECTF--
++
++Warning: stream_socket_client(): unable to connect to tcp://localhost\0.example.com:%d (The hostname must not contain null bytes) in %s
++bool(false)
+diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c
+index 46b23b63ada..7a192ea6c0b 100644
+--- a/main/streams/xp_socket.c
++++ b/main/streams/xp_socket.c
+@@ -580,12 +580,15 @@ static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *po
+ char *colon;
+ char *host = NULL;
+
+-#ifdef HAVE_IPV6
+- char *p;
++ if (memchr(str, '\0', str_len)) {
++ *err = strpprintf(0, "The hostname must not contain null bytes");
++ return NULL;
++ }
+
++#ifdef HAVE_IPV6
+ if (*(str) == '[' && str_len > 1) {
+ /* IPV6 notation to specify raw address with port (i.e. [fe80::1]:80) */
+- p = memchr(str + 1, ']', str_len - 2);
++ char *p = memchr(str + 1, ']', str_len - 2);
+ if (!p || *(p + 1) != ':') {
+ if (get_err) {
+ *err = strpprintf(0, "Failed to parse IPv6 address \"%s\"", str);
+--
+2.50.0
+
diff --git a/php-cve-2025-1734.patch b/php-cve-2025-1734.patch
new file mode 100644
index 0000000..6c9aa52
--- /dev/null
+++ b/php-cve-2025-1734.patch
@@ -0,0 +1,314 @@
+From 0b965cf85f512b1a7b87f100ac77e4aa13f7f421 Mon Sep 17 00:00:00 2001
+From: Jakub Zelenka <bukka@php.net>
+Date: Sun, 19 Jan 2025 17:49:53 +0100
+Subject: [PATCH 02/11] Fix GHSA-pcmh-g36c-qc44: http headers without colon
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The header line must contain colon otherwise it is invalid and it needs
+to fail.
+
+Reviewed-by: Tim Düsterhus <tim@tideways-gmbh.com>
+(cherry picked from commit 0548c4c1756724a89ef8310709419b08aadb2b3b)
+(cherry picked from commit e81d0cd14bfeb17e899c73e3aece4991bbda76af)
+---
+ ext/standard/http_fopen_wrapper.c | 51 ++++++++++++++-----
+ ext/standard/tests/http/bug47021.phpt | 26 ++++++----
+ ext/standard/tests/http/bug75535.phpt | 4 +-
+ .../tests/http/ghsa-pcmh-g36c-qc44-001.phpt | 51 +++++++++++++++++++
+ .../tests/http/ghsa-pcmh-g36c-qc44-002.phpt | 51 +++++++++++++++++++
+ 5 files changed, 156 insertions(+), 27 deletions(-)
+ create mode 100644 ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt
+ create mode 100644 ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt
+
+diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
+index 08386cfafcd..071d6a4d119 100644
+--- a/ext/standard/http_fopen_wrapper.c
++++ b/ext/standard/http_fopen_wrapper.c
+@@ -119,6 +119,7 @@ static zend_bool check_has_header(const char *headers, const char *header) {
+ typedef struct _php_stream_http_response_header_info {
+ php_stream_filter *transfer_encoding;
+ size_t file_size;
++ zend_bool error;
+ zend_bool follow_location;
+ char location[HTTP_HEADER_BLOCK_SIZE];
+ } php_stream_http_response_header_info;
+@@ -128,6 +129,7 @@ static void php_stream_http_response_header_info_init(
+ {
+ header_info->transfer_encoding = NULL;
+ header_info->file_size = 0;
++ header_info->error = 0;
+ header_info->follow_location = 1;
+ header_info->location[0] = '\0';
+ }
+@@ -165,10 +167,11 @@ static zend_bool php_stream_http_response_header_trim(char *http_header_line,
+ /* Process folding headers of the current line and if there are none, parse last full response
+ * header line. It returns NULL if the last header is finished, otherwise it returns updated
+ * last header line. */
+-static zend_string *php_stream_http_response_headers_parse(php_stream *stream,
+- php_stream_context *context, int options, zend_string *last_header_line_str,
+- char *header_line, size_t *header_line_length, int response_code,
+- zval *response_header, php_stream_http_response_header_info *header_info)
++static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *wrapper,
++ php_stream *stream, php_stream_context *context, int options,
++ zend_string *last_header_line_str, char *header_line, size_t *header_line_length,
++ int response_code, zval *response_header,
++ php_stream_http_response_header_info *header_info)
+ {
+ char *last_header_line = ZSTR_VAL(last_header_line_str);
+ size_t last_header_line_length = ZSTR_LEN(last_header_line_str);
+@@ -211,6 +214,19 @@ static zend_string *php_stream_http_response_headers_parse(php_stream *stream,
+ /* Find header separator position. */
+ char *last_header_value = memchr(last_header_line, ':', last_header_line_length);
+ if (last_header_value) {
++ /* Verify there is no space in header name */
++ char *last_header_name = last_header_line + 1;
++ while (last_header_name < last_header_value) {
++ if (*last_header_name == ' ' || *last_header_name == '\t') {
++ header_info->error = 1;
++ php_stream_wrapper_log_error(wrapper, options,
++ "HTTP invalid response format (space in header name)!");
++ zend_string_efree(last_header_line_str);
++ return NULL;
++ }
++ ++last_header_name;
++ }
++
+ last_header_value++; /* Skip ':'. */
+
+ /* Strip leading whitespace. */
+@@ -219,9 +235,12 @@ static zend_string *php_stream_http_response_headers_parse(php_stream *stream,
+ last_header_value++;
+ }
+ } else {
+- /* There is no colon. Set the value to the end of the header line, which is effectively
+- * an empty string. */
+- last_header_value = last_header_line_end;
++ /* There is no colon which means invalid response so error. */
++ header_info->error = 1;
++ php_stream_wrapper_log_error(wrapper, options,
++ "HTTP invalid response format (no colon in header line)!");
++ zend_string_efree(last_header_line_str);
++ return NULL;
+ }
+
+ zend_bool store_header = 1;
+@@ -927,10 +946,16 @@ finish:
+
+ if (last_header_line_str != NULL) {
+ /* Parse last header line. */
+- last_header_line_str = php_stream_http_response_headers_parse(stream, context,
+- options, last_header_line_str, http_header_line, &http_header_line_length,
+- response_code, response_header, &header_info);
+- if (last_header_line_str != NULL) {
++ last_header_line_str = php_stream_http_response_headers_parse(wrapper, stream,
++ context, options, last_header_line_str, http_header_line,
++ &http_header_line_length, response_code, response_header, &header_info);
++ if (EXPECTED(last_header_line_str == NULL)) {
++ if (UNEXPECTED(header_info.error)) {
++ php_stream_close(stream);
++ stream = NULL;
++ goto out;
++ }
++ } else {
+ /* Folding header present so continue. */
+ continue;
+ }
+@@ -960,8 +985,8 @@ finish:
+
+ /* If the stream was closed early, we still want to process the last line to keep BC. */
+ if (last_header_line_str != NULL) {
+- php_stream_http_response_headers_parse(stream, context, options, last_header_line_str,
+- NULL, NULL, response_code, response_header, &header_info);
++ php_stream_http_response_headers_parse(wrapper, stream, context, options,
++ last_header_line_str, NULL, NULL, response_code, response_header, &header_info);
+ }
+
+ if (!reqok || (header_info.location[0] != '\0' && header_info.follow_location)) {
+diff --git a/ext/standard/tests/http/bug47021.phpt b/ext/standard/tests/http/bug47021.phpt
+index f3db3e1be23..a287b714c62 100644
+--- a/ext/standard/tests/http/bug47021.phpt
++++ b/ext/standard/tests/http/bug47021.phpt
+@@ -47,9 +47,9 @@ function do_test($num_spaces, $leave_trailing_space=false) {
+ ];
+ $pid = http_server('tcp://127.0.0.1:12342', $responses);
+
+- echo file_get_contents('http://127.0.0.1:12342/', false, $ctx);
++ echo file_get_contents('http://127.0.0.1:12342', false, $ctx);
+ echo "\n";
+- echo file_get_contents('http://127.0.0.1:12342/', false, $ctx);
++ echo file_get_contents('http://127.0.0.1:12342', false, $ctx);
+ echo "\n";
+
+ http_server_kill($pid);
+@@ -70,23 +70,27 @@ do_test(1, true);
+ echo "\n";
+
+ ?>
+---EXPECT--
++--EXPECTF--
++
+ Type='text/plain'
+ Hello
+-Size=5
+-World
++
++Warning: file_get_contents(http://%s:%d): failed to open stream: HTTP invalid response format (no colon in header line)! in %s
++
+
+ Type='text/plain'
+ Hello
+-Size=5
+-World
++
++Warning: file_get_contents(http://%s:%d): failed to open stream: HTTP invalid response format (no colon in header line)! in %s
++
+
+ Type='text/plain'
+ Hello
+-Size=5
+-World
++
++Warning: file_get_contents(http://%s:%d): failed to open stream: HTTP invalid response format (no colon in header line)! in %s
++
+
+ Type='text/plain'
+ Hello
+-Size=5
+-World
++
++Warning: file_get_contents(http://%s:%d): failed to open stream: HTTP invalid response format (no colon in header line)! in %s
+diff --git a/ext/standard/tests/http/bug75535.phpt b/ext/standard/tests/http/bug75535.phpt
+index 9bf298cc065..e3757ba4f1d 100644
+--- a/ext/standard/tests/http/bug75535.phpt
++++ b/ext/standard/tests/http/bug75535.phpt
+@@ -22,10 +22,8 @@ http_server_kill($pid);
+ ==DONE==
+ --EXPECT--
+ string(0) ""
+-array(2) {
++array(1) {
+ [0]=>
+ string(15) "HTTP/1.0 200 Ok"
+- [1]=>
+- string(14) "Content-Length"
+ }
+ ==DONE==
+diff --git a/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt
+new file mode 100644
+index 00000000000..53baa1c92d6
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt
+@@ -0,0 +1,51 @@
++--TEST--
++GHSA-pcmh-g36c-qc44: Header parser of http stream wrapper does not verify header name and colon (colon)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++ $ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
++
++ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html\r\nWrong-Header\r\nGood-Header: test\r\n\r\nbody\r\n");
++CODE;
++
++$clientCode = <<<'CODE'
++ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
++ switch($notification_code) {
++ case STREAM_NOTIFY_MIME_TYPE_IS:
++ echo "Found the mime-type: ", $message, PHP_EOL;
++ break;
++ }
++ }
++
++ $ctx = stream_context_create();
++ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
++ var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++Found the mime-type: text/html
++
++Warning: file_get_contents(http://127.0.0.1:%d): failed to open stream: HTTP invalid response format (no colon in header line)! in %s
++bool(false)
++array(2) {
++ [0]=>
++ string(15) "HTTP/1.0 200 Ok"
++ [1]=>
++ string(23) "Content-Type: text/html"
++}
+diff --git a/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt
+new file mode 100644
+index 00000000000..5aa0ee00618
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt
+@@ -0,0 +1,51 @@
++--TEST--
++GHSA-pcmh-g36c-qc44: Header parser of http stream wrapper does not verify header name and colon (name)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++ $ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
++
++ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html\r\nWrong-Header : test\r\nGood-Header: test\r\n\r\nbody\r\n");
++CODE;
++
++$clientCode = <<<'CODE'
++ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
++ switch($notification_code) {
++ case STREAM_NOTIFY_MIME_TYPE_IS:
++ echo "Found the mime-type: ", $message, PHP_EOL;
++ break;
++ }
++ }
++
++ $ctx = stream_context_create();
++ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
++ var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++Found the mime-type: text/html
++
++Warning: file_get_contents(http://127.0.0.1:%d): failed to open stream: HTTP invalid response format (space in header name)! in %s
++bool(false)
++array(2) {
++ [0]=>
++ string(15) "HTTP/1.0 200 Ok"
++ [1]=>
++ string(23) "Content-Type: text/html"
++}
+--
+2.48.1
+
diff --git a/php-cve-2025-1735.patch b/php-cve-2025-1735.patch
new file mode 100644
index 0000000..e144cc4
--- /dev/null
+++ b/php-cve-2025-1735.patch
@@ -0,0 +1,492 @@
+From df2ecf34256c4a301e8959fe2eed0323f8b1b57a Mon Sep 17 00:00:00 2001
+From: Jakub Zelenka <bukka@php.net>
+Date: Tue, 4 Mar 2025 17:23:01 +0100
+Subject: [PATCH 3/4] Fix GHSA-hrwm-9436-5mv3: pgsql escaping no error checks
+
+This adds error checks for escape function is pgsql and pdo_pgsql
+extensions. It prevents possibility of storing not properly escaped
+data which could potentially lead to some security issues.
+
+(cherry picked from commit 9376aeef9f8ff81f2705b8016237ec3e30bdee44)
+(cherry picked from commit 7633d987cc11ee2601223e73cfdb8b31fed5980f)
+---
+ ext/pdo_pgsql/pgsql_driver.c | 10 +-
+ ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt | 22 ++++
+ ext/pgsql/pgsql.c | 129 +++++++++++++++----
+ ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt | 64 +++++++++
+ 4 files changed, 202 insertions(+), 23 deletions(-)
+ create mode 100644 ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt
+ create mode 100644 ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt
+
+diff --git a/ext/pdo_pgsql/pgsql_driver.c b/ext/pdo_pgsql/pgsql_driver.c
+index e578bbc2720..021471cefc0 100644
+--- a/ext/pdo_pgsql/pgsql_driver.c
++++ b/ext/pdo_pgsql/pgsql_driver.c
+@@ -323,11 +323,15 @@ static int pgsql_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unqu
+ unsigned char *escaped;
+ pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
+ size_t tmp_len;
++ int err;
+
+ switch (paramtype) {
+ case PDO_PARAM_LOB:
+ /* escapedlen returned by PQescapeBytea() accounts for trailing 0 */
+ escaped = PQescapeByteaConn(H->server, (unsigned char *)unquoted, unquotedlen, &tmp_len);
++ if (escaped == NULL) {
++ return 0;
++ }
+ *quotedlen = tmp_len + 1;
+ *quoted = emalloc(*quotedlen + 1);
+ memcpy((*quoted)+1, escaped, *quotedlen-2);
+@@ -339,7 +343,11 @@ static int pgsql_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unqu
+ default:
+ *quoted = safe_emalloc(2, unquotedlen, 3);
+ (*quoted)[0] = '\'';
+- *quotedlen = PQescapeStringConn(H->server, *quoted + 1, unquoted, unquotedlen, NULL);
++ *quotedlen = PQescapeStringConn(H->server, *quoted + 1, unquoted, unquotedlen, &err);
++ if (err) {
++ efree(*quoted);
++ return 0;
++ }
+ (*quoted)[*quotedlen + 1] = '\'';
+ (*quoted)[*quotedlen + 2] = '\0';
+ *quotedlen += 2;
+diff --git a/ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt b/ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt
+new file mode 100644
+index 00000000000..60e13613d04
+--- /dev/null
++++ b/ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt
+@@ -0,0 +1,22 @@
++--TEST--
++#GHSA-hrwm-9436-5mv3: pdo_pgsql extension does not check for errors during escaping
++--SKIPIF--
++<?php
++if (!extension_loaded('pdo') || !extension_loaded('pdo_pgsql')) die('skip not loaded');
++require_once dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
++require_once dirname(__FILE__) . '/config.inc';
++PDOTest::skip();
++?>
++--FILE--
++<?php
++require_once dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
++require_once dirname(__FILE__) . '/config.inc';
++$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
++$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
++
++$invalid = "ABC\xff\x30';";
++var_dump($db->quote($invalid));
++
++?>
++--EXPECT--
++bool(false)
+diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c
+index 7dcd56cf144..9e06497125c 100644
+--- a/ext/pgsql/pgsql.c
++++ b/ext/pgsql/pgsql.c
+@@ -4393,10 +4393,16 @@ PHP_FUNCTION(pg_escape_string)
+ to = zend_string_safe_alloc(ZSTR_LEN(from), 2, 0, 0);
+ #ifdef HAVE_PQESCAPE_CONN
+ if (link) {
++ int err;
+ if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) {
+ RETURN_FALSE;
+ }
+- ZSTR_LEN(to) = PQescapeStringConn(pgsql, ZSTR_VAL(to), ZSTR_VAL(from), ZSTR_LEN(from), NULL);
++ ZSTR_LEN(to) = PQescapeStringConn(pgsql, ZSTR_VAL(to), ZSTR_VAL(from), ZSTR_LEN(from), &err);
++ if (err) {
++ zend_throw_exception(zend_ce_exception, "Escaping string failed", 0);
++ zend_string_efree(to);
++ return;
++ }
+ } else
+ #endif
+ {
+@@ -4445,6 +4451,10 @@ PHP_FUNCTION(pg_escape_bytea)
+ } else
+ #endif
+ to = (char *)PQescapeBytea((unsigned char*)from, from_len, &to_len);
++ if (to == NULL) {
++ zend_throw_exception(zend_ce_exception, "Escape failure", 0);
++ return;
++ }
+
+ RETVAL_STRINGL(to, to_len-1); /* to_len includes additional '\0' */
+ PQfreemem(to);
+@@ -5529,7 +5539,7 @@ PHP_PGSQL_API int php_pgsql_meta_data(PGconn *pg_link, const char *table_name, z
+ char *escaped;
+ smart_str querystr = {0};
+ size_t new_len;
+- int i, num_rows;
++ int i, num_rows, err;
+ zval elem;
+
+ if (!*table_name) {
+@@ -5570,7 +5580,14 @@ PHP_PGSQL_API int php_pgsql_meta_data(PGconn *pg_link, const char *table_name, z
+ "WHERE a.attnum > 0 AND c.relname = '");
+ }
+ escaped = (char *)safe_emalloc(strlen(tmp_name2), 2, 1);
+- new_len = PQescapeStringConn(pg_link, escaped, tmp_name2, strlen(tmp_name2), NULL);
++ new_len = PQescapeStringConn(pg_link, escaped, tmp_name2, strlen(tmp_name2), &err);
++ if (err) {
++ php_error_docref(NULL, E_WARNING, "Escaping table name '%s' failed", table_name);
++ efree(src);
++ efree(escaped);
++ smart_str_free(&querystr);
++ return FAILURE;
++ }
+ if (new_len) {
+ smart_str_appendl(&querystr, escaped, new_len);
+ }
+@@ -5578,7 +5595,14 @@ PHP_PGSQL_API int php_pgsql_meta_data(PGconn *pg_link, const char *table_name, z
+
+ smart_str_appends(&querystr, "' AND n.nspname = '");
+ escaped = (char *)safe_emalloc(strlen(tmp_name), 2, 1);
+- new_len = PQescapeStringConn(pg_link, escaped, tmp_name, strlen(tmp_name), NULL);
++ new_len = PQescapeStringConn(pg_link, escaped, tmp_name, strlen(tmp_name), &err);
++ if (err) {
++ php_error_docref(NULL, E_WARNING, "Escaping table namespace '%s' failed", table_name);
++ efree(src);
++ efree(escaped);
++ smart_str_free(&querystr);
++ return FAILURE;
++ }
+ if (new_len) {
+ smart_str_appendl(&querystr, escaped, new_len);
+ }
+@@ -5850,7 +5874,7 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con
+ {
+ zend_string *field = NULL;
+ zval meta, *def, *type, *not_null, *has_default, *is_enum, *val, new_val;
+- int err = 0, skip_field;
++ int err = 0, escape_err = 0, skip_field;
+ php_pgsql_data_type data_type;
+
+ assert(pg_link != NULL);
+@@ -6101,10 +6125,14 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con
+ /* PostgreSQL ignores \0 */
+ str = zend_string_alloc(Z_STRLEN_P(val) * 2, 0);
+ /* better to use PGSQLescapeLiteral since PGescapeStringConn does not handle special \ */
+- ZSTR_LEN(str) = PQescapeStringConn(pg_link, ZSTR_VAL(str), Z_STRVAL_P(val), Z_STRLEN_P(val), NULL);
+- str = zend_string_truncate(str, ZSTR_LEN(str), 0);
+- ZVAL_NEW_STR(&new_val, str);
+- php_pgsql_add_quotes(&new_val, 1);
++ ZSTR_LEN(str) = PQescapeStringConn(pg_link, ZSTR_VAL(str), Z_STRVAL_P(val), Z_STRLEN_P(val), &escape_err);
++ if (escape_err) {
++ err = 1;
++ } else {
++ str = zend_string_truncate(str, ZSTR_LEN(str), 0);
++ ZVAL_NEW_STR(&new_val, str);
++ php_pgsql_add_quotes(&new_val, 1);
++ }
+ }
+ break;
+
+@@ -6126,7 +6154,15 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con
+ }
+ PGSQL_CONV_CHECK_IGNORE();
+ if (err) {
+- php_error_docref(NULL, E_NOTICE, "Expects NULL, string, long or double value for PostgreSQL '%s' (%s)", Z_STRVAL_P(type), ZSTR_VAL(field));
++ if (escape_err) {
++ php_error_docref(NULL, E_NOTICE,
++ "String value escaping failed for PostgreSQL '%s' (%s)",
++ Z_STRVAL_P(type), ZSTR_VAL(field));
++ } else {
++ php_error_docref(NULL, E_NOTICE,
++ "Expects NULL, string, long or double value for PostgreSQL '%s' (%s)",
++ Z_STRVAL_P(type), ZSTR_VAL(field));
++ }
+ }
+ break;
+
+@@ -6406,6 +6442,11 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con
+ #else
+ tmp = PQescapeBytea(Z_STRVAL_P(val), (unsigned char *)Z_STRLEN_P(val), &to_len);
+ #endif
++ if (tmp == NULL) {
++ php_error_docref(NULL, E_NOTICE, "Escaping value failed for %s field (%s)", Z_STRVAL_P(type), ZSTR_VAL(field));
++ err = 1;
++ break;
++ }
+ ZVAL_STRINGL(&new_val, (char *)tmp, to_len - 1); /* PQescapeBytea's to_len includes additional '\0' */
+ PQfreemem(tmp);
+ php_pgsql_add_quotes(&new_val, 1);
+@@ -6488,6 +6529,12 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con
+ zend_hash_update(Z_ARRVAL_P(result), field, &new_val);
+ } else {
+ char *escaped = PGSQLescapeIdentifier(pg_link, ZSTR_VAL(field), ZSTR_LEN(field));
++ if (escaped == NULL) {
++ /* This cannot fail because of invalid string but only due to failed memory allocation */
++ php_error_docref(NULL, E_NOTICE, "Escaping field '%s' failed", ZSTR_VAL(field));
++ err = 1;
++ break;
++ }
+ add_assoc_zval(result, escaped, &new_val);
+ PGSQLfree(escaped);
+ }
+@@ -6566,7 +6613,7 @@ static int do_exec(smart_str *querystr, ExecStatusType expect, PGconn *pg_link,
+ }
+ /* }}} */
+
+-static inline void build_tablename(smart_str *querystr, PGconn *pg_link, const char *table) /* {{{ */
++static inline int build_tablename(smart_str *querystr, PGconn *pg_link, const char *table) /* {{{ */
+ {
+ size_t table_len = strlen(table);
+
+@@ -6577,6 +6624,10 @@ static inline void build_tablename(smart_str *querystr, PGconn *pg_link, const c
+ smart_str_appendl(querystr, table, len);
+ } else {
+ char *escaped = PGSQLescapeIdentifier(pg_link, table, len);
++ if (escaped == NULL) {
++ php_error_docref(NULL, E_NOTICE, "Failed to escape table name '%s'", table);
++ return FAILURE;
++ }
+ smart_str_appends(querystr, escaped);
+ PGSQLfree(escaped);
+ }
+@@ -6589,11 +6640,17 @@ static inline void build_tablename(smart_str *querystr, PGconn *pg_link, const c
+ smart_str_appendl(querystr, after_dot, len);
+ } else {
+ char *escaped = PGSQLescapeIdentifier(pg_link, after_dot, len);
++ if (escaped == NULL) {
++ php_error_docref(NULL, E_NOTICE, "Failed to escape table name '%s'", table);
++ return FAILURE;
++ }
+ smart_str_appendc(querystr, '.');
+ smart_str_appends(querystr, escaped);
+ PGSQLfree(escaped);
+ }
+ }
++
++ return SUCCESS;
+ }
+ /* }}} */
+
+@@ -6615,7 +6672,9 @@ PHP_PGSQL_API int php_pgsql_insert(PGconn *pg_link, const char *table, zval *var
+ ZVAL_UNDEF(&converted);
+ if (zend_hash_num_elements(Z_ARRVAL_P(var_array)) == 0) {
+ smart_str_appends(&querystr, "INSERT INTO ");
+- build_tablename(&querystr, pg_link, table);
++ if (build_tablename(&querystr, pg_link, table) == FAILURE) {
++ goto cleanup;
++ }
+ smart_str_appends(&querystr, " DEFAULT VALUES");
+
+ goto no_values;
+@@ -6631,7 +6690,9 @@ PHP_PGSQL_API int php_pgsql_insert(PGconn *pg_link, const char *table, zval *var
+ }
+
+ smart_str_appends(&querystr, "INSERT INTO ");
+- build_tablename(&querystr, pg_link, table);
++ if (build_tablename(&querystr, pg_link, table) == FAILURE) {
++ goto cleanup;
++ }
+ smart_str_appends(&querystr, " (");
+
+ ZEND_HASH_FOREACH_STR_KEY(Z_ARRVAL_P(var_array), fld) {
+@@ -6641,6 +6702,10 @@ PHP_PGSQL_API int php_pgsql_insert(PGconn *pg_link, const char *table, zval *var
+ }
+ if (opt & PGSQL_DML_ESCAPE) {
+ tmp = PGSQLescapeIdentifier(pg_link, ZSTR_VAL(fld), ZSTR_LEN(fld) + 1);
++ if (tmp == NULL) {
++ php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s'", ZSTR_VAL(fld));
++ goto cleanup;
++ }
+ smart_str_appends(&querystr, tmp);
+ PGSQLfree(tmp);
+ } else {
+@@ -6652,15 +6717,19 @@ PHP_PGSQL_API int php_pgsql_insert(PGconn *pg_link, const char *table, zval *var
+ smart_str_appends(&querystr, ") VALUES (");
+
+ /* make values string */
+- ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(var_array), val) {
++ ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(var_array), fld, val) {
+ /* we can avoid the key_type check here, because we tested it in the other loop */
+ switch (Z_TYPE_P(val)) {
+ case IS_STRING:
+ if (opt & PGSQL_DML_ESCAPE) {
+- size_t new_len;
+- char *tmp;
+- tmp = (char *)safe_emalloc(Z_STRLEN_P(val), 2, 1);
+- new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), NULL);
++ int error;
++ char *tmp = safe_emalloc(Z_STRLEN_P(val), 2, 1);
++ size_t new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), &error);
++ if (error) {
++ php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s' value", ZSTR_VAL(fld));
++ efree(tmp);
++ goto cleanup;
++ }
+ smart_str_appendc(&querystr, '\'');
+ smart_str_appendl(&querystr, tmp, new_len);
+ smart_str_appendc(&querystr, '\'');
+@@ -6810,6 +6879,10 @@ static inline int build_assignment_string(PGconn *pg_link, smart_str *querystr,
+ }
+ if (opt & PGSQL_DML_ESCAPE) {
+ char *tmp = PGSQLescapeIdentifier(pg_link, ZSTR_VAL(fld), ZSTR_LEN(fld) + 1);
++ if (tmp == NULL) {
++ php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s'", ZSTR_VAL(fld));
++ return -1;
++ }
+ smart_str_appends(querystr, tmp);
+ PGSQLfree(tmp);
+ } else {
+@@ -6824,8 +6897,14 @@ static inline int build_assignment_string(PGconn *pg_link, smart_str *querystr,
+ switch (Z_TYPE_P(val)) {
+ case IS_STRING:
+ if (opt & PGSQL_DML_ESCAPE) {
++ int error;
+ char *tmp = (char *)safe_emalloc(Z_STRLEN_P(val), 2, 1);
+- size_t new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), NULL);
++ size_t new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), &error);
++ if (error) {
++ php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s' value", ZSTR_VAL(fld));
++ efree(tmp);
++ return -1;
++ }
+ smart_str_appendc(querystr, '\'');
+ smart_str_appendl(querystr, tmp, new_len);
+ smart_str_appendc(querystr, '\'');
+@@ -6894,7 +6973,9 @@ PHP_PGSQL_API int php_pgsql_update(PGconn *pg_link, const char *table, zval *var
+ }
+
+ smart_str_appends(&querystr, "UPDATE ");
+- build_tablename(&querystr, pg_link, table);
++ if (build_tablename(&querystr, pg_link, table) == FAILURE) {
++ goto cleanup;
++ }
+ smart_str_appends(&querystr, " SET ");
+
+ if (build_assignment_string(pg_link, &querystr, Z_ARRVAL_P(var_array), 0, ",", 1, opt))
+@@ -6992,7 +7073,9 @@ PHP_PGSQL_API int php_pgsql_delete(PGconn *pg_link, const char *table, zval *ids
+ }
+
+ smart_str_appends(&querystr, "DELETE FROM ");
+- build_tablename(&querystr, pg_link, table);
++ if (build_tablename(&querystr, pg_link, table) == FAILURE) {
++ goto cleanup;
++ }
+ smart_str_appends(&querystr, " WHERE ");
+
+ if (build_assignment_string(pg_link, &querystr, Z_ARRVAL_P(ids_array), 1, " AND ", sizeof(" AND ")-1, opt))
+@@ -7130,7 +7213,9 @@ PHP_PGSQL_API int php_pgsql_result2array(PGresult *pg_result, zval *ret_array, l
+ }
+
+ smart_str_appends(&querystr, "SELECT * FROM ");
+- build_tablename(&querystr, pg_link, table);
++ if (build_tablename(&querystr, pg_link, table) == FAILURE) {
++ goto cleanup;
++ }
+ smart_str_appends(&querystr, " WHERE ");
+
+ if (build_assignment_string(pg_link, &querystr, Z_ARRVAL_P(ids_array), 1, " AND ", sizeof(" AND ")-1, opt))
+diff --git a/ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt b/ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt
+new file mode 100644
+index 00000000000..c1c5e05dce6
+--- /dev/null
++++ b/ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt
+@@ -0,0 +1,64 @@
++--TEST--
++#GHSA-hrwm-9436-5mv3: pgsql extension does not check for errors during escaping
++--EXTENSIONS--
++pgsql
++--SKIPIF--
++<?php include("skipif.inc"); ?>
++--FILE--
++<?php
++
++include 'config.inc';
++define('FILE_NAME', __DIR__ . '/php.gif');
++
++$db = pg_connect($conn_str);
++pg_query($db, "DROP TABLE IF EXISTS ghsa_hrmw_9436_5mv3");
++pg_query($db, "CREATE TABLE ghsa_hrmw_9436_5mv3 (bar text);");
++
++// pg_escape_literal/pg_escape_identifier
++
++$invalid = "ABC\xff\x30';";
++$flags = PGSQL_DML_NO_CONV | PGSQL_DML_ESCAPE;
++
++var_dump(pg_insert($db, $invalid, ['bar' => 'test'])); // table name str escape in php_pgsql_meta_data
++var_dump(pg_insert($db, "$invalid.tbl", ['bar' => 'test'])); // schema name str escape in php_pgsql_meta_data
++var_dump(pg_insert($db, 'ghsa_hrmw_9436_5mv3', ['bar' => $invalid])); // converted value str escape in php_pgsql_convert
++var_dump(pg_insert($db, $invalid, [])); // ident escape in build_tablename
++var_dump(pg_insert($db, 'ghsa_hrmw_9436_5mv3', [$invalid => 'foo'], $flags)); // ident escape for field php_pgsql_insert
++var_dump(pg_insert($db, 'ghsa_hrmw_9436_5mv3', ['bar' => $invalid], $flags)); // str escape for field value in php_pgsql_insert
++var_dump(pg_update($db, 'ghsa_hrmw_9436_5mv3', ['bar' => 'val'], [$invalid => 'test'], $flags)); // ident escape in build_assignment_string
++var_dump(pg_update($db, 'ghsa_hrmw_9436_5mv3', ['bar' => 'val'], ['bar' => $invalid], $flags)); // invalid str escape in build_assignment_string
++var_dump(pg_escape_literal($db, $invalid)); // pg_escape_literal escape
++var_dump(pg_escape_identifier($db, $invalid)); // pg_escape_identifier escape
++
++?>
++--EXPECTF--
++
++Warning: pg_insert(): Escaping table name 'ABC%s';' failed in %s on line %d
++bool(false)
++
++Warning: pg_insert(): Escaping table namespace 'ABC%s';.tbl' failed in %s on line %d
++bool(false)
++
++Notice: pg_insert(): String value escaping failed for PostgreSQL 'text' (bar) in %s on line %d
++bool(false)
++
++Notice: pg_insert(): Failed to escape table name 'ABC%s';' in %s on line %d
++bool(false)
++
++Notice: pg_insert(): Failed to escape field 'ABC%s';' in %s on line %d
++bool(false)
++
++Notice: pg_insert(): Failed to escape field 'bar' value in %s on line %d
++bool(false)
++
++Notice: pg_update(): Failed to escape field 'ABC%s';' in %s on line %d
++bool(false)
++
++Notice: pg_update(): Failed to escape field 'bar' value in %s on line %d
++bool(false)
++
++Warning: pg_escape_literal(): Failed to escape in %s on line %d
++bool(false)
++
++Warning: pg_escape_identifier(): Failed to escape in %s on line %d
++bool(false)
+--
+2.50.0
+
+From d52bcc1e66edd421dfea1698b1f897ad26c5f15f Mon Sep 17 00:00:00 2001
+From: Remi Collet <remi@remirepo.net>
+Date: Thu, 3 Jul 2025 09:32:25 +0200
+Subject: [PATCH 4/4] NEWS
+
+(cherry picked from commit 970548b94b7f23be32154d05a9545b10c98bfd62)
+---
+ NEWS | 14 ++++++++++++++
+ 1 file changed, 14 insertions(+)
+
+diff --git a/NEWS b/NEWS
+index fda646c7010..a9dd716c003 100644
+--- a/NEWS
++++ b/NEWS
+@@ -1,6 +1,20 @@
+ PHP NEWS
+ |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
++Backported from 8.1.33
++
++- PGSQL:
++ . Fixed GHSA-hrwm-9436-5mv3 (pgsql extension does not check for errors during
++ escaping). (CVE-2025-1735) (Jakub Zelenka)
++
++- SOAP:
++ . Fixed GHSA-453j-q27h-5p8x (NULL Pointer Dereference in PHP SOAP Extension
++ via Large XML Namespace Prefix). (CVE-2025-6491) (Lekssays, nielsdos)
++
++- Standard:
++ . Fixed GHSA-3cr5-j632-f35r (Null byte termination in hostnames).
++ (CVE-2025-1220) (Jakub Zelenka)
++
+ Backported from 8.1.32
+
+ - LibXML:
+--
+2.50.0
+
diff --git a/php-cve-2025-1736.patch b/php-cve-2025-1736.patch
new file mode 100644
index 0000000..eb33553
--- /dev/null
+++ b/php-cve-2025-1736.patch
@@ -0,0 +1,242 @@
+From 134f821622e2d2b68d66bea16e16c05b7b0f5114 Mon Sep 17 00:00:00 2001
+From: Jakub Zelenka <bukka@php.net>
+Date: Fri, 14 Feb 2025 19:17:22 +0100
+Subject: [PATCH 04/11] Fix GHSA-hgf5-96fm-v528: http user header check of crlf
+
+(cherry picked from commit 41d49abbd99dab06cdae4834db664435f8177174)
+(cherry picked from commit 8f65ef50929f6781f4973325f9b619f02cce19d8)
+---
+ ext/standard/http_fopen_wrapper.c | 2 +-
+ .../tests/http/ghsa-hgf5-96fm-v528-001.phpt | 65 +++++++++++++++++++
+ .../tests/http/ghsa-hgf5-96fm-v528-002.phpt | 62 ++++++++++++++++++
+ .../tests/http/ghsa-hgf5-96fm-v528-003.phpt | 64 ++++++++++++++++++
+ 4 files changed, 192 insertions(+), 1 deletion(-)
+ create mode 100644 ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt
+ create mode 100644 ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt
+ create mode 100644 ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt
+
+diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
+index b64a7c95446..46f7c7ebcee 100644
+--- a/ext/standard/http_fopen_wrapper.c
++++ b/ext/standard/http_fopen_wrapper.c
+@@ -109,7 +109,7 @@ static inline void strip_header(char *header_bag, char *lc_header_bag,
+ static zend_bool check_has_header(const char *headers, const char *header) {
+ const char *s = headers;
+ while ((s = strstr(s, header))) {
+- if (s == headers || *(s-1) == '\n') {
++ if (s == headers || (*(s-1) == '\n' && *(s-2) == '\r')) {
+ return 1;
+ }
+ s++;
+diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt
+new file mode 100644
+index 00000000000..c8dcd47a4a4
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt
+@@ -0,0 +1,65 @@
++--TEST--
++GHSA-hgf5-96fm-v528: Stream HTTP wrapper header check might omit basic auth header (incorrect inside pos)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++ $ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
++
++ $result = fread($conn, 1024);
++ $encoded_result = base64_encode($result);
++
++ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html; charset=utf-8\r\n\r\n$encoded_result\r\n");
++
++CODE;
++
++$clientCode = <<<'CODE'
++ $opts = [
++ "http" => [
++ "method" => "GET",
++ "header" => "Cookie: foo=bar\nauthorization:x\r\n"
++ ]
++ ];
++ $ctx = stream_context_create($opts);
++ var_dump(explode("\r\n", base64_decode(file_get_contents("http://user:pwd@{{ ADDR }}", false, $ctx))));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++array(7) {
++ [0]=>
++ string(14) "GET / HTTP/1.%d"
++ [1]=>
++ string(33) "Authorization: Basic dXNlcjpwd2Q="
++ [2]=>
++ string(21) "Host: 127.0.0.1:%d"
++ [3]=>
++ string(17) "Connection: close"
++ [4]=>
++ string(31) "Cookie: foo=bar
++authorization:x"
++ [5]=>
++ string(0) ""
++ [6]=>
++ string(0) ""
++}
++array(2) {
++ [0]=>
++ string(15) "HTTP/1.0 200 Ok"
++ [1]=>
++ string(38) "Content-Type: text/html; charset=utf-8"
++}
+diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt
+new file mode 100644
+index 00000000000..ca8f75f0327
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt
+@@ -0,0 +1,62 @@
++--TEST--
++GHSA-hgf5-96fm-v528: Header parser of http stream wrapper does not handle folded headers (correct start pos)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++ $ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
++
++ $result = fread($conn, 1024);
++ $encoded_result = base64_encode($result);
++
++ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html; charset=utf-8\r\n\r\n$encoded_result\r\n");
++
++CODE;
++
++$clientCode = <<<'CODE'
++ $opts = [
++ "http" => [
++ "method" => "GET",
++ "header" => "Authorization: Bearer x\r\n"
++ ]
++ ];
++ $ctx = stream_context_create($opts);
++ var_dump(explode("\r\n", base64_decode(file_get_contents("http://user:pwd@{{ ADDR }}", false, $ctx))));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++array(6) {
++ [0]=>
++ string(14) "GET / HTTP/1.%d"
++ [1]=>
++ string(21) "Host: 127.0.0.1:%d"
++ [2]=>
++ string(17) "Connection: close"
++ [3]=>
++ string(23) "Authorization: Bearer x"
++ [4]=>
++ string(0) ""
++ [5]=>
++ string(0) ""
++}
++array(2) {
++ [0]=>
++ string(15) "HTTP/1.0 200 Ok"
++ [1]=>
++ string(38) "Content-Type: text/html; charset=utf-8"
++}
+diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt
+new file mode 100644
+index 00000000000..4cfbc7ee804
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt
+@@ -0,0 +1,64 @@
++--TEST--
++GHSA-hgf5-96fm-v528: Header parser of http stream wrapper does not handle folded headers (correct middle pos)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++ $ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
++
++ $result = fread($conn, 1024);
++ $encoded_result = base64_encode($result);
++
++ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html; charset=utf-8\r\n\r\n$encoded_result\r\n");
++
++CODE;
++
++$clientCode = <<<'CODE'
++ $opts = [
++ "http" => [
++ "method" => "GET",
++ "header" => "Cookie: x=y\r\nAuthorization: Bearer x\r\n"
++ ]
++ ];
++ $ctx = stream_context_create($opts);
++ var_dump(explode("\r\n", base64_decode(file_get_contents("http://user:pwd@{{ ADDR }}", false, $ctx))));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++array(7) {
++ [0]=>
++ string(14) "GET / HTTP/1.%d"
++ [1]=>
++ string(21) "Host: 127.0.0.1:%d"
++ [2]=>
++ string(17) "Connection: close"
++ [3]=>
++ string(11) "Cookie: x=y"
++ [4]=>
++ string(23) "Authorization: Bearer x"
++ [5]=>
++ string(0) ""
++ [6]=>
++ string(0) ""
++}
++array(2) {
++ [0]=>
++ string(15) "HTTP/1.0 200 Ok"
++ [1]=>
++ string(38) "Content-Type: text/html; charset=utf-8"
++}
+--
+2.48.1
+
diff --git a/php-cve-2025-1861.patch b/php-cve-2025-1861.patch
new file mode 100644
index 0000000..b31e74f
--- /dev/null
+++ b/php-cve-2025-1861.patch
@@ -0,0 +1,349 @@
+From 5418040dcaaca46965ed6f8a4ad1541709c32e9f Mon Sep 17 00:00:00 2001
+From: Jakub Zelenka <bukka@php.net>
+Date: Tue, 4 Mar 2025 09:01:34 +0100
+Subject: [PATCH 03/11] Fix GHSA-52jp-hrpf-2jff: http redirect location
+ truncation
+
+It converts the allocation of location to be on heap instead of stack
+and errors if the location length is greater than 8086 bytes.
+
+(cherry picked from commit ac1a054bb3eb5994a199e8b18cca28cbabf5943e)
+(cherry picked from commit adc7e9f20c9a9aab9cd23ca47ec3fb96287898ae)
+---
+ ext/standard/http_fopen_wrapper.c | 87 ++++++++++++-------
+ .../tests/http/ghsa-52jp-hrpf-2jff-001.phpt | 58 +++++++++++++
+ .../tests/http/ghsa-52jp-hrpf-2jff-002.phpt | 55 ++++++++++++
+ 3 files changed, 168 insertions(+), 32 deletions(-)
+ create mode 100644 ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt
+ create mode 100644 ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt
+
+diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
+index 071d6a4d119..b64a7c95446 100644
+--- a/ext/standard/http_fopen_wrapper.c
++++ b/ext/standard/http_fopen_wrapper.c
+@@ -69,15 +69,16 @@
+
+ #include "php_fopen_wrappers.h"
+
+-#define HTTP_HEADER_BLOCK_SIZE 1024
+-#define PHP_URL_REDIRECT_MAX 20
+-#define HTTP_HEADER_USER_AGENT 1
+-#define HTTP_HEADER_HOST 2
+-#define HTTP_HEADER_AUTH 4
+-#define HTTP_HEADER_FROM 8
+-#define HTTP_HEADER_CONTENT_LENGTH 16
+-#define HTTP_HEADER_TYPE 32
+-#define HTTP_HEADER_CONNECTION 64
++#define HTTP_HEADER_BLOCK_SIZE 1024
++#define HTTP_HEADER_MAX_LOCATION_SIZE 8182 /* 8192 - 10 (size of "Location: ") */
++#define PHP_URL_REDIRECT_MAX 20
++#define HTTP_HEADER_USER_AGENT 1
++#define HTTP_HEADER_HOST 2
++#define HTTP_HEADER_AUTH 4
++#define HTTP_HEADER_FROM 8
++#define HTTP_HEADER_CONTENT_LENGTH 16
++#define HTTP_HEADER_TYPE 32
++#define HTTP_HEADER_CONNECTION 64
+
+ #define HTTP_WRAPPER_HEADER_INIT 1
+ #define HTTP_WRAPPER_REDIRECTED 2
+@@ -121,17 +122,15 @@ typedef struct _php_stream_http_response_header_info {
+ size_t file_size;
+ zend_bool error;
+ zend_bool follow_location;
+- char location[HTTP_HEADER_BLOCK_SIZE];
++ char *location;
++ size_t location_len;
+ } php_stream_http_response_header_info;
+
+ static void php_stream_http_response_header_info_init(
+ php_stream_http_response_header_info *header_info)
+ {
+- header_info->transfer_encoding = NULL;
+- header_info->file_size = 0;
+- header_info->error = 0;
++ memset(header_info, 0, sizeof(php_stream_http_response_header_info));
+ header_info->follow_location = 1;
+- header_info->location[0] = '\0';
+ }
+
+ /* Trim white spaces from response header line and update its length */
+@@ -258,7 +257,22 @@ static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *w
+ * RFC 7238 defines 308: http://tools.ietf.org/html/rfc7238 */
+ header_info->follow_location = 0;
+ }
+- strlcpy(header_info->location, last_header_value, sizeof(header_info->location));
++ size_t last_header_value_len = strlen(last_header_value);
++ if (last_header_value_len > HTTP_HEADER_MAX_LOCATION_SIZE) {
++ header_info->error = 1;
++ php_stream_wrapper_log_error(wrapper, options,
++ "HTTP Location header size is over the limit of %d bytes",
++ HTTP_HEADER_MAX_LOCATION_SIZE);
++ zend_string_efree(last_header_line_str);
++ return NULL;
++ }
++ if (header_info->location_len == 0) {
++ header_info->location = emalloc(last_header_value_len + 1);
++ } else if (header_info->location_len <= last_header_value_len) {
++ header_info->location = erealloc(header_info->location, last_header_value_len + 1);
++ }
++ header_info->location_len = last_header_value_len;
++ memcpy(header_info->location, last_header_value, last_header_value_len + 1);
+ } else if (!strncasecmp(last_header_line, "Content-Type:", sizeof("Content-Type:")-1)) {
+ php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, last_header_value, 0);
+ } else if (!strncasecmp(last_header_line, "Content-Length:", sizeof("Content-Length:")-1)) {
+@@ -541,6 +555,8 @@ finish:
+ }
+ }
+
++ php_stream_http_response_header_info_init(&header_info);
++
+ if (stream == NULL)
+ goto out;
+
+@@ -918,8 +934,6 @@ finish:
+ }
+ }
+
+- php_stream_http_response_header_info_init(&header_info);
+-
+ /* read past HTTP headers */
+ while (!php_stream_eof(stream)) {
+ size_t http_header_line_length;
+@@ -989,12 +1003,12 @@ finish:
+ last_header_line_str, NULL, NULL, response_code, response_header, &header_info);
+ }
+
+- if (!reqok || (header_info.location[0] != '\0' && header_info.follow_location)) {
++ if (!reqok || (header_info.location != NULL && header_info.follow_location)) {
+ if (!header_info.follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) {
+ goto out;
+ }
+
+- if (header_info.location[0] != '\0')
++ if (header_info.location != NULL)
+ php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, header_info.location, 0);
+
+ php_stream_close(stream);
+@@ -1005,18 +1019,17 @@ finish:
+ header_info.transfer_encoding = NULL;
+ }
+
+- if (header_info.location[0] != '\0') {
++ if (header_info.location != NULL) {
+
+- char new_path[HTTP_HEADER_BLOCK_SIZE];
+- char loc_path[HTTP_HEADER_BLOCK_SIZE];
++ char *new_path = NULL;
+
+- *new_path='\0';
+ if (strlen(header_info.location) < 8 ||
+ (strncasecmp(header_info.location, "http://", sizeof("http://")-1) &&
+ strncasecmp(header_info.location, "https://", sizeof("https://")-1) &&
+ strncasecmp(header_info.location, "ftp://", sizeof("ftp://")-1) &&
+ strncasecmp(header_info.location, "ftps://", sizeof("ftps://")-1)))
+ {
++ char *loc_path = NULL;
+ if (*header_info.location != '/') {
+ if (*(header_info.location+1) != '\0' && resource->path) {
+ char *s = strrchr(ZSTR_VAL(resource->path), '/');
+@@ -1034,31 +1047,35 @@ finish:
+ if (resource->path &&
+ ZSTR_VAL(resource->path)[0] == '/' &&
+ ZSTR_VAL(resource->path)[1] == '\0') {
+- snprintf(loc_path, sizeof(loc_path) - 1, "%s%s",
+- ZSTR_VAL(resource->path), header_info.location);
++ spprintf(&loc_path, 0, "%s%s", ZSTR_VAL(resource->path), header_info.location);
+ } else {
+- snprintf(loc_path, sizeof(loc_path) - 1, "%s/%s",
+- ZSTR_VAL(resource->path), header_info.location);
++ spprintf(&loc_path, 0, "%s/%s", ZSTR_VAL(resource->path), header_info.location);
+ }
+ } else {
+- snprintf(loc_path, sizeof(loc_path) - 1, "/%s", header_info.location);
++ spprintf(&loc_path, 0, "/%s", header_info.location);
+ }
+ } else {
+- strlcpy(loc_path, header_info.location, sizeof(loc_path));
++ loc_path = header_info.location;
++ header_info.location = NULL;
+ }
+ if ((use_ssl && resource->port != 443) || (!use_ssl && resource->port != 80)) {
+- snprintf(new_path, sizeof(new_path) - 1, "%s://%s:%d%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), resource->port, loc_path);
++ spprintf(&new_path, 0, "%s://%s:%d%s", ZSTR_VAL(resource->scheme),
++ ZSTR_VAL(resource->host), resource->port, loc_path);
+ } else {
+- snprintf(new_path, sizeof(new_path) - 1, "%s://%s%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), loc_path);
++ spprintf(&new_path, 0, "%s://%s%s", ZSTR_VAL(resource->scheme),
++ ZSTR_VAL(resource->host), loc_path);
+ }
++ efree(loc_path);
+ } else {
+- strlcpy(new_path, header_info.location, sizeof(new_path));
++ new_path = header_info.location;
++ header_info.location = NULL;
+ }
+
+ php_url_free(resource);
+ /* check for invalid redirection URLs */
+ if ((resource = php_url_parse(new_path)) == NULL) {
+ php_stream_wrapper_log_error(wrapper, options, "Invalid redirect URL! %s", new_path);
++ efree(new_path);
+ goto out;
+ }
+
+@@ -1070,6 +1087,7 @@ finish:
+ while (s < e) { \
+ if (iscntrl(*s)) { \
+ php_stream_wrapper_log_error(wrapper, options, "Invalid redirect URL! %s", new_path); \
++ efree(new_path); \
+ goto out; \
+ } \
+ s++; \
+@@ -1085,6 +1103,7 @@ finish:
+ stream = php_stream_url_wrap_http_ex(
+ wrapper, new_path, mode, options, opened_path, context,
+ --redirect_max, HTTP_WRAPPER_REDIRECTED, response_header STREAMS_CC);
++ efree(new_path);
+ } else {
+ php_stream_wrapper_log_error(wrapper, options, "HTTP request failed! %s", tmp_line);
+ }
+@@ -1097,6 +1116,10 @@ out:
+ efree(http_header_line);
+ }
+
++ if (header_info.location != NULL) {
++ efree(header_info.location);
++ }
++
+ if (resource) {
+ php_url_free(resource);
+ }
+diff --git a/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt
+new file mode 100644
+index 00000000000..46d77ec4aff
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt
+@@ -0,0 +1,58 @@
++--TEST--
++GHSA-52jp-hrpf-2jff: HTTP stream wrapper truncate redirect location to 1024 bytes (success)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++$ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
++
++ $loc = str_repeat("y", 8000);
++ fwrite($conn, "HTTP/1.0 301 Ok\r\nContent-Type: text/html;\r\nLocation: $loc\r\n\r\nbody\r\n");
++CODE;
++
++$clientCode = <<<'CODE'
++ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
++ switch($notification_code) {
++ case STREAM_NOTIFY_MIME_TYPE_IS:
++ echo "Found the mime-type: ", $message, PHP_EOL;
++ break;
++ case STREAM_NOTIFY_REDIRECTED:
++ echo "Redirected: ";
++ var_dump($message);
++ }
++ }
++
++ $ctx = stream_context_create();
++ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
++ var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx)));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++Found the mime-type: text/html;
++Redirected: string(8000) "%s"
++
++Warning: file_get_contents(http://127.0.0.1:%d): failed to open stream: %s
++string(0) ""
++array(3) {
++ [0]=>
++ string(15) "HTTP/1.0 301 Ok"
++ [1]=>
++ string(24) "Content-Type: text/html;"
++ [2]=>
++ string(8010) "Location: %s"
++}
+diff --git a/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt
+new file mode 100644
+index 00000000000..d25c89d06e5
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt
+@@ -0,0 +1,55 @@
++--TEST--
++GHSA-52jp-hrpf-2jff: HTTP stream wrapper truncate redirect location to 1024 bytes (over limit)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++$ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
++
++ $loc = str_repeat("y", 9000);
++ fwrite($conn, "HTTP/1.0 301 Ok\r\nContent-Type: text/html;\r\nLocation: $loc\r\n\r\nbody\r\n");
++CODE;
++
++$clientCode = <<<'CODE'
++ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
++ switch($notification_code) {
++ case STREAM_NOTIFY_MIME_TYPE_IS:
++ echo "Found the mime-type: ", $message, PHP_EOL;
++ break;
++ case STREAM_NOTIFY_REDIRECTED:
++ echo "Redirected: ";
++ var_dump($message);
++ }
++ }
++
++ $ctx = stream_context_create();
++ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
++ var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx)));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++Found the mime-type: text/html;
++
++Warning: file_get_contents(http://127.0.0.1:%d): failed to open stream: HTTP Location header size is over the limit of 8182 bytes in %s
++string(0) ""
++array(2) {
++ [0]=>
++ string(15) "HTTP/1.0 301 Ok"
++ [1]=>
++ string(24) "Content-Type: text/html;"
++}
+--
+2.48.1
+
diff --git a/php-cve-2025-6491.patch b/php-cve-2025-6491.patch
new file mode 100644
index 0000000..ec8ce61
--- /dev/null
+++ b/php-cve-2025-6491.patch
@@ -0,0 +1,103 @@
+From c13a3b2a3710c66231f0cad16ff74ef75c8672a7 Mon Sep 17 00:00:00 2001
+From: Ahmed Lekssays <lekssaysahmed@gmail.com>
+Date: Tue, 3 Jun 2025 09:00:55 +0000
+Subject: [PATCH 1/4] Fix GHSA-453j-q27h-5p8x
+
+Libxml versions prior to 2.13 cannot correctly handle a call to
+xmlNodeSetName() with a name longer than 2G. It will leave the node
+object in an invalid state with a NULL name. This later causes a NULL
+pointer dereference when using the name during message serialization.
+
+To solve this, implement a workaround that resets the name to the
+sentinel name if this situation arises.
+
+Versions of libxml of 2.13 and higher are not affected.
+
+This can be exploited if a SoapVar is created with a fully qualified
+name that is longer than 2G. This would be possible if some application
+code uses a namespace prefix from an untrusted source like from a remote
+SOAP service.
+
+Co-authored-by: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
+(cherry picked from commit 9cb3d8d200f0c822b17bda35a2a67a97b039d3e1)
+(cherry picked from commit 1b7410a57f8a5fd1dd43854bcf7b9200517c9fd2)
+---
+ ext/soap/soap.c | 6 ++--
+ ext/soap/tests/soap_qname_crash.phpt | 48 ++++++++++++++++++++++++++++
+ 2 files changed, 52 insertions(+), 2 deletions(-)
+ create mode 100644 ext/soap/tests/soap_qname_crash.phpt
+
+diff --git a/ext/soap/soap.c b/ext/soap/soap.c
+index 7429aebbf70..94f1db526c6 100644
+--- a/ext/soap/soap.c
++++ b/ext/soap/soap.c
+@@ -4457,8 +4457,10 @@ static xmlNodePtr serialize_zval(zval *val, sdlParamPtr param, char *paramName,
+ }
+ xmlParam = master_to_xml(enc, val, style, parent);
+ zval_ptr_dtor(&defval);
+- if (!strcmp((char*)xmlParam->name, "BOGUS")) {
+- xmlNodeSetName(xmlParam, BAD_CAST(paramName));
++ if (xmlParam != NULL) {
++ if (xmlParam->name == NULL || strcmp((char*)xmlParam->name, "BOGUS") == 0) {
++ xmlNodeSetName(xmlParam, BAD_CAST(paramName));
++ }
+ }
+ return xmlParam;
+ }
+diff --git a/ext/soap/tests/soap_qname_crash.phpt b/ext/soap/tests/soap_qname_crash.phpt
+new file mode 100644
+index 00000000000..7a1bf026022
+--- /dev/null
++++ b/ext/soap/tests/soap_qname_crash.phpt
+@@ -0,0 +1,48 @@
++--TEST--
++Test SoapClient with excessively large QName prefix in SoapVar
++--EXTENSIONS--
++soap
++--SKIPIF--
++<?php
++if (PHP_INT_SIZE != 8) die("skip: 64-bit only");
++?>
++--INI--
++memory_limit=8G
++--FILE--
++<?php
++
++class TestSoapClient extends SoapClient {
++ public function __doRequest(
++ $request,
++ $location,
++ $action,
++ $version,
++ $one_way = false
++ ): ?string {
++ die($request);
++ }
++}
++
++$prefix = str_repeat("A", 2 * 1024 * 1024 * 1024);
++$qname = "{$prefix}:tag";
++
++echo "Attempting to create SoapVar with very large QName\n";
++
++$var = new SoapVar("value", XSD_QNAME, null, null, $qname);
++
++echo "Attempting encoding\n";
++
++$options = [
++ 'location' => 'http://127.0.0.1/',
++ 'uri' => 'urn:dummy',
++ 'trace' => 1,
++ 'exceptions' => true,
++];
++$client = new TestSoapClient(null, $options);
++$client->__soapCall("DummyFunction", [$var]);
++?>
++--EXPECT--
++Attempting to create SoapVar with very large QName
++Attempting encoding
++<?xml version="1.0" encoding="UTF-8"?>
++<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:dummy" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:DummyFunction><param0 xsi:type="xsd:QName">value</param0></ns1:DummyFunction></SOAP-ENV:Body></SOAP-ENV:Envelope>
+--
+2.50.0
+
diff --git a/php-fpm.service b/php-fpm.service
index 687dfc0..0712a11 100644
--- a/php-fpm.service
+++ b/php-fpm.service
@@ -4,7 +4,7 @@
[Unit]
Description=The PHP FastCGI Process Manager
-After=syslog.target network.target
+After=network.target
[Service]
Type=notify
diff --git a/php-ghsa-4w77-75f9-2c8w.patch b/php-ghsa-4w77-75f9-2c8w.patch
new file mode 100644
index 0000000..4886973
--- /dev/null
+++ b/php-ghsa-4w77-75f9-2c8w.patch
@@ -0,0 +1,135 @@
+From 56488a8a4ec68e58eecc9e78dd75e41adf56984c Mon Sep 17 00:00:00 2001
+From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
+Date: Sat, 9 Nov 2024 15:29:52 +0100
+Subject: [PATCH 6/7] Fix GHSA-4w77-75f9-2c8w
+
+(cherry picked from commit 7dd336ae838bbf2c62dc47e3c900d657d3534c02)
+(cherry picked from commit 462092a48aa0dbad24d9fa8a4a9d418faa14d309)
+---
+ sapi/cli/php_cli_server.c | 6 +---
+ sapi/cli/tests/ghsa-4w77-75f9-2c8w.phpt | 41 +++++++++++++++++++++++++
+ 2 files changed, 42 insertions(+), 5 deletions(-)
+ create mode 100644 sapi/cli/tests/ghsa-4w77-75f9-2c8w.phpt
+
+diff --git a/sapi/cli/php_cli_server.c b/sapi/cli/php_cli_server.c
+index c3097861e3f..8717dc57418 100644
+--- a/sapi/cli/php_cli_server.c
++++ b/sapi/cli/php_cli_server.c
+@@ -1923,8 +1923,6 @@ static size_t php_cli_server_client_send_through(php_cli_server_client *client,
+
+ static void php_cli_server_client_populate_request_info(const php_cli_server_client *client, sapi_request_info *request_info) /* {{{ */
+ {
+- char *val;
+-
+ request_info->request_method = php_http_method_str(client->request.request_method);
+ request_info->proto_num = client->request.protocol_version;
+ request_info->request_uri = client->request.request_uri;
+@@ -1932,9 +1930,7 @@ static void php_cli_server_client_populate_request_info(const php_cli_server_cli
+ request_info->query_string = client->request.query_string;
+ request_info->content_length = client->request.content_len;
+ request_info->auth_user = request_info->auth_password = request_info->auth_digest = NULL;
+- if (NULL != (val = zend_hash_str_find_ptr(&client->request.headers, "content-type", sizeof("content-type")-1))) {
+- request_info->content_type = val;
+- }
++ request_info->content_type = zend_hash_str_find_ptr(&client->request.headers, "content-type", sizeof("content-type")-1);
+ } /* }}} */
+
+ static void destroy_request_info(sapi_request_info *request_info) /* {{{ */
+diff --git a/sapi/cli/tests/ghsa-4w77-75f9-2c8w.phpt b/sapi/cli/tests/ghsa-4w77-75f9-2c8w.phpt
+new file mode 100644
+index 00000000000..80944c3d14f
+--- /dev/null
++++ b/sapi/cli/tests/ghsa-4w77-75f9-2c8w.phpt
+@@ -0,0 +1,41 @@
++--TEST--
++GHSA-4w77-75f9-2c8w (Heap-Use-After-Free in sapi_read_post_data Processing in CLI SAPI Interface)
++--INI--
++allow_url_fopen=1
++--SKIPIF--
++<?php
++include "skipif.inc";
++?>
++--FILE--
++<?php
++include "php_cli_server.inc";
++
++$serverCode = <<<'CODE'
++var_dump(file_get_contents('php://input'));
++CODE;
++
++php_cli_server_start($serverCode, null, []);
++
++$options = [
++ "http" => [
++ "method" => "POST",
++ "header" => "Content-Type: application/x-www-form-urlencoded",
++ "content" => "AAAAA",
++ ],
++];
++$context = stream_context_create($options);
++
++echo file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS . "/", false, $context);
++
++$options = [
++ "http" => [
++ "method" => "POST",
++ ],
++];
++$context = stream_context_create($options);
++
++echo file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS . "/", false, $context);
++?>
++--EXPECT--
++string(5) "AAAAA"
++string(0) ""
+--
+2.47.0
+
+From d8d682d3d6a4d027771806c8fc77128cae078d29 Mon Sep 17 00:00:00 2001
+From: Remi Collet <remi@remirepo.net>
+Date: Fri, 22 Nov 2024 08:58:10 +0100
+Subject: [PATCH 7/7] NEWS for 8.1.31 backports
+
+(cherry picked from commit 22bdb43da0ecd6e72d63b63aa6c1f3a25d1bca3a)
+---
+ NEWS | 24 ++++++++++++++++++++++++
+ 1 file changed, 24 insertions(+)
+
+diff --git a/NEWS b/NEWS
+index 62616d6312d..f600d6aea65 100644
+--- a/NEWS
++++ b/NEWS
+@@ -1,6 +1,30 @@
+ PHP NEWS
+ |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
++Backported from 8.1.31
++
++- CLI:
++ . Fixed bug GHSA-4w77-75f9-2c8w (Heap-Use-After-Free in sapi_read_post_data
++ Processing in CLI SAPI Interface). (nielsdos)
++
++- LDAP:
++ . Fixed bug GHSA-g665-fm4p-vhff (OOB access in ldap_escape). (CVE-2024-8932)
++ (nielsdos)
++
++- PDO DBLIB:
++ . Fixed bug GHSA-5hqh-c84r-qjcv (Integer overflow in the dblib quoter causing
++ OOB writes). (CVE-2024-11236) (nielsdos)
++
++- PDO Firebird:
++ . Fixed bug GHSA-5hqh-c84r-qjcv (Integer overflow in the firebird quoter
++ causing OOB writes). (CVE-2024-11236) (nielsdos)
++
++- Streams:
++ . Fixed bug GHSA-c5f2-jwm7-mmq2 (Configuring a proxy in a stream context
++ might allow for CRLF injection in URIs). (CVE-2024-11234) (Jakub Zelenka)
++ . Fixed bug GHSA-r977-prxv-hc43 (Single byte overread with
++ convert.quoted-printable-decode filter). (CVE-2024-11233) (nielsdos)
++
+ Backported from 8.1.30
+
+ - CGI:
+--
+2.47.0
+
diff --git a/php.spec b/php.spec
index ab1f9ba..8990793 100644
--- a/php.spec
+++ b/php.spec
@@ -49,17 +49,10 @@
%global mysql_sock %(mysql_config --socket 2>/dev/null || echo /var/lib/mysql/mysql.sock)
-%ifarch aarch64
-%global oraclever 19.24
-%global oraclemax 20
-%global oraclelib 19.1
-%global oracledir 19.24
-%else
-%global oraclever 23.6
+%global oraclever 23.8
%global oraclemax 24
%global oraclelib 23.1
%global oracledir 23
-%endif
# Build for LiteSpeed Web Server (LSAPI)
%global with_lsws 1
@@ -75,12 +68,18 @@
# Optional components; pass "--with mssql" etc to rpmbuild.
%global with_oci8 %{?_with_oci8:1}%{!?_with_oci8:0}
%global with_imap 1
-%global with_interbase 1
%global with_freetds 1
%global with_tidy 1
%global with_sqlite3 1
%global with_enchant 1
+# Build firebird extensions, you can disable using --without firebird
+%if 0%{?rhel} == 10
+%bcond_with firebird
+%else
+%bcond_without firebird
+%endif
+
%if 0%{?fedora} >= 27 || 0%{?rhel} >= 8
%global with_libpcre 1
%else
@@ -119,7 +118,7 @@
Summary: PHP scripting language for creating dynamic web sites
Name: %{?scl_prefix}php
Version: %{upver}%{?rcver:~%{rcver}}%{?gh_date:.%{gh_date}}
-Release: 18%{?dist}
+Release: 24%{?dist}
# All files licensed under PHP version 3.01, except
# Zend is licensed under Zend
# TSRM is licensed under BSD
@@ -159,11 +158,12 @@ Source53: 20-ffi.ini
Patch1: php-7.4.0-httpd.patch
Patch5: php-7.2.0-includedir.patch
Patch6: php-7.4.0-embed.patch
-# For libxml 2.12 from 8.1
-Patch7: php-7.4.33-libxml212.patch
Patch8: php-7.2.0-libdb.patch
-Patch9: php-7.0.7-curl.patch
Patch10: php-7.4.33-gcc14.patch
+# For recent ICU from 8.2
+Patch11: php-7.4.33-icu.patch
+# Fix strict prototypes from 8.1
+Patch12: php-7.4.33-proto.patch
# Functional changes
Patch42: php-7.3.3-systzdata-v19.patch
@@ -201,6 +201,20 @@ Patch210: php-cve-2024-8925.patch
Patch211: php-cve-2024-8926.patch
Patch212: php-cve-2024-8927.patch
Patch213: php-cve-2024-9026.patch
+Patch214: php-cve-2024-11236.patch
+Patch215: php-cve-2024-11234.patch
+Patch216: php-cve-2024-8932.patch
+Patch217: php-cve-2024-11233.patch
+Patch218: php-ghsa-4w77-75f9-2c8w.patch
+Patch219: php-cve-2024-8929.patch
+Patch220: php-cve-2025-1217.patch
+Patch221: php-cve-2025-1734.patch
+Patch222: php-cve-2025-1861.patch
+Patch223: php-cve-2025-1736.patch
+Patch224: php-cve-2025-1219.patch
+Patch225: php-cve-2025-6491.patch
+Patch226: php-cve-2025-1220.patch
+Patch227: php-cve-2025-1735.patch
# Fixes for tests (300+)
# Factory is droped from system tzdata
@@ -209,6 +223,8 @@ Patch300: php-7.0.10-datetests.patch
Patch301: php-7.4.33-tests.patch
# For zlib-ng
Patch302: php-7.4.33-zlib-tests.patch
+# for pcre2 10.45
+Patch303: php-7.4.33-pcretests.patch
# WIP
@@ -621,7 +637,7 @@ BuildRequires: pkgconfig(libxml-2.0)
The %{?scl_prefix}php-soap package contains a dynamic shared object that will add
support to PHP for using the SOAP web services protocol.
-%if %{with_interbase}
+%if %{with firebird}
%package pdo-firebird
Summary: PDO driver for Interbase/Firebird databases
Group: Development/Languages
@@ -653,14 +669,7 @@ Summary: A module for PHP applications that use OCI8 databases
Group: Development/Languages
# All files licensed under PHP version 3.01
License: PHP
-%ifarch aarch64
-BuildRequires: oracle-instantclient%{oraclever}-devel
-# Should requires libclntsh.so.19.1()(aarch-64), but it's not provided by Oracle RPM.
-Requires: libclntsh.so.%{oraclelib}
-AutoReq: 0
-%else
BuildRequires: (oracle-instantclient-devel >= %{oraclever} with oracle-instantclient-devel < %{oraclemax})
-%endif
Requires: %{?scl_prefix}php-pdo%{?_isa} = %{version}-%{release}
Provides: %{?scl_prefix}php_database
Provides: %{?scl_prefix}php-pdo_oci
@@ -968,12 +977,10 @@ in pure PHP.
%patch -P1 -p1 -b .mpmcheck
%patch -P5 -p1 -b .includedir
%patch -P6 -p1 -b .embed
-%patch -P7 -p1 -b .libxml212
%patch -P8 -p1 -b .libdb
-%if 0%{?rhel} == 7
-%patch -P9 -p1 -b .curltls
-%endif
%patch -P10 -p1 -b .gcc14
+%patch -P11 -p1 -b .icu74
+%patch -P12 -p1 -b .proto
%patch -P42 -p1 -b .systzdata
%patch -P43 -p1 -b .headers
@@ -1006,11 +1013,26 @@ rm ext/openssl/tests/p12_with_extra_certs.p12
%patch -P211 -p1 -b .cve8926
%patch -P212 -p1 -b .cve8927
%patch -P213 -p1 -b .cve9026
+%patch -P214 -p1 -b .cve11236
+%patch -P215 -p1 -b .cve11234
+%patch -P216 -p1 -b .cve8932
+%patch -P217 -p1 -b .cve11233
+%patch -P218 -p1 -b .ghsa4w77
+%patch -P219 -p1 -b .cve8929
+%patch -P220 -p1 -b .cve1217
+%patch -P221 -p1 -b .cve1734
+%patch -P222 -p1 -b .cve1861
+%patch -P223 -p1 -b .cve1736
+%patch -P224 -p1 -b .cve1219
+%patch -P225 -p1 -b .cve6491
+%patch -P226 -p1 -b .cve1220
+%patch -P227 -p1 -b .cve1735
# Fixes for tests
%patch -P300 -p1 -b .datetests
%patch -P301 -p1 -b .tests
%patch -P302 -p1 -b .zlibng
+%patch -P303 -p1 -b .pcretests
# WIP patch
@@ -1052,6 +1074,13 @@ rm Zend/tests/bug68412.phpt
rm sapi/cli/tests/upload_2G.phpt
# tar issue
rm ext/zlib/tests/004-mb.phpt
+# Known to fail
+%if 0%{?rhel} == 8
+rm ext/openssl/tests/openssl_error_string_basic.phpt
+rm ext/openssl/tests/openssl_open_basic.phpt
+%endif
+rm ext/openssl/tests/openssl_private_decrypt_basic.phpt
+rm ext/openssl/tests/openssl_x509_parse_basic.phpt
# Safety check for API version change.
pver=$(sed -n '/#define PHP_VERSION /{s/.* "//;s/".*$//;p}' main/php_version.h)
@@ -1279,7 +1308,7 @@ build --libdir=%{_libdir}/php \
--with-oci8=shared,instantclient,%{_root_prefix}/lib/oracle/%{oracledir}/client64/lib,%{oraclever} \
--with-pdo-oci=shared,instantclient,%{_root_prefix}/lib/oracle/%{oracledir}/client64/lib,%{oraclever} \
%endif
-%if %{with_interbase}
+%if %{with firebird}
--with-pdo-firebird=shared \
%endif
--enable-dom=shared \
@@ -1584,7 +1613,7 @@ for mod in pgsql odbc ldap snmp \
%if %{with_oci8}
oci8 pdo_oci \
%endif
-%if %{with_interbase}
+%if %{with firebird}
pdo_firebird \
%endif
%if %{with_freetds}
@@ -1875,7 +1904,7 @@ EOF
%files pspell -f files.pspell
%files intl -f files.intl
%files process -f files.process
-%if %{with_interbase}
+%if %{with firebird}
%files pdo-firebird -f files.pdo_firebird
%endif
%if %{with_enchant}
@@ -1897,6 +1926,50 @@ EOF
%changelog
+* Thu Jul 3 2025 Remi Collet <remi@remirepo.net> - 7.4.33-24
+- Fix pgsql extension does not check for errors during escaping
+ CVE-2025-1735
+- Fix NULL Pointer Dereference in PHP SOAP Extension via Large XML Namespace Prefix
+ CVE-2025-6491
+- Fix Null byte termination in hostnames
+ CVE-2025-1220
+
+* Mon Mar 17 2025 Remi Collet <remi@remirepo.net> - 7.4.33-23
+- Fix libxml streams use wrong `content-type` header when requesting a redirected resource
+ CVE-2025-1219
+- Fix Stream HTTP wrapper header check might omit basic auth header
+ CVE-2025-1736
+- Fix Stream HTTP wrapper truncate redirect location to 1024 bytes
+ CVE-2025-1861
+- Fix Streams HTTP wrapper does not fail for headers without colon
+ CVE-2025-1734
+- Fix Header parser of `http` stream wrapper does not handle folded headers
+ CVE-2025-1217
+- use oracle client library version 23.7 on x86_64 and aarch64
+
+* Thu Feb 13 2025 Remi Collet <remi@remirepo.net> - 7.4.33-22
+- backport fix for ICU 74+
+- backport fix strict prototypes
+
+* Wed Nov 27 2024 Remi Collet <remi@remirepo.net> - 7.4.33-21
+- Fix Leak partial content of the heap through heap buffer over-read
+ CVE-2024-8929
+
+* Fri Nov 22 2024 Remi Collet <remi@remirepo.net> - 7.4.33-20
+- Fix Heap-Use-After-Free in sapi_read_post_data Processing in CLI SAPI Interface
+ GHSA-4w77-75f9-2c8w
+- Fix OOB access in ldap_escape
+ CVE-2024-8932
+- Fix Integer overflow in the dblib/firebird quoter causing OOB writes
+ CVE-2024-11236
+- Fix Configuring a proxy in a stream context might allow for CRLF injection in URIs
+ CVE-2024-11234
+- Fix Single byte overread with convert.quoted-printable-decode filter
+ CVE-2024-11233
+
+* Fri Nov 15 2024 Remi Collet <remi@remirepo.net> - 7.4.33-19
+- disable firebird on EL-10
+
* Thu Sep 26 2024 Remi Collet <remi@remirepo.net> - 7.4.33-18
- Fix Bypass of CVE-2012-1823, Argument Injection in PHP-CGI
CVE-2024-4577