diff --git a/.github/scripts/windows/build_task.bat b/.github/scripts/windows/build_task.bat index e8d84b8c0bfd6..071c0c28f5ac5 100644 --- a/.github/scripts/windows/build_task.bat +++ b/.github/scripts/windows/build_task.bat @@ -5,11 +5,6 @@ if /i "%GITHUB_ACTIONS%" neq "True" ( exit /b 3 ) -del /f /q C:\Windows\System32\libcrypto-1_1-x64.dll >NUL 2>NUL -if %errorlevel% neq 0 exit /b 3 -del /f /q C:\Windows\System32\libssl-1_1-x64.dll >NUL 2>NUL -if %errorlevel% neq 0 exit /b 3 - call %~dp0find-target-branch.bat set STABILITY=staging set DEPS_DIR=%PHP_BUILD_CACHE_BASE_DIR%\deps-%BRANCH%-%PHP_SDK_VS%-%PHP_SDK_ARCH% diff --git a/.github/scripts/windows/test_task.bat b/.github/scripts/windows/test_task.bat index 0bfdaf664125b..5762aa32414e4 100644 --- a/.github/scripts/windows/test_task.bat +++ b/.github/scripts/windows/test_task.bat @@ -137,6 +137,8 @@ for %%i in (ldap oci8_12c pdo_oci) do ( set TEST_PHPDBG_EXECUTABLE=%PHP_BUILD_DIR%\phpdbg.exe +copy /-y %DEPS_DIR%\bin\*.dll %PHP_BUILD_DIR%\* + mkdir c:\tests_tmp nmake test TESTS="%OPCACHE_OPTS% -g FAIL,BORK,LEAK,XLEAK --no-progress -q --offline --show-diff --show-slow 1000 --set-timeout 120 --temp-source c:\tests_tmp --temp-target c:\tests_tmp --bless %PARALLEL%" diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 4c8ec23b158a4..2377286ca830c 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -520,7 +520,7 @@ jobs: - name: Test Laravel if: ${{ !cancelled() }} run: | - git clone https://github.com/laravel/framework.git --branch=master --depth=1 + git clone https://github.com/laravel/framework.git --depth=1 cd framework git rev-parse HEAD php /usr/bin/composer install --no-progress --ignore-platform-reqs diff --git a/.github/workflows/windows-builds.yml b/.github/workflows/windows-builds.yml new file mode 100644 index 0000000000000..6bb4cd897164d --- /dev/null +++ b/.github/workflows/windows-builds.yml @@ -0,0 +1,23 @@ +name: Windows builds +run-name: Windows builds for ${{ inputs.tag || github.ref_name }} +on: + push: + tags: + - 'php-*' + workflow_dispatch: + inputs: + tag: + description: 'Tag version' + required: true + +jobs: + publish: + runs-on: ubuntu-latest + name: Build + steps: + - name: Build + env: + GITHUB_TOKEN: ${{ secrets.WINDOWS_BUILDS_TOKEN }} + run: | + TAG="${{ inputs.tag || github.ref_name }}" + gh workflow run php.yml -R php/php-windows-builder -f php-version="${TAG#php-}" diff --git a/.gitignore b/.gitignore index 449963153f36b..b9e60cc6b5bde 100644 --- a/.gitignore +++ b/.gitignore @@ -291,6 +291,11 @@ tmp-php.ini /junit.out.xml /.ccache/ +# ------------------------------------------------------------------------------ +# Additional test build files +# ------------------------------------------------------------------------------ +/ext/standard/tests/helpers/bad_cmd.exe + # ------------------------------------------------------------------------------ # Special cases to invert previous ignore patterns # ------------------------------------------------------------------------------ diff --git a/NEWS b/NEWS index e484b900923ed..07c3abca2c771 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,102 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.3.17 +13 Mar 2025, PHP 8.3.18 + +- BCMath: + . Fixed bug GH-17398 (bcmul memory leak). (SakiTakamachi) + +- Core: + . Fixed bug GH-17623 (Broken stack overflow detection for variable + compilation). (ilutov) + . Fixed bug GH-17618 (UnhandledMatchError does not take + zend.exception_ignore_args=1 into account). (timwolla) + . Fix fallback paths in fast_long_{add,sub}_function. (nielsdos) + . Fixed bug GH-17718 (Calling static methods on an interface that has + `__callStatic` is allowed). (timwolla) + . Fixed bug GH-17797 (zend_test_compile_string crash on invalid + script path). (David Carlier) + . Fixed GHSA-rwp7-7vc6-8477 (Reference counting in php_request_shutdown + causes Use-After-Free). (CVE-2024-11235) (ilutov) + +- DOM: + . Fixed bug GH-17847 (xinclude destroys live node). (nielsdos) + +- FFI: + . Fix FFI Parsing of Pointer Declaration Lists. (davnotdev) + +- FPM: + . Fixed bug GH-17643 (FPM with httpd ProxyPass encoded PATH_INFO env). + (Jakub Zelenka) + +- GD: + . Fixed bug GH-17772 (imagepalettetotruecolor crash with memory_limit=2M). + (David Carlier) + +- LDAP: + . Fixed bug GH-17704 (ldap_search fails when $attributes contains a + non-packed array with numerical keys). (nielsdos, 7u83) + +- LibXML: + . Fixed GHSA-wg4p-4hqh-c3g9 (Reocurrence of #72714). (nielsdos) + . Fixed GHSA-p3x9-6h7p-cgfc (libxml streams use wrong `content-type` header + when requesting a redirected resource). (CVE-2025-1219) (timwolla) + +- MBString: + . Fixed bug GH-17503 (Undefined float conversion in mb_convert_variables). + (cmb) + +- Opcache: + . Fixed bug GH-17654 (Multiple classes using same trait causes function + JIT crash). (nielsdos) + . Fixed bug GH-17577 (JIT packed type guard crash). (nielsdos, Dmitry) + . Fixed bug GH-17899 (zend_test_compile_string with invalid path + when opcache is enabled). (David Carlier) + . Fixed bug GH-17868 (Cannot allocate memory with tracing JIT). (nielsdos) + +- PDO_SQLite: + . Fixed GH-17837 ()::getColumnMeta() on unexecuted statement segfaults). + (cmb) + . Fix cycle leak in sqlite3 setAuthorizer(). (nielsdos) + +- Phar: + . Fixed bug GH-17808: PharFileInfo refcount bug. (nielsdos) + +- PHPDBG: + . Partially fixed bug GH-17387 (Trivial crash in phpdbg lexer). (nielsdos) + . Fix memory leak in phpdbg calling registered function. (nielsdos) + +- Reflection: + . Fixed bug GH-15902 (Core dumped in ext/reflection/php_reflection.c). + (DanielEScherzer) + +- Standard: + . Fixed bug #72666 (stat cache clearing inconsistent between file:// paths + and plain paths). (Jakub Zelenka) + +- Streams: + . Fixed bug GH-17650 (realloc with size 0 in user_filters.c). (nielsdos) + . Fix memory leak on overflow in _php_stream_scandir(). (nielsdos) + . Fixed GHSA-hgf54-96fm-v528 (Stream HTTP wrapper header check might omit + basic auth header). (CVE-2025-1736) (Jakub Zelenka) + . Fixed GHSA-52jp-hrpf-2jff (Stream HTTP wrapper truncate redirect location + to 1024 bytes). (CVE-2025-1861) (Jakub Zelenka) + . Fixed GHSA-pcmh-g36c-qc44 (Streams HTTP wrapper does not fail for headers + without colon). (CVE-2025-1734) (Jakub Zelenka) + . Fixed GHSA-v8xr-gpvj-cx9g (Header parser of `http` stream wrapper does not + handle folded headers). (CVE-2025-1217) (Jakub Zelenka) + +- Windows: + . Fixed phpize for Windows 11 (24H2). (bwoebi) + . Fixed GH-17855 (CURL_STATICLIB flag set even if linked with shared lib). + (cmb) + +- Zlib: + . Fixed bug GH-17745 (zlib extension incorrectly handles object arguments). + (nielsdos) + . Fix memory leak when encoding check fails. (nielsdos) + . Fix zlib support for large files. (nielsdos) + +13 Feb 2025, PHP 8.3.17 - Core: . Fixed bug GH-16892 (ini_parse_quantity() fails to parse inputs starting @@ -76,7 +172,7 @@ PHP NEWS . Fixed bug GH-17139 (Fix zip_entry_name() crash on invalid entry). (nielsdos) -02 Jan 2025, PHP 8.3.16RC1 +02 Jan 2025, PHP 8.3.16 - Core: . Fixed bug GH-17106 (ZEND_MATCH_ERROR misoptimization). (ilutov) @@ -275,7 +371,7 @@ PHP NEWS - Windows: . Fixed bug GH-16849 (Error dialog causes process to hang). (cmb) -07 Nov 2024, PHP 8.3.14RC1 +07 Nov 2024, PHP 8.3.14 - CLI: . Fixed bug GH-16373 (Shebang is not skipped for router script in cli-server @@ -1028,7 +1124,7 @@ PHP NEWS - Treewide: . Fix gcc-14 Wcalloc-transposed-args warnings. (Cristian Rodríguez) -28 Mar 2024, PHP 8.3.5RC1 +28 Mar 2024, PHP 8.3.5 - Core: . Fixed GH-13569 (GC buffer unnecessarily grows up to GC_MAX_BUF_SIZE when @@ -1297,7 +1393,7 @@ PHP NEWS . Fixed bug GH-12980 (tidynode.props.attribute is missing "Boolean Attributes" and empty attributes). (nielsdos) -07 Dec 2023, PHP 8.3.1RC1 +07 Dec 2023, PHP 8.3.1 - Core: . Fixed bug GH-12758 / GH-12768 (Invalid opline in OOM handlers within diff --git a/Zend/tests/gh_17718_001.phpt b/Zend/tests/gh_17718_001.phpt new file mode 100644 index 0000000000000..1f5bee961eb71 --- /dev/null +++ b/Zend/tests/gh_17718_001.phpt @@ -0,0 +1,17 @@ +--TEST-- +GH-17718: Disallow calling abstract `__callStatic()` trampoline on an interface +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Cannot call abstract method Foo::bar() in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/gh_17718_002.phpt b/Zend/tests/gh_17718_002.phpt new file mode 100644 index 0000000000000..f3f75fca40f72 --- /dev/null +++ b/Zend/tests/gh_17718_002.phpt @@ -0,0 +1,17 @@ +--TEST-- +GH-17718: Disallow calling abstract `__callStatic()` trampoline on an abstract class +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Cannot call abstract method Foo::bar() in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/gh_17728.phpt b/Zend/tests/gh_17728.phpt new file mode 100644 index 0000000000000..ef458a8e8d5e1 --- /dev/null +++ b/Zend/tests/gh_17728.phpt @@ -0,0 +1,24 @@ +--TEST-- +GH-17728: Assertion failure when calling static method of trait with `__callStatic()` with throwing error handler +--FILE-- +getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Calling static trait method Foo::bar is deprecated, it should only be called on a class using the trait diff --git a/Zend/tests/ghsa-rwp7-7vc6-8477_001.phpt b/Zend/tests/ghsa-rwp7-7vc6-8477_001.phpt new file mode 100644 index 0000000000000..d0e9ddcba410b --- /dev/null +++ b/Zend/tests/ghsa-rwp7-7vc6-8477_001.phpt @@ -0,0 +1,26 @@ +--TEST-- +GHSA-rwp7-7vc6-8477: Use-after-free for ??= due to incorrect live-range calculation +--FILE-- +foo()->baz ??= 1; +} catch (Exception $e) { + echo $e->getMessage(); +} + +?> +--EXPECT-- +Hello diff --git a/Zend/tests/ghsa-rwp7-7vc6-8477_002.phpt b/Zend/tests/ghsa-rwp7-7vc6-8477_002.phpt new file mode 100644 index 0000000000000..4115d9eae26fd --- /dev/null +++ b/Zend/tests/ghsa-rwp7-7vc6-8477_002.phpt @@ -0,0 +1,24 @@ +--TEST-- +GHSA-rwp7-7vc6-8477: Use-after-free for ??= due to incorrect live-range calculation +--FILE-- +foo()->prop ??= 'foo'; +} catch (Error $e) { + echo $e->getMessage(); +} + +?> +--EXPECT-- +Cannot assign string to property Foo::$prop of type int diff --git a/Zend/tests/ghsa-rwp7-7vc6-8477_003.phpt b/Zend/tests/ghsa-rwp7-7vc6-8477_003.phpt new file mode 100644 index 0000000000000..f2808afbf84de --- /dev/null +++ b/Zend/tests/ghsa-rwp7-7vc6-8477_003.phpt @@ -0,0 +1,22 @@ +--TEST-- +GHSA-rwp7-7vc6-8477: Use-after-free for ??= due to incorrect live-range calculation +--FILE-- +prop ??= 'foo'; +} catch (Error $e) { + echo $e->getMessage(); +} + +?> +--EXPECT-- +Cannot assign string to property Foo::$prop of type int diff --git a/Zend/tests/match/049.phpt b/Zend/tests/match/049.phpt new file mode 100644 index 0000000000000..2a2385f704ff4 --- /dev/null +++ b/Zend/tests/match/049.phpt @@ -0,0 +1,42 @@ +--TEST-- +Match expression error messages (zend.exception_ignore_args=1) +--INI-- +zend.exception_ignore_args=1 +--FILE-- +getMessage() . PHP_EOL; + } +} + +test(null); +test(1); +test(5.5); +test(5.0); +test("foo"); +test(true); +test(false); +test([1, 2, 3]); +test(new Beep()); +// Testing long strings. +test(str_repeat('e', 100)); +test(str_repeat("e\n", 100)); +?> +--EXPECT-- +Unhandled match case of type null +Unhandled match case of type int +Unhandled match case of type float +Unhandled match case of type float +Unhandled match case of type string +Unhandled match case of type bool +Unhandled match case of type bool +Unhandled match case of type array +Unhandled match case of type Beep +Unhandled match case of type string +Unhandled match case of type string diff --git a/Zend/tests/stack_limit/stack_limit_015.phpt b/Zend/tests/stack_limit/stack_limit_015.phpt new file mode 100644 index 0000000000000..b725523b7840a --- /dev/null +++ b/Zend/tests/stack_limit/stack_limit_015.phpt @@ -0,0 +1,71 @@ +--TEST-- +Stack limit 015 - Internal stack limit check in zend_compile_var() +--CREDITS-- +abdullahasif88 +--SKIPIF-- + +--EXTENSIONS-- +zend_test +--INI-- +zend.max_allowed_stack_size=128K +--FILE-- +p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p +; + +?> +--EXPECTF-- +Fatal error: Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached during compilation. Try splitting expression in %s on line %d diff --git a/Zend/zend.h b/Zend/zend.h index 0f73f888a6032..b5bed8b61187c 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.3.17-dev" +#define ZEND_VERSION "4.3.18" #define ZEND_ENGINE_3 diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index dbd8c9dc17fd7..41113e2f0055b 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -10686,6 +10686,8 @@ static zend_op *zend_compile_var_inner(znode *result, zend_ast *ast, uint32_t ty static zend_op *zend_compile_var(znode *result, zend_ast *ast, uint32_t type, bool by_ref) /* {{{ */ { + zend_check_stack_limit(); + uint32_t checkpoint = zend_short_circuiting_checkpoint(); zend_op *opcode = zend_compile_var_inner(result, ast, type, by_ref); zend_short_circuiting_commit(checkpoint, result, ast); @@ -10694,6 +10696,8 @@ static zend_op *zend_compile_var(znode *result, zend_ast *ast, uint32_t type, bo static zend_op *zend_delayed_compile_var(znode *result, zend_ast *ast, uint32_t type, bool by_ref) /* {{{ */ { + zend_check_stack_limit(); + switch (ast->kind) { case ZEND_AST_VAR: return zend_compile_simple_var(result, ast, type, 1); diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index bb3991f695f8b..dc28578f2c1a3 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -872,7 +872,7 @@ ZEND_COLD void zend_match_unhandled_error(const zval *value) { smart_str msg = {0}; - if (Z_TYPE_P(value) <= IS_STRING) { + if (!EG(exception_ignore_args) && Z_TYPE_P(value) <= IS_STRING) { smart_str_append_scalar(&msg, value, EG(exception_string_param_max_len)); } else { smart_str_appendl(&msg, "of type ", sizeof("of type ")-1); diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 10664cd118118..d688e4b63ed69 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1348,7 +1348,7 @@ ZEND_API zend_function *zend_get_call_trampoline_func(const zend_class_entry *ce func->fn_flags = ZEND_ACC_CALL_VIA_TRAMPOLINE | ZEND_ACC_PUBLIC | ZEND_ACC_VARIADIC - | (fbc->common.fn_flags & ZEND_ACC_RETURN_REFERENCE); + | (fbc->common.fn_flags & (ZEND_ACC_RETURN_REFERENCE|ZEND_ACC_ABSTRACT)); if (is_static) { func->fn_flags |= ZEND_ACC_STATIC; } @@ -1541,19 +1541,27 @@ ZEND_API zend_function *zend_std_get_static_method(zend_class_entry *ce, zend_st if (EXPECTED(fbc)) { if (UNEXPECTED(fbc->common.fn_flags & ZEND_ACC_ABSTRACT)) { zend_abstract_method_call(fbc); - fbc = NULL; + goto fail; } else if (UNEXPECTED(fbc->common.scope->ce_flags & ZEND_ACC_TRAIT)) { zend_error(E_DEPRECATED, "Calling static trait method %s::%s is deprecated, " "it should only be called on a class using the trait", ZSTR_VAL(fbc->common.scope->name), ZSTR_VAL(fbc->common.function_name)); if (EG(exception)) { - return NULL; + goto fail; } } } return fbc; + + fail: + if (UNEXPECTED(fbc->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { + zend_string_release_ex(fbc->common.function_name, 0); + zend_free_trampoline(fbc); + } + + return NULL; } /* }}} */ diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 55bf81376d99d..06f411a1d3663 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -922,6 +922,14 @@ static void zend_calc_live_ranges( opnum--; opline--; + /* SEPARATE always redeclares its op1. For the purposes of live-ranges, + * its declaration is irrelevant. Don't terminate the current live-range + * to avoid breaking special handling of COPY_TMP. */ + if (opline->opcode == ZEND_SEPARATE) { + ZEND_ASSERT(opline->op1.var == opline->result.var); + continue; + } + if ((opline->result_type & (IS_TMP_VAR|IS_VAR)) && !is_fake_def(opline)) { uint32_t var_num = EX_VAR_TO_NUM(opline->result.var) - var_offset; /* Defs without uses can occur for two reasons: Either because the result is diff --git a/Zend/zend_operators.h b/Zend/zend_operators.h index fd1db6f406328..76e95a92d4166 100644 --- a/Zend/zend_operators.h +++ b/Zend/zend_operators.h @@ -722,11 +722,13 @@ overflow: ZEND_ATTRIBUTE_COLD_LABEL * have read the values of op1 and op2. */ + zend_long sum = (zend_long) ((zend_ulong) Z_LVAL_P(op1) + (zend_ulong) Z_LVAL_P(op2)); + if (UNEXPECTED((Z_LVAL_P(op1) & LONG_SIGN_MASK) == (Z_LVAL_P(op2) & LONG_SIGN_MASK) - && (Z_LVAL_P(op1) & LONG_SIGN_MASK) != ((Z_LVAL_P(op1) + Z_LVAL_P(op2)) & LONG_SIGN_MASK))) { + && (Z_LVAL_P(op1) & LONG_SIGN_MASK) != (sum & LONG_SIGN_MASK))) { ZVAL_DOUBLE(result, (double) Z_LVAL_P(op1) + (double) Z_LVAL_P(op2)); } else { - ZVAL_LONG(result, Z_LVAL_P(op1) + Z_LVAL_P(op2)); + ZVAL_LONG(result, sum); } #endif } @@ -804,11 +806,19 @@ overflow: ZEND_ATTRIBUTE_COLD_LABEL ZVAL_LONG(result, llresult); } #else - ZVAL_LONG(result, Z_LVAL_P(op1) - Z_LVAL_P(op2)); + /* + * 'result' may alias with op1 or op2, so we need to + * ensure that 'result' is not updated until after we + * have read the values of op1 and op2. + */ + + zend_long sub = (zend_long) ((zend_ulong) Z_LVAL_P(op1) - (zend_ulong) Z_LVAL_P(op2)); if (UNEXPECTED((Z_LVAL_P(op1) & LONG_SIGN_MASK) != (Z_LVAL_P(op2) & LONG_SIGN_MASK) - && (Z_LVAL_P(op1) & LONG_SIGN_MASK) != (Z_LVAL_P(result) & LONG_SIGN_MASK))) { + && (Z_LVAL_P(op1) & LONG_SIGN_MASK) != (sub & LONG_SIGN_MASK))) { ZVAL_DOUBLE(result, (double) Z_LVAL_P(op1) - (double) Z_LVAL_P(op2)); + } else { + ZVAL_LONG(result, sub); } #endif } diff --git a/configure.ac b/configure.ac index ee039a0bad50a..d01440cb190a9 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.17-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.3.18],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) AC_CONFIG_SRCDIR([main/php_version.h]) AC_CONFIG_AUX_DIR([build]) AC_PRESERVE_HELP_ORDER diff --git a/ext/bcmath/bcmath.c b/ext/bcmath/bcmath.c index f53032fabc35c..bc78faa7b3994 100644 --- a/ext/bcmath/bcmath.c +++ b/ext/bcmath/bcmath.c @@ -96,9 +96,9 @@ static PHP_GINIT_FUNCTION(bcmath) /* {{{ PHP_GSHUTDOWN_FUNCTION */ static PHP_GSHUTDOWN_FUNCTION(bcmath) { - _bc_free_num_ex(&bcmath_globals->_zero_, 1); - _bc_free_num_ex(&bcmath_globals->_one_, 1); - _bc_free_num_ex(&bcmath_globals->_two_, 1); + bc_force_free_number(&bcmath_globals->_zero_); + bc_force_free_number(&bcmath_globals->_one_); + bc_force_free_number(&bcmath_globals->_two_); } /* }}} */ diff --git a/ext/bcmath/libbcmath/src/bcmath.h b/ext/bcmath/libbcmath/src/bcmath.h index de51ee7457110..0ec86f2086199 100644 --- a/ext/bcmath/libbcmath/src/bcmath.h +++ b/ext/bcmath/libbcmath/src/bcmath.h @@ -87,6 +87,8 @@ typedef struct bc_struct { void bc_init_numbers(void); +void bc_force_free_number(bc_num *num); + bc_num _bc_new_num_ex(size_t length, size_t scale, bool persistent); void _bc_free_num_ex(bc_num *num, bool persistent); diff --git a/ext/bcmath/libbcmath/src/init.c b/ext/bcmath/libbcmath/src/init.c index cef55324689f9..6bb538ef1cb16 100644 --- a/ext/bcmath/libbcmath/src/init.c +++ b/ext/bcmath/libbcmath/src/init.c @@ -82,6 +82,13 @@ void bc_init_numbers(void) BCG(_two_)->n_value[0] = 2; } +void bc_force_free_number(bc_num *num) +{ + pefree((*num)->n_ptr, 1); + pefree(*num, 1); + *num = NULL; +} + /* Make a copy of a number! Just increments the reference count! */ bc_num bc_copy_num(bc_num num) diff --git a/ext/bcmath/tests/gh17398.phpt b/ext/bcmath/tests/gh17398.phpt new file mode 100644 index 0000000000000..6a0cda09ec76f --- /dev/null +++ b/ext/bcmath/tests/gh17398.phpt @@ -0,0 +1,10 @@ +--TEST-- +GH-17398 (bcmul memory leak) +--EXTENSIONS-- +bcmath +--FILE-- + +--EXPECTF-- +Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d diff --git a/ext/curl/config.w32 b/ext/curl/config.w32 index f722c5faca5e3..65f3050f73a8f 100644 --- a/ext/curl/config.w32 +++ b/ext/curl/config.w32 @@ -13,7 +13,8 @@ if (PHP_CURL != "no") { } } - if (CHECK_LIB("libcurl_a.lib;libcurl.lib", "curl", PHP_CURL) && + var curl_location; + if ((curl_location = CHECK_LIB("libcurl_a.lib;libcurl.lib", "curl", PHP_CURL)) && CHECK_HEADER_ADD_INCLUDE("curl/easy.h", "CFLAGS_CURL") && SETUP_OPENSSL("curl", PHP_CURL) > 0 && CHECK_LIB("winmm.lib", "curl", PHP_CURL) && @@ -28,7 +29,10 @@ if (PHP_CURL != "no") { ) { EXTENSION("curl", "interface.c multi.c share.c curl_file.c"); AC_DEFINE('HAVE_CURL', 1, 'Have cURL library'); - ADD_FLAG("CFLAGS_CURL", "/D CURL_STATICLIB /D PHP_CURL_EXPORTS=1"); + ADD_FLAG("CFLAGS_CURL", "/D PHP_CURL_EXPORTS=1"); + if (curl_location.match(/libcurl_a\.lib$/)) { + ADD_FLAG("CFLAGS_CURL", "/D CURL_STATICLIB"); + } PHP_INSTALL_HEADERS("ext/curl", "php_curl.h"); // TODO: check for curl_version_info } else { diff --git a/ext/curl/tests/curl_basic_022.phpt b/ext/curl/tests/curl_basic_022.phpt index e905dfd885d16..4a2177e06bd47 100644 --- a/ext/curl/tests/curl_basic_022.phpt +++ b/ext/curl/tests/curl_basic_022.phpt @@ -11,10 +11,10 @@ curl_setopt($ch, CURLOPT_COOKIELIST, 'Set-Cookie: C2=v2; expires=Thu, 31-Dec-203 var_dump(curl_getinfo($ch, CURLINFO_COOKIELIST)); ?> ---EXPECT-- +--EXPECTF-- array(2) { [0]=> - string(38) ".php.net TRUE / FALSE 2145916799 C1 v1" + string(38) ".php.net TRUE / FALSE %d C1 v1" [1]=> - string(38) ".php.net TRUE / FALSE 2145916799 C2 v2" + string(38) ".php.net TRUE / FALSE %d C2 v2" } diff --git a/ext/dom/document.c b/ext/dom/document.c index d1437f1f42923..0388249766e1f 100644 --- a/ext/dom/document.c +++ b/ext/dom/document.c @@ -1586,47 +1586,6 @@ PHP_METHOD(DOMDocument, saveXML) } /* }}} end dom_document_savexml */ -static xmlNodePtr php_dom_free_xinclude_node(xmlNodePtr cur) /* {{{ */ -{ - xmlNodePtr xincnode; - - xincnode = cur; - cur = cur->next; - xmlUnlinkNode(xincnode); - php_libxml_node_free_resource(xincnode); - - return cur; -} -/* }}} */ - -static void php_dom_remove_xinclude_nodes(xmlNodePtr cur) /* {{{ */ -{ - while(cur) { - if (cur->type == XML_XINCLUDE_START) { - cur = php_dom_free_xinclude_node(cur); - - /* XML_XINCLUDE_END node will be a sibling of XML_XINCLUDE_START */ - while(cur && cur->type != XML_XINCLUDE_END) { - /* remove xinclude processing nodes from recursive xincludes */ - if (cur->type == XML_ELEMENT_NODE) { - php_dom_remove_xinclude_nodes(cur->children); - } - cur = cur->next; - } - - if (cur && cur->type == XML_XINCLUDE_END) { - cur = php_dom_free_xinclude_node(cur); - } - } else { - if (cur->type == XML_ELEMENT_NODE) { - php_dom_remove_xinclude_nodes(cur->children); - } - cur = cur->next; - } - } -} -/* }}} */ - /* Backported from master branch xml_common.h */ static zend_always_inline xmlNodePtr php_dom_next_in_tree_order(const xmlNode *nodep, const xmlNode *basep) { @@ -1660,17 +1619,19 @@ static void dom_xinclude_strip_references(xmlNodePtr basep) } } -/* See GH-14702. - * We have to remove userland references to xinclude fallback nodes because libxml2 will make clones of these +/* See GH-14702 and GH-17847. + * We have to remove userland references to xinclude nodes because libxml2 will make clones of these * and remove the original nodes. If the originals are removed while there are still userland references * this will cause memory corruption. */ static void dom_xinclude_strip_fallback_references(const xmlNode *basep) { xmlNodePtr current = basep->children; + /* TODO: try to improve loop search performance */ while (current) { - if (current->type == XML_ELEMENT_NODE && current->ns != NULL && current->_private != NULL - && xmlStrEqual(current->name, XINCLUDE_FALLBACK) + if (current->type == XML_ELEMENT_NODE + && current->ns != NULL + && xmlStrEqual(current->name, XINCLUDE_NODE) && (xmlStrEqual(current->ns->href, XINCLUDE_NS) || xmlStrEqual(current->ns->href, XINCLUDE_OLD_NS))) { dom_xinclude_strip_references(current); } @@ -1684,7 +1645,6 @@ PHP_METHOD(DOMDocument, xinclude) { zval *id; xmlDoc *docp; - xmlNodePtr root; zend_long flags = 0; int err; dom_object *intern; @@ -1703,22 +1663,11 @@ PHP_METHOD(DOMDocument, xinclude) dom_xinclude_strip_fallback_references((const xmlNode *) docp); + flags |= XML_PARSE_NOXINCNODE; PHP_LIBXML_SANITIZE_GLOBALS(xinclude); err = xmlXIncludeProcessFlags(docp, (int)flags); PHP_LIBXML_RESTORE_GLOBALS(xinclude); - /* XML_XINCLUDE_START and XML_XINCLUDE_END nodes need to be removed as these - are added via xmlXIncludeProcess to mark beginning and ending of xincluded document - but are not wanted in resulting document - must be done even if err as it could fail after - having processed some xincludes */ - root = (xmlNodePtr) docp->children; - while(root && root->type != XML_ELEMENT_NODE && root->type != XML_XINCLUDE_START) { - root = root->next; - } - if (root) { - php_dom_remove_xinclude_nodes(root); - } - php_libxml_invalidate_node_list_cache(intern->document); if (err) { diff --git a/ext/dom/tests/gh17847.phpt b/ext/dom/tests/gh17847.phpt new file mode 100644 index 0000000000000..5d5df0b3be05f --- /dev/null +++ b/ext/dom/tests/gh17847.phpt @@ -0,0 +1,49 @@ +--TEST-- +GH-17847 (xinclude destroys live node) +--EXTENSIONS-- +dom +--FILE-- +loadXML(<< + + fallback

garbage

+

garbage

+
+ + +

garbage

+
+
+ +XML); + +$xpath = new DOMXPath($doc); + +$garbage = []; +foreach ($xpath->query('//p') as $entry) + $garbage[] = $entry; + +@$doc->xinclude(); + +var_dump($garbage); +?> +--EXPECT-- +array(3) { + [0]=> + object(DOMElement)#3 (1) { + ["schemaTypeInfo"]=> + NULL + } + [1]=> + object(DOMElement)#4 (1) { + ["schemaTypeInfo"]=> + NULL + } + [2]=> + object(DOMElement)#5 (1) { + ["schemaTypeInfo"]=> + NULL + } +} diff --git a/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_001.phpt b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_001.phpt new file mode 100644 index 0000000000000..47212cb34100a --- /dev/null +++ b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_001.phpt @@ -0,0 +1,60 @@ +--TEST-- +GHSA-p3x9-6h7p-cgfc: libxml streams use wrong `content-type` header when requesting a redirected resource (Basic) +--EXTENSIONS-- +dom +--SKIPIF-- + +--FILE-- + + + + GHSA-p3x9-6h7p-cgfc + + + + + + +

GHSA-p3x9-6h7p-cgfc

+ + + EOT; + // Intentionally using non-standard casing for content-type to verify it is matched not case sensitively. + yield "data://text/plain,HTTP/1.1 200 OK\r\nconteNt-tyPe: text/html; charset=utf-8\r\n\r\n{$xml}"; +} + +['pid' => $pid, 'uri' => $uri] = http_server('genResponses', $output); +$document = new \DOMDocument(); +$document->loadHTMLFile($uri); + +$h1 = $document->getElementsByTagName('h1'); +var_dump($h1->length); +var_dump($document->saveHTML()); +http_server_kill($pid); +?> +--EXPECT-- +int(1) +string(266) " + + + GHSA-p3x9-6h7p-cgfc + + + + + + +

GHSA-p3x9-6h7p-cgfc

+ + +" diff --git a/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_002.phpt b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_002.phpt new file mode 100644 index 0000000000000..a7eff3b9a8b76 --- /dev/null +++ b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_002.phpt @@ -0,0 +1,60 @@ +--TEST-- +GHSA-p3x9-6h7p-cgfc: libxml streams use wrong `content-type` header when requesting a redirected resource (Missing content-type) +--EXTENSIONS-- +dom +--SKIPIF-- + +--FILE-- + + + + GHSA-p3x9-6h7p-cgfc + + + + + + +

GHSA-p3x9-6h7p-cgfc

+ + + EOT; + // Missing content-type in actual response. + yield "data://text/plain,HTTP/1.1 200 OK\r\n\r\n{$xml}"; +} + +['pid' => $pid, 'uri' => $uri] = http_server('genResponses', $output); +$document = new \DOMDocument(); +$document->loadHTMLFile($uri); + +$h1 = $document->getElementsByTagName('h1'); +var_dump($h1->length); +var_dump($document->saveHTML()); +http_server_kill($pid); +?> +--EXPECT-- +int(1) +string(266) " + + + GHSA-p3x9-6h7p-cgfc + + + + + + +

GHSA-p3x9-6h7p-cgfc

+ + +" diff --git a/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_003.phpt b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_003.phpt new file mode 100644 index 0000000000000..178b35f3525a5 --- /dev/null +++ b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_003.phpt @@ -0,0 +1,60 @@ +--TEST-- +GHSA-p3x9-6h7p-cgfc: libxml streams use wrong `content-type` header when requesting a redirected resource (Reason with colon) +--EXTENSIONS-- +dom +--SKIPIF-- + +--FILE-- + + + + GHSA-p3x9-6h7p-cgfc + + + + + + +

GHSA-p3x9-6h7p-cgfc

+ + + EOT; + // Missing content-type in actual response. + yield "data://text/plain,HTTP/1.1 200 OK: This is fine\r\n\r\n{$xml}"; +} + +['pid' => $pid, 'uri' => $uri] = http_server('genResponses', $output); +$document = new \DOMDocument(); +$document->loadHTMLFile($uri); + +$h1 = $document->getElementsByTagName('h1'); +var_dump($h1->length); +var_dump($document->saveHTML()); +http_server_kill($pid); +?> +--EXPECT-- +int(1) +string(266) " + + + GHSA-p3x9-6h7p-cgfc + + + + + + +

GHSA-p3x9-6h7p-cgfc

+ + +" diff --git a/ext/ffi/ffi.g b/ext/ffi/ffi.g index d70075267e54f..3648372428900 100644 --- a/ext/ffi/ffi.g +++ b/ext/ffi/ffi.g @@ -264,12 +264,14 @@ struct_contents(zend_ffi_dcl *dcl): struct_declaration(zend_ffi_dcl *struct_dcl): {zend_ffi_dcl common_field_dcl = ZEND_FFI_ATTR_INIT;} + {zend_ffi_dcl base_field_dcl = ZEND_FFI_ATTR_INIT;} specifier_qualifier_list(&common_field_dcl) + {base_field_dcl = common_field_dcl;} ( /* empty */ {zend_ffi_add_anonymous_field(struct_dcl, &common_field_dcl);} | struct_declarator(struct_dcl, &common_field_dcl) ( "," - {zend_ffi_dcl field_dcl = common_field_dcl;} + {zend_ffi_dcl field_dcl = base_field_dcl;} attributes(&field_dcl)? struct_declarator(struct_dcl, &field_dcl) )* diff --git a/ext/ffi/ffi_parser.c b/ext/ffi/ffi_parser.c index 2589ae81e0259..26d623a40290e 100644 --- a/ext/ffi/ffi_parser.c +++ b/ext/ffi/ffi_parser.c @@ -2472,14 +2472,16 @@ static int parse_struct_contents(int sym, zend_ffi_dcl *dcl) { static int parse_struct_declaration(int sym, zend_ffi_dcl *struct_dcl) { zend_ffi_dcl common_field_dcl = ZEND_FFI_ATTR_INIT; + zend_ffi_dcl base_field_dcl = ZEND_FFI_ATTR_INIT; sym = parse_specifier_qualifier_list(sym, &common_field_dcl); + base_field_dcl = common_field_dcl; if (sym == YY__SEMICOLON || sym == YY__RBRACE) { zend_ffi_add_anonymous_field(struct_dcl, &common_field_dcl); } else if (sym == YY__STAR || sym == YY_ID || sym == YY__LPAREN || sym == YY__COLON) { sym = parse_struct_declarator(sym, struct_dcl, &common_field_dcl); while (sym == YY__COMMA) { sym = get_sym(); - zend_ffi_dcl field_dcl = common_field_dcl; + zend_ffi_dcl field_dcl = base_field_dcl; if (YY_IN_SET(sym, (YY___ATTRIBUTE,YY___ATTRIBUTE__,YY___DECLSPEC,YY___CDECL,YY___STDCALL,YY___FASTCALL,YY___THISCALL,YY___VECTORCALL), "\000\000\000\000\000\000\360\017\000\000\000\000\000")) { sym = parse_attributes(sym, &field_dcl); } diff --git a/ext/ffi/tests/ptr_declaration_list.phpt b/ext/ffi/tests/ptr_declaration_list.phpt new file mode 100644 index 0000000000000..cb8ecafd935ba --- /dev/null +++ b/ext/ffi/tests/ptr_declaration_list.phpt @@ -0,0 +1,33 @@ +--TEST-- +Declaration Lists with Pointers +--EXTENSIONS-- +ffi +--SKIPIF-- +--FILE-- +new('struct MyStruct'); +$one = $ffi->new("uint8_t"); +$oneptr = $ffi->new("uint8_t*"); +$oneptrptr = $ffi->new("uint8_t**"); +$one->cdata = 1; +$oneptr = FFI::addr($one); +$oneptrptr = FFI::addr($oneptr); + +$test_struct->a = $oneptrptr; +$test_struct->b = $oneptr; +$test_struct->c = $one; + +var_dump($test_struct->a[0][0]); +var_dump($test_struct->b[0]); +var_dump($test_struct->c); +?> +--EXPECT-- +int(1) +int(1) +int(1) diff --git a/ext/gd/libgd/gd.c b/ext/gd/libgd/gd.c index 7265758696ad3..0bd6e4b587e9f 100644 --- a/ext/gd/libgd/gd.c +++ b/ext/gd/libgd/gd.c @@ -3108,7 +3108,11 @@ int gdImagePaletteToTrueColor(gdImagePtr src) const unsigned int sy = gdImageSY(src); const unsigned int sx = gdImageSX(src); - src->tpixels = (int **) gdMalloc(sizeof(int *) * sy); + // Note: do not revert back to gdMalloc() below ; reason here, + // due to a bug with a certain memory_limit INI value treshold, + // imagepalettetotruecolor crashes with even unrelated ZendMM allocations. + // See GH-17772 for an use case. + src->tpixels = (int **) gdCalloc(sizeof(int *), sy); if (src->tpixels == NULL) { return 0; } diff --git a/ext/gd/tests/bug22544-mb.phpt b/ext/gd/tests/bug22544-mb.phpt index b67478e558c47..f99307df1212b 100644 --- a/ext/gd/tests/bug22544-mb.phpt +++ b/ext/gd/tests/bug22544-mb.phpt @@ -2,6 +2,12 @@ Bug #22544 (TrueColor transparency in PNG images). --EXTENSIONS-- gd +--SKIPIF-- + --FILE-- --FILE-- --FILE-- --FILE-- 45) --EXTENSIONS-- gd ---SKIPIF-- - --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- = 2.2.5'); } +if (!(imagetypes() & IMG_PNG)) { + die("skip No PNG support"); +} ?> --FILE-- --FILE-- --FILE-- +--FILE-- + +--EXPECTF-- +Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d diff --git a/ext/gd/tests/imagearc_basic.phpt b/ext/gd/tests/imagearc_basic.phpt index be80f6c5f2317..339ea0b79d18d 100644 --- a/ext/gd/tests/imagearc_basic.phpt +++ b/ext/gd/tests/imagearc_basic.phpt @@ -5,6 +5,12 @@ Edgar Ferreira da Silva #testfest PHPSP on 2009-06-20 --EXTENSIONS-- gd +--SKIPIF-- + --FILE-- #testfest PHPSP on 2009-06-20 --EXTENSIONS-- gd +--SKIPIF-- + --FILE-- #testfest PHPSP on 2009-06-20 --EXTENSIONS-- gd +--SKIPIF-- + --FILE-- #testfest PHPSP on 2009-06-20 --EXTENSIONS-- gd +--SKIPIF-- + --FILE-- #testfest PHPSP on 2009-06-20 --EXTENSIONS-- gd +--SKIPIF-- + --FILE-- --FILE-- #testfest PHPSP on 2009-06-20 --EXTENSIONS-- gd +--SKIPIF-- + --FILE-- #testfest PHPSP on 2009-06-20 --EXTENSIONS-- gd +--SKIPIF-- + --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- #testfest PHPSP on 2009-06-20 --EXTENSIONS-- gd +--SKIPIF-- + --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- #testfest PHPSP on 2009-06-30 --EXTENSIONS-- gd +--SKIPIF-- + --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- #testfest PHPSP on 2009-06-30 --EXTENSIONS-- gd +--SKIPIF-- + --FILE-- --FILE-- --FILE-- #testfest PHPSP on 2009-06-20 --EXTENSIONS-- gd +--SKIPIF-- + --FILE-- --FILE-- #testfest PHPSP on 2009-06-20 --EXTENSIONS-- gd +--SKIPIF-- + --FILE-- #testfest PHPSP on 2009-06-20 --EXTENSIONS-- gd +--SKIPIF-- + --FILE-- --FILE-- --FILE-- --FILE-- $regions) { } } ?> ---EXPECT-- +--EXPECTF-- ** Gnomeregan bool(false) Error: intltz_get_windows_id: Unknown windows timezone: U_ILLEGAL_ARGUMENT_ERROR @@ -36,7 +36,7 @@ string(19) "America/Los_Angeles" string(17) "America/Vancouver" string(19) "America/Los_Angeles" string(19) "America/Los_Angeles" -string(7) "PST8PDT" +string(%d) "%r(PST8PDT|America\/Los_Angeles)%r" ** Romance Standard Time string(12) "Europe/Paris" string(15) "Europe/Brussels" diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c index e66ff070577bd..db4c5ab231042 100644 --- a/ext/ldap/ldap.c +++ b/ext/ldap/ldap.c @@ -248,7 +248,7 @@ static bool php_ldap_is_numerically_indexed_array(zend_array *arr) } } ZEND_HASH_FOREACH_END(); - return false; + return true; } /* {{{ Parse controls from and to arrays */ diff --git a/ext/ldap/tests/gh17704.phpt b/ext/ldap/tests/gh17704.phpt new file mode 100644 index 0000000000000..2403a63860e4a --- /dev/null +++ b/ext/ldap/tests/gh17704.phpt @@ -0,0 +1,24 @@ +--TEST-- +GH-17704 (ldap_search fails when $attributes contains a non-packed array with numerical keys) +--EXTENSIONS-- +ldap +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +object(LDAP\Result)#%d (0) { +} diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c index 6590f73f9edd6..18ca51e36a052 100644 --- a/ext/libxml/libxml.c +++ b/ext/libxml/libxml.c @@ -525,41 +525,52 @@ php_libxml_input_buffer_create_filename(const char *URI, xmlCharEncoding enc) if (Z_TYPE(s->wrapperdata) == IS_ARRAY) { zval *header; - ZEND_HASH_FOREACH_VAL_IND(Z_ARRVAL(s->wrapperdata), header) { + /* Scan backwards: The header array might contain the headers for multiple responses, if + * a redirect was followed. + */ + ZEND_HASH_REVERSE_FOREACH_VAL_IND(Z_ARRVAL(s->wrapperdata), header) { const char buf[] = "Content-Type:"; - if (Z_TYPE_P(header) == IS_STRING && - !zend_binary_strncasecmp(Z_STRVAL_P(header), Z_STRLEN_P(header), buf, sizeof(buf)-1, sizeof(buf)-1)) { - char needle[] = "charset="; - char *haystack = estrndup(Z_STRVAL_P(header), Z_STRLEN_P(header)); - char *encoding = php_stristr(haystack, needle, Z_STRLEN_P(header), strlen(needle)); - - if (encoding) { - char *end; - - encoding += sizeof("charset=")-1; - if (*encoding == '"') { - encoding++; - } - end = strchr(encoding, ';'); - if (end == NULL) { - end = encoding + strlen(encoding); - } - end--; /* end == encoding-1 isn't a buffer underrun */ - while (*end == ' ' || *end == '\t') { - end--; - } - if (*end == '"') { - end--; - } - if (encoding >= end) continue; - *(end+1) = '\0'; - enc = xmlParseCharEncoding(encoding); - if (enc <= XML_CHAR_ENCODING_NONE) { - enc = XML_CHAR_ENCODING_NONE; + if (Z_TYPE_P(header) == IS_STRING) { + /* If no colon is found in the header, we assume it's the HTTP status line and bail out. */ + char *colon = memchr(Z_STRVAL_P(header), ':', Z_STRLEN_P(header)); + char *space = memchr(Z_STRVAL_P(header), ' ', Z_STRLEN_P(header)); + if (colon == NULL || space < colon) { + break; + } + + if (!zend_binary_strncasecmp(Z_STRVAL_P(header), Z_STRLEN_P(header), buf, sizeof(buf)-1, sizeof(buf)-1)) { + char needle[] = "charset="; + char *haystack = estrndup(Z_STRVAL_P(header), Z_STRLEN_P(header)); + char *encoding = php_stristr(haystack, needle, Z_STRLEN_P(header), sizeof("charset=")-1); + + if (encoding) { + char *end; + + encoding += sizeof("charset=")-1; + if (*encoding == '"') { + encoding++; + } + end = strchr(encoding, ';'); + if (end == NULL) { + end = encoding + strlen(encoding); + } + end--; /* end == encoding-1 isn't a buffer underrun */ + while (*end == ' ' || *end == '\t') { + end--; + } + if (*end == '"') { + end--; + } + if (encoding >= end) continue; + *(end+1) = '\0'; + enc = xmlParseCharEncoding(encoding); + if (enc <= XML_CHAR_ENCODING_NONE) { + enc = XML_CHAR_ENCODING_NONE; + } } + efree(haystack); + break; /* found content-type */ } - efree(haystack); - break; /* found content-type */ } } ZEND_HASH_FOREACH_END(); } diff --git a/ext/mbstring/mbstring.c b/ext/mbstring/mbstring.c index 2da957454a902..dbf012174c494 100644 --- a/ext/mbstring/mbstring.c +++ b/ext/mbstring/mbstring.c @@ -3092,7 +3092,8 @@ try_next_encoding:; } for (size_t i = 0; i < length; i++) { - array[i].demerits *= array[i].multiplier; + double demerits = array[i].demerits * (double) array[i].multiplier; + array[i].demerits = demerits < (double) UINT64_MAX ? (uint64_t) demerits : UINT64_MAX; } return length; diff --git a/ext/mbstring/tests/gh17503.phpt b/ext/mbstring/tests/gh17503.phpt new file mode 100644 index 0000000000000..92a2cf39cb10f --- /dev/null +++ b/ext/mbstring/tests/gh17503.phpt @@ -0,0 +1,11 @@ +--TEST-- +GH-17503 (Undefined float conversion in mb_convert_variables) +--EXTENSIONS-- +mbstring +--FILE-- +"); +var_dump(mb_convert_variables("ASCII", ["UTF-8", "UTF-16"], $a)); +?> +--EXPECT-- +string(5) "UTF-8" diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index a71a512c03edd..eac5cbbc41f7d 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -1334,7 +1334,12 @@ zend_string *accel_make_persistent_key(zend_string *str) EXPECTED((parent_script = zend_get_executed_filename_ex()) != NULL)) { parent_script_len = ZSTR_LEN(parent_script); - while ((--parent_script_len > 0) && !IS_SLASH(ZSTR_VAL(parent_script)[parent_script_len])); + while (parent_script_len > 0) { + --parent_script_len; + if (IS_SLASH(ZSTR_VAL(parent_script)[parent_script_len])) { + break; + } + } if (UNEXPECTED((size_t)(key_length + parent_script_len + 1) >= ZCG_KEY_LEN)) { return NULL; diff --git a/ext/opcache/jit/zend_jit_arm64.dasc b/ext/opcache/jit/zend_jit_arm64.dasc index 2cbf68643086a..985c08f0512ea 100644 --- a/ext/opcache/jit/zend_jit_arm64.dasc +++ b/ext/opcache/jit/zend_jit_arm64.dasc @@ -8655,11 +8655,18 @@ static int zend_jit_push_call_frame(dasm_State **Dst, const zend_op *opline, con | str TMP1w, EX:RX->This.u1.type_info | // Z_PTR(call->This) = object_or_called_scope; | str REG1, EX:RX->This.value.ptr - | ldr TMP1, [REG0, #offsetof(zend_closure, func.op_array.run_time_cache__ptr)] - | cbnz TMP1, >1 - | add FCARG1x, REG0, #offsetof(zend_closure, func) - | EXT_CALL zend_jit_init_func_run_time_cache_helper, REG0 - |1: + if (!func) { + | ldrb TMP1w, [REG0, #offsetof(zend_closure, func.type)] + | cmp TMP1w, #ZEND_USER_FUNCTION + | beq >1 + } + if (!func || func->common.type == ZEND_USER_FUNCTION) { + | add FCARG1x, REG0, #offsetof(zend_closure, func) + | EXT_CALL zend_jit_init_func_run_time_cache_helper, REG0 + } + if (!func) { + |1: + } } | // ZEND_CALL_NUM_ARGS(call) = num_args; | LOAD_32BIT_VAL TMP1w, opline->extended_value @@ -9200,9 +9207,9 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend func = call_info->callee_func; } if ((op_array->fn_flags & ZEND_ACC_TRAIT_CLONE) - && JIT_G(current_frame) - && JIT_G(current_frame)->call - && !JIT_G(current_frame)->call->func) { + && (!JIT_G(current_frame) || + !JIT_G(current_frame)->call || + !JIT_G(current_frame)->call->func)) { call_info = NULL; func = NULL; /* megamorphic call from trait */ } } diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index 5d824c207d18f..33c554a3f78ef 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -1745,7 +1745,8 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin if (!(orig_op1_type & IS_TRACE_PACKED)) { zend_ssa_var_info *info = &tssa->var_info[tssa->ops[idx].op1_use]; - if (MAY_BE_PACKED(info->type) && MAY_BE_HASH(info->type)) { + if (MAY_BE_PACKED(info->type) && MAY_BE_HASH(info->type) + && (info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) { info->type |= MAY_BE_PACKED_GUARD; info->type &= ~MAY_BE_ARRAY_PACKED; } @@ -1754,7 +1755,8 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin && val_type != IS_UNDEF) { zend_ssa_var_info *info = &tssa->var_info[tssa->ops[idx].op1_use]; - if (MAY_BE_PACKED(info->type) && MAY_BE_HASH(info->type)) { + if (MAY_BE_PACKED(info->type) && MAY_BE_HASH(info->type) + && (info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) { info->type |= MAY_BE_PACKED_GUARD; info->type &= ~(MAY_BE_ARRAY_NUMERIC_HASH|MAY_BE_ARRAY_STRING_HASH); } @@ -1833,7 +1835,8 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin zend_ssa_var_info *info = &tssa->var_info[tssa->ops[idx].op1_use]; - if (MAY_BE_PACKED(info->type) && MAY_BE_HASH(info->type)) { + if (MAY_BE_PACKED(info->type) && MAY_BE_HASH(info->type) + && (info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) { info->type |= MAY_BE_PACKED_GUARD; if (orig_op1_type & IS_TRACE_PACKED) { info->type &= ~(MAY_BE_ARRAY_NUMERIC_HASH|MAY_BE_ARRAY_STRING_HASH); @@ -1935,7 +1938,8 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin zend_ssa_var_info *info = &tssa->var_info[tssa->ops[idx].op1_use]; - if (MAY_BE_PACKED(info->type) && MAY_BE_HASH(info->type)) { + if (MAY_BE_PACKED(info->type) && MAY_BE_HASH(info->type) + && (info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) { info->type |= MAY_BE_PACKED_GUARD; if (orig_op1_type & IS_TRACE_PACKED) { info->type &= ~(MAY_BE_ARRAY_NUMERIC_HASH|MAY_BE_ARRAY_STRING_HASH); @@ -1965,7 +1969,8 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin zend_ssa_var_info *info = &tssa->var_info[tssa->ops[idx].op1_use]; - if (MAY_BE_PACKED(info->type) && MAY_BE_HASH(info->type)) { + if (MAY_BE_PACKED(info->type) && MAY_BE_HASH(info->type) + && (info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) { info->type |= MAY_BE_PACKED_GUARD; info->type &= ~MAY_BE_ARRAY_PACKED; } @@ -4164,10 +4169,14 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par if ((info & MAY_BE_PACKED_GUARD) != 0 && (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP || trace_buffer->stop == ZEND_JIT_TRACE_STOP_RECURSIVE_CALL - || trace_buffer->stop == ZEND_JIT_TRACE_STOP_RECURSIVE_RET) + || (trace_buffer->stop == ZEND_JIT_TRACE_STOP_RECURSIVE_RET + && (opline-1)->result_type == IS_VAR + && EX_VAR_TO_NUM((opline-1)->result.var) == i)) && (ssa->vars[i].use_chain != -1 || (ssa->vars[i].phi_use_chain && !(ssa->var_info[ssa->vars[i].phi_use_chain->ssa_var].type & MAY_BE_PACKED_GUARD)))) { + ZEND_ASSERT(STACK_TYPE(stack, i) == IS_ARRAY); + if (!zend_jit_packed_guard(&dasm_state, opline, EX_NUM_TO_VAR(i), info)) { goto jit_failure; } diff --git a/ext/opcache/jit/zend_jit_x86.dasc b/ext/opcache/jit/zend_jit_x86.dasc index 9cf0c6cd8e881..1f1abb59a1c24 100644 --- a/ext/opcache/jit/zend_jit_x86.dasc +++ b/ext/opcache/jit/zend_jit_x86.dasc @@ -9255,11 +9255,17 @@ static int zend_jit_push_call_frame(dasm_State **Dst, const zend_op *opline, con | or dword EX:RX->This.u1.type_info, edx | // Z_PTR(call->This) = object_or_called_scope; | mov aword EX:RX->This.value.ptr, r1 - | cmp aword [r0 + offsetof(zend_closure, func.op_array.run_time_cache__ptr)], 0 - | jnz >1 - | lea FCARG1a, aword [r0 + offsetof(zend_closure, func)] - | EXT_CALL zend_jit_init_func_run_time_cache_helper, r0 - |1: + if (!func) { + | cmp byte [r0 + offsetof(zend_closure, func.type)], ZEND_USER_FUNCTION + | jnz >1 + } + if (!func || func->common.type == ZEND_USER_FUNCTION) { + | lea FCARG1a, aword [r0 + offsetof(zend_closure, func)] + | EXT_CALL zend_jit_init_func_run_time_cache_helper, r0 + } + if (!func) { + |1: + } } | // ZEND_CALL_NUM_ARGS(call) = num_args; | mov dword EX:RX->This.u2.num_args, opline->extended_value @@ -9931,9 +9937,9 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend func = call_info->callee_func; } if ((op_array->fn_flags & ZEND_ACC_TRAIT_CLONE) - && JIT_G(current_frame) - && JIT_G(current_frame)->call - && !JIT_G(current_frame)->call->func) { + && (!JIT_G(current_frame) || + !JIT_G(current_frame)->call || + !JIT_G(current_frame)->call->func)) { call_info = NULL; func = NULL; /* megamorphic call from trait */ } } diff --git a/ext/opcache/tests/jit/gh17577.phpt b/ext/opcache/tests/jit/gh17577.phpt new file mode 100644 index 0000000000000..2eac2d05e432d --- /dev/null +++ b/ext/opcache/tests/jit/gh17577.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-17577 (JIT packed type guard crash) +--EXTENSIONS-- +opcache +--INI-- +opcache.jit_buffer_size=16M +opcache.jit_hot_func=1 +--FILE-- + +--EXPECTF-- +Warning: Trying to access array offset on int in %s on line %d + +Warning: Trying to access array offset on int in %s on line %d + +Warning: Trying to access array offset on int in %s on line %d diff --git a/ext/opcache/tests/jit/gh17654.phpt b/ext/opcache/tests/jit/gh17654.phpt new file mode 100644 index 0000000000000..59d9205b37f2a --- /dev/null +++ b/ext/opcache/tests/jit/gh17654.phpt @@ -0,0 +1,38 @@ +--TEST-- +GH-17654 (Multiple classes using same trait causes function JIT crash) +--EXTENSIONS-- +opcache +--INI-- +opcache.jit=1214 +opcache.jit_buffer_size=16M +--FILE-- +addUnit("test2"); + (new Test)->addUnit("test"); +} + +main(); +?> +--EXPECT-- +string(5) "test2" +string(4) "test" diff --git a/ext/pdo_sqlite/sqlite_statement.c b/ext/pdo_sqlite/sqlite_statement.c index c6b907f6fc22f..16aac6095af1a 100644 --- a/ext/pdo_sqlite/sqlite_statement.c +++ b/ext/pdo_sqlite/sqlite_statement.c @@ -305,7 +305,7 @@ static int pdo_sqlite_stmt_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *ret const char *str; zval flags; - if (!S->stmt) { + if (!S->stmt || !stmt->executed) { return FAILURE; } if(colno >= sqlite3_column_count(S->stmt)) { diff --git a/ext/pdo_sqlite/tests/gh17837.phpt b/ext/pdo_sqlite/tests/gh17837.phpt new file mode 100644 index 0000000000000..c8e1f6dab2829 --- /dev/null +++ b/ext/pdo_sqlite/tests/gh17837.phpt @@ -0,0 +1,14 @@ +--TEST-- +GH-17837 (::getColumnMeta() on unexecuted statement segfaults) +--EXTENSIONS-- +pdo_sqlite +--CREDITS-- +YuanchengJiang +--FILE-- +prepare('select :a, :b, ?'); +var_dump($stmt->getColumnMeta(0)); +?> +--EXPECT-- +bool(false) diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c index 39526ce4b235b..bd724b1036949 100644 --- a/ext/phar/phar_object.c +++ b/ext/phar/phar_object.c @@ -4483,6 +4483,9 @@ PHP_METHOD(PharFileInfo, __construct) efree(entry); entry_obj->entry = entry_info; + if (!entry_info->is_persistent && !entry_info->is_temp_dir) { + ++entry_info->fp_refcount; + } ZVAL_STRINGL(&arg1, fname, fname_len); @@ -4512,15 +4515,23 @@ PHP_METHOD(PharFileInfo, __destruct) RETURN_THROWS(); } - if (entry_obj->entry && entry_obj->entry->is_temp_dir) { + if (!entry_obj->entry) { + return; + } + + if (entry_obj->entry->is_temp_dir) { if (entry_obj->entry->filename) { efree(entry_obj->entry->filename); entry_obj->entry->filename = NULL; } efree(entry_obj->entry); - entry_obj->entry = NULL; + } else if (!entry_obj->entry->is_persistent) { + --entry_obj->entry->fp_refcount; + /* It is necessarily still in the manifest, which will ultimately free this. */ } + + entry_obj->entry = NULL; } /* }}} */ diff --git a/ext/phar/tests/gh17808.phpt b/ext/phar/tests/gh17808.phpt new file mode 100644 index 0000000000000..03e54ff264bfa --- /dev/null +++ b/ext/phar/tests/gh17808.phpt @@ -0,0 +1,22 @@ +--TEST-- +GH-17808 (PharFileInfo refcount bug) +--EXTENSIONS-- +phar +zlib +--FILE-- +getContent())); +unlink("$file"); +var_dump($file->getATime()); +?> +--EXPECTF-- +string(%d) "phar://%spackage.xml" +int(6747) + +Warning: unlink(): phar error: "package.xml" in phar %s, has open file pointers, cannot unlink in %s on line %d +int(33188) diff --git a/ext/phar/tests/zip/033.phpt b/ext/phar/tests/zip/033.phpt index de4ba2b71f027..cf5ccd9a51050 100644 --- a/ext/phar/tests/zip/033.phpt +++ b/ext/phar/tests/zip/033.phpt @@ -5,6 +5,12 @@ phar --INI-- phar.readonly=0 phar.require_hash=0 +--SKIPIF-- + --FILE-- ce; - ZEND_ASSERT(class->ce_flags & ZEND_ACC_ENUM); - smart_str_append(str, class->name); - smart_str_appends(str, "::"); - smart_str_append(str, Z_STR_P(zend_enum_fetch_case_name(obj))); + if (class->ce_flags & ZEND_ACC_ENUM) { + smart_str_append(str, class->name); + smart_str_appends(str, "::"); + smart_str_append(str, Z_STR_P(zend_enum_fetch_case_name(obj))); + } else { + smart_str_appends(str, "object("); + smart_str_append(str, class->name); + smart_str_appends(str, ")"); + } } else { ZEND_ASSERT(Z_TYPE_P(value) == IS_CONSTANT_AST); zend_string *ast_str = zend_ast_export("", Z_ASTVAL_P(value), ""); diff --git a/ext/reflection/tests/gh15902/ReflectionClass-callable.phpt b/ext/reflection/tests/gh15902/ReflectionClass-callable.phpt new file mode 100644 index 0000000000000..0ed02dd837c9a --- /dev/null +++ b/ext/reflection/tests/gh15902/ReflectionClass-callable.phpt @@ -0,0 +1,37 @@ +--TEST-- +ReflectionClass object default property - used to say "callable" +--INI-- +opcache.enable_cli=0 +--FILE-- + +--EXPECTF-- +Class [ class C ] { + @@ %sReflectionClass-callable.php %d-%d + + - Constants [0] { + } + + - Static properties [0] { + } + + - Static methods [0] { + } + + - Properties [1] { + Property [ public stdClass $a = object(stdClass) ] + } + + - Methods [0] { + } +} diff --git a/ext/reflection/tests/gh15902/ReflectionClass-class.phpt b/ext/reflection/tests/gh15902/ReflectionClass-class.phpt new file mode 100644 index 0000000000000..cb03cdd383fec --- /dev/null +++ b/ext/reflection/tests/gh15902/ReflectionClass-class.phpt @@ -0,0 +1,38 @@ +--TEST-- +ReflectionClass object default property - used to say "__CLASS__" +--INI-- +opcache.enable_cli=0 +--FILE-- + +--EXPECTF-- +Class [ class C ] { + @@ %sReflectionClass-class.php %d-%d + + - Constants [0] { + } + + - Static properties [0] { + } + + - Static methods [0] { + } + + - Properties [1] { + Property [ public stdClass $a = object(stdClass) ] + } + + - Methods [0] { + } +} diff --git a/ext/reflection/tests/gh15902/ReflectionProperty-callable.phpt b/ext/reflection/tests/gh15902/ReflectionProperty-callable.phpt new file mode 100644 index 0000000000000..b88d52721fce0 --- /dev/null +++ b/ext/reflection/tests/gh15902/ReflectionProperty-callable.phpt @@ -0,0 +1,20 @@ +--TEST-- +ReflectionProperty object default - used to say "callable" +--INI-- +opcache.enable_cli=0 +--FILE-- + +--EXPECTF-- +Property [ public stdClass $a = object(stdClass) ] diff --git a/ext/reflection/tests/gh15902/ReflectionProperty-class.phpt b/ext/reflection/tests/gh15902/ReflectionProperty-class.phpt new file mode 100644 index 0000000000000..021df4fe87be2 --- /dev/null +++ b/ext/reflection/tests/gh15902/ReflectionProperty-class.phpt @@ -0,0 +1,20 @@ +--TEST-- +ReflectionProperty object default - used to say "__CLASS__" +--INI-- +opcache.enable_cli=0 +--FILE-- + +--EXPECTF-- +Property [ public stdClass $a = object(stdClass) ] diff --git a/ext/sqlite3/sqlite3.c b/ext/sqlite3/sqlite3.c index badcfcc29b0c5..09cb57410c87f 100644 --- a/ext/sqlite3/sqlite3.c +++ b/ext/sqlite3/sqlite3.c @@ -2256,7 +2256,7 @@ static HashTable *php_sqlite3_get_gc(zend_object *object, zval **table, int *n) { php_sqlite3_db_object *intern = php_sqlite3_db_from_obj(object); - if (intern->funcs == NULL && intern->collations == NULL) { + if (intern->funcs == NULL && intern->collations == NULL && !ZEND_FCC_INITIALIZED(intern->authorizer_fcc)) { /* Fast path without allocations */ *table = NULL; *n = 0; @@ -2264,6 +2264,8 @@ static HashTable *php_sqlite3_get_gc(zend_object *object, zval **table, int *n) } else { zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create(); + php_sqlite3_gc_buffer_add_fcc(gc_buffer, &intern->authorizer_fcc); + php_sqlite3_func *func = intern->funcs; while (func != NULL) { php_sqlite3_gc_buffer_add_fcc(gc_buffer, &func->func); diff --git a/ext/sqlite3/tests/setauthorizer_cycle_leak.phpt b/ext/sqlite3/tests/setauthorizer_cycle_leak.phpt new file mode 100644 index 0000000000000..b0ff384c72273 --- /dev/null +++ b/ext/sqlite3/tests/setauthorizer_cycle_leak.phpt @@ -0,0 +1,21 @@ +--TEST-- +setAuthorizer() cycle leak +--EXTENSIONS-- +sqlite3 +--FILE-- +setAuthorizer([$this, "foo"]); + } + + public function foo() {} +} + +$test = new Foo; + +echo "Done\n"; +?> +--EXPECT-- +Done diff --git a/ext/standard/filestat.c b/ext/standard/filestat.c index 1d83dcfe940df..9b8ff7efd6a66 100644 --- a/ext/standard/filestat.c +++ b/ext/standard/filestat.c @@ -388,6 +388,9 @@ static void php_do_chgrp(INTERNAL_FUNCTION_PARAMETERS, int do_lchgrp) /* {{{ */ php_error_docref(NULL, E_WARNING, "%s", strerror(errno)); RETURN_FALSE; } + + php_clear_stat_cache(0, NULL, 0); + RETURN_TRUE; #endif } @@ -527,6 +530,9 @@ static void php_do_chown(INTERNAL_FUNCTION_PARAMETERS, int do_lchown) /* {{{ */ php_error_docref(NULL, E_WARNING, "%s", strerror(errno)); RETURN_FALSE; } + + php_clear_stat_cache(0, NULL, 0); + RETURN_TRUE; #endif } @@ -591,6 +597,9 @@ PHP_FUNCTION(chmod) php_error_docref(NULL, E_WARNING, "%s", strerror(errno)); RETURN_FALSE; } + + php_clear_stat_cache(0, NULL, 0); + RETURN_TRUE; } /* }}} */ @@ -676,6 +685,9 @@ PHP_FUNCTION(touch) php_error_docref(NULL, E_WARNING, "Utime failed: %s", strerror(errno)); RETURN_FALSE; } + + php_clear_stat_cache(0, NULL, 0); + RETURN_TRUE; } /* }}} */ diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index fb987c82e5cc2..b4d065dd0b625 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -67,15 +67,16 @@ #include "php_fopen_wrappers.h" -#define HTTP_HEADER_BLOCK_SIZE 1024 -#define PHP_URL_REDIRECT_MAX 20 -#define HTTP_HEADER_USER_AGENT 1 -#define HTTP_HEADER_HOST 2 -#define HTTP_HEADER_AUTH 4 -#define HTTP_HEADER_FROM 8 -#define HTTP_HEADER_CONTENT_LENGTH 16 -#define HTTP_HEADER_TYPE 32 -#define HTTP_HEADER_CONNECTION 64 +#define HTTP_HEADER_BLOCK_SIZE 1024 +#define HTTP_HEADER_MAX_LOCATION_SIZE 8182 /* 8192 - 10 (size of "Location: ") */ +#define PHP_URL_REDIRECT_MAX 20 +#define HTTP_HEADER_USER_AGENT 1 +#define HTTP_HEADER_HOST 2 +#define HTTP_HEADER_AUTH 4 +#define HTTP_HEADER_FROM 8 +#define HTTP_HEADER_CONTENT_LENGTH 16 +#define HTTP_HEADER_TYPE 32 +#define HTTP_HEADER_CONNECTION 64 #define HTTP_WRAPPER_HEADER_INIT 1 #define HTTP_WRAPPER_REDIRECTED 2 @@ -107,7 +108,7 @@ static inline void strip_header(char *header_bag, char *lc_header_bag, static bool check_has_header(const char *headers, const char *header) { const char *s = headers; while ((s = strstr(s, header))) { - if (s == headers || *(s-1) == '\n') { + if (s == headers || (*(s-1) == '\n' && *(s-2) == '\r')) { return 1; } s++; @@ -115,6 +116,214 @@ static bool check_has_header(const char *headers, const char *header) { return 0; } +typedef struct _php_stream_http_response_header_info { + php_stream_filter *transfer_encoding; + size_t file_size; + bool error; + bool follow_location; + char *location; + size_t location_len; +} php_stream_http_response_header_info; + +static void php_stream_http_response_header_info_init( + php_stream_http_response_header_info *header_info) +{ + memset(header_info, 0, sizeof(php_stream_http_response_header_info)); + header_info->follow_location = 1; +} + +/* Trim white spaces from response header line and update its length */ +static bool php_stream_http_response_header_trim(char *http_header_line, + size_t *http_header_line_length) +{ + char *http_header_line_end = http_header_line + *http_header_line_length - 1; + while (http_header_line_end >= http_header_line && + (*http_header_line_end == '\n' || *http_header_line_end == '\r')) { + http_header_line_end--; + } + + /* The primary definition of an HTTP header in RFC 7230 states: + * > Each header field consists of a case-insensitive field name followed + * > by a colon (":"), optional leading whitespace, the field value, and + * > optional trailing whitespace. */ + + /* Strip trailing whitespace */ + bool space_trim = (*http_header_line_end == ' ' || *http_header_line_end == '\t'); + if (space_trim) { + do { + http_header_line_end--; + } while (http_header_line_end >= http_header_line && + (*http_header_line_end == ' ' || *http_header_line_end == '\t')); + } + http_header_line_end++; + *http_header_line_end = '\0'; + *http_header_line_length = http_header_line_end - http_header_line; + + return space_trim; +} + +/* Process folding headers of the current line and if there are none, parse last full response + * header line. It returns NULL if the last header is finished, otherwise it returns updated + * last header line. */ +static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *wrapper, + php_stream *stream, php_stream_context *context, int options, + zend_string *last_header_line_str, char *header_line, size_t *header_line_length, + int response_code, zval *response_header, + php_stream_http_response_header_info *header_info) +{ + char *last_header_line = ZSTR_VAL(last_header_line_str); + size_t last_header_line_length = ZSTR_LEN(last_header_line_str); + char *last_header_line_end = ZSTR_VAL(last_header_line_str) + ZSTR_LEN(last_header_line_str) - 1; + + /* Process non empty header line. */ + if (header_line && (*header_line != '\n' && *header_line != '\r')) { + /* Removing trailing white spaces. */ + if (php_stream_http_response_header_trim(header_line, header_line_length) && + *header_line_length == 0) { + /* Only spaces so treat as an empty folding header. */ + return last_header_line_str; + } + + /* Process folding headers if starting with a space or a tab. */ + if (header_line && (*header_line == ' ' || *header_line == '\t')) { + char *http_folded_header_line = header_line; + size_t http_folded_header_line_length = *header_line_length; + /* Remove the leading white spaces. */ + while (*http_folded_header_line == ' ' || *http_folded_header_line == '\t') { + http_folded_header_line++; + http_folded_header_line_length--; + } + /* It has to have some characters because it would get returned after the call + * php_stream_http_response_header_trim above. */ + ZEND_ASSERT(http_folded_header_line_length > 0); + /* Concatenate last header line, space and current header line. */ + zend_string *extended_header_str = zend_string_concat3( + last_header_line, last_header_line_length, + " ", 1, + http_folded_header_line, http_folded_header_line_length); + zend_string_efree(last_header_line_str); + last_header_line_str = extended_header_str; + /* Return new header line. */ + return last_header_line_str; + } + } + + /* Find header separator position. */ + char *last_header_value = memchr(last_header_line, ':', last_header_line_length); + if (last_header_value) { + /* Verify there is no space in header name */ + char *last_header_name = last_header_line + 1; + while (last_header_name < last_header_value) { + if (*last_header_name == ' ' || *last_header_name == '\t') { + header_info->error = true; + php_stream_wrapper_log_error(wrapper, options, + "HTTP invalid response format (space in header name)!"); + zend_string_efree(last_header_line_str); + return NULL; + } + ++last_header_name; + } + + last_header_value++; /* Skip ':'. */ + + /* Strip leading whitespace. */ + while (last_header_value < last_header_line_end + && (*last_header_value == ' ' || *last_header_value == '\t')) { + last_header_value++; + } + } else { + /* There is no colon which means invalid response so error. */ + header_info->error = true; + php_stream_wrapper_log_error(wrapper, options, + "HTTP invalid response format (no colon in header line)!"); + zend_string_efree(last_header_line_str); + return NULL; + } + + bool store_header = true; + zval *tmpzval = NULL; + + if (!strncasecmp(last_header_line, "Location:", sizeof("Location:")-1)) { + /* Check if the location should be followed. */ + if (context && (tmpzval = php_stream_context_get_option(context, "http", "follow_location")) != NULL) { + header_info->follow_location = zval_is_true(tmpzval); + } else if (!((response_code >= 300 && response_code < 304) + || 307 == response_code || 308 == response_code)) { + /* The redirection should not be automatic if follow_location is not set and + * response_code not in (300, 301, 302, 303 and 307) + * see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.1 + * RFC 7238 defines 308: http://tools.ietf.org/html/rfc7238 */ + header_info->follow_location = 0; + } + size_t last_header_value_len = strlen(last_header_value); + if (last_header_value_len > HTTP_HEADER_MAX_LOCATION_SIZE) { + header_info->error = true; + php_stream_wrapper_log_error(wrapper, options, + "HTTP Location header size is over the limit of %d bytes", + HTTP_HEADER_MAX_LOCATION_SIZE); + zend_string_efree(last_header_line_str); + return NULL; + } + if (header_info->location_len == 0) { + header_info->location = emalloc(last_header_value_len + 1); + } else if (header_info->location_len <= last_header_value_len) { + header_info->location = erealloc(header_info->location, last_header_value_len + 1); + } + header_info->location_len = last_header_value_len; + memcpy(header_info->location, last_header_value, last_header_value_len + 1); + } else if (!strncasecmp(last_header_line, "Content-Type:", sizeof("Content-Type:")-1)) { + php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, last_header_value, 0); + } else if (!strncasecmp(last_header_line, "Content-Length:", sizeof("Content-Length:")-1)) { + /* https://www.rfc-editor.org/rfc/rfc9110.html#name-content-length */ + const char *ptr = last_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 */ + header_info->file_size = MIN(parsed, ZEND_LONG_MAX); + php_stream_notify_file_size(context, header_info->file_size, last_header_line, 0); + } + } + } else if ( + !strncasecmp(last_header_line, "Transfer-Encoding:", sizeof("Transfer-Encoding:")-1) + && !strncasecmp(last_header_value, "Chunked", sizeof("Chunked")-1) + ) { + /* Create filter to decode response body. */ + if (!(options & STREAM_ONLY_GET_HEADERS)) { + zend_long decode = 1; + + if (context && (tmpzval = php_stream_context_get_option(context, "http", "auto_decode")) != NULL) { + decode = zend_is_true(tmpzval); + } + if (decode) { + if (header_info->transfer_encoding != NULL) { + /* Prevent a memory leak in case there are more transfer-encoding headers. */ + php_stream_filter_free(header_info->transfer_encoding); + } + header_info->transfer_encoding = php_stream_filter_create( + "dechunk", NULL, php_stream_is_persistent(stream)); + if (header_info->transfer_encoding != NULL) { + /* Do not store transfer-encoding header. */ + store_header = false; + } + } + } + } + + if (store_header) { + zval http_header; + ZVAL_NEW_STR(&http_header, last_header_line_str); + zend_hash_next_index_insert(Z_ARRVAL_P(response_header), &http_header); + } else { + zend_string_efree(last_header_line_str); + } + + return NULL; +} + static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context, int redirect_max, int flags, @@ -127,11 +336,12 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, zend_string *tmp = NULL; char *ua_str = NULL; zval *ua_zval = NULL, *tmpzval = NULL, ssl_proxy_peer_name; - char location[HTTP_HEADER_BLOCK_SIZE]; int reqok = 0; char *http_header_line = NULL; + zend_string *last_header_line_str = NULL; + php_stream_http_response_header_info header_info; char tmp_line[128]; - size_t chunk_size = 0, file_size = 0; + size_t chunk_size = 0; int eol_detect = 0; zend_string *transport_string; zend_string *errstr = NULL; @@ -142,8 +352,6 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, int header_init = ((flags & HTTP_WRAPPER_HEADER_INIT) != 0); int redirected = ((flags & HTTP_WRAPPER_REDIRECTED) != 0); int redirect_keep_method = ((flags & HTTP_WRAPPER_KEEP_METHOD) != 0); - bool follow_location = 1; - php_stream_filter *transfer_encoding = NULL; int response_code; smart_str req_buf = {0}; bool custom_request_method; @@ -366,6 +574,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, } } + php_stream_http_response_header_info_init(&header_info); + if (stream == NULL) goto out; @@ -666,8 +876,6 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, /* send it */ php_stream_write(stream, ZSTR_VAL(req_buf.s), ZSTR_LEN(req_buf.s)); - location[0] = '\0'; - if (Z_ISUNDEF_P(response_header)) { array_init(response_header); } @@ -750,140 +958,103 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, } /* read past HTTP headers */ - while (!php_stream_eof(stream)) { size_t http_header_line_length; if (http_header_line != NULL) { efree(http_header_line); } - if ((http_header_line = php_stream_get_line(stream, NULL, 0, &http_header_line_length)) && *http_header_line != '\n' && *http_header_line != '\r') { - char *e = http_header_line + http_header_line_length - 1; - char *http_header_value; - - while (e >= http_header_line && (*e == '\n' || *e == '\r')) { - e--; - } - - /* The primary definition of an HTTP header in RFC 7230 states: - * > Each header field consists of a case-insensitive field name followed - * > by a colon (":"), optional leading whitespace, the field value, and - * > optional trailing whitespace. */ - - /* Strip trailing whitespace */ - while (e >= http_header_line && (*e == ' ' || *e == '\t')) { - e--; - } - - /* Terminate header line */ - e++; - *e = '\0'; - http_header_line_length = e - http_header_line; - - http_header_value = memchr(http_header_line, ':', http_header_line_length); - if (http_header_value) { - http_header_value++; /* Skip ':' */ - - /* Strip leading whitespace */ - while (http_header_value < e - && (*http_header_value == ' ' || *http_header_value == '\t')) { - http_header_value++; + if ((http_header_line = php_stream_get_line(stream, NULL, 0, &http_header_line_length))) { + bool last_line; + if (*http_header_line == '\r') { + if (http_header_line[1] != '\n') { + php_stream_close(stream); + stream = NULL; + php_stream_wrapper_log_error(wrapper, options, + "HTTP invalid header name (cannot start with CR character)!"); + goto out; } + last_line = true; + } else if (*http_header_line == '\n') { + last_line = true; } else { - /* There is no colon. Set the value to the end of the header line, which is - * effectively an empty string. */ - http_header_value = e; + last_line = false; } - - if (!strncasecmp(http_header_line, "Location:", sizeof("Location:")-1)) { - if (context && (tmpzval = php_stream_context_get_option(context, "http", "follow_location")) != NULL) { - follow_location = zval_is_true(tmpzval); - } else if (!((response_code >= 300 && response_code < 304) - || 307 == response_code || 308 == response_code)) { - /* we shouldn't redirect automatically - if follow_location isn't set and response_code not in (300, 301, 302, 303 and 307) - see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.1 - RFC 7238 defines 308: http://tools.ietf.org/html/rfc7238 */ - follow_location = 0; - } - strlcpy(location, http_header_value, sizeof(location)); - } 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)) { - /* 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); + + if (last_header_line_str != NULL) { + /* Parse last header line. */ + last_header_line_str = php_stream_http_response_headers_parse(wrapper, stream, + context, options, last_header_line_str, http_header_line, + &http_header_line_length, response_code, response_header, &header_info); + if (EXPECTED(last_header_line_str == NULL)) { + if (UNEXPECTED(header_info.error)) { + php_stream_close(stream); + stream = NULL; + goto out; } + } else { + /* Folding header present so continue. */ + continue; } - } else if ( - !strncasecmp(http_header_line, "Transfer-Encoding:", sizeof("Transfer-Encoding:")-1) - && !strncasecmp(http_header_value, "Chunked", sizeof("Chunked")-1) - ) { - - /* create filter to decode response body */ - if (!(options & STREAM_ONLY_GET_HEADERS)) { - zend_long decode = 1; - - if (context && (tmpzval = php_stream_context_get_option(context, "http", "auto_decode")) != NULL) { - decode = zend_is_true(tmpzval); - } - if (decode) { - transfer_encoding = php_stream_filter_create("dechunk", NULL, php_stream_is_persistent(stream)); - if (transfer_encoding) { - /* don't store transfer-encodeing header */ - continue; - } - } + } else if (!last_line) { + /* The first line cannot start with spaces. */ + if (*http_header_line == ' ' || *http_header_line == '\t') { + php_stream_close(stream); + stream = NULL; + php_stream_wrapper_log_error(wrapper, options, + "HTTP invalid response format (folding header at the start)!"); + goto out; } + /* Trim the first line if it is not the last line. */ + php_stream_http_response_header_trim(http_header_line, &http_header_line_length); } - - { - zval http_header; - ZVAL_STRINGL(&http_header, http_header_line, http_header_line_length); - zend_hash_next_index_insert(Z_ARRVAL_P(response_header), &http_header); + if (last_line) { + /* For the last line the last header line must be NULL. */ + ZEND_ASSERT(last_header_line_str == NULL); + break; } + /* Save current line as the last line so it gets parsed in the next round. */ + last_header_line_str = zend_string_init(http_header_line, http_header_line_length, 0); } else { break; } } - if (!reqok || (location[0] != '\0' && follow_location)) { - if (!follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) { + /* If the stream was closed early, we still want to process the last line to keep BC. */ + if (last_header_line_str != NULL) { + php_stream_http_response_headers_parse(wrapper, stream, context, options, + last_header_line_str, NULL, NULL, response_code, response_header, &header_info); + } + + if (!reqok || (header_info.location != NULL && header_info.follow_location)) { + if (!header_info.follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) { goto out; } - if (location[0] != '\0') - php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, location, 0); + if (header_info.location != NULL) + php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, header_info.location, 0); php_stream_close(stream); stream = NULL; - if (transfer_encoding) { - php_stream_filter_free(transfer_encoding); - transfer_encoding = NULL; + if (header_info.transfer_encoding) { + php_stream_filter_free(header_info.transfer_encoding); + header_info.transfer_encoding = NULL; } - if (location[0] != '\0') { + if (header_info.location != NULL) { - char new_path[HTTP_HEADER_BLOCK_SIZE]; - char loc_path[HTTP_HEADER_BLOCK_SIZE]; + char *new_path = NULL; - *new_path='\0'; - if (strlen(location)<8 || (strncasecmp(location, "http://", sizeof("http://")-1) && - strncasecmp(location, "https://", sizeof("https://")-1) && - strncasecmp(location, "ftp://", sizeof("ftp://")-1) && - strncasecmp(location, "ftps://", sizeof("ftps://")-1))) + if (strlen(header_info.location) < 8 || + (strncasecmp(header_info.location, "http://", sizeof("http://")-1) && + strncasecmp(header_info.location, "https://", sizeof("https://")-1) && + strncasecmp(header_info.location, "ftp://", sizeof("ftp://")-1) && + strncasecmp(header_info.location, "ftps://", sizeof("ftps://")-1))) { - if (*location != '/') { - if (*(location+1) != '\0' && resource->path) { + char *loc_path = NULL; + if (*header_info.location != '/') { + if (*(header_info.location+1) != '\0' && resource->path) { char *s = strrchr(ZSTR_VAL(resource->path), '/'); if (!s) { s = ZSTR_VAL(resource->path); @@ -899,29 +1070,35 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, if (resource->path && ZSTR_VAL(resource->path)[0] == '/' && ZSTR_VAL(resource->path)[1] == '\0') { - snprintf(loc_path, sizeof(loc_path) - 1, "%s%s", ZSTR_VAL(resource->path), location); + spprintf(&loc_path, 0, "%s%s", ZSTR_VAL(resource->path), header_info.location); } else { - snprintf(loc_path, sizeof(loc_path) - 1, "%s/%s", ZSTR_VAL(resource->path), location); + spprintf(&loc_path, 0, "%s/%s", ZSTR_VAL(resource->path), header_info.location); } } else { - snprintf(loc_path, sizeof(loc_path) - 1, "/%s", location); + spprintf(&loc_path, 0, "/%s", header_info.location); } } else { - strlcpy(loc_path, location, sizeof(loc_path)); + loc_path = header_info.location; + header_info.location = NULL; } if ((use_ssl && resource->port != 443) || (!use_ssl && resource->port != 80)) { - snprintf(new_path, sizeof(new_path) - 1, "%s://%s:%d%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), resource->port, loc_path); + spprintf(&new_path, 0, "%s://%s:%d%s", ZSTR_VAL(resource->scheme), + ZSTR_VAL(resource->host), resource->port, loc_path); } else { - snprintf(new_path, sizeof(new_path) - 1, "%s://%s%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), loc_path); + spprintf(&new_path, 0, "%s://%s%s", ZSTR_VAL(resource->scheme), + ZSTR_VAL(resource->host), loc_path); } + efree(loc_path); } else { - strlcpy(new_path, location, sizeof(new_path)); + new_path = header_info.location; + header_info.location = NULL; } php_url_free(resource); /* check for invalid redirection URLs */ if ((resource = php_url_parse(new_path)) == NULL) { php_stream_wrapper_log_error(wrapper, options, "Invalid redirect URL! %s", new_path); + efree(new_path); goto out; } @@ -933,6 +1110,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, while (s < e) { \ if (iscntrl(*s)) { \ php_stream_wrapper_log_error(wrapper, options, "Invalid redirect URL! %s", new_path); \ + efree(new_path); \ goto out; \ } \ s++; \ @@ -955,6 +1133,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, stream = php_stream_url_wrap_http_ex( wrapper, new_path, mode, options, opened_path, context, --redirect_max, new_flags, response_header STREAMS_CC); + efree(new_path); } else { php_stream_wrapper_log_error(wrapper, options, "HTTP request failed! %s", tmp_line); } @@ -967,6 +1146,10 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, efree(http_header_line); } + if (header_info.location != NULL) { + efree(header_info.location); + } + if (resource) { php_url_free(resource); } @@ -975,7 +1158,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, if (header_init) { ZVAL_COPY(&stream->wrapperdata, response_header); } - php_stream_notify_progress_init(context, 0, file_size); + php_stream_notify_progress_init(context, 0, header_info.file_size); /* Restore original chunk size now that we're done with headers */ if (options & STREAM_WILL_CAST) @@ -991,8 +1174,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, /* restore mode */ strlcpy(stream->mode, mode, sizeof(stream->mode)); - if (transfer_encoding) { - php_stream_filter_append(&stream->readfilters, transfer_encoding); + if (header_info.transfer_encoding) { + php_stream_filter_append(&stream->readfilters, header_info.transfer_encoding); } /* It's possible that the server already sent in more data than just the headers. diff --git a/ext/standard/tests/file/bug72666_variation1.phpt b/ext/standard/tests/file/bug72666_variation1.phpt new file mode 100644 index 0000000000000..6e59405d14ca8 --- /dev/null +++ b/ext/standard/tests/file/bug72666_variation1.phpt @@ -0,0 +1,19 @@ +--TEST-- +Bug #72666 (stat cache clearing inconsistent - touch) +--FILE-- + 2); +touch($filename, 1); +var_dump(filemtime($filename)); + +?> +--CLEAN-- + +--EXPECT-- +bool(true) +int(1) diff --git a/ext/standard/tests/file/bug72666_variation2.phpt b/ext/standard/tests/file/bug72666_variation2.phpt new file mode 100644 index 0000000000000..7621133c71b24 --- /dev/null +++ b/ext/standard/tests/file/bug72666_variation2.phpt @@ -0,0 +1,35 @@ +--TEST-- +Bug #72666 (stat cache clearing inconsistent - chgrp, chmod) +--SKIPIF-- + +--FILE-- + $ctime1); +var_dump($ctime3 > $ctime2); +?> +--CLEAN-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/ext/standard/tests/file/bug72666_variation3.phpt b/ext/standard/tests/file/bug72666_variation3.phpt new file mode 100644 index 0000000000000..a491640c4f746 --- /dev/null +++ b/ext/standard/tests/file/bug72666_variation3.phpt @@ -0,0 +1,35 @@ +--TEST-- +Bug #72666 (stat cache clearing inconsistent - plain wrapper) +--FILE-- + $atime1); +} +var_dump($mtime2 > $mtime1); +?> +--CLEAN-- + +--EXPECT-- +string(4) "test" +int(4) +bool(true) +bool(true) diff --git a/ext/standard/tests/file/bug72666_variation4.phpt b/ext/standard/tests/file/bug72666_variation4.phpt new file mode 100644 index 0000000000000..09e32dafed9cf --- /dev/null +++ b/ext/standard/tests/file/bug72666_variation4.phpt @@ -0,0 +1,26 @@ +--TEST-- +Bug #72666 (stat cache clearing inconsistent - exec) +--FILE-- + 1); + + +touch($filename, 1); +var_dump(filemtime($filename)); +shell_exec("touch $filename"); +var_dump(filemtime($filename) > 1); +?> +--CLEAN-- + +--EXPECT-- +int(1) +bool(true) +int(1) +bool(true) diff --git a/ext/standard/tests/file/readfile_variation9.phpt b/ext/standard/tests/file/readfile_variation9.phpt index a09846587a6dd..382186fed10ba 100644 --- a/ext/standard/tests/file/readfile_variation9.phpt +++ b/ext/standard/tests/file/readfile_variation9.phpt @@ -5,8 +5,8 @@ Dave Kelsey --FILE-- int(%d) } -string(7) "Hello 6" +string(%d) "Hello%s" diff --git a/ext/standard/tests/http/bug47021.phpt b/ext/standard/tests/http/bug47021.phpt index 326eceb687a52..168721f4ec1b6 100644 --- a/ext/standard/tests/http/bug47021.phpt +++ b/ext/standard/tests/http/bug47021.phpt @@ -70,23 +70,27 @@ do_test(1, true); echo "\n"; ?> ---EXPECT-- +--EXPECTF-- + Type='text/plain' Hello -Size=5 -World + +Warning: file_get_contents(http://%s:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s + Type='text/plain' Hello -Size=5 -World + +Warning: file_get_contents(http://%s:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s + Type='text/plain' Hello -Size=5 -World + +Warning: file_get_contents(http://%s:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s + Type='text/plain' Hello -Size=5 -World + +Warning: file_get_contents(http://%s:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s diff --git a/ext/standard/tests/http/bug75535.phpt b/ext/standard/tests/http/bug75535.phpt index 7b015890d2f51..94348d1a027aa 100644 --- a/ext/standard/tests/http/bug75535.phpt +++ b/ext/standard/tests/http/bug75535.phpt @@ -21,9 +21,7 @@ http_server_kill($pid); --EXPECT-- string(0) "" -array(2) { +array(1) { [0]=> string(15) "HTTP/1.0 200 Ok" - [1]=> - string(14) "Content-Length" } diff --git a/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt new file mode 100644 index 0000000000000..744cff9cc72f2 --- /dev/null +++ b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt @@ -0,0 +1,58 @@ +--TEST-- +GHSA-52jp-hrpf-2jff: HTTP stream wrapper truncate redirect location to 1024 bytes (success) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + $loc = str_repeat("y", 8000); + fwrite($conn, "HTTP/1.0 301 Ok\r\nContent-Type: text/html;\r\nLocation: $loc\r\n\r\nbody\r\n"); +CODE; + +$clientCode = <<<'CODE' + function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) { + switch($notification_code) { + case STREAM_NOTIFY_MIME_TYPE_IS: + echo "Found the mime-type: ", $message, PHP_EOL; + break; + case STREAM_NOTIFY_REDIRECTED: + echo "Redirected: "; + var_dump($message); + } + } + + $ctx = stream_context_create(); + stream_context_set_params($ctx, array("notification" => "stream_notification_callback")); + var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx))); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- +Found the mime-type: text/html; +Redirected: string(8000) "%s" + +Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: %s +string(0) "" +array(3) { + [0]=> + string(15) "HTTP/1.0 301 Ok" + [1]=> + string(24) "Content-Type: text/html;" + [2]=> + string(8010) "Location: %s" +} diff --git a/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt new file mode 100644 index 0000000000000..bc71fd4e41167 --- /dev/null +++ b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt @@ -0,0 +1,55 @@ +--TEST-- +GHSA-52jp-hrpf-2jff: HTTP stream wrapper truncate redirect location to 1024 bytes (over limit) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + $loc = str_repeat("y", 9000); + fwrite($conn, "HTTP/1.0 301 Ok\r\nContent-Type: text/html;\r\nLocation: $loc\r\n\r\nbody\r\n"); +CODE; + +$clientCode = <<<'CODE' + function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) { + switch($notification_code) { + case STREAM_NOTIFY_MIME_TYPE_IS: + echo "Found the mime-type: ", $message, PHP_EOL; + break; + case STREAM_NOTIFY_REDIRECTED: + echo "Redirected: "; + var_dump($message); + } + } + + $ctx = stream_context_create(); + stream_context_set_params($ctx, array("notification" => "stream_notification_callback")); + var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx))); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- +Found the mime-type: text/html; + +Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: HTTP Location header size is over the limit of 8182 bytes in %s +string(0) "" +array(2) { + [0]=> + string(15) "HTTP/1.0 301 Ok" + [1]=> + string(24) "Content-Type: text/html;" +} diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt new file mode 100644 index 0000000000000..c40123560ef1e --- /dev/null +++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt @@ -0,0 +1,65 @@ +--TEST-- +GHSA-hgf5-96fm-v528: Stream HTTP wrapper header check might omit basic auth header (incorrect inside pos) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + $result = fread($conn, 1024); + $encoded_result = base64_encode($result); + + fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html; charset=utf-8\r\n\r\n$encoded_result\r\n"); + +CODE; + +$clientCode = <<<'CODE' + $opts = [ + "http" => [ + "method" => "GET", + "header" => "Cookie: foo=bar\nauthorization:x\r\n" + ] + ]; + $ctx = stream_context_create($opts); + var_dump(explode("\r\n", base64_decode(file_get_contents("http://user:pwd@{{ ADDR }}", false, $ctx)))); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- +array(7) { + [0]=> + string(14) "GET / HTTP/1.1" + [1]=> + string(33) "Authorization: Basic dXNlcjpwd2Q=" + [2]=> + string(21) "Host: 127.0.0.1:%d" + [3]=> + string(17) "Connection: close" + [4]=> + string(31) "Cookie: foo=bar +authorization:x" + [5]=> + string(0) "" + [6]=> + string(0) "" +} +array(2) { + [0]=> + string(15) "HTTP/1.0 200 Ok" + [1]=> + string(38) "Content-Type: text/html; charset=utf-8" +} diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt new file mode 100644 index 0000000000000..37a47df060a1c --- /dev/null +++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt @@ -0,0 +1,62 @@ +--TEST-- +GHSA-hgf5-96fm-v528: Header parser of http stream wrapper does not handle folded headers (correct start pos) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + $result = fread($conn, 1024); + $encoded_result = base64_encode($result); + + fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html; charset=utf-8\r\n\r\n$encoded_result\r\n"); + +CODE; + +$clientCode = <<<'CODE' + $opts = [ + "http" => [ + "method" => "GET", + "header" => "Authorization: Bearer x\r\n" + ] + ]; + $ctx = stream_context_create($opts); + var_dump(explode("\r\n", base64_decode(file_get_contents("http://user:pwd@{{ ADDR }}", false, $ctx)))); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- +array(6) { + [0]=> + string(14) "GET / HTTP/1.1" + [1]=> + string(21) "Host: 127.0.0.1:%d" + [2]=> + string(17) "Connection: close" + [3]=> + string(23) "Authorization: Bearer x" + [4]=> + string(0) "" + [5]=> + string(0) "" +} +array(2) { + [0]=> + string(15) "HTTP/1.0 200 Ok" + [1]=> + string(38) "Content-Type: text/html; charset=utf-8" +} diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt new file mode 100644 index 0000000000000..6c84679ff63bd --- /dev/null +++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt @@ -0,0 +1,64 @@ +--TEST-- +GHSA-hgf5-96fm-v528: Header parser of http stream wrapper does not handle folded headers (correct middle pos) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + $result = fread($conn, 1024); + $encoded_result = base64_encode($result); + + fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html; charset=utf-8\r\n\r\n$encoded_result\r\n"); + +CODE; + +$clientCode = <<<'CODE' + $opts = [ + "http" => [ + "method" => "GET", + "header" => "Cookie: x=y\r\nAuthorization: Bearer x\r\n" + ] + ]; + $ctx = stream_context_create($opts); + var_dump(explode("\r\n", base64_decode(file_get_contents("http://user:pwd@{{ ADDR }}", false, $ctx)))); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- +array(7) { + [0]=> + string(14) "GET / HTTP/1.1" + [1]=> + string(21) "Host: 127.0.0.1:%d" + [2]=> + string(17) "Connection: close" + [3]=> + string(11) "Cookie: x=y" + [4]=> + string(23) "Authorization: Bearer x" + [5]=> + string(0) "" + [6]=> + string(0) "" +} +array(2) { + [0]=> + string(15) "HTTP/1.0 200 Ok" + [1]=> + string(38) "Content-Type: text/html; charset=utf-8" +} diff --git a/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt new file mode 100644 index 0000000000000..bb7945ce62d0e --- /dev/null +++ b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt @@ -0,0 +1,51 @@ +--TEST-- +GHSA-pcmh-g36c-qc44: Header parser of http stream wrapper does not verify header name and colon (colon) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html\r\nWrong-Header\r\nGood-Header: test\r\n\r\nbody\r\n"); +CODE; + +$clientCode = <<<'CODE' + function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) { + switch($notification_code) { + case STREAM_NOTIFY_MIME_TYPE_IS: + echo "Found the mime-type: ", $message, PHP_EOL; + break; + } + } + + $ctx = stream_context_create(); + stream_context_set_params($ctx, array("notification" => "stream_notification_callback")); + var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx)); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- +Found the mime-type: text/html + +Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s +bool(false) +array(2) { + [0]=> + string(15) "HTTP/1.0 200 Ok" + [1]=> + string(23) "Content-Type: text/html" +} diff --git a/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt new file mode 100644 index 0000000000000..1d0e4fa70a2c9 --- /dev/null +++ b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt @@ -0,0 +1,51 @@ +--TEST-- +GHSA-pcmh-g36c-qc44: Header parser of http stream wrapper does not verify header name and colon (name) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html\r\nWrong-Header : test\r\nGood-Header: test\r\n\r\nbody\r\n"); +CODE; + +$clientCode = <<<'CODE' + function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) { + switch($notification_code) { + case STREAM_NOTIFY_MIME_TYPE_IS: + echo "Found the mime-type: ", $message, PHP_EOL; + break; + } + } + + $ctx = stream_context_create(); + stream_context_set_params($ctx, array("notification" => "stream_notification_callback")); + var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx)); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- +Found the mime-type: text/html + +Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: HTTP invalid response format (space in header name)! in %s +bool(false) +array(2) { + [0]=> + string(15) "HTTP/1.0 200 Ok" + [1]=> + string(23) "Content-Type: text/html" +} diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt new file mode 100644 index 0000000000000..f935b5a02ca94 --- /dev/null +++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt @@ -0,0 +1,49 @@ +--TEST-- +GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (single) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html;\r\n charset=utf-8\r\n\r\nbody\r\n"); +CODE; + +$clientCode = <<<'CODE' + function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) { + switch($notification_code) { + case STREAM_NOTIFY_MIME_TYPE_IS: + echo "Found the mime-type: ", $message, PHP_EOL; + break; + } + } + + $ctx = stream_context_create(); + stream_context_set_params($ctx, array("notification" => "stream_notification_callback")); + var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx))); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- +Found the mime-type: text/html; charset=utf-8 +string(4) "body" +array(2) { + [0]=> + string(15) "HTTP/1.0 200 Ok" + [1]=> + string(38) "Content-Type: text/html; charset=utf-8" +} diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt new file mode 100644 index 0000000000000..078d605b6718f --- /dev/null +++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt @@ -0,0 +1,51 @@ +--TEST-- +GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (multiple) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html;\r\nCustom-Header: somevalue;\r\n param1=value1; \r\n param2=value2\r\n\r\nbody\r\n"); +CODE; + +$clientCode = <<<'CODE' + function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) { + switch($notification_code) { + case STREAM_NOTIFY_MIME_TYPE_IS: + echo "Found the mime-type: ", $message, PHP_EOL; + break; + } + } + + $ctx = stream_context_create(); + stream_context_set_params($ctx, array("notification" => "stream_notification_callback")); + var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx))); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- +Found the mime-type: text/html; +string(4) "body" +array(3) { + [0]=> + string(15) "HTTP/1.0 200 Ok" + [1]=> + string(24) "Content-Type: text/html;" + [2]=> + string(54) "Custom-Header: somevalue; param1=value1; param2=value2" +} diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt new file mode 100644 index 0000000000000..ad5ddc879cead --- /dev/null +++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt @@ -0,0 +1,49 @@ +--TEST-- +GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (empty) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html;\r\n \r\n charset=utf-8\r\n\r\nbody\r\n"); +CODE; + +$clientCode = <<<'CODE' + function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) { + switch($notification_code) { + case STREAM_NOTIFY_MIME_TYPE_IS: + echo "Found the mime-type: ", $message, PHP_EOL; + break; + } + } + + $ctx = stream_context_create(); + stream_context_set_params($ctx, array("notification" => "stream_notification_callback")); + var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx))); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- +Found the mime-type: text/html; charset=utf-8 +string(4) "body" +array(2) { + [0]=> + string(15) "HTTP/1.0 200 Ok" + [1]=> + string(38) "Content-Type: text/html; charset=utf-8" +} diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt new file mode 100644 index 0000000000000..d0396e819fbd3 --- /dev/null +++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt @@ -0,0 +1,48 @@ +--TEST-- +GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (first line) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + fwrite($conn, "HTTP/1.0 200 Ok\r\n Content-Type: text/html;\r\n \r\n charset=utf-8\r\n\r\nbody\r\n"); +CODE; + +$clientCode = <<<'CODE' + function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) { + switch($notification_code) { + case STREAM_NOTIFY_MIME_TYPE_IS: + echo "Found the mime-type: ", $message, PHP_EOL; + break; + } + } + + $ctx = stream_context_create(); + stream_context_set_params($ctx, array("notification" => "stream_notification_callback")); + var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx)); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- + +Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: HTTP invalid response format (folding header at the start)! in %s +bool(false) +array(1) { + [0]=> + string(15) "HTTP/1.0 200 Ok" +} diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt new file mode 100644 index 0000000000000..037d2002cc537 --- /dev/null +++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt @@ -0,0 +1,48 @@ +--TEST-- +GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (CR before header name) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + fwrite($conn, "HTTP/1.0 200 Ok\r\n\rIgnored: ignored\r\n\r\nbody\r\n"); +CODE; + +$clientCode = <<<'CODE' + function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) { + switch($notification_code) { + case STREAM_NOTIFY_MIME_TYPE_IS: + echo "Found the mime-type: ", $message, PHP_EOL; + break; + } + } + + $ctx = stream_context_create(); + stream_context_set_params($ctx, array("notification" => "stream_notification_callback")); + var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx)); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- + +Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: HTTP invalid header name (cannot start with CR character)! in %s +bool(false) +array(1) { + [0]=> + string(15) "HTTP/1.0 200 Ok" +} diff --git a/ext/standard/tests/http/http_response_header_05.phpt b/ext/standard/tests/http/http_response_header_05.phpt deleted file mode 100644 index c5fe60fa612b7..0000000000000 --- a/ext/standard/tests/http/http_response_header_05.phpt +++ /dev/null @@ -1,30 +0,0 @@ ---TEST-- -$http_reponse_header (whitespace-only "header") ---SKIPIF-- - ---INI-- -allow_url_fopen=1 ---FILE-- - $pid, 'uri' => $uri] = http_server($responses, $output); - -$f = file_get_contents($uri); -var_dump($f); -var_dump($http_response_header); - -http_server_kill($pid); - ---EXPECT-- -string(4) "Body" -array(2) { - [0]=> - string(15) "HTTP/1.0 200 Ok" - [1]=> - string(0) "" -} diff --git a/ext/standard/tests/network/bug20134.phpt b/ext/standard/tests/network/bug20134.phpt index b5a7234f73c4f..6692e92b31052 100644 --- a/ext/standard/tests/network/bug20134.phpt +++ b/ext/standard/tests/network/bug20134.phpt @@ -2,6 +2,8 @@ Bug #20134 (UDP reads from invalid ports) --INI-- default_socket_timeout=1 +--CONFLICTS-- +all --FILE-- data = ''; + $consumed += strlen($bucket->data); + stream_bucket_append($out, $bucket); + } + return PSFS_PASS_ON; + } +} + +stream_filter_register('testfilter','testfilter'); + +$text = "Hello There!"; + +$fp = fopen('php://memory', 'w+'); +fwrite($fp, $text); + +rewind($fp); +stream_filter_append($fp, 'testfilter', STREAM_FILTER_READ, 'testuserfilter'); + +while ($x = fgets($fp)) { + var_dump($x); +} + +fclose($fp); + +echo "Done\n"; +?> +--EXPECT-- +Done diff --git a/ext/standard/user_filters.c b/ext/standard/user_filters.c index 737237f6630cd..acef5146fa25e 100644 --- a/ext/standard/user_filters.c +++ b/ext/standard/user_filters.c @@ -398,7 +398,7 @@ static void php_stream_bucket_attach(int append, INTERNAL_FUNCTION_PARAMETERS) bucket = php_stream_bucket_make_writeable(bucket); } if (bucket->buflen != Z_STRLEN_P(pzdata)) { - bucket->buf = perealloc(bucket->buf, Z_STRLEN_P(pzdata), bucket->is_persistent); + bucket->buf = perealloc(bucket->buf, MAX(Z_STRLEN_P(pzdata), 1), bucket->is_persistent); bucket->buflen = Z_STRLEN_P(pzdata); } memcpy(bucket->buf, Z_STRVAL_P(pzdata), bucket->buflen); diff --git a/ext/xml/tests/toffset_bounds.phpt b/ext/xml/tests/toffset_bounds.phpt new file mode 100644 index 0000000000000..5a3fd22f86cd7 --- /dev/null +++ b/ext/xml/tests/toffset_bounds.phpt @@ -0,0 +1,42 @@ +--TEST-- +XML_OPTION_SKIP_TAGSTART bounds +--EXTENSIONS-- +xml +--FILE-- +"; +$parser = xml_parser_create(); +xml_parser_set_option($parser, XML_OPTION_SKIP_TAGSTART, 100); +$res = xml_parse_into_struct($parser,$sample,$vals,$index); +var_dump($vals); +?> +--EXPECT-- +array(3) { + [0]=> + array(3) { + ["tag"]=> + string(0) "" + ["type"]=> + string(4) "open" + ["level"]=> + int(1) + } + [1]=> + array(3) { + ["tag"]=> + string(0) "" + ["type"]=> + string(8) "complete" + ["level"]=> + int(2) + } + [2]=> + array(3) { + ["tag"]=> + string(0) "" + ["type"]=> + string(5) "close" + ["level"]=> + int(1) + } +} diff --git a/ext/xml/xml.c b/ext/xml/xml.c index 62507a5d1307f..c4c1ac31a4baf 100644 --- a/ext/xml/xml.c +++ b/ext/xml/xml.c @@ -657,9 +657,11 @@ void _xml_startElementHandler(void *userData, const XML_Char *name, const XML_Ch array_init(&tag); array_init(&atr); - _xml_add_to_info(parser, ZSTR_VAL(tag_name) + parser->toffset); + char *skipped_tag_name = SKIP_TAGSTART(ZSTR_VAL(tag_name)); - add_assoc_string(&tag, "tag", SKIP_TAGSTART(ZSTR_VAL(tag_name))); /* cast to avoid gcc-warning */ + _xml_add_to_info(parser, skipped_tag_name); + + add_assoc_string(&tag, "tag", skipped_tag_name); add_assoc_string(&tag, "type", "open"); add_assoc_long(&tag, "level", parser->level); @@ -741,12 +743,14 @@ void _xml_endElementHandler(void *userData, const XML_Char *name) add_assoc_string(zv, "type", "complete"); } } else { - _xml_add_to_info(parser, ZSTR_VAL(tag_name) + parser->toffset); + char *skipped_tag_name = SKIP_TAGSTART(ZSTR_VAL(tag_name)); + + _xml_add_to_info(parser, skipped_tag_name); zval *data = xml_get_separated_data(parser); if (EXPECTED(data)) { array_init(&tag); - add_assoc_string(&tag, "tag", SKIP_TAGSTART(ZSTR_VAL(tag_name))); /* cast to avoid gcc-warning */ + add_assoc_string(&tag, "tag", skipped_tag_name); add_assoc_string(&tag, "type", "close"); add_assoc_long(&tag, "level", parser->level); zend_hash_next_index_insert(Z_ARRVAL_P(data), &tag); diff --git a/ext/zend_test/tests/gh17797.phpt b/ext/zend_test/tests/gh17797.phpt new file mode 100644 index 0000000000000..271841b4389db --- /dev/null +++ b/ext/zend_test/tests/gh17797.phpt @@ -0,0 +1,32 @@ +--TEST-- +GH-17797 (zend_test_compile_string crash on invalid script path) +--EXTENSIONS-- +zend_test +--CREDITS-- +YuanchengJiang +--FILE-- +'; +try {zend_test_compile_string($source,$source,$c);} catch (Exception $e) { echo($e); } +?> +--EXPECTF-- + +Warning: Undefined variable $c in %s on line %d + +Deprecated: zend_test_compile_string(): Passing null to parameter #3 ($position) of type int is deprecated in %s on line %d + +Warning: require(sumfile.php): Failed to open stream: No such file or directory in on line %d + +Fatal error: Uncaught Error: Failed opening required 'sumfile.php' (include_path='.%s') in :%d +Stack trace: +#0 %s(%d): zend_test_compile_string(' on line %d diff --git a/ext/zend_test/tests/gh17899.phpt b/ext/zend_test/tests/gh17899.phpt new file mode 100644 index 0000000000000..184fe0b2aa39d --- /dev/null +++ b/ext/zend_test/tests/gh17899.phpt @@ -0,0 +1,35 @@ +--TEST-- +GH-17899 (zend_test_compile_string with opcache crash on invalid script path) +--EXTENSIONS-- +zend_test +--INI-- +opcache.enable_cli=1 +--CREDITS-- +YuanchengJiang +--FILE-- +'; +try {zend_test_compile_string($source,$source,$c);} catch (Exception $e) { echo($e); } +?> +--EXPECTF-- + +Warning: Undefined variable $c in %s on line %d + +Deprecated: zend_test_compile_string(): Passing null to parameter #3 ($position) of type int is deprecated in %s on line %d + +Warning: require(sumfile.php): Failed to open stream: No such file or directory in on line %d + +Fatal error: Uncaught Error: Failed opening required 'sumfile.php' (include_path='.%s') in :%d +Stack trace: +#0 %s(%d): zend_test_compile_string(' on line %d + diff --git a/ext/zlib/tests/gh17745.phpt b/ext/zlib/tests/gh17745.phpt new file mode 100644 index 0000000000000..64331269dcdab --- /dev/null +++ b/ext/zlib/tests/gh17745.phpt @@ -0,0 +1,20 @@ +--TEST-- +GH-17745 (zlib extension incorrectly handles object arguments) +--EXTENSIONS-- +zlib +--FILE-- +level = 3; +var_dump(deflate_init(ZLIB_ENCODING_RAW, $obj)); + +class Options { + public int $level = 3; +} +var_dump(deflate_init(ZLIB_ENCODING_RAW, new Options)); +?> +--EXPECT-- +object(DeflateContext)#2 (0) { +} +object(DeflateContext)#3 (0) { +} diff --git a/ext/zlib/tests/leak_invalid_encoding_with_dict.phpt b/ext/zlib/tests/leak_invalid_encoding_with_dict.phpt new file mode 100644 index 0000000000000..da2a11849c0c2 --- /dev/null +++ b/ext/zlib/tests/leak_invalid_encoding_with_dict.phpt @@ -0,0 +1,20 @@ +--TEST-- +Memory leak when passing a dictionary with invalid encoding +--EXTENSIONS-- +zlib +--FILE-- + "dict"]); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + deflate_init(123456, ["dictionary" => "dict"]); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +Encoding mode must be ZLIB_ENCODING_RAW, ZLIB_ENCODING_GZIP or ZLIB_ENCODING_DEFLATE +deflate_init(): Argument #1 ($encoding) must be one of ZLIB_ENCODING_RAW, ZLIB_ENCODING_GZIP, or ZLIB_ENCODING_DEFLATE diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index cfa8eefb47461..a97d32c6d43eb 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -790,6 +790,7 @@ static bool zlib_create_dictionary_string(HashTable *options, char **dict, size_ zval *option_buffer; if (options && (option_buffer = zend_hash_str_find(options, ZEND_STRL("dictionary"))) != NULL) { + ZVAL_DEINDIRECT(option_buffer); ZVAL_DEREF(option_buffer); switch (Z_TYPE_P(option_buffer)) { case IS_STRING: { @@ -870,6 +871,7 @@ PHP_FUNCTION(inflate_init) } if (options && (option_buffer = zend_hash_str_find(options, ZEND_STRL("window"))) != NULL) { + ZVAL_DEINDIRECT(option_buffer); window = zval_get_long(option_buffer); } if (window < 8 || window > 15) { @@ -877,10 +879,6 @@ PHP_FUNCTION(inflate_init) RETURN_THROWS(); } - if (!zlib_create_dictionary_string(options, &dict, &dictlen)) { - RETURN_THROWS(); - } - switch (encoding) { case PHP_ZLIB_ENCODING_RAW: case PHP_ZLIB_ENCODING_GZIP: @@ -891,6 +889,10 @@ PHP_FUNCTION(inflate_init) RETURN_THROWS(); } + if (!zlib_create_dictionary_string(options, &dict, &dictlen)) { + RETURN_THROWS(); + } + object_init_ex(return_value, inflate_context_ce); ctx = Z_INFLATE_CONTEXT_P(return_value); @@ -1088,6 +1090,7 @@ PHP_FUNCTION(deflate_init) } if (options && (option_buffer = zend_hash_str_find(options, ZEND_STRL("level"))) != NULL) { + ZVAL_DEINDIRECT(option_buffer); level = zval_get_long(option_buffer); } if (level < -1 || level > 9) { @@ -1096,6 +1099,7 @@ PHP_FUNCTION(deflate_init) } if (options && (option_buffer = zend_hash_str_find(options, ZEND_STRL("memory"))) != NULL) { + ZVAL_DEINDIRECT(option_buffer); memory = zval_get_long(option_buffer); } if (memory < 1 || memory > 9) { @@ -1104,6 +1108,7 @@ PHP_FUNCTION(deflate_init) } if (options && (option_buffer = zend_hash_str_find(options, ZEND_STRL("window"))) != NULL) { + ZVAL_DEINDIRECT(option_buffer); window = zval_get_long(option_buffer); } if (window < 8 || window > 15) { @@ -1112,6 +1117,7 @@ PHP_FUNCTION(deflate_init) } if (options && (option_buffer = zend_hash_str_find(options, ZEND_STRL("strategy"))) != NULL) { + ZVAL_DEINDIRECT(option_buffer); strategy = zval_get_long(option_buffer); } switch (strategy) { @@ -1126,10 +1132,6 @@ PHP_FUNCTION(deflate_init) RETURN_THROWS(); } - if (!zlib_create_dictionary_string(options, &dict, &dictlen)) { - RETURN_THROWS(); - } - switch (encoding) { case PHP_ZLIB_ENCODING_RAW: case PHP_ZLIB_ENCODING_GZIP: @@ -1140,6 +1142,10 @@ PHP_FUNCTION(deflate_init) RETURN_THROWS(); } + if (!zlib_create_dictionary_string(options, &dict, &dictlen)) { + RETURN_THROWS(); + } + object_init_ex(return_value, deflate_context_ce); ctx = Z_DEFLATE_CONTEXT_P(return_value); diff --git a/ext/zlib/zlib.stub.php b/ext/zlib/zlib.stub.php index 1083564f76505..09c44fec2e3a7 100644 --- a/ext/zlib/zlib.stub.php +++ b/ext/zlib/zlib.stub.php @@ -270,11 +270,11 @@ function gzread($stream, int $length): string|false {} */ function gzgets($stream, ?int $length = null): string|false {} -function deflate_init(int $encoding, array $options = []): DeflateContext|false {} +function deflate_init(int $encoding, array|object $options = []): DeflateContext|false {} function deflate_add(DeflateContext $context, string $data, int $flush_mode = ZLIB_SYNC_FLUSH): string|false {} -function inflate_init(int $encoding, array $options = []): InflateContext|false {} +function inflate_init(int $encoding, array|object $options = []): InflateContext|false {} function inflate_add(InflateContext $context, string $data, int $flush_mode = ZLIB_SYNC_FLUSH): string|false {} diff --git a/ext/zlib/zlib_arginfo.h b/ext/zlib/zlib_arginfo.h index 743662fd036bd..1f0e082cae85f 100644 --- a/ext/zlib/zlib_arginfo.h +++ b/ext/zlib/zlib_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 3660ad3239f93c84b6909c36ddfcc92dd0773c70 */ + * Stub hash: a86ccc292b5312e740a9d798bfcaf014513d5cce */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_ob_gzhandler, 0, 2, MAY_BE_STRING|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0) @@ -106,7 +106,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_deflate_init, 0, 1, DeflateContext, MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, encoding, IS_LONG, 0) - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 0, "[]") + ZEND_ARG_TYPE_MASK(0, options, MAY_BE_ARRAY|MAY_BE_OBJECT, "[]") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_deflate_add, 0, 2, MAY_BE_STRING|MAY_BE_FALSE) @@ -117,7 +117,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_inflate_init, 0, 1, InflateContext, MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, encoding, IS_LONG, 0) - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 0, "[]") + ZEND_ARG_TYPE_MASK(0, options, MAY_BE_ARRAY|MAY_BE_OBJECT, "[]") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_inflate_add, 0, 2, MAY_BE_STRING|MAY_BE_FALSE) diff --git a/ext/zlib/zlib_fopen_wrapper.c b/ext/zlib/zlib_fopen_wrapper.c index b414b33a8724e..31b5212a720ac 100644 --- a/ext/zlib/zlib_fopen_wrapper.c +++ b/ext/zlib/zlib_fopen_wrapper.c @@ -33,24 +33,55 @@ struct php_gz_stream_data_t { static ssize_t php_gziop_read(php_stream *stream, char *buf, size_t count) { struct php_gz_stream_data_t *self = (struct php_gz_stream_data_t *) stream->abstract; - int read; + ssize_t total_read = 0; + + /* Despite the count argument of gzread() being "unsigned int", + * the return value is "int". Error returns are values < 0, otherwise the count is returned. + * To properly distinguish error values from success value, we therefore need to cap at INT_MAX. + */ + do { + unsigned int chunk_size = MIN(count, INT_MAX); + int read = gzread(self->gz_file, buf, chunk_size); + count -= chunk_size; + + if (gzeof(self->gz_file)) { + stream->eof = 1; + } - /* XXX this needs to be looped for the case count > UINT_MAX */ - read = gzread(self->gz_file, buf, count); + if (UNEXPECTED(read < 0)) { + return read; + } - if (gzeof(self->gz_file)) { - stream->eof = 1; - } + total_read += read; + buf += read; + } while (count > 0 && !stream->eof); - return read; + return total_read; } static ssize_t php_gziop_write(php_stream *stream, const char *buf, size_t count) { struct php_gz_stream_data_t *self = (struct php_gz_stream_data_t *) stream->abstract; + ssize_t total_written = 0; + + /* Despite the count argument of gzread() being "unsigned int", + * the return value is "int". Error returns are values < 0, otherwise the count is returned. + * To properly distinguish error values from success value, we therefore need to cap at INT_MAX. + */ + do { + unsigned int chunk_size = MIN(count, INT_MAX); + int written = gzwrite(self->gz_file, buf, chunk_size); + count -= chunk_size; + + if (UNEXPECTED(written < 0)) { + return written; + } + + total_written += written; + buf += written; + } while (count > 0); - /* XXX this needs to be looped for the case count > UINT_MAX */ - return gzwrite(self->gz_file, (char *) buf, count); + return total_written; } static int php_gziop_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffs) diff --git a/main/fopen_wrappers.c b/main/fopen_wrappers.c index 62f6b4de40433..3a1f452a644cd 100644 --- a/main/fopen_wrappers.c +++ b/main/fopen_wrappers.c @@ -603,7 +603,13 @@ PHPAPI zend_string *php_resolve_path(const char *filename, size_t filename_lengt const char *exec_fname = ZSTR_VAL(exec_filename); size_t exec_fname_length = ZSTR_LEN(exec_filename); - while ((--exec_fname_length < SIZE_MAX) && !IS_SLASH(exec_fname[exec_fname_length])); + while (exec_fname_length > 0) { + --exec_fname_length; + if (IS_SLASH(exec_fname[exec_fname_length])) { + break; + } + } + if (exec_fname_length > 0 && filename_length < (MAXPATHLEN - 2) && exec_fname_length + 1 + filename_length + 1 < MAXPATHLEN) { diff --git a/main/php_version.h b/main/php_version.h index f980e0328e8c3..d6d6d36f9d871 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 17 -#define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.3.17-dev" -#define PHP_VERSION_ID 80317 +#define PHP_RELEASE_VERSION 18 +#define PHP_EXTRA_VERSION "" +#define PHP_VERSION "8.3.18" +#define PHP_VERSION_ID 80318 diff --git a/main/streams/plain_wrapper.c b/main/streams/plain_wrapper.c index 83a1b9fed49d5..1d5b7cfdac40d 100644 --- a/main/streams/plain_wrapper.c +++ b/main/streams/plain_wrapper.c @@ -349,14 +349,15 @@ PHPAPI php_stream *_php_stream_fopen_from_pipe(FILE *file, const char *mode STRE static ssize_t php_stdiop_write(php_stream *stream, const char *buf, size_t count) { php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract; + ssize_t bytes_written; assert(data != NULL); if (data->fd >= 0) { #ifdef PHP_WIN32 - ssize_t bytes_written = _write(data->fd, buf, PLAIN_WRAP_BUF_SIZE(count)); + bytes_written = _write(data->fd, buf, PLAIN_WRAP_BUF_SIZE(count)); #else - ssize_t bytes_written = write(data->fd, buf, count); + bytes_written = write(data->fd, buf, count); #endif if (bytes_written < 0) { if (PHP_IS_TRANSIENT_ERROR(errno)) { @@ -370,7 +371,6 @@ static ssize_t php_stdiop_write(php_stream *stream, const char *buf, size_t coun php_error_docref(NULL, E_NOTICE, "Write of %zu bytes failed with errno=%d %s", count, errno, strerror(errno)); } } - return bytes_written; } else { #ifdef HAVE_FLUSHIO @@ -380,8 +380,15 @@ static ssize_t php_stdiop_write(php_stream *stream, const char *buf, size_t coun data->last_op = 'w'; #endif - return (ssize_t) fwrite(buf, 1, count, data->file); + bytes_written = (ssize_t) fwrite(buf, 1, count, data->file); } + + if (EG(active)) { + /* clear stat cache as mtime and ctime got changed */ + php_clear_stat_cache(0, NULL, 0); + } + + return bytes_written; } static ssize_t php_stdiop_read(php_stream *stream, char *buf, size_t count) @@ -460,6 +467,12 @@ static ssize_t php_stdiop_read(php_stream *stream, char *buf, size_t count) stream->eof = feof(data->file); } + + if (EG(active)) { + /* clear stat cache as atime got changed */ + php_clear_stat_cache(0, NULL, 0); + } + return ret; } @@ -540,6 +553,10 @@ static int php_stdiop_flush(php_stream *stream) * something completely different. */ if (data->file) { + if (EG(active)) { + /* clear stat cache as there might be a write so mtime and ctime might have changed */ + php_clear_stat_cache(0, NULL, 0); + } return fflush(data->file); } return 0; @@ -1154,6 +1171,12 @@ PHPAPI php_stream *_php_stream_fopen(const char *filename, const char *mode, zen ret = php_stream_fopen_from_fd_rel(fd, mode, persistent_id, (open_flags & O_APPEND) == 0); } + if (EG(active)) { + /* clear stat cache as mtime and ctime might got changed - phar can use stream before + * cache is initialized so we need to check if the execution is active. */ + php_clear_stat_cache(0, NULL, 0); + } + if (ret) { if (opened_path) { *opened_path = zend_string_init(realpath, strlen(realpath), 0); diff --git a/main/streams/streams.c b/main/streams/streams.c index 934ed14211508..7a5dc2a58aaf1 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -2469,25 +2469,19 @@ PHPAPI int _php_stream_scandir(const char *dirname, zend_string **namelist[], in vector_size = 10; } else { if(vector_size*2 < vector_size) { - /* overflow */ - php_stream_closedir(stream); - efree(vector); - return -1; + goto overflow; } vector_size *= 2; } - vector = (zend_string **) safe_erealloc(vector, vector_size, sizeof(char *), 0); + vector = (zend_string **) safe_erealloc(vector, vector_size, sizeof(zend_string *), 0); } vector[nfiles] = zend_string_init(sdp.d_name, strlen(sdp.d_name), 0); - nfiles++; - if(vector_size < 10 || nfiles == 0) { - /* overflow */ - php_stream_closedir(stream); - efree(vector); - return -1; + if(vector_size < 10 || nfiles + 1 == 0) { + goto overflow; } + nfiles++; } php_stream_closedir(stream); @@ -2497,5 +2491,13 @@ PHPAPI int _php_stream_scandir(const char *dirname, zend_string **namelist[], in qsort(*namelist, nfiles, sizeof(zend_string *), (int(*)(const void *, const void *))compare); } return nfiles; + +overflow: + php_stream_closedir(stream); + for (unsigned int i = 0; i < nfiles; i++) { + zend_string_efree(vector[i]); + } + efree(vector); + return -1; } /* }}} */ diff --git a/sapi/fpm/fpm/fpm_main.c b/sapi/fpm/fpm/fpm_main.c index a4b11627789ee..2a649b9a13501 100644 --- a/sapi/fpm/fpm/fpm_main.c +++ b/sapi/fpm/fpm/fpm_main.c @@ -1153,6 +1153,7 @@ static void init_request_info(void) } if (tflag) { + char *decoded_path_info = NULL; if (orig_path_info) { char old; @@ -1174,7 +1175,6 @@ static void init_request_info(void) * As we can extract PATH_INFO from PATH_TRANSLATED * it is probably also in SCRIPT_NAME and need to be removed */ - char *decoded_path_info = NULL; size_t decoded_path_info_len = 0; if (strchr(path_info, '%')) { decoded_path_info = estrdup(path_info); @@ -1197,11 +1197,13 @@ static void init_request_info(void) env_script_name[env_script_file_info_start] = 0; SG(request_info).request_uri = FCGI_PUTENV(request, "SCRIPT_NAME", env_script_name); } - if (decoded_path_info) { - efree(decoded_path_info); - } } - env_path_info = FCGI_PUTENV(request, "PATH_INFO", path_info); + if (decoded_path_info) { + env_path_info = FCGI_PUTENV(request, "PATH_INFO", decoded_path_info); + efree(decoded_path_info); + } else { + env_path_info = FCGI_PUTENV(request, "PATH_INFO", path_info); + } } if (!orig_script_filename || strcmp(orig_script_filename, pt) != 0) { diff --git a/sapi/fpm/tests/fcgi-env-pif-apache-pp-sn-strip-encoded-plus.phpt b/sapi/fpm/tests/fcgi-env-pif-apache-pp-sn-strip-encoded-plus.phpt index 4bef11ec668f0..06a974ce40693 100644 --- a/sapi/fpm/tests/fcgi-env-pif-apache-pp-sn-strip-encoded-plus.phpt +++ b/sapi/fpm/tests/fcgi-env-pif-apache-pp-sn-strip-encoded-plus.phpt @@ -39,7 +39,7 @@ $tester scriptFilename: "proxy:fcgi://" . $tester->getAddr() . $sourceFilePath . '/1%20+2', scriptName: $scriptName . '/1 +2' ) - ->expectBody([$scriptName, $scriptName . '/1 +2', $sourceFilePath, '/1%20+2', $scriptName . '/1%20+2']); + ->expectBody([$scriptName, $scriptName . '/1 +2', $sourceFilePath, '/1 +2', $scriptName . '/1 +2']); $tester->terminate(); $tester->close(); diff --git a/sapi/fpm/tests/fcgi-env-pif-apache-pp-sn-strip-encoded.phpt b/sapi/fpm/tests/fcgi-env-pif-apache-pp-sn-strip-encoded.phpt index 22114e1abde47..29834f3e13149 100644 --- a/sapi/fpm/tests/fcgi-env-pif-apache-pp-sn-strip-encoded.phpt +++ b/sapi/fpm/tests/fcgi-env-pif-apache-pp-sn-strip-encoded.phpt @@ -39,7 +39,7 @@ $tester scriptFilename: "proxy:fcgi://" . $tester->getAddr() . $sourceFilePath . '/1%202', scriptName: $scriptName . '/1 2' ) - ->expectBody([$scriptName, $scriptName . '/1 2', $sourceFilePath, '/1%202', $scriptName . '/1%202']); + ->expectBody([$scriptName, $scriptName . '/1 2', $sourceFilePath, '/1 2', $scriptName . '/1 2']); $tester->terminate(); $tester->close(); diff --git a/sapi/phpdbg/phpdbg_lexer.l b/sapi/phpdbg/phpdbg_lexer.l index 6245262a00598..60d995526ea27 100644 --- a/sapi/phpdbg/phpdbg_lexer.l +++ b/sapi/phpdbg/phpdbg_lexer.l @@ -160,8 +160,9 @@ INPUT ("\\"[#"']|["]("\\\\"|"\\"["]|[^\n\000"])*["]|[']("\\"[']|"\\\\"|[^\ {GENERIC_ID} { phpdbg_init_param(yylval, STR_PARAM); - yylval->str = estrndup(yytext, yyleng - unescape_string(yytext)); - yylval->len = yyleng; + size_t len = yyleng - unescape_string(yytext); + yylval->str = estrndup(yytext, len); + yylval->len = len; return T_ID; } diff --git a/sapi/phpdbg/phpdbg_prompt.c b/sapi/phpdbg/phpdbg_prompt.c index 5276d62ee295e..e43bf647c7611 100644 --- a/sapi/phpdbg/phpdbg_prompt.c +++ b/sapi/phpdbg/phpdbg_prompt.c @@ -189,6 +189,9 @@ static inline int phpdbg_call_register(phpdbg_param_t *stack) /* {{{ */ zval_ptr_dtor_str(&fci.function_name); efree(lc_name); + if (fci.named_params) { + zend_array_destroy(fci.named_params); + } return SUCCESS; } diff --git a/sapi/phpdbg/tests/register_function_leak.phpt b/sapi/phpdbg/tests/register_function_leak.phpt new file mode 100644 index 0000000000000..b5416ea95bcc8 --- /dev/null +++ b/sapi/phpdbg/tests/register_function_leak.phpt @@ -0,0 +1,24 @@ +--TEST-- +registering a function and calling it leaks arguments memory +--FILE-- + +--PHPDBG-- +register var_dump +var_dump "a" "b" +register flush +flush +r +q +--EXPECTF-- +[Successful compilation of %s] +prompt> [Registered var_dump] +prompt> string(1) "a" +string(1) "b" + +prompt> [Registered flush] +prompt> +prompt> Done +[Script ended normally] +prompt> diff --git a/win32/build/phpize.js.in b/win32/build/phpize.js.in index 49871481e8907..c5f57737c97d0 100644 --- a/win32/build/phpize.js.in +++ b/win32/build/phpize.js.in @@ -91,6 +91,7 @@ function find_config_w32(dirname) deps = get_module_dep(contents); + n = ""; item = new Module_Item(n, c, dir_line, deps, contents); MODULES.Add(n, item); }