diff --git a/.github/actions/apt-x32/action.yml b/.github/actions/apt-x32/action.yml index 879300f992747..0147b15cfcf5f 100644 --- a/.github/actions/apt-x32/action.yml +++ b/.github/actions/apt-x32/action.yml @@ -6,6 +6,8 @@ runs: run: | set -x + OPCACHE_TLS_TESTS_DEPS="gcc clang lld" + export DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386 apt-get update -y | true @@ -50,4 +52,5 @@ runs: re2c \ unzip \ wget \ - zlib1g-dev:i386 + zlib1g-dev:i386 \ + $OPCACHE_TLS_TESTS_DEPS diff --git a/.github/actions/apt-x64/action.yml b/.github/actions/apt-x64/action.yml index bc2aa00df20b1..98c7003175d73 100644 --- a/.github/actions/apt-x64/action.yml +++ b/.github/actions/apt-x64/action.yml @@ -6,6 +6,8 @@ runs: run: | set -x + OPCACHE_TLS_TESTS_DEPS="gcc clang lld" + export DEBIAN_FRONTEND=noninteractive # Install sudo in Docker for consistent actions @@ -73,4 +75,5 @@ runs: libqdbm-dev \ libjpeg-dev \ libpng-dev \ - libfreetype6-dev + libfreetype6-dev \ + $OPCACHE_TLS_TESTS_DEPS diff --git a/.github/actions/brew/action.yml b/.github/actions/brew/action.yml index eeae02fde1c3f..a1b3e92f37f63 100644 --- a/.github/actions/brew/action.yml +++ b/.github/actions/brew/action.yml @@ -11,10 +11,14 @@ runs: code=" keg.link\(verbose: verbose\?" sudo sed -Ei '' "s/$code.*/$code, overwrite: true\)/" "$formula_installer" + # Some packages exist on x86 but not arm, or vice versa. + # Install them with reinstall to avoid warnings. + brew reinstall autoconf webp tidy-html5 libzip libsodium icu4c brew install \ bison \ re2c brew install \ + aspell \ bzip2 \ enchant \ libffi \ diff --git a/.github/actions/configure-macos/action.yml b/.github/actions/configure-macos/action.yml index 7312ff5d69f19..071e31708c21b 100644 --- a/.github/actions/configure-macos/action.yml +++ b/.github/actions/configure-macos/action.yml @@ -10,15 +10,15 @@ runs: run: | set -x BREW_OPT="$(brew --prefix)"/opt - export PATH="/usr/local/opt/bison/bin:$PATH" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/openssl@1.1/lib/pkgconfig" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/curl/lib/pkgconfig" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/krb5/lib/pkgconfig" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/libffi/lib/pkgconfig" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/libxml2/lib/pkgconfig" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/libxslt/lib/pkgconfig" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/zlib/lib/pkgconfig" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/icu4c/lib/pkgconfig" + export PATH="$BREW_OPT/bison/bin:$PATH" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/openssl@1.1/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/curl/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/krb5/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/libffi/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/libxml2/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/libxslt/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/zlib/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/icu4c/lib/pkgconfig" sed -i -e 's/Requires.private:.*//g' "$BREW_OPT/curl/lib/pkgconfig/libcurl.pc" ./buildconf --force ./configure \ @@ -28,8 +28,8 @@ runs: --enable-fpm \ --with-pdo-mysql=mysqlnd \ --with-mysqli=mysqlnd \ - --with-pgsql=/usr/local/opt/libpq \ - --with-pdo-pgsql=/usr/local/opt/libpq \ + --with-pgsql="$BREW_OPT"/libpq \ + --with-pdo-pgsql="$BREW_OPT"/libpq \ --with-pdo-sqlite \ --without-pear \ --enable-gd \ @@ -42,25 +42,25 @@ runs: --enable-soap \ --enable-xmlreader \ --with-xsl \ - --with-tidy=/usr/local/opt/tidy-html5 \ + --with-tidy="$BREW_OPT"/tidy-html5 \ --with-libxml \ --enable-sysvsem \ --enable-sysvshm \ --enable-shmop \ --enable-pcntl \ - --with-readline=/usr/local/opt/readline \ + --with-readline="$BREW_OPT"/readline \ --enable-mbstring \ --with-curl \ - --with-gettext=/usr/local/opt/gettext \ + --with-gettext="$BREW_OPT"/gettext \ --enable-sockets \ - --with-bz2=/usr/local/opt/bzip2 \ + --with-bz2="$BREW_OPT"/bzip2 \ --with-openssl \ - --with-gmp=/usr/local/opt/gmp \ - --with-iconv=/usr/local/opt/libiconv \ + --with-gmp="$BREW_OPT"/gmp \ + --with-iconv="$BREW_OPT"/libiconv \ --enable-bcmath \ --enable-calendar \ --enable-ftp \ - --with-pspell=/usr/local/opt/aspell \ + --with-pspell="$BREW_OPT"/aspell \ --with-kerberos \ --enable-sysvmsg \ --with-ffi \ diff --git a/.github/actions/extra-tests/action.yml b/.github/actions/extra-tests/action.yml new file mode 100644 index 0000000000000..0537496064008 --- /dev/null +++ b/.github/actions/extra-tests/action.yml @@ -0,0 +1,7 @@ +name: Extra tests +runs: + using: composite + steps: + - shell: sh + run: | + sapi/cli/php run-extra-tests.php diff --git a/.github/actions/freebsd/action.yml b/.github/actions/freebsd/action.yml index 949bbcc895292..370f4c22511b8 100644 --- a/.github/actions/freebsd/action.yml +++ b/.github/actions/freebsd/action.yml @@ -3,13 +3,16 @@ inputs: configurationParameters: default: '' required: false + runExtraTests: + default: false + required: false runs: using: composite steps: - name: FreeBSD uses: vmactions/freebsd-vm@v1 with: - release: '13.3' + release: '13.5' usesh: true copyback: false # Temporarily disable sqlite, as FreeBSD ships it with disabled double quotes. We'll need to fix our tests. @@ -17,6 +20,8 @@ runs: prepare: | cd $GITHUB_WORKSPACE + OPCACHE_TLS_TESTS_DEPS="gcc" + kldload accf_http pkg install -y \ autoconf \ @@ -41,9 +46,11 @@ runs: webp \ libavif \ `#sqlite3` \ - curl + curl \ + $OPCACHE_TLS_TESTS_DEPS ./buildconf -f + CC=clang CXX=clang++ \ ./configure \ --prefix=/usr/local \ --enable-debug \ @@ -107,3 +114,7 @@ runs: --show-slow 1000 \ --set-timeout 120 \ -d zend_extension=opcache.so + + if test "${{ inputs.runExtraTests }}" = "true"; then + sapi/cli/php run-extra-tests.php + fi diff --git a/.github/actions/verify-generated-files/action.yml b/.github/actions/verify-generated-files/action.yml index 266f4d33c93b1..21f98fa39570e 100644 --- a/.github/actions/verify-generated-files/action.yml +++ b/.github/actions/verify-generated-files/action.yml @@ -5,7 +5,7 @@ runs: - shell: bash run: | set -x - [[ "$OSTYPE" == "darwin"* ]] && export PATH="/usr/local/opt/bison/bin:$PATH" + [[ "$OSTYPE" == "darwin"* ]] && export PATH="$(brew --prefix)/opt/bison/bin:$PATH" scripts/dev/credits scripts/dev/genfiles Zend/zend_vm_gen.php diff --git a/.github/scripts/setup-slapd.sh b/.github/scripts/setup-slapd.sh index fcaa67d0a5f7d..f6b976783c77e 100755 --- a/.github/scripts/setup-slapd.sh +++ b/.github/scripts/setup-slapd.sh @@ -72,6 +72,9 @@ olcTLSCertificateKeyFile: /etc/ldap/ssl/server.key add: olcTLSVerifyClient olcTLSVerifyClient: never - +add: olcTLSProtocolMin +olcTLSProtocolMin: 3.3 +- add: olcAuthzRegexp olcAuthzRegexp: uid=usera,cn=digest-md5,cn=auth cn=usera,dc=my-domain,dc=com - diff --git a/.github/scripts/windows/find-target-branch.bat b/.github/scripts/windows/find-target-branch.bat index 77d1684048142..a0b47f2488946 100644 --- a/.github/scripts/windows/find-target-branch.bat +++ b/.github/scripts/windows/find-target-branch.bat @@ -3,6 +3,6 @@ for /f "usebackq tokens=3" %%i in (`findstr PHP_MAJOR_VERSION main\php_version.h`) do set BRANCH=%%i for /f "usebackq tokens=3" %%i in (`findstr PHP_MINOR_VERSION main\php_version.h`) do set BRANCH=%BRANCH%.%%i -if /i "%BRANCH%" equ "8.4" ( +if /i "%BRANCH%" equ "8.5" ( set BRANCH=master ) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 5817c647a871a..ffa43fe744a26 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -85,6 +85,8 @@ jobs: with: runTestsParameters: >- --asan -x + - name: Extra tests + uses: ./.github/actions/extra-tests ALPINE: if: inputs.run_alpine name: ALPINE_X64_ASAN_UBSAN_DEBUG_ZTS @@ -134,6 +136,8 @@ jobs: --asan -x -d zend_extension=opcache.so -d opcache.enable_cli=1 + - name: Extra tests + uses: ./.github/actions/extra-tests - name: Notify Slack if: failure() uses: ./.github/actions/notify-slack @@ -266,6 +270,8 @@ jobs: ${{ matrix.run_tests_parameters }} -d zend_extension=opcache.so -d opcache.enable_cli=1 + - name: Extra tests + uses: ./.github/actions/extra-tests - name: Verify generated files are up to date uses: ./.github/actions/verify-generated-files - name: Notify Slack @@ -355,6 +361,8 @@ jobs: ${{ matrix.run_tests_parameters }} -d zend_extension=opcache.so -d opcache.enable_cli=1 + - name: Extra tests + uses: ./.github/actions/extra-tests - name: Notify Slack if: failure() uses: ./.github/actions/notify-slack @@ -414,6 +422,8 @@ jobs: runTestsParameters: >- -d zend_extension=opcache.so -d opcache.enable_cli=1 + - name: Extra tests + uses: ./.github/actions/extra-tests - name: Verify generated files are up to date uses: ./.github/actions/verify-generated-files - name: Notify Slack @@ -555,14 +565,17 @@ jobs: repositories="amp cache dns file http parallel parser pipeline process serialization socket sync websocket-client websocket-server" X=0 for repository in $repositories; do - printf "Testing amp/%s\n" "$repository" + echo "::group::$repository" git clone "https://github.com/amphp/$repository.git" "amphp-$repository" --depth 1 cd "amphp-$repository" git rev-parse HEAD php /usr/bin/composer install --no-progress --ignore-platform-req=php+ + EXIT_CODE=0 vendor/bin/phpunit || EXIT_CODE=$? + echo -e "\n::endgroup::" if [ ${EXIT_CODE:-0} -gt 128 ]; then X=1; + echo "Failed" fi cd .. done @@ -586,14 +599,17 @@ jobs: repositories="async cache child-process datagram dns event-loop promise promise-stream promise-timer stream" X=0 for repository in $repositories; do - printf "Testing reactphp/%s\n" "$repository" + echo "::group::$repository" git clone "https://github.com/reactphp/$repository.git" "reactphp-$repository" --depth 1 cd "reactphp-$repository" git rev-parse HEAD php /usr/bin/composer install --no-progress --ignore-platform-req=php+ + EXIT_CODE=0 vendor/bin/phpunit || EXIT_CODE=$? + echo -e "\n::endgroup::" if [ $[EXIT_CODE:-0} -gt 128 ]; then X=1; + echo "Failed" fi cd .. done @@ -618,15 +634,19 @@ jobs: php /usr/bin/composer install --no-progress --ignore-platform-req=php+ php ./phpunit install # Test causes a heap-buffer-overflow but I cannot reproduce it locally... - php -r '$c = file_get_contents("src/Symfony/Component/HtmlSanitizer/Tests/HtmlSanitizerCustomTest.php"); $c = str_replace("public function testSanitizeDeepNestedString()", "/** @group skip */\n public function testSanitizeDeepNestedString()", $c); file_put_contents("src/Symfony/Component/HtmlSanitizer/Tests/HtmlSanitizerCustomTest.php", $c);' + php -r '$c = file_get_contents("src/Symfony/Component/HtmlSanitizer/Tests/HtmlSanitizerCustomTest.php"); $c = str_replace("public function testSanitizeDeepNestedString()", "#[\\PHPUnit\\Framework\\Attributes\\Group('"'"'skip'"'"')]\n public function testSanitizeDeepNestedString()", $c); file_put_contents("src/Symfony/Component/HtmlSanitizer/Tests/HtmlSanitizerCustomTest.php", $c);' # Buggy FFI test in Symfony, see https://github.com/symfony/symfony/issues/47668 - php -r '$c = file_get_contents("src/Symfony/Component/VarDumper/Tests/Caster/FFICasterTest.php"); $c = str_replace("public function testCastNonTrailingCharPointer()", "/** @group skip */\n public function testCastNonTrailingCharPointer()", $c); file_put_contents("src/Symfony/Component/VarDumper/Tests/Caster/FFICasterTest.php", $c);' + php -r '$c = file_get_contents("src/Symfony/Component/VarDumper/Tests/Caster/FFICasterTest.php"); $c = str_replace("public function testCastNonTrailingCharPointer()", "#[\\PHPUnit\\Framework\\Attributes\\Group('"'"'skip'"'"')]\n public function testCastNonTrailingCharPointer()", $c); file_put_contents("src/Symfony/Component/VarDumper/Tests/Caster/FFICasterTest.php", $c);' export SYMFONY_DEPRECATIONS_HELPER=max[total]=999 X=0 for component in $(find src/Symfony -mindepth 2 -type f -name phpunit.xml.dist -printf '%h\n'); do - php ./phpunit $component --exclude-group tty,benchmark,intl-data,transient --exclude-group skip || EXIT_CODE=$? + echo "::group::$component" + EXIT_CODE=0 + php ./phpunit $component --exclude-group tty --exclude-group benchmark --exclude-group intl-data --exclude-group transient --exclude-group skip || EXIT_CODE=$? + echo -e "\n::endgroup::" if [ ${EXIT_CODE:-0} -gt 128 ]; then X=1; + echo "Failed" fi done exit $X @@ -1066,6 +1086,7 @@ jobs: - zts: ${{ !inputs.run_freebsd_zts && true || '*never*' }} name: "FREEBSD_${{ matrix.zts && 'ZTS' || 'NTS' }}" runs-on: ubuntu-latest + timeout-minutes: 50 steps: - name: git checkout uses: actions/checkout@v4 @@ -1076,3 +1097,4 @@ jobs: with: configurationParameters: >- --${{ matrix.zts && 'enable' || 'disable' }}-zts + runExtraTests: true diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index b14fc2ef3bdc3..6c572f6530e23 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -181,7 +181,7 @@ jobs: -d opcache.enable_cli=1 MACOS_DEBUG_NTS: if: github.repository == 'php/php-src' || github.event_name == 'pull_request' - runs-on: macos-13 + runs-on: macos-14 steps: - name: git checkout uses: actions/checkout@v4 @@ -199,7 +199,7 @@ jobs: configurationParameters: --enable-debug --disable-zts - name: make run: |- - export PATH="/usr/local/opt/bison/bin:$PATH" + export PATH="$(brew --prefix)/opt/bison/bin:$PATH" make -j$(sysctl -n hw.logicalcpu) >/dev/null - name: make install run: sudo make install @@ -340,6 +340,7 @@ jobs: if: github.repository == 'php/php-src' || github.event_name == 'pull_request' name: FREEBSD runs-on: ubuntu-latest + timeout-minutes: 50 steps: - name: git checkout uses: actions/checkout@v4 diff --git a/.gitignore b/.gitignore index 4e95d9a9da33d..cd9ba81306fd9 100644 --- a/.gitignore +++ b/.gitignore @@ -132,6 +132,7 @@ config.h.in /sapi/cgi/php-cgi /sapi/fpm/php-fpm /sapi/phpdbg/phpdbg +/sapi/fuzzer/php-fuzz-* /scripts/php-config /scripts/phpize php diff --git a/NEWS b/NEWS index c395d0dcd3b59..bead37f038333 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,94 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.3.24 +28 Aug 2025, PHP 8.3.25 + +- Core: + . Fixed GH-19169 build issue with C++17 and ZEND_STATIC_ASSERT macro. + (psumbera) + . Fixed bug GH-18581 (Coerce numeric string keys from iterators when argument + unpacking). (ilutov) + . Fixed OSS-Fuzz #434346548 (Failed assertion with throwing __toString in + binary const expr). (ilutov) + . Fixed bug GH-19305 (Operands may be being released during comparison). + (Arnaud) + . Fixed bug GH-19303 (Unpacking empty packed array into uninitialized array + causes assertion failure). (nielsdos) + . Fixed bug GH-19306 (Generator can be resumed while fetching next value from + delegated Generator). (Arnaud) + . Fixed bug GH-19326 (Calling Generator::throw() on a running generator with + a non-Generator delegate crashes). (Arnaud) + . Fixed bug GH-18736 (Circumvented type check with return by ref + finally). + (ilutov) + . Fixed zend call stack size for macOs/arm64. (David Carlier) + . Fixed bug GH-19065 (Long match statement can segfault compiler during + recursive SSA renaming). (nielsdos, Arnaud) + +- Calendar: + . Fixed bug GH-19371 (integer overflow in calendar.c). (nielsdos) + +- FTP: + . Fix theoretical issues with hrtime() not being available. (nielsdos) + +- GD: + . Fix incorrect comparison with result of php_stream_can_cast(). (Girgias) + +- Hash: + . Fix crash on clone failure. (nielsdos) + +- Intl: + . Fixed GH-19261: msgfmt_parse_message leaks on message creation failure. + (David Carlier) + . Fix return value on failure for resourcebundle count handler. (Girgias) + +- LDAP: + . Fixed bug GH-18529 (additional inheriting of TLS int options). + (Jakub Zelenka) + +- LibXML: + . Fixed bug GH-19098 (libxml<2.13 segmentation fault caused by + php_libxml_node_free). (nielsdos) + +- MbString: + . Fixed bug GH-19397 (mb_list_encodings() can cause crashes on shutdown). + (nielsdos) + +- Opcache: + . Reset global pointers to prevent use-after-free in zend_jit_status(). + (Florian Engelhardt) + +- OpenSSL: + . Fixed bug GH-18986 (OpenSSL backend: incorrect RAND_{load,write}_file() + return value check). (nielsdos, botovq) + . Fix error return check of EVP_CIPHER_CTX_ctrl(). (nielsdos) + . Fixed bug GH-19428 (openssl_pkey_derive segfaults for DH derive with low + key_length param). (Jakub Zelenka) + +- PDO Pgsql: + . Fixed dangling pointer access on _pdo_pgsql_trim_message helper. + (dixyes) + +- Readline: + . Fixed bug GH-19250 and bug #51360 (Invalid conftest for rl_pending_input). + (petk, nielsdos) + +- SOAP: + . Fixed bug GH-18640 (heap-use-after-free ext/soap/php_encoding.c:299:32 + in soap_check_zval_ref). (nielsdos) + +- Sockets: + . Fix some potential crashes on incorrect argument value. (nielsdos) + +- Standard: + . Fixed OSS Fuzz #433303828 (Leak in failed unserialize() with opcache). + (ilutov) + . Fix theoretical issues with hrtime() not being available. (nielsdos) + . Fixed bug GH-19300 (Nested array_multisort invocation with error breaks). + (nielsdos) + +- Windows: + . Free opened_path when opened_path_len >= MAXPATHLEN. (dixyes) + +31 Jul 2025, PHP 8.3.24 - Calendar: . Fixed jewishtojd overflow on year argument. (David Carlier) @@ -17,6 +105,9 @@ PHP NEWS (nielsdos) . Remove incorrect string release. (nielsdos) +- Intl: + . Fix memleak on failure in collator_get_sort_key(). (nielsdos) + - LDAP: . Fixed GH-18902 ldap_exop/ldap_exop_sync assert triggered on empty request OID. (David Carlier) diff --git a/Zend/Optimizer/zend_ssa.c b/Zend/Optimizer/zend_ssa.c index 590df8155e395..cb8acc3775d19 100644 --- a/Zend/Optimizer/zend_ssa.c +++ b/Zend/Optimizer/zend_ssa.c @@ -22,6 +22,7 @@ #include "zend_ssa.h" #include "zend_dump.h" #include "zend_inference.h" +#include "zend_worklist.h" #include "Optimizer/zend_optimizer_internal.h" static bool dominates(const zend_basic_block *blocks, int a, int b) { @@ -787,7 +788,7 @@ ZEND_API int zend_ssa_rename_op(const zend_op_array *op_array, const zend_op *op } /* }}} */ -static zend_result zend_ssa_rename(const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, int *var, int n) /* {{{ */ +static void zend_ssa_rename_in_block(const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, int *var, int n) /* {{{ */ { zend_basic_block *blocks = ssa->cfg.blocks; zend_ssa_block *ssa_blocks = ssa->blocks; @@ -795,15 +796,6 @@ static zend_result zend_ssa_rename(const zend_op_array *op_array, uint32_t build int ssa_vars_count = ssa->vars_count; int i, j; zend_op *opline, *end; - int *tmp = NULL; - ALLOCA_FLAG(use_heap = 0); - - // FIXME: Can we optimize this copying out in some cases? - if (blocks[n].next_child >= 0) { - tmp = do_alloca(sizeof(int) * (op_array->last_var + op_array->T), use_heap); - memcpy(tmp, var, sizeof(int) * (op_array->last_var + op_array->T)); - var = tmp; - } if (ssa_blocks[n].phis) { zend_ssa_phi *phi = ssa_blocks[n].phis; @@ -887,22 +879,90 @@ static zend_result zend_ssa_rename(const zend_op_array *op_array, uint32_t build } ssa->vars_count = ssa_vars_count; +} +/* }}} */ - j = blocks[n].children; - while (j >= 0) { - // FIXME: Tail call optimization? - if (zend_ssa_rename(op_array, build_flags, ssa, var, j) == FAILURE) - return FAILURE; - j = blocks[j].next_child; - } +static zend_result zend_ssa_rename(const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, int *var, int n) +{ + /* The worklist contains block numbers, encoded as positive or negative value. + * Positive values indicate that the variable rename still needs to happen for the block. + * Negative values indicate the variable rename was done and all children were handled too. + * In that case, we will clean up. + * Because block 0 is valid, we bias the block numbers by adding 1 such that we can distinguish + * positive and negative values in all cases. */ + zend_worklist_stack work; + ALLOCA_FLAG(work_use_heap); + ZEND_WORKLIST_STACK_ALLOCA(&work, ssa->cfg.blocks_count, work_use_heap); + zend_worklist_stack_push(&work, n + 1); + + /* This is used to backtrack the right version of the renamed variables to use. */ + ALLOCA_FLAG(save_vars_use_heap); + unsigned int save_vars_top = 0; + int **save_vars = do_alloca(sizeof(int *) * (ssa->cfg.blocks_count + 1), save_vars_use_heap); + save_vars[0] = var; + + while (work.len) { + n = zend_worklist_stack_pop(&work); + + /* Enter state: perform SSA variable rename */ + if (n > 0) { + n--; + + // FIXME: Can we optimize this copying out in some cases? + int *new_var; + if (ssa->cfg.blocks[n].next_child >= 0) { + new_var = emalloc(sizeof(int) * (op_array->last_var + op_array->T)); + memcpy(new_var, save_vars[save_vars_top], sizeof(int) * (op_array->last_var + op_array->T)); + save_vars[++save_vars_top] = new_var; + } else { + new_var = save_vars[save_vars_top]; + } - if (tmp) { - free_alloca(tmp, use_heap); + zend_ssa_rename_in_block(op_array, build_flags, ssa, new_var, n); + + int j = ssa->cfg.blocks[n].children; + if (j >= 0) { + /* Push backtrack state */ + zend_worklist_stack_push(&work, -(n + 1)); + + /* Push children in enter state */ + unsigned int child_count = 0; + int len_prior = work.len; + do { + zend_worklist_stack_push(&work, j + 1); + j = ssa->cfg.blocks[j].next_child; + child_count++; + } while (j >= 0); + + /* Reverse block order to maintain SSA variable number order given in previous PHP versions, + * but the data structure doesn't allow reverse dominator tree traversal. */ + for (unsigned int i = 0; i < child_count / 2; i++) { + int tmp = work.buf[len_prior + i]; + work.buf[len_prior + i] = work.buf[work.len - 1 - i]; + work.buf[work.len - 1 - i] = tmp; + } + } else { + /* Leafs jump directly to backtracking */ + goto backtrack; + } + } + /* Leave state: backtrack */ + else { + n = -n; + n--; +backtrack:; + if (ssa->cfg.blocks[n].next_child >= 0) { + efree(save_vars[save_vars_top]); + save_vars_top--; + } + } } + free_alloca(save_vars, save_vars_use_heap); + ZEND_WORKLIST_STACK_FREE_ALLOCA(&work, work_use_heap); + return SUCCESS; } -/* }}} */ ZEND_API zend_result zend_build_ssa(zend_arena **arena, const zend_script *script, const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa) /* {{{ */ { diff --git a/Zend/tests/array_unpack/gh19303.phpt b/Zend/tests/array_unpack/gh19303.phpt new file mode 100644 index 0000000000000..af594c3740c2d --- /dev/null +++ b/Zend/tests/array_unpack/gh19303.phpt @@ -0,0 +1,11 @@ +--TEST-- +GH-19303 (Unpacking empty packed array into uninitialized array causes assertion failure) +--FILE-- + +--EXPECT-- +array(0) { +} diff --git a/Zend/tests/gh18581.phpt b/Zend/tests/gh18581.phpt new file mode 100644 index 0000000000000..cc5c0fff02e92 --- /dev/null +++ b/Zend/tests/gh18581.phpt @@ -0,0 +1,32 @@ +--TEST-- +GH-18581: Coerce numeric string keys from iterators when argument unpacking +--FILE-- + 'first'; + yield '101' => 'second'; + yield '102' => 'third'; + yield 'named' => 'fourth'; +} + +function test($x = null, $y = null, ...$z) { + var_dump($x, $y, $z); + var_dump($z[0]); + var_dump($z['named']); +} + +test(...g()); + +?> +--EXPECT-- +string(5) "first" +string(6) "second" +array(2) { + [0]=> + string(5) "third" + ["named"]=> + string(6) "fourth" +} +string(5) "third" +string(6) "fourth" diff --git a/Zend/tests/gh18736.phpt b/Zend/tests/gh18736.phpt new file mode 100644 index 0000000000000..f397ee39a310b --- /dev/null +++ b/Zend/tests/gh18736.phpt @@ -0,0 +1,24 @@ +--TEST-- +GH-18736: Circumvented type check with return by ref + finally +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECT-- +test(): Return value must be of type int, string returned diff --git a/Zend/tests/gh19305-001.phpt b/Zend/tests/gh19305-001.phpt new file mode 100644 index 0000000000000..4c9ee37473e34 --- /dev/null +++ b/Zend/tests/gh19305-001.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-19305 001: Operands may be released during comparison +--FILE-- + 'test', + 'bar' => 2, +]; +$b = (object)[ + 'foo' => new class { + public function __toString() { + global $a, $b; + $a = $b = null; + return ''; + } + }, + 'bar' => 2, +]; + +// Comparison of $a->foo and $b->foo calls __toString(), which releases +// both $a and $b. +var_dump($a > $b); + +?> +--EXPECT-- +bool(true) diff --git a/Zend/tests/gh19305-002.phpt b/Zend/tests/gh19305-002.phpt new file mode 100644 index 0000000000000..790156191317b --- /dev/null +++ b/Zend/tests/gh19305-002.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-19305 002: Operands may be released during comparison +--FILE-- + 'test', + 'bar' => 2, +]; +$b = [ + 'foo' => new class { + public function __toString() { + global $a, $b; + $a = $b = null; + return ''; + } + }, + 'bar' => 2, +]; + +// Comparison of $a['foo'] and $b['foo'] calls __toString(), which releases +// both $a and $b. +var_dump($a > $b); + +?> +--EXPECT-- +bool(true) diff --git a/Zend/tests/gh19305-003.phpt b/Zend/tests/gh19305-003.phpt new file mode 100644 index 0000000000000..7b071156ef8e3 --- /dev/null +++ b/Zend/tests/gh19305-003.phpt @@ -0,0 +1,28 @@ +--TEST-- +GH-19305 003: Operands may be released during comparison +--SKIPIF-- + +--FILE-- +newLazyProxy(function () { return new C; }); + +// Comparison calls initializers, which releases $o +var_dump($o > +$r->newLazyGhost(function () { + global $o; + $o = null; +})); + +?> +--EXPECT-- +bool(false) diff --git a/Zend/tests/gh19306.phpt b/Zend/tests/gh19306.phpt new file mode 100644 index 0000000000000..e19735d94c846 --- /dev/null +++ b/Zend/tests/gh19306.phpt @@ -0,0 +1,40 @@ +--TEST-- +GH-19306: Generator suspended in yield from may be resumed +--FILE-- +next(); + echo "Fiber return\n"; +}); +$fiber->start(); +echo "Fiber suspended\n"; +try { + $a->next(); +} catch (Throwable $t) { + echo $t->getMessage(), "\n"; +} +echo "Destroying fiber\n"; +$fiber = null; +echo "Shutdown\n"; +?> +--EXPECT-- +Fiber start +Fiber suspended +Cannot resume an already running generator +Destroying fiber +Shutdown diff --git a/Zend/tests/gh19326.phpt b/Zend/tests/gh19326.phpt new file mode 100644 index 0000000000000..335fdd382ea67 --- /dev/null +++ b/Zend/tests/gh19326.phpt @@ -0,0 +1,36 @@ +--TEST-- +GH-19326: Calling Generator::throw() on a running generator with a non-Generator delegate crashes +--FILE-- +rewind(); + +$fiber = new Fiber(function () use ($b) { + $b->next(); +}); + +$fiber->start(); + +try { + $b->throw(new Exception('test')); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +$fiber->resume(); + +?> +--EXPECT-- +Cannot resume an already running generator diff --git a/Zend/tests/oss_fuzz_434346548.phpt b/Zend/tests/oss_fuzz_434346548.phpt new file mode 100644 index 0000000000000..139e36758bc49 --- /dev/null +++ b/Zend/tests/oss_fuzz_434346548.phpt @@ -0,0 +1,22 @@ +--TEST-- +OSS-Fuzz #434346548: Failed assertion with throwing __toString in binary const expr +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECT-- +Foo::__toString(): Return value must be of type string, none returned diff --git a/Zend/tests/stack_limit/stack_limit_010.phpt b/Zend/tests/stack_limit/stack_limit_010.phpt index 312c2cf551696..9e37555e6decc 100644 --- a/Zend/tests/stack_limit/stack_limit_010.phpt +++ b/Zend/tests/stack_limit/stack_limit_010.phpt @@ -15,7 +15,10 @@ $stack = zend_test_zend_call_stack_get(); var_dump($stack); $expectedMaxSize = match(php_uname('s')) { - 'Darwin' => 8*1024*1024, + 'Darwin' => match(php_uname('m')) { + 'x86_64' => 8*1024*1024, + 'arm64' => 8372224, + }, 'FreeBSD' => match(php_uname('m')) { 'amd64' => 512*1024*1024 - 4096, 'i386' => 64*1024*1024 - 4096, diff --git a/Zend/zend.h b/Zend/zend.h index 0d2c52e2505e2..40f580bfb52d8 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.3.24-dev" +#define ZEND_VERSION "4.3.25" #define ZEND_ENGINE_3 diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index f8c4ca17a9b95..1ecb85f1d2a2f 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -548,9 +548,10 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_inner( ret = FAILURE; } else { binary_op_type op = get_binary_op(ast->attr); - ret = op(result, &op1, &op2); + op(result, &op1, &op2); zval_ptr_dtor_nogc(&op1); zval_ptr_dtor_nogc(&op2); + ret = EG(exception) ? FAILURE : SUCCESS; } break; case ZEND_AST_GREATER: diff --git a/Zend/zend_call_stack.c b/Zend/zend_call_stack.c index bbaa8810af9e1..8e0ce4c32488a 100644 --- a/Zend/zend_call_stack.c +++ b/Zend/zend_call_stack.c @@ -417,7 +417,9 @@ static bool zend_call_stack_get_macos(zend_call_stack *stack) void *base = pthread_get_stackaddr_np(pthread_self()); size_t max_size; - if (pthread_main_np()) { +#if !defined(__aarch64__) + if (pthread_main_np()) + { /* pthread_get_stacksize_np() returns a too low value for the main * thread in OSX 10.9, 10.10: * https://mail.openjdk.org/pipermail/hotspot-dev/2013-October/011353.html @@ -427,7 +429,10 @@ static bool zend_call_stack_get_macos(zend_call_stack *stack) /* Stack size is 8MiB by default for main threads * https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html */ max_size = 8 * 1024 * 1024; - } else { + } + else +#endif + { max_size = pthread_get_stacksize_np(pthread_self()); } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 41113e2f0055b..6c8577cef5881 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5201,8 +5201,20 @@ static void zend_compile_return(zend_ast *ast) /* {{{ */ expr_ast ? &expr_node : NULL, CG(active_op_array)->arg_info - 1, 0); } + uint32_t opnum_before_finally = get_next_op_number(); + zend_handle_loops_and_finally((expr_node.op_type & (IS_TMP_VAR | IS_VAR)) ? &expr_node : NULL); + /* Content of reference might have changed in finally, repeat type check. */ + if (by_ref + /* Check if any opcodes were emitted since the last return type check. */ + && opnum_before_finally != get_next_op_number() + && !is_generator + && (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) { + zend_emit_return_type_check( + expr_ast ? &expr_node : NULL, CG(active_op_array)->arg_info - 1, 0); + } + opline = zend_emit_op(NULL, by_ref ? ZEND_RETURN_BY_REF : ZEND_RETURN, &expr_node, NULL); diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c index f82dd4c1daf13..eeab16b9a1352 100644 --- a/Zend/zend_generators.c +++ b/Zend/zend_generators.c @@ -492,8 +492,14 @@ ZEND_API zend_execute_data *zend_generator_check_placeholder_frame(zend_execute_ return ptr; } -static void zend_generator_throw_exception(zend_generator *generator, zval *exception) +static zend_result zend_generator_throw_exception(zend_generator *generator, zval *exception) { + if (generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING) { + zval_ptr_dtor(exception); + zend_throw_error(NULL, "Cannot resume an already running generator"); + return FAILURE; + } + zend_execute_data *original_execute_data = EG(current_execute_data); /* Throw the exception in the context of the generator. Decrementing the opline @@ -519,6 +525,8 @@ static void zend_generator_throw_exception(zend_generator *generator, zval *exce } EG(current_execute_data) = original_execute_data; + + return SUCCESS; } static void zend_generator_add_child(zend_generator *generator, zend_generator *child) @@ -787,6 +795,8 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ orig_generator->execute_fake.prev_execute_data = original_execute_data; } + generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING; + /* Ensure this is run after executor_data swap to have a proper stack trace */ if (UNEXPECTED(!Z_ISUNDEF(generator->values))) { if (EXPECTED(zend_generator_get_next_delegated_value(generator) == SUCCESS)) { @@ -795,7 +805,7 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ EG(jit_trace_num) = original_jit_trace_num; orig_generator->flags &= ~(ZEND_GENERATOR_DO_INIT | ZEND_GENERATOR_IN_FIBER); - generator->flags &= ~ZEND_GENERATOR_IN_FIBER; + generator->flags &= ~(ZEND_GENERATOR_CURRENTLY_RUNNING | ZEND_GENERATOR_IN_FIBER); return; } if (UNEXPECTED(EG(exception))) { @@ -817,7 +827,6 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ } /* Resume execution */ - generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING; if (!ZEND_OBSERVER_ENABLED) { zend_execute_ex(generator->execute_data); } else { @@ -1025,7 +1034,9 @@ ZEND_METHOD(Generator, throw) if (generator->execute_data) { zend_generator *root = zend_generator_get_current(generator); - zend_generator_throw_exception(root, exception); + if (zend_generator_throw_exception(root, exception) == FAILURE) { + return; + } zend_generator_resume(generator); diff --git a/Zend/zend_multiply.h b/Zend/zend_multiply.h index a99e858bd7798..b3f0086009f47 100644 --- a/Zend/zend_multiply.h +++ b/Zend/zend_multiply.h @@ -176,13 +176,15 @@ static zend_always_inline size_t zend_safe_address(size_t nmemb, size_t size, si __asm__ ("mull %3\n\tadcl $0,%1" : "=&a"(res), "=&d" (m_overflow) : "%0"(res), - "rm"(size)); + "rm"(size) + : "cc"); } else { __asm__ ("mull %3\n\taddl %4,%0\n\tadcl $0,%1" : "=&a"(res), "=&d" (m_overflow) : "%0"(res), "rm"(size), - "rm"(offset)); + "rm"(offset) + : "cc"); } if (UNEXPECTED(m_overflow)) { @@ -211,7 +213,8 @@ static zend_always_inline size_t zend_safe_address(size_t nmemb, size_t size, si "adc $0,%1" : "=&a"(res), "=&d" (m_overflow) : "%0"(res), - "rm"(size)); + "rm"(size) + : "cc"); } else { __asm__ ("mul" LP_SUFF " %3\n\t" "add %4,%0\n\t" @@ -219,7 +222,8 @@ static zend_always_inline size_t zend_safe_address(size_t nmemb, size_t size, si : "=&a"(res), "=&d" (m_overflow) : "%0"(res), "rm"(size), - "rm"(offset)); + "rm"(offset) + : "cc"); } #undef LP_SUFF if (UNEXPECTED(m_overflow)) { diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 180364b248d71..2eacb614f97c4 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1792,6 +1792,10 @@ ZEND_API int zend_std_compare_objects(zval *o1, zval *o2) /* {{{ */ } Z_PROTECT_RECURSION_P(o1); + GC_ADDREF(zobj1); + GC_ADDREF(zobj2); + int ret; + for (i = 0; i < zobj1->ce->default_properties_count; i++) { zval *p1, *p2; @@ -1806,35 +1810,50 @@ ZEND_API int zend_std_compare_objects(zval *o1, zval *o2) /* {{{ */ if (Z_TYPE_P(p1) != IS_UNDEF) { if (Z_TYPE_P(p2) != IS_UNDEF) { - int ret; - ret = zend_compare(p1, p2); if (ret != 0) { Z_UNPROTECT_RECURSION_P(o1); - return ret; + goto done; } } else { Z_UNPROTECT_RECURSION_P(o1); - return 1; + ret = 1; + goto done; } } else { if (Z_TYPE_P(p2) != IS_UNDEF) { Z_UNPROTECT_RECURSION_P(o1); - return 1; + ret = 1; + goto done; } } } Z_UNPROTECT_RECURSION_P(o1); - return 0; + ret = 0; + +done: + OBJ_RELEASE(zobj1); + OBJ_RELEASE(zobj2); + + return ret; } else { + GC_ADDREF(zobj1); + GC_ADDREF(zobj2); + if (!zobj1->properties) { rebuild_object_properties(zobj1); } if (!zobj2->properties) { rebuild_object_properties(zobj2); } - return zend_compare_symbol_tables(zobj1->properties, zobj2->properties); + + int ret = zend_compare_symbol_tables(zobj1->properties, zobj2->properties); + + OBJ_RELEASE(zobj1); + OBJ_RELEASE(zobj2); + + return ret; } } /* }}} */ diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index 38c87dfe98dbd..8646fc25be260 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -3384,7 +3384,19 @@ static int hash_zval_compare_function(zval *z1, zval *z2) /* {{{ */ ZEND_API int ZEND_FASTCALL zend_compare_symbol_tables(HashTable *ht1, HashTable *ht2) /* {{{ */ { - return ht1 == ht2 ? 0 : zend_hash_compare(ht1, ht2, (compare_func_t) hash_zval_compare_function, 0); + if (ht1 == ht2) { + return 0; + } + + GC_TRY_ADDREF(ht1); + GC_TRY_ADDREF(ht2); + + int ret = zend_hash_compare(ht1, ht2, (compare_func_t) hash_zval_compare_function, 0); + + GC_TRY_DTOR_NO_REF(ht1); + GC_TRY_DTOR_NO_REF(ht2); + + return ret; } /* }}} */ diff --git a/Zend/zend_portability.h b/Zend/zend_portability.h index 670687973148f..2308d6cdb3057 100644 --- a/Zend/zend_portability.h +++ b/Zend/zend_portability.h @@ -761,7 +761,9 @@ extern "C++" { /** @deprecated */ #define ZEND_CGG_DIAGNOSTIC_IGNORED_END ZEND_DIAGNOSTIC_IGNORED_END -#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* C11 */ +#if defined(__cplusplus) +# define ZEND_STATIC_ASSERT(c, m) static_assert((c), m) +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* C11 */ # define ZEND_STATIC_ASSERT(c, m) _Static_assert((c), m) #else # define ZEND_STATIC_ASSERT(c, m) diff --git a/Zend/zend_types.h b/Zend/zend_types.h index c4a07f58874ab..63f6ddbc97242 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -731,6 +731,18 @@ static zend_always_inline uint8_t zval_get_type(const zval* pz) { } \ } while (0) +#define GC_TRY_DTOR_NO_REF(p) \ + do { \ + zend_refcounted_h *_p = &(p)->gc; \ + if (!(_p->u.type_info & GC_IMMUTABLE)) { \ + if (zend_gc_delref(_p) == 0) { \ + rc_dtor_func((zend_refcounted *)_p); \ + } else { \ + gc_check_possible_root_no_ref((zend_refcounted *)_p); \ + } \ + } \ + } while (0) + #define GC_TYPE_MASK 0x0000000f #define GC_FLAGS_MASK 0x000003f0 #define GC_INFO_MASK 0xfffffc00 diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 0eacdfe145d49..f1d4f7448ce1e 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -5236,6 +5236,11 @@ ZEND_VM_C_LABEL(send_again): } name = Z_STR_P(&key); + + zend_ulong tmp; + if (ZEND_HANDLE_NUMERIC(name, tmp)) { + name = NULL; + } } } @@ -6157,17 +6162,22 @@ ZEND_VM_C_LABEL(add_unpack_again): zval *val; if (HT_IS_PACKED(ht) && (zend_hash_num_elements(result_ht) == 0 || HT_IS_PACKED(result_ht))) { - zend_hash_extend(result_ht, result_ht->nNumUsed + zend_hash_num_elements(ht), 1); - ZEND_HASH_FILL_PACKED(result_ht) { - ZEND_HASH_PACKED_FOREACH_VAL(ht, val) { - if (UNEXPECTED(Z_ISREF_P(val)) && - UNEXPECTED(Z_REFCOUNT_P(val) == 1)) { - val = Z_REFVAL_P(val); - } - Z_TRY_ADDREF_P(val); - ZEND_HASH_FILL_ADD(val); - } ZEND_HASH_FOREACH_END(); - } ZEND_HASH_FILL_END(); + /* zend_hash_extend() skips initialization when the number of elements is 0, + * but the code below expects that result_ht is initialized as packed. + * We can just skip the work in that case. */ + if (result_ht->nNumUsed + zend_hash_num_elements(ht) > 0) { + zend_hash_extend(result_ht, result_ht->nNumUsed + zend_hash_num_elements(ht), 1); + ZEND_HASH_FILL_PACKED(result_ht) { + ZEND_HASH_PACKED_FOREACH_VAL(ht, val) { + if (UNEXPECTED(Z_ISREF_P(val)) && + UNEXPECTED(Z_REFCOUNT_P(val) == 1)) { + val = Z_REFVAL_P(val); + } + Z_TRY_ADDREF_P(val); + ZEND_HASH_FILL_ADD(val); + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FILL_END(); + } } else { zend_string *key; diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 4ea6b302c8cfc..5f7f4997ce8d7 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -2355,6 +2355,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SEND_UNPACK_SPEC_HANDLER(ZEND_ } name = Z_STR_P(&key); + + zend_ulong tmp; + if (ZEND_HANDLE_NUMERIC(name, tmp)) { + name = NULL; + } } } @@ -2652,17 +2657,22 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ADD_ARRAY_UNPACK_SPEC_HANDLER( zval *val; if (HT_IS_PACKED(ht) && (zend_hash_num_elements(result_ht) == 0 || HT_IS_PACKED(result_ht))) { - zend_hash_extend(result_ht, result_ht->nNumUsed + zend_hash_num_elements(ht), 1); - ZEND_HASH_FILL_PACKED(result_ht) { - ZEND_HASH_PACKED_FOREACH_VAL(ht, val) { - if (UNEXPECTED(Z_ISREF_P(val)) && - UNEXPECTED(Z_REFCOUNT_P(val) == 1)) { - val = Z_REFVAL_P(val); - } - Z_TRY_ADDREF_P(val); - ZEND_HASH_FILL_ADD(val); - } ZEND_HASH_FOREACH_END(); - } ZEND_HASH_FILL_END(); + /* zend_hash_extend() skips initialization when the number of elements is 0, + * but the code below expects that result_ht is initialized as packed. + * We can just skip the work in that case. */ + if (result_ht->nNumUsed + zend_hash_num_elements(ht) > 0) { + zend_hash_extend(result_ht, result_ht->nNumUsed + zend_hash_num_elements(ht), 1); + ZEND_HASH_FILL_PACKED(result_ht) { + ZEND_HASH_PACKED_FOREACH_VAL(ht, val) { + if (UNEXPECTED(Z_ISREF_P(val)) && + UNEXPECTED(Z_REFCOUNT_P(val) == 1)) { + val = Z_REFVAL_P(val); + } + Z_TRY_ADDREF_P(val); + ZEND_HASH_FILL_ADD(val); + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FILL_END(); + } } else { zend_string *key; diff --git a/benchmark/generate_diff.php b/benchmark/generate_diff.php index 94c020df4b998..466a6ae0e1ed3 100644 --- a/benchmark/generate_diff.php +++ b/benchmark/generate_diff.php @@ -62,7 +62,7 @@ function formatDiff(?int $baseInstructions, int $headInstructions): string { } function find_benchmarked_commit_hash(string $repo, string $commitHash): ?string { - $repeat = 10; + $repeat = 100; while (true) { if ($repeat-- <= 0) { diff --git a/build/Makefile.global b/build/Makefile.global index ec19efcbc5894..49d4f8bcb3cfe 100644 --- a/build/Makefile.global +++ b/build/Makefile.global @@ -90,7 +90,7 @@ PHP_TEST_SHARED_EXTENSIONS = ` \ . $$i; $(top_srcdir)/build/shtool echo -n -- " -d zend_extension=$(top_builddir)/modules/$$dlname"; \ done; \ fi` -PHP_DEPRECATED_DIRECTIVES_REGEX = '^(magic_quotes_(gpc|runtime|sybase)?|(zend_)?extension(_debug)?(_ts)?)[\t\ ]*=' +PHP_DEPRECATED_DIRECTIVES_REGEX = '^[\t\ ]*(magic_quotes_(gpc|runtime|sybase)?|(zend_)?extension(_debug)?(_ts)?)[\t\ ]*=' test: all @if test ! -z "$(PHP_EXECUTABLE)" && test -x "$(PHP_EXECUTABLE)"; then \ diff --git a/configure.ac b/configure.ac index e96c12486fa2c..f494346fc4347 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.3.24-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.3.25],[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/calendar/calendar.c b/ext/calendar/calendar.c index 6da7e69529e2e..b387b20c09b18 100644 --- a/ext/calendar/calendar.c +++ b/ext/calendar/calendar.c @@ -194,6 +194,16 @@ PHP_FUNCTION(cal_days_in_month) RETURN_THROWS(); } + if (UNEXPECTED(month <= 0 || month > INT32_MAX - 1)) { + zend_argument_value_error(2, "must be between 1 and %d", INT32_MAX - 1); + RETURN_THROWS(); + } + + if (UNEXPECTED(year > INT32_MAX - 1)) { + zend_argument_value_error(3, "must be less than %d", INT32_MAX - 1); + RETURN_THROWS(); + } + calendar = &cal_conversion_table[cal]; sdn_start = calendar->to_jd(year, month, 1); @@ -239,6 +249,21 @@ PHP_FUNCTION(cal_to_jd) RETURN_THROWS(); } + if (UNEXPECTED(month <= 0 || month > INT32_MAX - 1)) { + zend_argument_value_error(2, "must be between 1 and %d", INT32_MAX - 1); + RETURN_THROWS(); + } + + if (UNEXPECTED(ZEND_LONG_EXCEEDS_INT(day))) { + zend_argument_value_error(3, "must be between %d and %d", INT32_MIN, INT32_MAX); + RETURN_THROWS(); + } + + if (UNEXPECTED(year > INT32_MAX - 1)) { + zend_argument_value_error(4, "must be less than %d", INT32_MAX - 1); + RETURN_THROWS(); + } + RETURN_LONG(cal_conversion_table[cal].to_jd(year, month, day)); } /* }}} */ diff --git a/ext/calendar/tests/cal_days_in_month_error1.phpt b/ext/calendar/tests/cal_days_in_month_error1.phpt index f334888479f20..e110c13cc2a78 100644 --- a/ext/calendar/tests/cal_days_in_month_error1.phpt +++ b/ext/calendar/tests/cal_days_in_month_error1.phpt @@ -12,7 +12,7 @@ try { echo "{$ex->getMessage()}\n"; } try{ - cal_days_in_month(CAL_GREGORIAN,0, 2009); + cal_days_in_month(CAL_GREGORIAN,20, 2009); } catch (ValueError $ex) { echo "{$ex->getMessage()}\n"; } diff --git a/ext/calendar/tests/gh19371.phpt b/ext/calendar/tests/gh19371.phpt new file mode 100644 index 0000000000000..1d807a9838867 --- /dev/null +++ b/ext/calendar/tests/gh19371.phpt @@ -0,0 +1,61 @@ +--TEST-- +GH-19371 (integer overflow in calendar.c) +--SKIPIF-- + +--EXTENSIONS-- +calendar +--FILE-- +getMessage(), "\n"; +} +try { + echo cal_days_in_month(CAL_GREGORIAN, PHP_INT_MIN, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + echo cal_days_in_month(CAL_GREGORIAN, PHP_INT_MAX, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +try { + echo cal_to_jd(CAL_GREGORIAN, PHP_INT_MIN, 1, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + echo cal_to_jd(CAL_GREGORIAN, PHP_INT_MAX, 1, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + echo cal_to_jd(CAL_GREGORIAN, 1, PHP_INT_MIN, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + echo cal_to_jd(CAL_GREGORIAN, 1, PHP_INT_MAX, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + echo cal_to_jd(CAL_GREGORIAN, 1, 1, PHP_INT_MAX); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +cal_days_in_month(): Argument #3 ($year) must be less than 2147483646 +cal_days_in_month(): Argument #2 ($month) must be between 1 and 2147483646 +cal_days_in_month(): Argument #2 ($month) must be between 1 and 2147483646 +cal_to_jd(): Argument #2 ($month) must be between 1 and 2147483646 +cal_to_jd(): Argument #2 ($month) must be between 1 and 2147483646 +cal_to_jd(): Argument #3 ($day) must be between -2147483648 and 2147483647 +cal_to_jd(): Argument #3 ($day) must be between -2147483648 and 2147483647 +cal_to_jd(): Argument #4 ($year) must be less than 2147483646 diff --git a/ext/ftp/ftp.c b/ext/ftp/ftp.c index acdc1522e58fc..3f9a78a81f0f4 100644 --- a/ext/ftp/ftp.c +++ b/ext/ftp/ftp.c @@ -1471,7 +1471,8 @@ static int my_poll(php_socket_t fd, int events, int timeout) { if (n == -1 && php_socket_errno() == EINTR) { zend_hrtime_t delta_ns = zend_hrtime() - start_ns; - if (delta_ns > timeout_hr) { + /* delta_ns == 0 is only possible with a platform that does not support a high-res timer. */ + if (delta_ns > timeout_hr || UNEXPECTED(delta_ns == 0)) { #ifndef PHP_WIN32 errno = ETIMEDOUT; #endif diff --git a/ext/gd/gd.c b/ext/gd/gd.c index 6b727a211189a..cce2a5ca42fc9 100644 --- a/ext/gd/gd.c +++ b/ext/gd/gd.c @@ -1566,7 +1566,7 @@ static void _php_image_create_from(INTERNAL_FUNCTION_PARAMETERS, int image_type, pefree(pstr, 1); zend_string_release_ex(buff, 0); } - else if (php_stream_can_cast(stream, PHP_STREAM_AS_STDIO)) { + else if (php_stream_can_cast(stream, PHP_STREAM_AS_STDIO) == SUCCESS) { /* try and force the stream to be FILE* */ if (FAILURE == php_stream_cast(stream, PHP_STREAM_AS_STDIO | PHP_STREAM_CAST_TRY_HARD, (void **) &fp, REPORT_ERRORS)) { goto out_err; diff --git a/ext/hash/hash.c b/ext/hash/hash.c index 19d72ed7699b1..58b68fc63b9b7 100644 --- a/ext/hash/hash.c +++ b/ext/hash/hash.c @@ -850,8 +850,6 @@ PHP_FUNCTION(hash_copy) RETVAL_OBJ(Z_OBJ_HANDLER_P(zhash, clone_obj)(Z_OBJ_P(zhash))); if (php_hashcontext_from_object(Z_OBJ_P(return_value))->context == NULL) { - zval_ptr_dtor(return_value); - zend_throw_error(NULL, "Cannot copy hash"); RETURN_THROWS(); } diff --git a/ext/hash/php_hash_xxhash.h b/ext/hash/php_hash_xxhash.h index a1e8840ce272a..ace70deedb008 100644 --- a/ext/hash/php_hash_xxhash.h +++ b/ext/hash/php_hash_xxhash.h @@ -18,7 +18,7 @@ #define PHP_HASH_XXHASH_H #define XXH_INLINE_ALL 1 -#include "xxhash.h" +#include "xxhash/xxhash.h" typedef struct { XXH32_state_t s; diff --git a/ext/intl/collator/collator_sort.c b/ext/intl/collator/collator_sort.c index 0634e68fc7a36..b610a8aa6c789 100644 --- a/ext/intl/collator/collator_sort.c +++ b/ext/intl/collator/collator_sort.c @@ -556,6 +556,7 @@ PHP_FUNCTION( collator_get_sort_key ) key_len = ucol_getSortKey(co->ucoll, ustr, ustr_len, (uint8_t*)ZSTR_VAL(key_str), key_len); efree( ustr ); if(!key_len) { + zend_string_efree(key_str); RETURN_FALSE; } ZSTR_LEN(key_str) = key_len - 1; diff --git a/ext/intl/msgformat/msgformat_parse.c b/ext/intl/msgformat/msgformat_parse.c index baa6dfcce74b0..31441c92373da 100644 --- a/ext/intl/msgformat/msgformat_parse.c +++ b/ext/intl/msgformat/msgformat_parse.c @@ -127,10 +127,11 @@ PHP_FUNCTION( msgfmt_parse_message ) if(spattern && spattern_len) { efree(spattern); } - INTL_METHOD_CHECK_STATUS(mfo, "Creating message formatter failed"); + INTL_METHOD_CHECK_STATUS_OR_GOTO(mfo, "Creating message formatter failed", clean); msgfmt_do_parse(mfo, source, src_len, return_value); +clean: /* drop the temporary formatter */ msgformat_data_free(&mfo->mf_data); } diff --git a/ext/intl/resourcebundle/resourcebundle_class.c b/ext/intl/resourcebundle/resourcebundle_class.c index 9e86b41e761a2..8a2ff19d60efc 100644 --- a/ext/intl/resourcebundle/resourcebundle_class.c +++ b/ext/intl/resourcebundle/resourcebundle_class.c @@ -257,7 +257,7 @@ static zend_result resourcebundle_array_count(zend_object *object, zend_long *co if (rb->me == NULL) { intl_errors_set(&rb->error, U_ILLEGAL_ARGUMENT_ERROR, "Found unconstructed ResourceBundle", 0); - return 0; + return FAILURE; } *count = ures_getSize( rb->me ); diff --git a/ext/intl/tests/gh19261.phpt b/ext/intl/tests/gh19261.phpt new file mode 100644 index 0000000000000..3f281919a9562 --- /dev/null +++ b/ext/intl/tests/gh19261.phpt @@ -0,0 +1,28 @@ +--TEST-- +MessageFormatter::parseMessage() with invalid locale +--EXTENSIONS-- +intl +--CREDITS-- +girgias@php.net +--FILE-- + +--EXPECT-- +bool(false) +string(59) "Creating message formatter failed: U_ILLEGAL_ARGUMENT_ERROR" +bool(false) +string(59) "Creating message formatter failed: U_ILLEGAL_ARGUMENT_ERROR" diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c index 769e6caa277b4..0b0a0c21df4ad 100644 --- a/ext/ldap/ldap.c +++ b/ext/ldap/ldap.c @@ -3732,7 +3732,8 @@ PHP_FUNCTION(ldap_rename_ext) */ static int _php_ldap_tls_newctx(LDAP *ld) { - int val = 0, i, opts[] = { + int val = 0, i; + int str_opts[] = { #if (LDAP_API_VERSION > 2000) LDAP_OPT_X_TLS_CACERTDIR, LDAP_OPT_X_TLS_CACERTFILE, @@ -3752,21 +3753,42 @@ static int _php_ldap_tls_newctx(LDAP *ld) #endif 0}; - for (i=0 ; opts[i] ; i++) { + for (i=0 ; str_opts[i] ; i++) { char *path = NULL; - ldap_get_option(ld, opts[i], &path); + ldap_get_option(ld, str_opts[i], &path); if (path) { /* already set locally */ ldap_memfree(path); } else { - ldap_get_option(NULL, opts[i], &path); + ldap_get_option(NULL, str_opts[i], &path); if (path) { /* set globally, inherit */ - ldap_set_option(ld, opts[i], path); + ldap_set_option(ld, str_opts[i], path); ldap_memfree(path); } } } +#ifdef LDAP_OPT_X_TLS_PROTOCOL_MIN + int int_opts[] = { + LDAP_OPT_X_TLS_PROTOCOL_MIN, +#ifdef LDAP_OPT_X_TLS_PROTOCOL_MAX + LDAP_OPT_X_TLS_PROTOCOL_MAX, +#endif + 0 + }; + for (i=0 ; int_opts[i] ; i++) { + int value = 0; + + ldap_get_option(ld, int_opts[i], &value); + if (value <= 0) { /* if value is not set already */ + ldap_get_option(NULL, int_opts[i], &value); + if (value > 0) { /* set globally, inherit */ + ldap_set_option(ld, int_opts[i], &value); + } + } + } +#endif + return ldap_set_option(ld, LDAP_OPT_X_TLS_NEWCTX, &val); } diff --git a/ext/ldap/tests/ldap_start_tls_rc_max_version.conf b/ext/ldap/tests/ldap_start_tls_rc_max_version.conf new file mode 100644 index 0000000000000..0cd03f8b8e191 --- /dev/null +++ b/ext/ldap/tests/ldap_start_tls_rc_max_version.conf @@ -0,0 +1 @@ +TLS_PROTOCOL_MAX 3.2 \ No newline at end of file diff --git a/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt b/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt new file mode 100644 index 0000000000000..e983b97c4b4ea --- /dev/null +++ b/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt @@ -0,0 +1,45 @@ +--TEST-- +ldap_start_tls() - Basic ldap_start_tls test +--EXTENSIONS-- +ldap +--ENV-- +LDAPCONF={PWD}/ldap_start_tls_rc_max_version.conf +--SKIPIF-- + "OpenLDAP", + "min_version" => 20600, +]; +require_once __DIR__ .'/skipifbindfailure.inc'; +?> +--FILE-- + +--EXPECT-- +bool(false) +bool(false) +bool(false) diff --git a/ext/ldap/tests/skipifbindfailure.inc b/ext/ldap/tests/skipifbindfailure.inc index 1a0d0c6d1998b..81c7998cfbb59 100644 --- a/ext/ldap/tests/skipifbindfailure.inc +++ b/ext/ldap/tests/skipifbindfailure.inc @@ -10,4 +10,37 @@ if ($skip_on_bind_failure) { ldap_unbind($link); } + +if (isset($require_vendor)) { + ob_start(); + phpinfo(INFO_MODULES); + $phpinfo = ob_get_clean(); + + // Extract the LDAP section specifically + if (preg_match('/^ldap\s*$(.*?)^[a-z_]+\s*$/ims', $phpinfo, $ldap_section_match)) { + $ldap_section = $ldap_section_match[1]; + + // Extract vendor info from the LDAP section only + if (preg_match('/Vendor Name\s*=>\s*(.+)/i', $ldap_section, $name_match) && + preg_match('/Vendor Version\s*=>\s*(\d+)/i', $ldap_section, $version_match)) { + + $vendor_name = trim($name_match[1]); + $vendor_version = (int)$version_match[1]; + + // Check vendor name if specified + if (isset($require_vendor['name']) && $vendor_name !== $require_vendor['name']) { + die("skip Requires {$require_vendor['name']} (detected: $vendor_name)"); + } + + // Check minimum version if specified + if (isset($require_vendor['min_version']) && $vendor_version < $require_vendor['min_version']) { + die("skip Requires minimum version {$require_vendor['min_version']} (detected: $vendor_version)"); + } + } else { + die("skip Cannot determine LDAP vendor information"); + } + } else { + die("skip LDAP extension information not found"); + } +} ?> diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c index 5c903d2c9a228..eecbca4ed8908 100644 --- a/ext/libxml/libxml.c +++ b/ext/libxml/libxml.c @@ -331,13 +331,26 @@ PHP_LIBXML_API void php_libxml_node_free_list(xmlNodePtr node) if (curnode->type == XML_ELEMENT_NODE) { /* This ensures that namespace references in this subtree are defined within this subtree, * otherwise a use-after-free would be possible when the original namespace holder gets freed. */ -#if 0 - xmlDOMWrapCtxt dummy_ctxt = {0}; - xmlDOMWrapReconcileNamespaces(&dummy_ctxt, curnode, /* options */ 0); -#else - /* See php_dom.c */ - xmlReconciliateNs(curnode->doc, curnode); -#endif + if (LIBXML_VERSION < 21300 && UNEXPECTED(curnode->doc == NULL)) { + /* xmlReconciliateNs() in these versions just uses the document for xmlNewReconciledNs(), + * which can create an oldNs xml namespace declaration via xmlSearchNs() -> xmlTreeEnsureXMLDecl(). */ + xmlDoc dummy; + memset(&dummy, 0, sizeof(dummy)); + dummy.type = XML_DOCUMENT_NODE; + curnode->doc = &dummy; + xmlReconciliateNs(curnode->doc, curnode); + curnode->doc = NULL; + + /* Append oldNs to current node's nsDef, which can be at most one node. */ + if (dummy.oldNs) { + ZEND_ASSERT(dummy.oldNs->next == NULL); + xmlNsPtr old = curnode->nsDef; + curnode->nsDef = dummy.oldNs; + dummy.oldNs->next = old; + } + } else { + xmlReconciliateNs(curnode->doc, curnode); + } } /* Skip freeing */ curnode = next; diff --git a/ext/mbstring/mbstring.c b/ext/mbstring/mbstring.c index dec565707fa78..1d5c27a2a3815 100644 --- a/ext/mbstring/mbstring.c +++ b/ext/mbstring/mbstring.c @@ -1165,8 +1165,8 @@ PHP_RSHUTDOWN_FUNCTION(mbstring) MBSTRG(outconv_state) = 0; if (MBSTRG(all_encodings_list)) { - GC_DELREF(MBSTRG(all_encodings_list)); - zend_array_destroy(MBSTRG(all_encodings_list)); + /* must be *array* release to remove from GC root buffer and free the hashtable itself */ + zend_array_release(MBSTRG(all_encodings_list)); MBSTRG(all_encodings_list) = NULL; } diff --git a/ext/mbstring/tests/gh19397.phpt b/ext/mbstring/tests/gh19397.phpt new file mode 100644 index 0000000000000..e6e07b161c089 --- /dev/null +++ b/ext/mbstring/tests/gh19397.phpt @@ -0,0 +1,11 @@ +--TEST-- +GH-19397 (mb_list_encodings() can cause crashes on shutdown) +--EXTENSIONS-- +mbstring +--FILE-- + 0); +?> +--EXPECT-- +bool(true) diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 7caa3387016e7..147d0c0b112fb 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -5101,6 +5101,14 @@ ZEND_EXT_API void zend_jit_shutdown(void) #else zend_jit_trace_free_caches(&jit_globals); #endif + + /* Reset global pointers to prevent use-after-free in `zend_jit_status()` + * after gracefully restarting Apache with mod_php, see: + * https://github.com/php/php-src/pull/19212 */ + dasm_ptr = NULL; + dasm_buf = NULL; + dasm_end = NULL; + dasm_size = 0; } static void zend_jit_reset_counters(void) diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 718f946ad176d..256d152158496 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -1095,7 +1095,7 @@ static int php_openssl_load_rand_file(const char * file, int *egdsocket, int *se return SUCCESS; #endif } - if (file == NULL || !RAND_load_file(file, -1)) { + if (file == NULL || RAND_load_file(file, -1) < 0) { if (RAND_status() == 0) { php_openssl_store_errors(); php_error_docref(NULL, E_WARNING, "Unable to load random state; not enough random data!"); @@ -1122,7 +1122,7 @@ static int php_openssl_write_rand_file(const char * file, int egdsocket, int see file = RAND_file_name(buffer, sizeof(buffer)); } PHP_OPENSSL_RAND_ADD_TIME(); - if (file == NULL || !RAND_write_file(file)) { + if (file == NULL || RAND_write_file(file) < 0) { php_openssl_store_errors(); php_error_docref(NULL, E_WARNING, "Unable to write random state"); return FAILURE; @@ -5200,12 +5200,20 @@ PHP_FUNCTION(openssl_pkey_get_details) } /* }}} */ -static zend_string *php_openssl_pkey_derive(EVP_PKEY *key, EVP_PKEY *peer_key, size_t key_size) { +static zend_string *php_openssl_pkey_derive(EVP_PKEY *key, EVP_PKEY *peer_key, size_t requested_key_size) { + size_t key_size = requested_key_size; EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(key, NULL); if (!ctx) { return NULL; } +#if OPENSSL_VERSION_NUMBER < 0x30000000L + /* OpenSSL 1.1 does not respect key_size for DH, so force size discovery so it can be compared later. */ + if (EVP_PKEY_base_id(key) == EVP_PKEY_DH && key_size != 0) { + key_size = 0; + } +#endif + if (EVP_PKEY_derive_init(ctx) <= 0 || EVP_PKEY_derive_set_peer(ctx, peer_key) <= 0 || (key_size == 0 && EVP_PKEY_derive(ctx, NULL, &key_size) <= 0)) { @@ -5214,6 +5222,14 @@ static zend_string *php_openssl_pkey_derive(EVP_PKEY *key, EVP_PKEY *peer_key, s return NULL; } +#if OPENSSL_VERSION_NUMBER < 0x30000000L + /* Now compare the computed size for DH to mirror OpenSSL 3.0+ behavior. */ + if (EVP_PKEY_base_id(key) == EVP_PKEY_DH && requested_key_size > 0 && requested_key_size < key_size) { + EVP_PKEY_CTX_free(ctx); + return NULL; + } +#endif + zend_string *result = zend_string_alloc(key_size, 0); if (EVP_PKEY_derive(ctx, (unsigned char *)ZSTR_VAL(result), &key_size) <= 0) { php_openssl_store_errors(); @@ -7475,7 +7491,7 @@ static int php_openssl_validate_iv(const char **piv, size_t *piv_len, size_t iv_ char *iv_new; if (mode->is_aead) { - if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_ivlen_flag, *piv_len, NULL) != 1) { + if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_ivlen_flag, *piv_len, NULL) <= 0) { php_error_docref(NULL, E_WARNING, "Setting of IV length for AEAD mode failed"); return FAILURE; } @@ -7547,7 +7563,7 @@ static int php_openssl_cipher_init(const EVP_CIPHER *cipher_type, return FAILURE; } if (mode->set_tag_length_always || (enc && mode->set_tag_length_when_encrypting)) { - if (!EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, NULL)) { + if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, NULL) <= 0) { php_error_docref(NULL, E_WARNING, "Setting tag length for AEAD cipher failed"); return FAILURE; } @@ -7555,7 +7571,7 @@ static int php_openssl_cipher_init(const EVP_CIPHER *cipher_type, if (!enc && tag && tag_len > 0) { if (!mode->is_aead) { php_error_docref(NULL, E_WARNING, "The tag cannot be used because the cipher algorithm does not support AEAD"); - } else if (!EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, (unsigned char *) tag)) { + } else if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, (unsigned char *) tag) <= 0) { php_error_docref(NULL, E_WARNING, "Setting tag for AEAD cipher decryption failed"); return FAILURE; } @@ -7693,7 +7709,7 @@ PHP_OPENSSL_API zend_string* php_openssl_encrypt( if (mode.is_aead && tag) { zend_string *tag_str = zend_string_alloc(tag_len, 0); - if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode.aead_get_tag_flag, tag_len, ZSTR_VAL(tag_str)) == 1) { + if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode.aead_get_tag_flag, tag_len, ZSTR_VAL(tag_str)) > 0) { ZSTR_VAL(tag_str)[tag_len] = '\0'; ZSTR_LEN(tag_str) = tag_len; ZEND_TRY_ASSIGN_REF_NEW_STR(tag, tag_str); diff --git a/ext/openssl/tests/gh19428.phpt b/ext/openssl/tests/gh19428.phpt new file mode 100644 index 0000000000000..5d290f32e62a5 --- /dev/null +++ b/ext/openssl/tests/gh19428.phpt @@ -0,0 +1,44 @@ +--TEST-- +GH-19428: openssl_pkey_derive() DH with low key_length +--EXTENSIONS-- +openssl +--FILE-- + +--EXPECT-- +bool(false) diff --git a/ext/pdo_pgsql/pgsql_driver.c b/ext/pdo_pgsql/pgsql_driver.c index 1cccfd2ab07ae..e9a6efe14fae3 100644 --- a/ext/pdo_pgsql/pgsql_driver.c +++ b/ext/pdo_pgsql/pgsql_driver.c @@ -38,8 +38,14 @@ static bool pgsql_handle_in_transaction(pdo_dbh_t *dbh); static char * _pdo_pgsql_trim_message(const char *message, int persistent) { - size_t i = strlen(message)-1; + size_t i = strlen(message); char *tmp; + if (UNEXPECTED(i == 0)) { + tmp = pemalloc(1, persistent); + tmp[0] = '\0'; + return tmp; + } + --i; if (i>1 && (message[i-1] == '\r' || message[i-1] == '\n') && message[i] == '.') { --i; diff --git a/ext/readline/config.m4 b/ext/readline/config.m4 index 209f92b6e0a76..873b116fcffb6 100644 --- a/ext/readline/config.m4 +++ b/ext/readline/config.m4 @@ -47,13 +47,6 @@ if test "$PHP_READLINE" && test "$PHP_READLINE" != "no"; then -L$READLINE_DIR/$PHP_LIBDIR $PHP_READLINE_LIBS ]) - PHP_CHECK_LIBRARY(readline, rl_pending_input, - [], [ - AC_MSG_ERROR([invalid readline installation detected. Try --with-libedit instead.]) - ], [ - -L$READLINE_DIR/$PHP_LIBDIR $PHP_READLINE_LIBS - ]) - PHP_CHECK_LIBRARY(readline, rl_callback_read_char, [ AC_DEFINE(HAVE_RL_CALLBACK_READ_CHAR, 1, [ ]) @@ -75,6 +68,26 @@ if test "$PHP_READLINE" && test "$PHP_READLINE" != "no"; then -L$READLINE_DIR/$PHP_LIBDIR $PHP_READLINE_LIBS ]) + CFLAGS_SAVE=$CFLAGS + LDFLAGS_SAVE=$LDFLAGS + LIBS_SAVE=$LIBS + CFLAGS="$CFLAGS $INCLUDES" + LDFLAGS="$LDFLAGS -L$READLINE_DIR/$PHP_LIBDIR" + LIBS="$LIBS -lreadline" + + dnl Sanity and minimum version check if readline library has variable + dnl rl_pending_input. + AC_CHECK_DECL([rl_pending_input],, [AC_MSG_FAILURE([ + Invalid readline installation detected. Try --with-libedit instead. + ])], [ + #include + #include + ]) + + CFLAGS=$CFLAGS_SAVE + LDFLAGS=$LDFLAGS_SAVE + LIBS=$LIBS_SAVE + AC_DEFINE(HAVE_HISTORY_LIST, 1, [ ]) AC_DEFINE(HAVE_LIBREADLINE, 1, [ ]) diff --git a/ext/soap/php_encoding.c b/ext/soap/php_encoding.c index 27b2c4e652b96..0b4b553d87540 100644 --- a/ext/soap/php_encoding.c +++ b/ext/soap/php_encoding.c @@ -1936,6 +1936,11 @@ static xmlNodePtr to_xml_object(encodeTypePtr type, zval *data, int style, xmlNo sdlAttributePtr attr; zval *zattr, rv; + /* Attributes can't refer to other attributes as there's nothing to attach the href to. */ + HashTable **ref_map = &SOAP_GLOBAL(ref_map); + HashTable *old_ref_map = *ref_map; + *ref_map = NULL; + ZEND_HASH_FOREACH_PTR(sdlType->attributes, attr) { if (attr->name) { zattr = get_zval_property(data, attr->name, &rv); @@ -1965,6 +1970,8 @@ static xmlNodePtr to_xml_object(encodeTypePtr type, zval *data, int style, xmlNo } } } ZEND_HASH_FOREACH_END(); + + *ref_map = old_ref_map; } } if (style == SOAP_ENCODED) { @@ -3034,6 +3041,12 @@ static xmlNodePtr to_xml_list(encodeTypePtr enc, zval *data, int style, xmlNodeP ret = xmlNewNode(NULL, BAD_CAST("BOGUS")); xmlAddChild(parent, ret); FIND_ZVAL_NULL(data, ret, style); + + /* Literals are unique and can't refer to other references via attributes. */ + HashTable **ref_map = &SOAP_GLOBAL(ref_map); + HashTable *old_ref_map = *ref_map; + *ref_map = NULL; + if (Z_TYPE_P(data) == IS_ARRAY) { zval *tmp; smart_str list = {0}; @@ -3108,6 +3121,7 @@ static xmlNodePtr to_xml_list(encodeTypePtr enc, zval *data, int style, xmlNodeP zval_ptr_dtor_str(&tmp); } } + *ref_map = old_ref_map; return ret; } diff --git a/ext/soap/tests/bugs/gh18640.phpt b/ext/soap/tests/bugs/gh18640.phpt new file mode 100644 index 0000000000000..493659eca30c7 --- /dev/null +++ b/ext/soap/tests/bugs/gh18640.phpt @@ -0,0 +1,42 @@ +--TEST--- +GH-18640 (heap-use-after-free ext/soap/php_encoding.c:299:32 in soap_check_zval_ref) +--EXTENSIONS-- +soap +--CREDITS-- +YuanchengJiang +--FILE-- + 1, 'classmap' => ['logOnEvent' => 'LogOnEvent', 'events' => 'IVREvents']]); +$timestamp = new LogOnEvent(); // Bogus! +$logOffEvents[] = new LogOffEvent($timestamp); +$logOffEvents[] = new LogOffEvent($timestamp); +$ivrEvents = new IVREvents($logOffEvents); +$result = $soapClient->PostEvents($ivrEvents); + +class LogOffEvent { + function __construct(public $timestamp) { + $this->timestamp = $timestamp; + } +} + +class LogOnEvent { +} + +class IVREvents { + function __construct(public $logOffEvent) { + } +} +?> +--EXPECT-- +string(359) " + +" diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index c252dc6e07a41..a0d5e52458f32 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -2511,7 +2511,6 @@ PHP_FUNCTION(socket_addrinfo_bind) } default: close(php_sock->bsd_socket); - zval_ptr_dtor(return_value); zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET, or AF_INET6"); RETURN_THROWS(); } @@ -2575,7 +2574,6 @@ PHP_FUNCTION(socket_addrinfo_connect) default: zend_argument_value_error(1, "socket type must be one of AF_UNIX, AF_INET, or AF_INET6"); close(php_sock->bsd_socket); - zval_ptr_dtor(return_value); RETURN_THROWS(); } diff --git a/ext/spl/tests/gh14639.phpt b/ext/spl/tests/gh14639.phpt index 1b6f621d27bd3..db85b2b044efa 100644 --- a/ext/spl/tests/gh14639.phpt +++ b/ext/spl/tests/gh14639.phpt @@ -7,6 +7,9 @@ memory_limit=2M if (getenv("USE_ZEND_ALLOC") === "0") { die("skip Zend MM disabled"); } +if (getenv("GITHUB_ACTIONS") && substr(PHP_OS, 0, 3) == "WIN") { + die("skip Segfaults in GitHub actions on Windows"); +} ?> --FILE-- 0) { - ARRAYG(multisort_func)[num_arrays - 1] = php_get_data_compare_func_unstable(sort_type, sort_order != PHP_SORT_ASC); + func[num_arrays - 1] = php_get_data_compare_func_unstable(sort_type, sort_order != PHP_SORT_ASC); sort_order = PHP_SORT_ASC; sort_type = PHP_SORT_REGULAR; } @@ -5979,8 +5979,6 @@ PHP_FUNCTION(array_multisort) MULTISORT_ABORT; } } - /* Take care of the last array sort flags. */ - ARRAYG(multisort_func)[num_arrays - 1] = php_get_data_compare_func_unstable(sort_type, sort_order != PHP_SORT_ASC); /* Make sure the arrays are of the same size. */ array_size = zend_hash_num_elements(Z_ARRVAL_P(arrays[0])); @@ -5998,6 +5996,11 @@ PHP_FUNCTION(array_multisort) RETURN_TRUE; } + /* Take care of the last array sort flags. */ + func[num_arrays - 1] = php_get_data_compare_func_unstable(sort_type, sort_order != PHP_SORT_ASC); + bucket_compare_func_t *old_multisort_func = ARRAYG(multisort_func); + ARRAYG(multisort_func) = func; + /* Create the indirection array. This array is of size MxN, where * M is the number of entries in each input array and N is the number * of the input arrays + 1. The last column is UNDEF to indicate the end @@ -6074,6 +6077,7 @@ PHP_FUNCTION(array_multisort) efree(indirect); efree(func); efree(arrays); + ARRAYG(multisort_func) = old_multisort_func; } /* }}} */ diff --git a/ext/standard/hrtime.c b/ext/standard/hrtime.c index 6af8bfc965069..831e903e1cc38 100644 --- a/ext/standard/hrtime.c +++ b/ext/standard/hrtime.c @@ -46,7 +46,6 @@ delivered timestamp is monotonic and cannot be adjusted. */ PHP_FUNCTION(hrtime) { -#if ZEND_HRTIME_AVAILABLE bool get_as_num = 0; zend_hrtime_t t = zend_hrtime(); @@ -55,6 +54,7 @@ PHP_FUNCTION(hrtime) Z_PARAM_BOOL(get_as_num) ZEND_PARSE_PARAMETERS_END(); +#if ZEND_HRTIME_AVAILABLE if (UNEXPECTED(get_as_num)) { PHP_RETURN_HRTIME(t); } else { diff --git a/ext/standard/tests/array/gh19300_1.phpt b/ext/standard/tests/array/gh19300_1.phpt new file mode 100644 index 0000000000000..18b639bf425b8 --- /dev/null +++ b/ext/standard/tests/array/gh19300_1.phpt @@ -0,0 +1,40 @@ +--TEST-- +GH-19300 (Nested array_multisort invocation with error breaks) - correct invocation variation +--FILE-- +data; + } +} + +$inputs = [ + new MyStringable('3'), + new MyStringable('1'), + new MyStringable('2'), +]; + +var_dump(array_multisort($inputs, SORT_STRING)); +var_dump($inputs); +?> +--EXPECT-- +bool(true) +array(3) { + [0]=> + object(MyStringable)#2 (1) { + ["data":"MyStringable":private]=> + string(1) "1" + } + [1]=> + object(MyStringable)#3 (1) { + ["data":"MyStringable":private]=> + string(1) "2" + } + [2]=> + object(MyStringable)#1 (1) { + ["data":"MyStringable":private]=> + string(1) "3" + } +} diff --git a/ext/standard/tests/array/gh19300_2.phpt b/ext/standard/tests/array/gh19300_2.phpt new file mode 100644 index 0000000000000..41ae7e82bb796 --- /dev/null +++ b/ext/standard/tests/array/gh19300_2.phpt @@ -0,0 +1,40 @@ +--TEST-- +GH-19300 (Nested array_multisort invocation with error breaks) - error variation +--FILE-- +getMessage(), "\n"; + } +} +set_error_handler('error_handle'); + +$inputs = [ + new stdClass, + new stdClass, + new stdClass, +]; + +var_dump(array_multisort($inputs, SORT_NUMERIC)); +var_dump($inputs); +?> +--EXPECT-- +array_multisort(): Argument #1 ($array) must be an array or a sort flag +array_multisort(): Argument #1 ($array) must be an array or a sort flag +array_multisort(): Argument #1 ($array) must be an array or a sort flag +array_multisort(): Argument #1 ($array) must be an array or a sort flag +bool(true) +array(3) { + [0]=> + object(stdClass)#1 (0) { + } + [1]=> + object(stdClass)#2 (0) { + } + [2]=> + object(stdClass)#3 (0) { + } +} diff --git a/ext/standard/tests/file/chmod_variation2.phpt b/ext/standard/tests/file/chmod_variation2.phpt index e96af25ec469b..c2b6c2e810a3c 100644 --- a/ext/standard/tests/file/chmod_variation2.phpt +++ b/ext/standard/tests/file/chmod_variation2.phpt @@ -34,7 +34,7 @@ clearstatcache(); printf("%o\n", fileperms($filepath) & PERMISSIONS_MASK); echo "\nchmod() on a linked file\n"; -$linkname = "somelink"; +$linkname = "somelink2"; var_dump(symlink($filepath, $linkname)); var_dump(chmod($filepath, 0777)); var_dump(chmod($linkname, 0755)); diff --git a/ext/standard/tests/file/file_variation5.phpt b/ext/standard/tests/file/file_variation5.phpt index d1c5e1498bebd..3db848cdbbfbd 100644 --- a/ext/standard/tests/file/file_variation5.phpt +++ b/ext/standard/tests/file/file_variation5.phpt @@ -27,7 +27,7 @@ echo "\nfile() on a path containing .. with invalid directories\n"; var_dump(file("./$test_dirname/bad_dir/../../$filename")); echo "\nfile() on a linked file\n"; -$linkname = "somelink"; +$linkname = "somelink5"; var_dump(symlink($filepath, $linkname)); var_dump(file($linkname)); var_dump(unlink($linkname)); diff --git a/ext/standard/tests/serialize/oss_fuzz_433303828.phpt b/ext/standard/tests/serialize/oss_fuzz_433303828.phpt new file mode 100644 index 0000000000000..fb90b51d4dadf --- /dev/null +++ b/ext/standard/tests/serialize/oss_fuzz_433303828.phpt @@ -0,0 +1,13 @@ +--TEST-- +OSS-Fuzz #433303828 +--FILE-- + +--EXPECTF-- +Warning: unserialize(): Error at offset 9 of 10 bytes in %s on line %d + +Warning: unserialize(): Error at offset 10 of 11 bytes in %s on line %d diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index a050fb5f74a70..1d23269ef03d4 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -1310,10 +1310,12 @@ object ":" uiv ":" ["] { YYCURSOR = *p; if (*(YYCURSOR) != ':') { + zend_string_release_ex(class_name, 0); return 0; } if (*(YYCURSOR+1) != '{') { *p = YYCURSOR+1; + zend_string_release_ex(class_name, 0); return 0; } diff --git a/ext/xml/compat.c b/ext/xml/compat.c index a3283f71f7552..3de77d0723e5f 100644 --- a/ext/xml/compat.c +++ b/ext/xml/compat.c @@ -375,7 +375,9 @@ _get_entity(void *user, const xmlChar *name) if (ret == NULL) ret = xmlGetDocEntity(parser->parser->myDoc, name); + ZEND_DIAGNOSTIC_IGNORED_START("-Wdeprecated-declarations") if (ret == NULL || parser->parser->instate == XML_PARSER_CONTENT) { + ZEND_DIAGNOSTIC_IGNORED_END if (ret == NULL || ret->etype == XML_INTERNAL_GENERAL_ENTITY || ret->etype == XML_INTERNAL_PARAMETER_ENTITY || ret->etype == XML_INTERNAL_PREDEFINED_ENTITY) { /* Predefined entities will expand unless no cdata handler is present */ if (parser->h_default && ! (ret && ret->etype == XML_INTERNAL_PREDEFINED_ENTITY && parser->h_cdata)) { diff --git a/ext/xmlreader/tests/gh19098.phpt b/ext/xmlreader/tests/gh19098.phpt new file mode 100644 index 0000000000000..13a6eda328f25 --- /dev/null +++ b/ext/xmlreader/tests/gh19098.phpt @@ -0,0 +1,44 @@ +--TEST-- +GH-19098 (libxml<2.13 segmentation fault caused by php_libxml_node_free) +--EXTENSIONS-- +xmlreader +dom +--FILE-- + + + + +'); + +$success = $xml_reader->next("sparql"); + +$success = $xml_reader->read(); +$success = $xml_reader->next("results"); + +while ($xml_reader->read()) { + if ($xml_reader->next("result")) { + $result_as_dom_node = $xml_reader->expand(); + $child = $result_as_dom_node->firstChild; + unset($result_as_dom_node); + var_dump($child->namespaceURI); + foreach ($child->attributes as $attr) { + var_dump($attr->namespaceURI); + } + $doc = new DOMDocument; + $doc->adoptNode($child); + echo $doc->saveXML($child), "\n"; + unset($child); + break; + } +} + +?> +--EXPECT-- +string(38) "http://www.w3.org/2005/sparql-results#" +string(36) "http://www.w3.org/XML/1998/namespace" +string(10) "urn:custom" +NULL + diff --git a/main/php_open_temporary_file.c b/main/php_open_temporary_file.c index dcea78358486d..608f3ecf73bae 100644 --- a/main/php_open_temporary_file.c +++ b/main/php_open_temporary_file.c @@ -157,6 +157,7 @@ static int php_do_open_temporary_file(const char *path, const char *pfx, zend_st free(cwdw); free(pfxw); efree(new_state.cwd); + free(opened_path); return -1; } assert(strlen(opened_path) == opened_path_len); diff --git a/main/php_version.h b/main/php_version.h index cf74940556ddc..75ef8a97b2752 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 3 -#define PHP_RELEASE_VERSION 24 -#define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.3.24-dev" -#define PHP_VERSION_ID 80324 +#define PHP_RELEASE_VERSION 25 +#define PHP_EXTRA_VERSION "" +#define PHP_VERSION "8.3.25" +#define PHP_VERSION_ID 80325 diff --git a/run-extra-tests.php b/run-extra-tests.php new file mode 100755 index 0000000000000..a299addf4044c --- /dev/null +++ b/run-extra-tests.php @@ -0,0 +1,123 @@ +#!/usr/bin/env php +os}\n"; + echo "CPU Arch: {$environment->cpuArch}\n"; + echo "ZTS: " . ($environment->zts ? 'Yes' : 'No') . "\n"; + echo "DEBUG: " . ($environment->debug ? 'Yes' : 'No') . "\n"; + echo "=====================================================================\n"; + + echo "No tests in this branch yet.\n"; + + echo "All OK\n"; +} + +function output_group_start(Environment $environment, string $name): void +{ + if ($environment->githubAction) { + printf("::group::%s\n", $name); + } else { + printf("%s\n", $name); + } +} + +function output_group_end(Environment $environment): void +{ + if ($environment->githubAction) { + printf("::endgroup::\n"); + } +} + +/** + * Returns getenv('TEST_PHP_OS') if defined, otherwise returns one of + * 'Windows NT', 'Linux', 'FreeBSD', 'Darwin', ... + */ +function detect_os(): string +{ + $os = (string) getenv('TEST_PHP_OS'); + if ($os !== '') { + return $os; + } + + return php_uname('s'); +} + +/** + * Returns getenv('TEST_PHP_CPU_ARCH') if defined, otherwise returns one of + * 'x86', 'x86_64', 'aarch64', ... + */ +function detect_cpu_arch(): string +{ + $cpu = (string) getenv('TEST_PHP_CPU_ARCH'); + if ($cpu !== '') { + return $cpu; + } + + $cpu = php_uname('m'); + if (strtolower($cpu) === 'amd64') { + $cpu = 'x86_64'; + } else if (in_array($cpu, ['i386', 'i686'])) { + $cpu = 'x86'; + } else if ($cpu === 'arm64') { + $cpu = 'aarch64'; + } + + return $cpu; +} + +main($argc, $argv); diff --git a/sapi/fpm/fpm/events/kqueue.c b/sapi/fpm/fpm/events/kqueue.c index 8fc4a6f049b0d..b81650d7741dc 100644 --- a/sapi/fpm/fpm/events/kqueue.c +++ b/sapi/fpm/fpm/events/kqueue.c @@ -96,7 +96,7 @@ static int fpm_event_kqueue_init(int max) /* {{{ */ /* * release kqueue stuff */ -static int fpm_event_kqueue_clean() /* {{{ */ +static int fpm_event_kqueue_clean(void) /* {{{ */ { if (kevents) { free(kevents);