diff --git a/.cirrus.yml b/.cirrus.yml
deleted file mode 100644
index a8e5d8f9e97a3..0000000000000
--- a/.cirrus.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-env:
- CIRRUS_CLONE_DEPTH: 1
-
-freebsd_task:
- name: FREEBSD_DEBUG_NTS
- skip: "changesIncludeOnly('NEWS', 'EXTENSIONS', 'UPGRADING', 'UPGRADING.INTERNALS', '**.md', 'docs/*', 'docs-old/*', '**/README.*', 'CONTRIBUTING.md', 'CODING_STANDARDS.md')"
- freebsd_instance:
- image_family: freebsd-13-3
- env:
- ARCH: amd64
- install_script:
- #- sed -i -e 's/quarterly/latest/g' /etc/pkg/FreeBSD.conf
- #- pkg upgrade -y
- - kldload accf_http
- - pkg install -y autoconf bison gmake re2c icu libiconv png freetype2 enchant2 bzip2 t1lib gmp tidyp libsodium libzip libxml2 libxslt openssl oniguruma pkgconf webp libavif
- script:
- - ./buildconf -f
- - ./configure --prefix=/usr/local --enable-debug --enable-option-checking=fatal --enable-fpm --with-pdo-sqlite --without-pear --with-bz2 --with-avif --with-jpeg --with-webp --with-freetype --enable-gd --enable-exif --with-zip --with-zlib --enable-soap --enable-xmlreader --with-xsl --with-libxml --enable-shmop --enable-pcntl --enable-mbstring --with-curl --enable-sockets --with-openssl --with-iconv=/usr/local --enable-bcmath --enable-calendar --enable-ftp --with-ffi --enable-zend-test --enable-dl-test=shared --enable-intl --with-mhash --with-sodium --enable-werror --with-config-file-path=/etc --with-config-file-scan-dir=/etc/php.d
- - gmake -j2
- - mkdir /etc/php.d
- - gmake install
- - echo opcache.enable_cli=1 > /etc/php.d/opcache.ini
- - echo opcache.protect_memory=1 >> /etc/php.d/opcache.ini
- # Specify opcache.preload_user as we're running as root.
- - echo opcache.preload_user=root >> /etc/php.d/opcache.ini
- tests_script:
- - export SKIP_IO_CAPTURE_TESTS=1
- - export CI_NO_IPV6=1
- - export STACK_LIMIT_DEFAULTS_CHECK=1
- - sapi/cli/php run-tests.php -P -q -j2 -g FAIL,BORK,LEAK,XLEAK --no-progress --offline --show-diff --show-slow 1000 --set-timeout 120 -d zend_extension=opcache.so
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index bca6af8471fcd..5603700ecc98d 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -29,7 +29,8 @@
/ext/json @bukka
/ext/libxml @nielsdos
/ext/mbstring @alexdowad @youkidearitai
-/ext/mysqlnd @SakiTakamachi
+/ext/mysqli @bukka @kamil-tekiela
+/ext/mysqlnd @bukka @kamil-tekiela @SakiTakamachi
/ext/odbc @NattyNarwhal
/ext/opcache @dstogov
/ext/openssl @bukka
@@ -37,7 +38,7 @@
/ext/pdo @SakiTakamachi
/ext/pdo_dblib @SakiTakamachi
/ext/pdo_firebird @SakiTakamachi
-/ext/pdo_mysql @SakiTakamachi
+/ext/pdo_mysql @kamil-tekiela @SakiTakamachi
/ext/pdo_odbc @NattyNarwhal @SakiTakamachi
/ext/pdo_pgsql @devnexen @SakiTakamachi
/ext/pdo_sqlite @SakiTakamachi
diff --git a/.github/actions/freebsd/action.yml b/.github/actions/freebsd/action.yml
new file mode 100644
index 0000000000000..415790bab5a71
--- /dev/null
+++ b/.github/actions/freebsd/action.yml
@@ -0,0 +1,105 @@
+name: FreeBSD
+runs:
+ using: composite
+ steps:
+ - name: FreeBSD
+ uses: vmactions/freebsd-vm@v1
+ with:
+ release: '13.3'
+ usesh: true
+ copyback: false
+ # Temporarily disable sqlite, as FreeBSD ships it with disabled double quotes. We'll need to fix our tests.
+ # https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=269889
+ prepare: |
+ cd $GITHUB_WORKSPACE
+
+ kldload accf_http
+ pkg install -y \
+ autoconf \
+ bison \
+ gmake \
+ re2c \
+ icu \
+ libiconv \
+ png \
+ freetype2 \
+ enchant2 \
+ bzip2 \
+ t1lib \
+ gmp \
+ tidyp \
+ libsodium \
+ libzip \
+ libxml2 \
+ libxslt \
+ openssl \
+ oniguruma \
+ pkgconf \
+ webp \
+ libavif \
+ `#sqlite3` \
+ curl
+
+ ./buildconf -f
+ ./configure \
+ --prefix=/usr/local \
+ --enable-debug \
+ --enable-option-checking=fatal \
+ --enable-fpm \
+ `#--with-pdo-sqlite` \
+ --without-sqlite3 \
+ --without-pdo-sqlite \
+ --without-pear \
+ --with-bz2 \
+ --with-avif \
+ --with-jpeg \
+ --with-webp \
+ --with-freetype \
+ --enable-gd \
+ --enable-exif \
+ --with-zip \
+ --with-zlib \
+ --enable-soap \
+ --enable-xmlreader \
+ --with-xsl \
+ --with-libxml \
+ --enable-shmop \
+ --enable-pcntl \
+ --enable-mbstring \
+ --with-curl \
+ --enable-sockets \
+ --with-openssl \
+ --with-iconv=/usr/local \
+ --enable-bcmath \
+ --enable-calendar \
+ --enable-ftp \
+ --with-ffi \
+ --enable-zend-test \
+ --enable-dl-test=shared \
+ --enable-intl \
+ --with-mhash \
+ --with-sodium \
+ --enable-werror \
+ --with-config-file-path=/etc \
+ --with-config-file-scan-dir=/etc/php.d
+ gmake -j2
+ mkdir /etc/php.d
+ gmake install > /dev/null
+ echo opcache.enable_cli=1 > /etc/php.d/opcache.ini
+ echo opcache.protect_memory=1 >> /etc/php.d/opcache.ini
+ echo opcache.preload_user=root >> /etc/php.d/opcache.ini
+ run: |
+ cd $GITHUB_WORKSPACE
+
+ export SKIP_IO_CAPTURE_TESTS=1
+ export CI_NO_IPV6=1
+ export STACK_LIMIT_DEFAULTS_CHECK=1
+ sapi/cli/php run-tests.php \
+ -P -q -j2 \
+ -g FAIL,BORK,LEAK,XLEAK \
+ --no-progress \
+ --offline \
+ --show-diff \
+ --show-slow 1000 \
+ --set-timeout 120 \
+ -d zend_extension=opcache.so
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index b87b7389ef02d..90e9a1d7b760c 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -814,12 +814,14 @@ jobs:
with:
withMysqli: ${{ inputs.libmysqlclient_with_mysqli }}
- name: Build mysql-8.4
+ if: ${{ !inputs.libmysqlclient_with_mysqli }}
uses: ./.github/actions/build-libmysqlclient
with:
configurationParameters: ${{ !inputs.libmysqlclient_with_mysqli && '--enable-werror' || '' }}
libmysql: mysql-8.4.0-linux-glibc2.28-x86_64.tar.xz
withMysqli: ${{ inputs.libmysqlclient_with_mysqli }}
- name: Test mysql-8.4
+ if: ${{ !inputs.libmysqlclient_with_mysqli }}
uses: ./.github/actions/test-libmysqlclient
with:
withMysqli: ${{ inputs.libmysqlclient_with_mysqli }}
@@ -979,3 +981,13 @@ jobs:
run: .github/scripts/windows/build.bat
- name: Test
run: .github/scripts/windows/test.bat
+ FREEBSD:
+ name: FREEBSD
+ runs-on: ubuntu-latest
+ steps:
+ - name: git checkout
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.branch }}
+ - name: FreeBSD
+ uses: ./.github/actions/freebsd
diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml
index d8912db4baa21..4eaa8fee40eab 100644
--- a/.github/workflows/push.yml
+++ b/.github/workflows/push.yml
@@ -354,3 +354,11 @@ jobs:
name: profiles
path: ${{ github.workspace }}/benchmark/profiles
retention-days: 30
+ FREEBSD:
+ name: FREEBSD
+ runs-on: ubuntu-latest
+ steps:
+ - name: git checkout
+ uses: actions/checkout@v4
+ - name: FreeBSD
+ uses: ./.github/actions/freebsd
diff --git a/.github/workflows/root.yml b/.github/workflows/root.yml
index f526e9bea30d5..cefabd0394a46 100644
--- a/.github/workflows/root.yml
+++ b/.github/workflows/root.yml
@@ -46,12 +46,17 @@ jobs:
matrix:
branch: ${{ fromJson(needs.GENERATE_MATRIX.outputs.branches) }}
with:
- asan_ubuntu_version: '20.04'
+ asan_ubuntu_version: ${{
+ (((matrix.branch.version[0] == 8 && matrix.branch.version[1] >= 5) || matrix.branch.version[0] >= 9) && '24.04')
+ || '20.04' }}
branch: ${{ matrix.branch.ref }}
community_verify_type_inference: ${{ (matrix.branch.version[0] == 8 && matrix.branch.version[1] >= 4) || matrix.branch.version[0] >= 9 }}
libmysqlclient_with_mysqli: ${{ (matrix.branch.version[0] == 8 && matrix.branch.version[1] == 1) }}
run_alpine: ${{ (matrix.branch.version[0] == 8 && matrix.branch.version[1] >= 4) || matrix.branch.version[0] >= 9 }}
run_macos_arm64: ${{ (matrix.branch.version[0] == 8 && matrix.branch.version[1] >= 4) || matrix.branch.version[0] >= 9 }}
- ubuntu_version: ${{ ((matrix.branch.version[0] == 8 && matrix.branch.version[1] >= 3) || matrix.branch.version[0] >= 9) && '22.04' || '20.04' }}
+ ubuntu_version: ${{
+ (((matrix.branch.version[0] == 8 && matrix.branch.version[1] >= 5) || matrix.branch.version[0] >= 9) && '24.04')
+ || ((matrix.branch.version[0] == 8 && matrix.branch.version[1] >= 3) && '22.04')
+ || '20.04' }}
windows_version: ${{ ((matrix.branch.version[0] == 8 && matrix.branch.version[1] >= 4) || matrix.branch.version[0] >= 9) && '2022' || '2019' }}
secrets: inherit
diff --git a/NEWS b/NEWS
index 337fc0b44fdb2..88522ad7ad9a5 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,107 @@
PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+19 Dec 2024, PHP 8.4.2
+
+- BcMath:
+ . Fixed bug GH-16978 (Avoid unnecessary padding with leading zeros)
+ (Saki Takamachi)
+
+- Calendar:
+ . Fixed jdtogregorian overflow. (David Carlier)
+ . Fixed cal_to_jd julian_days argument overflow. (David Carlier)
+
+- COM:
+ . Fixed bug GH-16991 (Getting typeinfo of non DISPATCH variant segfaults).
+ (cmb)
+
+- Core:
+ . Fail early in *nix configuration build script. (hakre)
+ . Fixed bug GH-16344 (setRawValueWithoutLazyInitialization() and
+ skipLazyInitialization() may change initialized proxy). (Arnaud)
+ . Fixed bug GH-16727 (Opcache bad signal 139 crash in ZTS bookworm
+ (frankenphp)). (nielsdos)
+ . Fixed bug GH-16799 (Assertion failure at Zend/zend_vm_execute.h:7469).
+ (nielsdos)
+ . Fixed bug GH-16630 (UAF in lexer with encoding translation and heredocs).
+ (nielsdos)
+ . Fix is_zend_ptr() huge block comparison. (nielsdos)
+ . Fixed potential OOB read in zend_dirname() on Windows. (cmb)
+ . Fixed bug GH-15964 (printf() can strip sign of -INF). (divinity76, cmb)
+
+- Curl:
+ . Fixed bug GH-16802 (open_basedir bypass using curl extension). (nielsdos)
+ . Fix various memory leaks in curl mime handling. (nielsdos)
+
+- DBA:
+ . Fixed bug GH-16990 (dba_list() is now zero-indexed instead of using
+ resource ids) (kocsismate)
+
+- DOM:
+ . Fixed bug GH-16777 (Calling the constructor again on a DOM object after it
+ is in a document causes UAF). (nielsdos)
+ . Fixed bug GH-16906 (Reloading document can cause UAF in iterator).
+ (nielsdos)
+
+- FPM:
+ . Fixed GH-16432 (PHP-FPM 8.2 SIGSEGV in fpm_get_status). (Jakub Zelenka)
+ . Fixed bug GH-16932 (wrong FPM status output). (Jakub Zelenka, James Lucas)
+
+- GD:
+ . Fixed GH-16776 (imagecreatefromstring overflow). (David Carlier)
+
+- GMP:
+ . Fixed bug GH-16890 (array_sum() with GMP can loose precision (LLP64)).
+ (cmb)
+
+- Hash:
+ . Fixed GH-16711: Segfault in mhash(). (Girgias)
+
+- Opcache:
+ . Fixed bug GH-16851 (JIT_G(enabled) not set correctly on other threads).
+ (dktapps)
+ . Fixed bug GH-16902 (Set of opcache tests fail zts+aarch64). (nielsdos)
+ . Fixed bug GH-16879 (JIT dead code skipping does not update call_level).
+ (nielsdos)
+
+- OpenSSL:
+ . Prevent unexpected array entry conversion when reading key. (nielsdos)
+ . Fix various memory leaks related to openssl exports. (nielsdos)
+ . Fix memory leak in php_openssl_pkey_from_zval(). (nielsdos)
+
+- PDO:
+ . Fixed memory leak of `setFetchMode()`. (SakiTakamachi)
+
+- Phar:
+ . Fixed bug GH-16695 (phar:// tar parser and zero-length file header blocks).
+ (nielsdos, Hans Krentel)
+
+- PHPDBG:
+ . Fixed bug GH-15208 (Segfault with breakpoint map and phpdbg_clear()).
+ (nielsdos)
+
+- SAPI:
+ . Fixed bug GH-16998 (UBSAN warning in rfc1867). (nielsdos)
+
+- SimpleXML:
+ . Fixed bug GH-16808 (Segmentation fault in RecursiveIteratorIterator
+ ->current() with a xml element input). (nielsdos)
+
+- SOAP:
+ . Fix make check being invoked in ext/soap. (Ma27)
+
+- Standard:
+ . Fixed bug GH-16905 (Internal iterator functions can't handle UNDEF
+ properties). (nielsdos)
+ . Fixed bug GH-16957 (Assertion failure in array_shift with
+ self-referencing array). (nielsdos)
+
+- Streams:
+ . Fixed network connect poll interuption handling. (Jakub Zelenka)
+
+- Windows:
+ . Fixed bug GH-16849 (Error dialog causes process to hang). (cmb)
+ . Windows Server 2025 is now properly reported. (cmb)
+
21 Nov 2024, PHP 8.4.1
- BcMath:
diff --git a/UPGRADING b/UPGRADING
index 31b51a2c5fb9f..3bab45da53e74 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -127,12 +127,12 @@ PHP 8.4 UPGRADE NOTES
ValueErrors if it is not an array of class names.
. XMLReader:
. Passing an invalid character encoding to XMLReader::open() or
- XMLReader::XML() now throws a ValueError.
- . Passing a string containing null bytes previously emitted a
- warning and now throws a ValueError as well.
+ XMLReader::XML() now throws a ValueError. Passing an encoding
+ containing null bytes previously emitted a warning and now throws
+ a ValueError as well.
. XMLWriter:
- . Passing a string containing null bytes previously emitted a
- warning and now throws a ValueError as well.
+ . Passing an encoding containing null bytes previously emitted a
+ warning and now throws a ValueError.
. XSL:
. XSLTProcessor::setParameter() will now throw a ValueError when its
arguments contain null bytes. This never actually worked correctly in
diff --git a/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_initialized.phpt b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_initialized.phpt
index caa5211f371a5..25580b32ae2cc 100644
--- a/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_initialized.phpt
+++ b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_initialized.phpt
@@ -17,6 +17,7 @@ function test(string $name, object $obj) {
$reflector->initializeLazyObject($obj);
$reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, 'test');
+ var_dump($obj->a);
var_dump($obj);
}
@@ -33,9 +34,22 @@ $obj = $reflector->newLazyProxy(function () {
test('Proxy', $obj);
+$real = new C('foo');
+$obj = $reflector->newLazyProxy(function () use ($real) {
+ return $real;
+});
+$reflector->initializeLazyObject($obj);
+$reflector->resetAsLazyProxy($real, function () {
+ return new C('bar');
+});
+$reflector->initializeLazyObject($real);
+
+test('Nested Proxy', $obj);
+
?>
--EXPECTF--
# Ghost
+string(4) "test"
object(C)#%d (2) {
["a"]=>
string(4) "test"
@@ -43,12 +57,27 @@ object(C)#%d (2) {
NULL
}
# Proxy
+string(4) "test"
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (2) {
["a"]=>
- NULL
+ string(4) "test"
["b"]=>
NULL
}
}
+# Nested Proxy
+string(4) "test"
+lazy proxy object(C)#%d (1) {
+ ["instance"]=>
+ lazy proxy object(C)#%d (1) {
+ ["instance"]=>
+ object(C)#%d (2) {
+ ["a"]=>
+ string(4) "test"
+ ["b"]=>
+ NULL
+ }
+ }
+}
diff --git a/Zend/tests/lazy_objects/skipLazyInitialization_initialized_object.phpt b/Zend/tests/lazy_objects/skipLazyInitialization_initialized_object.phpt
index bf8ff2094ca15..8c2a52de53511 100644
--- a/Zend/tests/lazy_objects/skipLazyInitialization_initialized_object.phpt
+++ b/Zend/tests/lazy_objects/skipLazyInitialization_initialized_object.phpt
@@ -36,6 +36,19 @@ $obj = $reflector->newLazyProxy(function () {
test('Proxy', $obj);
+$real = new C('foo');
+$obj = $reflector->newLazyProxy(function () use ($real) {
+ return $real;
+});
+$reflector->initializeLazyObject($obj);
+$reflector->resetAsLazyProxy($real, function () {
+ var_dump("initializer");
+ return new C('bar');
+});
+$reflector->initializeLazyObject($real);
+
+test('Nested Proxy', $obj);
+
?>
--EXPECTF--
# Ghost
@@ -48,7 +61,7 @@ object(C)#%d (2) {
NULL
}
# Proxy
-int(1)
+int(2)
bool(true)
lazy proxy object(C)#%d (1) {
["instance"]=>
@@ -59,3 +72,19 @@ lazy proxy object(C)#%d (1) {
NULL
}
}
+string(11) "initializer"
+# Nested Proxy
+int(2)
+bool(true)
+lazy proxy object(C)#%d (1) {
+ ["instance"]=>
+ lazy proxy object(C)#%d (1) {
+ ["instance"]=>
+ object(C)#%d (2) {
+ ["a"]=>
+ int(2)
+ ["b"]=>
+ NULL
+ }
+ }
+}
diff --git a/Zend/zend.h b/Zend/zend.h
index 815d6ca58a5da..eac87afc99ee4 100644
--- a/Zend/zend.h
+++ b/Zend/zend.h
@@ -20,7 +20,7 @@
#ifndef ZEND_H
#define ZEND_H
-#define ZEND_VERSION "4.4.1-dev"
+#define ZEND_VERSION "4.4.2"
#define ZEND_ENGINE_3
diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c
index 2c8023e62577e..12e322d0347b3 100644
--- a/Zend/zend_alloc.c
+++ b/Zend/zend_alloc.c
@@ -2617,8 +2617,8 @@ ZEND_API bool is_zend_ptr(const void *ptr)
zend_mm_huge_list *block = AG(mm_heap)->huge_list;
while (block) {
- if (ptr >= (void*)block
- && ptr < (void*)((char*)block + block->size)) {
+ if (ptr >= block->ptr
+ && ptr < (void*)((char*)block->ptr + block->size)) {
return 1;
}
block = block->next;
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index 15ff271c1032e..2d1a50191ff03 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -2201,7 +2201,7 @@ ZEND_API size_t zend_dirname(char *path, size_t len)
}
/* Strip trailing slashes */
- while (end >= path && IS_SLASH_P(end)) {
+ while (end >= path && IS_SLASH_P_EX(end, end == path)) {
end--;
}
if (end < path) {
@@ -2212,7 +2212,7 @@ ZEND_API size_t zend_dirname(char *path, size_t len)
}
/* Strip filename */
- while (end >= path && !IS_SLASH_P(end)) {
+ while (end >= path && !IS_SLASH_P_EX(end, end == path)) {
end--;
}
if (end < path) {
@@ -2223,7 +2223,7 @@ ZEND_API size_t zend_dirname(char *path, size_t len)
}
/* Strip slashes which came before the file name */
- while (end >= path && IS_SLASH_P(end)) {
+ while (end >= path && IS_SLASH_P_EX(end, end == path)) {
end--;
}
if (end < path) {
diff --git a/Zend/zend_virtual_cwd.h b/Zend/zend_virtual_cwd.h
index 0fc2799118692..21735f6dfae57 100644
--- a/Zend/zend_virtual_cwd.h
+++ b/Zend/zend_virtual_cwd.h
@@ -75,8 +75,11 @@ typedef unsigned short mode_t;
#define DEFAULT_SLASH '\\'
#define DEFAULT_DIR_SEPARATOR ';'
#define IS_SLASH(c) ((c) == '/' || (c) == '\\')
+// IS_SLASH_P() may read the previous char on Windows, which may be OOB; use IS_SLASH_P_EX() instead
#define IS_SLASH_P(c) (*(c) == '/' || \
(*(c) == '\\' && !IsDBCSLeadByte(*(c-1))))
+#define IS_SLASH_P_EX(c, first_byte) (*(c) == '/' || \
+ (*(c) == '\\' && ((first_byte) || !IsDBCSLeadByte(*(c-1)))))
/* COPY_WHEN_ABSOLUTE is 2 under Win32 because by chance both regular absolute paths
in the file system and UNC paths need copying of two characters */
@@ -110,7 +113,9 @@ typedef unsigned short mode_t;
#endif
#define IS_SLASH(c) ((c) == '/')
+// IS_SLASH_P() may read the previous char on Windows, which may be OOB; use IS_SLASH_P_EX() instead
#define IS_SLASH_P(c) (*(c) == '/')
+#define IS_SLASH_P_EX(c, first_byte) IS_SLASH_P(c)
#endif
diff --git a/configure.ac b/configure.ac
index cab7e839a7763..4a3f2a010e611 100644
--- a/configure.ac
+++ b/configure.ac
@@ -17,7 +17,7 @@ dnl Basic autoconf initialization, generation of config.nice.
dnl ----------------------------------------------------------------------------
AC_PREREQ([2.68])
-AC_INIT([PHP],[8.4.1-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net])
+AC_INIT([PHP],[8.4.2],[https://github.com/php/php-src/issues],[php],[https://www.php.net])
AC_CONFIG_SRCDIR([main/php_version.h])
AC_CONFIG_AUX_DIR([build])
AC_PRESERVE_HELP_ORDER
diff --git a/ext/bcmath/libbcmath/src/div.c b/ext/bcmath/libbcmath/src/div.c
index 9c8344fe771a8..e9377fcfc4279 100644
--- a/ext/bcmath/libbcmath/src/div.c
+++ b/ext/bcmath/libbcmath/src/div.c
@@ -436,6 +436,7 @@ bool bc_divide(bc_num numerator, bc_num divisor, bc_num *quot, size_t scale)
numerator_bottom_extension = 0;
numeratorend -= scale_diff > numerator_top_extension ? scale_diff - numerator_top_extension : 0;
}
+ numerator_top_extension = MIN(numerator_top_extension, scale);
} else {
numerator_bottom_extension += scale - numerator_scale;
}
diff --git a/ext/bcmath/tests/gh16978.phpt b/ext/bcmath/tests/gh16978.phpt
new file mode 100644
index 0000000000000..4bb19a9be6bbe
--- /dev/null
+++ b/ext/bcmath/tests/gh16978.phpt
@@ -0,0 +1,12 @@
+--TEST--
+GH-16978 Stack buffer overflow ext/bcmath/libbcmath/src/div.c:464:12 in bc_divide
+--EXTENSIONS--
+bcmath
+--FILE--
+
+--EXPECT--
+0.0000000000
+0.0
diff --git a/ext/com_dotnet/com_typeinfo.c b/ext/com_dotnet/com_typeinfo.c
index 4f6a3309b8cd5..28306b5609b08 100644
--- a/ext/com_dotnet/com_typeinfo.c
+++ b/ext/com_dotnet/com_typeinfo.c
@@ -331,7 +331,7 @@ ITypeInfo *php_com_locate_typeinfo(zend_string *type_lib_name, php_com_dotnet_ob
if (obj->typeinfo) {
ITypeInfo_AddRef(obj->typeinfo);
return obj->typeinfo;
- } else {
+ } else if (V_VT(&obj->v) == VT_DISPATCH) {
IDispatch_GetTypeInfo(V_DISPATCH(&obj->v), 0, LANG_NEUTRAL, &typeinfo);
if (typeinfo) {
return typeinfo;
diff --git a/ext/com_dotnet/tests/gh16991.phpt b/ext/com_dotnet/tests/gh16991.phpt
new file mode 100644
index 0000000000000..3623f1f3c4a63
--- /dev/null
+++ b/ext/com_dotnet/tests/gh16991.phpt
@@ -0,0 +1,10 @@
+--TEST--
+GH-16991 (Getting typeinfo of non DISPATCH variant segfaults)
+--EXTENSIONS--
+com_dotnet
+--FILE--
+
+--EXPECTF--
+Warning: com_print_typeinfo(): Unable to find typeinfo using the parameters supplied in %s on line %d
diff --git a/ext/curl/interface.c b/ext/curl/interface.c
index 4186569ace883..255fe98faf48b 100644
--- a/ext/curl/interface.c
+++ b/ext/curl/interface.c
@@ -1438,7 +1438,7 @@ static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpo
postval = Z_STR_P(prop);
if (php_check_open_basedir(ZSTR_VAL(postval))) {
- return FAILURE;
+ goto out_string;
}
prop = zend_read_property(curl_CURLFile_class, Z_OBJ_P(current), "mime", sizeof("mime")-1, 0, &rv);
@@ -1463,15 +1463,18 @@ static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpo
seekfunc = NULL;
}
+ part = curl_mime_addpart(mime);
+ if (part == NULL) {
+ if (stream) {
+ php_stream_close(stream);
+ }
+ goto out_string;
+ }
+
cb_arg = emalloc(sizeof *cb_arg);
cb_arg->filename = zend_string_copy(postval);
cb_arg->stream = stream;
- part = curl_mime_addpart(mime);
- if (part == NULL) {
- zend_string_release_ex(string_key, 0);
- return FAILURE;
- }
if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK
|| (form_error = curl_mime_data_cb(part, filesize, read_cb, seekfunc, free_cb, cb_arg)) != CURLE_OK
|| (form_error = curl_mime_filename(part, filename ? filename : ZSTR_VAL(postval))) != CURLE_OK
@@ -1492,8 +1495,7 @@ static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpo
prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "postname", sizeof("postname")-1, 0, &rv);
if (EG(exception)) {
- zend_string_release_ex(string_key, 0);
- return FAILURE;
+ goto out_string;
}
ZVAL_DEREF(prop);
ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING);
@@ -1502,8 +1504,7 @@ static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpo
prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "mime", sizeof("mime")-1, 0, &rv);
if (EG(exception)) {
- zend_string_release_ex(string_key, 0);
- return FAILURE;
+ goto out_string;
}
ZVAL_DEREF(prop);
ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING);
@@ -1512,8 +1513,7 @@ static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpo
prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "data", sizeof("data")-1, 0, &rv);
if (EG(exception)) {
- zend_string_release_ex(string_key, 0);
- return FAILURE;
+ goto out_string;
}
ZVAL_DEREF(prop);
ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING);
@@ -1525,8 +1525,7 @@ static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpo
part = curl_mime_addpart(mime);
if (part == NULL) {
- zend_string_release_ex(string_key, 0);
- return FAILURE;
+ goto out_string;
}
if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK
|| (form_error = curl_mime_data(part, ZSTR_VAL(postval), ZSTR_LEN(postval))) != CURLE_OK
@@ -1557,7 +1556,7 @@ static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpo
SAVE_CURL_ERROR(ch, error);
if (error != CURLE_OK) {
- return FAILURE;
+ goto out_mime;
}
if ((*ch->clone) == 1) {
@@ -1568,6 +1567,12 @@ static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpo
SAVE_CURL_ERROR(ch, error);
return error == CURLE_OK ? SUCCESS : FAILURE;
+
+out_string:
+ zend_string_release_ex(string_key, false);
+out_mime:
+ curl_mime_free(mime);
+ return FAILURE;
}
/* }}} */
diff --git a/ext/dba/dba.c b/ext/dba/dba.c
index f094fb1f612b1..e4986e1cd2321 100644
--- a/ext/dba/dba.c
+++ b/ext/dba/dba.c
@@ -1293,9 +1293,9 @@ PHP_FUNCTION(dba_list)
zval *zv;
ZEND_HASH_MAP_FOREACH_VAL(&DBA_G(connections), zv) {
- dba_info *info = Z_DBA_INFO_P(zv);
- if (info) {
- add_next_index_str(return_value, zend_string_copy(info->path));
+ dba_connection *connection = Z_DBA_CONNECTION_P(zv);
+ if (connection->info) {
+ add_index_str(return_value, connection->std.handle, zend_string_copy(connection->info->path));
}
} ZEND_HASH_FOREACH_END();
}
diff --git a/ext/dba/tests/gh16990.phpt b/ext/dba/tests/gh16990.phpt
new file mode 100644
index 0000000000000..f3191904722ba
--- /dev/null
+++ b/ext/dba/tests/gh16990.phpt
@@ -0,0 +1,46 @@
+--TEST--
+GH-16990 (dba_list() is now zero-indexed instead of using resource ids)
+--EXTENSIONS--
+dba
+--CONFLICTS--
+dba
+--SKIPIF--
+
+--FILE--
+
+--CLEAN--
+
+--EXPECTF--
+array(2) {
+ [2]=>
+ string(%d) "%s%etest1.dbm"
+ [4]=>
+ string(%d) "%s%etest2.dbm"
+}
diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c
index 9c3922ab5f625..c8372ef8e17ee 100644
--- a/ext/dom/php_dom.c
+++ b/ext/dom/php_dom.c
@@ -1469,6 +1469,10 @@ void dom_namednode_iter(dom_object *basenode, int ntype, dom_object *intern, xml
mapptr->baseobj = basenode;
mapptr->nodetype = ntype;
mapptr->ht = ht;
+ if (EXPECTED(doc != NULL)) {
+ mapptr->dict = doc->dict;
+ xmlDictReference(doc->dict);
+ }
const xmlChar* tmp;
@@ -1582,6 +1586,7 @@ void dom_nnodemap_objects_free_storage(zend_object *object) /* {{{ */
if (!Z_ISUNDEF(objmap->baseobj_zv)) {
zval_ptr_dtor(&objmap->baseobj_zv);
}
+ xmlDictFree(objmap->dict);
efree(objmap);
intern->ptr = NULL;
}
@@ -1613,6 +1618,7 @@ zend_object *dom_nnodemap_objects_new(zend_class_entry *class_type)
objmap->cached_length = -1;
objmap->cached_obj = NULL;
objmap->cached_obj_index = 0;
+ objmap->dict = NULL;
return &intern->std;
}
diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h
index 851bc14d12574..56fdc244f8cfb 100644
--- a/ext/dom/php_dom.h
+++ b/ext/dom/php_dom.h
@@ -88,6 +88,7 @@ typedef struct dom_nnodemap_object {
php_libxml_cache_tag cache_tag;
dom_object *cached_obj;
zend_long cached_obj_index;
+ xmlDictPtr dict;
bool free_local : 1;
bool free_ns : 1;
} dom_nnodemap_object;
diff --git a/ext/dom/php_dom.stub.php b/ext/dom/php_dom.stub.php
index f46550a012c0c..81713403e0e09 100644
--- a/ext/dom/php_dom.stub.php
+++ b/ext/dom/php_dom.stub.php
@@ -2096,7 +2096,7 @@ public function saveXmlFile(string $filename, int $options = 0): int|false {}
* @not-serializable
* @strict-properties
*/
- final class TokenList implements IteratorAggregate, Countable
+ final class TokenList implements \IteratorAggregate, \Countable
{
/** @implementation-alias Dom\Node::__construct */
private function __construct() {}
diff --git a/ext/dom/php_dom_arginfo.h b/ext/dom/php_dom_arginfo.h
index 1481c39bc1e03..ea42d6de49801 100644
--- a/ext/dom/php_dom_arginfo.h
+++ b/ext/dom/php_dom_arginfo.h
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
- * Stub hash: 55ab8f866af63bd2edf96839d35bc8aba88e37ca */
+ * Stub hash: d8a9d33a072c3c9e3798be5eee1833163a18f441 */
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_dom_import_simplexml, 0, 1, DOMAttr|DOMElement, 0)
ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 0)
@@ -3525,13 +3525,13 @@ static zend_class_entry *register_class_Dom_XMLDocument(zend_class_entry *class_
return class_entry;
}
-static zend_class_entry *register_class_Dom_TokenList(zend_class_entry *class_entry_Dom_IteratorAggregate, zend_class_entry *class_entry_Dom_Countable)
+static zend_class_entry *register_class_Dom_TokenList(zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_Countable)
{
zend_class_entry ce, *class_entry;
INIT_NS_CLASS_ENTRY(ce, "Dom", "TokenList", class_Dom_TokenList_methods);
class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE);
- zend_class_implements(class_entry, 2, class_entry_Dom_IteratorAggregate, class_entry_Dom_Countable);
+ zend_class_implements(class_entry, 2, class_entry_IteratorAggregate, class_entry_Countable);
zval property_length_default_value;
ZVAL_UNDEF(&property_length_default_value);
diff --git a/ext/dom/tests/gh16906.phpt b/ext/dom/tests/gh16906.phpt
new file mode 100644
index 0000000000000..791ca13b390e0
--- /dev/null
+++ b/ext/dom/tests/gh16906.phpt
@@ -0,0 +1,17 @@
+--TEST--
+GH-16906 (Reloading document can cause UAF in iterator)
+--EXTENSIONS--
+dom
+--FILE--
+loadXML('');
+$list = $doc->getElementsByTagName('strong');
+$doc->load(__DIR__."/book.xml");
+var_dump($list);
+?>
+--EXPECT--
+object(DOMNodeList)#2 (1) {
+ ["length"]=>
+ int(0)
+}
diff --git a/ext/filter/logical_filters.c b/ext/filter/logical_filters.c
index 81f44dafc3fda..f2125750bf4dc 100644
--- a/ext/filter/logical_filters.c
+++ b/ext/filter/logical_filters.c
@@ -925,13 +925,13 @@ void php_filter_validate_ip(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
if (flags & FILTER_FLAG_GLOBAL_RANGE) {
if (
- (ip[0] == 100 && ip[1] >= 64 && ip[1] <= 127 ) ||
- (ip[0] == 192 && ip[1] == 0 && ip[2] == 0 ) ||
- (ip[0] == 192 && ip[1] == 0 && ip[2] == 2 ) ||
- (ip[0] == 198 && ip[1] >= 18 && ip[1] <= 19 ) ||
- (ip[0] == 198 && ip[1] == 51 && ip[2] == 100 ) ||
- (ip[0] == 203 && ip[1] == 0 && ip[2] == 113 )
- ) {
+ (ip[0] == 100 && ip[1] >= 64 && ip[1] <= 127 ) ||
+ (ip[0] == 192 && ip[1] == 0 && ip[2] == 0 ) ||
+ (ip[0] == 192 && ip[1] == 0 && ip[2] == 2 ) ||
+ (ip[0] == 198 && ip[1] >= 18 && ip[1] <= 19 ) ||
+ (ip[0] == 198 && ip[1] == 51 && ip[2] == 100 ) ||
+ (ip[0] == 203 && ip[1] == 0 && ip[2] == 113 )
+ ) {
RETURN_VALIDATION_FAILED
}
}
@@ -952,23 +952,24 @@ void php_filter_validate_ip(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
}
}
if (flags & FILTER_FLAG_NO_RES_RANGE || flags & FILTER_FLAG_GLOBAL_RANGE) {
- if ((ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0
- && ip[4] == 0 && ip[5] == 0 && ip[6] == 0 && (ip[7] == 0 || ip[7] == 1))
- || (ip[0] == 0x5f)
- || (ip[0] >= 0xfe80 && ip[0] <= 0xfebf)
- || (ip[0] == 0x2001 && (ip[1] == 0x0db8 || (ip[1] >= 0x0010 && ip[1] <= 0x001f)))
- || (ip[0] == 0x3ff3)
- ) {
- RETURN_VALIDATION_FAILED
- }
+ if (
+ (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0 && ip[4] == 0 && ip[5] == 0 && ip[6] == 0 && (ip[7] == 0 || ip[7] == 1)) ||
+ (ip[0] == 0x5f) ||
+ (ip[0] >= 0xfe80 && ip[0] <= 0xfebf) ||
+ (ip[0] == 0x2001 && (ip[1] == 0x0db8 || (ip[1] >= 0x0010 && ip[1] <= 0x001f))) ||
+ (ip[0] == 0x3ff3)
+ ) {
+ RETURN_VALIDATION_FAILED
+ }
}
if (flags & FILTER_FLAG_GLOBAL_RANGE) {
- if ((ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0 && ip[4] == 0 && ip[5] == 0xffff) ||
- (ip[0] == 0x0100 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0) ||
- (ip[0] == 0x2001 && ip[1] <= 0x01ff) ||
- (ip[0] == 0x2001 && ip[1] == 0x0002 && ip[2] == 0) ||
- (ip[0] >= 0xfc00 && ip[0] <= 0xfdff)
- ) {
+ if (
+ (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0 && ip[4] == 0 && ip[5] == 0xffff) ||
+ (ip[0] == 0x0100 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0) ||
+ (ip[0] == 0x2001 && ip[1] <= 0x01ff) ||
+ (ip[0] == 0x2001 && ip[1] == 0x0002 && ip[2] == 0) ||
+ (ip[0] >= 0xfc00 && ip[0] <= 0xfdff)
+ ) {
RETURN_VALIDATION_FAILED
}
}
diff --git a/ext/gmp/gmp.c b/ext/gmp/gmp.c
index 177d0c7f7c91e..420497d2693e0 100644
--- a/ext/gmp/gmp.c
+++ b/ext/gmp/gmp.c
@@ -32,6 +32,10 @@
#include "ext/random/php_random.h"
#include "ext/random/php_random_csprng.h"
+#ifndef mpz_fits_si_p
+# define mpz_fits_si_p mpz_fits_slong_p
+#endif
+
#define GMP_ROUND_ZERO 0
#define GMP_ROUND_PLUSINF 1
#define GMP_ROUND_MINUSINF 2
@@ -293,7 +297,7 @@ static zend_result gmp_cast_object(zend_object *readobj, zval *writeobj, int typ
return SUCCESS;
case _IS_NUMBER:
gmpnum = GET_GMP_OBJECT_FROM_OBJ(readobj)->num;
- if (mpz_fits_slong_p(gmpnum)) {
+ if (mpz_fits_si_p(gmpnum)) {
ZVAL_LONG(writeobj, mpz_get_si(gmpnum));
} else {
ZVAL_DOUBLE(writeobj, mpz_get_d(gmpnum));
@@ -1360,26 +1364,13 @@ ZEND_FUNCTION(gmp_pow)
RETURN_THROWS();
}
- double powmax = log((double)ZEND_LONG_MAX);
-
if (Z_TYPE_P(base_arg) == IS_LONG && Z_LVAL_P(base_arg) >= 0) {
INIT_GMP_RETVAL(gmpnum_result);
- if ((log(Z_LVAL_P(base_arg)) * exp) > powmax) {
- zend_value_error("base and exponent overflow");
- RETURN_THROWS();
- }
mpz_ui_pow_ui(gmpnum_result, Z_LVAL_P(base_arg), exp);
} else {
mpz_ptr gmpnum_base;
- zend_ulong gmpnum;
FETCH_GMP_ZVAL(gmpnum_base, base_arg, temp_base, 1);
INIT_GMP_RETVAL(gmpnum_result);
- gmpnum = mpz_get_ui(gmpnum_base);
- if ((log(gmpnum) * exp) > powmax) {
- FREE_GMP_TEMP(temp_base);
- zend_value_error("base and exponent overflow");
- RETURN_THROWS();
- }
mpz_pow_ui(gmpnum_result, gmpnum_base, exp);
FREE_GMP_TEMP(temp_base);
}
diff --git a/ext/gmp/tests/gh16890.phpt b/ext/gmp/tests/gh16890.phpt
new file mode 100644
index 0000000000000..08fc060559625
--- /dev/null
+++ b/ext/gmp/tests/gh16890.phpt
@@ -0,0 +1,11 @@
+--TEST--
+GH-16890 (array_sum() with GMP can loose precision (LLP64))
+--EXTENSIONS--
+gmp
+--FILE--
+
+--EXPECT--
+bool(true)
diff --git a/ext/gmp/tests/gmp_pow.phpt b/ext/gmp/tests/gmp_pow.phpt
index 1d77bd5e96c80..f42e44e31abed 100644
--- a/ext/gmp/tests/gmp_pow.phpt
+++ b/ext/gmp/tests/gmp_pow.phpt
@@ -2,8 +2,6 @@
gmp_pow() basic tests
--EXTENSIONS--
gmp
---SKIPIF--
-
--FILE--
---FILE--
-getMessage() . "\n";
-}
-var_dump(gmp_strval(gmp_pow("-2",10)));
-try {
- gmp_pow(20,10);
-} catch (ValueError $exception) {
- echo $exception->getMessage() . "\n";
-}
-try {
- gmp_pow(50,10);
-} catch (ValueError $exception) {
- echo $exception->getMessage() . "\n";
-}
-try {
- gmp_pow(50,-5);
-} catch (ValueError $exception) {
- echo $exception->getMessage() . "\n";
-}
-try {
- $n = gmp_init("20");
- gmp_pow($n,10);
-} catch (ValueError $exception) {
- echo $exception->getMessage() . "\n";
-}
-try {
- $n = gmp_init("-20");
- gmp_pow($n,10);
-} catch (ValueError $exception) {
- echo $exception->getMessage() . "\n";
-}
-try {
- var_dump(gmp_pow(2,array()));
-} catch (TypeError $e) {
- echo $e->getMessage(), "\n";
-}
-
-try {
- var_dump(gmp_pow(array(),10));
-} catch (\TypeError $e) {
- echo $e->getMessage() . \PHP_EOL;
-}
-
-echo "Done\n";
-?>
---EXPECT--
-string(4) "1024"
-string(4) "1024"
-string(5) "-2048"
-string(4) "1024"
-string(1) "1"
-gmp_pow(): Argument #2 ($exponent) must be greater than or equal to 0
-string(4) "1024"
-base and exponent overflow
-base and exponent overflow
-gmp_pow(): Argument #2 ($exponent) must be greater than or equal to 0
-base and exponent overflow
-base and exponent overflow
-gmp_pow(): Argument #2 ($exponent) must be of type int, array given
-gmp_pow(): Argument #1 ($num) must be of type GMP|string|int, array given
-Done
diff --git a/ext/gmp/tests/gmp_pow_fpe.phpt b/ext/gmp/tests/gmp_pow_fpe.phpt
deleted file mode 100644
index 248922e80514d..0000000000000
--- a/ext/gmp/tests/gmp_pow_fpe.phpt
+++ /dev/null
@@ -1,35 +0,0 @@
---TEST--
-gmp_pow() floating point exception
---EXTENSIONS--
-gmp
---FILE--
-getMessage() . PHP_EOL;
-}
-try {
- gmp_pow(256, PHP_INT_MAX);
-} catch (\ValueError $e) {
- echo $e->getMessage() . PHP_EOL;
-}
-
-try {
- gmp_pow(gmp_add(gmp_mul(gmp_init(PHP_INT_MAX), gmp_init(PHP_INT_MAX)), 3), 256);
-} catch (\ValueError $e) {
- echo $e->getMessage() . PHP_EOL;
-}
-try {
- gmp_pow(gmp_init(PHP_INT_MAX), 256);
-} catch (\ValueError $e) {
- echo $e->getMessage();
-}
-?>
---EXPECTF--
-base and exponent overflow
-base and exponent overflow
-base and exponent overflow
-base and exponent overflow
diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c
index a683ba68e366b..e49cf467dd56b 100644
--- a/ext/ldap/ldap.c
+++ b/ext/ldap/ldap.c
@@ -3783,13 +3783,23 @@ static zend_string* php_ldap_do_escape(const bool *map, const char *value, size_
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;
}
@@ -3856,7 +3866,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_argument_value_error(1, "is too long");
+ RETURN_THROWS();
+ }
+
+ 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 0000000000000..8e2c4fb160de3
--- /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--
+
+--FILE--
+getMessage(), "\n";
+}
+
+try {
+ ldap_escape(str_repeat("#", 1431655758).' ', "", LDAP_ESCAPE_DN);
+} catch (ValueError $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 0000000000000..a69597084be6c
--- /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--
+
+--FILE--
+getMessage(), "\n";
+}
+
+// would allocate a string of length 2
+try {
+ ldap_escape(str_repeat("*", 1431655766), "", LDAP_ESCAPE_FILTER);
+} catch (ValueError $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/mysqli/tests/063.phpt b/ext/mysqli/tests/063.phpt
index 54076897e8449..1f7761c89a3d0 100644
--- a/ext/mysqli/tests/063.phpt
+++ b/ext/mysqli/tests/063.phpt
@@ -29,6 +29,6 @@ require_once 'skipifconnectfailure.inc';
$mysql->close();
?>
---EXPECT--
+--EXPECTF--
string(3) "foo"
-Unknown column 'invalid' in 'field list'
+Unknown column 'invalid' in '%r(SELECT|field list)%r'
diff --git a/ext/mysqli/tests/bug71863.phpt b/ext/mysqli/tests/bug71863.phpt
index 4fc872bc9df59..f017fd68a58f6 100644
--- a/ext/mysqli/tests/bug71863.phpt
+++ b/ext/mysqli/tests/bug71863.phpt
@@ -30,4 +30,4 @@ if (!mysqli_query($link, "DROP TABLE IF EXISTS test_bug_71863"))
mysqli_close($link);
?>
--EXPECTF--
-%AUnknown column 'owner_id' in 'where clause'
+%AUnknown column 'owner_id' in '%r(WHERE|where clause)%r'
diff --git a/ext/mysqli/tests/fake_server.inc b/ext/mysqli/tests/fake_server.inc
new file mode 100644
index 0000000000000..1127f6c00e3f9
--- /dev/null
+++ b/ext/mysqli/tests/fake_server.inc
@@ -0,0 +1,856 @@
+ [
+ '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 20ms to fill the buffer
+ usleep(20000);
+ $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 0000000000000..279aec6a2cba1
--- /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--
+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:33305
+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 0000000000000..77f2232eca687
--- /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--
+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 0000000000000..0b4db8ccece95
--- /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--
+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 0000000000000..f141a79bdaa85
--- /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--
+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 0000000000000..e43518217eb63
--- /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--
+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 0000000000000..76158e940d09d
--- /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--
+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 0000000000000..f53d5b83bd432
--- /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--
+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 0000000000000..03c9b045d7375
--- /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--
+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 0000000000000..b1ec9aa51eca1
--- /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--
+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 0000000000000..426d9ea7b3f9b
--- /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--
+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 0000000000000..6db6952d42a15
--- /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--
+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 0000000000000..55bad4cc544aa
--- /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--
+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 0000000000000..06918c375f31a
--- /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--
+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/mysqli_stmt_datatype_change.phpt b/ext/mysqli/tests/mysqli_stmt_datatype_change.phpt
index e98824a65db3a..78c9da6286b03 100644
--- a/ext/mysqli/tests/mysqli_stmt_datatype_change.phpt
+++ b/ext/mysqli/tests/mysqli_stmt_datatype_change.phpt
@@ -65,7 +65,7 @@ if (!mysqli_query($link, "DROP TABLE IF EXISTS type_change"))
mysqli_close($link);
?>
---EXPECT--
+--EXPECTF--
bool(true)
bool(true)
---- Row 1
@@ -80,7 +80,7 @@ NULL
ALTER
bool(true)
bool(false)
-string(34) "Unknown column 'a' in 'field list'"
+string(%d) "Unknown column 'a' in '%r(SELECT|field list)%r'"
---- Row 1
bool(false)
int(2)
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 0000000000000..524fe5e587c63
--- /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--
+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 0000000000000..af16a9eb2d05f
--- /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--
+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!";
+?>
+--EXPECTF--
+[*] 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
+%s578721382704613384%s
+[*] 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 a9c420dd1473b..a2e98cf358bb4 100644
--- a/ext/mysqlnd/mysqlnd_ps_codec.c
+++ b/ext/mysqlnd/mysqlnd_ps_codec.c
@@ -50,11 +50,46 @@ 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 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 false;
+ }
+ size_t length_len = *row - p;
+ if (length_len > pack_len || length > pack_len - length_len) {
+ ps_fetch_over_read_error(row);
+ return true;
+ }
+ return false;
+}
+
+static inline 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 true;
+ }
+ return false;
+}
+
+
/* {{{ 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,
const zend_uchar ** row, unsigned int byte_count)
{
+ if (UNEXPECTED(ps_fetch_is_packet_over_read_with_static_length(pack_len, row, byte_count))) {
+ return;
+ }
+
bool is_bit = field->type == MYSQL_TYPE_BIT;
DBG_ENTER("ps_fetch_from_1_to_8_bytes");
DBG_INF_FMT("zv=%p byte_count=%u", zv, byte_count);
@@ -174,6 +209,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);
@@ -196,6 +236,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;
@@ -211,9 +256,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 */
+ 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;
@@ -256,9 +306,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*/
+ 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;
@@ -288,9 +343,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*/
+ 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;
@@ -332,7 +392,11 @@ ps_fetch_datetime(zval * zv, const MYSQLND_FIELD * const field, const unsigned i
static void
ps_fetch_string(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;
+ }
DBG_ENTER("ps_fetch_string");
DBG_INF_FMT("len = " ZEND_ULONG_FMT, length);
DBG_INF("copying from the row buffer");
@@ -348,7 +412,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_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c
index b1f38f01b2180..36fd532337375 100644
--- a/ext/mysqlnd/mysqlnd_wireprotocol.c
+++ b/ext/mysqlnd/mysqlnd_wireprotocol.c
@@ -455,8 +455,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",
@@ -712,7 +735,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;
@@ -1092,6 +1122,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);
memcpy(packet->info_or_local_file.s, p, len);
packet->info_or_local_file.s[len] = '\0';
@@ -1242,23 +1283,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 " ZEND_ULONG_FMT, 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);
@@ -1421,8 +1455,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 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;
@@ -1455,7 +1491,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) {
@@ -1513,7 +1563,7 @@ php_mysqlnd_rowp_read_text_protocol(MYSQLND_ROW_BUFFER * row_buffer, zval * fiel
unsigned int field_count, const MYSQLND_FIELD * fields_metadata,
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;
@@ -1534,9 +1584,11 @@ php_mysqlnd_rowp_read_text_protocol(MYSQLND_ROW_BUFFER * row_buffer, zval * fiel
/* 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 %zu"
- " 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 {
struct st_mysqlnd_perm_bind perm_bind =
diff --git a/ext/odbc/odbc.stub.php b/ext/odbc/odbc.stub.php
index f1eb2a890b3c3..8a54e913e88ed 100644
--- a/ext/odbc/odbc.stub.php
+++ b/ext/odbc/odbc.stub.php
@@ -351,18 +351,15 @@ function odbc_exec(Odbc\Connection $odbc, string $query): Odbc\Result|false {}
function odbc_do(Odbc\Connection $odbc, string $query): Odbc\Result|false {}
#ifdef PHP_ODBC_HAVE_FETCH_HASH
- /** @param resource $statement */
- function odbc_fetch_object($statement, ?int $row = null): stdClass|false {}
+ function odbc_fetch_object(Odbc\Result $statement, ?int $row = null): stdClass|false {}
- /** @param resource $statement */
- function odbc_fetch_array($statement, ?int $row = null): array|false {}
+ function odbc_fetch_array(Odbc\Result $statement, ?int $row = null): array|false {}
#endif
/**
- * @param resource $statement
* @param array $array
*/
- function odbc_fetch_into($statement, &$array, ?int $row = null): int|false {}
+ function odbc_fetch_into(Odbc\Result $statement, &$array, ?int $row = null): int|false {}
function odbc_fetch_row(Odbc\Result $statement, ?int $row = null): bool {}
diff --git a/ext/odbc/odbc_arginfo.h b/ext/odbc/odbc_arginfo.h
index d586f5a948edc..91df4da846d49 100644
--- a/ext/odbc/odbc_arginfo.h
+++ b/ext/odbc/odbc_arginfo.h
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
- * Stub hash: 30ed66d5e97f6615a461d39f40f85a18ba618711 */
+ * Stub hash: efd913e4fcacb2949dc5392857032ab9c59c818d */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_odbc_close_all, 0, 0, IS_VOID, 0)
ZEND_END_ARG_INFO()
@@ -41,18 +41,18 @@ ZEND_END_ARG_INFO()
#if defined(PHP_ODBC_HAVE_FETCH_HASH)
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_odbc_fetch_object, 0, 1, stdClass, MAY_BE_FALSE)
- ZEND_ARG_INFO(0, statement)
+ ZEND_ARG_OBJ_INFO(0, statement, Odbc\\Result, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, row, IS_LONG, 1, "null")
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_odbc_fetch_array, 0, 1, MAY_BE_ARRAY|MAY_BE_FALSE)
- ZEND_ARG_INFO(0, statement)
+ ZEND_ARG_OBJ_INFO(0, statement, Odbc\\Result, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, row, IS_LONG, 1, "null")
ZEND_END_ARG_INFO()
#endif
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_odbc_fetch_into, 0, 2, MAY_BE_LONG|MAY_BE_FALSE)
- ZEND_ARG_INFO(0, statement)
+ ZEND_ARG_OBJ_INFO(0, statement, Odbc\\Result, 0)
ZEND_ARG_INFO(1, array)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, row, IS_LONG, 1, "null")
ZEND_END_ARG_INFO()
diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c
index e0f8cda2298e8..bf536c08153e2 100644
--- a/ext/opcache/ZendAccelerator.c
+++ b/ext/opcache/ZendAccelerator.c
@@ -3282,6 +3282,7 @@ static zend_result accel_post_startup(void)
zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Could not enable JIT: could not use reserved buffer!");
} else {
zend_jit_startup(ZSMMG(reserved), jit_size, reattached);
+ zend_jit_startup_ok = true;
}
}
#endif
diff --git a/ext/opcache/jit/ir/ir.c b/ext/opcache/jit/ir/ir.c
index 5da5beaae4e50..c45b1efc21eeb 100644
--- a/ext/opcache/jit/ir/ir.c
+++ b/ext/opcache/jit/ir/ir.c
@@ -1092,13 +1092,31 @@ void ir_set_op(ir_ctx *ctx, ir_ref ref, int32_t n, ir_ref val)
ir_insn_set_op(insn, n, val);
}
+ir_ref ir_get_op(ir_ctx *ctx, ir_ref ref, int32_t n)
+{
+ ir_insn *insn = &ctx->ir_base[ref];
+
+#ifdef IR_DEBUG
+ if (n > 3) {
+ int32_t count;
+
+ IR_ASSERT(IR_OP_HAS_VAR_INPUTS(ir_op_flags[insn->op]));
+ count = insn->inputs_count;
+ IR_ASSERT(n <= count);
+ }
+#endif
+ return ir_insn_op(insn, n);
+}
+
ir_ref ir_param(ir_ctx *ctx, ir_type type, ir_ref region, const char *name, int pos)
{
+ IR_ASSERT(ctx->ir_base[region].op == IR_START);
return ir_emit(ctx, IR_OPT(IR_PARAM, type), region, ir_str(ctx, name), pos);
}
ir_ref ir_var(ir_ctx *ctx, ir_type type, ir_ref region, const char *name)
{
+ IR_ASSERT(IR_IS_BB_START(ctx->ir_base[region].op));
return ir_emit(ctx, IR_OPT(IR_VAR, type), region, ir_str(ctx, name), IR_UNUSED);
}
@@ -1121,6 +1139,12 @@ ir_ref ir_bind(ir_ctx *ctx, ir_ref var, ir_ref def)
return def;
}
+ir_ref ir_binding_find(const ir_ctx *ctx, ir_ref ref)
+{
+ ir_ref var = ir_hashtab_find(ctx->binding, ref);
+ return (var != (ir_ref)IR_INVALID_VAL) ? var : 0;
+}
+
/* Batch construction of def->use edges */
#if 0
void ir_build_def_use_lists(ir_ctx *ctx)
@@ -1158,7 +1182,7 @@ void ir_build_def_use_lists(ir_ctx *ctx)
use_list->count = 0;
}
- edges = ir_mem_malloc(edges_count * sizeof(ir_ref));
+ edges = ir_mem_malloc(IR_ALIGNED_SIZE(edges_count * sizeof(ir_ref), 4096));
for (i = IR_UNUSED + 1, insn = ctx->ir_base + i; i < ctx->insns_count;) {
n = insn->inputs_count;
for (j = n, p = insn->ops + 1; j > 0; j--, p++) {
@@ -1227,7 +1251,7 @@ void ir_build_def_use_lists(ir_ctx *ctx)
}
ctx->use_edges_count = edges_count;
- edges = ir_mem_malloc(edges_count * sizeof(ir_ref));
+ edges = ir_mem_malloc(IR_ALIGNED_SIZE(edges_count * sizeof(ir_ref), 4096));
for (use_list = lists + ctx->insns_count - 1; use_list != lists; use_list--) {
n = use_list->refs;
if (n) {
@@ -1338,8 +1362,13 @@ bool ir_use_list_add(ir_ctx *ctx, ir_ref to, ir_ref ref)
use_list->count++;
return 0;
} else {
- /* Reallocate the whole edges buffer (this is inefficient) */
- ctx->use_edges = ir_mem_realloc(ctx->use_edges, (ctx->use_edges_count + use_list->count + 1) * sizeof(ir_ref));
+ size_t old_size = IR_ALIGNED_SIZE(ctx->use_edges_count * sizeof(ir_ref), 4096);
+ size_t new_size = IR_ALIGNED_SIZE((ctx->use_edges_count + use_list->count + 1) * sizeof(ir_ref), 4096);
+
+ if (old_size < new_size) {
+ /* Reallocate the whole edges buffer (this is inefficient) */
+ ctx->use_edges = ir_mem_realloc(ctx->use_edges, new_size);
+ }
memcpy(ctx->use_edges + ctx->use_edges_count, ctx->use_edges + use_list->refs, use_list->count * sizeof(ir_ref));
use_list->refs = ctx->use_edges_count;
ctx->use_edges[use_list->refs + use_list->count] = ref;
@@ -1947,7 +1976,7 @@ ir_ref _ir_VAR(ir_ctx *ctx, ir_type type, const char* name)
ir_ref ref = ctx->control;
while (1) {
- IR_ASSERT(ctx->control);
+ IR_ASSERT(ref);
if (IR_IS_BB_START(ctx->ir_base[ref].op)) {
break;
}
@@ -2820,6 +2849,10 @@ void _ir_VSTORE(ir_ctx *ctx, ir_ref var, ir_ref val)
}
} else if (insn->op == IR_VLOAD) {
if (insn->op2 == var) {
+ if (ref == val) {
+ /* dead STORE */
+ return;
+ }
break;
}
} else if (insn->op == IR_GUARD || insn->op == IR_GUARD_NOT) {
@@ -2910,6 +2943,10 @@ void _ir_STORE(ir_ctx *ctx, ir_ref addr, ir_ref val)
}
} else if (insn->op == IR_LOAD) {
if (insn->op2 == addr) {
+ if (ref == val) {
+ /* dead STORE */
+ return;
+ }
break;
}
type2 = insn->type;
diff --git a/ext/opcache/jit/ir/ir.h b/ext/opcache/jit/ir/ir.h
index cf7580dc7496a..c4f0926e08589 100644
--- a/ext/opcache/jit/ir/ir.h
+++ b/ext/opcache/jit/ir/ir.h
@@ -720,6 +720,8 @@ IR_ALWAYS_INLINE void ir_set_op3(ir_ctx *ctx, ir_ref ref, ir_ref val)
ctx->ir_base[ref].op3 = val;
}
+ir_ref ir_get_op(ir_ctx *ctx, ir_ref ref, int32_t n);
+
IR_ALWAYS_INLINE ir_ref ir_insn_op(const ir_insn *insn, int32_t n)
{
const ir_ref *p = insn->ops + n;
@@ -741,7 +743,10 @@ ir_ref ir_fold3(ir_ctx *ctx, uint32_t opt, ir_ref op1, ir_ref op2, ir_ref op3);
ir_ref ir_param(ir_ctx *ctx, ir_type type, ir_ref region, const char *name, int pos);
ir_ref ir_var(ir_ctx *ctx, ir_type type, ir_ref region, const char *name);
+
+/* IR Binding */
ir_ref ir_bind(ir_ctx *ctx, ir_ref var, ir_ref def);
+ir_ref ir_binding_find(const ir_ctx *ctx, ir_ref ref);
/* Def -> Use lists */
void ir_build_def_use_lists(ir_ctx *ctx);
diff --git a/ext/opcache/jit/ir/ir_aarch64.dasc b/ext/opcache/jit/ir/ir_aarch64.dasc
index 11c0f320dd2b1..27595ad31248d 100644
--- a/ext/opcache/jit/ir/ir_aarch64.dasc
+++ b/ext/opcache/jit/ir/ir_aarch64.dasc
@@ -3731,6 +3731,10 @@ static void ir_emit_vload(ir_ctx *ctx, ir_ref def, ir_insn *insn)
int32_t offset;
ir_mem mem;
+ if (ctx->use_lists[def].count == 1) {
+ /* dead load */
+ return;
+ }
IR_ASSERT(var_insn->op == IR_VAR);
fp = (ctx->flags & IR_USE_FRAME_POINTER) ? IR_REG_FRAME_POINTER : IR_REG_STACK_POINTER;
offset = IR_SPILL_POS_TO_OFFSET(var_insn->op3);
@@ -4128,6 +4132,10 @@ static void ir_emit_block_begin(ir_ctx *ctx, ir_ref def, ir_insn *insn)
dasm_State **Dst = &data->dasm_state;
ir_reg def_reg = IR_REG_NUM(ctx->regs[def][0]);
+ if (ctx->use_lists[def].count == 1) {
+ /* dead load */
+ return;
+ }
| mov Rx(def_reg), sp
if (IR_REG_SPILLED(ctx->regs[def][0])) {
@@ -5247,6 +5255,14 @@ static void ir_emit_tls(ir_ctx *ctx, ir_ref def, ir_insn *insn)
| ldr Rx(reg), [Rx(reg), #insn->op2]
| ldr Rx(reg), [Rx(reg), #insn->op3]
|| }
+||# elif defined(__MUSL__)
+|| if (insn->op3 == IR_NULL) {
+| ldr Rx(reg), [Rx(reg), #insn->op2]
+|| } else {
+| ldr Rx(reg), [Rx(reg), #-8]
+| ldr Rx(reg), [Rx(reg), #insn->op2]
+| ldr Rx(reg), [Rx(reg), #insn->op3]
+|| }
||# else
||//??? IR_ASSERT(insn->op2 <= LDR_STR_PIMM64);
| ldr Rx(reg), [Rx(reg), #insn->op2]
diff --git a/ext/opcache/jit/ir/ir_cfg.c b/ext/opcache/jit/ir/ir_cfg.c
index c2893dcf292d6..0a36d5d9880d7 100644
--- a/ext/opcache/jit/ir/ir_cfg.c
+++ b/ext/opcache/jit/ir/ir_cfg.c
@@ -59,7 +59,7 @@ IR_ALWAYS_INLINE void _ir_add_predecessors(const ir_insn *insn, ir_worklist *wor
int ir_build_cfg(ir_ctx *ctx)
{
- ir_ref n, *p, ref, start, end, next;
+ ir_ref n, *p, ref, start, end;
uint32_t b;
ir_insn *insn;
ir_worklist worklist;
@@ -145,18 +145,8 @@ int ir_build_cfg(ir_ctx *ctx)
start = ref;
/* Skip control nodes untill BB end */
while (1) {
- use_list = &ctx->use_lists[ref];
- n = use_list->count;
- next = IR_UNUSED;
- for (p = &ctx->use_edges[use_list->refs]; n > 0; p++, n--) {
- next = *p;
- insn = &ctx->ir_base[next];
- if ((ir_op_flags[insn->op] & IR_OP_FLAG_CONTROL) && insn->op1 == ref) {
- break;
- }
- }
- IR_ASSERT(next != IR_UNUSED);
- ref = next;
+ ref = ir_next_control(ctx, ref);
+ insn = &ctx->ir_base[ref];
if (IR_IS_BB_END(insn->op)) {
break;
}
diff --git a/ext/opcache/jit/ir/ir_emit.c b/ext/opcache/jit/ir/ir_emit.c
index ea39830da08c9..83fc242a20c11 100644
--- a/ext/opcache/jit/ir/ir_emit.c
+++ b/ext/opcache/jit/ir/ir_emit.c
@@ -566,6 +566,9 @@ static int ir_parallel_copy(ir_ctx *ctx, ir_copy *copies, int count, ir_reg tmp_
if (IR_IS_TYPE_INT(type)) {
#ifdef IR_HAVE_SWAP_INT
if (pred[from] == to) {
+ if (ir_type_size[types[to]] > ir_type_size[type]) {
+ type = types[to];
+ }
ir_emit_swap(ctx, type, to, from);
IR_REGSET_EXCL(todo, from);
loc[to] = from;
@@ -579,7 +582,7 @@ static int ir_parallel_copy(ir_ctx *ctx, ir_copy *copies, int count, ir_reg tmp_
loc[to] = tmp_reg;
} else {
#ifdef IR_HAVE_SWAP_FP
- if (pred[from] == to) {
+ if (pred[from] == to && types[to] == type) {
ir_emit_swap_fp(ctx, type, to, from);
IR_REGSET_EXCL(todo, from);
loc[to] = from;
diff --git a/ext/opcache/jit/ir/ir_fold.h b/ext/opcache/jit/ir/ir_fold.h
index 2e65ae3119f17..b23ea832df962 100644
--- a/ext/opcache/jit/ir/ir_fold.h
+++ b/ext/opcache/jit/ir/ir_fold.h
@@ -1513,6 +1513,34 @@ IR_FOLD(NOT(UGT))
IR_FOLD_NEXT;
}
+IR_FOLD(EQ(SUB, C_U8))
+IR_FOLD(EQ(SUB, C_U16))
+IR_FOLD(EQ(SUB, C_U32))
+IR_FOLD(EQ(SUB, C_U64))
+IR_FOLD(EQ(SUB, C_I8))
+IR_FOLD(EQ(SUB, C_I16))
+IR_FOLD(EQ(SUB, C_I32))
+IR_FOLD(EQ(SUB, C_I64))
+IR_FOLD(EQ(SUB, C_ADDR))
+IR_FOLD(NE(SUB, C_U8))
+IR_FOLD(NE(SUB, C_U16))
+IR_FOLD(NE(SUB, C_U32))
+IR_FOLD(NE(SUB, C_U64))
+IR_FOLD(NE(SUB, C_I8))
+IR_FOLD(NE(SUB, C_I16))
+IR_FOLD(NE(SUB, C_I32))
+IR_FOLD(NE(SUB, C_I64))
+IR_FOLD(NE(SUB, C_ADDR))
+{
+ /* (a - b) == 0 => a == b */
+ if (ctx->use_lists && ctx->use_lists[op1].count == 1 && op2_insn->val.u64 == 0) {
+ op1 = op1_insn->op1;
+ op2 = op1_insn->op2;
+ IR_FOLD_RESTART;
+ }
+ IR_FOLD_NEXT;
+}
+
IR_FOLD(ADD(_, C_U8))
IR_FOLD(ADD(_, C_U16))
IR_FOLD(ADD(_, C_U32))
diff --git a/ext/opcache/jit/ir/ir_gcm.c b/ext/opcache/jit/ir/ir_gcm.c
index 0d816ab88e229..ed1cd7e39be78 100644
--- a/ext/opcache/jit/ir/ir_gcm.c
+++ b/ext/opcache/jit/ir/ir_gcm.c
@@ -890,9 +890,11 @@ int ir_schedule(ir_ctx *ctx)
/* Topological sort according dependencies inside each basic block */
for (b = 1, bb = ctx->cfg_blocks + 1; b <= ctx->cfg_blocks_count; b++, bb++) {
+ ir_ref start;
+
IR_ASSERT(!(bb->flags & IR_BB_UNREACHABLE));
/* Schedule BB start */
- i = bb->start;
+ start = i = bb->start;
_xlat[i] = bb->start = insns_count;
insn = &ctx->ir_base[i];
if (insn->op == IR_CASE_VAL) {
@@ -904,12 +906,15 @@ int ir_schedule(ir_ctx *ctx)
i = _next[i];
insn = &ctx->ir_base[i];
if (bb->flags & (IR_BB_HAS_PHI|IR_BB_HAS_PI|IR_BB_HAS_PARAM|IR_BB_HAS_VAR)) {
+ int count = 0;
+
/* Schedule PARAM, VAR, PI */
while (insn->op == IR_PARAM || insn->op == IR_VAR || insn->op == IR_PI) {
_xlat[i] = insns_count;
insns_count += 1;
i = _next[i];
insn = &ctx->ir_base[i];
+ count++;
}
/* Schedule PHIs */
while (insn->op == IR_PHI) {
@@ -926,6 +931,52 @@ int ir_schedule(ir_ctx *ctx)
}
i = _next[i];
insn = &ctx->ir_base[i];
+ count++;
+ }
+ /* Schedule remaining PHIs */
+ if (UNEXPECTED(count < ctx->use_lists[start].count - 1)) {
+ ir_use_list *use_list = &ctx->use_lists[start];
+ ir_ref *p, count = use_list->count;
+ ir_ref phis = _prev[i];
+
+ for (p = &ctx->use_edges[use_list->refs]; count > 0; p++, count--) {
+ ir_ref use = *p;
+ if (!_xlat[use]) {
+ ir_insn *use_insn = &ctx->ir_base[use];
+ if (use_insn->op == IR_PARAM
+ || use_insn->op == IR_VAR
+ || use_insn->op == IR_PI
+ || use_insn->op == IR_PHI) {
+ if (_prev[use] != phis) {
+ /* remove "use" */
+ _prev[_next[use]] = _prev[use];
+ _next[_prev[use]] = _next[use];
+ /* insert "use" after "phis" */
+ _prev[use] = phis;
+ _next[use] = _next[phis];
+ _prev[_next[phis]] = use;
+ _next[phis] = use;
+ }
+ phis = use;
+ _xlat[use] = insns_count;
+ if (use_insn->op == IR_PHI) {
+ ir_ref *q;
+ /* Reuse "n" from MERGE and skip first input */
+ insns_count += ir_insn_inputs_to_len(n + 1);
+ for (j = n, q = use_insn->ops + 2; j > 0; q++, j--) {
+ ir_ref input = *q;
+ if (input < IR_TRUE) {
+ consts_count += ir_count_constant(_xlat, input);
+ }
+ }
+ } else {
+ insns_count += 1;
+ }
+ }
+ }
+ }
+ i = _next[phis];
+ insn = &ctx->ir_base[i];
}
}
if (bb->successors_count > 1) {
diff --git a/ext/opcache/jit/ir/ir_private.h b/ext/opcache/jit/ir/ir_private.h
index 064d713b20147..fe4a79426862f 100644
--- a/ext/opcache/jit/ir/ir_private.h
+++ b/ext/opcache/jit/ir/ir_private.h
@@ -1013,13 +1013,6 @@ IR_ALWAYS_INLINE uint32_t ir_insn_len(const ir_insn *insn)
#define IR_RESERVED_FLAG_1 (1U<<31)
-/*** IR Binding ***/
-IR_ALWAYS_INLINE ir_ref ir_binding_find(const ir_ctx *ctx, ir_ref ref)
-{
- ir_ref var = ir_hashtab_find(ctx->binding, ref);
- return (var != (ir_ref)IR_INVALID_VAL) ? var : 0;
-}
-
/*** IR Use Lists ***/
struct _ir_use_list {
ir_ref refs; /* index in ir_ctx->use_edges[] array */
@@ -1032,6 +1025,25 @@ void ir_use_list_replace_all(ir_ctx *ctx, ir_ref ref, ir_ref use, ir_ref new_use
void ir_use_list_replace_one(ir_ctx *ctx, ir_ref ref, ir_ref use, ir_ref new_use);
bool ir_use_list_add(ir_ctx *ctx, ir_ref to, ir_ref new_use);
+IR_ALWAYS_INLINE ir_ref ir_next_control(const ir_ctx *ctx, ir_ref ref)
+{
+ ir_use_list *use_list = &ctx->use_lists[ref];
+ ir_ref n = use_list->count;
+ ir_ref *p;
+
+ IR_ASSERT(ir_op_flags[ctx->ir_base[ref].op] & IR_OP_FLAG_CONTROL);
+ for (p = &ctx->use_edges[use_list->refs]; n > 0; p++, n--) {
+ ir_ref next = *p;
+ ir_insn *insn = &ctx->ir_base[next];
+
+ if ((ir_op_flags[insn->op] & IR_OP_FLAG_CONTROL) && insn->op1 == ref) {
+ return next;
+ }
+ }
+ IR_ASSERT(0);
+ return IR_UNUSED;
+}
+
/*** Modification helpers ***/
#define MAKE_NOP(_insn) do { \
ir_insn *__insn = _insn; \
diff --git a/ext/opcache/jit/ir/ir_sccp.c b/ext/opcache/jit/ir/ir_sccp.c
index c5665873aa989..05577f05b31ff 100644
--- a/ext/opcache/jit/ir/ir_sccp.c
+++ b/ext/opcache/jit/ir/ir_sccp.c
@@ -255,7 +255,7 @@ static bool ir_is_dead_load_ex(ir_ctx *ctx, ir_ref ref, uint32_t flags, ir_insn
{
if ((flags & (IR_OP_FLAG_MEM|IR_OP_FLAG_MEM_MASK)) == (IR_OP_FLAG_MEM|IR_OP_FLAG_MEM_LOAD)) {
return ctx->use_lists[ref].count == 1;
- } else if (insn->op == IR_ALLOCA) {
+ } else if (insn->op == IR_ALLOCA || insn->op == IR_BLOCK_BEGIN) {
return ctx->use_lists[ref].count == 1;
}
return 0;
@@ -644,8 +644,13 @@ static void ir_sccp_remove_unfeasible_merge_inputs(ir_ctx *ctx, ir_insn *_values
next_insn = use_insn;
} else if (use_insn->op != IR_NOP) {
IR_ASSERT(use_insn->op1 == ref);
- use_insn->op1 = prev;
- ir_use_list_add(ctx, prev, use);
+ IR_ASSERT(use_insn->op == IR_VAR);
+ ir_ref region = prev;
+ while (!IR_IS_BB_START(ctx->ir_base[region].op)) {
+ region = ctx->ir_base[region].op1;
+ }
+ use_insn->op1 = region;
+ ir_use_list_add(ctx, region, use);
p = &ctx->use_edges[use_list->refs + k];
}
}
@@ -1240,6 +1245,22 @@ static void ir_merge_blocks(ir_ctx *ctx, ir_ref end, ir_ref begin, ir_bitqueue *
}
}
+static void ir_remove_unused_vars(ir_ctx *ctx, ir_ref start, ir_ref end)
+{
+ ir_use_list *use_list = &ctx->use_lists[start];
+ ir_ref *p, use, n = use_list->count;
+
+ for (p = &ctx->use_edges[use_list->refs]; n > 0; p++, n--) {
+ use = *p;
+ if (use != end) {
+ ir_insn *use_insn = &ctx->ir_base[use];
+ IR_ASSERT(use_insn->op == IR_VAR);
+ IR_ASSERT(ctx->use_lists[use].count == 0);
+ MAKE_NOP(use_insn);
+ }
+ }
+}
+
static bool ir_try_remove_empty_diamond(ir_ctx *ctx, ir_ref ref, ir_insn *insn, ir_bitqueue *worklist)
{
if (insn->inputs_count == 2) {
@@ -1289,8 +1310,12 @@ static bool ir_try_remove_empty_diamond(ir_ctx *ctx, ir_ref ref, ir_insn *insn,
ir_ref next_ref = ctx->use_edges[ctx->use_lists[ref].refs];
ir_insn *next = &ctx->ir_base[next_ref];
- IR_ASSERT(ctx->use_lists[start1_ref].count == 1);
- IR_ASSERT(ctx->use_lists[start2_ref].count == 1);
+ if (ctx->use_lists[start1_ref].count != 1) {
+ ir_remove_unused_vars(ctx, start1_ref, end1_ref);
+ }
+ if (ctx->use_lists[start2_ref].count != 1) {
+ ir_remove_unused_vars(ctx, start2_ref, end2_ref);
+ }
next->op1 = root->op1;
ir_use_list_replace_one(ctx, root->op1, root_ref, next_ref);
@@ -1331,7 +1356,9 @@ static bool ir_try_remove_empty_diamond(ir_ctx *ctx, ir_ref ref, ir_insn *insn,
if (start->op != IR_CASE_VAL && start->op != IR_CASE_DEFAULT) {
return 0;
}
- IR_ASSERT(ctx->use_lists[start_ref].count == 1);
+ if (ctx->use_lists[start_ref].count != 1) {
+ ir_remove_unused_vars(ctx, start_ref, end_ref);
+ }
if (!root_ref) {
root_ref = start->op1;
if (ctx->use_lists[root_ref].count != count) {
@@ -1454,8 +1481,12 @@ static bool ir_optimize_phi(ir_ctx *ctx, ir_ref merge_ref, ir_insn *merge, ir_re
}
next = &ctx->ir_base[next_ref];
- IR_ASSERT(ctx->use_lists[start1_ref].count == 1);
- IR_ASSERT(ctx->use_lists[start2_ref].count == 1);
+ if (ctx->use_lists[start1_ref].count != 1) {
+ ir_remove_unused_vars(ctx, start1_ref, end1_ref);
+ }
+ if (ctx->use_lists[start2_ref].count != 1) {
+ ir_remove_unused_vars(ctx, start2_ref, end2_ref);
+ }
insn->op = (
(is_less ? cond->op1 : cond->op2)
@@ -1540,8 +1571,12 @@ static bool ir_optimize_phi(ir_ctx *ctx, ir_ref merge_ref, ir_insn *merge, ir_re
}
next = &ctx->ir_base[next_ref];
- IR_ASSERT(ctx->use_lists[start1_ref].count == 1);
- IR_ASSERT(ctx->use_lists[start2_ref].count == 1);
+ if (ctx->use_lists[start1_ref].count != 1) {
+ ir_remove_unused_vars(ctx, start1_ref, end1_ref);
+ }
+ if (ctx->use_lists[start2_ref].count != 1) {
+ ir_remove_unused_vars(ctx, start2_ref, end2_ref);
+ }
insn->op = IR_ABS;
insn->inputs_count = 1;
@@ -1605,8 +1640,12 @@ static bool ir_optimize_phi(ir_ctx *ctx, ir_ref merge_ref, ir_insn *merge, ir_re
}
next = &ctx->ir_base[next_ref];
- IR_ASSERT(ctx->use_lists[start1_ref].count == 1);
- IR_ASSERT(ctx->use_lists[start2_ref].count == 1);
+ if (ctx->use_lists[start1_ref].count != 1) {
+ ir_remove_unused_vars(ctx, start1_ref, end1_ref);
+ }
+ if (ctx->use_lists[start2_ref].count != 1) {
+ ir_remove_unused_vars(ctx, start2_ref, end2_ref);
+ }
insn->op = IR_COND;
insn->inputs_count = 3;
@@ -2126,9 +2165,13 @@ static void ir_optimize_merge(ir_ctx *ctx, ir_ref merge_ref, ir_insn *merge, ir_
ir_ref next_ref = ctx->use_edges[use_list->refs + 1];
ir_insn *next = &ctx->ir_base[next_ref];
- IR_ASSERT(next->op != IR_PHI);
- if (phi->op == IR_PHI) {
+ if (next->op == IR_PHI) {
+ SWAP_REFS(phi_ref, next_ref);
+ SWAP_INSNS(phi, next);
+ }
+
+ if (phi->op == IR_PHI && next->op != IR_PHI) {
if (next->op == IR_IF && next->op1 == merge_ref && ctx->use_lists[phi_ref].count == 1) {
if (next->op2 == phi_ref) {
if (ir_try_split_if(ctx, next_ref, next, worklist)) {
@@ -2213,7 +2256,7 @@ int ir_sccp(ir_ctx *ctx)
if (!may_benefit) {
IR_MAKE_BOTTOM(i);
if (insn->op == IR_FP2FP || insn->op == IR_FP2INT || insn->op == IR_TRUNC
- || insn->op == IR_ZEXT || insn->op == IR_SEXT) {
+ || insn->op == IR_ZEXT || insn->op == IR_SEXT || insn->op == IR_EQ || insn->op == IR_NE) {
ir_bitqueue_add(&worklist2, i);
}
} else if (!ir_sccp_fold(ctx, _values, i, insn->opt, insn->op1, insn->op2, insn->op3)) {
@@ -2222,7 +2265,7 @@ int ir_sccp(ir_ctx *ctx)
} else if (_values[i].optx == IR_BOTTOM) {
insn = &ctx->ir_base[i];
if (insn->op == IR_FP2FP || insn->op == IR_FP2INT || insn->op == IR_TRUNC
- || insn->op == IR_ZEXT || insn->op == IR_SEXT) {
+ || insn->op == IR_ZEXT || insn->op == IR_SEXT || insn->op == IR_EQ || insn->op == IR_NE) {
ir_bitqueue_add(&worklist2, i);
}
}
diff --git a/ext/opcache/jit/ir/ir_x86.dasc b/ext/opcache/jit/ir/ir_x86.dasc
index 1fa7001198c94..284e1480d3835 100644
--- a/ext/opcache/jit/ir/ir_x86.dasc
+++ b/ext/opcache/jit/ir/ir_x86.dasc
@@ -1149,8 +1149,10 @@ int ir_get_target_constraints(ir_ctx *ctx, ir_ref ref, ir_target_constraints *co
} else {
flags = IR_DEF_REUSES_OP1_REG | IR_USE_MUST_BE_IN_REG | IR_OP1_SHOULD_BE_IN_REG | IR_OP2_SHOULD_BE_IN_REG;
}
- if (IR_IS_CONST_REF(insn->op2) && insn->op1 != insn->op2) {
- n = ir_add_const_tmp_reg(ctx, insn->op2, 2, n, constraints);
+ if (IR_IS_CONST_REF(insn->op2)) {
+ if (insn->op1 != insn->op2) {
+ n = ir_add_const_tmp_reg(ctx, insn->op2, 2, n, constraints);
+ }
} else if (ir_rule(ctx, insn->op2) == IR_STATIC_ALLOCA) {
constraints->tmp_regs[n] = IR_TMP_REG(2, IR_ADDR, IR_LOAD_SUB_REF, IR_DEF_SUB_REF);
n++;
@@ -1223,9 +1225,11 @@ op2_const:
} else if (ir_rule(ctx, insn->op1) & IR_FUSED) {
flags = IR_USE_MUST_BE_IN_REG | IR_OP2_MUST_BE_IN_REG;
}
- if (IR_IS_CONST_REF(insn->op2) && insn->op1 != insn->op2) {
- flags = IR_USE_MUST_BE_IN_REG | IR_OP1_SHOULD_BE_IN_REG;
- n = ir_add_const_tmp_reg(ctx, insn->op2, 2, n, constraints);
+ if (IR_IS_CONST_REF(insn->op2)) {
+ if (insn->op1 != insn->op2) {
+ flags = IR_USE_MUST_BE_IN_REG | IR_OP1_SHOULD_BE_IN_REG;
+ n = ir_add_const_tmp_reg(ctx, insn->op2, 2, n, constraints);
+ }
} else if (ir_rule(ctx, insn->op2) == IR_STATIC_ALLOCA) {
constraints->tmp_regs[n] = IR_TMP_REG(2, IR_ADDR, IR_LOAD_SUB_REF, IR_DEF_SUB_REF);
n++;
@@ -3360,10 +3364,23 @@ static ir_mem ir_fuse_addr(ir_ctx *ctx, ir_ref root, ir_ref ref)
offset_insn = insn;
break;
case IR_LEA_IB_O:
- base_reg_ref = insn->op1 * sizeof(ir_ref) + 1;
- index_reg_ref = insn->op1 * sizeof(ir_ref) + 2;
+ op1_insn = &ctx->ir_base[insn->op1];
offset_insn = insn;
scale = 1;
+ if (ir_rule(ctx, op1_insn->op2) == IR_STATIC_ALLOCA) {
+ offset = IR_SPILL_POS_TO_OFFSET(ctx->ir_base[op1_insn->op2].op3);
+ base_reg = (ctx->flags & IR_USE_FRAME_POINTER) ? IR_REG_FRAME_POINTER : IR_REG_STACK_POINTER;
+ base_reg_ref = IR_UNUSED;
+ index_reg_ref = insn->op1 * sizeof(ir_ref) + 1;
+ } else if (ir_rule(ctx, op1_insn->op1) == IR_STATIC_ALLOCA) {
+ offset = IR_SPILL_POS_TO_OFFSET(ctx->ir_base[op1_insn->op1].op3);
+ base_reg = (ctx->flags & IR_USE_FRAME_POINTER) ? IR_REG_FRAME_POINTER : IR_REG_STACK_POINTER;
+ base_reg_ref = IR_UNUSED;
+ index_reg_ref = insn->op1 * sizeof(ir_ref) + 2;
+ } else {
+ base_reg_ref = insn->op1 * sizeof(ir_ref) + 1;
+ index_reg_ref = insn->op1 * sizeof(ir_ref) + 2;
+ }
break;
case IR_LEA_OB_SI:
index_reg_ref = insn->op2 * sizeof(ir_ref) + 1;
@@ -7463,6 +7480,10 @@ static void ir_emit_vload(ir_ctx *ctx, ir_ref def, ir_insn *insn)
ir_reg fp;
ir_mem mem;
+ if (ctx->use_lists[def].count == 1) {
+ /* dead load */
+ return;
+ }
IR_ASSERT(var_insn->op == IR_VAR);
fp = (ctx->flags & IR_USE_FRAME_POINTER) ? IR_REG_FRAME_POINTER : IR_REG_STACK_POINTER;
mem = IR_MEM_BO(fp, IR_SPILL_POS_TO_OFFSET(var_insn->op3));
@@ -7909,6 +7930,10 @@ static void ir_emit_block_begin(ir_ctx *ctx, ir_ref def, ir_insn *insn)
dasm_State **Dst = &data->dasm_state;
ir_reg def_reg = IR_REG_NUM(ctx->regs[def][0]);
+ if (ctx->use_lists[def].count == 1) {
+ /* dead load */
+ return;
+ }
| mov Ra(def_reg), Ra(IR_REG_RSP)
if (IR_REG_SPILLED(ctx->regs[def][0])) {
diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c
index 54ffd79a35871..558faae22d0a0 100644
--- a/ext/opcache/jit/zend_jit.c
+++ b/ext/opcache/jit/zend_jit.c
@@ -69,6 +69,8 @@ zend_jit_globals jit_globals;
#define JIT_STUB_PREFIX "JIT$$"
#define TRACE_PREFIX "TRACE-"
+bool zend_jit_startup_ok = false;
+
zend_ulong zend_jit_profile_counter = 0;
int zend_jit_profile_counter_rid = -1;
@@ -725,6 +727,38 @@ ZEND_EXT_API void zend_jit_status(zval *ret)
add_assoc_zval(ret, "jit", &stats);
}
+static bool zend_jit_inc_call_level(uint8_t opcode)
+{
+ switch (opcode) {
+ case ZEND_INIT_FCALL:
+ case ZEND_INIT_FCALL_BY_NAME:
+ case ZEND_INIT_NS_FCALL_BY_NAME:
+ case ZEND_INIT_METHOD_CALL:
+ case ZEND_INIT_DYNAMIC_CALL:
+ case ZEND_INIT_STATIC_METHOD_CALL:
+ case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL:
+ case ZEND_INIT_USER_CALL:
+ case ZEND_NEW:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool zend_jit_dec_call_level(uint8_t opcode)
+{
+ switch (opcode) {
+ case ZEND_DO_FCALL:
+ case ZEND_DO_ICALL:
+ case ZEND_DO_UCALL:
+ case ZEND_DO_FCALL_BY_NAME:
+ case ZEND_CALLABLE_CONVERT:
+ return true;
+ default:
+ return false;
+ }
+}
+
static zend_string *zend_jit_func_name(const zend_op_array *op_array)
{
smart_str buf = {0};
@@ -1461,17 +1495,8 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
for (i = ssa->cfg.blocks[b].start; i <= end; i++) {
zend_ssa_op *ssa_op = ssa->ops ? &ssa->ops[i] : NULL;
opline = op_array->opcodes + i;
- switch (opline->opcode) {
- case ZEND_INIT_FCALL:
- case ZEND_INIT_FCALL_BY_NAME:
- case ZEND_INIT_NS_FCALL_BY_NAME:
- case ZEND_INIT_METHOD_CALL:
- case ZEND_INIT_DYNAMIC_CALL:
- case ZEND_INIT_STATIC_METHOD_CALL:
- case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL:
- case ZEND_INIT_USER_CALL:
- case ZEND_NEW:
- call_level++;
+ if (zend_jit_inc_call_level(opline->opcode)) {
+ call_level++;
}
if (JIT_G(opt_level) >= ZEND_JIT_LEVEL_INLINE) {
@@ -2574,7 +2599,19 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
}
/* THROW and EXIT may be used in the middle of BB */
/* don't generate code for the rest of BB */
- i = end;
+
+ /* Skip current opline for call_level computation
+ * Don't include last opline because end of loop already checks call level of last opline */
+ i++;
+ for (; i < end; i++) {
+ opline = op_array->opcodes + i;
+ if (zend_jit_inc_call_level(opline->opcode)) {
+ call_level++;
+ } else if (zend_jit_dec_call_level(opline->opcode)) {
+ call_level--;
+ }
+ }
+ opline = op_array->opcodes + i;
break;
/* stackless execution */
case ZEND_INCLUDE_OR_EVAL:
@@ -2686,13 +2723,8 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
}
}
done:
- switch (opline->opcode) {
- case ZEND_DO_FCALL:
- case ZEND_DO_ICALL:
- case ZEND_DO_UCALL:
- case ZEND_DO_FCALL_BY_NAME:
- case ZEND_CALLABLE_CONVERT:
- call_level--;
+ if (zend_jit_dec_call_level(opline->opcode)) {
+ call_level--;
}
}
zend_jit_bb_end(&ctx, b);
@@ -3634,6 +3666,13 @@ static void zend_jit_reset_counters(void)
void zend_jit_activate(void)
{
+#ifdef ZTS
+ if (!zend_jit_startup_ok) {
+ JIT_G(enabled) = 0;
+ JIT_G(on) = 0;
+ return;
+ }
+#endif
zend_jit_profile_counter = 0;
if (JIT_G(on)) {
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_COUNTERS) {
diff --git a/ext/opcache/jit/zend_jit.h b/ext/opcache/jit/zend_jit.h
index 0ce6c1a4409a2..9178d340a0ede 100644
--- a/ext/opcache/jit/zend_jit.h
+++ b/ext/opcache/jit/zend_jit.h
@@ -100,6 +100,8 @@ typedef struct _zend_jit_trace_rec zend_jit_trace_rec;
typedef struct _zend_jit_trace_stack_frame zend_jit_trace_stack_frame;
typedef struct _sym_node zend_sym_node;
+extern bool zend_jit_startup_ok;
+
typedef struct _zend_jit_globals {
bool enabled;
bool on;
diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c
index 6245852c7b840..beab53894a1b8 100644
--- a/ext/opcache/jit/zend_jit_ir.c
+++ b/ext/opcache/jit/zend_jit_ir.c
@@ -1306,6 +1306,13 @@ static bool zend_jit_spilling_may_cause_conflict(zend_jit_ctx *jit, int var, ir_
&& (jit->ssa->cfg.blocks[jit->ssa->vars[jit->ssa->ops[jit->ssa->vars[var].definition].op1_use].definition_phi->block].flags & ZEND_BB_LOOP_HEADER)) {
/* Avoid moving spill store out of loop */
return 1;
+ } else if (jit->ssa->vars[var].definition >= 0
+ && jit->ssa->ops[jit->ssa->vars[var].definition].op1_def == var
+ && jit->ssa->ops[jit->ssa->vars[var].definition].op1_use >= 0
+ && jit->ssa->ops[jit->ssa->vars[var].definition].op2_use >= 0
+ && jit->ra[jit->ssa->ops[jit->ssa->vars[var].definition].op2_use].ref == val) {
+ /* Avoid spill conflict between of ASSIGN.op1_def and ASSIGN.op1_use */
+ return 1;
}
return 0;
}
@@ -3233,6 +3240,20 @@ static void zend_jit_setup(void)
/* Index is offset by 1 on FreeBSD (https://github.com/freebsd/freebsd-src/blob/22ca6db50f4e6bd75a141f57cf953d8de6531a06/lib/libc/gen/tls.c#L88) */
tsrm_tls_index = (tlsdesc->index + 1) * 8;
}
+# elif defined(__MUSL__)
+ if (tsrm_ls_cache_tcb_offset == 0) {
+ size_t **where;
+
+ __asm__(
+ "adrp %0, :tlsdesc:_tsrm_ls_cache\n"
+ "add %0, %0, :tlsdesc_lo12:_tsrm_ls_cache\n"
+ : "=r" (where));
+ /* See https://github.com/ARM-software/abi-aa/blob/2a70c42d62e9c3eb5887fa50b71257f20daca6f9/aaelf64/aaelf64.rst */
+ size_t *tlsdesc = where[1];
+
+ tsrm_tls_offset = tlsdesc[1];
+ tsrm_tls_index = tlsdesc[0] * 8;
+ }
# else
ZEND_ASSERT(tsrm_ls_cache_tcb_offset != 0);
# endif
@@ -7183,9 +7204,9 @@ static int zend_jit_cmp(zend_jit_ctx *jit,
while (n) {
n--;
- ir_IF_TRUE(end_inputs->refs[n]);
+ jit_IF_TRUE_FALSE_ex(jit, end_inputs->refs[n], label);
ir_END_list(true_inputs);
- ir_IF_FALSE(end_inputs->refs[n]);
+ jit_IF_TRUE_FALSE_ex(jit, end_inputs->refs[n], label2);
ir_END_list(false_inputs);
}
ir_MERGE_list(true_inputs);
@@ -11509,6 +11530,32 @@ static int zend_jit_rope(zend_jit_ctx *jit, const zend_op *opline, uint32_t op2_
return 1;
}
+static int zend_jit_zval_copy_deref_reg(zend_jit_ctx *jit, zend_jit_addr res_addr, uint32_t res_info, zend_jit_addr val_addr, ir_ref type, ir_ref *values)
+{
+ ir_ref if_type, val;
+
+ if (res_info == MAY_BE_LONG) {
+ if_type = ir_IF(ir_EQ(type, ir_CONST_U32(IS_LONG)));
+ ir_IF_TRUE(if_type);
+ val = jit_ZVAL_ADDR(jit, val_addr);
+ ir_END_PHI_list(*values, val);
+ ir_IF_FALSE(if_type);
+ val = ir_ADD_OFFSET(jit_Z_PTR(jit, val_addr), offsetof(zend_reference, val));
+ ir_END_PHI_list(*values, val);
+ } else if (res_info == MAY_BE_DOUBLE) {
+ if_type = ir_IF(ir_EQ(type, ir_CONST_U32(IS_DOUBLE)));
+ ir_IF_TRUE(if_type);
+ val = jit_ZVAL_ADDR(jit, val_addr);
+ ir_END_PHI_list(*values, val);
+ ir_IF_FALSE(if_type);
+ val = ir_ADD_OFFSET(jit_Z_PTR(jit, val_addr), offsetof(zend_reference, val));
+ ir_END_PHI_list(*values, val);
+ } else {
+ ZEND_UNREACHABLE();
+ }
+ return 1;
+}
+
static int zend_jit_zval_copy_deref(zend_jit_ctx *jit, zend_jit_addr res_addr, zend_jit_addr val_addr, ir_ref type)
{
ir_ref if_refcounted, if_reference, if_refcounted2, ptr, val2, ptr2, type2;
@@ -14239,9 +14286,16 @@ static int zend_jit_fetch_obj(zend_jit_ctx *jit,
}
ir_END_list(end_inputs);
} else {
- if (((res_info & MAY_BE_GUARD) && JIT_G(current_frame) && prop_info)
- || Z_MODE(res_addr) == IS_REG) {
+ if ((res_info & MAY_BE_GUARD) && JIT_G(current_frame) && prop_info) {
ir_END_PHI_list(end_values, jit_ZVAL_ADDR(jit, prop_addr));
+ } else if ((res_info & MAY_BE_GUARD) && Z_MODE(res_addr) == IS_REG) {
+ ir_END_PHI_list(end_values, jit_ZVAL_ADDR(jit, prop_addr));
+ } else if (Z_MODE(res_addr) == IS_REG) {
+ prop_type_ref = jit_Z_TYPE_INFO(jit, prop_addr);
+
+ if (!zend_jit_zval_copy_deref_reg(jit, res_addr, res_info & ~MAY_BE_GUARD, prop_addr, prop_type_ref, &end_values)) {
+ return 0;
+ }
} else {
prop_type_ref = jit_Z_TYPE_INFO(jit, prop_addr);
@@ -17327,8 +17381,15 @@ static void jit_frameless_icall2(zend_jit_ctx *jit, const zend_op *opline, uint3
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, NULL);
/* Set OP1 to UNDEF in case FREE_OP2() throws. */
- if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) != 0 && (opline->op2_type & (IS_VAR|IS_TMP_VAR)) != 0) {
+ if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) != 0
+ && (opline->op2_type & (IS_VAR|IS_TMP_VAR)) != 0
+ && (op2_info & MAY_BE_RC1)
+ && (op2_info & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY))) {
jit_set_Z_TYPE_INFO(jit, op1_addr, IS_UNDEF);
+ if (JIT_G(current_frame)) {
+ SET_STACK_TYPE(JIT_G(current_frame)->stack,
+ EX_VAR_TO_NUM(opline->op1.var), IS_UNKNOWN, 1);
+ }
}
jit_FREE_OP(jit, opline->op2_type, opline->op2, op2_info, NULL);
zend_jit_check_exception(jit);
@@ -17401,18 +17462,34 @@ static void jit_frameless_icall3(zend_jit_ctx *jit, const zend_op *opline, uint3
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, NULL);
/* Set OP1 to UNDEF in case FREE_OP2() throws. */
+ bool op1_undef = false;
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR))
- && ((opline->op2_type & (IS_VAR|IS_TMP_VAR))
- || (op_data_type & (IS_VAR|IS_TMP_VAR)))) {
+ && (((opline->op2_type & (IS_VAR|IS_TMP_VAR))
+ && (op2_info & MAY_BE_RC1)
+ && (op2_info & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY)))
+ || ((op_data_type & (IS_VAR|IS_TMP_VAR))
+ && (op1_data_info & MAY_BE_RC1)
+ && (op1_data_info & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY))))) {
+ op1_undef = true;
jit_set_Z_TYPE_INFO(jit, op1_addr, IS_UNDEF);
+ if (JIT_G(current_frame)) {
+ SET_STACK_TYPE(JIT_G(current_frame)->stack,
+ EX_VAR_TO_NUM(opline->op1.var), IS_UNKNOWN, 1);
+ }
}
jit_FREE_OP(jit, opline->op2_type, opline->op2, op2_info, NULL);
- /* If OP1 is a TMP|VAR, we don't need to set OP2 to UNDEF on free because
+ /* If OP1 is set to UNDEF, we don't need to set OP2 to UNDEF on free because
* zend_fetch_debug_backtrace aborts when it encounters the first UNDEF TMP|VAR. */
- if (!(opline->op1_type & (IS_VAR|IS_TMP_VAR))
+ if (!op1_undef
&& (opline->op2_type & (IS_VAR|IS_TMP_VAR)) != 0
- && (op_data_type & (IS_VAR|IS_TMP_VAR)) != 0) {
+ && (op_data_type & (IS_VAR|IS_TMP_VAR)) != 0
+ && (op1_data_info & MAY_BE_RC1)
+ && (op1_data_info & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY))) {
jit_set_Z_TYPE_INFO(jit, op2_addr, IS_UNDEF);
+ if (JIT_G(current_frame)) {
+ SET_STACK_TYPE(JIT_G(current_frame)->stack,
+ EX_VAR_TO_NUM(opline->op2.var), IS_UNKNOWN, 1);
+ }
}
jit_FREE_OP(jit, (opline+1)->op1_type, (opline+1)->op1, op1_data_info, NULL);
zend_jit_check_exception(jit);
diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c
index a8ea08cd1733c..c7c470330f060 100644
--- a/ext/opcache/jit/zend_jit_trace.c
+++ b/ext/opcache/jit/zend_jit_trace.c
@@ -1164,28 +1164,13 @@ static const zend_op *zend_jit_trace_find_init_fcall_op(zend_jit_trace_rec *p, c
if (opline) {
while (opline > op_array->opcodes) {
opline--;
- switch (opline->opcode) {
- case ZEND_INIT_FCALL:
- case ZEND_INIT_FCALL_BY_NAME:
- case ZEND_INIT_NS_FCALL_BY_NAME:
- case ZEND_INIT_METHOD_CALL:
- case ZEND_INIT_DYNAMIC_CALL:
- case ZEND_INIT_STATIC_METHOD_CALL:
- case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL:
- case ZEND_INIT_USER_CALL:
- case ZEND_NEW:
- if (call_level == 0) {
- return opline;
- }
- call_level--;
- break;
- case ZEND_DO_FCALL:
- case ZEND_DO_ICALL:
- case ZEND_DO_UCALL:
- case ZEND_DO_FCALL_BY_NAME:
- case ZEND_CALLABLE_CONVERT:
- call_level++;
- break;
+ if (zend_jit_inc_call_level(opline->opcode)) {
+ if (call_level == 0) {
+ return opline;
+ }
+ call_level--;
+ } else if (zend_jit_dec_call_level(opline->opcode)) {
+ call_level++;
}
}
}
@@ -4394,17 +4379,8 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par
frame_flags = 0;
- switch (opline->opcode) {
- case ZEND_INIT_FCALL:
- case ZEND_INIT_FCALL_BY_NAME:
- case ZEND_INIT_NS_FCALL_BY_NAME:
- case ZEND_INIT_METHOD_CALL:
- case ZEND_INIT_DYNAMIC_CALL:
- case ZEND_INIT_STATIC_METHOD_CALL:
- case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL:
- case ZEND_INIT_USER_CALL:
- case ZEND_NEW:
- frame->call_level++;
+ if (zend_jit_inc_call_level(opline->opcode)) {
+ frame->call_level++;
}
if (JIT_G(opt_level) >= ZEND_JIT_LEVEL_INLINE) {
@@ -6426,13 +6402,8 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par
done:
polymorphic_side_trace = 0;
- switch (opline->opcode) {
- case ZEND_DO_FCALL:
- case ZEND_DO_ICALL:
- case ZEND_DO_UCALL:
- case ZEND_DO_FCALL_BY_NAME:
- case ZEND_CALLABLE_CONVERT:
- frame->call_level--;
+ if (zend_jit_dec_call_level(opline->opcode)) {
+ frame->call_level--;
}
if (ra) {
diff --git a/ext/opcache/tests/jit/gh16879.phpt b/ext/opcache/tests/jit/gh16879.phpt
new file mode 100644
index 0000000000000..7a17fd34135b2
--- /dev/null
+++ b/ext/opcache/tests/jit/gh16879.phpt
@@ -0,0 +1,22 @@
+--TEST--
+GH-16879 (JIT dead code skipping does not update call_level)
+--EXTENSIONS--
+opcache
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_update_protection=0
+opcache.jit_buffer_size=32M
+opcache.jit=1235
+opcache.jit_hot_func=1
+--FILE--
+
+--EXPECTF--
+Fatal error: Uncaught UnhandledMatchError: Unhandled match case 0 in %s:%d
+Stack trace:
+#0 {main}
+ thrown in %s on line %d
diff --git a/ext/opcache/tests/jit/gh16984.phpt b/ext/opcache/tests/jit/gh16984.phpt
new file mode 100644
index 0000000000000..8432959c41027
--- /dev/null
+++ b/ext/opcache/tests/jit/gh16984.phpt
@@ -0,0 +1,41 @@
+--TEST--
+GH-16984 (function JIT overflow bug)
+--EXTENSIONS--
+opcache
+--SKIPIF--
+
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_update_protection=0
+opcache.jit_buffer_size=32M
+opcache.jit=function
+--FILE--
+foo($value);
+ if ($val <= PHP_INT_MAX) {
+ $test->integer = $val;
+ }
+}
+
+function main() {
+ $test = new Test;
+ foo($test, 9223372036854775806);
+ foo($test, 9223372036854775807); // Also reproduces without this call, but this imitates the psalm code
+ var_dump($test->integer);
+}
+
+main();
+?>
+--EXPECT--
+int(9223372036854775807)
diff --git a/ext/pdo_dblib/dblib_driver.c b/ext/pdo_dblib/dblib_driver.c
index 17036d2b002fd..0055dcb03b3c5 100644
--- a/ext/pdo_dblib/dblib_driver.c
+++ b/ext/pdo_dblib/dblib_driver.c
@@ -148,7 +148,7 @@ static zend_string* dblib_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquo
bool use_national_character_set = 0;
size_t i;
char *q;
- size_t quotedlen = 0;
+ size_t quotedlen = 0, extralen = 0;
zend_string *quoted_str;
if (H->assume_national_character_set_strings) {
@@ -163,7 +163,7 @@ static zend_string* dblib_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquo
/* Detect quoted length, adding extra char for doubled single quotes */
for (i = 0; i < ZSTR_LEN(unquoted); i++) {
- if (ZSTR_VAL(unquoted)[i] == '\'') ++quotedlen;
+ if (ZSTR_VAL(unquoted)[i] == '\'') ++extralen;
++quotedlen;
}
@@ -171,6 +171,12 @@ static zend_string* dblib_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquo
if (use_national_character_set) {
++quotedlen; /* N prefix */
}
+
+ if (UNEXPECTED(quotedlen > ZSTR_MAX_LEN - extralen)) {
+ return NULL;
+ }
+
+ quotedlen += extralen;
quoted_str = zend_string_alloc(quotedlen, 0);
q = ZSTR_VAL(quoted_str);
if (use_national_character_set) {
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 0000000000000..431c61951ee2a
--- /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--
+
+--INI--
+memory_limit=-1
+--FILE--
+quote(str_repeat("'", 2147483646)));
+
+?>
+--EXPECT--
+bool(false)
diff --git a/ext/pdo_firebird/firebird_driver.c b/ext/pdo_firebird/firebird_driver.c
index 0a54bf90e7bcd..08764ecf45a30 100644
--- a/ext/pdo_firebird/firebird_driver.c
+++ b/ext/pdo_firebird/firebird_driver.c
@@ -790,7 +790,7 @@ static zend_long firebird_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) /*
/* called by the PDO SQL parser to add quotes to values that are copied into SQL */
static zend_string* firebird_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquoted, enum pdo_param_type paramtype)
{
- int qcount = 0;
+ size_t qcount = 0;
char const *co, *l, *r;
char *c;
size_t quotedlen;
@@ -804,6 +804,10 @@ static zend_string* firebird_handle_quoter(pdo_dbh_t *dbh, const zend_string *un
/* count the number of ' characters */
for (co = ZSTR_VAL(unquoted); (co = strchr(co,'\'')); qcount++, co++);
+ if (UNEXPECTED(ZSTR_LEN(unquoted) + 2 > ZSTR_MAX_LEN - qcount)) {
+ return NULL;
+ }
+
quotedlen = ZSTR_LEN(unquoted) + qcount + 2;
quoted_str = zend_string_alloc(quotedlen, 0);
c = ZSTR_VAL(quoted_str);
diff --git a/ext/pgsql/pgsql.stub.php b/ext/pgsql/pgsql.stub.php
index f507de2e062a2..ec7e2614ce0a3 100644
--- a/ext/pgsql/pgsql.stub.php
+++ b/ext/pgsql/pgsql.stub.php
@@ -968,7 +968,7 @@ function pg_put_copy_end(PgSql\Connection $connection, ?string $error = null): i
function pg_socket_poll($socket, int $read, int $write, int $timeout = -1): int {}
#ifdef HAVE_PG_SET_CHUNKED_ROWS_SIZE
- function pg_set_chunked_rows_size(Pgsql\Connection $connection, int $size): bool {}
+ function pg_set_chunked_rows_size(PgSql\Connection $connection, int $size): bool {}
#endif
}
diff --git a/ext/pgsql/pgsql_arginfo.h b/ext/pgsql/pgsql_arginfo.h
index 182dea8d221a8..ab38b6a7a8bfa 100644
--- a/ext/pgsql/pgsql_arginfo.h
+++ b/ext/pgsql/pgsql_arginfo.h
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
- * Stub hash: 0b89a48c27c6682542312391f10a3ab8fb719ef8 */
+ * Stub hash: 14b0bdd019480b850940b2c2b012b5f6d51746b8 */
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_pg_connect, 0, 1, PgSql\\Connection, MAY_BE_FALSE)
ZEND_ARG_TYPE_INFO(0, connection_string, IS_STRING, 0)
@@ -490,7 +490,7 @@ ZEND_END_ARG_INFO()
#if defined(HAVE_PG_SET_CHUNKED_ROWS_SIZE)
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pg_set_chunked_rows_size, 0, 2, _IS_BOOL, 0)
- ZEND_ARG_OBJ_INFO(0, connection, Pgsql\\Connection, 0)
+ ZEND_ARG_OBJ_INFO(0, connection, PgSql\\Connection, 0)
ZEND_ARG_TYPE_INFO(0, size, IS_LONG, 0)
ZEND_END_ARG_INFO()
#endif
diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c
index 8bf9a7ccebe41..da1ef2cdffe73 100644
--- a/ext/reflection/php_reflection.c
+++ b/ext/reflection/php_reflection.c
@@ -6228,6 +6228,11 @@ ZEND_METHOD(ReflectionProperty, setRawValueWithoutLazyInitialization)
RETURN_THROWS();
}
+ while (zend_object_is_lazy_proxy(object)
+ && zend_lazy_object_initialized(object)) {
+ object = zend_lazy_object_get_instance(object);
+ }
+
zval *var_ptr = OBJ_PROP(object, ref->prop->offset);
bool prop_was_lazy = Z_PROP_FLAG_P(var_ptr) & IS_PROP_LAZY;
@@ -6271,7 +6276,10 @@ ZEND_METHOD(ReflectionProperty, skipLazyInitialization)
RETURN_THROWS();
}
- bool prop_was_lazy = (Z_PROP_FLAG_P(OBJ_PROP(object, ref->prop->offset)) & IS_PROP_LAZY);
+ while (zend_object_is_lazy_proxy(object)
+ && zend_lazy_object_initialized(object)) {
+ object = zend_lazy_object_get_instance(object);
+ }
zval *src = &object->ce->default_properties_table[OBJ_PROP_TO_NUM(ref->prop->offset)];
zval *dst = OBJ_PROP(object, ref->prop->offset);
@@ -6286,7 +6294,7 @@ ZEND_METHOD(ReflectionProperty, skipLazyInitialization)
ZVAL_COPY_PROP(dst, src);
/* Object becomes non-lazy if this was the last lazy prop */
- if (prop_was_lazy && zend_object_is_lazy(object)
+ if (zend_object_is_lazy(object)
&& !zend_lazy_object_initialized(object)) {
if (zend_lazy_object_decr_lazy_props(object)) {
zend_lazy_object_realize(object);
diff --git a/ext/snmp/snmp.c b/ext/snmp/snmp.c
index 1425cf12752a7..ec62dd91c72fb 100644
--- a/ext/snmp/snmp.c
+++ b/ext/snmp/snmp.c
@@ -629,6 +629,31 @@ static void php_snmp_internal(INTERNAL_FUNCTION_PARAMETERS, int st,
}
/* }}} */
+static void php_snmp_zend_string_release_from_char_pointer(char *ptr) {
+ if (ptr) {
+ zend_string *pptr = (zend_string *)(ptr - XtOffsetOf(zend_string, val));
+ zend_string_release(pptr);
+ }
+}
+
+static void php_free_objid_query(struct objid_query *objid_query, HashTable* oid_ht, const HashTable *value_ht, int st) {
+ if (oid_ht) {
+ uint32_t count = zend_hash_num_elements(oid_ht);
+
+ for (uint32_t i = 0; i < count; i ++) {
+ snmpobjarg *arg = &objid_query->vars[i];
+ if (!arg->oid) {
+ break;
+ }
+ if (value_ht) {
+ php_snmp_zend_string_release_from_char_pointer(arg->value);
+ }
+ php_snmp_zend_string_release_from_char_pointer(arg->oid);
+ }
+ }
+ efree(objid_query->vars);
+}
+
/* {{{ php_snmp_parse_oid
*
* OID parser (and type, value for SNMP_SET command)
@@ -677,10 +702,15 @@ static bool php_snmp_parse_oid(
return false;
}
objid_query->vars = (snmpobjarg *)safe_emalloc(sizeof(snmpobjarg), zend_hash_num_elements(oid_ht), 0);
+ memset(objid_query->vars, 0, sizeof(snmpobjarg) * zend_hash_num_elements(oid_ht));
objid_query->array_output = (st & SNMP_CMD_SET) == 0;
ZEND_HASH_FOREACH_VAL(oid_ht, tmp_oid) {
- convert_to_string(tmp_oid);
- objid_query->vars[objid_query->count].oid = Z_STRVAL_P(tmp_oid);
+ zend_string *tmp = zval_try_get_string(tmp_oid);
+ if (!tmp) {
+ php_free_objid_query(objid_query, oid_ht, value_ht, st);
+ return false;
+ }
+ objid_query->vars[objid_query->count].oid = ZSTR_VAL(tmp);
if (st & SNMP_CMD_SET) {
if (type_str) {
pptr = ZSTR_VAL(type_str);
@@ -704,18 +734,24 @@ static bool php_snmp_parse_oid(
}
}
if (idx_type < type_ht->nNumUsed) {
- convert_to_string(tmp_type);
- if (Z_STRLEN_P(tmp_type) != 1) {
+ zend_string *type = zval_try_get_string(tmp_type);
+ if (!type) {
+ php_free_objid_query(objid_query, oid_ht, value_ht, st);
+ return false;
+ }
+ size_t len = ZSTR_LEN(type);
+ char ptype = *ZSTR_VAL(type);
+ zend_string_release(type);
+ if (len != 1) {
zend_value_error("Type must be a single character");
- efree(objid_query->vars);
+ php_free_objid_query(objid_query, oid_ht, value_ht, st);
return false;
}
- pptr = Z_STRVAL_P(tmp_type);
- objid_query->vars[objid_query->count].type = *pptr;
+ objid_query->vars[objid_query->count].type = ptype;
idx_type++;
} else {
- php_error_docref(NULL, E_WARNING, "'%s': no type set", Z_STRVAL_P(tmp_oid));
- efree(objid_query->vars);
+ php_error_docref(NULL, E_WARNING, "'%s': no type set", ZSTR_VAL(tmp));
+ php_free_objid_query(objid_query, oid_ht, value_ht, st);
return false;
}
}
@@ -741,12 +777,16 @@ static bool php_snmp_parse_oid(
}
}
if (idx_value < value_ht->nNumUsed) {
- convert_to_string(tmp_value);
- objid_query->vars[objid_query->count].value = Z_STRVAL_P(tmp_value);
+ zend_string *tmp = zval_try_get_string(tmp_value);
+ if (!tmp) {
+ php_free_objid_query(objid_query, oid_ht, value_ht, st);
+ return false;
+ }
+ objid_query->vars[objid_query->count].value = ZSTR_VAL(tmp);
idx_value++;
} else {
- php_error_docref(NULL, E_WARNING, "'%s': no value set", Z_STRVAL_P(tmp_oid));
- efree(objid_query->vars);
+ php_error_docref(NULL, E_WARNING, "'%s': no value set", ZSTR_VAL(tmp));
+ php_free_objid_query(objid_query, oid_ht, value_ht, st);
return false;
}
}
@@ -759,14 +799,14 @@ static bool php_snmp_parse_oid(
if (st & SNMP_CMD_WALK) {
if (objid_query->count > 1) {
php_snmp_error(object, PHP_SNMP_ERRNO_OID_PARSING_ERROR, "Multi OID walks are not supported!");
- efree(objid_query->vars);
+ php_free_objid_query(objid_query, oid_ht, value_ht, st);
return false;
}
objid_query->vars[0].name_length = MAX_NAME_LEN;
if (strlen(objid_query->vars[0].oid)) { /* on a walk, an empty string means top of tree - no error */
if (!snmp_parse_oid(objid_query->vars[0].oid, objid_query->vars[0].name, &(objid_query->vars[0].name_length))) {
php_snmp_error(object, PHP_SNMP_ERRNO_OID_PARSING_ERROR, "Invalid object identifier: %s", objid_query->vars[0].oid);
- efree(objid_query->vars);
+ php_free_objid_query(objid_query, oid_ht, value_ht, st);
return false;
}
} else {
@@ -778,7 +818,7 @@ static bool php_snmp_parse_oid(
objid_query->vars[objid_query->offset].name_length = MAX_OID_LEN;
if (!snmp_parse_oid(objid_query->vars[objid_query->offset].oid, objid_query->vars[objid_query->offset].name, &(objid_query->vars[objid_query->offset].name_length))) {
php_snmp_error(object, PHP_SNMP_ERRNO_OID_PARSING_ERROR, "Invalid object identifier: %s", objid_query->vars[objid_query->offset].oid);
- efree(objid_query->vars);
+ php_free_objid_query(objid_query, oid_ht, value_ht, st);
return false;
}
}
@@ -1250,12 +1290,12 @@ static void php_snmp(INTERNAL_FUNCTION_PARAMETERS, int st, int version)
if (session_less_mode) {
if (!netsnmp_session_init(&session, version, a1, a2, timeout, retries)) {
- efree(objid_query.vars);
+ php_free_objid_query(&objid_query, oid_ht, value_ht, st);
netsnmp_session_free(&session);
RETURN_FALSE;
}
if (version == SNMP_VERSION_3 && !netsnmp_session_set_security(session, a3, a4, a5, a6, a7, NULL, NULL)) {
- efree(objid_query.vars);
+ php_free_objid_query(&objid_query, oid_ht, value_ht, st);
netsnmp_session_free(&session);
/* Warning message sent already, just bail out */
RETURN_FALSE;
@@ -1266,7 +1306,7 @@ static void php_snmp(INTERNAL_FUNCTION_PARAMETERS, int st, int version)
session = snmp_object->session;
if (!session) {
zend_throw_error(NULL, "Invalid or uninitialized SNMP object");
- efree(objid_query.vars);
+ php_free_objid_query(&objid_query, oid_ht, value_ht, st);
RETURN_THROWS();
}
@@ -1292,7 +1332,7 @@ static void php_snmp(INTERNAL_FUNCTION_PARAMETERS, int st, int version)
php_snmp_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU, st, session, &objid_query);
- efree(objid_query.vars);
+ php_free_objid_query(&objid_query, oid_ht, value_ht, st);
if (session_less_mode) {
netsnmp_session_free(&session);
diff --git a/ext/snmp/tests/gh16959.phpt b/ext/snmp/tests/gh16959.phpt
new file mode 100644
index 0000000000000..ce647b15b9dac
--- /dev/null
+++ b/ext/snmp/tests/gh16959.phpt
@@ -0,0 +1,69 @@
+--TEST--
+snmpget() modifies object_id array source
+--EXTENSIONS--
+snmp
+--SKIPIF--
+
+--FILE--
+ 077, -066 => -066, -0345 => -0345, 0 => 0
+);
+var_dump($bad_object_ids);
+var_dump(snmpget($hostname, "", $bad_object_ids) === false);
+// The array should remain unmodified
+var_dump($bad_object_ids);
+try {
+ snmpget($hostname, "", [0 => new stdClass()]);
+} catch (Throwable $e) {
+ echo $e->getMessage() . PHP_EOL;
+}
+
+try {
+ snmp2_set($hostname, $communityWrite, $bad_object_ids, array(new stdClass()), array(null));
+} catch (Throwable $e) {
+ echo $e->getMessage() . PHP_EOL;
+}
+try {
+ snmp2_set($hostname, $communityWrite, $bad_object_ids, array("toolongtype"), array(null));
+} catch (Throwable $e) {
+ echo $e->getMessage() . PHP_EOL;
+}
+try {
+ snmp2_set($hostname, $communityWrite, $bad_object_ids, array(str_repeat("onetoomuch", random_int(1, 1))), array(null));
+} catch (Throwable $e) {
+ echo $e->getMessage();
+}
+?>
+--EXPECTF--
+array(4) {
+ [63]=>
+ int(63)
+ [-54]=>
+ int(-54)
+ [-229]=>
+ int(-229)
+ [0]=>
+ int(0)
+}
+
+Warning: snmpget(): Invalid object identifier: -54 in %s on line %d
+bool(true)
+array(4) {
+ [63]=>
+ int(63)
+ [-54]=>
+ int(-54)
+ [-229]=>
+ int(-229)
+ [0]=>
+ int(0)
+}
+Object of class stdClass could not be converted to string
+Object of class stdClass could not be converted to string
+Type must be a single character
+Type must be a single character
diff --git a/ext/standard/array.c b/ext/standard/array.c
index 73a5f1ee4a328..d95ea012ad6bc 100644
--- a/ext/standard/array.c
+++ b/ext/standard/array.c
@@ -1024,11 +1024,50 @@ static inline HashTable *get_ht_for_iap(zval *zv, bool separate) {
return zobj->handlers->get_properties(zobj);
}
+static zval *php_array_iter_seek_current(HashTable *array, bool forward_direction)
+{
+ zval *entry;
+
+ while (true) {
+ if ((entry = zend_hash_get_current_data(array)) == NULL) {
+ return NULL;
+ }
+
+ ZVAL_DEINDIRECT(entry);
+
+ /* Possible with an uninitialized typed property */
+ if (UNEXPECTED(Z_TYPE_P(entry) == IS_UNDEF)) {
+ zend_result result;
+ if (forward_direction) {
+ result = zend_hash_move_forward(array);
+ } else {
+ result = zend_hash_move_backwards(array);
+ }
+ if (result != SUCCESS) {
+ return NULL;
+ }
+ } else {
+ break;
+ }
+ }
+
+ return entry;
+}
+
+static void php_array_iter_return_current(zval *return_value, HashTable *array, bool forward_direction)
+{
+ zval *entry = php_array_iter_seek_current(array, forward_direction);
+ if (EXPECTED(entry)) {
+ RETURN_COPY_DEREF(entry);
+ } else {
+ RETURN_FALSE;
+ }
+}
+
/* {{{ Advances array argument's internal pointer to the last element and return it */
PHP_FUNCTION(end)
{
zval *array_zv;
- zval *entry;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_OR_OBJECT_EX(array_zv, 0, 1)
@@ -1042,15 +1081,7 @@ PHP_FUNCTION(end)
zend_hash_internal_pointer_end(array);
if (USED_RET()) {
- if ((entry = zend_hash_get_current_data(array)) == NULL) {
- RETURN_FALSE;
- }
-
- if (Z_TYPE_P(entry) == IS_INDIRECT) {
- entry = Z_INDIRECT_P(entry);
- }
-
- RETURN_COPY_DEREF(entry);
+ php_array_iter_return_current(return_value, array, false);
}
}
/* }}} */
@@ -1059,7 +1090,6 @@ PHP_FUNCTION(end)
PHP_FUNCTION(prev)
{
zval *array_zv;
- zval *entry;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_OR_OBJECT_EX(array_zv, 0, 1)
@@ -1073,15 +1103,7 @@ PHP_FUNCTION(prev)
zend_hash_move_backwards(array);
if (USED_RET()) {
- if ((entry = zend_hash_get_current_data(array)) == NULL) {
- RETURN_FALSE;
- }
-
- if (Z_TYPE_P(entry) == IS_INDIRECT) {
- entry = Z_INDIRECT_P(entry);
- }
-
- RETURN_COPY_DEREF(entry);
+ php_array_iter_return_current(return_value, array, false);
}
}
/* }}} */
@@ -1090,7 +1112,6 @@ PHP_FUNCTION(prev)
PHP_FUNCTION(next)
{
zval *array_zv;
- zval *entry;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_OR_OBJECT_EX(array_zv, 0, 1)
@@ -1104,15 +1125,7 @@ PHP_FUNCTION(next)
zend_hash_move_forward(array);
if (USED_RET()) {
- if ((entry = zend_hash_get_current_data(array)) == NULL) {
- RETURN_FALSE;
- }
-
- if (Z_TYPE_P(entry) == IS_INDIRECT) {
- entry = Z_INDIRECT_P(entry);
- }
-
- RETURN_COPY_DEREF(entry);
+ php_array_iter_return_current(return_value, array, true);
}
}
/* }}} */
@@ -1121,7 +1134,6 @@ PHP_FUNCTION(next)
PHP_FUNCTION(reset)
{
zval *array_zv;
- zval *entry;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_OR_OBJECT_EX(array_zv, 0, 1)
@@ -1135,15 +1147,7 @@ PHP_FUNCTION(reset)
zend_hash_internal_pointer_reset(array);
if (USED_RET()) {
- if ((entry = zend_hash_get_current_data(array)) == NULL) {
- RETURN_FALSE;
- }
-
- if (Z_TYPE_P(entry) == IS_INDIRECT) {
- entry = Z_INDIRECT_P(entry);
- }
-
- RETURN_COPY_DEREF(entry);
+ php_array_iter_return_current(return_value, array, true);
}
}
/* }}} */
@@ -1152,22 +1156,13 @@ PHP_FUNCTION(reset)
PHP_FUNCTION(current)
{
zval *array_zv;
- zval *entry;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_OR_OBJECT(array_zv)
ZEND_PARSE_PARAMETERS_END();
HashTable *array = get_ht_for_iap(array_zv, /* separate */ false);
- if ((entry = zend_hash_get_current_data(array)) == NULL) {
- RETURN_FALSE;
- }
-
- if (Z_TYPE_P(entry) == IS_INDIRECT) {
- entry = Z_INDIRECT_P(entry);
- }
-
- RETURN_COPY_DEREF(entry);
+ php_array_iter_return_current(return_value, array, true);
}
/* }}} */
@@ -1181,7 +1176,10 @@ PHP_FUNCTION(key)
ZEND_PARSE_PARAMETERS_END();
HashTable *array = get_ht_for_iap(array_zv, /* separate */ false);
- zend_hash_get_current_key_zval(array, return_value);
+ zval *entry = php_array_iter_seek_current(array, true);
+ if (EXPECTED(entry)) {
+ zend_hash_get_current_key_zval(array, return_value);
+ }
}
/* }}} */
@@ -3634,7 +3632,8 @@ PHP_FUNCTION(array_shift)
}
idx++;
}
- RETVAL_COPY_DEREF(val);
+ RETVAL_COPY_VALUE(val);
+ ZVAL_UNDEF(val);
/* Delete the first value */
zend_hash_packed_del_val(Z_ARRVAL_P(stack), val);
@@ -3688,7 +3687,8 @@ PHP_FUNCTION(array_shift)
}
idx++;
}
- RETVAL_COPY_DEREF(val);
+ RETVAL_COPY_VALUE(val);
+ ZVAL_UNDEF(val);
/* Delete the first value */
zend_hash_del_bucket(Z_ARRVAL_P(stack), p);
@@ -3712,6 +3712,10 @@ PHP_FUNCTION(array_shift)
}
zend_hash_internal_pointer_reset(Z_ARRVAL_P(stack));
+
+ if (Z_ISREF_P(return_value)) {
+ zend_unwrap_reference(return_value);
+ }
}
/* }}} */
diff --git a/ext/standard/filters.c b/ext/standard/filters.c
index 3bbc999c9212d..3dcbc4bc320ab 100644
--- a/ext/standard/filters.c
+++ b/ext/standard/filters.c
@@ -987,6 +987,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;
@@ -1000,15 +1003,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/formatted_print.c b/ext/standard/formatted_print.c
index ba0f73d9a9c22..8d8c09f443c04 100644
--- a/ext/standard/formatted_print.c
+++ b/ext/standard/formatted_print.c
@@ -246,9 +246,10 @@ php_sprintf_appenddouble(zend_string **buffer, size_t *pos,
}
if (zend_isinf(number)) {
- is_negative = (number<0);
- php_sprintf_appendstring(buffer, pos, "INF", 3, 0, padding,
- alignment, 3, is_negative, 0, always_sign);
+ is_negative = (number<0);
+ char *str = is_negative ? "-INF" : "INF";
+ php_sprintf_appendstring(buffer, pos, str, strlen(str), 0, padding,
+ alignment, strlen(str), is_negative, 0, always_sign);
return;
}
diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
index 3194b65262ba0..4f8e5d520a39e 100644
--- a/ext/standard/http_fopen_wrapper.c
+++ b/ext/standard/http_fopen_wrapper.c
@@ -212,6 +212,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 = (ZSTR_LEN(resource->scheme) > 4) && ZSTR_VAL(resource->scheme)[4] == 's';
/* choose default ports */
if (use_ssl && resource->port == 0)
@@ -230,6 +235,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);
+ zend_string_release(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
@@ -376,12 +388,6 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
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/info.c b/ext/standard/info.c
index 8a706ef62eb57..4ed33f32eabcf 100644
--- a/ext/standard/info.c
+++ b/ext/standard/info.c
@@ -271,7 +271,9 @@ static char* php_get_windows_name()
major = "Windows 10";
}
} else {
- if (osvi.dwBuildNumber >= 20348) {
+ if (osvi.dwBuildNumber >= 26100) {
+ major = "Windows Server 2025";
+ } else if (osvi.dwBuildNumber >= 20348) {
major = "Windows Server 2022";
} else if (osvi.dwBuildNumber >= 19042) {
major = "Windows Server, version 20H2";
diff --git a/ext/standard/tests/array/gh16905.phpt b/ext/standard/tests/array/gh16905.phpt
new file mode 100644
index 0000000000000..89d11575789e4
--- /dev/null
+++ b/ext/standard/tests/array/gh16905.phpt
@@ -0,0 +1,92 @@
+--TEST--
+GH-16905 (Internal iterator functions can't handle UNDEF properties)
+--FILE--
+b = 1;
+$x->c = 2;
+
+var_dump(reset($x));
+var_dump(current($x));
+var_dump(end($x));
+
+var_dump(reset($x));
+var_dump(next($x));
+
+var_dump(end($x));
+var_dump(prev($x));
+
+var_dump(key($x));
+var_dump(current($x));
+
+$x = new TestAllUndef;
+var_dump(key($x));
+var_dump(current($x));
+
+$x->a = 1;
+var_dump(key($x));
+var_dump(current($x));
+reset($x);
+var_dump(key($x));
+var_dump(current($x));
+
+?>
+--EXPECTF--
+Deprecated: reset(): Calling reset() on an object is deprecated in %s on line %d
+int(1)
+
+Deprecated: current(): Calling current() on an object is deprecated in %s on line %d
+int(1)
+
+Deprecated: end(): Calling end() on an object is deprecated in %s on line %d
+int(2)
+
+Deprecated: reset(): Calling reset() on an object is deprecated in %s on line %d
+int(1)
+
+Deprecated: next(): Calling next() on an object is deprecated in %s on line %d
+int(2)
+
+Deprecated: end(): Calling end() on an object is deprecated in %s on line %d
+int(2)
+
+Deprecated: prev(): Calling prev() on an object is deprecated in %s on line %d
+int(1)
+
+Deprecated: key(): Calling key() on an object is deprecated in %s on line %d
+string(1) "b"
+
+Deprecated: current(): Calling current() on an object is deprecated in %s on line %d
+int(1)
+
+Deprecated: key(): Calling key() on an object is deprecated in %s on line %d
+NULL
+
+Deprecated: current(): Calling current() on an object is deprecated in %s on line %d
+bool(false)
+
+Deprecated: key(): Calling key() on an object is deprecated in %s on line %d
+NULL
+
+Deprecated: current(): Calling current() on an object is deprecated in %s on line %d
+bool(false)
+
+Deprecated: reset(): Calling reset() on an object is deprecated in %s on line %d
+
+Deprecated: key(): Calling key() on an object is deprecated in %s on line %d
+string(1) "a"
+
+Deprecated: current(): Calling current() on an object is deprecated in %s on line %d
+int(1)
diff --git a/ext/standard/tests/array/gh16957.phpt b/ext/standard/tests/array/gh16957.phpt
new file mode 100644
index 0000000000000..a716228249e7d
--- /dev/null
+++ b/ext/standard/tests/array/gh16957.phpt
@@ -0,0 +1,41 @@
+--TEST--
+GH-16957 (Assertion failure in array_shift with self-referencing array)
+--FILE--
+ 1, 300 => 'two');
+var_dump($shifted = array_shift($new_array2));
+var_dump($new_array2);
+var_dump($new_array2 === $shifted);
+?>
+--EXPECT--
+array(2) {
+ [0]=>
+ int(1)
+ [1]=>
+ string(3) "two"
+}
+array(2) {
+ [0]=>
+ int(1)
+ [1]=>
+ string(3) "two"
+}
+bool(true)
+array(2) {
+ [0]=>
+ int(1)
+ [1]=>
+ string(3) "two"
+}
+array(2) {
+ [0]=>
+ int(1)
+ [1]=>
+ string(3) "two"
+}
+bool(true)
diff --git a/ext/standard/tests/file/copy_variation5-win32.phpt b/ext/standard/tests/file/copy_variation5-win32.phpt
index d3f75262a1857..af352dbe07322 100644
--- a/ext/standard/tests/file/copy_variation5-win32.phpt
+++ b/ext/standard/tests/file/copy_variation5-win32.phpt
@@ -22,9 +22,9 @@ fclose($file_handle);
$dest_files = array(
/* Checking case sensitiveness */
- "COPY.tmp",
- "COPY.TMP",
- "CopY.TMP"
+ "COPY5.tmp",
+ "COPY5.TMP",
+ "CopY5.TMP"
);
echo "Size of the source file before copy operation => ";
@@ -80,25 +80,25 @@ Size of the source file before copy operation => int(1500)
-- Iteration 1 --
Copy operation => bool(true)
Existence of destination file => bool(true)
-Destination file name => %s/COPY.tmp
+Destination file name => %s/COPY5.tmp
Size of source file => int(1500)
Size of destination file => int(1500)
-- Iteration 2 --
Copy operation => bool(true)
Existence of destination file => bool(true)
-Destination file name => %s/COPY.TMP
+Destination file name => %s/COPY5.TMP
Size of source file => int(1500)
Size of destination file => int(1500)
-- Iteration 3 --
Copy operation => bool(true)
Existence of destination file => bool(true)
-Destination file name => %s/CopY.TMP
+Destination file name => %s/CopY5.TMP
Size of source file => int(1500)
Size of destination file => int(1500)
-Warning: unlink(%s/COPY.TMP): No such file or directory in %s on line %d
+Warning: unlink(%s/COPY5.TMP): No such file or directory in %s on line %d
-Warning: unlink(%s/CopY.TMP): No such file or directory in %s on line %d
+Warning: unlink(%s/CopY5.TMP): No such file or directory in %s on line %d
*** Done ***
diff --git a/ext/standard/tests/file/file_put_contents_variation7.phpt b/ext/standard/tests/file/file_put_contents_variation7.phpt
index 4e3d2e766f4dc..fe9cfe57894ac 100644
--- a/ext/standard/tests/file/file_put_contents_variation7.phpt
+++ b/ext/standard/tests/file/file_put_contents_variation7.phpt
@@ -2,6 +2,11 @@
Test file_put_contents() function : usage variation - various absolute and relative paths
--CREDITS--
Dave Kelsey
+--SKIPIF--
+
--FILE--
+--EXPECT--
+string(8190) "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAX"
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 0000000000000..e7dd194dbbe6f
--- /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--
+ ['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
diff --git a/ext/standard/tests/streams/bug51056.phpt b/ext/standard/tests/streams/bug51056.phpt
index abb87fcb037c8..e011405298c24 100644
--- a/ext/standard/tests/streams/bug51056.phpt
+++ b/ext/standard/tests/streams/bug51056.phpt
@@ -4,7 +4,7 @@ Bug #51056 (fread() on blocking stream will block even if data is available)
foo()->x ??= 42;
+?>
+--EXPECTF--
+Notice: Only variable references should be returned by reference in %s on line %d
+
+Fatal error: Uncaught Error: Attempt to assign property "x" on null in %s:%d
+Stack trace:
+#0 {main}
+ thrown in %s on line %d
diff --git a/main/main.c b/main/main.c
index 0b38f303c58fc..3a9299285f4e3 100644
--- a/main/main.c
+++ b/main/main.c
@@ -2118,8 +2118,9 @@ zend_result php_module_startup(sapi_module_struct *sf, zend_module_entry *additi
_set_invalid_parameter_handler(old_invalid_parameter_handler);
}
- /* Disable the message box for assertions.*/
+ /* Disable the message box for assertions and errors.*/
_CrtSetReportMode(_CRT_ASSERT, 0);
+ _CrtSetReportMode(_CRT_ERROR, 0);
#endif
#ifdef ZTS
diff --git a/main/network.c b/main/network.c
index 7d45cc8b78e90..06b637b878912 100644
--- a/main/network.c
+++ b/main/network.c
@@ -297,6 +297,35 @@ typedef int php_non_blocking_flags_t;
fcntl(sock, F_SETFL, save)
#endif
+#ifdef HAVE_GETTIMEOFDAY
+/* Subtract times */
+static inline void sub_times(struct timeval a, struct timeval b, struct timeval *result)
+{
+ result->tv_usec = a.tv_usec - b.tv_usec;
+ if (result->tv_usec < 0L) {
+ a.tv_sec--;
+ result->tv_usec += 1000000L;
+ }
+ result->tv_sec = a.tv_sec - b.tv_sec;
+ if (result->tv_sec < 0L) {
+ result->tv_sec++;
+ result->tv_usec -= 1000000L;
+ }
+}
+
+static inline void php_network_set_limit_time(struct timeval *limit_time,
+ struct timeval *timeout)
+{
+ gettimeofday(limit_time, NULL);
+ limit_time->tv_sec += timeout->tv_sec;
+ limit_time->tv_usec += timeout->tv_usec;
+ if (limit_time->tv_usec >= 1000000) {
+ limit_time->tv_usec -= 1000000;
+ limit_time->tv_sec++;
+ }
+}
+#endif
+
/* Connect to a socket using an interruptible connect with optional timeout.
* Optionally, the connect can be made asynchronously, which will implicitly
* enable non-blocking mode on the socket.
@@ -349,25 +378,53 @@ PHPAPI int php_network_connect_socket(php_socket_t sockfd,
* expected when a connection is actively refused. This way,
* php_pollfd_for will return a mask with POLLOUT if the connection
* is successful and with POLLPRI otherwise. */
- if ((n = php_pollfd_for(sockfd, POLLOUT|POLLPRI, timeout)) == 0) {
+ int events = POLLOUT|POLLPRI;
#else
- if ((n = php_pollfd_for(sockfd, PHP_POLLREADABLE|POLLOUT, timeout)) == 0) {
+ int events = PHP_POLLREADABLE|POLLOUT;
+#endif
+ struct timeval working_timeout;
+#ifdef HAVE_GETTIMEOFDAY
+ struct timeval limit_time, time_now;
+#endif
+ if (timeout) {
+ memcpy(&working_timeout, timeout, sizeof(working_timeout));
+#ifdef HAVE_GETTIMEOFDAY
+ php_network_set_limit_time(&limit_time, &working_timeout);
#endif
- error = PHP_TIMEOUT_ERROR_VALUE;
}
- if (n > 0) {
- len = sizeof(error);
- /*
- BSD-derived systems set errno correctly
- Solaris returns -1 from getsockopt in case of error
- */
- if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (char*)&error, &len) != 0) {
+ while (true) {
+ n = php_pollfd_for(sockfd, events, timeout ? &working_timeout : NULL);
+ if (n < 0) {
+ if (errno == EINTR) {
+#ifdef HAVE_GETTIMEOFDAY
+ if (timeout) {
+ gettimeofday(&time_now, NULL);
+
+ if (!timercmp(&time_now, &limit_time, <)) {
+ /* time limit expired; no need for another poll */
+ error = PHP_TIMEOUT_ERROR_VALUE;
+ break;
+ } else {
+ /* work out remaining time */
+ sub_times(limit_time, time_now, &working_timeout);
+ }
+ }
+#endif
+ continue;
+ }
ret = -1;
+ } else if (n == 0) {
+ error = PHP_TIMEOUT_ERROR_VALUE;
+ } else {
+ len = sizeof(error);
+ /* BSD-derived systems set errno correctly.
+ * Solaris returns -1 from getsockopt in case of error. */
+ if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (char*)&error, &len) != 0) {
+ ret = -1;
+ }
}
- } else {
- /* whoops: sockfd has disappeared */
- ret = -1;
+ break;
}
ok:
@@ -390,22 +447,6 @@ PHPAPI int php_network_connect_socket(php_socket_t sockfd,
}
/* }}} */
-/* {{{ sub_times */
-static inline void sub_times(struct timeval a, struct timeval b, struct timeval *result)
-{
- result->tv_usec = a.tv_usec - b.tv_usec;
- if (result->tv_usec < 0L) {
- a.tv_sec--;
- result->tv_usec += 1000000L;
- }
- result->tv_sec = a.tv_sec - b.tv_sec;
- if (result->tv_sec < 0L) {
- result->tv_sec++;
- result->tv_usec -= 1000000L;
- }
-}
-/* }}} */
-
/* Bind to a local IP address.
* Returns the bound socket, or -1 on failure.
* */
@@ -765,7 +806,6 @@ PHPAPI php_socket_t php_network_accept_incoming(php_socket_t srvsock,
}
/* }}} */
-
/* Connect to a remote host using an interruptible connect with optional timeout.
* Optionally, the connect can be made asynchronously, which will implicitly
* enable non-blocking mode on the socket.
@@ -797,13 +837,7 @@ php_socket_t php_network_connect_socket_to_host(const char *host, unsigned short
if (timeout) {
memcpy(&working_timeout, timeout, sizeof(working_timeout));
#ifdef HAVE_GETTIMEOFDAY
- gettimeofday(&limit_time, NULL);
- limit_time.tv_sec += working_timeout.tv_sec;
- limit_time.tv_usec += working_timeout.tv_usec;
- if (limit_time.tv_usec >= 1000000) {
- limit_time.tv_usec -= 1000000;
- limit_time.tv_sec++;
- }
+ php_network_set_limit_time(&limit_time, &working_timeout);
#endif
}
diff --git a/main/php_version.h b/main/php_version.h
index 51abf80ba6911..b5568497a586b 100644
--- a/main/php_version.h
+++ b/main/php_version.h
@@ -2,7 +2,7 @@
/* edit configure.ac to change version number */
#define PHP_MAJOR_VERSION 8
#define PHP_MINOR_VERSION 4
-#define PHP_RELEASE_VERSION 1
-#define PHP_EXTRA_VERSION "-dev"
-#define PHP_VERSION "8.4.1-dev"
-#define PHP_VERSION_ID 80401
+#define PHP_RELEASE_VERSION 2
+#define PHP_EXTRA_VERSION ""
+#define PHP_VERSION "8.4.2"
+#define PHP_VERSION_ID 80402
diff --git a/main/rfc1867.c b/main/rfc1867.c
index 8a5cd5a5e36c5..84b8788bbf7c3 100644
--- a/main/rfc1867.c
+++ b/main/rfc1867.c
@@ -319,8 +319,8 @@ static char *next_line(multipart_buffer *self)
}
/* return entire buffer as a partial line */
line[self->bufsize] = 0;
- self->buf_begin = ptr;
self->bytes_in_buffer = 0;
+ /* Let fill_buffer() handle the reset of self->buf_begin */
}
return line;
diff --git a/run-tests.php b/run-tests.php
index 5587c6c0aeb17..8d4c8340e0d41 100755
--- a/run-tests.php
+++ b/run-tests.php
@@ -26,7 +26,7 @@
/* Let there be no top-level code beyond this point:
* Only functions and classes, thanks!
*
- * Minimum required PHP version: 7.4.0
+ * Minimum required PHP version: 8.0.0
*/
function show_usage(): void
diff --git a/sapi/cli/php_cli_server.c b/sapi/cli/php_cli_server.c
index bcf5af320d73b..492576b732284 100644
--- a/sapi/cli/php_cli_server.c
+++ b/sapi/cli/php_cli_server.c
@@ -1946,6 +1946,8 @@ static void php_cli_server_client_populate_request_info(const php_cli_server_cli
request_info->auth_user = request_info->auth_password = request_info->auth_digest = NULL;
if (NULL != (val = zend_hash_str_find(&client->request.headers, "content-type", sizeof("content-type")-1))) {
request_info->content_type = Z_STRVAL_P(val);
+ } else {
+ request_info->content_type = NULL;
}
} /* }}} */
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 0000000000000..2c8aeff12d594
--- /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--
+
+--FILE--
+ [
+ "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 . "/", context: $context);
+
+$options = [
+ "http" => [
+ "method" => "POST",
+ ],
+];
+$context = stream_context_create($options);
+
+echo file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS . "/", context: $context);
+?>
+--EXPECT--
+string(5) "AAAAA"
+string(0) ""
diff --git a/sapi/fpm/fpm/fpm_request.c b/sapi/fpm/fpm/fpm_request.c
index 92c6fa8f2cca9..f07c6979f7064 100644
--- a/sapi/fpm/fpm/fpm_request.c
+++ b/sapi/fpm/fpm/fpm_request.c
@@ -201,7 +201,7 @@ void fpm_request_end(void)
fpm_scoreboard_proc_release(proc);
/* memory_peak */
- fpm_scoreboard_update_commit(0, 0, 0, 0, 0, 0, 0, proc->memory, FPM_SCOREBOARD_ACTION_SET, NULL);
+ fpm_scoreboard_update_commit(-1, -1, -1, -1, -1, -1, -1, proc->memory, FPM_SCOREBOARD_ACTION_SET, NULL);
}
void fpm_request_finished(void)
diff --git a/sapi/fpm/tests/gh16932-scoreboard-reset.phpt b/sapi/fpm/tests/gh16932-scoreboard-reset.phpt
new file mode 100644
index 0000000000000..18092a53f7aff
--- /dev/null
+++ b/sapi/fpm/tests/gh16932-scoreboard-reset.phpt
@@ -0,0 +1,56 @@
+--TEST--
+FPM: GH-16932 - scoreboard fields are reset after the request
+--EXTENSIONS--
+pcntl
+--SKIPIF--
+
+--FILE--
+start(extensions: ['pcntl']);
+$tester->expectLogStartNotices();
+$tester->request();
+$tester->request();
+$tester->request();
+$tester->request();
+$tester
+ ->request(uri: '/status', query: 'json')
+ ->expectJsonBodyPatternForStatusField('accepted conn', '5');
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+
diff --git a/sapi/fpm/tests/response.inc b/sapi/fpm/tests/response.inc
index b784df3164f46..aefd2e027c67d 100644
--- a/sapi/fpm/tests/response.inc
+++ b/sapi/fpm/tests/response.inc
@@ -148,6 +148,29 @@ class Response extends BaseResponse
return $this;
}
+ /**
+ * Expect status field with value that matches the supplied pattern.
+ *
+ * @param string $fieldName
+ * @param string $pattern
+ *
+ * @return Response
+ */
+ public function expectJsonBodyPatternForStatusField(string $fieldName, string $pattern): Response
+ {
+ $rawData = $this->getBody('application/json');
+ $data = json_decode($rawData, true);
+ if (preg_match('|' . $pattern . '|', $data[$fieldName]) > 0) {
+ return $this;
+ }
+
+ $this->error(
+ "Field $fieldName did not match pattern $pattern in status data '$rawData'"
+ );
+
+ return $this;
+ }
+
/**
* Expect that one of the processes in json status process list has a field with value that
* matches the supplied pattern.
@@ -167,7 +190,7 @@ class Response extends BaseResponse
);
}
foreach ($data['processes'] as $process) {
- if (preg_match('|' . $pattern . '|', $process[$fieldName]) !== false) {
+ if (preg_match('|' . $pattern . '|', $process[$fieldName]) > 0) {
return $this;
}
}
diff --git a/sapi/phpdbg/phpdbg.c b/sapi/phpdbg/phpdbg.c
index 76b3c7f725951..87d2510a9aadf 100644
--- a/sapi/phpdbg/phpdbg.c
+++ b/sapi/phpdbg/phpdbg.c
@@ -370,6 +370,7 @@ PHP_FUNCTION(phpdbg_clear)
zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_OPLINE]);
zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE]);
zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD]);
+ zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP]);
zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_COND]);
} /* }}} */
diff --git a/sapi/phpdbg/tests/gh15208.phpt b/sapi/phpdbg/tests/gh15208.phpt
new file mode 100644
index 0000000000000..4fa63a61c5262
--- /dev/null
+++ b/sapi/phpdbg/tests/gh15208.phpt
@@ -0,0 +1,15 @@
+--TEST--
+GH-15208 (Segfault with breakpoint map and phpdbg_clear())
+--PHPDBG--
+r
+q
+--FILE--
+
+--EXPECTF--
+[Successful compilation of %s]
+prompt> [Breakpoint #0 added at foo::bar]
+[Script ended normally]
+prompt>
diff --git a/tests/basic/gh16998.phpt b/tests/basic/gh16998.phpt
new file mode 100644
index 0000000000000..8bfcbbda99dd0
--- /dev/null
+++ b/tests/basic/gh16998.phpt
@@ -0,0 +1,49 @@
+--TEST--
+GH-16998 (UBSAN warning in rfc1867)
+--SKIPIF--
+
+--FILE--
+ '1',
+ 'CONTENT_TYPE' => "multipart/form-data; boundary=",
+ 'CONTENT_LENGTH' => strlen($body),
+ 'REQUEST_METHOD' => 'POST',
+ 'SCRIPT_FILENAME' => __DIR__ . '/GHSA-9pqp-7h25-4f32.inc',
+]);
+$spec = [
+ 0 => ['pipe', 'r'],
+ 1 => STDOUT,
+ 2 => STDOUT,
+];
+$pipes = [];
+$handle = proc_open($cmd, $spec, $pipes, getcwd(), $env);
+fwrite($pipes[0], $body);
+proc_close($handle);
+?>
+--EXPECTF--
+X-Powered-By: PHP/%s
+Content-type: text/html; charset=UTF-8
+
+Hello world
+array(0) {
+}
diff --git a/tests/strings/002.phpt b/tests/strings/002.phpt
index 54630836b1632..6284e9bf5d339 100644
--- a/tests/strings/002.phpt
+++ b/tests/strings/002.phpt
@@ -44,6 +44,8 @@ try {
} catch(\ValueError $e) {
print('Error found: '.$e->getMessage()."\n");
}
+printf("printf test 31:%.17g\n", INF);
+printf("printf test 32:%.17g\n", -INF);
vprintf("vprintf test 1:%2\$-2d %1\$2d\n", array(1, 2));
@@ -83,4 +85,6 @@ printf test 27:3 1 2
printf test 28:02 1
printf test 29:2 1
printf test 30:Error found: Argument number specifier must be greater than zero and less than 2147483647
+printf test 31:INF
+printf test 32:-INF
vprintf test 1:2 1