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