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 61545aeac59da..4822721f2c675 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -657,7 +657,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 662e4a4b5342c..8027bcdc85547 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -115,7 +115,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 @@ -182,7 +182,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 2276e3ff22b69..2df0f27a93017 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,75 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.2.22 +29 Aug 2024, PHP 8.2.23 + +- 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) + +- Tidy: + . Fix memory leaks in ext/tidy basedir restriction code. (nielsdos) + +01 Aug 2024, PHP 8.2.22 - Core: . Fixed bug GH-13922 (Fixed support for systems with @@ -12,6 +81,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/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 b7c0a5ec4466a..4bb2b58af776d 100644 --- a/Zend/Optimizer/escape_analysis.c +++ b/Zend/Optimizer/escape_analysis.c @@ -400,6 +400,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 f274edb039c17..8a029c8007b85 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 @@ -1097,8 +1096,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; } @@ -1128,8 +1125,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) { @@ -1557,12 +1552,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]); @@ -1578,8 +1567,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 675dcdd338fa6..dfd34e2886648 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.2.22-dev" +#define ZEND_VERSION "4.2.23" #define ZEND_ENGINE_3 diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 4c447845e5474..eb6e349f7ec21 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2559,8 +2559,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 f807c8196548e..85d015b19b77b 100644 --- a/Zend/zend_enum.c +++ b/Zend/zend_enum.c @@ -279,7 +279,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) { @@ -303,7 +303,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; } @@ -333,7 +333,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; @@ -368,12 +368,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 797eb0c8ab5b6..86efcf32170e2 100644 --- a/Zend/zend_enum.h +++ b/Zend/zend_enum.h @@ -41,7 +41,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 3de48fb1358c5..716756ad93ba2 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -4471,7 +4471,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 1d785377e82e7..85127025679d6 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 */ @@ -462,6 +513,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) { @@ -470,13 +523,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; } @@ -606,8 +660,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); @@ -689,14 +741,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; } /* }}} */ @@ -722,6 +772,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; @@ -752,9 +807,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. */ } @@ -765,8 +830,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 { @@ -817,7 +881,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 4ccc42b92f668..a3daba3a839bb 100644 --- a/Zend/zend_generators.h +++ b/Zend/zend_generators.h @@ -93,6 +93,7 @@ static const zend_uchar ZEND_GENERATOR_FORCED_CLOSE = 0x2; static const zend_uchar ZEND_GENERATOR_AT_FIRST_YIELD = 0x4; static const zend_uchar ZEND_GENERATOR_DO_INIT = 0x8; static const zend_uchar ZEND_GENERATOR_IN_FIBER = 0x10; +static const zend_uchar 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 8ba3d7ee48f0d..cfa394d43e978 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 c73a50948d6bc..c3b27cbfc321c 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1587,12 +1587,6 @@ NEWLINE ("\r"|"\n"|"\r\n") RETURN_TOKEN_WITH_STR(T_STRING, 0); } -{ANY_CHAR} { - yyless(0); - yy_pop_state(); - goto restart; -} - "::" { RETURN_TOKEN(T_PAAMAYIM_NEKUDOTAYIM); } @@ -2375,7 +2369,7 @@ inline_char_handler: } -"#"|"//" { +"#"|"//" { while (YYCURSOR < YYLIMIT) { switch (*YYCURSOR++) { case '\r': @@ -2399,7 +2393,7 @@ inline_char_handler: RETURN_OR_SKIP_TOKEN(T_COMMENT); } -"/*"|"/**"{WHITESPACE} { +"/*"|"/**"{WHITESPACE} { int doc_com; if (yyleng > 2) { @@ -2435,6 +2429,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 d694a0a7a14ba..50992e95f7fc8 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.2.22-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.2.23],[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 707f4e0a6fc4e..4884ddc8228a1 100644 --- a/ext/curl/interface.c +++ b/ext/curl/interface.c @@ -2764,7 +2764,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) nodep->properties; } else { - curnode = (xmlNodePtr) nodep->children; + curnode = dom_nodelist_iter_start_first_child(nodep); } } else { if (nodep->type == XML_DOCUMENT_NODE || nodep->type == XML_HTML_DOCUMENT_NODE) { diff --git a/ext/dom/nodelist.c b/ext/dom/nodelist.c index a8e713f67d03e..6ed561e6d68e0 100644 --- a/ext/dom/nodelist.c +++ b/ext/dom/nodelist.c @@ -31,7 +31,7 @@ * Since: */ -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 ccf665c417cd8..d0637e4674e3d 100644 --- a/ext/dom/php_dom.h +++ b/ext/dom/php_dom.h @@ -156,6 +156,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_OBJ(__ptr, __id, __prtype, __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 560338e71f335..8b0b58105d320 100644 --- a/ext/ffi/ffi.c +++ b/ext/ffi/ffi.c @@ -3544,7 +3544,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 a893879f9cf01..f31206fc17464 100644 --- a/ext/hash/config.m4 +++ b/ext/hash/config.m4 @@ -28,8 +28,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 771805925827f..7cb6a553af824 100644 --- a/ext/intl/uchar/uchar.c +++ b/ext/intl/uchar/uchar.c @@ -392,8 +392,9 @@ IC_METHOD(foldCase) { zend_string *string_codepoint; zend_long int_codepoint; - 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 b546de2cd9d8c..5ab3c6ca5c524 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 6bf07ad35e2e0..b3929382e1173 100644 --- a/ext/opcache/config.m4 +++ b/ext/opcache/config.m4 @@ -317,6 +317,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 \ @@ -332,7 +336,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 1840b214705fa..bfa0e9161ac75 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 @@ -45,7 +48,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; @@ -76,8 +79,13 @@ static void *find_prefered_mmap_base(size_t requested_size) } if ((uintptr_t)execute_ex >= start) { /* the current segment lays before PHP .text segment or PHP .text segment itself */ + /*Search for candidates at the end of the free segment near the .text segment + to prevent candidates from being missed due to large hole*/ if (last_free_addr + requested_size <= start) { - last_candidate = last_free_addr; + last_candidate = ZEND_MM_ALIGNED_SIZE_EX(start - requested_size, huge_page_size); + if (last_candidate + requested_size > start) { + last_candidate -= huge_page_size; + } } if ((uintptr_t)execute_ex < end) { /* the current segment is PHP .text segment itself */ @@ -128,7 +136,10 @@ static void *find_prefered_mmap_base(size_t requested_size) if ((uintptr_t)execute_ex >= e_start) { /* the current segment lays before PHP .text segment or PHP .text segment itself */ if (last_free_addr + requested_size <= e_start) { - last_candidate = last_free_addr; + last_candidate = ZEND_MM_ALIGNED_SIZE_EX(e_start - requested_size, huge_page_size); + if (last_candidate + requested_size > e_start) { + last_candidate -= huge_page_size; + } } if ((uintptr_t)execute_ex < e_end) { /* the current segment is PHP .text segment itself */ @@ -180,8 +191,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 7dc40f510d459..03c9f1b7741c5 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 ceedbdae35389..0e5c9dd1f2ae7 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/session/session.c b/ext/session/session.c index 74a0546b8490c..8b95b31e8fe49 100644 --- a/ext/session/session.c +++ b/ext/session/session.c @@ -57,6 +57,7 @@ PHPAPI ZEND_DECLARE_MODULE_GLOBALS(ps) static int php_session_rfc1867_callback(unsigned int event, void *event_data, void **extra); +/* the following signature must match (*php_rfc1867_callback) in main/rfc1867.h */ static int (*php_session_rfc1867_orig_callback)(unsigned int event, void *event_data, void **extra); static void php_session_track_init(void); @@ -97,8 +98,8 @@ zend_class_entry *php_session_update_timestamp_iface_entry; #define APPLY_TRANS_SID (PS(use_trans_sid) && !PS(use_only_cookies)) -static int php_session_send_cookie(void); -static int php_session_abort(void); +static zend_result php_session_send_cookie(void); +static zend_result php_session_abort(void); /* Initialized in MINIT, readonly otherwise. */ static int my_module_number = 0; @@ -122,7 +123,7 @@ static inline void php_rinit_session_globals(void) /* {{{ */ /* }}} */ /* Dispatched by RSHUTDOWN and by php_session_destroy */ -static inline void php_rshutdown_session_globals(void) /* {{{ */ +static void php_rshutdown_session_globals(void) /* {{{ */ { /* Do NOT destroy PS(mod_user_names) here! */ if (!Z_ISUNDEF(PS(http_session_vars))) { diff --git a/ext/soap/php_encoding.c b/ext/soap/php_encoding.c index 82b21d1588146..6568446249a31 100644 --- a/ext/soap/php_encoding.c +++ b/ext/soap/php_encoding.c @@ -449,8 +449,11 @@ static xmlNodePtr master_to_xml_int(encodePtr encode, zval *data, int style, xml zend_string *type_name; ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(SOAP_GLOBAL(class_map), type_name, tmp) { - if (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) { + 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; } @@ -3630,48 +3634,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 eaea09b461d57..e98820c630eda 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..40a2cf3f11d26 --- /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(424) "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 03b959daa3bbf..0440ff59c8ba9 100644 --- a/ext/spl/spl_directory.c +++ b/ext/spl/spl_directory.c @@ -240,7 +240,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 db35511f08d2d..736f151da5846 100644 --- a/ext/spl/spl_heap.c +++ b/ext/spl/spl_heap.c @@ -501,7 +501,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 a9950fa6d48d2..dca176ad3f526 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -791,8 +791,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 53fa8d33dad6b..2a5487bf51f8d 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/non_finite_values.phpt b/ext/standard/tests/streams/non_finite_values.phpt new file mode 100644 index 0000000000000..5dba0d3b48266 --- /dev/null +++ b/ext/standard/tests/streams/non_finite_values.phpt @@ -0,0 +1,31 @@ +--TEST-- +Non-finite timeout values in stream functions +--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 a1165f6a29d6b..1133bbb3e86cb 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_version.h b/main/php_version.h index 8fcd1fa0c4703..36fa814789d65 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 2 -#define PHP_RELEASE_VERSION 22 -#define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.2.22-dev" -#define PHP_VERSION_ID 80222 +#define PHP_RELEASE_VERSION 23 +#define PHP_EXTRA_VERSION "" +#define PHP_VERSION "8.2.23" +#define PHP_VERSION_ID 80223 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 7e6a87fcc89e3..6045b6564da0d 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) { @@ -745,17 +749,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 994ac829b0a5e..887064cf2e70b 100644 --- a/sapi/phpdbg/phpdbg_prompt.c +++ b/sapi/phpdbg/phpdbg_prompt.c @@ -1551,6 +1551,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 fef29a8293830..f0a9bf5d4fcb5 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