From 725f136f9a86bd37591683899c852f77f7fbfc7c Mon Sep 17 00:00:00 2001 From: Patrick Allaert Date: Tue, 25 Apr 2023 16:18:30 +0200 Subject: [PATCH 01/38] PHP-8.1 is now for PHP 8.1.20-dev --- NEWS | 5 ++++- Zend/zend.h | 2 +- configure.ac | 2 +- main/php_version.h | 6 +++--- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/NEWS b/NEWS index cb6c70b760845..49a182e388476 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.1.19 +?? ??? ????, PHP 8.1.20 + + +11 May 2023, PHP 8.1.19 - Core: . Fix inconsistent float negation in constant expressions. (ilutov) diff --git a/Zend/zend.h b/Zend/zend.h index 863d06499c9c8..5c3c1de49d5ce 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.1.19-dev" +#define ZEND_VERSION "4.1.20-dev" #define ZEND_ENGINE_3 diff --git a/configure.ac b/configure.ac index 235a0bb304d97..166d313f133bc 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.1.19-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.1.20-dev],[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/main/php_version.h b/main/php_version.h index d42ff869a27b1..8b01d87fbacc1 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 1 -#define PHP_RELEASE_VERSION 19 +#define PHP_RELEASE_VERSION 20 #define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.1.19-dev" -#define PHP_VERSION_ID 80119 +#define PHP_VERSION "8.1.20-dev" +#define PHP_VERSION_ID 80120 From 3a76f795f8543aca37df6bb8b63a860d9701e62d Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 26 Apr 2023 11:55:23 +0200 Subject: [PATCH 02/38] Fix incorrect match default branch optimization Fixes GH-11134 Closes GH-11135 --- NEWS | 2 ++ Zend/Optimizer/dfa_pass.c | 29 +++++++++++++++++++---------- Zend/tests/match/gh11134.phpt | 28 ++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 Zend/tests/match/gh11134.phpt diff --git a/NEWS b/NEWS index 49a182e388476..d6da8fdab79f2 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,8 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.1.20 +- Opcache: + . Fixed bug GH-11134 (Incorrect match default branch optimization). (ilutov) 11 May 2023, PHP 8.1.19 diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c index 754dec7ee60cf..0087e957ff395 100644 --- a/Zend/Optimizer/dfa_pass.c +++ b/Zend/Optimizer/dfa_pass.c @@ -1000,32 +1000,41 @@ static int zend_dfa_optimize_jmps(zend_op_array *op_array, zend_ssa *ssa) || (opline->opcode == ZEND_SWITCH_STRING && type == IS_STRING) || (opline->opcode == ZEND_MATCH && (type == IS_LONG || type == IS_STRING)); - if (!correct_type) { + /* Switch statements have a fallback chain for loose comparison. In those + * cases the SWITCH_* instruction is a NOP. Match does strict comparison and + * thus jumps to the default branch on mismatched types, so we need to + * convert MATCH to a jmp. */ + if (!correct_type && opline->opcode != ZEND_MATCH) { removed_ops++; MAKE_NOP(opline); opline->extended_value = 0; take_successor_ex(ssa, block_num, block, block->successors[block->successors_count - 1]); goto optimize_nop; - } else { + } + + uint32_t target; + if (correct_type) { HashTable *jmptable = Z_ARRVAL_P(CT_CONSTANT_EX(op_array, opline->op2.constant)); zval *jmp_zv = type == IS_LONG ? zend_hash_index_find(jmptable, Z_LVAL_P(zv)) : zend_hash_find(jmptable, Z_STR_P(zv)); - uint32_t target; if (jmp_zv) { target = ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(jmp_zv)); } else { target = ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value); } - opline->opcode = ZEND_JMP; - opline->extended_value = 0; - SET_UNUSED(opline->op1); - ZEND_SET_OP_JMP_ADDR(opline, opline->op1, op_array->opcodes + target); - SET_UNUSED(opline->op2); - take_successor_ex(ssa, block_num, block, ssa->cfg.map[target]); - goto optimize_jmp; + } else { + ZEND_ASSERT(opline->opcode == ZEND_MATCH); + target = ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value); } + opline->opcode = ZEND_JMP; + opline->extended_value = 0; + SET_UNUSED(opline->op1); + ZEND_SET_OP_JMP_ADDR(opline, opline->op1, op_array->opcodes + target); + SET_UNUSED(opline->op2); + take_successor_ex(ssa, block_num, block, ssa->cfg.map[target]); + goto optimize_jmp; } break; case ZEND_NOP: diff --git a/Zend/tests/match/gh11134.phpt b/Zend/tests/match/gh11134.phpt new file mode 100644 index 0000000000000..94e3223d2a07a --- /dev/null +++ b/Zend/tests/match/gh11134.phpt @@ -0,0 +1,28 @@ +--TEST-- +GH-11134: Incorrect match optimization +--FILE-- + 'foo', + 'bar' => 'bar', + default => 'baz', + }; +} + +function testSwitch() { + switch ($unset ?? null) { + case 'foo': return 'foo'; + case 'bar': return 'bar'; + default: return 'baz'; + } +} + +var_dump(testMatch()); +var_dump(testSwitch()); + +?> +--EXPECT-- +string(3) "baz" +string(3) "baz" From f0149c5c0b2b0af25d03c7b02e3f6eed3354376f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Tue, 25 Apr 2023 23:20:39 +0200 Subject: [PATCH 03/38] Fix ZPP of pg_lo_export() Closes GH-11132 --- NEWS | 3 +++ ext/pgsql/pgsql.c | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index d6da8fdab79f2..4318d871d9dd5 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,9 @@ PHP NEWS - Opcache: . Fixed bug GH-11134 (Incorrect match default branch optimization). (ilutov) +- PGSQL: + . Fixed parameter parsing of pg_lo_export(). (kocsismate) + 11 May 2023, PHP 8.1.19 - Core: diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index 13120e2b588b4..cbed6b7db18e2 100644 --- a/ext/pgsql/pgsql.c +++ b/ext/pgsql/pgsql.c @@ -2760,7 +2760,7 @@ PHP_FUNCTION(pg_lo_export) /* allow string to handle large OID value correctly */ if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), - "rlP", &pgsql_link, pgsql_link_ce, &oid_long, &file_out) == SUCCESS) { + "OlP", &pgsql_link, pgsql_link_ce, &oid_long, &file_out) == SUCCESS) { if (oid_long <= (zend_long)InvalidOid) { zend_value_error("Invalid OID value passed"); RETURN_THROWS(); From 8bf2d587d7de46350468ab1d63d350fcd6808511 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Fri, 28 Apr 2023 18:04:47 +0200 Subject: [PATCH 04/38] Propagate STREAM_DISABLE_OPEN_BASEDIR src flag to php_stream_stat_path_ex Otherwise we can get open_basedir warnings from the stat call while still performing the actual copy. Fixes GH-11138 Closes GH-11156 --- NEWS | 4 ++++ Zend/tests/gh11138.phpt | 28 ++++++++++++++++++++++++++++ ext/standard/file.c | 3 ++- 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/gh11138.phpt diff --git a/NEWS b/NEWS index 4318d871d9dd5..bac82772401d4 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,10 @@ PHP NEWS - PGSQL: . Fixed parameter parsing of pg_lo_export(). (kocsismate) +- Standard: + . Fixed bug GH-11138 (move_uploaded_file() emits open_basedir warning for + source file). (ilutov) + 11 May 2023, PHP 8.1.19 - Core: diff --git a/Zend/tests/gh11138.phpt b/Zend/tests/gh11138.phpt new file mode 100644 index 0000000000000..fcd5cd11cfb8e --- /dev/null +++ b/Zend/tests/gh11138.phpt @@ -0,0 +1,28 @@ +--TEST-- +move_uploaded_file() emits open_basedir warning for source file +--POST_RAW-- +Content-type: multipart/form-data, boundary=AaB03x + +--AaB03x +content-disposition: form-data; name="file"; filename="file.txt" +Content-Type: text/plain + +foo +--AaB03x-- +--FILE-- + +--CLEAN-- + +--EXPECT-- +bool(true) +foo diff --git a/ext/standard/file.c b/ext/standard/file.c index 548bcc7a37ca3..d51a584ed9c7a 100644 --- a/ext/standard/file.c +++ b/ext/standard/file.c @@ -1669,8 +1669,9 @@ PHPAPI int php_copy_file_ctx(const char *src, const char *dest, int src_flg, php php_stream *srcstream = NULL, *deststream = NULL; int ret = FAILURE; php_stream_statbuf src_s, dest_s; + int src_stat_flags = (src_flg & STREAM_DISABLE_OPEN_BASEDIR) ? PHP_STREAM_URL_STAT_IGNORE_OPEN_BASEDIR : 0; - switch (php_stream_stat_path_ex(src, 0, &src_s, ctx)) { + switch (php_stream_stat_path_ex(src, src_stat_flags, &src_s, ctx)) { case -1: /* non-statable stream */ goto safe_to_copy; From 5cbc917feeb0e82ca7de4e03ad1784c36547f23a Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Sun, 30 Apr 2023 13:52:18 +0200 Subject: [PATCH 05/38] Update libenchant in arm build --- .cirrus.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cirrus.yml b/.cirrus.yml index 270fd25551b64..bb65ac062f447 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -62,7 +62,7 @@ arm_task: libgmp-dev libicu-dev libtidy-dev - libenchant-dev + libenchant-2-dev libaspell-dev libpspell-dev libsasl2-dev From 43e267aeab8a84ceae34c3a3a93e72d39f8cfc9b Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Mon, 1 May 2023 11:28:33 +0200 Subject: [PATCH 06/38] [skip ci] Upgrade libmysql version --- .github/workflows/nightly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 63fce11ac0225..dff19b9c46a4a 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -632,7 +632,7 @@ jobs: with: # FIXME: There are new warnings # configurationParameters: --enable-werror - libmysql: mysql-8.0.30-linux-glibc2.12-x86_64.tar.xz + libmysql: mysql-8.0.33-linux-glibc2.12-x86_64.tar.xz withMysqli: ${{ matrix.branch.ref == 'PHP-8.1' }} - name: Test mysql-8.0 uses: ./.github/actions/test-libmysqlclient From dc20cd9c3a9a5c3ad595ae61205ee90a7fda38e5 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Mon, 1 May 2023 13:16:13 +0200 Subject: [PATCH 07/38] Endless recursion when using + on array in foreach This reverts commit 84b4020eb4a8ebc45cb80164d4589cbf818f47f2. Fixes GH-11171 --- NEWS | 2 -- Zend/tests/gh10085_1.phpt | 22 ---------------------- Zend/tests/gh10085_2.phpt | 25 ------------------------- Zend/tests/gh11171.phpt | 15 +++++++++++++++ Zend/zend_operators.c | 18 ++++++------------ 5 files changed, 21 insertions(+), 61 deletions(-) delete mode 100644 Zend/tests/gh10085_1.phpt delete mode 100644 Zend/tests/gh10085_2.phpt create mode 100644 Zend/tests/gh11171.phpt diff --git a/NEWS b/NEWS index bac82772401d4..b99a989402b3f 100644 --- a/NEWS +++ b/NEWS @@ -18,8 +18,6 @@ PHP NEWS . Fix inconsistent float negation in constant expressions. (ilutov) . Fixed bug GH-8841 (php-cli core dump calling a badly formed function). (nielsdos) - . Fixed bug GH-10085 (Assertion when adding two arrays with += where the first - array is contained in the second). (ilutov) . Fixed bug GH-10737 (PHP 8.1.16 segfaults on line 597 of sapi/apache2handler/sapi_apache2.c). (nielsdos, ElliotNB) . Fixed bug GH-11028 (Heap Buffer Overflow in zval_undefined_cv.). (nielsdos) diff --git a/Zend/tests/gh10085_1.phpt b/Zend/tests/gh10085_1.phpt deleted file mode 100644 index cc11c96a09d32..0000000000000 --- a/Zend/tests/gh10085_1.phpt +++ /dev/null @@ -1,22 +0,0 @@ ---TEST-- -GH-10085: Assertion in add_function_array() ---FILE-- - ---EXPECT-- -array(2) { - [0]=> - array(2) { - [0]=> - array(0) { - } - [1]=> - int(0) - } - [1]=> - int(0) -} diff --git a/Zend/tests/gh10085_2.phpt b/Zend/tests/gh10085_2.phpt deleted file mode 100644 index 7895999f2cd05..0000000000000 --- a/Zend/tests/gh10085_2.phpt +++ /dev/null @@ -1,25 +0,0 @@ ---TEST-- -GH-10085: Assertion in add_function_array() ---FILE-- - ---EXPECT-- -array(2) { - [0]=> - array(2) { - [0]=> - array(0) { - } - [1]=> - int(0) - } - [1]=> - int(0) -} diff --git a/Zend/tests/gh11171.phpt b/Zend/tests/gh11171.phpt new file mode 100644 index 0000000000000..8bda7da0b7b41 --- /dev/null +++ b/Zend/tests/gh11171.phpt @@ -0,0 +1,15 @@ +--TEST-- +GH-11171: Test +--FILE-- + +--EXPECT-- +array(1) { + [0]=> + &string(4) "test" +} diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index 387cc8baa7450..ef5c50c4336d2 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -965,22 +965,16 @@ static ZEND_COLD zend_never_inline void ZEND_FASTCALL zend_binop_error(const cha static zend_never_inline void ZEND_FASTCALL add_function_array(zval *result, zval *op1, zval *op2) /* {{{ */ { + if (result == op1 && Z_ARR_P(op1) == Z_ARR_P(op2)) { + /* $a += $a */ + return; + } if (result != op1) { ZVAL_ARR(result, zend_array_dup(Z_ARR_P(op1))); - zend_hash_merge(Z_ARRVAL_P(result), Z_ARRVAL_P(op2), zval_add_ref, 0); - } else if (Z_ARR_P(op1) == Z_ARR_P(op2)) { - /* $a += $a */ } else { - /* We have to duplicate op1 (even with refcount == 1) because it may be an element of op2 - * and therefore its reference counter may be increased by zend_hash_merge(). That leads to - * an assertion in _zend_hash_add_or_update_i() that only allows adding elements to hash - * tables with RC1. See GH-10085 and Zend/tests/gh10085*.phpt */ - zval tmp; - ZVAL_ARR(&tmp, zend_array_dup(Z_ARR_P(op1))); - zend_hash_merge(Z_ARRVAL(tmp), Z_ARRVAL_P(op2), zval_add_ref, 0); - zval_ptr_dtor(result); - ZVAL_COPY_VALUE(result, &tmp); + SEPARATE_ARRAY(result); } + zend_hash_merge(Z_ARRVAL_P(result), Z_ARRVAL_P(op2), zval_add_ref, 0); } /* }}} */ From 8fc023cbaeab4763374aaf3a005e66c6f3482b84 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Mon, 1 May 2023 15:28:59 +0200 Subject: [PATCH 08/38] [skip ci] Fix tmp file clash in ext/zip/tests/oo_cancel.phpt --- ext/zip/tests/oo_cancel.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/zip/tests/oo_cancel.phpt b/ext/zip/tests/oo_cancel.phpt index fe9f9868c75b1..fa5fe92dadaad 100644 --- a/ext/zip/tests/oo_cancel.phpt +++ b/ext/zip/tests/oo_cancel.phpt @@ -12,7 +12,7 @@ date.timezone=UTC --FILE-- Date: Tue, 2 May 2023 12:02:20 +0300 Subject: [PATCH 09/38] JIT: Fixed inaccurate range inference usage for UNDEF/NULL/FALSE Fixes oss-fuzz #58459 --- ext/opcache/jit/zend_jit_arm64.dasc | 2 +- ext/opcache/jit/zend_jit_x86.dasc | 2 +- ext/opcache/tests/jit/mod_007.phpt | 23 +++++++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 ext/opcache/tests/jit/mod_007.phpt diff --git a/ext/opcache/jit/zend_jit_arm64.dasc b/ext/opcache/jit/zend_jit_arm64.dasc index a1f60030574f6..0621d7e76c4f2 100644 --- a/ext/opcache/jit/zend_jit_arm64.dasc +++ b/ext/opcache/jit/zend_jit_arm64.dasc @@ -4834,7 +4834,7 @@ static int zend_jit_long_math_helper(dasm_State **Dst, op2_reg = Z_REG(op2_addr); } - if (!op2_range || (op2_range->min <= 0 && op2_range->max >= 0)) { + if ((op2_type & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) || !op2_range || (op2_range->min <= 0 && op2_range->max >= 0)) { | cbz Rx(op2_reg), >1 |.cold_code |1: diff --git a/ext/opcache/jit/zend_jit_x86.dasc b/ext/opcache/jit/zend_jit_x86.dasc index 44e14d1f1126d..d47e346989d65 100644 --- a/ext/opcache/jit/zend_jit_x86.dasc +++ b/ext/opcache/jit/zend_jit_x86.dasc @@ -5272,7 +5272,7 @@ static int zend_jit_long_math_helper(dasm_State **Dst, } } } else { - if (!op2_range || (op2_range->min <= 0 && op2_range->max >= 0)) { + if ((op2_type & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) || !op2_range || (op2_range->min <= 0 && op2_range->max >= 0)) { if (Z_MODE(op2_addr) == IS_MEM_ZVAL) { | cmp aword [Ra(Z_REG(op2_addr))+Z_OFFSET(op2_addr)], 0 } else if (Z_MODE(op2_addr) == IS_REG) { diff --git a/ext/opcache/tests/jit/mod_007.phpt b/ext/opcache/tests/jit/mod_007.phpt new file mode 100644 index 0000000000000..c83bd0e5a058b --- /dev/null +++ b/ext/opcache/tests/jit/mod_007.phpt @@ -0,0 +1,23 @@ +--TEST-- +JIT MOD: 007 +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit_buffer_size=1M +opcache.protect_memory=1 +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught DivisionByZeroError: Modulo by zero in %smod_007.php:4 +Stack trace: +#0 %smod_007.php(7): test(NULL) +#1 {main} + thrown in %smod_007.php on line 4 \ No newline at end of file From ed0b593c11225e5a013cd09bc3992efa2e7f5a49 Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Tue, 2 May 2023 20:32:48 +0300 Subject: [PATCH 10/38] Fixed GH-11127 (JIT fault) * Fixed GH-11127 (JIT fault) * Added test * Add new line --- ext/opcache/jit/zend_jit_arm64.dasc | 12 +++++++++- ext/opcache/jit/zend_jit_x86.dasc | 24 ++++++++++++++++++-- ext/opcache/tests/jit/init_fcall_003.inc | 6 +++++ ext/opcache/tests/jit/init_fcall_003.phpt | 27 +++++++++++++++++++++++ 4 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 ext/opcache/tests/jit/init_fcall_003.inc create mode 100644 ext/opcache/tests/jit/init_fcall_003.phpt diff --git a/ext/opcache/jit/zend_jit_arm64.dasc b/ext/opcache/jit/zend_jit_arm64.dasc index 0621d7e76c4f2..06969de67c65d 100644 --- a/ext/opcache/jit/zend_jit_arm64.dasc +++ b/ext/opcache/jit/zend_jit_arm64.dasc @@ -8836,7 +8836,17 @@ static int zend_jit_init_fcall(dasm_State **Dst, const zend_op *opline, uint32_t | // if (CACHED_PTR(opline->result.num)) | ldr REG2, EX->run_time_cache | MEM_ACCESS_64_WITH_UOFFSET ldr, REG0, REG2, opline->result.num, TMP1 - | cbz REG0, >1 + if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE + && func + && (func->common.fn_flags & ZEND_ACC_IMMUTABLE) + && opline->opcode != ZEND_INIT_FCALL) { + /* Called func may be changed because of recompilation. See ext/opcache/tests/jit/init_fcall_003.phpt */ + | LOAD_ADDR REG1, ((ptrdiff_t)func) + | cmp REG0, REG1 + | bne >1 + } else { + | cbz REG0, >1 + } |.cold_code |1: if (opline->opcode == ZEND_INIT_FCALL diff --git a/ext/opcache/jit/zend_jit_x86.dasc b/ext/opcache/jit/zend_jit_x86.dasc index d47e346989d65..3e6ecaec5b179 100644 --- a/ext/opcache/jit/zend_jit_x86.dasc +++ b/ext/opcache/jit/zend_jit_x86.dasc @@ -9453,8 +9453,28 @@ static int zend_jit_init_fcall(dasm_State **Dst, const zend_op *opline, uint32_t | // if (CACHED_PTR(opline->result.num)) | mov r2, EX->run_time_cache | mov r0, aword [r2 + opline->result.num] - | test r0, r0 - | jz >1 + if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE + && func + && (func->common.fn_flags & ZEND_ACC_IMMUTABLE) + && opline->opcode != ZEND_INIT_FCALL) { + /* Called func may be changed because of recompilation. See ext/opcache/tests/jit/init_fcall_003.phpt */ + | .if X64 + || if (!IS_SIGNED_32BIT(func)) { + | mov64 r1, ((ptrdiff_t)func) + | cmp r0, r1 + || } else { + | cmp r0, func + || } + | .else + | cmp r0, func + | .endif + | jnz >1 + |.cold_code + |1: + } else { + | test r0, r0 + | jz >1 + } |.cold_code |1: if (opline->opcode == ZEND_INIT_FCALL diff --git a/ext/opcache/tests/jit/init_fcall_003.inc b/ext/opcache/tests/jit/init_fcall_003.inc new file mode 100644 index 0000000000000..b35315778f42a --- /dev/null +++ b/ext/opcache/tests/jit/init_fcall_003.inc @@ -0,0 +1,6 @@ + diff --git a/ext/opcache/tests/jit/init_fcall_003.phpt b/ext/opcache/tests/jit/init_fcall_003.phpt new file mode 100644 index 0000000000000..f37344cbce4a9 --- /dev/null +++ b/ext/opcache/tests/jit/init_fcall_003.phpt @@ -0,0 +1,27 @@ +--TEST-- +JIT INIT_FCALL: 003 incorrect init fcall guard (fail with tracing JIT and --repeat 3) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit_buffer_size=1M +opcache.jit_max_polymorphic_calls=0 +opcache.jit=tracing +opcache.jit_hot_loop=64 +opcache.jit_hot_func=127 +opcache.jit_hot_return=8 +opcache.jit_hot_side_exit=8 +--FILE-- + +DONE +--EXPECT-- +DONE From fbf5216ca0480af791488a790dadd2a8e01e48b7 Mon Sep 17 00:00:00 2001 From: nielsdos <7771979+nielsdos@users.noreply.github.com> Date: Mon, 1 May 2023 01:54:23 +0200 Subject: [PATCH 11/38] Fix too wide OR and AND range inference There is a typo which causes the AND and OR range inference to infer a wider range than necessary. Fix this typo. There are many ranges for which the inference is too wide, I just picked one for AND and one for OR that I found through symbolic execution. In this example test, the previous range inferred for test_or was [-27..-1] instead of [-20..-1]. And the previous range inferred for test_and was [-32..-25] instead of [-28..-25]. Closes GH-11170. --- NEWS | 1 + Zend/Optimizer/zend_inference.c | 4 +- ext/opcache/tests/opt/gh11170.phpt | 145 +++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 ext/opcache/tests/opt/gh11170.phpt diff --git a/NEWS b/NEWS index b99a989402b3f..9a3421557113f 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,7 @@ PHP NEWS - Opcache: . Fixed bug GH-11134 (Incorrect match default branch optimization). (ilutov) + . Fixed too wide OR and AND range inference. (nielsdos) - PGSQL: . Fixed parameter parsing of pg_lo_export(). (kocsismate) diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index ba7c302582f64..91a142a9f863f 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -436,7 +436,7 @@ static void zend_ssa_range_or(zend_long a, zend_long b, zend_long c, zend_long d int x = ((a < 0) ? 8 : 0) | ((b < 0) ? 4 : 0) | ((c < 0) ? 2 : 0) | - ((d < 0) ? 2 : 0); + ((d < 0) ? 1 : 0); switch (x) { case 0x0: case 0x3: @@ -484,7 +484,7 @@ static void zend_ssa_range_and(zend_long a, zend_long b, zend_long c, zend_long int x = ((a < 0) ? 8 : 0) | ((b < 0) ? 4 : 0) | ((c < 0) ? 2 : 0) | - ((d < 0) ? 2 : 0); + ((d < 0) ? 1 : 0); switch (x) { case 0x0: case 0x3: diff --git a/ext/opcache/tests/opt/gh11170.phpt b/ext/opcache/tests/opt/gh11170.phpt new file mode 100644 index 0000000000000..ca00c5e852966 --- /dev/null +++ b/ext/opcache/tests/opt/gh11170.phpt @@ -0,0 +1,145 @@ +--TEST-- +GH-11170 (Too wide OR and AND range inferred) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x400000 +opcache.preload= +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=5, args=0, vars=0, tmps=2, ssa_vars=0, no_loops) + ; (after dfa pass) + ; %s + ; return [long] RANGE[1..1] +BB0: + ; start exit lines=[0-4] + ; level=0 +0000 INIT_FCALL 0 %d string("test_or") +0001 DO_UCALL +0002 INIT_FCALL 0 %d string("test_and") +0003 DO_UCALL +0004 RETURN int(1) + +test_or: + ; (lines=11, args=0, vars=2, tmps=7, ssa_vars=11, no_loops) + ; (after dfa pass) + ; %s + ; return [long] RANGE[-20..-1] + ; #0.CV0($a) NOVAL [undef] + ; #1.CV1($b) NOVAL [undef] +BB0: + ; start lines=[0-3] + ; to=(BB2, BB1) + ; level=0 + ; children=(BB1, BB2, BB3) +0000 INIT_FCALL 0 %d string("rand") +0001 #2.V2 [long] = DO_ICALL +0002 #3.T3 [long] RANGE[MIN..MAX] = MOD #2.V2 [long] int(10) +0003 JMPZ #3.T3 [long] RANGE[MIN..MAX] BB2 + +BB1: + ; follow lines=[4-6] + ; from=(BB0) + ; to=(BB3) + ; idom=BB0 + ; level=1 +0004 #4.CV0($a) [long] RANGE[-27..-27] = QM_ASSIGN int(-27) +0005 #5.CV1($b) [long] RANGE[-20..-20] = QM_ASSIGN int(-20) +0006 JMP BB3 + +BB2: + ; target lines=[7-8] + ; from=(BB0) + ; to=(BB3) + ; idom=BB0 + ; level=1 +0007 #6.CV0($a) [long] RANGE[-7..-7] = QM_ASSIGN int(-7) +0008 #7.CV1($b) [long] RANGE[-10..-10] = QM_ASSIGN int(-10) + +BB3: + ; follow target exit lines=[9-10] + ; from=(BB1, BB2) + ; idom=BB0 + ; level=1 + #8.CV0($a) [long] RANGE[-27..-7] = Phi(#4.CV0($a) [long] RANGE[-27..-27], #6.CV0($a) [long] RANGE[-7..-7]) + #9.CV1($b) [long] RANGE[-20..-10] = Phi(#5.CV1($b) [long] RANGE[-20..-20], #7.CV1($b) [long] RANGE[-10..-10]) +0009 #10.T8 [long] RANGE[-20..-1] = BW_OR #8.CV0($a) [long] RANGE[-27..-7] #9.CV1($b) [long] RANGE[-20..-10] +0010 RETURN #10.T8 [long] RANGE[-20..-1] + +test_and: + ; (lines=11, args=0, vars=2, tmps=7, ssa_vars=11, no_loops) + ; (after dfa pass) + ; %s + ; return [long] RANGE[-28..-25] + ; #0.CV0($a) NOVAL [undef] + ; #1.CV1($b) NOVAL [undef] +BB0: + ; start lines=[0-3] + ; to=(BB2, BB1) + ; level=0 + ; children=(BB1, BB2, BB3) +0000 INIT_FCALL 0 %d string("rand") +0001 #2.V2 [long] = DO_ICALL +0002 #3.T3 [long] RANGE[MIN..MAX] = MOD #2.V2 [long] int(10) +0003 JMPZ #3.T3 [long] RANGE[MIN..MAX] BB2 + +BB1: + ; follow lines=[4-6] + ; from=(BB0) + ; to=(BB3) + ; idom=BB0 + ; level=1 +0004 #4.CV0($a) [long] RANGE[-12..-12] = QM_ASSIGN int(-12) +0005 #5.CV1($b) [long] RANGE[-27..-27] = QM_ASSIGN int(-27) +0006 JMP BB3 + +BB2: + ; target lines=[7-8] + ; from=(BB0) + ; to=(BB3) + ; idom=BB0 + ; level=1 +0007 #6.CV0($a) [long] RANGE[-9..-9] = QM_ASSIGN int(-9) +0008 #7.CV1($b) [long] RANGE[-25..-25] = QM_ASSIGN int(-25) + +BB3: + ; follow target exit lines=[9-10] + ; from=(BB1, BB2) + ; idom=BB0 + ; level=1 + #8.CV0($a) [long] RANGE[-12..-9] = Phi(#4.CV0($a) [long] RANGE[-12..-12], #6.CV0($a) [long] RANGE[-9..-9]) + #9.CV1($b) [long] RANGE[-27..-25] = Phi(#5.CV1($b) [long] RANGE[-27..-27], #7.CV1($b) [long] RANGE[-25..-25]) +0009 #10.T8 [long] RANGE[-28..-25] = BW_AND #8.CV0($a) [long] RANGE[-12..-9] #9.CV1($b) [long] RANGE[-27..-25] +0010 RETURN #10.T8 [long] RANGE[-28..-25] From 11597d18d6ca59ef1d61670120e74a82a6c85179 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Mon, 20 Mar 2023 22:15:54 +0100 Subject: [PATCH 12/38] Add retry mechanism in run-tests.php We have lots of spurious failures in CI, many of them with the "all" CONFLICT. We're limiting the retrying to specific error messages. In the future we may also provide a FLAKY section to retry specific tests. Closes GH-10892 --- run-tests.php | 50 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/run-tests.php b/run-tests.php index 8bc59e2d847ec..14eb45eecd609 100755 --- a/run-tests.php +++ b/run-tests.php @@ -1886,6 +1886,10 @@ function run_test(string $php, $file, array $env): string $skipCache = new SkipCache($enableSkipCache, $cfg['keep']['skip']); } + $retriable = true; + $retried = false; +retry: + $temp_filenames = null; $org_file = $file; $orig_php = $php; @@ -1930,8 +1934,11 @@ function run_test(string $php, $file, array $env): string $tested = $test->getName(); - if ($num_repeats > 1 && $test->hasSection('FILE_EXTERNAL')) { - return skip_test($tested, $tested_file, $shortname, 'Test with FILE_EXTERNAL might not be repeatable'); + if ($test->hasSection('FILE_EXTERNAL')) { + $retriable = false; + if ($num_repeats > 1) { + return skip_test($tested, $tested_file, $shortname, 'Test with FILE_EXTERNAL might not be repeatable'); + } } if ($test->hasSection('CAPTURE_STDIO')) { @@ -1957,6 +1964,7 @@ function run_test(string $php, $file, array $env): string } $php = $php_cgi . ' -C '; $uses_cgi = true; + $retriable = false; if ($num_repeats > 1) { return skip_test($tested, $tested_file, $shortname, 'CGI does not support --repeat'); } @@ -1974,20 +1982,18 @@ function run_test(string $php, $file, array $env): string } else { return skip_test($tested, $tested_file, $shortname, 'phpdbg not available'); } + $retriable = false; if ($num_repeats > 1) { return skip_test($tested, $tested_file, $shortname, 'phpdbg does not support --repeat'); } } - if ($num_repeats > 1) { - if ($test->hasSection('CLEAN')) { - return skip_test($tested, $tested_file, $shortname, 'Test with CLEAN might not be repeatable'); - } - if ($test->hasSection('STDIN')) { - return skip_test($tested, $tested_file, $shortname, 'Test with STDIN might not be repeatable'); - } - if ($test->hasSection('CAPTURE_STDIO')) { - return skip_test($tested, $tested_file, $shortname, 'Test with CAPTURE_STDIO might not be repeatable'); + foreach (['CLEAN', 'STDIN', 'CAPTURE_STDIO'] as $section) { + if ($test->hasSection($section)) { + $retriable = false; + if ($num_repeats > 1) { + return skip_test($tested, $tested_file, $shortname, "Test with $section might not be repeatable"); + } } } @@ -2166,8 +2172,11 @@ function run_test(string $php, $file, array $env): string $ini = preg_replace('/{MAIL:(\S+)}/', $replacement, $ini); settings2array(preg_split("/[\n\r]+/", $ini), $ini_settings); - if ($num_repeats > 1 && isset($ini_settings['opcache.opt_debug_level'])) { - return skip_test($tested, $tested_file, $shortname, 'opt_debug_level tests are not repeatable'); + if (isset($ini_settings['opcache.opt_debug_level'])) { + $retriable = false; + if ($num_repeats > 1) { + return skip_test($tested, $tested_file, $shortname, 'opt_debug_level tests are not repeatable'); + } } } @@ -2693,6 +2702,9 @@ function run_test(string $php, $file, array $env): string } elseif ($test->hasSection('XLEAK')) { $warn = true; $info = " (warn: XLEAK section but test passes)"; + } elseif ($retried) { + $warn = true; + $info = " (warn: Test passed on retry attempt)"; } else { show_result("PASS", $tested, $tested_file, '', $temp_filenames); $junit->markTestAs('PASS', $shortname, $tested); @@ -2722,6 +2734,9 @@ function run_test(string $php, $file, array $env): string } elseif ($test->hasSection('XLEAK')) { $warn = true; $info = " (warn: XLEAK section but test passes)"; + } elseif ($retried) { + $warn = true; + $info = " (warn: Test passed on retry attempt)"; } else { show_result("PASS", $tested, $tested_file, '', $temp_filenames); $junit->markTestAs('PASS', $shortname, $tested); @@ -2732,6 +2747,10 @@ function run_test(string $php, $file, array $env): string $wanted_re = null; } + if (!$passed && !$retried && $retriable && error_may_be_retried($output)) { + $retried = true; + goto retry; + } // Test failed so we need to report details. if ($failed_headers) { @@ -2856,6 +2875,11 @@ function run_test(string $php, $file, array $env): string return $restype[0] . 'ED'; } +function error_may_be_retried(string $output): bool +{ + return preg_match('((timed out)|(connection refused))i', $output) === 1; +} + /** * @return bool|int */ From 4ca8daf3eda7eca5430e9234785c1401d14667c2 Mon Sep 17 00:00:00 2001 From: nielsdos <7771979+nielsdos@users.noreply.github.com> Date: Tue, 2 May 2023 21:21:01 +0200 Subject: [PATCH 13/38] Fix GH-9068: Conditional jump or move depends on uninitialised value(s) This patch preserves the scratch registers of the SysV x86-64 ABI by storing them to the stack and restoring them later. We need to do this to prevent the registers of the caller from being corrupted. The reason these get corrupted is because the compiler is unaware of the Valgrind replacement function and thus makes assumptions about the original function regarding registers which are not true for the replacement function. For implementation I used a GCC and Clang attribute. A more general approach would be to use inline assembly but that's also less portable and quite hacky. This attributes is supported since GCC 7.x, but the target option is only supported since 11.x. For Clang the target option does not matter. Closes GH-10221. --- NEWS | 4 ++++ Zend/zend_string.c | 19 ++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 9a3421557113f..4c29303a5ad22 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,10 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.1.20 +- Core: + . Fixed bug GH-9068 (Conditional jump or move depends on uninitialised + value(s)). (nielsdos) + - Opcache: . Fixed bug GH-11134 (Incorrect match default branch optimization). (ilutov) . Fixed too wide OR and AND range inference. (nielsdos) diff --git a/Zend/zend_string.c b/Zend/zend_string.c index 8e6a16c64afb5..1da3ce5248522 100644 --- a/Zend/zend_string.c +++ b/Zend/zend_string.c @@ -372,11 +372,28 @@ ZEND_API void zend_interned_strings_switch_storage(bool request) # define I_REPLACE_SONAME_FNNAME_ZU(soname, fnname) _vgr00000ZU_ ## soname ## _ ## fnname #endif -ZEND_API bool ZEND_FASTCALL I_REPLACE_SONAME_FNNAME_ZU(NONE,zend_string_equal_val)(zend_string *s1, zend_string *s2) +/* See GH-9068 */ +#if defined(__GNUC__) && (__GNUC__ >= 11 || defined(__clang__)) && __has_attribute(no_caller_saved_registers) +# define NO_CALLER_SAVED_REGISTERS __attribute__((no_caller_saved_registers)) +# ifndef __clang__ +# pragma GCC push_options +# pragma GCC target ("general-regs-only") +# define POP_OPTIONS +# endif +#else +# define NO_CALLER_SAVED_REGISTERS +#endif + +ZEND_API bool ZEND_FASTCALL NO_CALLER_SAVED_REGISTERS I_REPLACE_SONAME_FNNAME_ZU(NONE,zend_string_equal_val)(zend_string *s1, zend_string *s2) { return !memcmp(ZSTR_VAL(s1), ZSTR_VAL(s2), ZSTR_LEN(s1)); } +#ifdef POP_OPTIONS +# pragma GCC pop_options +# undef POP_OPTIONS +#endif + #if defined(__GNUC__) && defined(__i386__) ZEND_API bool ZEND_FASTCALL zend_string_equal_val(zend_string *s1, zend_string *s2) { From d75c1d00a98faedbc36031ab42780e63192d9e2b Mon Sep 17 00:00:00 2001 From: nielsdos <7771979+nielsdos@users.noreply.github.com> Date: Tue, 2 May 2023 23:45:50 +0200 Subject: [PATCH 14/38] Fix GH-11175 and GH-11177: Stream socket timeout undefined behaviour A negative value like -1 may overflow and cause incorrect results in the timeout variable, which causes an immediate timeout. As this is caused by undefined behaviour the exact behaviour depends on the compiler, its version, and the platform. A large overflow is also possible, if an extremely large timeout value is passed we also set an indefinite timeout. This is because the timeout value is at least a 64-bit number and waiting for UINT64_MAX/1000000 seconds is waiting about 584K years. Closes GH-11183. --- NEWS | 5 +++++ ext/standard/streamsfuncs.c | 39 +++++++++++++++++++++++++------------ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/NEWS b/NEWS index 4c29303a5ad22..2f9d717c0d2c4 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,11 @@ PHP NEWS . Fixed bug GH-11138 (move_uploaded_file() emits open_basedir warning for source file). (ilutov) +- Streams: + . Fixed bug GH-11175 (Stream Socket Timeout). (nielsdos) + . Fixed bug GH-11177 (ASAN UndefinedBehaviorSanitizer when timeout = -1 + passed to stream_socket_accept/stream_socket_client). (nielsdos) + 11 May 2023, PHP 8.1.19 - Core: diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c index 188bcee885481..8bd345cc2c1c3 100644 --- a/ext/standard/streamsfuncs.c +++ b/ext/standard/streamsfuncs.c @@ -33,11 +33,13 @@ #ifndef PHP_WIN32 #define php_select(m, r, w, e, t) select(m, r, w, e, t) typedef unsigned long long php_timeout_ull; +#define PHP_TIMEOUT_ULL_MAX ULLONG_MAX #else #include "win32/select.h" #include "win32/sockets.h" #include "win32/console.h" typedef unsigned __int64 php_timeout_ull; +#define PHP_TIMEOUT_ULL_MAX UINT64_MAX #endif #define GET_CTX_OPT(stream, wrapper, name, val) (PHP_STREAM_CONTEXT(stream) && NULL != (val = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), wrapper, name))) @@ -134,14 +136,21 @@ PHP_FUNCTION(stream_socket_client) } /* prepare the timeout value for use */ - conv = (php_timeout_ull) (timeout * 1000000.0); + struct timeval *tv_pointer; + if (timeout < 0.0 || timeout >= (double) PHP_TIMEOUT_ULL_MAX / 1000000.0) { + tv_pointer = NULL; + } else { + conv = (php_timeout_ull) (timeout * 1000000.0); #ifdef PHP_WIN32 - tv.tv_sec = (long)(conv / 1000000); - tv.tv_usec =(long)(conv % 1000000); + tv.tv_sec = (long)(conv / 1000000); + tv.tv_usec = (long)(conv % 1000000); #else - tv.tv_sec = conv / 1000000; - tv.tv_usec = conv % 1000000; + tv.tv_sec = conv / 1000000; + tv.tv_usec = conv % 1000000; #endif + tv_pointer = &tv; + } + if (zerrno) { ZEND_TRY_ASSIGN_REF_LONG(zerrno, 0); } @@ -152,7 +161,7 @@ PHP_FUNCTION(stream_socket_client) stream = php_stream_xport_create(ZSTR_VAL(host), ZSTR_LEN(host), REPORT_ERRORS, STREAM_XPORT_CLIENT | (flags & PHP_STREAM_CLIENT_CONNECT ? STREAM_XPORT_CONNECT : 0) | (flags & PHP_STREAM_CLIENT_ASYNC_CONNECT ? STREAM_XPORT_CONNECT_ASYNC : 0), - hashkey, &tv, context, &errstr, &err); + hashkey, tv_pointer, context, &errstr, &err); if (stream == NULL) { @@ -275,19 +284,25 @@ PHP_FUNCTION(stream_socket_accept) php_stream_from_zval(stream, zstream); /* prepare the timeout value for use */ - conv = (php_timeout_ull) (timeout * 1000000.0); + struct timeval *tv_pointer; + if (timeout < 0.0 || timeout >= (double) PHP_TIMEOUT_ULL_MAX / 1000000.0) { + tv_pointer = NULL; + } else { + conv = (php_timeout_ull) (timeout * 1000000.0); #ifdef PHP_WIN32 - tv.tv_sec = (long)(conv / 1000000); - tv.tv_usec = (long)(conv % 1000000); + tv.tv_sec = (long)(conv / 1000000); + tv.tv_usec = (long)(conv % 1000000); #else - tv.tv_sec = conv / 1000000; - tv.tv_usec = conv % 1000000; + tv.tv_sec = conv / 1000000; + tv.tv_usec = conv % 1000000; #endif + tv_pointer = &tv; + } if (0 == php_stream_xport_accept(stream, &clistream, zpeername ? &peername : NULL, NULL, NULL, - &tv, &errstr + tv_pointer, &errstr ) && clistream) { if (peername) { From 81e50b4ee3fa737f57f177ad21f2b57fd8edabc7 Mon Sep 17 00:00:00 2001 From: nielsdos <7771979+nielsdos@users.noreply.github.com> Date: Tue, 2 May 2023 20:53:22 +0200 Subject: [PATCH 15/38] Fix GH-11178: Segmentation fault in spl_array_it_get_current_data (PHP 8.1.18) Dynamic property case in zend_get_property_info() can return NULL for prop info. This was not handled. Closes GH-11182. --- NEWS | 4 ++++ ext/spl/spl_array.c | 3 ++- ext/spl/tests/gh11178.phpt | 28 ++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 ext/spl/tests/gh11178.phpt diff --git a/NEWS b/NEWS index 2f9d717c0d2c4..91e22d7252365 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,10 @@ PHP NEWS - PGSQL: . Fixed parameter parsing of pg_lo_export(). (kocsismate) +- SPL: + . Fixed bug GH-11178 (Segmentation fault in spl_array_it_get_current_data + (PHP 8.1.18)). (nielsdos) + - Standard: . Fixed bug GH-11138 (move_uploaded_file() emits open_basedir warning for source file). (ilutov) diff --git a/ext/spl/spl_array.c b/ext/spl/spl_array.c index 75607e80f4365..676f68fb6b276 100644 --- a/ext/spl/spl_array.c +++ b/ext/spl/spl_array.c @@ -1037,7 +1037,8 @@ static zval *spl_array_it_get_current_data(zend_object_iterator *iter) /* {{{ */ zend_hash_get_current_key_ex(aht, &key, NULL, spl_array_get_pos_ptr(aht, object)); zend_class_entry *ce = Z_OBJCE(object->array); zend_property_info *prop_info = zend_get_property_info(ce, key, true); - if (ZEND_TYPE_IS_SET(prop_info->type)) { + ZEND_ASSERT(prop_info != ZEND_WRONG_PROPERTY_INFO); + if (EXPECTED(prop_info != NULL) && ZEND_TYPE_IS_SET(prop_info->type)) { if (prop_info->flags & ZEND_ACC_READONLY) { zend_throw_error(NULL, "Cannot acquire reference to readonly property %s::$%s", diff --git a/ext/spl/tests/gh11178.phpt b/ext/spl/tests/gh11178.phpt new file mode 100644 index 0000000000000..3732c57a59d19 --- /dev/null +++ b/ext/spl/tests/gh11178.phpt @@ -0,0 +1,28 @@ +--TEST-- +GH-11178 (Segmentation fault in spl_array_it_get_current_data (PHP 8.1.18)) +--FILE-- +{'x'} = 1; + } + + function getIterator(): Traversable { + return new ArrayIterator($this); + } +} + +$obj = new A; + +foreach ($obj as $k => &$v) { + $v = 3; +} + +var_dump($obj); +?> +--EXPECT-- +object(A)#1 (1) { + ["x"]=> + &int(3) +} From 05bd1423eee788c3fda31fbaa99f9be2f3da1df3 Mon Sep 17 00:00:00 2001 From: Bob Weinand Date: Fri, 5 May 2023 12:00:32 +0200 Subject: [PATCH 16/38] Fix GH-11189: Exceeding memory limit in zend_hash_do_resize leaves the array in an invalid state There are more places in zend_hash.c where the resize happened after some values on the HashTable struct were set. I reordered them all, but writing a test for these would rely on the particular amount of bytes allocated at given points in time. --- NEWS | 2 ++ Zend/tests/gh11189.phpt | 29 +++++++++++++++++++++++++++++ Zend/tests/gh11189_1.phpt | 29 +++++++++++++++++++++++++++++ Zend/zend_hash.c | 17 ++++++++++------- 4 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 Zend/tests/gh11189.phpt create mode 100644 Zend/tests/gh11189_1.phpt diff --git a/NEWS b/NEWS index 91e22d7252365..8938421723666 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,8 @@ PHP NEWS - Core: . Fixed bug GH-9068 (Conditional jump or move depends on uninitialised value(s)). (nielsdos) + . Fixed bug GH-11189 (Exceeding memory limit in zend_hash_do_resize leaves + the array in an invalid state). (Bob) - Opcache: . Fixed bug GH-11134 (Incorrect match default branch optimization). (ilutov) diff --git a/Zend/tests/gh11189.phpt b/Zend/tests/gh11189.phpt new file mode 100644 index 0000000000000..f1c877f20ee47 --- /dev/null +++ b/Zend/tests/gh11189.phpt @@ -0,0 +1,29 @@ +--TEST-- +GH-11189: Exceeding memory limit in zend_hash_do_resize leaves the array in an invalid state (packed array) +--SKIPIF-- + +--INI-- +memory_limit=2M +--FILE-- + 0; --$i) { + $a[] = 2; + } + fwrite(STDOUT, "Success"); +}); + +$a = []; +// trigger OOM in a resize operation +while (1) { + $a[] = 1; +} + +?> +--EXPECTF-- +Success +Fatal error: Allowed memory size of %s bytes exhausted%s(tried to allocate %s bytes) in %s on line %d diff --git a/Zend/tests/gh11189_1.phpt b/Zend/tests/gh11189_1.phpt new file mode 100644 index 0000000000000..53727908e5e2a --- /dev/null +++ b/Zend/tests/gh11189_1.phpt @@ -0,0 +1,29 @@ +--TEST-- +GH-11189: Exceeding memory limit in zend_hash_do_resize leaves the array in an invalid state (not packed array) +--SKIPIF-- + +--INI-- +memory_limit=2M +--FILE-- + 0; --$i) { + $a[] = 2; + } + fwrite(STDOUT, "Success"); +}); + +$a = ["not packed" => 1]; +// trigger OOM in a resize operation +while (1) { + $a[] = 1; +} + +?> +--EXPECTF-- +Success +Fatal error: Allowed memory size of %s bytes exhausted%s(tried to allocate %s bytes) in %s on line %d diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c index a13bb196924e6..49c0df614369b 100644 --- a/Zend/zend_hash.c +++ b/Zend/zend_hash.c @@ -309,8 +309,9 @@ ZEND_API void ZEND_FASTCALL zend_hash_packed_grow(HashTable *ht) if (ht->nTableSize >= HT_MAX_SIZE) { zend_error_noreturn(E_ERROR, "Possible integer overflow in memory allocation (%u * %zu + %zu)", ht->nTableSize * 2, sizeof(Bucket), sizeof(Bucket)); } - ht->nTableSize += ht->nTableSize; - HT_SET_DATA_ADDR(ht, perealloc2(HT_GET_DATA_ADDR(ht), HT_SIZE_EX(ht->nTableSize, HT_MIN_MASK), HT_USED_SIZE(ht), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT)); + uint32_t newTableSize = ht->nTableSize * 2; + HT_SET_DATA_ADDR(ht, perealloc2(HT_GET_DATA_ADDR(ht), HT_SIZE_EX(newTableSize, HT_MIN_MASK), HT_USED_SIZE(ht), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT)); + ht->nTableSize = newTableSize; } ZEND_API void ZEND_FASTCALL zend_hash_real_init(HashTable *ht, bool packed) @@ -346,8 +347,9 @@ ZEND_API void ZEND_FASTCALL zend_hash_packed_to_hash(HashTable *ht) ZEND_ASSERT(HT_SIZE_TO_MASK(nSize)); HT_ASSERT_RC1(ht); - HT_FLAGS(ht) &= ~HASH_FLAG_PACKED; + // Alloc before assign to avoid inconsistencies on OOM new_data = pemalloc(HT_SIZE_EX(nSize, HT_SIZE_TO_MASK(nSize)), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT); + HT_FLAGS(ht) &= ~HASH_FLAG_PACKED; ht->nTableMask = HT_SIZE_TO_MASK(ht->nTableSize); HT_SET_DATA_ADDR(ht, new_data); memcpy(ht->arData, old_buckets, sizeof(Bucket) * ht->nNumUsed); @@ -387,8 +389,9 @@ ZEND_API void ZEND_FASTCALL zend_hash_extend(HashTable *ht, uint32_t nSize, bool if (packed) { ZEND_ASSERT(HT_FLAGS(ht) & HASH_FLAG_PACKED); if (nSize > ht->nTableSize) { - ht->nTableSize = zend_hash_check_size(nSize); - HT_SET_DATA_ADDR(ht, perealloc2(HT_GET_DATA_ADDR(ht), HT_SIZE_EX(ht->nTableSize, HT_MIN_MASK), HT_USED_SIZE(ht), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT)); + uint32_t newTableSize = zend_hash_check_size(nSize); + HT_SET_DATA_ADDR(ht, perealloc2(HT_GET_DATA_ADDR(ht), HT_SIZE_EX(newTableSize, HT_MIN_MASK), HT_USED_SIZE(ht), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT)); + ht->nTableSize = newTableSize; } } else { ZEND_ASSERT(!(HT_FLAGS(ht) & HASH_FLAG_PACKED)); @@ -396,8 +399,8 @@ ZEND_API void ZEND_FASTCALL zend_hash_extend(HashTable *ht, uint32_t nSize, bool void *new_data, *old_data = HT_GET_DATA_ADDR(ht); Bucket *old_buckets = ht->arData; nSize = zend_hash_check_size(nSize); - ht->nTableSize = nSize; new_data = pemalloc(HT_SIZE_EX(nSize, HT_SIZE_TO_MASK(nSize)), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT); + ht->nTableSize = nSize; ht->nTableMask = HT_SIZE_TO_MASK(ht->nTableSize); HT_SET_DATA_ADDR(ht, new_data); memcpy(ht->arData, old_buckets, sizeof(Bucket) * ht->nNumUsed); @@ -1217,8 +1220,8 @@ static void ZEND_FASTCALL zend_hash_do_resize(HashTable *ht) ZEND_ASSERT(HT_SIZE_TO_MASK(nSize)); - ht->nTableSize = nSize; new_data = pemalloc(HT_SIZE_EX(nSize, HT_SIZE_TO_MASK(nSize)), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT); + ht->nTableSize = nSize; ht->nTableMask = HT_SIZE_TO_MASK(ht->nTableSize); HT_SET_DATA_ADDR(ht, new_data); memcpy(ht->arData, old_buckets, sizeof(Bucket) * ht->nNumUsed); From b33fbbfe3d470aceddea37c1cb84d91bacc1f81f Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Fri, 3 Feb 2023 00:00:42 +0100 Subject: [PATCH 17/38] Fix GH-10031: [Stream] STREAM_NOTIFY_PROGRESS over HTTP emitted irregularly for last chunk of data It's possible that the server already sent in more data than just the headers. Since the stream only accepts progress increments after the headers are processed, the already read data is never added to the process. We account for this by adjusting the progress counter by the difference of already read header data and the body. For the test: Co-authored-by: aetonsi <18366087+aetonsi@users.noreply.github.com> Closes GH-10492. --- NEWS | 2 + ext/standard/http_fopen_wrapper.c | 7 ++++ ext/standard/tests/streams/gh10031.phpt | 52 +++++++++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 ext/standard/tests/streams/gh10031.phpt diff --git a/NEWS b/NEWS index 8938421723666..0028d6944f82c 100644 --- a/NEWS +++ b/NEWS @@ -24,6 +24,8 @@ PHP NEWS source file). (ilutov) - Streams: + . Fixed bug GH-10031 ([Stream] STREAM_NOTIFY_PROGRESS over HTTP emitted + irregularly for last chunk of data). (nielsdos) . Fixed bug GH-11175 (Stream Socket Timeout). (nielsdos) . Fixed bug GH-11177 (ASAN UndefinedBehaviorSanitizer when timeout = -1 passed to stream_socket_accept/stream_socket_client). (nielsdos) diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index 5964efd2f9a1c..fa0dcb5e6890a 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -955,6 +955,13 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, if (transfer_encoding) { php_stream_filter_append(&stream->readfilters, transfer_encoding); } + + /* It's possible that the server already sent in more data than just the headers. + * We account for this by adjusting the progress counter by the difference of + * already read header data and the body. */ + if (stream->writepos > stream->readpos) { + php_stream_notify_progress_increment(context, stream->writepos - stream->readpos, 0); + } } return stream; diff --git a/ext/standard/tests/streams/gh10031.phpt b/ext/standard/tests/streams/gh10031.phpt new file mode 100644 index 0000000000000..aa3576dab51ad --- /dev/null +++ b/ext/standard/tests/streams/gh10031.phpt @@ -0,0 +1,52 @@ +--TEST-- +GH-10031 ([Stream] STREAM_NOTIFY_PROGRESS over HTTP emitted irregularly for last chunk of data) +--SKIPIF-- + +--INI-- +allow_url_fopen=1 +--CONFLICTS-- +server +--FILE-- + ['ignore_errors' => true,]]); +$lastBytesTransferred = 0; +stream_context_set_params($context, ['notification' => function ($code, $s, $m, $mc, $bytes_transferred, $bytes_max) +use (&$lastBytesTransferred) { + if ($code === STREAM_NOTIFY_FILE_SIZE_IS) echo "expected filesize=$bytes_max".PHP_EOL; + $lastBytesTransferred = $bytes_transferred; + @ob_flush(); +}]); + +$get = file_get_contents("http://".PHP_CLI_SERVER_ADDRESS, false, $context); + +echo "got filesize=" . strlen($get) . PHP_EOL; +var_dump($lastBytesTransferred); + +?> +--EXPECT-- +expected filesize=1000 +got filesize=1000 +int(1000) From 7c0dfc5cf58d3c445b935fa14ea8f5f13568c419 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 29 Apr 2023 21:07:50 +0200 Subject: [PATCH 18/38] Fix GH-11160: Few tests failed building with new libxml 2.11.0 It's possible to categorise the failures into 2 categories: - Changed error message. In this case we either duplicate the test and modify the error message. Or if the change in error message is small, we use the EXPECTF matchers to make the test compatible with both old and new versions of libxml2. - Missing warnings. This is caused by a change in libxml2 where the parser started using SAX APIs internally [1]. In this case the error_type passed to php_libxml_internal_error_handler() changed from PHP_LIBXML_ERROR to PHP_LIBXML_CTX_WARNING because it internally started to use the SAX handlers instead of the generic handlers. However, for the SAX handlers the current input stack is empty, so nothing is actually printed. I fixed this by falling back to a regular warning without a filename & line number reference, which mimicks the old behaviour. Furthermore, this change now also shows an additional warning in a test which was previously hidden. [1] https://gitlab.gnome.org/GNOME/libxml2/-/commit/9a82b94a94bd310db426edd453b0f38c6c8f69f5 Closes GH-11162. --- NEWS | 4 + .../DOMDocument_loadXML_error2_gte2_11.phpt | 34 +++++++ ...> DOMDocument_loadXML_error2_pre2_11.phpt} | 4 + .../DOMDocument_load_error2_gte2_11.phpt | 34 +++++++ ...t => DOMDocument_load_error2_pre2_11.phpt} | 4 + ext/libxml/libxml.c | 2 + ext/libxml/tests/bug61367-read_2.phpt | 2 +- .../tests/libxml_disable_entity_loader_2.phpt | 2 +- ...set_external_entity_loader_variation2.phpt | 2 + ext/xml/tests/bug26614_libxml_gte2_11.phpt | 95 +++++++++++++++++++ ...bxml.phpt => bug26614_libxml_pre2_11.phpt} | 1 + 11 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 ext/dom/tests/DOMDocument_loadXML_error2_gte2_11.phpt rename ext/dom/tests/{DOMDocument_loadXML_error2.phpt => DOMDocument_loadXML_error2_pre2_11.phpt} (90%) create mode 100644 ext/dom/tests/DOMDocument_load_error2_gte2_11.phpt rename ext/dom/tests/{DOMDocument_load_error2.phpt => DOMDocument_load_error2_pre2_11.phpt} (90%) create mode 100644 ext/xml/tests/bug26614_libxml_gte2_11.phpt rename ext/xml/tests/{bug26614_libxml.phpt => bug26614_libxml_pre2_11.phpt} (96%) diff --git a/NEWS b/NEWS index 0028d6944f82c..b61cfd4cdcbbc 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,10 @@ PHP NEWS . Fixed bug GH-11189 (Exceeding memory limit in zend_hash_do_resize leaves the array in an invalid state). (Bob) +- LibXML: + . Fixed bug GH-11160 (Few tests failed building with new libxml 2.11.0). + (nielsdos) + - Opcache: . Fixed bug GH-11134 (Incorrect match default branch optimization). (ilutov) . Fixed too wide OR and AND range inference. (nielsdos) diff --git a/ext/dom/tests/DOMDocument_loadXML_error2_gte2_11.phpt b/ext/dom/tests/DOMDocument_loadXML_error2_gte2_11.phpt new file mode 100644 index 0000000000000..ff5ceb3fbed53 --- /dev/null +++ b/ext/dom/tests/DOMDocument_loadXML_error2_gte2_11.phpt @@ -0,0 +1,34 @@ +--TEST-- +Test DOMDocument::loadXML() detects not-well formed XML +--SKIPIF-- += 2.11'); +?> +--DESCRIPTION-- +This test verifies the method detects attributes values not closed between " or ' +Environment variables used in the test: +- XML_FILE: the xml file to load +- LOAD_OPTIONS: the second parameter to pass to the method +- EXPECTED_RESULT: the expected result +--CREDITS-- +Antonio Diaz Ruiz +--INI-- +assert.bail=true +--EXTENSIONS-- +dom +--ENV-- +XML_FILE=/not_well_formed2.xml +LOAD_OPTIONS=0 +EXPECTED_RESULT=0 +--FILE_EXTERNAL-- +domdocumentloadxml_test_method.inc +--EXPECTF-- +Warning: DOMDocument::loadXML(): AttValue: " or ' expected in Entity, line: 4 in %s on line %d + +Warning: DOMDocument::loadXML(): internal error: xmlParseStartTag: problem parsing attributes in Entity, line: 4 in %s on line %d + +Warning: DOMDocument::loadXML(): Couldn't find end of Start Tag book line 4 in Entity, line: 4 in %s on line %d + +Warning: DOMDocument::loadXML(): Opening and ending tag mismatch: books line 3 and book in Entity, line: 7 in %s on line %d + +Warning: DOMDocument::loadXML(): Extra content at the end of the document in Entity, line: 8 in %s on line %d diff --git a/ext/dom/tests/DOMDocument_loadXML_error2.phpt b/ext/dom/tests/DOMDocument_loadXML_error2_pre2_11.phpt similarity index 90% rename from ext/dom/tests/DOMDocument_loadXML_error2.phpt rename to ext/dom/tests/DOMDocument_loadXML_error2_pre2_11.phpt index a24d5215da48b..c826386f7a4e1 100644 --- a/ext/dom/tests/DOMDocument_loadXML_error2.phpt +++ b/ext/dom/tests/DOMDocument_loadXML_error2_pre2_11.phpt @@ -1,5 +1,9 @@ --TEST-- Test DOMDocument::loadXML() detects not-well formed XML +--SKIPIF-- += 21100) die('skip libxml2 test variant for version < 2.11'); +?> --DESCRIPTION-- This test verifies the method detects attributes values not closed between " or ' Environment variables used in the test: diff --git a/ext/dom/tests/DOMDocument_load_error2_gte2_11.phpt b/ext/dom/tests/DOMDocument_load_error2_gte2_11.phpt new file mode 100644 index 0000000000000..32b6bf161142e --- /dev/null +++ b/ext/dom/tests/DOMDocument_load_error2_gte2_11.phpt @@ -0,0 +1,34 @@ +--TEST-- +Test DOMDocument::load() detects not-well formed +--SKIPIF-- += 2.11'); +?> +--DESCRIPTION-- +This test verifies the method detects attributes values not closed between " or ' +Environment variables used in the test: +- XML_FILE: the xml file to load +- LOAD_OPTIONS: the second parameter to pass to the method +- EXPECTED_RESULT: the expected result +--CREDITS-- +Antonio Diaz Ruiz +--INI-- +assert.bail=true +--EXTENSIONS-- +dom +--ENV-- +XML_FILE=/not_well_formed2.xml +LOAD_OPTIONS=0 +EXPECTED_RESULT=0 +--FILE_EXTERNAL-- +domdocumentload_test_method.inc +--EXPECTF-- +Warning: DOMDocument::load(): AttValue: " or ' expected in %s on line %d + +Warning: DOMDocument::load(): internal error: xmlParseStartTag: problem parsing attributes in %s on line %d + +Warning: DOMDocument::load(): Couldn't find end of Start Tag book line 4 in %s on line %d + +Warning: DOMDocument::load(): Opening and ending tag mismatch: books line 3 and book in %s on line %d + +Warning: DOMDocument::load(): Extra content at the end of the document in %s on line %d diff --git a/ext/dom/tests/DOMDocument_load_error2.phpt b/ext/dom/tests/DOMDocument_load_error2_pre2_11.phpt similarity index 90% rename from ext/dom/tests/DOMDocument_load_error2.phpt rename to ext/dom/tests/DOMDocument_load_error2_pre2_11.phpt index cd13b3f901b27..695740be9ca92 100644 --- a/ext/dom/tests/DOMDocument_load_error2.phpt +++ b/ext/dom/tests/DOMDocument_load_error2_pre2_11.phpt @@ -1,5 +1,9 @@ --TEST-- Test DOMDocument::load() detects not-well formed XML +--SKIPIF-- += 21100) die('skip libxml2 test variant for version < 2.11'); +?> --DESCRIPTION-- This test verifies the method detects attributes values not closed between " or ' Environment variables used in the test: diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c index b4099bb7b8227..3959b362a0e40 100644 --- a/ext/libxml/libxml.c +++ b/ext/libxml/libxml.c @@ -527,6 +527,8 @@ static void php_libxml_ctx_error_level(int level, void *ctx, const char *msg) } else { php_error_docref(NULL, level, "%s in Entity, line: %d", msg, parser->input->line); } + } else { + php_error_docref(NULL, E_WARNING, "%s", msg); } } diff --git a/ext/libxml/tests/bug61367-read_2.phpt b/ext/libxml/tests/bug61367-read_2.phpt index 8adad1ce429fa..38f12949bcbb2 100644 --- a/ext/libxml/tests/bug61367-read_2.phpt +++ b/ext/libxml/tests/bug61367-read_2.phpt @@ -56,6 +56,6 @@ bool(true) int(4) bool(true) -Warning: DOMDocument::loadXML(): I/O warning : failed to load external entity "file:///%s/test_bug_61367-read/bad" in %s on line %d +Warning: DOMDocument::loadXML(): %Sfailed to load external entity "file:///%s/test_bug_61367-read/bad" in %s on line %d Warning: Attempt to read property "nodeValue" on null in %s on line %d diff --git a/ext/libxml/tests/libxml_disable_entity_loader_2.phpt b/ext/libxml/tests/libxml_disable_entity_loader_2.phpt index ad253171625f9..182fe13cfda96 100644 --- a/ext/libxml/tests/libxml_disable_entity_loader_2.phpt +++ b/ext/libxml/tests/libxml_disable_entity_loader_2.phpt @@ -39,6 +39,6 @@ bool(true) Deprecated: Function libxml_disable_entity_loader() is deprecated in %s on line %d bool(false) -Warning: DOMDocument::loadXML(): I/O warning : failed to load external entity "%s" in %s on line %d +Warning: DOMDocument::loadXML(): %Sfailed to load external entity "%s" in %s on line %d bool(true) Done diff --git a/ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt b/ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt index 9ebf2c0e9d32a..5657b727bacd2 100644 --- a/ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt +++ b/ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt @@ -39,6 +39,8 @@ echo "Done.\n"; string(10) "-//FOO/BAR" string(%d) "%sfoobar.dtd" +Warning: DOMDocument::validate(): Failed to load external entity "-//FOO/BAR" in %s on line %d + Warning: DOMDocument::validate(): Could not load the external subset "foobar.dtd" in %s on line %d bool(false) bool(true) diff --git a/ext/xml/tests/bug26614_libxml_gte2_11.phpt b/ext/xml/tests/bug26614_libxml_gte2_11.phpt new file mode 100644 index 0000000000000..9a81b67686d14 --- /dev/null +++ b/ext/xml/tests/bug26614_libxml_gte2_11.phpt @@ -0,0 +1,95 @@ +--TEST-- +Bug #26614 (CDATA sections skipped on line count) +--EXTENSIONS-- +xml +--SKIPIF-- += 2.11'); +?> +--FILE-- + + + +'; + +// Case 2: replace some characters so that we get comments instead +$xmls["Comment"] =' + + +'; + +// Case 3: replace even more characters so that only textual data is left +$xmls["Text"] =' + +-!-- ATA[ +multi +line +CDATA +block +--- +'; + +function startElement($parser, $name, $attrs) { + printf("<$name> at line %d, col %d (byte %d)\n", + xml_get_current_line_number($parser), + xml_get_current_column_number($parser), + xml_get_current_byte_index($parser)); +} + +function endElement($parser, $name) { + printf(" at line %d, col %d (byte %d)\n", + xml_get_current_line_number($parser), + xml_get_current_column_number($parser), + xml_get_current_byte_index($parser)); +} + +function characterData($parser, $data) { + // dummy +} + +foreach ($xmls as $desc => $xml) { + echo "$desc\n"; + $xml_parser = xml_parser_create(); + xml_set_element_handler($xml_parser, "startElement", "endElement"); + xml_set_character_data_handler($xml_parser, "characterData"); + if (!xml_parse($xml_parser, $xml, true)) + echo "Error: ".xml_error_string(xml_get_error_code($xml_parser))."\n"; + xml_parser_free($xml_parser); +} +?> +--EXPECTF-- +CDATA + at line 2, col %d (byte 50) + at line 9, col %d (byte 96) +Comment + at line 2, col %d (byte 50) + at line 9, col %d (byte 96) +Text + at line 2, col %d (byte 50) + at line 9, col %d (byte 96) diff --git a/ext/xml/tests/bug26614_libxml.phpt b/ext/xml/tests/bug26614_libxml_pre2_11.phpt similarity index 96% rename from ext/xml/tests/bug26614_libxml.phpt rename to ext/xml/tests/bug26614_libxml_pre2_11.phpt index 6acf2c44b2a66..c581a08e9b8fb 100644 --- a/ext/xml/tests/bug26614_libxml.phpt +++ b/ext/xml/tests/bug26614_libxml_pre2_11.phpt @@ -5,6 +5,7 @@ xml --SKIPIF-- = 21100) die('skip libxml2 test variant for version < 2.11'); ?> --FILE-- Date: Sat, 6 May 2023 18:23:24 +0200 Subject: [PATCH 19/38] Fix GH-11180: hash_file() appears to be restricted to 3 arguments Closes GH-11198. --- NEWS | 4 ++++ ext/hash/hash.c | 2 +- ext/hash/tests/hash_file_basic1.phpt | 12 ++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index b61cfd4cdcbbc..bebc691d019fe 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,10 @@ PHP NEWS . Fixed bug GH-11189 (Exceeding memory limit in zend_hash_do_resize leaves the array in an invalid state). (Bob) +- Hash: + . Fixed bug GH-11180 (hash_file() appears to be restricted to 3 arguments). + (nielsdos) + - LibXML: . Fixed bug GH-11160 (Few tests failed building with new libxml 2.11.0). (nielsdos) diff --git a/ext/hash/hash.c b/ext/hash/hash.c index aa80f7429fa58..5b33d946376b3 100644 --- a/ext/hash/hash.c +++ b/ext/hash/hash.c @@ -449,7 +449,7 @@ PHP_FUNCTION(hash_file) bool raw_output = 0; HashTable *args = NULL; - ZEND_PARSE_PARAMETERS_START(2, 3) + ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_STR(algo) Z_PARAM_STRING(data, data_len) Z_PARAM_OPTIONAL diff --git a/ext/hash/tests/hash_file_basic1.phpt b/ext/hash/tests/hash_file_basic1.phpt index 9ffe9a2f6cb01..334bad8c2726a 100644 --- a/ext/hash/tests/hash_file_basic1.phpt +++ b/ext/hash/tests/hash_file_basic1.phpt @@ -42,6 +42,12 @@ echo "sha512: " . hash_file('sha512', $file). "\n"; echo "snefru: " . hash_file('snefru', $file). "\n"; echo "tiger192,3: " . hash_file('tiger192,3', $file). "\n"; echo "whirlpool: " . hash_file('whirlpool', $file). "\n"; +echo "murmur3a: " . hash_file('murmur3a', $file). "\n"; +echo "murmur3a: " . hash_file('murmur3a', $file, false, ['seed' => 1234]). "\n"; +echo "murmur3c: " . hash_file('murmur3c', $file). "\n"; +echo "murmur3c: " . hash_file('murmur3c', $file, false, ['seed' => 1234]). "\n"; +echo "murmur3f: " . hash_file('murmur3f', $file). "\n"; +echo "murmur3f: " . hash_file('murmur3f', $file, false, ['seed' => 1234]). "\n"; echo "adler32(raw): " . bin2hex(hash_file('adler32', $file, TRUE)) . "\n"; echo "md5(raw): " . bin2hex(hash_file('md5', $file, TRUE)). "\n"; @@ -70,6 +76,12 @@ sha512: 1f42adaf938fbf136e381b164bae5f984c7f9fe60c82728bd889c14f187c7d63e81a0305 snefru: d414b2345d3e7fa1a31c044cf334bfc1fec24d89e464411998d579d24663895f tiger192,3: 7acf4ebea075fac6fc8ea0e2b4af3cfa71b9460e4c53403a whirlpool: 4248b149e000477269a4a5f1a84d97cfc3d0199b7aaf505913e6f010a6f83276029d11a9ad545374bc710eb59c7d958985023ab886ffa9ec9a23852844c764ec +murmur3a: bc6554c8 +murmur3a: 432e4379 +murmur3c: 8779de509ffc06fb27bcf5fc861504d6 +murmur3c: b43afac65c38a617323020432c170005 +murmur3f: 2b84cd546b2f18a9ab6f893194224afd +murmur3f: 6cc7716646664d6a83d68cb6563ac38e adler32(raw): ff87222e md5(raw): 704bf818448f5bbb94061332d2c889aa sha256(raw): a0f5702fa5d3670b80033d668e8732b70550392abb53841355447f8bb0f72245 From 78ec64af44a02baf337fe0550c1bb73fb936598a Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Sun, 7 May 2023 12:01:13 +0200 Subject: [PATCH 20/38] Fix use-of-uninitialized value in phar_object.c resource would stay uninitialized if the first call to zend_parse_parameters fails, but the value is still passed to phar_add_file(). It's not used there if cont_str is provided and so didn't cause any issues. Closes GH-11202 --- ext/phar/phar_object.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c index e32b530b82297..f329c3b0b17b2 100644 --- a/ext/phar/phar_object.c +++ b/ext/phar/phar_object.c @@ -3697,7 +3697,7 @@ PHP_METHOD(Phar, offsetSet) { char *fname, *cont_str = NULL; size_t fname_len, cont_len; - zval *zresource; + zval *zresource = NULL; if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "pr", &fname, &fname_len, &zresource) == FAILURE && zend_parse_parameters(ZEND_NUM_ARGS(), "ps", &fname, &fname_len, &cont_str, &cont_len) == FAILURE) { From 6ba0b0681998f24e3db9af1e96cdb7bbe186def2 Mon Sep 17 00:00:00 2001 From: nielsdos <7771979+nielsdos@users.noreply.github.com> Date: Mon, 8 May 2023 23:37:12 +0200 Subject: [PATCH 21/38] Fix GH-8426: make test fail while soap extension build If you build soap as a shared object, then these tests fail on non-Windows, or when the PHP install hasn't been make install-ed yet, but is executed from the development directory. Closes GH-11211. --- NEWS | 3 +++ ext/soap/tests/bug73037.phpt | 8 ++++++-- ext/soap/tests/custom_content_type.phpt | 8 ++++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index bebc691d019fe..050955d207f5f 100644 --- a/NEWS +++ b/NEWS @@ -23,6 +23,9 @@ PHP NEWS - PGSQL: . Fixed parameter parsing of pg_lo_export(). (kocsismate) +- Soap: + . Fixed bug GH-8426 (make test fail while soap extension build). (nielsdos) + - SPL: . Fixed bug GH-11178 (Segmentation fault in spl_array_it_get_current_data (PHP 8.1.18)). (nielsdos) diff --git a/ext/soap/tests/bug73037.phpt b/ext/soap/tests/bug73037.phpt index 4cf46eb373aa4..25fde2cb0dabe 100644 --- a/ext/soap/tests/bug73037.phpt +++ b/ext/soap/tests/bug73037.phpt @@ -59,8 +59,12 @@ function get_data($max) } $router = "bug73037_server.php"; -$args = substr(PHP_OS, 0, 3) == 'WIN' - ? ["-d", "extension_dir=" . ini_get("extension_dir"), "-d", "extension=php_soap.dll"] : []; +$args = ["-d", "extension_dir=" . ini_get("extension_dir"), "-d", "extension=" . (substr(PHP_OS, 0, 3) == "WIN" ? "php_" : "") . "soap." . PHP_SHLIB_SUFFIX]; +if (php_ini_loaded_file()) { + // Necessary such that it works from a development directory in which case extension_dir might not be the real extension dir + $args[] = "-c"; + $args[] = php_ini_loaded_file(); +} $code = <<<'PHP' $s = new SoapServer(NULL, array('uri' => 'http://here/')); $s->setObject(new stdclass()); diff --git a/ext/soap/tests/custom_content_type.phpt b/ext/soap/tests/custom_content_type.phpt index b8bc8c9870113..d32f1df783591 100644 --- a/ext/soap/tests/custom_content_type.phpt +++ b/ext/soap/tests/custom_content_type.phpt @@ -13,8 +13,12 @@ soap include __DIR__ . "/../../../sapi/cli/tests/php_cli_server.inc"; -$args = substr(PHP_OS, 0, 3) == 'WIN' - ? ["-d", "extension_dir=" . ini_get("extension_dir"), "-d", "extension=php_soap.dll"] : []; +$args = ["-d", "extension_dir=" . ini_get("extension_dir"), "-d", "extension=" . (substr(PHP_OS, 0, 3) == "WIN" ? "php_" : "") . "soap." . PHP_SHLIB_SUFFIX]; +if (php_ini_loaded_file()) { + // Necessary such that it works from a development directory in which case extension_dir might not be the real extension dir + $args[] = "-c"; + $args[] = php_ini_loaded_file(); +} $code = <<<'PHP' /* Receive */ $content = trim(file_get_contents("php://input")) . PHP_EOL; From 175ff603c3a8ae9dd3e6ccb3fc3081b06263f989 Mon Sep 17 00:00:00 2001 From: Amedeo Baragiola Date: Tue, 9 May 2023 19:09:42 +0100 Subject: [PATCH 22/38] Fix compilation error on old GCC versions In older versions of GCC (<=4.5) designated initializers would not accept member names nested inside anonymous structures. Instead, we need to use a positional member wrapped in {}. Fixes GH-11063 Closes GH-11212 --- NEWS | 1 + Zend/zend_hash.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 050955d207f5f..6a4a49b1cfb17 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,7 @@ PHP NEWS value(s)). (nielsdos) . Fixed bug GH-11189 (Exceeding memory limit in zend_hash_do_resize leaves the array in an invalid state). (Bob) + . Fixed bug GH-11063 (Compilation error on old GCC versions). (ingamedeo) - Hash: . Fixed bug GH-11180 (hash_file() appears to be restricted to 3 arguments). diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c index 49c0df614369b..58e5b40d05055 100644 --- a/Zend/zend_hash.c +++ b/Zend/zend_hash.c @@ -240,7 +240,7 @@ ZEND_API const HashTable zend_empty_array = { .gc.u.type_info = IS_ARRAY | (GC_IMMUTABLE << GC_FLAGS_SHIFT), .u.flags = HASH_FLAG_UNINITIALIZED, .nTableMask = HT_MIN_MASK, - .arData = (Bucket*)&uninitialized_bucket[2], + {.arData = (Bucket*)&uninitialized_bucket[2]}, .nNumUsed = 0, .nNumOfElements = 0, .nTableSize = HT_MIN_SIZE, From 975d28e278d0ba1936ac38774d7903941f3c95b9 Mon Sep 17 00:00:00 2001 From: Bob Weinand Date: Wed, 10 May 2023 16:44:29 +0200 Subject: [PATCH 23/38] Fix GH-11222: foreach by-ref may jump over keys during a rehash Signed-off-by: Bob Weinand --- NEWS | 2 ++ Zend/tests/gh11222.phpt | 29 +++++++++++++++++++++++++++++ Zend/zend_hash.c | 2 +- 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/gh11222.phpt diff --git a/NEWS b/NEWS index 6a4a49b1cfb17..8f51e4aec7751 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,8 @@ PHP NEWS . Fixed bug GH-11189 (Exceeding memory limit in zend_hash_do_resize leaves the array in an invalid state). (Bob) . Fixed bug GH-11063 (Compilation error on old GCC versions). (ingamedeo) + . Fixed bug GH-11222 (foreach by-ref may jump over keys during a rehash). + (Bob) - Hash: . Fixed bug GH-11180 (hash_file() appears to be restricted to 3 arguments). diff --git a/Zend/tests/gh11222.phpt b/Zend/tests/gh11222.phpt new file mode 100644 index 0000000000000..c2c2b5eb4881a --- /dev/null +++ b/Zend/tests/gh11222.phpt @@ -0,0 +1,29 @@ +--TEST-- +GH-112222: foreach by-ref may jump over keys during a rehash +--FILE-- + 0, 1 => 1, 2, 3, 4, 5, 6]; +foreach ($a as $k => &$v) { + if ($k == 1) { + // force that it'll be rehashed by adding enough holes + unset($a[4], $a[5]); + // actually make the array larger than 8 elements to trigger rehash + $a[] = 8; $a[] = 9; $a[] = 10; + + } + // observe the iteration jumping from key 1 to key 6, skipping keys 2 and 3 + echo "$k => $v\n"; +} + +?> +--EXPECTF-- +k => 0 +1 => 1 +2 => 2 +3 => 3 +6 => 6 +7 => 8 +8 => 9 +9 => 10 diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c index 58e5b40d05055..0f7ed38347087 100644 --- a/Zend/zend_hash.c +++ b/Zend/zend_hash.c @@ -1282,7 +1282,7 @@ ZEND_API void ZEND_FASTCALL zend_hash_rehash(HashTable *ht) } } } else { - uint32_t iter_pos = zend_hash_iterators_lower_pos(ht, 0); + uint32_t iter_pos = zend_hash_iterators_lower_pos(ht, i + 1); while (++i < ht->nNumUsed) { p++; From 8f66b67ccffad70fdd21e189539989e65bf484c2 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 10 May 2023 23:58:50 +0200 Subject: [PATCH 24/38] Fix compilation for PHP 8.1 Accidentally introduced in 175ff603c3a8ae9dd3e6ccb3fc3081b06263f989. arData was not part of an anonymous union. --- Zend/zend_hash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c index 0f7ed38347087..5032668e1bfab 100644 --- a/Zend/zend_hash.c +++ b/Zend/zend_hash.c @@ -240,7 +240,7 @@ ZEND_API const HashTable zend_empty_array = { .gc.u.type_info = IS_ARRAY | (GC_IMMUTABLE << GC_FLAGS_SHIFT), .u.flags = HASH_FLAG_UNINITIALIZED, .nTableMask = HT_MIN_MASK, - {.arData = (Bucket*)&uninitialized_bucket[2]}, + .arData = (Bucket*)&uninitialized_bucket[2], .nNumUsed = 0, .nNumOfElements = 0, .nTableSize = HT_MIN_SIZE, From ad747d93c32013a08860e19c51eaa6c8ab10ee20 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 11 May 2023 11:53:18 +0200 Subject: [PATCH 25/38] [skip ci] Remove NEWS entry for reverted change in PHP 8.1 --- NEWS | 1 - 1 file changed, 1 deletion(-) diff --git a/NEWS b/NEWS index 8f51e4aec7751..fcc2e3b58a6d5 100644 --- a/NEWS +++ b/NEWS @@ -7,7 +7,6 @@ PHP NEWS value(s)). (nielsdos) . Fixed bug GH-11189 (Exceeding memory limit in zend_hash_do_resize leaves the array in an invalid state). (Bob) - . Fixed bug GH-11063 (Compilation error on old GCC versions). (ingamedeo) . Fixed bug GH-11222 (foreach by-ref may jump over keys during a rehash). (Bob) From 7b768485f3388e487c0887622092ce2df74fe1f9 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Fri, 24 Mar 2023 16:01:52 +0100 Subject: [PATCH 26/38] Fix GH-10834: exif_read_data() cannot read smaller stream wrapper chunk sizes php_stream_read() may return less than the requested amount of bytes by design. This patch introduces a static function for exif which reads from the stream in a loop until all the requested bytes are read. For the test: Co-authored-by: dotpointer Closes GH-10924. --- NEWS | 4 ++ ext/exif/exif.c | 43 +++++++++++++++----- ext/exif/tests/gh10834.phpt | 79 +++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 10 deletions(-) create mode 100644 ext/exif/tests/gh10834.phpt diff --git a/NEWS b/NEWS index fcc2e3b58a6d5..a835815dd17bc 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,10 @@ PHP NEWS . Fixed bug GH-11222 (foreach by-ref may jump over keys during a rehash). (Bob) +- Exif: + . Fixed bug GH-10834 (exif_read_data() cannot read smaller stream wrapper + chunk sizes). (nielsdos) + - Hash: . Fixed bug GH-11180 (hash_file() appears to be restricted to 3 arguments). (nielsdos) diff --git a/ext/exif/exif.c b/ext/exif/exif.c index 273149ccbb845..be77433e63e49 100644 --- a/ext/exif/exif.c +++ b/ext/exif/exif.c @@ -215,6 +215,25 @@ zend_module_entry exif_module_entry = { ZEND_GET_MODULE(exif) #endif +/* php_stream_read() may return early without reading all data, depending on the chunk size + * and whether it's a URL stream or not. This helper keeps reading until the requested amount + * is read or until there is no more data available to read. */ +static ssize_t exif_read_from_stream_file_looped(php_stream *stream, char *buf, size_t count) +{ + ssize_t total_read = 0; + while (total_read < count) { + ssize_t ret = php_stream_read(stream, buf + total_read, count - total_read); + if (ret == -1) { + return -1; + } + if (ret == 0) { + break; + } + total_read += ret; + } + return total_read; +} + /* {{{ php_strnlen * get length of string if buffer if less than buffer size or buffer size */ static size_t php_strnlen(char* str, size_t maxlen) { @@ -3321,7 +3340,7 @@ static bool exif_process_IFD_TAG_impl(image_info_type *ImageInfo, char *dir_entr exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Wrong file pointer: 0x%08X != 0x%08X", fgot, displacement+offset_val); return false; } - fgot = php_stream_read(ImageInfo->infile, value_ptr, byte_count); + fgot = exif_read_from_stream_file_looped(ImageInfo->infile, value_ptr, byte_count); php_stream_seek(ImageInfo->infile, fpos, SEEK_SET); if (fgot != byte_count) { EFREE_IF(outside); @@ -3854,7 +3873,7 @@ static bool exif_scan_JPEG_header(image_info_type *ImageInfo) Data[0] = (uchar)lh; Data[1] = (uchar)ll; - got = php_stream_read(ImageInfo->infile, (char*)(Data+2), itemlen-2); /* Read the whole section. */ + got = exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(Data+2), itemlen-2); /* Read the whole section. */ if (got != itemlen-2) { exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error reading from file: got=x%04X(=%d) != itemlen-2=x%04X(=%d)", got, got, itemlen-2, itemlen-2); return false; @@ -3872,7 +3891,7 @@ static bool exif_scan_JPEG_header(image_info_type *ImageInfo) size = ImageInfo->FileSize - fpos; sn = exif_file_sections_add(ImageInfo, M_PSEUDO, size, NULL); Data = ImageInfo->file.list[sn].data; - got = php_stream_read(ImageInfo->infile, (char*)Data, size); + got = exif_read_from_stream_file_looped(ImageInfo->infile, (char*)Data, size); if (got != size) { EXIF_ERRLOG_FILEEOF(ImageInfo) return false; @@ -4049,7 +4068,9 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF: filesize(x%04X), IFD dir(x%04X + x%04X)", ImageInfo->FileSize, dir_offset, 2); #endif php_stream_seek(ImageInfo->infile, dir_offset, SEEK_SET); /* we do not know the order of sections */ - php_stream_read(ImageInfo->infile, (char*)ImageInfo->file.list[sn].data, 2); + if (UNEXPECTED(exif_read_from_stream_file_looped(ImageInfo->infile, (char*)ImageInfo->file.list[sn].data, 2) != 2)) { + return false; + } num_entries = php_ifd_get16u(ImageInfo->file.list[sn].data, ImageInfo->motorola_intel); dir_size = 2/*num dir entries*/ +12/*length of entry*/*(size_t)num_entries +4/* offset to next ifd (points to thumbnail or NULL)*/; if (ImageInfo->FileSize >= dir_size && ImageInfo->FileSize - dir_size >= dir_offset) { @@ -4059,7 +4080,9 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir if (exif_file_sections_realloc(ImageInfo, sn, dir_size)) { return false; } - php_stream_read(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+2), dir_size-2); + if (UNEXPECTED(exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+2), dir_size-2) != dir_size - 2)) { + return false; + } next_offset = php_ifd_get32u(ImageInfo->file.list[sn].data + dir_size - 4, ImageInfo->motorola_intel); #ifdef EXIF_DEBUG exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF done, next offset x%04X", next_offset); @@ -4147,7 +4170,7 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir #ifdef EXIF_DEBUG exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF: filesize(x%04X), IFD(x%04X + x%04X)", ImageInfo->FileSize, dir_offset, ifd_size); #endif - php_stream_read(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+dir_size), ifd_size-dir_size); + exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+dir_size), ifd_size-dir_size); #ifdef EXIF_DEBUG exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF, done"); #endif @@ -4198,7 +4221,7 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir if (!ImageInfo->Thumbnail.data) { ImageInfo->Thumbnail.data = safe_emalloc(ImageInfo->Thumbnail.size, 1, 0); php_stream_seek(ImageInfo->infile, ImageInfo->Thumbnail.offset, SEEK_SET); - fgot = php_stream_read(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size); + fgot = exif_read_from_stream_file_looped(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size); if (fgot != ImageInfo->Thumbnail.size) { EXIF_ERRLOG_THUMBEOF(ImageInfo) efree(ImageInfo->Thumbnail.data); @@ -4238,7 +4261,7 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir if (!ImageInfo->Thumbnail.data && ImageInfo->Thumbnail.offset && ImageInfo->Thumbnail.size && ImageInfo->read_thumbnail) { ImageInfo->Thumbnail.data = safe_emalloc(ImageInfo->Thumbnail.size, 1, 0); php_stream_seek(ImageInfo->infile, ImageInfo->Thumbnail.offset, SEEK_SET); - fgot = php_stream_read(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size); + fgot = exif_read_from_stream_file_looped(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size); if (fgot != ImageInfo->Thumbnail.size) { EXIF_ERRLOG_THUMBEOF(ImageInfo) efree(ImageInfo->Thumbnail.data); @@ -4293,7 +4316,7 @@ static bool exif_scan_FILE_header(image_info_type *ImageInfo) if (ImageInfo->FileSize >= 2) { php_stream_seek(ImageInfo->infile, 0, SEEK_SET); - if (php_stream_read(ImageInfo->infile, (char*)file_header, 2) != 2) { + if (exif_read_from_stream_file_looped(ImageInfo->infile, (char*)file_header, 2) != 2) { return false; } if ((file_header[0]==0xff) && (file_header[1]==M_SOI)) { @@ -4304,7 +4327,7 @@ static bool exif_scan_FILE_header(image_info_type *ImageInfo) exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid JPEG file"); } } else if (ImageInfo->FileSize >= 8) { - if (php_stream_read(ImageInfo->infile, (char*)(file_header+2), 6) != 6) { + if (exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(file_header+2), 6) != 6) { return false; } if (!memcmp(file_header, "II\x2A\x00", 4)) { diff --git a/ext/exif/tests/gh10834.phpt b/ext/exif/tests/gh10834.phpt new file mode 100644 index 0000000000000..3c9caebdb70b6 --- /dev/null +++ b/ext/exif/tests/gh10834.phpt @@ -0,0 +1,79 @@ +--TEST-- +GH-10834 (exif_read_data() cannot read smaller stream wrapper chunk sizes) +--EXTENSIONS-- +exif +--FILE-- +position >= strlen($this->data); + } + + function stream_open($path, $mode, $options, &$opened_path) { + $this->position = 0; + $this->data = file_get_contents(__DIR__.'/bug50845.jpg'); + return true; + } + + function stream_seek($offset, $whence) { + switch ($whence) { + case SEEK_SET: + if ($offset < strlen($this->data) && $offset >= 0) { + $this->position = $offset; + return true; + } else { + return false; + } + break; + case SEEK_CUR: + if ($offset >= 0) { + $this->position += $offset; + return true; + } else { + return false; + } + break; + case SEEK_END: + if (strlen($this->data) + $offset >= 0) { + $this->position = strlen($this->data) + $offset; + return true; + } else { + return false; + } + break; + default: + return false; + } + } + + function stream_read($count) { + $ret = substr($this->data, $this->position, $count); + $this->position += strlen($ret); + return $ret; + } + + function stream_tell() { + return $this->position; + } +} + +stream_wrapper_register('var', 'VariableStream'); + +$fp = fopen('var://myvar', 'rb'); + +stream_set_chunk_size($fp, 10); +$headers = exif_read_data($fp); +var_dump(is_array($headers)); + +fclose($fp); +?> +--EXPECT-- +bool(true) From 102953735c13943694ece1698676426c88392104 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sat, 15 Apr 2023 16:07:09 +0100 Subject: [PATCH 27/38] Fix GH-10461: Postpone FPM child freeing in event loop This is to prevent after free accessing of the child event that might happen when child is killed and the message is delivered at that same time. Also fixes GH-10889 and properly fixes GH-8517 that was not previously fixed correctly. --- NEWS | 4 ++++ sapi/fpm/fpm/fpm_children.c | 25 ++++++++++++++++++++++++- sapi/fpm/fpm/fpm_children.h | 5 +++-- sapi/fpm/fpm/fpm_process_ctl.c | 2 +- sapi/fpm/fpm/fpm_stdio.c | 10 ++++------ 5 files changed, 36 insertions(+), 10 deletions(-) diff --git a/NEWS b/NEWS index a835815dd17bc..c01d4d36dfa76 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,10 @@ PHP NEWS . Fixed bug GH-10834 (exif_read_data() cannot read smaller stream wrapper chunk sizes). (nielsdos) +- FPM: + . Fixed bug GH-10461 (PHP-FPM segfault due to after free usage of + child->ev_std(out|err)). (Jakub Zelenka) + - Hash: . Fixed bug GH-11180 (hash_file() appears to be restricted to 3 arguments). (nielsdos) diff --git a/sapi/fpm/fpm/fpm_children.c b/sapi/fpm/fpm/fpm_children.c index 2f8e3dc4d0acc..1c9780e3de3c1 100644 --- a/sapi/fpm/fpm/fpm_children.c +++ b/sapi/fpm/fpm/fpm_children.c @@ -63,10 +63,27 @@ static void fpm_child_free(struct fpm_child_s *child) /* {{{ */ } /* }}} */ +static void fpm_postponed_child_free(struct fpm_event_s *ev, short which, void *arg) +{ + struct fpm_child_s *child = (struct fpm_child_s *) arg; + + if (child->fd_stdout != -1) { + fpm_event_del(&child->ev_stdout); + close(child->fd_stdout); + } + if (child->fd_stderr != -1) { + fpm_event_del(&child->ev_stderr); + close(child->fd_stderr); + } + + fpm_child_free((struct fpm_child_s *) child); +} + static void fpm_child_close(struct fpm_child_s *child, int in_event_loop) /* {{{ */ { if (child->fd_stdout != -1) { if (in_event_loop) { + child->postponed_free = true; fpm_event_fire(&child->ev_stdout); } if (child->fd_stdout != -1) { @@ -76,6 +93,7 @@ static void fpm_child_close(struct fpm_child_s *child, int in_event_loop) /* {{{ if (child->fd_stderr != -1) { if (in_event_loop) { + child->postponed_free = true; fpm_event_fire(&child->ev_stderr); } if (child->fd_stderr != -1) { @@ -83,7 +101,12 @@ static void fpm_child_close(struct fpm_child_s *child, int in_event_loop) /* {{{ } } - fpm_child_free(child); + if (in_event_loop && child->postponed_free) { + fpm_event_set_timer(&child->ev_free, 0, &fpm_postponed_child_free, child); + fpm_event_add(&child->ev_free, 1000); + } else { + fpm_child_free(child); + } } /* }}} */ diff --git a/sapi/fpm/fpm/fpm_children.h b/sapi/fpm/fpm/fpm_children.h index 679c34ba0383e..fe06eb3ba84cd 100644 --- a/sapi/fpm/fpm/fpm_children.h +++ b/sapi/fpm/fpm/fpm_children.h @@ -23,12 +23,13 @@ struct fpm_child_s { struct fpm_child_s *prev, *next; struct timeval started; struct fpm_worker_pool_s *wp; - struct fpm_event_s ev_stdout, ev_stderr; + struct fpm_event_s ev_stdout, ev_stderr, ev_free; int shm_slot_i; int fd_stdout, fd_stderr; void (*tracer)(struct fpm_child_s *); struct timeval slow_logged; - int idle_kill; + bool idle_kill; + bool postponed_free; pid_t pid; int scoreboard_i; struct zlog_stream *log_stream; diff --git a/sapi/fpm/fpm/fpm_process_ctl.c b/sapi/fpm/fpm/fpm_process_ctl.c index 48eb0003d4918..7a55d98b046fc 100644 --- a/sapi/fpm/fpm/fpm_process_ctl.c +++ b/sapi/fpm/fpm/fpm_process_ctl.c @@ -318,7 +318,7 @@ static void fpm_pctl_kill_idle_child(struct fpm_child_s *child) /* {{{ */ if (child->idle_kill) { fpm_pctl_kill(child->pid, FPM_PCTL_KILL); } else { - child->idle_kill = 1; + child->idle_kill = true; fpm_pctl_kill(child->pid, FPM_PCTL_QUIT); } } diff --git a/sapi/fpm/fpm/fpm_stdio.c b/sapi/fpm/fpm/fpm_stdio.c index 78326924acd9b..8f71e8cbfcd08 100644 --- a/sapi/fpm/fpm/fpm_stdio.c +++ b/sapi/fpm/fpm/fpm_stdio.c @@ -181,10 +181,7 @@ static void fpm_stdio_child_said(struct fpm_event_s *ev, short which, void *arg) if (!arg) { return; } - child = fpm_child_find((intptr_t) arg); - if (!child) { - return; - } + child = (struct fpm_child_s *) arg; is_stdout = (fd == child->fd_stdout); if (is_stdout) { @@ -277,6 +274,7 @@ static void fpm_stdio_child_said(struct fpm_event_s *ev, short which, void *arg) fpm_event_del(event); + child->postponed_free = true; if (is_stdout) { close(child->fd_stdout); child->fd_stdout = -1; @@ -330,10 +328,10 @@ int fpm_stdio_parent_use_pipes(struct fpm_child_s *child) /* {{{ */ child->fd_stdout = fd_stdout[0]; child->fd_stderr = fd_stderr[0]; - fpm_event_set(&child->ev_stdout, child->fd_stdout, FPM_EV_READ, fpm_stdio_child_said, (void *) (intptr_t) child->pid); + fpm_event_set(&child->ev_stdout, child->fd_stdout, FPM_EV_READ, fpm_stdio_child_said, child); fpm_event_add(&child->ev_stdout, 0); - fpm_event_set(&child->ev_stderr, child->fd_stderr, FPM_EV_READ, fpm_stdio_child_said, (void *) (intptr_t) child->pid); + fpm_event_set(&child->ev_stderr, child->fd_stderr, FPM_EV_READ, fpm_stdio_child_said, child); fpm_event_add(&child->ev_stderr, 0); return 0; } From e8a836eb394c956658e4ec77d7322cd7956a9b53 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 10 Apr 2023 14:13:34 +0100 Subject: [PATCH 28/38] Expose JSON internal function to escape string --- ext/json/json.c | 15 +++++++++++++++ ext/json/json_encoder.c | 6 +----- ext/json/php_json.h | 2 ++ ext/json/php_json_encoder.h | 2 ++ 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/ext/json/json.c b/ext/json/json.c index c8f0ed5e93a3d..13efeb901e322 100644 --- a/ext/json/json.c +++ b/ext/json/json.c @@ -142,6 +142,21 @@ static PHP_MINFO_FUNCTION(json) } /* }}} */ +PHP_JSON_API zend_string *php_json_encode_string(const char *s, size_t len, int options) +{ + smart_str buf = {0}; + php_json_encoder encoder; + + php_json_encode_init(&encoder); + + if (php_json_escape_string(&buf, s, len, options, &encoder) == FAILURE) { + smart_str_free(&buf); + return NULL; + } + + return smart_str_extract(&buf); +} + PHP_JSON_API int php_json_encode_ex(smart_str *buf, zval *val, int options, zend_long depth) /* {{{ */ { php_json_encoder encoder; diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c index b0f703041b068..f3523ed3258b4 100644 --- a/ext/json/json_encoder.c +++ b/ext/json/json_encoder.c @@ -31,10 +31,6 @@ static const char digits[] = "0123456789abcdef"; -static int php_json_escape_string( - smart_str *buf, const char *s, size_t len, - int options, php_json_encoder *encoder); - static int php_json_determine_array_type(zval *val) /* {{{ */ { zend_array *myht = Z_ARRVAL_P(val); @@ -312,7 +308,7 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options, php_jso } /* }}} */ -static int php_json_escape_string( +int php_json_escape_string( smart_str *buf, const char *s, size_t len, int options, php_json_encoder *encoder) /* {{{ */ { diff --git a/ext/json/php_json.h b/ext/json/php_json.h index 89d04ed7f4d57..f8905b10cbb80 100644 --- a/ext/json/php_json.h +++ b/ext/json/php_json.h @@ -97,6 +97,8 @@ PHP_JSON_API ZEND_EXTERN_MODULE_GLOBALS(json) ZEND_TSRMLS_CACHE_EXTERN() #endif +PHP_JSON_API zend_string *php_json_encode_string(const char *s, size_t len, int options); + PHP_JSON_API int php_json_encode_ex(smart_str *buf, zval *val, int options, zend_long depth); PHP_JSON_API int php_json_encode(smart_str *buf, zval *val, int options); PHP_JSON_API int php_json_decode_ex(zval *return_value, const char *str, size_t str_len, zend_long options, zend_long depth); diff --git a/ext/json/php_json_encoder.h b/ext/json/php_json_encoder.h index 51d2d6b59ab49..a1ddd3c349e63 100644 --- a/ext/json/php_json_encoder.h +++ b/ext/json/php_json_encoder.h @@ -35,4 +35,6 @@ static inline void php_json_encode_init(php_json_encoder *encoder) int php_json_encode_zval(smart_str *buf, zval *val, int options, php_json_encoder *encoder); +int php_json_escape_string(smart_str *buf, const char *s, size_t len, int options, php_json_encoder *encoder); + #endif /* PHP_JSON_ENCODER_H */ From 5e64ead64ab5eaba5d62847483c847c1836171d7 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 10 Apr 2023 14:15:57 +0100 Subject: [PATCH 29/38] Fix bug #64539: FPM status - query_string not properly JSON encoded Closes GH-11050 --- NEWS | 2 + sapi/fpm/fpm/fpm_status.c | 34 ++++++++++--- .../tests/bug64539-status-json-encoding.phpt | 50 +++++++++++++++++++ sapi/fpm/tests/response.inc | 12 +++-- sapi/fpm/tests/tester.inc | 4 ++ 5 files changed, 90 insertions(+), 12 deletions(-) create mode 100644 sapi/fpm/tests/bug64539-status-json-encoding.phpt diff --git a/NEWS b/NEWS index c01d4d36dfa76..b5a60d5c12cb9 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,8 @@ PHP NEWS - FPM: . Fixed bug GH-10461 (PHP-FPM segfault due to after free usage of child->ev_std(out|err)). (Jakub Zelenka) + . Fixed bug #64539 (FPM status page: query_string not properly JSON encoded). + (Jakub Zelenka) - Hash: . Fixed bug GH-11180 (hash_file() appears to be restricted to 3 arguments). diff --git a/sapi/fpm/fpm/fpm_status.c b/sapi/fpm/fpm/fpm_status.c index 514d60d176e39..f0d869444afce 100644 --- a/sapi/fpm/fpm/fpm_status.c +++ b/sapi/fpm/fpm/fpm_status.c @@ -13,7 +13,8 @@ #include "fpm_atomic.h" #include "fpm_conf.h" #include "fpm_php.h" -#include +#include "ext/standard/html.h" +#include "ext/json/php_json.h" static char *fpm_status_uri = NULL; static char *fpm_status_ping_uri = NULL; @@ -140,7 +141,8 @@ int fpm_status_handle_request(void) /* {{{ */ struct fpm_scoreboard_proc_s *proc; char *buffer, *time_format, time_buffer[64]; time_t now_epoch; - int full, encode, has_start_time; + int full, has_start_time; + bool encode_html, encode_json; char *short_syntax, *short_post; char *full_pre, *full_syntax, *full_post, *full_separator; zend_string *_GET_str; @@ -175,7 +177,8 @@ int fpm_status_handle_request(void) /* {{{ */ full = (fpm_php_get_string_from_table(_GET_str, "full") != NULL); short_syntax = short_post = NULL; full_separator = full_pre = full_syntax = full_post = NULL; - encode = 0; + encode_html = false; + encode_json = false; has_start_time = 1; scoreboard_p = fpm_scoreboard_get(); @@ -218,7 +221,7 @@ int fpm_status_handle_request(void) /* {{{ */ if (fpm_php_get_string_from_table(_GET_str, "html")) { sapi_add_header_ex(ZEND_STRL("Content-Type: text/html"), 1, 1); time_format = "%d/%b/%Y:%H:%M:%S %z"; - encode = 1; + encode_html = true; short_syntax = "\n" @@ -287,7 +290,7 @@ int fpm_status_handle_request(void) /* {{{ */ } else if (fpm_php_get_string_from_table(_GET_str, "xml")) { sapi_add_header_ex(ZEND_STRL("Content-Type: text/xml"), 1, 1); time_format = "%s"; - encode = 1; + encode_html = true; short_syntax = "\n" @@ -336,6 +339,8 @@ int fpm_status_handle_request(void) /* {{{ */ sapi_add_header_ex(ZEND_STRL("Content-Type: application/json"), 1, 1); time_format = "%s"; + encode_json = true; + short_syntax = "{" "\"pool\":\"%s\"," @@ -549,11 +554,24 @@ int fpm_status_handle_request(void) /* {{{ */ query_string = NULL; tmp_query_string = NULL; if (proc->query_string[0] != '\0') { - if (!encode) { - query_string = proc->query_string; + if (encode_html) { + tmp_query_string = php_escape_html_entities_ex( + (const unsigned char *) proc->query_string, + strlen(proc->query_string), 1, ENT_HTML_IGNORE_ERRORS & ENT_COMPAT, + NULL, /* double_encode */ 1, /* quiet */ 0); + } else if (encode_json) { + tmp_query_string = php_json_encode_string(proc->query_string, + strlen(proc->query_string), PHP_JSON_INVALID_UTF8_IGNORE); } else { - tmp_query_string = php_escape_html_entities_ex((const unsigned char *) proc->query_string, strlen(proc->query_string), 1, ENT_HTML_IGNORE_ERRORS & ENT_COMPAT, NULL, /* double_encode */ 1, /* quiet */ 0); + query_string = proc->query_string; + } + if (tmp_query_string) { query_string = ZSTR_VAL(tmp_query_string); + /* remove quotes around the string */ + if (encode_json && ZSTR_LEN(tmp_query_string) >= 2) { + query_string[ZSTR_LEN(tmp_query_string) - 1] = '\0'; + ++query_string; + } } } diff --git a/sapi/fpm/tests/bug64539-status-json-encoding.phpt b/sapi/fpm/tests/bug64539-status-json-encoding.phpt new file mode 100644 index 0000000000000..0d735925593a4 --- /dev/null +++ b/sapi/fpm/tests/bug64539-status-json-encoding.phpt @@ -0,0 +1,50 @@ +--TEST-- +FPM: bug64539 - status json format escaping +--SKIPIF-- + +--FILE-- +start(); +$tester->expectLogStartNotices(); +$responses = $tester + ->multiRequest([ + ['query' => 'a=b"c'], + ['uri' => '/status', 'query' => 'full&json', 'delay' => 100000], + ]); +$data = json_decode($responses[1]->getBody('application/json'), true); +var_dump(explode('?', $data['processes'][0]['request uri'])[1]); +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +?> +Done +--EXPECT-- +string(5) "a=b"c" +Done +--CLEAN-- + diff --git a/sapi/fpm/tests/response.inc b/sapi/fpm/tests/response.inc index 99290e72f41d4..c1c4566d4b12d 100644 --- a/sapi/fpm/tests/response.inc +++ b/sapi/fpm/tests/response.inc @@ -192,18 +192,22 @@ class Response /** * Print raw body. + * + * @param string $contentType Expect body to have specified content type. */ - public function dumpBody() + public function dumpBody(string $contentType = 'text/html') { - var_dump($this->getBody()); + var_dump($this->getBody($contentType)); } /** * Print raw body. + * + * @param string $contentType Expect body to have specified content type. */ - public function printBody() + public function printBody(string $contentType = 'text/html') { - echo $this->getBody() . "\n"; + echo $this->getBody($contentType) . "\n"; } /** diff --git a/sapi/fpm/tests/tester.inc b/sapi/fpm/tests/tester.inc index 0b6ad9d0f831b..ddab17162a227 100644 --- a/sapi/fpm/tests/tester.inc +++ b/sapi/fpm/tests/tester.inc @@ -795,6 +795,10 @@ class Tester $requestData['uri'] ?? null ); + if (isset($requestData['delay'])) { + usleep($requestData['delay']); + } + return [ 'client' => $client, 'requestId' => $client->async_request($params, false), From 4294e8d448c38fa379ebf0daf80aef2254b665bd Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 16 Apr 2023 15:54:45 +0100 Subject: [PATCH 30/38] FPM: Fix memory leak for invalid primary script file handle Closes GH-11088 --- NEWS | 1 + sapi/fpm/fpm/fpm_main.c | 19 ++++++++----------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/NEWS b/NEWS index b5a60d5c12cb9..9c44373d21534 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,7 @@ PHP NEWS child->ev_std(out|err)). (Jakub Zelenka) . Fixed bug #64539 (FPM status page: query_string not properly JSON encoded). (Jakub Zelenka) + . Fixed memory leak for invalid primary script file handle. (Jakub Zelenka) - Hash: . Fixed bug GH-11180 (hash_file() appears to be restricted to 3 arguments). diff --git a/sapi/fpm/fpm/fpm_main.c b/sapi/fpm/fpm/fpm_main.c index 64ef27dadeb39..b91bb8d055dff 100644 --- a/sapi/fpm/fpm/fpm_main.c +++ b/sapi/fpm/fpm/fpm_main.c @@ -1924,19 +1924,16 @@ consult the installation file that came with this distribution, or visit \n\ } } zend_catch { } zend_end_try(); - /* we want to serve more requests if this is fastcgi - * so cleanup and continue, request shutdown is - * handled later */ - - goto fastcgi_request_done; - } - - fpm_request_executing(); + /* We want to serve more requests if this is fastcgi so cleanup and continue, + * request shutdown is handled later. */ + } else { + fpm_request_executing(); - /* Reset exit status from the previous execution */ - EG(exit_status) = 0; + /* Reset exit status from the previous execution */ + EG(exit_status) = 0; - php_execute_script(&file_handle); + php_execute_script(&file_handle); + } /* Without opcache, or the first time with opcache, the file handle will be placed * in the CG(open_files) list by open_file_for_scanning(). Starting from the second From e31fe111a56b9d9fd7837ba69e130ad9e1fc57a7 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 17 May 2023 17:42:05 +0200 Subject: [PATCH 31/38] [skip ci] Mark frequently failing fpm test as XFAIL Reported here: https://github.com/php/php-src/pull/11050#issuecomment-1546990346 --- sapi/fpm/tests/bug64539-status-json-encoding.phpt | 1 + 1 file changed, 1 insertion(+) diff --git a/sapi/fpm/tests/bug64539-status-json-encoding.phpt b/sapi/fpm/tests/bug64539-status-json-encoding.phpt index 0d735925593a4..7daa43ab75b13 100644 --- a/sapi/fpm/tests/bug64539-status-json-encoding.phpt +++ b/sapi/fpm/tests/bug64539-status-json-encoding.phpt @@ -1,5 +1,6 @@ --TEST-- FPM: bug64539 - status json format escaping +--XFAIL-- --SKIPIF-- From aa061cd40b1b8c935b41f43774bfcbc091e943d2 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Fri, 19 May 2023 13:18:36 +0100 Subject: [PATCH 32/38] Fix FPM status json encoded value test Closes GH-11276 --- .../tests/bug64539-status-json-encoding.phpt | 5 +-- sapi/fpm/tests/response.inc | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/sapi/fpm/tests/bug64539-status-json-encoding.phpt b/sapi/fpm/tests/bug64539-status-json-encoding.phpt index 7daa43ab75b13..f5c856c11c8a5 100644 --- a/sapi/fpm/tests/bug64539-status-json-encoding.phpt +++ b/sapi/fpm/tests/bug64539-status-json-encoding.phpt @@ -1,6 +1,5 @@ --TEST-- FPM: bug64539 - status json format escaping ---XFAIL-- --SKIPIF-- @@ -33,8 +32,7 @@ $responses = $tester ['query' => 'a=b"c'], ['uri' => '/status', 'query' => 'full&json', 'delay' => 100000], ]); -$data = json_decode($responses[1]->getBody('application/json'), true); -var_dump(explode('?', $data['processes'][0]['request uri'])[1]); +$responses[1]->expectJsonBodyPatternForStatusProcessField('request uri', '\?a=b"c$'); $tester->terminate(); $tester->expectLogTerminatingNotices(); $tester->close(); @@ -42,7 +40,6 @@ $tester->close(); ?> Done --EXPECT-- -string(5) "a=b"c" Done --CLEAN-- getBody('application/json'); + $data = json_decode($rawData, true); + if (empty($data['processes']) || !is_array($data['processes'])) { + $this->error( + "The body data is not a valid status json containing processes field '$rawData'" + ); + } + foreach ($data['processes'] as $process) { + if (preg_match('|' . $pattern . '|', $process[$fieldName]) !== false) { + return $this; + } + } + + $this->error( + "No field $fieldName matched pattern $pattern for any process in status data '$rawData'" + ); + + return $this; + } + /** * @return Response */ From 1ede3137c9abe19da1fafef66d8e4038c63516b4 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Fri, 19 May 2023 12:55:39 +0200 Subject: [PATCH 33/38] Fix GH-11274: POST/PATCH request via file_get_contents + stream_context_create switches to GET after a HTTP 308 redirect RFC 7231 states that status code 307 should keep the POST method upon redirect. RFC 7538 does the same for code 308. Although it's not mandated by the RFCs that PATCH is also kept (we can choose), it seems like keeping PATCH will be the most consistent and understandable behaviour. This patch also changes an existing test because it was testing for the wrong behaviour. Closes GH-11275. --- NEWS | 2 + ext/standard/http_fopen_wrapper.c | 21 ++++++--- ext/standard/tests/http/bug67430.phpt | 2 +- ext/standard/tests/http/gh11274.phpt | 62 +++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 ext/standard/tests/http/gh11274.phpt diff --git a/NEWS b/NEWS index 9c44373d21534..0f24c304b84aa 100644 --- a/NEWS +++ b/NEWS @@ -46,6 +46,8 @@ PHP NEWS - Standard: . Fixed bug GH-11138 (move_uploaded_file() emits open_basedir warning for source file). (ilutov) + . Fixed bug GH-11274 (POST/PATCH request switches to GET after a HTTP 308 + redirect). (nielsdos) - Streams: . Fixed bug GH-10031 ([Stream] STREAM_NOTIFY_PROGRESS over HTTP emitted diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index fa0dcb5e6890a..f6a4a094b425e 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -79,6 +79,7 @@ #define HTTP_WRAPPER_HEADER_INIT 1 #define HTTP_WRAPPER_REDIRECTED 2 +#define HTTP_WRAPPER_KEEP_METHOD 4 static inline void strip_header(char *header_bag, char *lc_header_bag, const char *lc_header_name) @@ -140,6 +141,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *user_headers = NULL; 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; @@ -363,8 +365,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, if (context && (tmpzval = php_stream_context_get_option(context, "http", "method")) != NULL) { if (Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval) > 0) { /* As per the RFC, automatically redirected requests MUST NOT use other methods than - * GET and HEAD unless it can be confirmed by the user */ - if (!redirected + * GET and HEAD unless it can be confirmed by the user. */ + if (!redirected || redirect_keep_method || zend_string_equals_literal(Z_STR_P(tmpzval), "GET") || zend_string_equals_literal(Z_STR_P(tmpzval), "HEAD") ) { @@ -458,7 +460,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, zend_str_tolower(ZSTR_VAL(tmp), ZSTR_LEN(tmp)); t = ZSTR_VAL(tmp); - if (!header_init) { + if (!header_init && !redirect_keep_method) { /* strip POST headers on redirect */ strip_header(user_headers, t, "content-length:"); strip_header(user_headers, t, "content-type:"); @@ -606,7 +608,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, * see bug #44603 for details. Since Content-Type maybe part of user's headers we need to do this check first. */ if ( - header_init && + (header_init || redirect_keep_method) && context && !(have_header & HTTP_HEADER_CONTENT_LENGTH) && (tmpzval = php_stream_context_get_option(context, "http", "content")) != NULL && @@ -624,7 +626,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, } /* Request content, such as for POST requests */ - if (header_init && context && + if ((header_init || redirect_keep_method) && context && (tmpzval = php_stream_context_get_option(context, "http", "content")) != NULL && Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval) > 0) { if (!(have_header & HTTP_HEADER_CONTENT_LENGTH)) { @@ -913,9 +915,16 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, CHECK_FOR_CNTRL_CHARS(resource->pass); CHECK_FOR_CNTRL_CHARS(resource->path); } + int new_flags = HTTP_WRAPPER_REDIRECTED; + if (response_code == 307 || response_code == 308) { + /* RFC 7538 specifies that status code 308 does not allow changing the request method from POST to GET. + * RFC 7231 does the same for status code 307. + * To keep consistency between POST and PATCH requests, we'll also not change the request method from PATCH to GET, even though it's allowed it's not mandated by the RFC. */ + new_flags |= HTTP_WRAPPER_KEEP_METHOD; + } stream = php_stream_url_wrap_http_ex( wrapper, new_path, mode, options, opened_path, context, - --redirect_max, HTTP_WRAPPER_REDIRECTED, response_header STREAMS_CC); + --redirect_max, new_flags, response_header STREAMS_CC); } else { php_stream_wrapper_log_error(wrapper, options, "HTTP request failed! %s", tmp_line); } diff --git a/ext/standard/tests/http/bug67430.phpt b/ext/standard/tests/http/bug67430.phpt index e72e419fc02ac..1a515537e6609 100644 --- a/ext/standard/tests/http/bug67430.phpt +++ b/ext/standard/tests/http/bug67430.phpt @@ -41,7 +41,7 @@ POST / HTTP/1.1 Host: %s:%d Connection: close -GET /foo HTTP/1.1 +POST /foo HTTP/1.1 Host: %s:%d Connection: close diff --git a/ext/standard/tests/http/gh11274.phpt b/ext/standard/tests/http/gh11274.phpt new file mode 100644 index 0000000000000..fc125bfc494cf --- /dev/null +++ b/ext/standard/tests/http/gh11274.phpt @@ -0,0 +1,62 @@ +--TEST-- +GH-11274 (POST/PATCH request via file_get_contents + stream_context_create switches to GET after a HTTP 308 redirect) +--INI-- +allow_url_fopen=1 +--CONFLICTS-- +server +--FILE-- + ['method' => 'POST', 'header' => 'Content-type: application/x-www-form-urlencoded', 'content' => http_build_query(['hello' => 'world'])]])); + echo file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS . "/test$suffix", false, stream_context_create(['http' => ['method' => 'PATCH', 'header' => 'Content-type: application/x-www-form-urlencoded', 'content' => http_build_query(['hello' => 'world'])]])); + echo file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS . "/test/$suffix", false, stream_context_create(['http' => ['method' => 'POST', 'header' => 'Content-type: application/x-www-form-urlencoded', 'content' => http_build_query(['hello' => 'world'])]])); + echo file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS . "/test/$suffix", false, stream_context_create(['http' => ['method' => 'PATCH', 'header' => 'Content-type: application/x-www-form-urlencoded', 'content' => http_build_query(['hello' => 'world'])]])); +} +?> +--EXPECT-- +-- Testing unredirected request -- +method: POST; body: hello=world +method: PATCH; body: hello=world +method: POST; body: hello=world +method: PATCH; body: hello=world +-- Testing redirect status code 301 -- +method: GET; body: +method: GET; body: +method: GET; body: +method: GET; body: +-- Testing redirect status code 302 -- +method: GET; body: +method: GET; body: +method: GET; body: +method: GET; body: +-- Testing redirect status code 307 -- +method: POST; body: hello=world +method: PATCH; body: hello=world +method: POST; body: hello=world +method: PATCH; body: hello=world +-- Testing redirect status code 308 -- +method: POST; body: hello=world +method: PATCH; body: hello=world +method: POST; body: hello=world +method: PATCH; body: hello=world From 93fa9613e162d1a0e8479ba83c4b6a399846e209 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 16 May 2023 00:31:57 +0800 Subject: [PATCH 34/38] Fix GH-11099: Generating phar.php during cross-compile can't be done Closes GH-11243. --- NEWS | 4 ++++ ext/phar/Makefile.frag | 44 ++++++++++++++++++++++++++++-------------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/NEWS b/NEWS index 0f24c304b84aa..32f2b0e8dc969 100644 --- a/NEWS +++ b/NEWS @@ -36,6 +36,10 @@ PHP NEWS - PGSQL: . Fixed parameter parsing of pg_lo_export(). (kocsismate) +- Phar: + . Fixed bug GH-11099 (Generating phar.php during cross-compile can't be + done). (peter279k) + - Soap: . Fixed bug GH-8426 (make test fail while soap extension build). (nielsdos) diff --git a/ext/phar/Makefile.frag b/ext/phar/Makefile.frag index 58789cae25b57..e5646b2029261 100644 --- a/ext/phar/Makefile.frag +++ b/ext/phar/Makefile.frag @@ -29,22 +29,38 @@ $(builddir)/phar/phar.inc: $(srcdir)/phar/phar.inc -@test -d $(builddir)/phar || mkdir $(builddir)/phar -@test -f $(builddir)/phar/phar.inc || cp $(srcdir)/phar/phar.inc $(builddir)/phar/phar.inc + +TEST_PHP_EXECUTABLE = $(shell $(PHP_EXECUTABLE) -v 2>&1) +TEST_PHP_EXECUTABLE_RES = $(shell echo "$(TEST_PHP_EXECUTABLE)" | grep -c 'Exec format error') + $(builddir)/phar.php: $(srcdir)/build_precommand.php $(srcdir)/phar/*.inc $(srcdir)/phar/*.php $(SAPI_CLI_PATH) - -@echo "Generating phar.php" - @$(PHP_PHARCMD_EXECUTABLE) $(PHP_PHARCMD_SETTINGS) $(srcdir)/build_precommand.php > $(builddir)/phar.php + -@(echo "Generating phar.php"; \ + if [ $(TEST_PHP_EXECUTABLE_RES) -ne 1 ]; then \ + $(PHP_PHARCMD_EXECUTABLE) $(PHP_PHARCMD_SETTINGS) $(srcdir)/build_precommand.php > $(builddir)/phar.php; \ + else \ + echo "Skipping phar.php generating during cross compilation"; \ + fi) $(builddir)/phar.phar: $(builddir)/phar.php $(builddir)/phar/phar.inc $(srcdir)/phar/*.inc $(srcdir)/phar/*.php $(SAPI_CLI_PATH) - -@echo "Generating phar.phar" - -@rm -f $(builddir)/phar.phar - -@rm -f $(srcdir)/phar.phar - @$(PHP_PHARCMD_EXECUTABLE) $(PHP_PHARCMD_SETTINGS) $(builddir)/phar.php pack -f $(builddir)/phar.phar -a pharcommand -c auto -x \\.svn -p 0 -s $(srcdir)/phar/phar.php -h sha1 -b "$(PHP_PHARCMD_BANG)" $(srcdir)/phar/ - -@chmod +x $(builddir)/phar.phar + -@(echo "Generating phar.phar"; \ + if [ $(TEST_PHP_EXECUTABLE_RES) -ne 1 ]; then \ + rm -f $(builddir)/phar.phar; \ + rm -f $(srcdir)/phar.phar; \ + $(PHP_PHARCMD_EXECUTABLE) $(PHP_PHARCMD_SETTINGS) $(builddir)/phar.php pack -f $(builddir)/phar.phar -a pharcommand -c auto -x \\.svn -p 0 -s $(srcdir)/phar/phar.php -h sha1 -b "$(PHP_PHARCMD_BANG)" $(srcdir)/phar/; \ + chmod +x $(builddir)/phar.phar; \ + else \ + echo "Skipping phar.phar generating during cross compilation"; \ + fi) install-pharcmd: pharcmd - -@$(mkinstalldirs) $(INSTALL_ROOT)$(bindir) - $(INSTALL) $(builddir)/phar.phar $(INSTALL_ROOT)$(bindir)/$(program_prefix)phar$(program_suffix).phar - -@rm -f $(INSTALL_ROOT)$(bindir)/$(program_prefix)phar$(program_suffix) - $(LN_S) -f $(program_prefix)phar$(program_suffix).phar $(INSTALL_ROOT)$(bindir)/$(program_prefix)phar$(program_suffix) - @$(mkinstalldirs) $(INSTALL_ROOT)$(mandir)/man1 - @$(INSTALL_DATA) $(builddir)/phar.1 $(INSTALL_ROOT)$(mandir)/man1/$(program_prefix)phar$(program_suffix).1 - @$(INSTALL_DATA) $(builddir)/phar.phar.1 $(INSTALL_ROOT)$(mandir)/man1/$(program_prefix)phar$(program_suffix).phar.1 + @(if [ $(TEST_PHP_EXECUTABLE_RES) -ne 1 ]; then \ + $(mkinstalldirs) $(INSTALL_ROOT)$(bindir); \ + $(INSTALL) $(builddir)/phar.phar $(INSTALL_ROOT)$(bindir)/$(program_prefix)phar$(program_suffix).phar; \ + rm -f $(INSTALL_ROOT)$(bindir)/$(program_prefix)phar$(program_suffix); \ + $(LN_S) -f $(program_prefix)phar$(program_suffix).phar $(INSTALL_ROOT)$(bindir)/$(program_prefix)phar$(program_suffix); \ + $(mkinstalldirs) $(INSTALL_ROOT)$(mandir)/man1; \ + $(INSTALL_DATA) $(builddir)/phar.1 $(INSTALL_ROOT)$(mandir)/man1/$(program_prefix)phar$(program_suffix).1; \ + $(INSTALL_DATA) $(builddir)/phar.phar.1 $(INSTALL_ROOT)$(mandir)/man1/$(program_prefix)phar$(program_suffix).phar.1; \ + else \ + echo "Skipping install-pharcmd during cross compilation"; \ + fi) From 5cad1a717692a5a3f14b889c6aee538a9bbcc374 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 16 May 2023 18:03:47 +0200 Subject: [PATCH 35/38] Fix GH-11245 (In some specific cases SWITCH with one default statement will cause segfault) The block optimizer pass allows the use of sources of the preceding block if the block is a follower and not a target. This causes issues when trying to remove FREE instructions: if the source is not in the block of the FREE, then the FREE and source are still removed. Therefore the other successor blocks, which must consume or FREE the temporary, will still contain the FREE opline. This opline will now refer to a temporary that doesn't exist anymore, which most of the time results in a crash. For these kind of non-local scenarios, we'll let the SSA based optimizations handle those cases. Closes GH-11251. --- NEWS | 2 ++ Zend/Optimizer/block_pass.c | 10 +++++++- ext/opcache/tests/opt/gh11245_1.phpt | 33 ++++++++++++++++++++++++++ ext/opcache/tests/opt/gh11245_2.phpt | 35 ++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 ext/opcache/tests/opt/gh11245_1.phpt create mode 100644 ext/opcache/tests/opt/gh11245_2.phpt diff --git a/NEWS b/NEWS index 32f2b0e8dc969..2e714c631ba7e 100644 --- a/NEWS +++ b/NEWS @@ -32,6 +32,8 @@ PHP NEWS - Opcache: . Fixed bug GH-11134 (Incorrect match default branch optimization). (ilutov) . Fixed too wide OR and AND range inference. (nielsdos) + . Fixed bug GH-11245 (In some specific cases SWITCH with one default + statement will cause segfault). (nielsdos) - PGSQL: . Fixed parameter parsing of pg_lo_export(). (kocsismate) diff --git a/Zend/Optimizer/block_pass.c b/Zend/Optimizer/block_pass.c index 72ae012066094..b17fcde4bdf85 100644 --- a/Zend/Optimizer/block_pass.c +++ b/Zend/Optimizer/block_pass.c @@ -257,6 +257,10 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array break; case ZEND_FREE: + /* Note: Only remove the source if the source is local to this block. + * If it's not local, then the other blocks successors must also eventually either FREE or consume the temporary, + * hence removing the temporary is not safe in the general case, especially when other consumers are not FREE. + * A FREE may not be removed without also removing the source's result, because otherwise that would cause a memory leak. */ if (opline->op1_type == IS_TMP_VAR) { src = VAR_SOURCE(opline->op1); if (src) { @@ -265,6 +269,7 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array case ZEND_BOOL_NOT: /* T = BOOL(X), FREE(T) => T = BOOL(X) */ /* The remaining BOOL is removed by a separate optimization */ + /* The source is a bool, no source removals take place, so this may be done non-locally. */ VAR_SOURCE(opline->op1) = NULL; MAKE_NOP(opline); ++(*opt_count); @@ -283,6 +288,9 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array case ZEND_PRE_DEC_OBJ: case ZEND_PRE_INC_STATIC_PROP: case ZEND_PRE_DEC_STATIC_PROP: + if (src < op_array->opcodes + block->start) { + break; + } src->result_type = IS_UNUSED; VAR_SOURCE(opline->op1) = NULL; MAKE_NOP(opline); @@ -295,7 +303,7 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array } else if (opline->op1_type == IS_VAR) { src = VAR_SOURCE(opline->op1); /* V = OP, FREE(V) => OP. NOP */ - if (src && + if (src >= op_array->opcodes + block->start && src->opcode != ZEND_FETCH_R && src->opcode != ZEND_FETCH_STATIC_PROP_R && src->opcode != ZEND_FETCH_DIM_R && diff --git a/ext/opcache/tests/opt/gh11245_1.phpt b/ext/opcache/tests/opt/gh11245_1.phpt new file mode 100644 index 0000000000000..eac085ac44025 --- /dev/null +++ b/ext/opcache/tests/opt/gh11245_1.phpt @@ -0,0 +1,33 @@ +--TEST-- +GH-11245: In some specific cases SWITCH with one default statement will cause segfault (VAR variation) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=0x7FFFBFFF +opcache.opt_debug_level=0x20000 +opcache.preload= +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=4, args=0, vars=1, tmps=1) + ; (after optimizer) + ; %s +0000 T1 = ISSET_ISEMPTY_CV (empty) CV0($xx) +0001 JMPNZ T1 0003 +0002 RETURN null +0003 RETURN int(1) + +xx: + ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %s +0000 RETURN string("somegarbage") diff --git a/ext/opcache/tests/opt/gh11245_2.phpt b/ext/opcache/tests/opt/gh11245_2.phpt new file mode 100644 index 0000000000000..8e967bf9f41be --- /dev/null +++ b/ext/opcache/tests/opt/gh11245_2.phpt @@ -0,0 +1,35 @@ +--TEST-- +GH-11245: In some specific cases SWITCH with one default statement will cause segfault (TMP variation) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=0x7FFFBFFF +opcache.opt_debug_level=0x20000 +opcache.preload= +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=7, args=0, vars=1, tmps=2) + ; (after optimizer) + ; %s +0000 T1 = PRE_INC_STATIC_PROP string("prop") string("X") +0001 T2 = ISSET_ISEMPTY_CV (empty) CV0($xx) +0002 JMPZ T2 0005 +0003 FREE T1 +0004 RETURN null +0005 FREE T1 +0006 RETURN int(1) +LIVE RANGES: + 1: 0001 - 0005 (tmp/var) From f9117eb82487a0566c8fee3fda798cc2eb969d4a Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 20 May 2023 01:19:28 +0200 Subject: [PATCH 36/38] Fix GH-11281: DateTimeZone::getName() does not include seconds in offset If the seconds portion is non-zero, include the seconds in the output. Closes GH-11282. --- NEWS | 4 ++++ ext/date/php_date.c | 18 +++++++++++++++--- ext/date/tests/bug81097.phpt | 2 +- ext/date/tests/bug81565.phpt | 2 +- ext/date/tests/gh11281.phpt | 33 +++++++++++++++++++++++++++++++++ 5 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 ext/date/tests/gh11281.phpt diff --git a/NEWS b/NEWS index 2e714c631ba7e..ff7857f62e9bd 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,10 @@ PHP NEWS . Fixed bug GH-11222 (foreach by-ref may jump over keys during a rehash). (Bob) +- Date: + . Fixed bug GH-11281 (DateTimeZone::getName() does not include seconds in + offset). (nielsdos) + - Exif: . Fixed bug GH-10834 (exif_read_data() cannot read smaller stream wrapper chunk sizes). (nielsdos) diff --git a/ext/date/php_date.c b/ext/date/php_date.c index a0625c96c9826..92ebd4f9c688b 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -1957,13 +1957,25 @@ static void php_timezone_to_string(php_timezone_obj *tzobj, zval *zv) ZVAL_STRING(zv, tzobj->tzi.tz->name); break; case TIMELIB_ZONETYPE_OFFSET: { - zend_string *tmpstr = zend_string_alloc(sizeof("UTC+05:00")-1, 0); timelib_sll utc_offset = tzobj->tzi.utc_offset; + int seconds = utc_offset % 60; + size_t size; + const char *format; + if (seconds == 0) { + size = sizeof("+05:00"); + format = "%c%02d:%02d"; + } else { + size = sizeof("+05:00:01"); + format = "%c%02d:%02d:%02d"; + } + zend_string *tmpstr = zend_string_alloc(size - 1, 0); - ZSTR_LEN(tmpstr) = snprintf(ZSTR_VAL(tmpstr), sizeof("+05:00"), "%c%02d:%02d", + /* Note: if seconds == 0, the seconds argument will be excessive and therefore ignored. */ + ZSTR_LEN(tmpstr) = snprintf(ZSTR_VAL(tmpstr), size, format, utc_offset < 0 ? '-' : '+', abs((int)(utc_offset / 3600)), - abs((int)(utc_offset % 3600) / 60)); + abs((int)(utc_offset % 3600) / 60), + abs(seconds)); ZVAL_NEW_STR(zv, tmpstr); } diff --git a/ext/date/tests/bug81097.phpt b/ext/date/tests/bug81097.phpt index 2cfd7e00a9dd4..7a3baf06a6389 100644 --- a/ext/date/tests/bug81097.phpt +++ b/ext/date/tests/bug81097.phpt @@ -10,5 +10,5 @@ object(DateTimeZone)#%d (%d) { ["timezone_type"]=> int(1) ["timezone"]=> - string(6) "+01:45" + string(9) "+01:45:30" } diff --git a/ext/date/tests/bug81565.phpt b/ext/date/tests/bug81565.phpt index 34f8d869fa32a..b7392540b8f68 100644 --- a/ext/date/tests/bug81565.phpt +++ b/ext/date/tests/bug81565.phpt @@ -17,4 +17,4 @@ DateTime::__set_state(array( 'timezone_type' => 1, 'timezone' => '+00:49', )) -+01:45 ++01:45:30 diff --git a/ext/date/tests/gh11281.phpt b/ext/date/tests/gh11281.phpt new file mode 100644 index 0000000000000..be1fe30b88c9d --- /dev/null +++ b/ext/date/tests/gh11281.phpt @@ -0,0 +1,33 @@ +--TEST-- +GH-11281 (DateTimeZone::getName() does not include seconds in offset) +--FILE-- +getName(), "\n"; +$tz = new DateTimeZone('+03:00:00'); +echo $tz->getName(), "\n"; +$tz = new DateTimeZone('-03:00:00'); +echo $tz->getName(), "\n"; +$tz = new DateTimeZone('+03:00:01'); +echo $tz->getName(), "\n"; +$tz = new DateTimeZone('-03:00:01'); +echo $tz->getName(), "\n"; +$tz = new DateTimeZone('+03:00:58'); +echo $tz->getName(), "\n"; +$tz = new DateTimeZone('-03:00:58'); +echo $tz->getName(), "\n"; +$tz = new DateTimeZone('+03:00:59'); +echo $tz->getName(), "\n"; +$tz = new DateTimeZone('-03:00:59'); +echo $tz->getName(), "\n"; +?> +--EXPECT-- ++03:00 ++03:00 +-03:00 ++03:00:01 +-03:00:01 ++03:00:58 +-03:00:58 ++03:00:59 +-03:00:59 From 8648eba93a90c5b786df97d961f9e0e2f5bec502 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 6 Jun 2023 18:01:56 -0400 Subject: [PATCH 37/38] Fix missing randomness check and insufficient random bytes for SOAP HTTP Digest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If php_random_bytes_throw fails, the nonce will be uninitialized, but still sent to the server. The client nonce is intended to protect against a malicious server. See section 5.10 and 5.12 of RFC 7616 [1], and bullet point 2 below. Tim pointed out that even though it's the MD5 of the nonce that gets sent, enumerating 31 bits is trivial. So we have still a stack information leak of 31 bits. Furthermore, Tim found the following issues: * The small size of cnonce might cause the server to erroneously reject a request due to a repeated (cnonce, nc) pair. As per the birthday problem 31 bits of randomness will return a duplication with 50% chance after less than 55000 requests and nc always starts counting at 1. * The cnonce is intended to protect the client and password against a malicious server that returns a constant server nonce where the server precomputed a rainbow table between passwords and correct client response. As storage is fairly cheap, a server could precompute the client responses for (a subset of) client nonces and still have a chance of reversing the client response with the same probability as the cnonce duplication. Precomputing the rainbow table for all 2^31 cnonces increases the rainbow table size by factor 2 billion, which is infeasible. But precomputing it for 2^14 cnonces only increases the table size by factor 16k and the server would still have a 10% chance of successfully reversing a password with a single client request. This patch fixes the issues by increasing the nonce size, and checking the return value of php_random_bytes_throw(). In the process we also get rid of the MD5 hashing of the nonce. [1] RFC 7616: https://www.rfc-editor.org/rfc/rfc7616 Additionally: * Fix GH-11382 add missing hash header for bin2hex * Update NEWS Co-authored-by: Tim Düsterhus Co-authored-by: Remi Collet Co-authored-by: Pierrick Charron --- NEWS | 2 ++ ext/soap/php_http.c | 22 ++++++++++++++-------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/NEWS b/NEWS index ff7857f62e9bd..e77baf42481b7 100644 --- a/NEWS +++ b/NEWS @@ -47,6 +47,8 @@ PHP NEWS done). (peter279k) - Soap: + . Fixed bug GHSA-76gg-c692-v2mw (Missing error check and insufficient random + bytes in HTTP Digest authentication for SOAP). (nielsdos, timwolla) . Fixed bug GH-8426 (make test fail while soap extension build). (nielsdos) - SPL: diff --git a/ext/soap/php_http.c b/ext/soap/php_http.c index db3c97b647391..5f964ee9db5c6 100644 --- a/ext/soap/php_http.c +++ b/ext/soap/php_http.c @@ -20,6 +20,7 @@ #include "ext/standard/base64.h" #include "ext/standard/md5.h" #include "ext/standard/php_random.h" +#include "ext/hash/php_hash.h" static char *get_http_header_value_nodup(char *headers, char *type, size_t *len); static char *get_http_header_value(char *headers, char *type); @@ -657,18 +658,23 @@ int make_http_soap_request(zval *this_ptr, has_authorization = 1; if (Z_TYPE_P(digest) == IS_ARRAY) { char HA1[33], HA2[33], response[33], cnonce[33], nc[9]; - zend_long nonce; + unsigned char nonce[16]; PHP_MD5_CTX md5ctx; unsigned char hash[16]; - php_random_bytes_throw(&nonce, sizeof(nonce)); - nonce &= 0x7fffffff; + if (UNEXPECTED(php_random_bytes_throw(&nonce, sizeof(nonce)) != SUCCESS)) { + ZEND_ASSERT(EG(exception)); + php_stream_close(stream); + convert_to_null(Z_CLIENT_HTTPURL_P(this_ptr)); + convert_to_null(Z_CLIENT_HTTPSOCKET_P(this_ptr)); + convert_to_null(Z_CLIENT_USE_PROXY_P(this_ptr)); + smart_str_free(&soap_headers_z); + smart_str_free(&soap_headers); + return FALSE; + } - PHP_MD5Init(&md5ctx); - snprintf(cnonce, sizeof(cnonce), ZEND_LONG_FMT, nonce); - PHP_MD5Update(&md5ctx, (unsigned char*)cnonce, strlen(cnonce)); - PHP_MD5Final(hash, &md5ctx); - make_digest(cnonce, hash); + php_hash_bin2hex(cnonce, nonce, sizeof(nonce)); + cnonce[32] = 0; if ((tmp = zend_hash_str_find(Z_ARRVAL_P(digest), "nc", sizeof("nc")-1)) != NULL && Z_TYPE_P(tmp) == IS_LONG) { From 418d1f85f4f50694bdf61e5b5b812307f108a397 Mon Sep 17 00:00:00 2001 From: Ben Ramsey Date: Tue, 6 Jun 2023 17:56:35 -0500 Subject: [PATCH 38/38] Update versions for PHP 8.1.20 --- NEWS | 2 +- Zend/zend.h | 2 +- configure.ac | 2 +- main/php_version.h | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index e77baf42481b7..3df49abf26675 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,6 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.1.20 +08 Jun 2023, PHP 8.1.20 - Core: . Fixed bug GH-9068 (Conditional jump or move depends on uninitialised diff --git a/Zend/zend.h b/Zend/zend.h index 5c3c1de49d5ce..b3ec277e48776 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.1.20-dev" +#define ZEND_VERSION "4.1.20" #define ZEND_ENGINE_3 diff --git a/configure.ac b/configure.ac index 166d313f133bc..90b1a908ec231 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.1.20-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.1.20],[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/main/php_version.h b/main/php_version.h index 8b01d87fbacc1..33d23ebb35297 100644 --- a/main/php_version.h +++ b/main/php_version.h @@ -3,6 +3,6 @@ #define PHP_MAJOR_VERSION 8 #define PHP_MINOR_VERSION 1 #define PHP_RELEASE_VERSION 20 -#define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.1.20-dev" +#define PHP_EXTRA_VERSION "" +#define PHP_VERSION "8.1.20" #define PHP_VERSION_ID 80120