diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ed1a5105db797..24a656eac19e0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -5,36 +5,53 @@ # the problem area and could aid in deciding whether a pull request is ready # for merging. # +# When changing this file, please make sure to commit the changes to the +# earliest supported PHP branch (PHP-X.Y) and not only to the master branch. +# GitHub reads the CODEOWNERS file from the pull request's targeted branch. +# Commit changes here similar to bug fixes: +# https://github.com/php/php-src/blob/master/CONTRIBUTING.md#pull-requests +# # For more information, see the GitHub CODEOWNERS documentation: # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners /.github @iluuu1994 @TimWolla /build/gen_stub.php @kocsismate -/ext/bcmath @Girgias +/ext/bcmath @Girgias @nielsdos @SakiTakamachi /ext/curl @adoy /ext/date @derickr /ext/dba @Girgias /ext/dom @nielsdos /ext/ffi @dstogov +/ext/gd @devnexen /ext/gettext @devnexen /ext/gmp @Girgias /ext/imap @Girgias /ext/intl @devnexen /ext/json @bukka /ext/libxml @nielsdos -/ext/mbstring @alexdowad +/ext/mbstring @alexdowad @youkidearitai +/ext/mysqlnd @SakiTakamachi /ext/odbc @NattyNarwhal /ext/opcache @dstogov @iluuu1994 /ext/openssl @bukka -/ext/pdo_odbc @NattyNarwhal -/ext/pdo_pgsql @devnexen +/ext/pcntl @devnexen +/ext/pdo @SakiTakamachi +/ext/pdo_dblib @SakiTakamachi +/ext/pdo_firebird @SakiTakamachi +/ext/pdo_mysql @SakiTakamachi +/ext/pdo_odbc @NattyNarwhal @SakiTakamachi +/ext/pdo_pgsql @devnexen @SakiTakamachi +/ext/pdo_sqlite @SakiTakamachi /ext/pgsql @devnexen /ext/random @TimWolla @zeriyoshi /ext/session @Girgias +/ext/simplexml @nielsdos /ext/sockets @devnexen /ext/spl @Girgias /ext/standard @bukka +/ext/xml @nielsdos /ext/xmlreader @nielsdos +/ext/xmlwriter @nielsdos /ext/xsl @nielsdos /main @bukka /sapi/fpm @bukka @@ -44,6 +61,8 @@ /Zend/zend_API.* @dstogov @iluuu1994 /Zend/zend_call_stack.* @arnaud-lb /Zend/zend_closures.* @dstogov +/Zend/zend_compile.* @iluuu1994 +/Zend/zend_enum.* @iluuu1994 /Zend/zend_execute.* @dstogov @iluuu1994 /Zend/zend_execute_API.c @dstogov @iluuu1994 /Zend/zend_gc.* @dstogov @arnaud-lb diff --git a/.github/actions/verify-generated-files/action.yml b/.github/actions/verify-generated-files/action.yml index 139dd84662411..266f4d33c93b1 100644 --- a/.github/actions/verify-generated-files/action.yml +++ b/.github/actions/verify-generated-files/action.yml @@ -12,4 +12,5 @@ runs: ext/tokenizer/tokenizer_data_gen.php build/gen_stub.php -f build/gen_stub.php --generate-optimizer-info - git add . -N && git diff --exit-code + # Use the -a flag for a bug in git 2.46.0, which doesn't consider changed -diff files. + git add . -N && git diff -a --exit-code diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index b3bff2c76171c..67c10a54ba205 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -860,7 +860,7 @@ jobs: PHP_BUILD_CACHE_BASE_DIR: C:\build-cache PHP_BUILD_OBJ_DIR: C:\obj PHP_BUILD_CACHE_SDK_DIR: C:\build-cache\sdk - PHP_BUILD_SDK_BRANCH: php-sdk-2.2.0 + PHP_BUILD_SDK_BRANCH: php-sdk-2.3.0 PHP_BUILD_CRT: vs16 PLATFORM: ${{ matrix.x64 && 'x64' || 'x86' }} THREAD_SAFE: "${{ matrix.zts && '1' || '0' }}" diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 0d10c452b7cb9..3e239b24f7495 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -155,7 +155,7 @@ jobs: MYSQL_ROOT_PASSWORD: root steps: - name: git checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: apt uses: ./.github/actions/apt-x32 - name: ccache @@ -222,7 +222,7 @@ jobs: PHP_BUILD_CACHE_BASE_DIR: C:\build-cache PHP_BUILD_OBJ_DIR: C:\obj PHP_BUILD_CACHE_SDK_DIR: C:\build-cache\sdk - PHP_BUILD_SDK_BRANCH: php-sdk-2.2.0 + PHP_BUILD_SDK_BRANCH: php-sdk-2.3.0 PHP_BUILD_CRT: vs16 PLATFORM: x64 THREAD_SAFE: "1" diff --git a/NEWS b/NEWS index 4c6cbbb8ca310..887ebc6c6ca63 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,77 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.3.10 +29 Aug 2024, PHP 8.3.11 + +- Core: + . Fixed bug GH-15020 (Memory leak in Zend/Optimizer/escape_analysis.c). + (nielsdos) + . Fixed bug GH-15023 (Memory leak in Zend/zend_ini.c). (nielsdos) + . Fixed bug GH-13330 (Append -Wno-implicit-fallthrough flag conditionally). + (Peter Kokot) + . Fix uninitialized memory in network.c. (nielsdos) + . Fixed bug GH-15108 (Segfault when destroying generator during shutdown). + (Arnaud) + . Fixed bug GH-15275 (Crash during GC of suspended generator delegate). + (Arnaud) + +- Curl: + . Fixed case when curl_error returns an empty string. + (David Carlier) + +- DOM: + . Fix UAF when removing doctype and using foreach iteration. (nielsdos) + +- FFI: + . Fixed bug GH-14286 (ffi enum type (when enum has no name) make memory + leak). (nielsdos, dstogov) + +- Hash: + . Fix crash when converting array data for array in shm in xxh3. (nielsdos) + +- Intl: + . Fixed bug GH-15087 (IntlChar::foldCase()'s $option is not optional). (cmb) + +- Opcache: + . Fixed bug GH-13817 (Segmentation fault for enabled observers after pass 4). + (Bob) + . Fixed bug GH-13775 (Memory leak possibly related to opcache SHM placement). + (Arnaud, nielsdos) + +- Output: + . Fixed bug GH-15179 (Segmentation fault (null pointer dereference) in + ext/standard/url_scanner_ex.re). (nielsdos) + +- PDO_Firebird: + . Fix bogus fallthrough path in firebird_handle_get_attribute(). (nielsdos) + +- PHPDBG: + . Fixed bug GH-13199 (EOF emits redundant prompt in phpdbg local console mode + with libedit/readline). (Peter Kokot) + . Fixed bug GH-15268 (heap buffer overflow in phpdbg + (zend_hash_num_elements() Zend/zend_hash.h)). (nielsdos) + . Fixed bug GH-15210 use-after-free on watchpoint allocations. (nielsdos) + +- Soap: + . Fixed bug #55639 (Digest autentication dont work). (nielsdos) + . Fix SoapFault property destruction. (nielsdos) + . Fixed bug GH-15252 (SOAP XML broken since PHP 8.3.9 when using classmap + constructor option). (nielsdos) + +- Standard: + . Fix passing non-finite timeout values in stream functions. (nielsdos) + . Fixed GH-14780 p(f)sockopen timeout overflow. (David Carlier) + +- Streams: + . Fixed bug GH-15028 (Memory leak in ext/phar/stream.c). (nielsdos) + . Fixed bug GH-15034 (Integer overflow on stream_notification_callback + byte_max parameter with files bigger than 2GB). (nielsdos) + . Reverted fix for GH-14930 (Custom stream wrapper dir_readdir output + truncated to 255 characters). (Jakub Zelenka) + +- Tidy: + . Fix memory leaks in ext/tidy basedir restriction code. (nielsdos) + +01 Aug 2024, PHP 8.3.10 - Core: . Fixed bug GH-13922 (Fixed support for systems with @@ -12,6 +83,8 @@ PHP NEWS . Fixed bug GH-14741 (Segmentation fault in Zend/zend_types.h). (nielsdos) . Fixed bug GH-14969 (Use-after-free in property coercion with __toString()). (ilutov) + . Fixed bug GH-14961 (Comment between -> and keyword results in parse error). + (ilutov) - Dom: . Fixed bug GH-14702 (DOMDocument::xinclude() crash). (nielsdos) diff --git a/UPGRADING b/UPGRADING index 519c1eec4150c..39ff20b0bafdd 100644 --- a/UPGRADING +++ b/UPGRADING @@ -45,6 +45,9 @@ PHP 8.3 UPGRADE NOTES be removed during cycle collection if the key is not reachable except by iterating over the WeakMap (reachability via iteration is considered weak). Previously, such entries would never be automatically removed. + . In addition to whitespace characters, now comments are allowed between + `yield` and `from`. The whole "construct" (e.g. `yield /* comment */ from`) + is reported as a single `T_YIELD_FROM` token by the tokenizer. - DOM: . DOMChildNode::after(), DOMChildNode::before(), DOMChildNode::replaceWith() diff --git a/Zend/Optimizer/compact_vars.c b/Zend/Optimizer/compact_vars.c index a8ef8846deca2..9898714a17c5e 100644 --- a/Zend/Optimizer/compact_vars.c +++ b/Zend/Optimizer/compact_vars.c @@ -18,6 +18,7 @@ #include "Optimizer/zend_optimizer_internal.h" #include "zend_bitset.h" +#include "zend_observer.h" /* This pass removes all CVs and temporaries that are completely unused. It does *not* merge any CVs or TMPs. * This pass does not operate on SSA form anymore. */ @@ -117,7 +118,7 @@ void zend_optimizer_compact_vars(zend_op_array *op_array) { op_array->last_var = num_cvs; } - op_array->T = num_tmps; + op_array->T = num_tmps + ZEND_OBSERVER_ENABLED; // reserve last temporary for observers if enabled free_alloca(vars_map, use_heap2); } diff --git a/Zend/Optimizer/escape_analysis.c b/Zend/Optimizer/escape_analysis.c index 193479bae4b74..840a18341a0ff 100644 --- a/Zend/Optimizer/escape_analysis.c +++ b/Zend/Optimizer/escape_analysis.c @@ -414,6 +414,7 @@ zend_result zend_ssa_escape_analysis(const zend_script *script, zend_op_array *o } if (zend_build_equi_escape_sets(ees, op_array, ssa) == FAILURE) { + free_alloca(ees, use_heap); return FAILURE; } diff --git a/Zend/Optimizer/optimize_temp_vars_5.c b/Zend/Optimizer/optimize_temp_vars_5.c index 33d418ffbd99c..de0b189d5dca1 100644 --- a/Zend/Optimizer/optimize_temp_vars_5.c +++ b/Zend/Optimizer/optimize_temp_vars_5.c @@ -26,6 +26,7 @@ #include "zend_execute.h" #include "zend_vm.h" #include "zend_bitset.h" +#include "zend_observer.h" #define INVALID_VAR ((uint32_t)-1) #define GET_AVAILABLE_T() \ @@ -173,5 +174,5 @@ void zend_optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_c } zend_arena_release(&ctx->arena, checkpoint); - op_array->T = max + 1; + op_array->T = max + 1 + ZEND_OBSERVER_ENABLED; // reserve last temporary for observers if enabled } diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c index cf1c98c64bb59..d8ff9e5f0b8f3 100644 --- a/Zend/Optimizer/zend_optimizer.c +++ b/Zend/Optimizer/zend_optimizer.c @@ -31,7 +31,6 @@ #include "zend_inference.h" #include "zend_dump.h" #include "php.h" -#include "zend_observer.h" #ifndef ZEND_OPTIMIZER_MAX_REGISTERED_PASSES # define ZEND_OPTIMIZER_MAX_REGISTERED_PASSES 32 @@ -1102,8 +1101,6 @@ static void zend_revert_pass_two(zend_op_array *op_array) } #endif - op_array->T -= ZEND_OBSERVER_ENABLED; - op_array->fn_flags &= ~ZEND_ACC_DONE_PASS_TWO; } @@ -1133,8 +1130,6 @@ static void zend_redo_pass_two(zend_op_array *op_array) } #endif - op_array->T += ZEND_OBSERVER_ENABLED; // reserve last temporary for observers if enabled - opline = op_array->opcodes; end = opline + op_array->last; while (opline < end) { @@ -1563,12 +1558,6 @@ ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_l } } - if (ZEND_OBSERVER_ENABLED) { - for (i = 0; i < call_graph.op_arrays_count; i++) { - ++call_graph.op_arrays[i]->T; // ensure accurate temporary count for stack size precalculation - } - } - if (ZEND_OPTIMIZER_PASS_12 & optimization_level) { for (i = 0; i < call_graph.op_arrays_count; i++) { zend_adjust_fcall_stack_size_graph(call_graph.op_arrays[i]); @@ -1584,8 +1573,6 @@ ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_l zend_recalc_live_ranges(op_array, needs_live_range); } } else { - op_array->T -= ZEND_OBSERVER_ENABLED; // redo_pass_two will re-increment it - zend_redo_pass_two(op_array); if (op_array->live_range) { zend_recalc_live_ranges(op_array, NULL); diff --git a/Zend/tests/gh14961.phpt b/Zend/tests/gh14961.phpt new file mode 100644 index 0000000000000..f066943c4ec44 --- /dev/null +++ b/Zend/tests/gh14961.phpt @@ -0,0 +1,26 @@ +--TEST-- +GH-14961: Comment between -> and keyword +--FILE-- +/* comment */class = 42; +var_dump($c->/** doc comment */class); +var_dump($c-> + // line comment + class); +var_dump($c-> + # hash comment + class); +var_dump($c?->/* comment */class); + +?> +--EXPECT-- +int(42) +int(42) +int(42) +int(42) diff --git a/Zend/tests/gh15108-001.phpt b/Zend/tests/gh15108-001.phpt new file mode 100644 index 0000000000000..9971ada5b37fb --- /dev/null +++ b/Zend/tests/gh15108-001.phpt @@ -0,0 +1,36 @@ +--TEST-- +GH-15108 001: Segfault with delegated generator in suspended fiber +--FILE-- +current()); + $iterable->next(); + var_dump("not executed"); +}); + +$ref = $fiber; + +$fiber->start(); + +?> +==DONE== +--EXPECT-- +string(3) "foo" +==DONE== diff --git a/Zend/tests/gh15108-002.phpt b/Zend/tests/gh15108-002.phpt new file mode 100644 index 0000000000000..64bdafaebf5ed --- /dev/null +++ b/Zend/tests/gh15108-002.phpt @@ -0,0 +1,40 @@ +--TEST-- +GH-15108 002: Segfault with delegated generator in suspended fiber +--FILE-- +current()); + $iterable->next(); + var_dump("not executed"); +}); + +$ref = $fiber; + +$fiber->start(); + +?> +==DONE== +--EXPECT-- +string(3) "foo" +==DONE== diff --git a/Zend/tests/gh15108-003.phpt b/Zend/tests/gh15108-003.phpt new file mode 100644 index 0000000000000..c32e7b8f0c8d7 --- /dev/null +++ b/Zend/tests/gh15108-003.phpt @@ -0,0 +1,38 @@ +--TEST-- +GH-15108 003: Segfault with delegated generator in suspended fiber +--FILE-- +current()); + $b->next(); + var_dump("not executed"); +}); + +$ref = $fiber; + +$fiber->start(); + +?> +==DONE== +--EXPECT-- +string(3) "foo" +==DONE== diff --git a/Zend/tests/gh15108-004.phpt b/Zend/tests/gh15108-004.phpt new file mode 100644 index 0000000000000..caa548e515d3a --- /dev/null +++ b/Zend/tests/gh15108-004.phpt @@ -0,0 +1,39 @@ +--TEST-- +GH-15108 004: Segfault with delegated generator in suspended fiber +--FILE-- +current()); + var_dump($c->current()); + $b->next(); + var_dump("not executed"); +}); + +$ref = $fiber; + +$fiber->start(); + +?> +==DONE== +--EXPECT-- +string(3) "foo" +string(3) "foo" +==DONE== diff --git a/Zend/tests/gh15108-005.phpt b/Zend/tests/gh15108-005.phpt new file mode 100644 index 0000000000000..76457e62c3f12 --- /dev/null +++ b/Zend/tests/gh15108-005.phpt @@ -0,0 +1,45 @@ +--TEST-- +GH-15108 005: Segfault with delegated generator in suspended fiber +--FILE-- +current()); + +$fiber = new Fiber(function () use ($iterable) { + $iterable->next(); + var_dump("not executed"); +}); + +$ref = $fiber; + +$fiber->start(); + +?> +==DONE== +--EXPECT-- +string(3) "foo" +==DONE== diff --git a/Zend/tests/gh15108-006.phpt b/Zend/tests/gh15108-006.phpt new file mode 100644 index 0000000000000..5fb830d97cb1d --- /dev/null +++ b/Zend/tests/gh15108-006.phpt @@ -0,0 +1,49 @@ +--TEST-- +GH-15108 006: Segfault with delegated generator in suspended fiber +--FILE-- +current()); +var_dump($b->current()); + +$fiber = new Fiber(function () use ($a, $b, $g) { + $a->next(); + var_dump("not executed"); +}); + +$ref = $fiber; + +$fiber->start(); + +?> +==DONE== +--EXPECT-- +string(3) "foo" +string(3) "foo" +==DONE== diff --git a/Zend/tests/gh15108-007.phpt b/Zend/tests/gh15108-007.phpt new file mode 100644 index 0000000000000..d66a8010a83f4 --- /dev/null +++ b/Zend/tests/gh15108-007.phpt @@ -0,0 +1,51 @@ +--TEST-- +GH-15108 007: Segfault with delegated generator in suspended fiber +--FILE-- +current()); +var_dump($b->current()); + +$fiber = new Fiber(function () use ($a, $b, $c, $d, $g) { + $b->next(); + var_dump("not executed"); +}); + +$ref = $fiber; + +$fiber->start(); + +?> +==DONE== +--EXPECT-- +string(3) "foo" +string(3) "foo" +==DONE== diff --git a/Zend/tests/gh15275-001.phpt b/Zend/tests/gh15275-001.phpt new file mode 100644 index 0000000000000..bfea734c6ba9b --- /dev/null +++ b/Zend/tests/gh15275-001.phpt @@ -0,0 +1,38 @@ +--TEST-- +GH-15275 001: Crash during GC of suspended generator delegate +--FILE-- +current()); + $iterable->next(); + var_dump("not executed"); +}); + +$ref = $fiber; + +$fiber->start(); + +gc_collect_cycles(); + +?> +==DONE== +--EXPECT-- +string(3) "foo" +==DONE== diff --git a/Zend/tests/gh15275-002.phpt b/Zend/tests/gh15275-002.phpt new file mode 100644 index 0000000000000..7b1f88c9f2b26 --- /dev/null +++ b/Zend/tests/gh15275-002.phpt @@ -0,0 +1,57 @@ +--TEST-- +GH-15275 002: Crash during GC of suspended generator delegate +--FILE-- +current()); + $gen->next(); + var_dump("not executed"); +}); + +$ref = $fiber; + +$fiber->start(); + +gc_collect_cycles(); + +?> +==DONE== +--EXPECT-- +string(3) "foo" +==DONE== +string(15) "It::getIterator" +string(1) "f" +string(1) "g" diff --git a/Zend/tests/gh15275-003.phpt b/Zend/tests/gh15275-003.phpt new file mode 100644 index 0000000000000..d62187e5342b7 --- /dev/null +++ b/Zend/tests/gh15275-003.phpt @@ -0,0 +1,51 @@ +--TEST-- +GH-15275 003: Crash during GC of suspended generator delegate +--FILE-- +current()); +$gen->next(); + +?> +==DONE== +--EXPECTF-- +string(3) "foo" +string(1) "f" +string(1) "g" + +Fatal error: Uncaught Exception in %s:8 +Stack trace: +#0 %s(15): It->getIterator() +#1 %s(23): f() +#2 [internal function]: g() +#3 %s(32): Generator->next() +#4 {main} + thrown in %s on line 8 diff --git a/Zend/tests/gh15275-004.phpt b/Zend/tests/gh15275-004.phpt new file mode 100644 index 0000000000000..61939ad2a4557 --- /dev/null +++ b/Zend/tests/gh15275-004.phpt @@ -0,0 +1,44 @@ +--TEST-- +GH-15275 004: Crash during GC of suspended generator delegate +--FILE-- +current()); +$gen->next(); + +gc_collect_cycles(); + +?> +==DONE== +--EXPECTF-- +string(3) "foo" +baz + +Fatal error: Uncaught Exception in %s:9 +Stack trace: +#0 %s(19): It->getIterator() +#1 [internal function]: f() +#2 %s(25): Generator->next() +#3 {main} + thrown in %s on line 9 diff --git a/Zend/tests/gh15275-005.phpt b/Zend/tests/gh15275-005.phpt new file mode 100644 index 0000000000000..07a6f48e7a17e --- /dev/null +++ b/Zend/tests/gh15275-005.phpt @@ -0,0 +1,51 @@ +--TEST-- +GH-15275 005: Crash during GC of suspended generator delegate +--FILE-- +current()); +$gen->next(); + +?> +==DONE== +--EXPECTF-- +string(3) "foo" +string(1) "f" +string(1) "g" + +Fatal error: Uncaught Exception in %s:8 +Stack trace: +#0 %s(15): It->getIterator() +#1 %s(23): f() +#2 [internal function]: g() +#3 %s(32): Generator->next() +#4 {main} + thrown in %s on line 8 diff --git a/Zend/tests/gh15275-006.phpt b/Zend/tests/gh15275-006.phpt new file mode 100644 index 0000000000000..7f585acf0f40b --- /dev/null +++ b/Zend/tests/gh15275-006.phpt @@ -0,0 +1,51 @@ +--TEST-- +GH-15275 006: Crash during GC of suspended generator delegate +--FILE-- +current()); +$gen->next(); + +gc_collect_cycles(); + +?> +==DONE== +--EXPECTF-- +string(3) "foo" +baz + +Fatal error: Uncaught Exception in %s:9 +Stack trace: +#0 %s(19): It->getIterator() +#1 [internal function]: f() +#2 %s(25): Generator->next() +#3 {main} + +Next Exception in %s:14 +Stack trace: +#0 %s(19): It->__destruct() +#1 [internal function]: f() +#2 %s(25): Generator->next() +#3 {main} + thrown in %s on line 14 diff --git a/Zend/zend.h b/Zend/zend.h index 7b375c58067fd..090bb1cd69bb9 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.3.10-dev" +#define ZEND_VERSION "4.3.11" #define ZEND_ENGINE_3 diff --git a/Zend/zend_API.c b/Zend/zend_API.c index cb7f3b6d29b43..e93c4a93b7d17 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2635,8 +2635,8 @@ static void zend_check_magic_method_no_return_type( ZEND_API void zend_check_magic_method_implementation(const zend_class_entry *ce, const zend_function *fptr, zend_string *lcname, int error_type) /* {{{ */ { - if (ZSTR_VAL(fptr->common.function_name)[0] != '_' - || ZSTR_VAL(fptr->common.function_name)[1] != '_') { + if (ZSTR_VAL(lcname)[0] != '_' + || ZSTR_VAL(lcname)[1] != '_') { return; } diff --git a/Zend/zend_enum.c b/Zend/zend_enum.c index 58862c1564da2..a701cccfa23f2 100644 --- a/Zend/zend_enum.c +++ b/Zend/zend_enum.c @@ -286,7 +286,7 @@ static ZEND_NAMED_FUNCTION(zend_enum_cases_func) } ZEND_HASH_FOREACH_END(); } -ZEND_API zend_result zend_enum_get_case_by_value(zend_object **result, zend_class_entry *ce, zend_long long_key, zend_string *string_key, bool try) +ZEND_API zend_result zend_enum_get_case_by_value(zend_object **result, zend_class_entry *ce, zend_long long_key, zend_string *string_key, bool try_from) { if (ce->type == ZEND_USER_CLASS && !(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) { if (zend_update_class_constants(ce) == FAILURE) { @@ -310,7 +310,7 @@ ZEND_API zend_result zend_enum_get_case_by_value(zend_object **result, zend_clas if (case_name_zv == NULL) { not_found: - if (try) { + if (try_from) { *result = NULL; return SUCCESS; } @@ -340,7 +340,7 @@ ZEND_API zend_result zend_enum_get_case_by_value(zend_object **result, zend_clas return SUCCESS; } -static void zend_enum_from_base(INTERNAL_FUNCTION_PARAMETERS, bool try) +static void zend_enum_from_base(INTERNAL_FUNCTION_PARAMETERS, bool try_from) { zend_class_entry *ce = execute_data->func->common.scope; bool release_string = false; @@ -375,12 +375,12 @@ static void zend_enum_from_base(INTERNAL_FUNCTION_PARAMETERS, bool try) } zend_object *case_obj; - if (zend_enum_get_case_by_value(&case_obj, ce, long_key, string_key, try) == FAILURE) { + if (zend_enum_get_case_by_value(&case_obj, ce, long_key, string_key, try_from) == FAILURE) { goto throw; } if (case_obj == NULL) { - ZEND_ASSERT(try); + ZEND_ASSERT(try_from); goto return_null; } diff --git a/Zend/zend_enum.h b/Zend/zend_enum.h index 7b58592d42bd5..33613817d9053 100644 --- a/Zend/zend_enum.h +++ b/Zend/zend_enum.h @@ -44,7 +44,7 @@ ZEND_API void zend_enum_add_case(zend_class_entry *ce, zend_string *case_name, z ZEND_API void zend_enum_add_case_cstr(zend_class_entry *ce, const char *name, zval *value); ZEND_API zend_object *zend_enum_get_case(zend_class_entry *ce, zend_string *name); ZEND_API zend_object *zend_enum_get_case_cstr(zend_class_entry *ce, const char *name); -ZEND_API zend_result zend_enum_get_case_by_value(zend_object **result, zend_class_entry *ce, zend_long long_key, zend_string *string_key, bool try); +ZEND_API zend_result zend_enum_get_case_by_value(zend_object **result, zend_class_entry *ce, zend_long long_key, zend_string *string_key, bool try_from); static zend_always_inline zval *zend_enum_fetch_case_name(zend_object *zobj) { diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 8da2d37a25aad..73f7995779d10 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -4598,7 +4598,13 @@ ZEND_API HashTable *zend_unfinished_execution_gc_ex(zend_execute_data *execute_d } if (call) { - uint32_t op_num = execute_data->opline - op_array->opcodes; + uint32_t op_num; + if (UNEXPECTED(execute_data->opline->opcode == ZEND_HANDLE_EXCEPTION)) { + op_num = EG(opline_before_exception) - op_array->opcodes; + } else { + op_num = execute_data->opline - op_array->opcodes; + } + ZEND_ASSERT(op_num < op_array->last); if (suspended_by_yield) { /* When the execution was suspended by yield, EX(opline) points to * next opline to execute. Otherwise, it points to the opline that diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c index ec715013ba71e..c17ce5c48000e 100644 --- a/Zend/zend_generators.c +++ b/Zend/zend_generators.c @@ -19,12 +19,14 @@ #include "zend.h" #include "zend_API.h" +#include "zend_hash.h" #include "zend_interfaces.h" #include "zend_exceptions.h" #include "zend_generators.h" #include "zend_closures.h" #include "zend_generators_arginfo.h" #include "zend_observer.h" +#include "zend_vm_opcodes.h" ZEND_API zend_class_entry *zend_ce_generator; ZEND_API zend_class_entry *zend_ce_ClosedGeneratorException; @@ -216,19 +218,68 @@ static zend_always_inline void clear_link_to_root(zend_generator *generator) { } } +/* In the context of zend_generator_dtor_storage during shutdown, check if + * the intermediate node 'generator' is running in a fiber */ +static inline bool check_node_running_in_fiber(zend_generator *generator) { + ZEND_ASSERT(EG(flags) & EG_FLAGS_IN_SHUTDOWN); + ZEND_ASSERT(generator->execute_data); + + if (generator->flags & ZEND_GENERATOR_IN_FIBER) { + return true; + } + + if (generator->node.children == 0) { + return false; + } + + if (generator->flags & ZEND_GENERATOR_DTOR_VISITED) { + return false; + } + generator->flags |= ZEND_GENERATOR_DTOR_VISITED; + + if (generator->node.children == 1) { + if (check_node_running_in_fiber(generator->node.child.single)) { + goto in_fiber; + } + return false; + } + + zend_generator *child; + ZEND_HASH_FOREACH_PTR(generator->node.child.ht, child) { + if (check_node_running_in_fiber(child)) { + goto in_fiber; + } + } ZEND_HASH_FOREACH_END(); + return false; + +in_fiber: + generator->flags |= ZEND_GENERATOR_IN_FIBER; + return true; +} + static void zend_generator_dtor_storage(zend_object *object) /* {{{ */ { zend_generator *generator = (zend_generator*) object; + zend_generator *current_generator = zend_generator_get_current(generator); zend_execute_data *ex = generator->execute_data; uint32_t op_num, try_catch_offset; int i; - /* Generator is running in a suspended fiber. - * Will be dtor during fiber dtor */ - if (zend_generator_get_current(generator)->flags & ZEND_GENERATOR_IN_FIBER) { - /* Prevent finally blocks from yielding */ - generator->flags |= ZEND_GENERATOR_FORCED_CLOSE; - return; + /* If current_generator is running in a fiber, there are 2 cases to consider: + * - If generator is also marked with ZEND_GENERATOR_IN_FIBER, then the + * entire path from current_generator to generator is executing in a + * fiber. Do not dtor now: These will be dtor when terminating the fiber. + * - If generator is not marked with ZEND_GENERATOR_IN_FIBER, and has a + * child marked with ZEND_GENERATOR_IN_FIBER, then this an intermediate + * node of case 1. Otherwise generator is not executing in a fiber and we + * can dtor. + */ + if (current_generator->flags & ZEND_GENERATOR_IN_FIBER) { + if (check_node_running_in_fiber(generator)) { + /* Prevent finally blocks from yielding */ + generator->flags |= ZEND_GENERATOR_FORCED_CLOSE; + return; + } } /* leave yield from mode to properly allow finally execution */ @@ -458,6 +509,8 @@ static void zend_generator_throw_exception(zend_generator *generator, zval *exce * to pretend the exception happened during the YIELD opcode. */ EG(current_execute_data) = generator->execute_data; generator->execute_data->opline--; + ZEND_ASSERT(generator->execute_data->opline->opcode == ZEND_YIELD + || generator->execute_data->opline->opcode == ZEND_YIELD_FROM); generator->execute_data->prev_execute_data = original_execute_data; if (exception) { @@ -466,13 +519,14 @@ static void zend_generator_throw_exception(zend_generator *generator, zval *exce zend_rethrow_exception(EG(current_execute_data)); } + generator->execute_data->opline++; + /* if we don't stop an array/iterator yield from, the exception will only reach the generator after the values were all iterated over */ if (UNEXPECTED(Z_TYPE(generator->values) != IS_UNDEF)) { zval_ptr_dtor(&generator->values); ZVAL_UNDEF(&generator->values); } - generator->execute_data->opline++; EG(current_execute_data) = original_execute_data; } @@ -602,8 +656,6 @@ ZEND_API zend_generator *zend_generator_update_current(zend_generator *generator static zend_result zend_generator_get_next_delegated_value(zend_generator *generator) /* {{{ */ { - --generator->execute_data->opline; - zval *value; if (Z_TYPE(generator->values) == IS_ARRAY) { HashTable *ht = Z_ARR(generator->values); @@ -685,14 +737,12 @@ static zend_result zend_generator_get_next_delegated_value(zend_generator *gener } } - ++generator->execute_data->opline; return SUCCESS; failure: zval_ptr_dtor(&generator->values); ZVAL_UNDEF(&generator->values); - ++generator->execute_data->opline; return FAILURE; } /* }}} */ @@ -718,6 +768,11 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ return; } + if (EG(active_fiber)) { + orig_generator->flags |= ZEND_GENERATOR_IN_FIBER; + generator->flags |= ZEND_GENERATOR_IN_FIBER; + } + /* Drop the AT_FIRST_YIELD flag */ orig_generator->flags &= ~ZEND_GENERATOR_AT_FIRST_YIELD; @@ -748,9 +803,19 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ EG(current_execute_data) = original_execute_data; EG(jit_trace_num) = original_jit_trace_num; - orig_generator->flags &= ~ZEND_GENERATOR_DO_INIT; + orig_generator->flags &= ~(ZEND_GENERATOR_DO_INIT | ZEND_GENERATOR_IN_FIBER); + generator->flags &= ~ZEND_GENERATOR_IN_FIBER; return; } + if (UNEXPECTED(EG(exception))) { + /* Decrementing opline_before_exception to pretend the exception + * happened during the YIELD_FROM opcode. */ + if (generator->execute_data) { + ZEND_ASSERT(generator->execute_data->opline == EG(exception_op)); + ZEND_ASSERT((EG(opline_before_exception)-1)->opcode == ZEND_YIELD_FROM); + EG(opline_before_exception)--; + } + } /* If there are no more delegated values, resume the generator * after the "yield from" expression. */ } @@ -761,8 +826,7 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ } /* Resume execution */ - generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING - | (EG(active_fiber) ? ZEND_GENERATOR_IN_FIBER : 0); + generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING; if (!ZEND_OBSERVER_ENABLED) { zend_execute_ex(generator->execute_data); } else { @@ -813,7 +877,7 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ goto try_again; } - orig_generator->flags &= ~ZEND_GENERATOR_DO_INIT; + orig_generator->flags &= ~(ZEND_GENERATOR_DO_INIT | ZEND_GENERATOR_IN_FIBER); } /* }}} */ diff --git a/Zend/zend_generators.h b/Zend/zend_generators.h index b3fb44c353355..a93c315f50a80 100644 --- a/Zend/zend_generators.h +++ b/Zend/zend_generators.h @@ -95,6 +95,7 @@ static const uint8_t ZEND_GENERATOR_FORCED_CLOSE = 0x2; static const uint8_t ZEND_GENERATOR_AT_FIRST_YIELD = 0x4; static const uint8_t ZEND_GENERATOR_DO_INIT = 0x8; static const uint8_t ZEND_GENERATOR_IN_FIBER = 0x10; +static const uint8_t ZEND_GENERATOR_DTOR_VISITED = 0x20; void zend_register_generator_ce(void); ZEND_API void zend_generator_close(zend_generator *generator, bool finished_execution); diff --git a/Zend/zend_ini.c b/Zend/zend_ini.c index 40bd38c163c70..9588574df6cfb 100644 --- a/Zend/zend_ini.c +++ b/Zend/zend_ini.c @@ -243,6 +243,7 @@ ZEND_API zend_result zend_register_ini_entries_ex(const zend_ini_entry_def *ini_ if (p->name) { zend_string_release_ex(p->name, 1); } + pefree(p, true); zend_unregister_ini_entries_ex(module_number, module_type); return FAILURE; } diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 054ed7bdc1ef6..545e06e58e0bf 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1597,12 +1597,6 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_ RETURN_TOKEN_WITH_STR(T_STRING, 0); } -{ANY_CHAR} { - yyless(0); - yy_pop_state(); - goto restart; -} - "::" { RETURN_TOKEN(T_PAAMAYIM_NEKUDOTAYIM); } @@ -2385,7 +2379,7 @@ inline_char_handler: } -"#"|"//" { +"#"|"//" { while (YYCURSOR < YYLIMIT) { switch (*YYCURSOR++) { case '\r': @@ -2409,7 +2403,7 @@ inline_char_handler: RETURN_OR_SKIP_TOKEN(T_COMMENT); } -"/*"|"/**"{WHITESPACE} { +"/*"|"/**"{WHITESPACE} { int doc_com; if (yyleng > 2) { @@ -2445,6 +2439,12 @@ inline_char_handler: RETURN_OR_SKIP_TOKEN(T_COMMENT); } +{ANY_CHAR} { + yyless(0); + yy_pop_state(); + goto restart; +} + "?>"{NEWLINE}? { BEGIN(INITIAL); if (yytext[yyleng-1] != '>') { diff --git a/configure.ac b/configure.ac index 0f9526cc0a52f..7c922eee50e6d 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.10-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.3.11],[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/curl/interface.c b/ext/curl/interface.c index 44284b7a29670..80cc87c2d2d3c 100644 --- a/ext/curl/interface.c +++ b/ext/curl/interface.c @@ -2869,7 +2869,11 @@ PHP_FUNCTION(curl_error) if (ch->err.no) { ch->err.str[CURL_ERROR_SIZE] = 0; - RETURN_STRING(ch->err.str); + if (strlen(ch->err.str) > 0) { + RETURN_STRING(ch->err.str); + } else { + RETURN_STRING(curl_easy_strerror(ch->err.no)); + } } else { RETURN_EMPTY_STRING(); } diff --git a/ext/curl/tests/bug45161.phpt b/ext/curl/tests/bug45161.phpt index 7e67b6d741ed5..9ba8f5f90249c 100644 --- a/ext/curl/tests/bug45161.phpt +++ b/ext/curl/tests/bug45161.phpt @@ -2,10 +2,6 @@ Bug #45161 (Reusing a curl handle leaks memory) --EXTENSIONS-- curl ---SKIPIF-- - --FILE-- nodetype == XML_ATTRIBUTE_NODE) { curnode = (xmlNodePtr) basep->properties; } else { - curnode = (xmlNodePtr) basep->children; + curnode = dom_nodelist_iter_start_first_child(basep); } } else { xmlNodePtr nodep = basep; diff --git a/ext/dom/nodelist.c b/ext/dom/nodelist.c index 96868b05437cc..bb58161f16e4f 100644 --- a/ext/dom/nodelist.c +++ b/ext/dom/nodelist.c @@ -49,7 +49,7 @@ static zend_always_inline void reset_objmap_cache(dom_nnodemap_object *objmap) objmap->cached_length = -1; } -static xmlNodePtr dom_nodelist_iter_start_first_child(xmlNodePtr nodep) +xmlNodePtr dom_nodelist_iter_start_first_child(xmlNodePtr nodep) { if (nodep->type == XML_ENTITY_REF_NODE) { /* See entityreference.c */ diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h index cebbe47f2652b..86e6b131545c0 100644 --- a/ext/dom/php_dom.h +++ b/ext/dom/php_dom.h @@ -174,6 +174,7 @@ void php_dom_named_node_map_get_item_into_zval(dom_nnodemap_object *objmap, zend void php_dom_nodelist_get_item_into_zval(dom_nnodemap_object *objmap, zend_long index, zval *return_value); int php_dom_get_namednodemap_length(dom_object *obj); int php_dom_get_nodelist_length(dom_object *obj); +xmlNodePtr dom_nodelist_iter_start_first_child(xmlNodePtr nodep); #define DOM_GET_INTERN(__id, __intern) { \ __intern = Z_DOMOBJ_P(__id); \ diff --git a/ext/dom/tests/uaf_doctype_iterator.phpt b/ext/dom/tests/uaf_doctype_iterator.phpt new file mode 100644 index 0000000000000..b166a83955248 --- /dev/null +++ b/ext/dom/tests/uaf_doctype_iterator.phpt @@ -0,0 +1,26 @@ +--TEST-- +UAF when removing doctype and iterating over the child nodes +--EXTENSIONS-- +dom +--CREDITS-- +Yuancheng Jiang +--FILE-- +loadXML(<< +]> +&foo1; +XML); +$ref = $dom->documentElement->firstChild; +$nodes = $ref->childNodes; +$dom->removeChild($dom->doctype); +foreach($nodes as $str) {} +var_dump($nodes); +?> +--EXPECTF-- +object(DOMNodeList)#%d (1) { + ["length"]=> + int(0) +} diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c index bbfe07576e6b2..86da8211ef2e6 100644 --- a/ext/ffi/ffi.c +++ b/ext/ffi/ffi.c @@ -3582,7 +3582,7 @@ ZEND_METHOD(FFI, scope) /* {{{ */ } /* }}} */ -static void zend_ffi_cleanup_dcl(zend_ffi_dcl *dcl) /* {{{ */ +void zend_ffi_cleanup_dcl(zend_ffi_dcl *dcl) /* {{{ */ { if (dcl) { zend_ffi_type_dtor(dcl->type); diff --git a/ext/ffi/ffi.g b/ext/ffi/ffi.g index b70e3f1299629..d70075267e54f 100644 --- a/ext/ffi/ffi.g +++ b/ext/ffi/ffi.g @@ -64,6 +64,7 @@ php llk.php ffi.g /* forward declarations */ static void yy_error(const char *msg); static void yy_error_sym(const char *msg, int sym); +static void yy_error_str(const char *msg, const char *str); %} @@ -96,7 +97,10 @@ declarations: initializer? {zend_ffi_declare(name, name_len, &dcl);} )* - )? + | + /* empty */ + {if (common_dcl.flags & (ZEND_FFI_DCL_ENUM | ZEND_FFI_DCL_STRUCT | ZEND_FFI_DCL_UNION)) zend_ffi_cleanup_dcl(&common_dcl);} + ) ";" )* ; @@ -918,3 +922,7 @@ static void yy_error(const char *msg) { static void yy_error_sym(const char *msg, int sym) { zend_ffi_parser_error("%s '%s' at line %d", msg, sym_name[sym], yy_line); } + +static void yy_error_str(const char *msg, const char *str) { + zend_ffi_parser_error("%s '%s' at line %d\n", msg, str, yy_line); +} diff --git a/ext/ffi/ffi_parser.c b/ext/ffi/ffi_parser.c index b956f885ee001..2589ae81e0259 100644 --- a/ext/ffi/ffi_parser.c +++ b/ext/ffi/ffi_parser.c @@ -34,6 +34,7 @@ /* forward declarations */ static void yy_error(const char *msg); static void yy_error_sym(const char *msg, int sym); +static void yy_error_str(const char *msg, const char *str); #define YYPOS cpos #define YYEND cend @@ -246,6 +247,63 @@ static const char * sym_name[] = { #define YY_IN_SET(sym, set, bitset) \ (bitset[sym>>3] & (1 << (sym & 0x7))) +size_t yy_escape(char *buf, unsigned char ch) +{ + switch (ch) { + case '\\': buf[0] = '\\'; buf[1] = '\\'; return 2; + case '\'': buf[0] = '\\'; buf[1] = '\''; return 2; + case '\"': buf[0] = '\\'; buf[1] = '\"'; return 2; + case '\a': buf[0] = '\\'; buf[1] = '\a'; return 2; + case '\b': buf[0] = '\\'; buf[1] = '\b'; return 2; + case 27: buf[0] = '\\'; buf[1] = 27; return 2; + case '\f': buf[0] = '\\'; buf[1] = '\f'; return 2; + case '\n': buf[0] = '\\'; buf[1] = '\n'; return 2; + case '\r': buf[0] = '\\'; buf[1] = '\r'; return 2; + case '\t': buf[0] = '\\'; buf[1] = '\t'; return 2; + case '\v': buf[0] = '\\'; buf[1] = '\v'; return 2; + case '\?': buf[0] = '\\'; buf[1] = 0x3f; return 2; + default: break; + } + if (ch < 32 || ch >= 127) { + buf[0] = '\\'; + buf[1] = '0' + ((ch >> 6) % 8); + buf[2] = '0' + ((ch >> 3) % 8); + buf[3] = '0' + (ch % 8); + return 4; + } else { + buf[0] = ch; + return 1; + } +} + +const char *yy_escape_char(char *buf, unsigned char ch) +{ + size_t len = yy_escape(buf, ch); + buf[len] = 0; + return buf; +} + +const char *yy_escape_string(char *buf, size_t size, const unsigned char *str, size_t n) +{ + size_t i = 0; + size_t pos = 0; + size_t len; + + while (i < n) { + if (pos + 8 > size) { + buf[pos++] = '.'; + buf[pos++] = '.'; + buf[pos++] = '.'; + break; + } + len = yy_escape(buf + pos, str[i]); + i++; + pos += len; + } + buf[pos] = 0; + return buf; +} + static int skip_EOL(int sym); static int skip_WS(int sym); static int skip_ONE_LINE_COMMENT(int sym); @@ -310,6 +368,7 @@ static int synpred_5(int sym); static int synpred_6(int sym); static int get_skip_sym(void) { + char buf[64]; int ch; int ret; int accept = -1; @@ -1667,9 +1726,9 @@ static int get_skip_sym(void) { if (YYPOS >= YYEND) { yy_error("unexpected "); } else if (YYPOS == yy_text) { - yy_error("unexpected character 'escape_char(ch)'"); + yy_error_str("unexpected character", yy_escape_char(buf, ch)); } else { - yy_error("unexpected sequence 'escape_string(yy_text, 1 + YYPOS - yy_text))'"); + yy_error_str("unexpected sequence", yy_escape_string(buf, sizeof(buf), yy_text, 1 + YYPOS - yy_text)); } YYPOS++; goto _yy_state_start; @@ -2056,6 +2115,10 @@ static int parse_declarations(int sym) { } zend_ffi_declare(name, name_len, &dcl); } + } else if (sym == YY__SEMICOLON) { + if (common_dcl.flags & (ZEND_FFI_DCL_ENUM | ZEND_FFI_DCL_STRUCT | ZEND_FFI_DCL_UNION)) zend_ffi_cleanup_dcl(&common_dcl); + } else { + yy_error_sym("unexpected", sym); } if (sym != YY__SEMICOLON) { yy_error_sym("';' expected, got", sym); @@ -3592,3 +3655,7 @@ static void yy_error(const char *msg) { static void yy_error_sym(const char *msg, int sym) { zend_ffi_parser_error("%s '%s' at line %d", msg, sym_name[sym], yy_line); } + +static void yy_error_str(const char *msg, const char *str) { + zend_ffi_parser_error("%s '%s' at line %d\n", msg, str, yy_line); +} diff --git a/ext/ffi/php_ffi.h b/ext/ffi/php_ffi.h index 02a241c6bb691..430b8a2e568f8 100644 --- a/ext/ffi/php_ffi.h +++ b/ext/ffi/php_ffi.h @@ -208,6 +208,7 @@ typedef struct _zend_ffi_val { zend_result zend_ffi_parse_decl(const char *str, size_t len); zend_result zend_ffi_parse_type(const char *str, size_t len, zend_ffi_dcl *dcl); +void zend_ffi_cleanup_dcl(zend_ffi_dcl *dcl); /* parser callbacks */ void ZEND_NORETURN zend_ffi_parser_error(const char *msg, ...); diff --git a/ext/ffi/tests/gh14286_1.phpt b/ext/ffi/tests/gh14286_1.phpt new file mode 100644 index 0000000000000..19701f634a631 --- /dev/null +++ b/ext/ffi/tests/gh14286_1.phpt @@ -0,0 +1,46 @@ +--TEST-- +GH-14286 (ffi enum type (when enum has no name) make memory leak) +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +--FILE-- +TEST_ONE); +var_dump($ffi->TEST_TWO); +var_dump($ffi->TEST_THREE); +var_dump($ffi->TEST_FOUR); +var_dump($ffi->TEST_FIVE); +var_dump($ffi->TEST_SIX); +?> +--EXPECT-- +int(1) +int(2) +int(3) +int(4) +int(5) +int(6) diff --git a/ext/ffi/tests/gh14286_2.phpt b/ext/ffi/tests/gh14286_2.phpt new file mode 100644 index 0000000000000..683929780c053 --- /dev/null +++ b/ext/ffi/tests/gh14286_2.phpt @@ -0,0 +1,25 @@ +--TEST-- +GH-14286 (ffi enum type (when enum has no name) make memory leak) +--EXTENSIONS-- +ffi +--SKIPIF-- + +--INI-- +ffi.enable=1 +--FILE-- +getMessage(), "\n"; +} +?> +--EXPECT-- +Failed resolving C variable 'x' diff --git a/ext/hash/config.m4 b/ext/hash/config.m4 index dd0b24a1320ce..46e66aa9f8cde 100644 --- a/ext/hash/config.m4 +++ b/ext/hash/config.m4 @@ -24,8 +24,13 @@ else SHA3_OPT_SRC="$SHA3_DIR/KeccakP-1600-opt64.c" ]) EXT_HASH_SHA3_SOURCES="$SHA3_OPT_SRC $SHA3_DIR/KeccakHash.c $SHA3_DIR/KeccakSponge.c hash_sha3.c" + dnl Add -Wno-implicit-fallthrough flag as it happens on 32 bit builds - PHP_HASH_CFLAGS="-Wno-implicit-fallthrough -I@ext_srcdir@/$SHA3_DIR -DKeccakP200_excluded -DKeccakP400_excluded -DKeccakP800_excluded -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1" + AX_CHECK_COMPILE_FLAG([-Wno-implicit-fallthrough], + [PHP_HASH_CFLAGS="$PHP_HASH_CFLAGS -Wno-implicit-fallthrough"],, + [-Werror]) + + PHP_HASH_CFLAGS="$PHP_HASH_CFLAGS -I@ext_srcdir@/$SHA3_DIR -DKeccakP200_excluded -DKeccakP400_excluded -DKeccakP800_excluded -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1" PHP_ADD_BUILD_DIR(ext/hash/$SHA3_DIR, 1) fi diff --git a/ext/hash/hash_xxhash.c b/ext/hash/hash_xxhash.c index 8a155c9862271..24da754d8835a 100644 --- a/ext/hash/hash_xxhash.c +++ b/ext/hash/hash_xxhash.c @@ -174,11 +174,14 @@ zend_always_inline static void _PHP_XXH3_Init(PHP_XXH3_64_CTX *ctx, HashTable *a func_init_seed(&ctx->s, (XXH64_hash_t)Z_LVAL_P(_seed)); return; } else if (_secret) { - if (!try_convert_to_string(_secret)) { + zend_string *secret_string = zval_try_get_string(_secret); + if (UNEXPECTED(!secret_string)) { + ZEND_ASSERT(EG(exception)); return; } - size_t len = Z_STRLEN_P(_secret); + size_t len = ZSTR_LEN(secret_string); if (len < PHP_XXH3_SECRET_SIZE_MIN) { + zend_string_release(secret_string); zend_throw_error(NULL, "%s: Secret length must be >= %u bytes, %zu bytes passed", algo_name, XXH3_SECRET_SIZE_MIN, len); return; } @@ -186,7 +189,8 @@ zend_always_inline static void _PHP_XXH3_Init(PHP_XXH3_64_CTX *ctx, HashTable *a len = sizeof(ctx->secret); php_error_docref(NULL, E_WARNING, "%s: Secret content exceeding %zu bytes discarded", algo_name, sizeof(ctx->secret)); } - memcpy((unsigned char *)ctx->secret, Z_STRVAL_P(_secret), len); + memcpy((unsigned char *)ctx->secret, ZSTR_VAL(secret_string), len); + zend_string_release(secret_string); func_init_secret(&ctx->s, ctx->secret, len); return; } diff --git a/ext/hash/tests/xxh3_convert_secret_to_string.phpt b/ext/hash/tests/xxh3_convert_secret_to_string.phpt new file mode 100644 index 0000000000000..dfc161b084c0c --- /dev/null +++ b/ext/hash/tests/xxh3_convert_secret_to_string.phpt @@ -0,0 +1,14 @@ +--TEST-- +xxh3 convert secret to string should not modify (shm) array +--FILE-- + 4]); +} catch (Throwable) {} +var_dump($x); +?> +--EXPECT-- +array(1) { + ["secret"]=> + int(4) +} diff --git a/ext/intl/uchar/tests/gh15087.phpt b/ext/intl/uchar/tests/gh15087.phpt new file mode 100644 index 0000000000000..62009857ab1d6 --- /dev/null +++ b/ext/intl/uchar/tests/gh15087.phpt @@ -0,0 +1,10 @@ +--TEST-- +GH-15087 (IntlChar::foldCase()'s $option is not optional) +--EXTENSIONS-- +intl +--FILE-- + +--EXPECT-- +string(1) "i" diff --git a/ext/intl/uchar/uchar.c b/ext/intl/uchar/uchar.c index 2cd0de941a019..b1c02dc04da51 100644 --- a/ext/intl/uchar/uchar.c +++ b/ext/intl/uchar/uchar.c @@ -393,8 +393,9 @@ IC_METHOD(foldCase) { zend_string *string_codepoint; zend_long int_codepoint = 0; - ZEND_PARSE_PARAMETERS_START(2, 2) + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR_OR_LONG(string_codepoint, int_codepoint) + Z_PARAM_OPTIONAL Z_PARAM_LONG(options) ZEND_PARSE_PARAMETERS_END(); diff --git a/ext/odbc/tests/odbc_data_source_001.phpt b/ext/odbc/tests/odbc_data_source_001.phpt index be3c1226b68b9..f376d69f62e09 100644 --- a/ext/odbc/tests/odbc_data_source_001.phpt +++ b/ext/odbc/tests/odbc_data_source_001.phpt @@ -17,7 +17,7 @@ include 'config.inc'; $conn = odbc_connect($dsn, $user, $pass); try { - var_dump(odbc_data_source($conn, NULL)); + var_dump(odbc_data_source($conn, SQL_FETCH_FIRST + SQL_FETCH_NEXT)); } catch (\ValueError $e) { echo $e->getMessage() . \PHP_EOL; } diff --git a/ext/opcache/config.m4 b/ext/opcache/config.m4 index 44a55a90dfd5d..6561406d52e53 100644 --- a/ext/opcache/config.m4 +++ b/ext/opcache/config.m4 @@ -300,6 +300,10 @@ int main(void) { fi AC_MSG_RESULT([$have_shm_mmap_posix]) + AX_CHECK_COMPILE_FLAG([-Wno-implicit-fallthrough], + [PHP_OPCACHE_CFLAGS="$PHP_OPCACHE_CFLAGS -Wno-implicit-fallthrough"],, + [-Werror]) + PHP_NEW_EXTENSION(opcache, ZendAccelerator.c \ zend_accelerator_blacklist.c \ @@ -315,7 +319,7 @@ int main(void) { shared_alloc_mmap.c \ shared_alloc_posix.c \ $ZEND_JIT_SRC, - shared,,"-Wno-implicit-fallthrough -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1",,yes) + shared,,"$PHP_OPCACHE_CFLAGS -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1",,yes) PHP_ADD_EXTENSION_DEP(opcache, pcre) diff --git a/ext/opcache/shared_alloc_mmap.c b/ext/opcache/shared_alloc_mmap.c index 92d089bb96cc8..d428d97d36ffc 100644 --- a/ext/opcache/shared_alloc_mmap.c +++ b/ext/opcache/shared_alloc_mmap.c @@ -20,6 +20,9 @@ */ #include "zend_shared_alloc.h" +#ifdef HAVE_JIT +# include "jit/zend_jit.h" +#endif #ifdef USE_MMAP @@ -48,7 +51,7 @@ # define MAP_HUGETLB MAP_ALIGNED_SUPER #endif -#if (defined(__linux__) || defined(__FreeBSD__)) && (defined(__x86_64__) || defined (__aarch64__)) && !defined(__SANITIZE_ADDRESS__) +#if defined(HAVE_JIT) && (defined(__linux__) || defined(__FreeBSD__)) && (defined(__x86_64__) || defined (__aarch64__)) && !defined(__SANITIZE_ADDRESS__) static void *find_prefered_mmap_base(size_t requested_size) { size_t huge_page_size = 2 * 1024 * 1024; @@ -197,8 +200,17 @@ static int create_segments(size_t requested_size, zend_shared_segment ***shared_ #ifdef PROT_MAX flags |= PROT_MAX(PROT_READ | PROT_WRITE | PROT_EXEC); #endif -#if (defined(__linux__) || defined(__FreeBSD__)) && (defined(__x86_64__) || defined (__aarch64__)) && !defined(__SANITIZE_ADDRESS__) - void *hint = find_prefered_mmap_base(requested_size); +#if defined(HAVE_JIT) && (defined(__linux__) || defined(__FreeBSD__)) && (defined(__x86_64__) || defined (__aarch64__)) && !defined(__SANITIZE_ADDRESS__) + void *hint; + if (JIT_G(enabled) && JIT_G(buffer_size) + && zend_jit_check_support() == SUCCESS) { + hint = find_prefered_mmap_base(requested_size); + } else { + /* Do not use a hint if JIT is not enabled, as this profits only JIT and + * this is potentially unsafe when the only suitable candidate is just + * after the heap (e.g. in non-PIE builds) (GH-13775). */ + hint = MAP_FAILED; + } if (hint != MAP_FAILED) { # ifdef MAP_HUGETLB size_t huge_page_size = 2 * 1024 * 1024; diff --git a/ext/opcache/tests/gh13817.phpt b/ext/opcache/tests/gh13817.phpt new file mode 100644 index 0000000000000..0e5eb560d46f3 --- /dev/null +++ b/ext/opcache/tests/gh13817.phpt @@ -0,0 +1,51 @@ +--TEST-- +GH-13712 (Segmentation fault for enabled observers after pass 4) +--EXTENSIONS-- +opcache +zend_test +--INI-- +zend_test.observer.enabled=1 +zend_test.observer.show_output=1 +zend_test.observer.observe_all=1 +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=0x4069 +--FILE-- + +--EXPECTF-- + + + + + + + + +Ok + + + + + diff --git a/ext/opcache/tests/opt/gh11170.phpt b/ext/opcache/tests/opt/gh11170.phpt index ca00c5e852966..9ea733f92b104 100644 --- a/ext/opcache/tests/opt/gh11170.phpt +++ b/ext/opcache/tests/opt/gh11170.phpt @@ -39,7 +39,7 @@ test_and(); ?> --EXPECTF-- $_main: - ; (lines=5, args=0, vars=0, tmps=2, ssa_vars=0, no_loops) + ; (lines=5, args=0, vars=0, tmps=%d, ssa_vars=0, no_loops) ; (after dfa pass) ; %s ; return [long] RANGE[1..1] @@ -53,7 +53,7 @@ BB0: 0004 RETURN int(1) test_or: - ; (lines=11, args=0, vars=2, tmps=7, ssa_vars=11, no_loops) + ; (lines=11, args=0, vars=2, tmps=%d, ssa_vars=11, no_loops) ; (after dfa pass) ; %s ; return [long] RANGE[-20..-1] @@ -99,7 +99,7 @@ BB3: 0010 RETURN #10.T8 [long] RANGE[-20..-1] test_and: - ; (lines=11, args=0, vars=2, tmps=7, ssa_vars=11, no_loops) + ; (lines=11, args=0, vars=2, tmps=%d, ssa_vars=11, no_loops) ; (after dfa pass) ; %s ; return [long] RANGE[-28..-25] diff --git a/ext/opcache/tests/opt/nullsafe_002.phpt b/ext/opcache/tests/opt/nullsafe_002.phpt index 73e607a66cf22..a8d9f1482c6e1 100644 --- a/ext/opcache/tests/opt/nullsafe_002.phpt +++ b/ext/opcache/tests/opt/nullsafe_002.phpt @@ -20,7 +20,7 @@ function test(?Test $test) { ?> --EXPECTF-- $_main: - ; (lines=1, args=0, vars=0, tmps=0, ssa_vars=0, no_loops) + ; (lines=1, args=0, vars=0, tmps=%d, ssa_vars=0, no_loops) ; (before dfa pass) ; %s ; return [long] RANGE[1..1] @@ -30,7 +30,7 @@ BB0: 0000 RETURN int(1) test: - ; (lines=7, args=1, vars=1, tmps=2, ssa_vars=6, no_loops) + ; (lines=7, args=1, vars=1, tmps=%d, ssa_vars=6, no_loops) ; (before dfa pass) ; %s ; return [null] RANGE[0..0] diff --git a/ext/opcache/tests/opt/prop_types.phpt b/ext/opcache/tests/opt/prop_types.phpt index 44e8f3d9767d2..ec57bf13580cf 100644 --- a/ext/opcache/tests/opt/prop_types.phpt +++ b/ext/opcache/tests/opt/prop_types.phpt @@ -40,7 +40,7 @@ function noScope(Test $test) { ?> --EXPECTF-- $_main: - ; (lines=1, args=0, vars=0, tmps=0, ssa_vars=0, no_loops) + ; (lines=1, args=0, vars=0, tmps=%d, ssa_vars=0, no_loops) ; (before dfa pass) ; %s ; return [long] RANGE[1..1] @@ -50,7 +50,7 @@ BB0: 0000 RETURN int(1) noScope: - ; (lines=10, args=1, vars=1, tmps=4, ssa_vars=5, no_loops) + ; (lines=10, args=1, vars=1, tmps=%d, ssa_vars=5, no_loops) ; (before dfa pass) ; %s ; return [null] RANGE[0..0] @@ -70,7 +70,7 @@ BB0: 0009 RETURN null Test::inTest: - ; (lines=9, args=0, vars=0, tmps=4, ssa_vars=3, no_loops) + ; (lines=9, args=0, vars=0, tmps=%d, ssa_vars=3, no_loops) ; (before dfa pass) ; %s ; return [null] RANGE[0..0] @@ -88,7 +88,7 @@ BB0: 0008 RETURN null Test::inTestWithTest2: - ; (lines=10, args=1, vars=1, tmps=4, ssa_vars=5, no_loops) + ; (lines=10, args=1, vars=1, tmps=%d, ssa_vars=5, no_loops) ; (before dfa pass) ; %s ; return [null] RANGE[0..0] @@ -108,7 +108,7 @@ BB0: 0009 RETURN null Test2::inTest2: - ; (lines=9, args=0, vars=0, tmps=4, ssa_vars=3, no_loops) + ; (lines=9, args=0, vars=0, tmps=%d, ssa_vars=3, no_loops) ; (before dfa pass) ; %s ; return [null] RANGE[0..0] diff --git a/ext/pcre/config0.m4 b/ext/pcre/config0.m4 index 5f74b5df6b5b8..cd2f60c78430d 100644 --- a/ext/pcre/config0.m4 +++ b/ext/pcre/config0.m4 @@ -66,7 +66,12 @@ else pcre2lib/pcre2_string_utils.c pcre2lib/pcre2_study.c pcre2lib/pcre2_substitute.c pcre2lib/pcre2_substring.c \ pcre2lib/pcre2_tables.c pcre2lib/pcre2_ucd.c pcre2lib/pcre2_valid_utf.c pcre2lib/pcre2_xclass.c \ pcre2lib/pcre2_find_bracket.c pcre2lib/pcre2_convert.c pcre2lib/pcre2_extuni.c pcre2lib/pcre2_script_run.c" - PHP_PCRE_CFLAGS="-Wno-implicit-fallthrough -DHAVE_CONFIG_H -I@ext_srcdir@/pcre2lib -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1" + + AX_CHECK_COMPILE_FLAG([-Wno-implicit-fallthrough], + [PHP_PCRE_CFLAGS="$PHP_PCRE_CFLAGS -Wno-implicit-fallthrough"],, + [-Werror]) + + PHP_PCRE_CFLAGS="$PHP_PCRE_CFLAGS -DHAVE_CONFIG_H -I@ext_srcdir@/pcre2lib -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1" AC_DEFINE(HAVE_BUNDLED_PCRE, 1, [ ]) AC_DEFINE(PCRE2_CODE_UNIT_WIDTH, 8, [ ]) diff --git a/ext/pdo_firebird/firebird_driver.c b/ext/pdo_firebird/firebird_driver.c index cbd7ee7cfd377..e570b684c5a4b 100644 --- a/ext/pdo_firebird/firebird_driver.c +++ b/ext/pdo_firebird/firebird_driver.c @@ -977,8 +977,7 @@ static int firebird_handle_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *v ZVAL_STRING(val, tmp); return 1; } - /* TODO Check this is correct? */ - ZEND_FALLTHROUGH; + return -1; case PDO_ATTR_FETCH_TABLE_NAMES: ZVAL_BOOL(val, H->fetch_table_names); diff --git a/ext/phar/stream.c b/ext/phar/stream.c index 752e15ed7176d..b53d4297c4227 100644 --- a/ext/phar/stream.c +++ b/ext/phar/stream.c @@ -782,6 +782,7 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from } if (PHAR_G(readonly) && (!pto || !pto->is_data)) { php_url_free(resource_from); + php_url_free(resource_to); php_error_docref(NULL, E_WARNING, "phar error: Write operations disabled by the php.ini setting phar.readonly"); return 0; } diff --git a/ext/readline/tests/libedit_callback_handler_install_001.phpt b/ext/readline/tests/libedit_callback_handler_install_001.phpt index 9cea634bae520..a98027fbb9ebc 100644 --- a/ext/readline/tests/libedit_callback_handler_install_001.phpt +++ b/ext/readline/tests/libedit_callback_handler_install_001.phpt @@ -1,5 +1,6 @@ --TEST-- readline_callback_handler_install(): Basic test +lsan disabled due to a leak on ubuntu focal only. --EXTENSIONS-- readline --SKIPIF-- @@ -8,6 +9,8 @@ if (READLINE_LIB != "libedit") die("skip libedit only"); ?> --INI-- zend.signal_check=0 +--ENV-- +LSAN_OPTIONS=detect_leaks=0 --FILE-- --INI-- zend.signal_check=0 +--ENV-- +LSAN_OPTIONS=detect_leaks=0 --FILE-- name) == Z_STRLEN_P(tmp) && - zend_binary_strncasecmp(ZSTR_VAL(ce->name), ZSTR_LEN(ce->name), Z_STRVAL_P(tmp), ZSTR_LEN(ce->name), ZSTR_LEN(ce->name)) == 0) { + ZVAL_DEREF(tmp); + if (Z_TYPE_P(tmp) == IS_STRING && + ZSTR_LEN(ce->name) == Z_STRLEN_P(tmp) && + zend_binary_strncasecmp(ZSTR_VAL(ce->name), ZSTR_LEN(ce->name), Z_STRVAL_P(tmp), ZSTR_LEN(ce->name), ZSTR_LEN(ce->name)) == 0 && + type_name) { /* TODO: namespace isn't stored */ encodePtr enc = NULL; @@ -1379,6 +1382,7 @@ static zval *to_zval_object_ex(zval *ret, encodeTypePtr type, xmlNodePtr data, z zend_class_entry *tmp; if ((classname = zend_hash_str_find_deref(SOAP_GLOBAL(class_map), type->type_str, strlen(type->type_str))) != NULL && + Z_TYPE_P(classname) == IS_STRING && (tmp = zend_fetch_class(Z_STR_P(classname), ZEND_FETCH_CLASS_AUTO)) != NULL) { ce = tmp; } @@ -3637,48 +3641,3 @@ void delete_encoder_persistent(zval *zv) assert(t->details.map == NULL); free(t); } - -/* Normalize leading backslash similarly to how the engine strips it away. */ -static inline zend_string *drop_leading_backslash(zend_string *str) { - if (ZSTR_VAL(str)[0] == '\\') { - return zend_string_init(ZSTR_VAL(str) + 1, ZSTR_LEN(str) - 1, false); - } else { - return zend_string_copy(str); - } -} - -static HashTable *create_normalized_classmap_copy(HashTable *class_map) -{ - HashTable *normalized = zend_new_array(zend_hash_num_elements(class_map)); - - zend_string *key; - zval *value; - ZEND_HASH_FOREACH_STR_KEY_VAL(class_map, key, value) { - ZVAL_DEREF(value); - - if (key != NULL && Z_TYPE_P(value) == IS_STRING) { - zval zv; - ZVAL_STR(&zv, drop_leading_backslash(Z_STR_P(value))); - zend_hash_add_new(normalized, key, &zv); - } - } ZEND_HASH_FOREACH_END(); - - return normalized; -} - -void create_normalized_classmap(zval *return_value, zval *class_map) -{ - /* Check if we need to make a copy. */ - zend_string *key; - zval *value; - ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARR_P(class_map), key, value) { - if (key == NULL || Z_TYPE_P(value) != IS_STRING || ZSTR_VAL(Z_STR_P(value))[0] == '\\') { - /* TODO: should probably throw in some of these cases to indicate programmer error, - * e.g. in the case where a non-string (after dereferencing) is provided. */ - RETURN_ARR(create_normalized_classmap_copy(Z_ARR_P(class_map))); - } - } ZEND_HASH_FOREACH_END(); - - /* We didn't have to make an actual copy, just increment the refcount. */ - RETURN_COPY(class_map); -} diff --git a/ext/soap/php_encoding.h b/ext/soap/php_encoding.h index 574ee4942a5ae..a4e16666e680f 100644 --- a/ext/soap/php_encoding.h +++ b/ext/soap/php_encoding.h @@ -214,8 +214,6 @@ encodePtr get_conversion(int encode); void delete_encoder(zval *zv); void delete_encoder_persistent(zval *zv); -void create_normalized_classmap(zval *return_value, zval *class_map); - extern const encode defaultEncoding[]; extern int numDefaultEncodings; diff --git a/ext/soap/php_http.c b/ext/soap/php_http.c index 1aa0d9f6f6df4..2aefdba0fb840 100644 --- a/ext/soap/php_http.c +++ b/ext/soap/php_http.c @@ -745,7 +745,7 @@ int make_http_soap_request(zval *this_ptr, PHP_MD5Update(&md5ctx, (unsigned char*)":", 1); PHP_MD5Update(&md5ctx, (unsigned char*)cnonce, 8); PHP_MD5Update(&md5ctx, (unsigned char*)":", 1); - /* TODO: Support for qop="auth-int" */ + /* TODO: Support for qop=auth-int */ PHP_MD5Update(&md5ctx, (unsigned char*)"auth", sizeof("auth")-1); PHP_MD5Update(&md5ctx, (unsigned char*)":", 1); } @@ -781,11 +781,11 @@ int make_http_soap_request(zval *this_ptr, } if ((tmp = zend_hash_str_find(Z_ARRVAL_P(digest), "qop", sizeof("qop")-1)) != NULL && Z_TYPE_P(tmp) == IS_STRING) { - /* TODO: Support for qop="auth-int" */ - smart_str_append_const(&soap_headers, "\", qop=\"auth"); - smart_str_append_const(&soap_headers, "\", nc=\""); + /* TODO: Support for qop=auth-int */ + smart_str_append_const(&soap_headers, "\", qop=auth"); + smart_str_append_const(&soap_headers, ", nc="); smart_str_appendl(&soap_headers, nc, 8); - smart_str_append_const(&soap_headers, "\", cnonce=\""); + smart_str_append_const(&soap_headers, ", cnonce=\""); smart_str_appendl(&soap_headers, cnonce, 8); } smart_str_append_const(&soap_headers, "\", response=\""); diff --git a/ext/soap/php_soap.h b/ext/soap/php_soap.h index c30a5a45c3a9e..33a071ed41819 100644 --- a/ext/soap/php_soap.h +++ b/ext/soap/php_soap.h @@ -98,7 +98,7 @@ struct _soapService { char *actor; char *uri; xmlCharEncodingHandlerPtr encoding; - zval class_map; + HashTable *class_map; int features; struct _soapHeader **soap_headers_ptr; int send_errors; diff --git a/ext/soap/soap.c b/ext/soap/soap.c index d0f51e6361717..114d3695789fa 100644 --- a/ext/soap/soap.c +++ b/ext/soap/soap.c @@ -529,6 +529,13 @@ static void soap_fault_dtor_properties(zval *obj) zval_ptr_dtor(Z_FAULT_DETAIL_P(obj)); zval_ptr_dtor(Z_FAULT_NAME_P(obj)); zval_ptr_dtor(Z_FAULT_HEADERFAULT_P(obj)); + ZVAL_EMPTY_STRING(Z_FAULT_STRING_P(obj)); + ZVAL_NULL(Z_FAULT_CODE_P(obj)); + ZVAL_NULL(Z_FAULT_CODENS_P(obj)); + ZVAL_NULL(Z_FAULT_ACTOR_P(obj)); + ZVAL_NULL(Z_FAULT_DETAIL_P(obj)); + ZVAL_NULL(Z_FAULT_NAME_P(obj)); + ZVAL_NULL(Z_FAULT_HEADERFAULT_P(obj)); } /* {{{ SoapFault constructor */ @@ -550,9 +557,6 @@ PHP_METHOD(SoapFault, __construct) Z_PARAM_ZVAL_OR_NULL(headerfault) ZEND_PARSE_PARAMETERS_END(); - /* Delete previously set properties */ - soap_fault_dtor_properties(ZEND_THIS); - if (code_str) { fault_code = ZSTR_VAL(code_str); fault_code_len = ZSTR_LEN(code_str); @@ -571,6 +575,9 @@ PHP_METHOD(SoapFault, __construct) RETURN_THROWS(); } + /* Delete previously set properties */ + soap_fault_dtor_properties(ZEND_THIS); + if (name != NULL && name_len == 0) { name = NULL; } @@ -830,7 +837,7 @@ PHP_METHOD(SoapServer, __construct) if ((tmp = zend_hash_str_find(ht, "classmap", sizeof("classmap")-1)) != NULL && Z_TYPE_P(tmp) == IS_ARRAY) { - create_normalized_classmap(&service->class_map, tmp); + service->class_map = zend_array_dup(Z_ARRVAL_P(tmp)); } if ((tmp = zend_hash_str_find(ht, "typemap", sizeof("typemap")-1)) != NULL && @@ -1296,7 +1303,7 @@ PHP_METHOD(SoapServer, handle) old_encoding = SOAP_GLOBAL(encoding); SOAP_GLOBAL(encoding) = service->encoding; old_class_map = SOAP_GLOBAL(class_map); - SOAP_GLOBAL(class_map) = Z_ARR(service->class_map); + SOAP_GLOBAL(class_map) = service->class_map; old_typemap = SOAP_GLOBAL(typemap); SOAP_GLOBAL(typemap) = service->typemap; old_features = SOAP_GLOBAL(features); @@ -1996,7 +2003,7 @@ PHP_METHOD(SoapClient, __construct) } if ((tmp = zend_hash_str_find(ht, "classmap", sizeof("classmap")-1)) != NULL && Z_TYPE_P(tmp) == IS_ARRAY) { - create_normalized_classmap(Z_CLIENT_CLASSMAP_P(this_ptr), tmp); + ZVAL_COPY(Z_CLIENT_CLASSMAP_P(this_ptr), tmp); } if ((tmp = zend_hash_str_find(ht, "typemap", sizeof("typemap")-1)) != NULL && @@ -4398,7 +4405,10 @@ static void delete_service(void *data) /* {{{ */ if (service->encoding) { xmlCharEncCloseFunc(service->encoding); } - zval_ptr_dtor(&service->class_map); + if (service->class_map) { + zend_hash_destroy(service->class_map); + FREE_HASHTABLE(service->class_map); + } zval_ptr_dtor(&service->soap_object); efree(service); } diff --git a/ext/soap/tests/SoapFault/gh14586.phpt b/ext/soap/tests/SoapFault/gh14586.phpt index 91a273da09d5d..7aa7c37eb542a 100644 --- a/ext/soap/tests/SoapFault/gh14586.phpt +++ b/ext/soap/tests/SoapFault/gh14586.phpt @@ -6,7 +6,17 @@ soap __construct(null, "x"); +try { + $sf->__construct("", ""); +} catch (ValueError) {} +$sf->__construct(null, "x", headerFault: []); +var_dump($sf->headerfault); +$sf->__construct(null, "x"); +var_dump($sf->headerfault); ?> DONE --EXPECT-- +array(0) { +} +NULL DONE diff --git a/ext/soap/tests/bug69280.phpt b/ext/soap/tests/bug69280.phpt deleted file mode 100644 index 8c4e6068591f9..0000000000000 --- a/ext/soap/tests/bug69280.phpt +++ /dev/null @@ -1,44 +0,0 @@ ---TEST-- -Bug #69280 (SoapClient classmap doesn't support fully qualified class name) ---EXTENSIONS-- -soap ---INI-- -soap.wsdl_cache_enabled=0 ---CREDITS-- -champetier dot etienne at gmail dot com ---FILE-- -__soapCall('TestMethod', [$parameters], [ - 'uri' => 'http://tempuri.org/', - 'soapaction' => '' - ] - ); - } - - public function __doRequest(string $request, string $location, string $action, int $version, bool $oneWay = false): ?string { - die($request); - } -} - -$a = new TestWS(__DIR__ . '/bug69280.wsdl', ['classmap' => [ - 'AbstractClass' => '\AbstractClass', - 'RealClass1' => '\RealClass1', -]]); -$r1 = new \RealClass1(); -$r1->prop = "prop"; -$r1->prop1 = "prop1"; -$a->TestMethod($r1); -?> ---EXPECT-- - -propprop1 diff --git a/ext/soap/tests/bug69280.wsdl b/ext/soap/tests/bug69280.wsdl deleted file mode 100644 index 8615ac77cdcf2..0000000000000 --- a/ext/soap/tests/bug69280.wsdl +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ext/soap/tests/bugs/bug55639.phpt b/ext/soap/tests/bugs/bug55639.phpt new file mode 100644 index 0000000000000..16d7f7a377193 --- /dev/null +++ b/ext/soap/tests/bugs/bug55639.phpt @@ -0,0 +1,65 @@ +--TEST-- +Bug #55639 (Digest authentication dont work) +--INI-- +soap.wsdl_cache_enabled=0 +--EXTENSIONS-- +soap +--SKIPIF-- + +--FILE-- + 'http://' . PHP_CLI_SERVER_ADDRESS, + 'uri' => 'misc-uri', + 'authentication' => SOAP_AUTHENTICATION_DIGEST, + 'realm' => 'myrealm', + 'login' => 'user', + 'password' => 'pass', + 'trace' => true, +]); + +try { + $client->__soapCall("foo", []); +} catch (Throwable $e) { + echo $e->getMessage(), "\n"; +} + +$headers = $client->__getLastRequestHeaders(); +var_dump($headers); + +?> +--EXPECTF-- +Unauthorized +string(%d) "POST / HTTP/1.1 +Host: %s +Connection: Keep-Alive +User-Agent: %s +Content-Type: text/xml; charset=utf-8 +SOAPAction: "misc-uri#foo" +Content-Length: %d +Authorization: Digest username="user", realm="realm", nonce="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", uri="/", qop=auth, nc=00000001, cnonce="%s", response="%s", opaque="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + +" diff --git a/ext/spl/spl_directory.c b/ext/spl/spl_directory.c index 417b38fa99ecf..b6ca7c8da65ba 100644 --- a/ext/spl/spl_directory.c +++ b/ext/spl/spl_directory.c @@ -230,7 +230,7 @@ PHPAPI zend_string *spl_filesystem_object_get_path(spl_filesystem_object *intern return zend_string_copy(intern->path); } /* }}} */ -static inline zend_result spl_filesystem_object_get_file_name(spl_filesystem_object *intern) /* {{{ */ +static zend_result spl_filesystem_object_get_file_name(spl_filesystem_object *intern) /* {{{ */ { if (intern->file_name) { /* already known */ diff --git a/ext/spl/spl_heap.c b/ext/spl/spl_heap.c index 5d61741425185..2d79619ef1f4e 100644 --- a/ext/spl/spl_heap.c +++ b/ext/spl/spl_heap.c @@ -505,7 +505,7 @@ static zend_result spl_heap_object_count_elements(zend_object *object, zend_long } /* }}} */ -static inline HashTable* spl_heap_object_get_debug_info(zend_class_entry *ce, zend_object *obj) { /* {{{ */ +static HashTable* spl_heap_object_get_debug_info(zend_class_entry *ce, zend_object *obj) { /* {{{ */ spl_heap_object *intern = spl_heap_from_obj(obj); zval tmp, heap_array; zend_string *pnstr; diff --git a/ext/standard/file.h b/ext/standard/file.h index 2b2307cab7278..0f3ccd7c27ab3 100644 --- a/ext/standard/file.h +++ b/ext/standard/file.h @@ -60,6 +60,12 @@ PHPAPI ssize_t php_fputcsv(php_stream *stream, zval *fields, char delimiter, cha #define PHP_FILE_APPEND (1 << 3) #define PHP_FILE_NO_DEFAULT_CONTEXT (1 << 4) +#ifndef _WIN32 +#define PHP_TIMEOUT_ULL_MAX ULLONG_MAX +#else +#define PHP_TIMEOUT_ULL_MAX UINT64_MAX +#endif + typedef enum _php_meta_tags_token { TOK_EOF = 0, TOK_OPENTAG, diff --git a/ext/standard/fsock.c b/ext/standard/fsock.c index 9e1a53c0ec2a0..cb7a471e935a6 100644 --- a/ext/standard/fsock.c +++ b/ext/standard/fsock.c @@ -73,14 +73,27 @@ static void php_fsockopen_stream(INTERNAL_FUNCTION_PARAMETERS, int persistent) } /* prepare the timeout value for use */ + if (timeout != -1.0 && !(timeout >= 0.0 && timeout <= (double) PHP_TIMEOUT_ULL_MAX / 1000000.0)) { + if (port > 0) { + efree(hostname); + } + + if (hashkey) { + efree(hashkey); + } + + zend_argument_value_error(6, "must be -1 or between 0 and " ZEND_ULONG_FMT, ((double) PHP_TIMEOUT_ULL_MAX / 1000000.0)); + RETURN_THROWS(); + } else { #ifndef PHP_WIN32 - conv = (time_t) (timeout * 1000000.0); - tv.tv_sec = conv / 1000000; + conv = (time_t) (timeout * 1000000.0); + tv.tv_sec = conv / 1000000; #else - conv = (long) (timeout * 1000000.0); - tv.tv_sec = conv / 1000000; + conv = (long) (timeout * 1000000.0); + tv.tv_sec = conv / 1000000; #endif - tv.tv_usec = conv % 1000000; + tv.tv_usec = conv % 1000000; + } stream = php_stream_xport_create(hostname, hostname_len, REPORT_ERRORS, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, hashkey, &tv, NULL, &errstr, &err); diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index daaaa41b00f9b..c2ddd26f7582b 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -792,8 +792,19 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, } else if (!strncasecmp(http_header_line, "Content-Type:", sizeof("Content-Type:")-1)) { php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, http_header_value, 0); } else if (!strncasecmp(http_header_line, "Content-Length:", sizeof("Content-Length:")-1)) { - file_size = atoi(http_header_value); - php_stream_notify_file_size(context, file_size, http_header_line, 0); + /* https://www.rfc-editor.org/rfc/rfc9110.html#name-content-length */ + const char *ptr = http_header_value; + /* must contain only digits, no + or - symbols */ + if (*ptr >= '0' && *ptr <= '9') { + char *endptr = NULL; + size_t parsed = ZEND_STRTOUL(ptr, &endptr, 10); + /* check whether there was no garbage in the header value and the conversion was successful */ + if (endptr && !*endptr) { + /* truncate for 32-bit such that no negative file sizes occur */ + file_size = MIN(parsed, ZEND_LONG_MAX); + php_stream_notify_file_size(context, file_size, http_header_line, 0); + } + } } else if ( !strncasecmp(http_header_line, "Transfer-Encoding:", sizeof("Transfer-Encoding:")-1) && !strncasecmp(http_header_value, "Chunked", sizeof("Chunked")-1) diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c index be1d4347a3f3c..134b966107cf6 100644 --- a/ext/standard/streamsfuncs.c +++ b/ext/standard/streamsfuncs.c @@ -33,13 +33,11 @@ #ifndef PHP_WIN32 #define php_select(m, r, w, e, t) select(m, r, w, e, t) typedef unsigned long long php_timeout_ull; -#define PHP_TIMEOUT_ULL_MAX ULLONG_MAX #else #include "win32/select.h" #include "win32/sockets.h" #include "win32/console.h" typedef unsigned __int64 php_timeout_ull; -#define PHP_TIMEOUT_ULL_MAX UINT64_MAX #endif #define GET_CTX_OPT(stream, wrapper, name, val) (PHP_STREAM_CONTEXT(stream) && NULL != (val = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), wrapper, name))) @@ -127,6 +125,9 @@ PHP_FUNCTION(stream_socket_client) if (timeout_is_null) { timeout = (double)FG(default_socket_timeout); + } else if (!zend_finite(timeout)) { + zend_argument_value_error(4, "must be a finite value"); + RETURN_THROWS(); } context = php_stream_context_from_zval(zcontext, flags & PHP_FILE_NO_DEFAULT_CONTEXT); @@ -279,6 +280,9 @@ PHP_FUNCTION(stream_socket_accept) if (timeout_is_null) { timeout = (double)FG(default_socket_timeout); + } else if (!zend_finite(timeout)) { + zend_argument_value_error(2, "must be a finite value"); + RETURN_THROWS(); } php_stream_from_zval(stream, zstream); diff --git a/ext/standard/tests/file/bug52820.phpt b/ext/standard/tests/file/bug52820.phpt index ff816178cef67..2d3cedad87944 100644 --- a/ext/standard/tests/file/bug52820.phpt +++ b/ext/standard/tests/file/bug52820.phpt @@ -46,21 +46,21 @@ echo "\nDone.\n"; temp stream \(close after\): About to rewind! (\* processing: file:\/\/\/i_dont_exist\/\n)?\* Couldn't open file \/i_dont_exist\/ -\* Closing connection( -?\d+)? +\* [Cc]losing connection( #?-?\d+)? memory stream \(close after\): About to rewind! (\* processing: file:\/\/\/i_dont_exist\/\n)?\* Couldn't open file \/i_dont_exist\/ -\* Closing connection( -?\d+)? +\* [Cc]losing connection( #?-?\d+)? temp stream \(leak\): About to rewind! (\* processing: file:\/\/\/i_dont_exist\/\n)?\* Couldn't open file \/i_dont_exist\/ -\* Closing connection( -?\d+)? +\* [Cc]losing connection( #?-?\d+)? memory stream \(leak\): About to rewind! (\* processing: file:\/\/\/i_dont_exist\/\n)?\* Couldn't open file \/i_dont_exist\/ -\* Closing connection( -?\d+)? +\* [Cc]losing connection( #?-?\d+)? Done\. diff --git a/ext/standard/tests/http/gh15034.phpt b/ext/standard/tests/http/gh15034.phpt new file mode 100644 index 0000000000000..c0841ef954437 --- /dev/null +++ b/ext/standard/tests/http/gh15034.phpt @@ -0,0 +1,44 @@ +--TEST-- +GH-15034 (Integer overflow on stream_notification_callback byte_max parameter with files bigger than 2GB) +--SKIPIF-- + +--INI-- +allow_url_fopen=1 +--FILE-- + $pid, 'uri' => $uri] = http_server($responses); + +$params = ['notification' => function( + int $notification_code, + int $severity, + ?string $message, + int $message_code, + int $bytes_transferred, + int $bytes_max +) { + global $max; + $max = $bytes_max; +}]; +$contextResource = stream_context_create([], $params); + +$resource = fopen($uri, 'r', false, $contextResource); +fclose($resource); + +http_server_kill($pid); + +var_dump($max); +?> +--EXPECT-- +int(3000000000) diff --git a/ext/standard/tests/streams/gh14780.phpt b/ext/standard/tests/streams/gh14780.phpt new file mode 100644 index 0000000000000..064e496b17538 --- /dev/null +++ b/ext/standard/tests/streams/gh14780.phpt @@ -0,0 +1,38 @@ +--TEST-- +GH-14780: p(f)sockopen overflow on timeout. +--SKIPIF-- + +--FILE-- +getMessage() . PHP_EOL; +} +try { + pfsockopen('udp://127.0.0.1', '63844', $code, $err, (PHP_INT_MIN/100000)-1); +} catch (\ValueError $e) { + echo $e->getMessage() . PHP_EOL; +} +var_dump(pfsockopen('udp://127.0.0.1', '63844', $code, $err, -1)); +try { + pfsockopen('udp://127.0.0.1', '63844', $code, $err, NAN); +} catch (\ValueError $e) { + echo $e->getMessage() . PHP_EOL; +} +try { + pfsockopen('udp://127.0.0.1', '63844', $code, $err, INF); +} catch (\ValueError $e) { + echo $e->getMessage(); +} +?> +--EXPECTF-- +pfsockopen(): Argument #6 must be -1 or between 0 and %s +pfsockopen(): Argument #6 must be -1 or between 0 and %s +resource(%d) of type (persistent stream) +pfsockopen(): Argument #6 must be -1 or between 0 and %s +pfsockopen(): Argument #6 must be -1 or between 0 and %s diff --git a/ext/standard/tests/streams/gh14930.phpt b/ext/standard/tests/streams/gh14930.phpt index 7e034a81235a7..54a187332ab03 100644 --- a/ext/standard/tests/streams/gh14930.phpt +++ b/ext/standard/tests/streams/gh14930.phpt @@ -1,5 +1,7 @@ --TEST-- GH-14930: Custom stream wrapper dir_readdir output truncated to 255 characters in PHP 8.3 +--XFAIL-- +Fix is an ABI break so reverted from 8.3 --FILE-- getMessage(), "\n"; + } +} +fclose($socket); + +foreach ([NAN, -NAN, INF, -INF] as $value) { + try { + stream_socket_client("tcp://0.0.0.0:14781", timeout: $value); + } catch (ValueError $e) { + echo $e->getMessage(), "\n"; + } +} +?> +--EXPECT-- +stream_socket_accept(): Argument #2 ($timeout) must be a finite value +stream_socket_accept(): Argument #2 ($timeout) must be a finite value +stream_socket_accept(): Argument #2 ($timeout) must be a finite value +stream_socket_accept(): Argument #2 ($timeout) must be a finite value +stream_socket_client(): Argument #4 ($timeout) must be a finite value +stream_socket_client(): Argument #4 ($timeout) must be a finite value +stream_socket_client(): Argument #4 ($timeout) must be a finite value +stream_socket_client(): Argument #4 ($timeout) must be a finite value diff --git a/ext/standard/url_scanner_ex.re b/ext/standard/url_scanner_ex.re index 77b4d79793b65..b22cf3cc49c15 100644 --- a/ext/standard/url_scanner_ex.re +++ b/ext/standard/url_scanner_ex.re @@ -736,6 +736,7 @@ static inline int php_url_scanner_add_var_impl(const char *name, size_t name_len zend_string *encoded; url_adapt_state_ex_t *url_state; php_output_handler_func_t handler; + bool should_start = false; if (type) { url_state = &BG(url_adapt_session_ex); @@ -747,7 +748,7 @@ static inline int php_url_scanner_add_var_impl(const char *name, size_t name_len if (!url_state->active) { php_url_scanner_ex_activate(type); - php_output_start_internal(ZEND_STRL("URL-Rewriter"), handler, 0, PHP_OUTPUT_HANDLER_STDFLAGS); + should_start = true; url_state->active = 1; } @@ -786,6 +787,10 @@ static inline int php_url_scanner_add_var_impl(const char *name, size_t name_len smart_str_free(&hname); smart_str_free(&hvalue); + if (should_start) { + php_output_start_internal(ZEND_STRL("URL-Rewriter"), handler, 0, PHP_OUTPUT_HANDLER_STDFLAGS); + } + return SUCCESS; } diff --git a/ext/tidy/tests/open_basedir/test.html b/ext/tidy/tests/open_basedir/test.html new file mode 100644 index 0000000000000..5b9b4b71f8f0e --- /dev/null +++ b/ext/tidy/tests/open_basedir/test.html @@ -0,0 +1 @@ +

my epic string

diff --git a/ext/tidy/tests/open_basedir_failure_config.phpt b/ext/tidy/tests/open_basedir_failure_config.phpt new file mode 100644 index 0000000000000..b14393b7b801c --- /dev/null +++ b/ext/tidy/tests/open_basedir_failure_config.phpt @@ -0,0 +1,48 @@ +--TEST-- +Tidy with basedir restriction failure on configuration file +--EXTENSIONS-- +tidy +--INI-- +open_basedir={PWD}/open_basedir +--FILE-- +repairString('my epic string', 'my_config_file.ini'); + +echo "=== tidy_parse_string ===\n"; +tidy_parse_string('my epic string', 'my_config_file.ini'); + +echo "=== tidy_parse_file ===\n"; +tidy_parse_file(__DIR__.'/open_basedir/test.html', 'my_config_file.ini'); + +echo "=== __construct ===\n"; +$tidy = new tidy(__DIR__.'/open_basedir/test.html', 'my_config_file.ini'); + +echo "=== parseFile ===\n"; +$tidy = new tidy; +$tidy->parseFile(__DIR__.'/open_basedir/test.html', 'my_config_file.ini'); + +echo "=== parseString ===\n"; +$tidy = new tidy; +$tidy->parseString('my epic string', 'my_config_file.ini'); +?> +--EXPECTF-- +=== repairString === + +Warning: tidy::repairString(): open_basedir restriction in effect. File(my_config_file.ini) is not within the allowed path(s): (%sopen_basedir) in %s on line %d +=== tidy_parse_string === + +Warning: tidy_parse_string(): open_basedir restriction in effect. File(my_config_file.ini) is not within the allowed path(s): (%sopen_basedir) in %s on line %d +=== tidy_parse_file === + +Warning: tidy_parse_file(): open_basedir restriction in effect. File(my_config_file.ini) is not within the allowed path(s): (%sopen_basedir) in %s on line %d +=== __construct === + +Warning: tidy::__construct(): open_basedir restriction in effect. File(my_config_file.ini) is not within the allowed path(s): (%sopen_basedir) in %s on line %d +=== parseFile === + +Warning: tidy::parseFile(): open_basedir restriction in effect. File(my_config_file.ini) is not within the allowed path(s): (%sopen_basedir) in %s on line %d +=== parseString === + +Warning: tidy::parseString(): open_basedir restriction in effect. File(my_config_file.ini) is not within the allowed path(s): (%sopen_basedir) in %s on line %d diff --git a/ext/tidy/tidy.c b/ext/tidy/tidy.c index 38e97a71a2a15..831fcb3815399 100644 --- a/ext/tidy/tidy.c +++ b/ext/tidy/tidy.c @@ -74,19 +74,6 @@ } \ obj = Z_TIDY_P(object); \ -#define TIDY_APPLY_CONFIG(_doc, _val_str, _val_ht) \ - if (_val_ht) { \ - _php_tidy_apply_config_array(_doc, _val_ht); \ - } else if (_val_str) { \ - TIDY_OPEN_BASE_DIR_CHECK(ZSTR_VAL(_val_str)); \ - php_tidy_load_config(_doc, ZSTR_VAL(_val_str)); \ - } - -#define TIDY_OPEN_BASE_DIR_CHECK(filename) \ -if (php_check_open_basedir(filename)) { \ - RETURN_FALSE; \ -} \ - #define TIDY_SET_DEFAULT_CONFIG(_doc) \ if (TG(default_config) && TG(default_config)[0]) { \ php_tidy_load_config(_doc, TG(default_config)); \ @@ -221,6 +208,19 @@ static void php_tidy_load_config(TidyDoc doc, const char *path) } } +static zend_result php_tidy_apply_config(TidyDoc doc, zend_string *str_string, HashTable *ht_options) +{ + if (ht_options) { + return _php_tidy_apply_config_array(doc, ht_options); + } else if (str_string) { + if (php_check_open_basedir(ZSTR_VAL(str_string))) { + return FAILURE; + } + php_tidy_load_config(doc, ZSTR_VAL(str_string)); + } + return SUCCESS; +} + static int _php_tidy_set_tidy_opt(TidyDoc doc, char *optname, zval *value) { TidyOption opt = tidyGetOptionByName(doc, optname); @@ -329,9 +329,9 @@ static void php_tidy_quick_repair(INTERNAL_FUNCTION_PARAMETERS, bool is_file) TIDY_SET_DEFAULT_CONFIG(doc); - TIDY_APPLY_CONFIG(doc, config_str, config_ht); - - if(enc_len) { + if (php_tidy_apply_config(doc, config_str, config_ht) != SUCCESS) { + RETVAL_FALSE; + } else if (enc_len) { if (tidySetCharEncoding(doc, enc) < 0) { php_error_docref(NULL, E_WARNING, "Could not set encoding \"%s\"", enc); RETVAL_FALSE; @@ -1024,9 +1024,8 @@ PHP_FUNCTION(tidy_parse_string) tidy_instantiate(tidy_ce_doc, return_value); obj = Z_TIDY_P(return_value); - TIDY_APPLY_CONFIG(obj->ptdoc->doc, options_str, options_ht); - - if (php_tidy_parse_string(obj, ZSTR_VAL(input), (uint32_t)ZSTR_LEN(input), enc) == FAILURE) { + if (php_tidy_apply_config(obj->ptdoc->doc, options_str, options_ht) != SUCCESS + || php_tidy_parse_string(obj, ZSTR_VAL(input), (uint32_t)ZSTR_LEN(input), enc) == FAILURE) { zval_ptr_dtor(return_value); RETURN_FALSE; } @@ -1093,9 +1092,8 @@ PHP_FUNCTION(tidy_parse_file) tidy_instantiate(tidy_ce_doc, return_value); obj = Z_TIDY_P(return_value); - TIDY_APPLY_CONFIG(obj->ptdoc->doc, options_str, options_ht); - - if (php_tidy_parse_string(obj, ZSTR_VAL(contents), (uint32_t)ZSTR_LEN(contents), enc) == FAILURE) { + if (php_tidy_apply_config(obj->ptdoc->doc, options_str, options_ht) != SUCCESS + || php_tidy_parse_string(obj, ZSTR_VAL(contents), (uint32_t)ZSTR_LEN(contents), enc) == FAILURE) { zval_ptr_dtor(return_value); RETVAL_FALSE; } @@ -1388,7 +1386,11 @@ PHP_METHOD(tidy, __construct) RETURN_THROWS(); } - TIDY_APPLY_CONFIG(obj->ptdoc->doc, options_str, options_ht); + if (php_tidy_apply_config(obj->ptdoc->doc, options_str, options_ht) != SUCCESS) { + /* TODO: this is the constructor, we should throw probably... */ + zend_string_release_ex(contents, 0); + RETURN_FALSE; + } php_tidy_parse_string(obj, ZSTR_VAL(contents), (uint32_t)ZSTR_LEN(contents), enc); @@ -1427,9 +1429,8 @@ PHP_METHOD(tidy, parseFile) RETURN_THROWS(); } - TIDY_APPLY_CONFIG(obj->ptdoc->doc, options_str, options_ht); - - if (php_tidy_parse_string(obj, ZSTR_VAL(contents), (uint32_t)ZSTR_LEN(contents), enc) == FAILURE) { + if (php_tidy_apply_config(obj->ptdoc->doc, options_str, options_ht) != SUCCESS + || php_tidy_parse_string(obj, ZSTR_VAL(contents), (uint32_t)ZSTR_LEN(contents), enc) == FAILURE) { RETVAL_FALSE; } else { RETVAL_TRUE; @@ -1461,9 +1462,8 @@ PHP_METHOD(tidy, parseString) TIDY_SET_CONTEXT; obj = Z_TIDY_P(object); - TIDY_APPLY_CONFIG(obj->ptdoc->doc, options_str, options_ht); - - if(php_tidy_parse_string(obj, ZSTR_VAL(input), (uint32_t)ZSTR_LEN(input), enc) == SUCCESS) { + if (php_tidy_apply_config(obj->ptdoc->doc, options_str, options_ht) == SUCCESS + && php_tidy_parse_string(obj, ZSTR_VAL(input), (uint32_t)ZSTR_LEN(input), enc) == SUCCESS) { RETURN_TRUE; } diff --git a/main/network.c b/main/network.c index ba1080e8fe10b..d44f3cae2e4b9 100644 --- a/main/network.c +++ b/main/network.c @@ -861,7 +861,7 @@ php_socket_t php_network_connect_socket_to_host(const char *host, unsigned short #if HAVE_IPV6 && HAVE_INET_PTON struct sockaddr_in6 in6; #endif - } local_address; + } local_address = {0}; int local_address_len = 0; if (sa->sa_family == AF_INET) { @@ -873,7 +873,6 @@ php_socket_t php_network_connect_socket_to_host(const char *host, unsigned short local_address_len = sizeof(struct sockaddr_in); local_address.in4.sin_family = sa->sa_family; local_address.in4.sin_port = htons(bindport); - memset(&(local_address.in4.sin_zero), 0, sizeof(local_address.in4.sin_zero)); } } #if HAVE_IPV6 && HAVE_INET_PTON diff --git a/main/php_streams.h b/main/php_streams.h index 33b14ff4eb3df..31b80de986053 100644 --- a/main/php_streams.h +++ b/main/php_streams.h @@ -107,7 +107,11 @@ typedef struct _php_stream_statbuf { } php_stream_statbuf; typedef struct _php_stream_dirent { +#ifdef NAME_MAX + char d_name[NAME_MAX + 1]; +#else char d_name[MAXPATHLEN]; +#endif unsigned char d_type; } php_stream_dirent; diff --git a/main/php_version.h b/main/php_version.h index afe90c887c4e2..130145550ef35 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 10 -#define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.3.10-dev" -#define PHP_VERSION_ID 80310 +#define PHP_RELEASE_VERSION 11 +#define PHP_EXTRA_VERSION "" +#define PHP_VERSION "8.3.11" +#define PHP_VERSION_ID 80311 diff --git a/sapi/phpdbg/phpdbg.h b/sapi/phpdbg/phpdbg.h index 76630dc53c7d4..88578168c6ea7 100644 --- a/sapi/phpdbg/phpdbg.h +++ b/sapi/phpdbg/phpdbg.h @@ -254,6 +254,7 @@ ZEND_BEGIN_MODULE_GLOBALS(phpdbg) HashTable watch_recreation; /* watch elements pending recreation of their respective watchpoints */ HashTable watch_free; /* pointers to watch for being freed */ HashTable *watchlist_mem; /* triggered watchpoints */ + HashTable *original_watchlist_mem; /* the original allocation for watchlist_mem, used when watchlist_mem has changed temporarily */ HashTable *watchlist_mem_backup; /* triggered watchpoints backup table while iterating over it */ bool watchpoint_hit; /* a watchpoint was hit */ void (*original_free_function)(void *); /* the original AG(mm_heap)->_free function */ diff --git a/sapi/phpdbg/phpdbg_cmd.c b/sapi/phpdbg/phpdbg_cmd.c index f5701384d3a20..6249e79417f32 100644 --- a/sapi/phpdbg/phpdbg_cmd.c +++ b/sapi/phpdbg/phpdbg_cmd.c @@ -23,6 +23,10 @@ #include "phpdbg_prompt.h" #include "phpdbg_io.h" +#ifdef HAVE_UNISTD_H +# include +#endif + ZEND_EXTERN_MODULE_GLOBALS(phpdbg) static inline const char *phpdbg_command_name(const phpdbg_command_t *command, char *buffer) { @@ -746,17 +750,29 @@ PHPDBG_API char *phpdbg_read_input(const char *buffered) /* {{{ */ if ((PHPDBG_G(flags) & (PHPDBG_IS_STOPPING | PHPDBG_IS_RUNNING)) != PHPDBG_IS_STOPPING) { if (buffered == NULL) { #ifdef HAVE_PHPDBG_READLINE - char *cmd = readline(phpdbg_get_prompt()); - PHPDBG_G(last_was_newline) = 1; +# ifdef HAVE_UNISTD_H + /* EOF makes readline write prompt again in local console mode and + ignored if compiled without readline integration. */ + if (!isatty(PHPDBG_G(io)[PHPDBG_STDIN].fd)) { + char buf[PHPDBG_MAX_CMD]; + phpdbg_write("%s", phpdbg_get_prompt()); + phpdbg_consume_stdin_line(buf); + buffer = estrdup(buf); + } else +# endif + { + char *cmd = readline(phpdbg_get_prompt()); + PHPDBG_G(last_was_newline) = 1; + + if (!cmd) { + PHPDBG_G(flags) |= PHPDBG_IS_QUITTING; + zend_bailout(); + } - if (!cmd) { - PHPDBG_G(flags) |= PHPDBG_IS_QUITTING; - zend_bailout(); + add_history(cmd); + buffer = estrdup(cmd); + free(cmd); } - - add_history(cmd); - buffer = estrdup(cmd); - free(cmd); #else phpdbg_write("%s", phpdbg_get_prompt()); phpdbg_consume_stdin_line(buf); diff --git a/sapi/phpdbg/phpdbg_info.c b/sapi/phpdbg/phpdbg_info.c index 0a1e7570a493c..b6c48d548f1f0 100644 --- a/sapi/phpdbg/phpdbg_info.c +++ b/sapi/phpdbg/phpdbg_info.c @@ -403,12 +403,15 @@ PHPDBG_INFO(classes) /* {{{ */ phpdbg_print_class_name(ce); if (ce->parent) { - zend_class_entry *pce; - pce = ce->parent; - do { - phpdbg_out("|-------- "); - phpdbg_print_class_name(pce); - } while ((pce = pce->parent)); + if (ce->ce_flags & ZEND_ACC_LINKED) { + zend_class_entry *pce = ce->parent; + do { + phpdbg_out("|-------- "); + phpdbg_print_class_name(pce); + } while ((pce = pce->parent)); + } else { + phpdbg_writeln("|-------- User Class %s (not yet linked because declaration for parent was not encountered when declaring the class)", ZSTR_VAL(ce->parent_name)); + } } if (ce->info.user.filename) { diff --git a/sapi/phpdbg/phpdbg_prompt.c b/sapi/phpdbg/phpdbg_prompt.c index 8fc7f4fe36ffe..c054326084425 100644 --- a/sapi/phpdbg/phpdbg_prompt.c +++ b/sapi/phpdbg/phpdbg_prompt.c @@ -1549,6 +1549,8 @@ int phpdbg_interactive(bool allow_async_unsafe, char *input) /* {{{ */ ret = phpdbg_stack_execute(&stack, allow_async_unsafe); } zend_catch { phpdbg_stack_free(&stack); + phpdbg_destroy_input(&input); + /* TODO: should use proper unwinding instead of bailing out */ zend_bailout(); } zend_end_try(); diff --git a/sapi/phpdbg/phpdbg_watch.c b/sapi/phpdbg/phpdbg_watch.c index dedad8b2289b2..3dc638ab9833d 100644 --- a/sapi/phpdbg/phpdbg_watch.c +++ b/sapi/phpdbg/phpdbg_watch.c @@ -516,7 +516,9 @@ phpdbg_watch_element *phpdbg_add_watch_element(phpdbg_watchpoint_t *watch, phpdb phpdbg_watch_element *old_element; watch = res->ptr; if ((old_element = zend_hash_find_ptr(&watch->elements, element->str))) { - phpdbg_free_watch_element(element); + if (element != old_element) { + phpdbg_free_watch_element(element); + } return old_element; } } @@ -1471,11 +1473,13 @@ void phpdbg_setup_watchpoints(void) { /* put these on a separate page, to avoid conflicts with other memory */ PHPDBG_G(watchlist_mem) = malloc(phpdbg_pagesize > sizeof(HashTable) ? phpdbg_pagesize : sizeof(HashTable)); + PHPDBG_G(original_watchlist_mem) = PHPDBG_G(watchlist_mem); zend_hash_init(PHPDBG_G(watchlist_mem), phpdbg_pagesize / (sizeof(Bucket) + sizeof(uint32_t)), NULL, NULL, 1); PHPDBG_G(watchlist_mem_backup) = malloc(phpdbg_pagesize > sizeof(HashTable) ? phpdbg_pagesize : sizeof(HashTable)); zend_hash_init(PHPDBG_G(watchlist_mem_backup), phpdbg_pagesize / (sizeof(Bucket) + sizeof(uint32_t)), NULL, NULL, 1); PHPDBG_G(watch_tmp) = NULL; + PHPDBG_G(watchpoint_hit) = false; #ifdef HAVE_USERFAULTFD_WRITEFAULT PHPDBG_G(watch_userfaultfd) = syscall(SYS_userfaultfd, O_CLOEXEC); @@ -1517,8 +1521,8 @@ void phpdbg_destroy_watchpoints(void) { zend_hash_destroy(&PHPDBG_G(watch_recreation)); zend_hash_destroy(&PHPDBG_G(watch_free)); zend_hash_destroy(&PHPDBG_G(watch_collisions)); - zend_hash_destroy(PHPDBG_G(watchlist_mem)); - free(PHPDBG_G(watchlist_mem)); + zend_hash_destroy(PHPDBG_G(original_watchlist_mem)); + free(PHPDBG_G(original_watchlist_mem)); zend_hash_destroy(PHPDBG_G(watchlist_mem_backup)); free(PHPDBG_G(watchlist_mem_backup)); } diff --git a/sapi/phpdbg/tests/gh15210_001.phpt b/sapi/phpdbg/tests/gh15210_001.phpt new file mode 100644 index 0000000000000..119ed6e460eed --- /dev/null +++ b/sapi/phpdbg/tests/gh15210_001.phpt @@ -0,0 +1,36 @@ +--TEST-- +GH-15210 use after free after continue +--SKIPIF-- + +--PHPDBG-- +b 4 +r +w $a[0] +c +q +--FILE-- + +--EXPECTF-- +[Successful compilation of %s] +prompt> [Breakpoint #0 added at %s:%d] +prompt> [Breakpoint #0 at %s:%d, hits: 1] +>00004: $a[0] = 1; + 00005: ?> + 00006: +prompt> [Added watchpoint #0 for $a[0]] +prompt> [Breaking on watchpoint $a[0]] +Old value: [Breaking on watchpoint $a[0]] +Old value: 0 +New value: 1 +>00002: header_register_callback(function() { echo "sent";}); + 00003: $a = [0]; + 00004: $a[0] = 1; +prompt> [$a[0] has been removed, removing watchpoint] diff --git a/sapi/phpdbg/tests/gh15210_002.phpt b/sapi/phpdbg/tests/gh15210_002.phpt new file mode 100644 index 0000000000000..7848500a9e949 --- /dev/null +++ b/sapi/phpdbg/tests/gh15210_002.phpt @@ -0,0 +1,42 @@ +--TEST-- +GH-15210 use after free after continue +--SKIPIF-- + +--PHPDBG-- +b 4 +r +w $a[0] +c +c +q +--FILE-- + +--EXPECTF-- +[Successful compilation of %s] +prompt> [Breakpoint #0 added at %s:%d] +prompt> [Breakpoint #0 at %s:%d, hits: 1] +>00004: $a[0] = 1; + 00005: ?> + 00006: +prompt> [Added watchpoint #0 for $a[0]] +prompt> [Breaking on watchpoint $a[0]] +Old value: [Breaking on watchpoint $a[0]] +Old value: 0 +New value: 1 +>00002: header_register_callback(function() { echo "sent";}); + 00003: $a = [0]; + 00004: $a[0] = 1; +prompt> sent0 +New value: 1 + +[$a[0] has been removed, removing watchpoint] +[Script ended normally] +prompt> diff --git a/sapi/phpdbg/tests/gh15268.phpt b/sapi/phpdbg/tests/gh15268.phpt new file mode 100644 index 0000000000000..1afd29fb34668 --- /dev/null +++ b/sapi/phpdbg/tests/gh15268.phpt @@ -0,0 +1,25 @@ +--TEST-- +GH-15268 (heap buffer overflow in phpdbg (zend_hash_num_elements() Zend/zend_hash.h)) +--SKIPIF-- + +--FILE-- + +--PHPDBG-- +i classes +q +--EXPECTF-- +[Successful compilation of %s] +prompt> [User Classes (2)] +User Class B (0) +|-------- User Class A (not yet linked because declaration for parent was not encountered when declaring the class) +|---- in %s on line %d +User Class A (0) +|---- in %s on line %d +prompt> diff --git a/tests/output/gh15179.phpt b/tests/output/gh15179.phpt new file mode 100644 index 0000000000000..207728446df75 --- /dev/null +++ b/tests/output/gh15179.phpt @@ -0,0 +1,18 @@ +--TEST-- +GH-15179 (Segmentation fault (null pointer dereference) in ext/standard/url_scanner_ex.re) +--CREDITS-- +YuanchengJiang +--INI-- +memory_limit=64M +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Fatal error: Allowed memory size of %d bytes exhausted %s