From fcd691b377d02285740744bee17c0f298be227d5 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 28 Jul 2026 02:48:11 +0200 Subject: [PATCH 01/12] libgd patch for CVE-2026-9672 Patch by Pierre Joye (pierrejoye). --- ext/gd/libgd/gd_gif_in.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/gd/libgd/gd_gif_in.c b/ext/gd/libgd/gd_gif_in.c index 1f697236107f..e055f9024455 100644 --- a/ext/gd/libgd/gd_gif_in.c +++ b/ext/gd/libgd/gd_gif_in.c @@ -450,7 +450,7 @@ LWZReadByte_(gdIOCtx *fd, LZW_STATIC_DATA *sd, char flag, int input_code_size, i sd->table[1][i] = i; } for (; i < (1<table[0][i] = sd->table[1][0] = 0; + sd->table[0][i] = sd->table[1][i] = 0; sd->sp = sd->stack; @@ -494,6 +494,8 @@ LWZReadByte_(gdIOCtx *fd, LZW_STATIC_DATA *sd, char flag, int input_code_size, i if (count != 0) return -2; + + return -2; } incode = code; @@ -560,7 +562,7 @@ ReadImage(gdImagePtr im, gdIOCtx *fd, int len, int height, unsigned char (*cmap) int v; int xpos = 0, ypos = 0, pass = 0; int i; - LZW_STATIC_DATA sd; + LZW_STATIC_DATA sd = {0}; /* From 7abd4931d67a64fd76fd111b9f6ac885b9b018c5 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Tue, 28 Jul 2026 16:38:19 +0800 Subject: [PATCH 02/12] ext/gmp: Add gmp_prevprime() (#22807) Expose mpz_prevprime() to the userland, namely gmp_prevprime(). --- NEWS | 1 + UPGRADING | 9 +++++++++ ext/gmp/config.m4 | 1 + ext/gmp/config.w32 | 3 +++ ext/gmp/gmp.c | 28 ++++++++++++++++++++++++++ ext/gmp/gmp.stub.php | 4 ++++ ext/gmp/gmp_arginfo.h | 14 ++++++++++++- ext/gmp/tests/bug80560.phpt | 3 +++ ext/gmp/tests/gmp_prevprime.phpt | 34 ++++++++++++++++++++++++++++++++ 9 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 ext/gmp/tests/gmp_prevprime.phpt diff --git a/NEWS b/NEWS index 7ec4eb4b809e..a7aa29cd9043 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,7 @@ PHP NEWS declares a default value for the attribute). (iliaal) - GMP: + . Added gmp_prevprime(). (Weilin Du, David Carlier) . Fixed GMP power and shift operators to reject GMP right operands outside the unsigned long range instead of silently truncating them. (Weilin Du) . Fixed GMP integer string parsing to reject strings containing NUL bytes diff --git a/UPGRADING b/UPGRADING index d30a5663274f..3d9cfdc6aeb9 100644 --- a/UPGRADING +++ b/UPGRADING @@ -271,6 +271,12 @@ PHP 8.6 UPGRADE NOTES - Fileinfo: . finfo_file() now works with remote streams. +- GMP: + . Added gmp_prevprime() to get the largest prime smaller than the given + number. A ValueError is thrown if no such prime exists. This function is + available only when PHP is built against GNU MP 6.3.0 or later; it is not + available on official Windows builds using MPIR. + - Intl: . Added Locale::getDisplayKeyword() and Locale::getDisplayKeywordValue(), with the alias of locale_get_display_keyword() and @@ -421,6 +427,9 @@ PHP 8.6 UPGRADE NOTES 6. New Functions ======================================== +- GMP: + . gmp_prevprime() + - Intl: . grapheme_strrev() RFC: https://wiki.php.net/rfc/grapheme_strrev diff --git a/ext/gmp/config.m4 b/ext/gmp/config.m4 index f0f07d377078..c4ffc2d42d4d 100644 --- a/ext/gmp/config.m4 +++ b/ext/gmp/config.m4 @@ -22,6 +22,7 @@ if test "$PHP_GMP" != "no"; then LIBS="$LIBS $GMP_LIBS" gmp_check=no AC_CHECK_HEADER([gmp.h], [AC_CHECK_FUNC([__gmpz_rootrem], [gmp_check=yes])]) + AC_CHECK_FUNCS([__gmpz_prevprime]) CFLAGS=$CFLAGS_SAVED LIBS=$LIBS_SAVED diff --git a/ext/gmp/config.w32 b/ext/gmp/config.w32 index dc0c1e978d31..d83048ebe9c6 100644 --- a/ext/gmp/config.w32 +++ b/ext/gmp/config.w32 @@ -5,6 +5,9 @@ ARG_WITH("gmp", "Include GNU MP support.", "no"); if (PHP_GMP != "no") { if (CHECK_LIB("mpir_a.lib", "gmp", PHP_GMP) && CHECK_HEADER("gmp.h", "CFLAGS_GMP", PHP_GMP + ";" + PHP_PHP_BUILD + "\\include\\mpir")) { + if (GREP_HEADER("gmp.h", "mpz_prevprime", PHP_GMP + ";" + PHP_PHP_BUILD + "\\include\\mpir")) { + AC_DEFINE('HAVE___GMPZ_PREVPRIME', 1, "Define to 1 if GMP has the 'mpz_prevprime' function."); + } EXTENSION("gmp", "gmp.c", null, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); PHP_INSTALL_HEADERS("ext/gmp", "php_gmp_int.h"); AC_DEFINE('HAVE_GMP', 1, "Define to 1 if the PHP extension 'gmp' is available."); diff --git a/ext/gmp/gmp.c b/ext/gmp/gmp.c index 7f8799a7ae2d..9ade8eade215 100644 --- a/ext/gmp/gmp.c +++ b/ext/gmp/gmp.c @@ -1082,6 +1082,34 @@ GMP_UNARY_OP_FUNCTION(com); /* {{{ Finds next prime of a */ GMP_UNARY_OP_FUNCTION(nextprime); +#ifdef HAVE___GMPZ_PREVPRIME +/* {{{ Finds previous prime of a */ +ZEND_FUNCTION(gmp_prevprime) +{ + mpz_ptr gmpnum_a, gmpnum_result; + int res; + + ZEND_PARSE_PARAMETERS_START(1, 1) + GMP_Z_PARAM_INTO_MPZ_PTR(gmpnum_a) + ZEND_PARSE_PARAMETERS_END(); + + if (mpz_cmp_ui(gmpnum_a, 2) <= 0) { + /* + * mpz_prevprime() returns 0 when no previous prime exists, which happens + * for operands not greater than 2. + * https://gmplib.org/manual/Number-Theoretic-Functions#index-mpz_005fprevprime + */ + zend_argument_value_error(1, "must be greater than 2"); + RETURN_THROWS(); + } + + INIT_GMP_RETVAL(gmpnum_result); + res = mpz_prevprime(gmpnum_result, gmpnum_a); + ZEND_ASSERT(res); +} +/* }}} */ +#endif + /* Add a and b */ GMP_BINARY_OP_FUNCTION(add); /* Subtract b from a */ diff --git a/ext/gmp/gmp.stub.php b/ext/gmp/gmp.stub.php index 75812c62c5ca..d3c603310a4a 100644 --- a/ext/gmp/gmp.stub.php +++ b/ext/gmp/gmp.stub.php @@ -183,4 +183,8 @@ function gmp_hamdist(GMP|int|string $num1, GMP|int|string $num2): int {} function gmp_nextprime(GMP|int|string $num): GMP {} +#ifdef HAVE___GMPZ_PREVPRIME +function gmp_prevprime(GMP|int|string $num): GMP {} +#endif + function gmp_binomial(GMP|int|string $n, int $k): GMP {} diff --git a/ext/gmp/gmp_arginfo.h b/ext/gmp/gmp_arginfo.h index 436e3a22ea72..957fdc8d7765 100644 --- a/ext/gmp/gmp_arginfo.h +++ b/ext/gmp/gmp_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit gmp.stub.php instead. - * Stub hash: 3aabd5a5d2db0df15b249a425465ae718c13ab6b */ + * Stub hash: 9d651cc4ba238a496ebe8302fe3e0f985d48d769 */ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_gmp_init, 0, 1, GMP, 0) ZEND_ARG_TYPE_MASK(0, num, MAY_BE_LONG|MAY_BE_STRING, NULL) @@ -179,6 +179,12 @@ ZEND_END_ARG_INFO() #define arginfo_gmp_nextprime arginfo_gmp_neg +#if defined(HAVE___GMPZ_PREVPRIME) +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_gmp_prevprime, 0, 1, GMP, 0) + ZEND_ARG_OBJ_TYPE_MASK(0, num, GMP, MAY_BE_LONG|MAY_BE_STRING, NULL) +ZEND_END_ARG_INFO() +#endif + ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_gmp_binomial, 0, 2, GMP, 0) ZEND_ARG_OBJ_TYPE_MASK(0, n, GMP, MAY_BE_LONG|MAY_BE_STRING, NULL) ZEND_ARG_TYPE_INFO(0, k, IS_LONG, 0) @@ -245,6 +251,9 @@ ZEND_FUNCTION(gmp_scan1); ZEND_FUNCTION(gmp_popcount); ZEND_FUNCTION(gmp_hamdist); ZEND_FUNCTION(gmp_nextprime); +#if defined(HAVE___GMPZ_PREVPRIME) +ZEND_FUNCTION(gmp_prevprime); +#endif ZEND_FUNCTION(gmp_binomial); ZEND_METHOD(GMP, __construct); ZEND_METHOD(GMP, __serialize); @@ -301,6 +310,9 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(gmp_popcount, arginfo_gmp_popcount) ZEND_FE(gmp_hamdist, arginfo_gmp_hamdist) ZEND_FE(gmp_nextprime, arginfo_gmp_nextprime) +#if defined(HAVE___GMPZ_PREVPRIME) + ZEND_FE(gmp_prevprime, arginfo_gmp_prevprime) +#endif ZEND_FE(gmp_binomial, arginfo_gmp_binomial) ZEND_FE_END }; diff --git a/ext/gmp/tests/bug80560.phpt b/ext/gmp/tests/bug80560.phpt index 25af4a42d9b9..c5ccc050070c 100644 --- a/ext/gmp/tests/bug80560.phpt +++ b/ext/gmp/tests/bug80560.phpt @@ -24,6 +24,9 @@ $functions1 = [ 'gmp_com', 'gmp_nextprime', ]; +if (function_exists('gmp_prevprime')) { + $functions1[] = 'gmp_prevprime'; +} $functions1_need_int_2 = [ 'gmp_testbit', 'gmp_scan0', diff --git a/ext/gmp/tests/gmp_prevprime.phpt b/ext/gmp/tests/gmp_prevprime.phpt new file mode 100644 index 000000000000..42c08566c413 --- /dev/null +++ b/ext/gmp/tests/gmp_prevprime.phpt @@ -0,0 +1,34 @@ +--TEST-- +gmp_prevprime() +--EXTENSIONS-- +gmp +--SKIPIF-- + +--FILE-- +getMessage() . \PHP_EOL; + } +} + +var_dump(gmp_strval(gmp_prevprime(3))); +var_dump(gmp_strval(gmp_prevprime(4))); +var_dump(gmp_strval(gmp_prevprime(10000))); + +?> +--EXPECT-- +gmp_prevprime(): Argument #1 ($num) must be greater than 2 +gmp_prevprime(): Argument #1 ($num) must be greater than 2 +gmp_prevprime(): Argument #1 ($num) must be greater than 2 +gmp_prevprime(): Argument #1 ($num) must be greater than 2 +string(1) "2" +string(1) "3" +string(4) "9973" From ab048bd83b578119cf81b456526d50498421d617 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Mon, 27 Jul 2026 16:49:38 +0200 Subject: [PATCH 03/12] Fix SQL injection in ext-pgsql via E'...' backslash breakout php_pgsql_add_quotes() quotes the string with E'...', but PQescapeStringConn() does not escape \ unless standard_conforming_strings is off. PQescapeStringConn() is meant to be used with '', so do that instead. Fixes GHSA-7qpv-r5mr-78m4 --- ext/pgsql/pgsql.c | 3 +- ext/pgsql/tests/10pg_convert_9.phpt | 18 +++++- ext/pgsql/tests/10pg_convert_json_array.phpt | 4 +- ext/pgsql/tests/12pg_insert_9.phpt | 2 +- ext/pgsql/tests/14pg_update_9.phpt | 2 +- ext/pgsql/tests/GHSA-7qpv-r5mr-78m4.phpt | 58 ++++++++++++++++++++ ext/pgsql/tests/bug64609.phpt | 2 +- ext/pgsql/tests/bug68638.phpt | 2 +- 8 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 ext/pgsql/tests/GHSA-7qpv-r5mr-78m4.phpt diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index cfa915206b8e..1a496a7970ca 100644 --- a/ext/pgsql/pgsql.c +++ b/ext/pgsql/pgsql.c @@ -4469,7 +4469,7 @@ static int php_pgsql_convert_match(const zend_string *str, const char *regex , s */ static zend_string *php_pgsql_add_quotes(zend_string *src) { - return zend_string_concat3("E'", strlen("E'"), ZSTR_VAL(src), ZSTR_LEN(src), "'", strlen("'")); + return zend_string_concat3("'", strlen("'"), ZSTR_VAL(src), ZSTR_LEN(src), "'", strlen("'")); } /* }}} */ @@ -4747,7 +4747,6 @@ PHP_PGSQL_API zend_result php_pgsql_convert(PGconn *pg_link, const zend_string * zend_string *str; /* PostgreSQL ignores \0 */ str = zend_string_alloc(Z_STRLEN_P(val) * 2, 0); - /* better to use PGSQLescapeLiteral since PGescapeStringConn does not handle special \ */ ZSTR_LEN(str) = PQescapeStringConn(pg_link, ZSTR_VAL(str), Z_STRVAL_P(val), Z_STRLEN_P(val), &escape_err); if (escape_err) { diff --git a/ext/pgsql/tests/10pg_convert_9.phpt b/ext/pgsql/tests/10pg_convert_9.phpt index f4bfaf5e3955..fb36e5801c82 100644 --- a/ext/pgsql/tests/10pg_convert_9.phpt +++ b/ext/pgsql/tests/10pg_convert_9.phpt @@ -21,6 +21,8 @@ $converted = pg_convert($db, $table_name, $fields); var_dump($converted); +var_dump(pg_convert($db, $table_name, ['str' => "\\' OR 1=1"])); + /* Invalid values */ try { $converted = pg_convert($db, $table_name, [5 => 'AAA']); @@ -48,18 +50,30 @@ try { } catch (\TypeError $e) { echo $e->getMessage(), \PHP_EOL; } + +/* standard_conforming_strings = 1 */ +pg_query($db, "SET standard_conforming_strings = 1"); +var_dump(pg_convert($db, $table_name, ['str' => "\\' OR 1=1"])); ?> --EXPECT-- array(3) { [""num""]=> string(4) "1234" [""str""]=> - string(6) "E'AAA'" + string(5) "'AAA'" [""bin""]=> - string(12) "E'\\x424242'" + string(11) "'\\x424242'" +} +array(1) { + [""str""]=> + string(13) "'\\'' OR 1=1'" } Array of values must be an associative array with string keys Array of values must be an associative array with string keys Values must be of type string|int|float|bool|null, array given Values must be of type string|int|float|bool|null, stdClass given Values must be of type string|int|float|bool|null, PgSql\Connection given +array(1) { + [""str""]=> + string(12) "'\'' OR 1=1'" +} diff --git a/ext/pgsql/tests/10pg_convert_json_array.phpt b/ext/pgsql/tests/10pg_convert_json_array.phpt index 918765db06c5..5353e04dac5e 100644 --- a/ext/pgsql/tests/10pg_convert_json_array.phpt +++ b/ext/pgsql/tests/10pg_convert_json_array.phpt @@ -32,8 +32,8 @@ if (!pg_insert($db, $table_name_92, $fields)) { --EXPECT-- array(2) { [""textary""]=> - string(51) "E'{"meeting", "lunch", "training", "presentation"}'" + string(50) "'{"meeting", "lunch", "training", "presentation"}'" [""jsn""]=> - string(22) "E'{"f1":1,"f2":"foo"}'" + string(21) "'{"f1":1,"f2":"foo"}'" } OK diff --git a/ext/pgsql/tests/12pg_insert_9.phpt b/ext/pgsql/tests/12pg_insert_9.phpt index 892494fe6f0a..699426c89b21 100644 --- a/ext/pgsql/tests/12pg_insert_9.phpt +++ b/ext/pgsql/tests/12pg_insert_9.phpt @@ -54,7 +54,7 @@ try { echo "Ok\n"; ?> --EXPECTF-- -INSERT INTO "php_pgsql_test" ("num","str","bin") VALUES (1234,E'AAA',E'\\x424242'); +INSERT INTO "php_pgsql_test" ("num","str","bin") VALUES (1234,'AAA','\\x424242'); INSERT INTO "php_pgsql_test" ("num","str","bin") VALUES ('1234','AAA','BBB'); object(PgSql\Result)#%d (0) { } diff --git a/ext/pgsql/tests/14pg_update_9.phpt b/ext/pgsql/tests/14pg_update_9.phpt index d34f05216ec4..64493075490a 100644 --- a/ext/pgsql/tests/14pg_update_9.phpt +++ b/ext/pgsql/tests/14pg_update_9.phpt @@ -26,6 +26,6 @@ echo pg_update($db, $table_name, $fields, $ids, PGSQL_DML_STRING|PGSQL_DML_ESCAP echo "Ok\n"; ?> --EXPECT-- -UPDATE "php_pgsql_test" SET "num"=1234,"str"=E'ABC',"bin"=E'\\x58595a' WHERE "num"=1234; +UPDATE "php_pgsql_test" SET "num"=1234,"str"='ABC',"bin"='\\x58595a' WHERE "num"=1234; UPDATE "php_pgsql_test" SET "num"='1234',"str"='ABC',"bin"='XYZ' WHERE "num"='1234'; Ok diff --git a/ext/pgsql/tests/GHSA-7qpv-r5mr-78m4.phpt b/ext/pgsql/tests/GHSA-7qpv-r5mr-78m4.phpt new file mode 100644 index 000000000000..88b37a8432fe --- /dev/null +++ b/ext/pgsql/tests/GHSA-7qpv-r5mr-78m4.phpt @@ -0,0 +1,58 @@ +--TEST-- +GHSA-7qpv-r5mr-78m4: SQL injection via E'...' backslash breakout +--CREDITS-- +expatch.llc +--EXTENSIONS-- +pgsql +--SKIPIF-- + +--FILE-- + "zzz' OR 1=1 --"]; +echo pg_select($db, 'ghsa_7qpv_r5mr_78m4', $params, PGSQL_DML_STRING) . "\n"; +printf("returned: %d\n\n", count(pg_select($db, 'ghsa_7qpv_r5mr_78m4', $params))); + +$params = ['name' => "zzz\\' OR 1=1 --"]; +echo pg_select($db, 'ghsa_7qpv_r5mr_78m4', $params, PGSQL_DML_STRING) . "\n"; +printf("returned: %d\n\n", count(pg_select($db, 'ghsa_7qpv_r5mr_78m4', $params))); + +$params = ['name' => "john\\', true) --", 'admin' => 'false']; +echo pg_insert($db, 'ghsa_7qpv_r5mr_78m4', $params, PGSQL_DML_STRING) . "\n"; +pg_insert($db, 'ghsa_7qpv_r5mr_78m4', $params); +var_dump(pg_select($db, 'ghsa_7qpv_r5mr_78m4', ['id' => 3])[0]['admin']); +echo "\n"; + +$params = ['name' => "jake\\', true) --", 'admin' => 'f']; +echo pg_insert($db, 'ghsa_7qpv_r5mr_78m4', $params, PGSQL_DML_ESCAPE|PGSQL_DML_STRING) . "\n"; +pg_insert($db, 'ghsa_7qpv_r5mr_78m4', $params, PGSQL_DML_EXEC|PGSQL_DML_ESCAPE); +var_dump(pg_select($db, 'ghsa_7qpv_r5mr_78m4', ['id' => 4])[0]['admin']); + +?> +--EXPECT-- +SELECT * FROM "ghsa_7qpv_r5mr_78m4" WHERE "name"='zzz'' OR 1=1 --'; +returned: 0 + +SELECT * FROM "ghsa_7qpv_r5mr_78m4" WHERE "name"='zzz\'' OR 1=1 --'; +returned: 0 + +INSERT INTO "ghsa_7qpv_r5mr_78m4" ("name","admin") VALUES ('john\'', true) --','f'); +string(1) "f" + +INSERT INTO "ghsa_7qpv_r5mr_78m4" ("name","admin") VALUES ('jake\'', true) --','f'); +string(1) "f" +--CLEAN-- + diff --git a/ext/pgsql/tests/bug64609.phpt b/ext/pgsql/tests/bug64609.phpt index ebf878dbeed9..ba27091ecf38 100644 --- a/ext/pgsql/tests/bug64609.phpt +++ b/ext/pgsql/tests/bug64609.phpt @@ -28,5 +28,5 @@ var_dump($converted); --EXPECT-- array(1) { [""a""]=> - string(5) "E'ok'" + string(4) "'ok'" } diff --git a/ext/pgsql/tests/bug68638.phpt b/ext/pgsql/tests/bug68638.phpt index d95fa1e44125..2a1025cad8d8 100644 --- a/ext/pgsql/tests/bug68638.phpt +++ b/ext/pgsql/tests/bug68638.phpt @@ -34,7 +34,7 @@ pg_query($conn, "DROP TABLE $table"); ?> --EXPECT-- -string(52) "UPDATE "test_68638" SET "value"=E'inf' WHERE "id"=1;" +string(51) "UPDATE "test_68638" SET "value"='inf' WHERE "id"=1;" array(2) { ["id"]=> string(1) "1" From 2e0fa0a44441d74bf8cc4e1ce1c8af9cd4209f52 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 3 May 2026 19:26:31 +0200 Subject: [PATCH 04/12] Fix GHSA-vc5h-9ppw-p5f3: phar circular symlink crash Prevents infinite recursion in phar_get_link_source. --- .../tests/tar/files/circular_symlinks.tar | Bin 0 -> 10240 bytes .../tar/files/circular_symlinks_long.tar | Bin 0 -> 215040 bytes .../tests/tar/files/circular_symlinks_rho.tar | Bin 0 -> 10240 bytes .../ghsa-vc5h-9ppw-p5f3-symlink-circular.phpt | 27 ++++++++++ ext/phar/util.c | 51 ++++++++++++++---- 5 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 ext/phar/tests/tar/files/circular_symlinks.tar create mode 100644 ext/phar/tests/tar/files/circular_symlinks_long.tar create mode 100644 ext/phar/tests/tar/files/circular_symlinks_rho.tar create mode 100644 ext/phar/tests/tar/ghsa-vc5h-9ppw-p5f3-symlink-circular.phpt diff --git a/ext/phar/tests/tar/files/circular_symlinks.tar b/ext/phar/tests/tar/files/circular_symlinks.tar new file mode 100644 index 0000000000000000000000000000000000000000..2af7bb8c2384bb98ddeaaa31211dfb8b3850fc42 GIT binary patch literal 10240 zcmeIwK?;K~5Cza2#Un%sy-u;9Ah@db{w8!IZVH8n;G4~aERxTg8K-%8_MvLUT#uAO z>A8-5fA>ANu}d*D-B`lCs%8tHW$oWNQr%xJ`}!HgWvKtpzw&b4eEyeROu+~cAV7cs n0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U_$jaf78oJV literal 0 HcmV?d00001 diff --git a/ext/phar/tests/tar/files/circular_symlinks_long.tar b/ext/phar/tests/tar/files/circular_symlinks_long.tar new file mode 100644 index 0000000000000000000000000000000000000000..b2b4c1cb095c13d455f191f74e6598859cbe6a4e GIT binary patch literal 215040 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 Heju;`KgL6^ literal 0 HcmV?d00001 diff --git a/ext/phar/tests/tar/ghsa-vc5h-9ppw-p5f3-symlink-circular.phpt b/ext/phar/tests/tar/ghsa-vc5h-9ppw-p5f3-symlink-circular.phpt new file mode 100644 index 000000000000..cf0048c0fcd9 --- /dev/null +++ b/ext/phar/tests/tar/ghsa-vc5h-9ppw-p5f3-symlink-circular.phpt @@ -0,0 +1,27 @@ +--TEST-- +GHSA-vc5h-9ppw-p5f3 (circular symlinks in tar should not cause stack overflow) +--CREDITS-- +Calvin Young - eWalker Consulting (HK) Limited +Enoch Chow - Isomorph Cyber +--EXTENSIONS-- +phar +--FILE-- +getContent() === ''); + +// rho-shaped cycle (tail leading into a loop) +$phar = new PharData($base . '/files/circular_symlinks_rho.tar'); +var_dump($phar['file_a']->getContent() === ''); + +// long cycle (400 entries) +$phar = new PharData($base . '/files/circular_symlinks_long.tar'); +var_dump($phar['link_0']->getContent() === ''); +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) diff --git a/ext/phar/util.c b/ext/phar/util.c index 352819bc9348..e11d0ceb5142 100644 --- a/ext/phar/util.c +++ b/ext/phar/util.c @@ -57,30 +57,59 @@ static char *phar_get_link_location(phar_entry_info *entry) /* {{{ */ } /* }}} */ -phar_entry_info *phar_get_link_source(phar_entry_info *entry) /* {{{ */ +static phar_entry_info *phar_follow_one_link(phar_entry_info *entry) { phar_entry_info *link_entry; char *link; - if (!entry->link) { - return entry; - } - link = phar_get_link_location(entry); if (NULL != (link_entry = zend_hash_str_find_ptr(&(entry->phar->manifest), entry->link, strlen(entry->link))) || NULL != (link_entry = zend_hash_str_find_ptr(&(entry->phar->manifest), link, strlen(link)))) { if (link != entry->link) { efree(link); } - return phar_get_link_source(link_entry); - } else { - if (link != entry->link) { - efree(link); + return link_entry; + } + + if (link != entry->link) { + efree(link); + } + return NULL; +} + +phar_entry_info *phar_get_link_source(phar_entry_info *entry) +{ + phar_entry_info *slow, *fast; + + if (!entry->link) { + return entry; + } + + /* + * Use Floyd's cycle detection algorithm to follow the symlink chain without unbounded + * recursion. Each entry has at most one outgoing link, so if a cycle exists the fast pointer + * will eventually meet the slow one. Otherwise the fast pointer reaches the end first. + */ + slow = fast = entry; + while (1) { + fast = phar_follow_one_link(fast); + if (!fast || !fast->link) { + return fast; + } + fast = phar_follow_one_link(fast); + if (!fast || !fast->link) { + return fast; + } + + /* no need to check slow as it's always behind */ + slow = phar_follow_one_link(slow); + + if (slow == fast) { + /* circular symlink chain */ + return NULL; } - return NULL; } } -/* }}} */ /* retrieve a phar_entry_info's current file pointer for reading contents */ php_stream *phar_get_efp(phar_entry_info *entry, int follow_links) /* {{{ */ From 4fc487f6d9c6876bdc328914d7887d933eee3d84 Mon Sep 17 00:00:00 2001 From: Marc Date: Tue, 28 Jul 2026 16:27:20 +0700 Subject: [PATCH 05/12] Make php-cli functionality available in embed build (#21385) --- NEWS | 3 +++ UPGRADING.INTERNALS | 8 ++++++++ sapi/cli/cli.h | 3 +++ sapi/cli/cli_win32.c | 5 +++++ sapi/cli/config.m4 | 15 ++++++++++++++- sapi/cli/config.w32 | 7 ++++++- sapi/cli/php_cli.c | 14 +++----------- sapi/cli/php_cli_main.c | 19 +++++++++++++++++++ sapi/embed/config.m4 | 10 +++------- sapi/embed/config.w32 | 3 +++ sapi/embed/config0.m4 | 6 ++++++ sapi/embed/php_embed.c | 4 ---- 12 files changed, 73 insertions(+), 24 deletions(-) create mode 100644 sapi/cli/php_cli_main.c create mode 100644 sapi/embed/config0.m4 diff --git a/NEWS b/NEWS index a7aa29cd9043..48864929c225 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,9 @@ PHP NEWS . Fixed bug GH-22825 (DOMElement::setAttribute() fails silently when the DTD declares a default value for the attribute). (iliaal) +- Embed: + . Made php-cli functionality available in embed builds. (henderkes) + - GMP: . Added gmp_prevprime(). (Weilin Du, David Carlier) . Fixed GMP power and shift operators to reject GMP right operands outside diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index 39f5d72e2701..8b4b5912317a 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -191,6 +191,7 @@ PHP 8.6 INTERNALS UPGRADE NOTES strings from one or more buffers. . Added zend_string_ends_with() and related variants. . Added trait support for internal classes. + . Added do_php_cli(). ======================== 2. Build system changes @@ -227,6 +228,10 @@ PHP 8.6 INTERNALS UPGRADE NOTES . Added a new function CHECK_HEADER() which is intended to be used instead of the CHECK_HEADER_ADD_INCLUDE(). +- Embed: + . The CLI SAPI can not be disabled when building the embed SAPI + (--enable-embed is incompatible with --disable-cli). + ======================== 3. Module changes ======================== @@ -307,3 +312,6 @@ PHP 8.6 INTERNALS UPGRADE NOTES - AG and SCNG are now allocated with ts_allocate_tls_id() and live in native __thread storage on ZTS builds. + +- php-cli functionality is now available in embed builds via the do_php_cli() + function. diff --git a/sapi/cli/cli.h b/sapi/cli/cli.h index 2f6ef95052ce..9502a6726fe4 100644 --- a/sapi/cli/cli.h +++ b/sapi/cli/cli.h @@ -54,4 +54,7 @@ typedef struct php_cli_server_context { php_cli_mode mode; } php_cli_server_context; +/* this performs full cli-SAPI boot, loads modules, sets up TSRM and co. */ +extern PHP_CLI_API int do_php_cli(int argc, char *argv[]); + #endif /* CLI_H */ diff --git a/sapi/cli/cli_win32.c b/sapi/cli/cli_win32.c index 4407fd088f68..de7d0a0282fd 100644 --- a/sapi/cli/cli_win32.c +++ b/sapi/cli/cli_win32.c @@ -1,2 +1,7 @@ #define PHP_CLI_WIN32_NO_CONSOLE 1 #include "php_cli.c" + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) +{ + return do_php_cli(__argc, __argv); +} diff --git a/sapi/cli/config.m4 b/sapi/cli/config.m4 index 76c2d64e8c8a..6abaa88df419 100644 --- a/sapi/cli/config.m4 +++ b/sapi/cli/config.m4 @@ -5,6 +5,11 @@ PHP_ARG_ENABLE([cli], [yes], [no]) +dnl The embed SAPI requires the CLI sources for do_php_cli(). +if test "$PHP_EMBED" != "no" -a "$PHP_CLI" = "no"; then + AC_MSG_ERROR([--enable-embed requires the CLI SAPI, do not use --disable-cli with --enable-embed]) +fi + if test "$PHP_CLI" != "no"; then AC_CHECK_FUNCS([setproctitle]) @@ -32,9 +37,17 @@ if test "$PHP_CLI" != "no"; then dnl Select SAPI. PHP_SELECT_SAPI([cli], [program], - [php_cli.c php_http_parser.c php_cli_server.c ps_title.c php_cli_process_title.c], + [php_cli_main.c], [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1]) + dnl Everything except the main() entry point, so that the embed SAPI can link + dnl the same objects into libphp for do_php_cli(). + PHP_ADD_SOURCES_X([sapi/cli], + [php_cli.c php_http_parser.c php_cli_server.c ps_title.c php_cli_process_title.c], + [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1], + [PHP_CLI_SHARED_OBJS]) + PHP_CLI_OBJS="$PHP_CLI_OBJS $PHP_CLI_SHARED_OBJS" + AS_CASE([$host_alias], [*aix*], [ AS_VAR_IF([php_sapi_module], [shared], [ diff --git a/sapi/cli/config.w32 b/sapi/cli/config.w32 index 5dff4054b69d..695a5b46ec10 100644 --- a/sapi/cli/config.w32 +++ b/sapi/cli/config.w32 @@ -3,8 +3,13 @@ ARG_ENABLE('cli', 'Build CLI version of PHP', 'yes'); ARG_ENABLE('cli-win32', 'Build console-less CLI version of PHP', 'no'); +// The embed SAPI requires the CLI sources for do_php_cli(). +if (PHP_EMBED != "no" && PHP_CLI != "yes") { + ERROR("--enable-embed requires the CLI SAPI, do not use --disable-cli with --enable-embed"); +} + if (PHP_CLI == "yes") { - SAPI('cli', 'php_cli.c php_http_parser.c php_cli_server.c', 'php.exe', '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1'); + SAPI('cli', 'php_cli.c php_cli_main.c php_http_parser.c php_cli_server.c', 'php.exe', '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1'); ADD_SOURCES(configure_module_dirname, 'php_cli_process_title.c ps_title.c', 'cli'); ADD_FLAG("LIBS_CLI", "ws2_32.lib"); ADD_FLAG("LIBS_CLI", "shell32.lib"); diff --git a/sapi/cli/php_cli.c b/sapi/cli/php_cli.c index 9095fb10dd41..296b7cd521ff 100644 --- a/sapi/cli/php_cli.c +++ b/sapi/cli/php_cli.c @@ -1173,18 +1173,10 @@ static int do_cli(int argc, char **argv) /* {{{ */ } /* }}} */ -/* {{{ main */ -#ifdef PHP_CLI_WIN32_NO_CONSOLE -int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) -#else -int main(int argc, char *argv[]) -#endif +/* {{{ do_php_cli */ +PHP_CLI_API int do_php_cli(int argc, char *argv[]) { #if defined(PHP_WIN32) -# ifdef PHP_CLI_WIN32_NO_CONSOLE - int argc = __argc; - char **argv = __argv; -# endif int num_args; wchar_t **argv_wide; char **argv_save = argv; @@ -1387,6 +1379,6 @@ int main(int argc, char *argv[]) * exiting. */ cleanup_ps_args(argv); - exit(exit_status); + return exit_status; } /* }}} */ diff --git a/sapi/cli/php_cli_main.c b/sapi/cli/php_cli_main.c new file mode 100644 index 000000000000..1030b5feb923 --- /dev/null +++ b/sapi/cli/php_cli_main.c @@ -0,0 +1,19 @@ +/* + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ +*/ + +#include "php.h" +#include "cli.h" + +int main(int argc, char *argv[]) +{ + return do_php_cli(argc, argv); +} diff --git a/sapi/embed/config.m4 b/sapi/embed/config.m4 index 2bb5ed4df4da..e0051ecf66d1 100644 --- a/sapi/embed/config.m4 +++ b/sapi/embed/config.m4 @@ -1,10 +1,3 @@ -PHP_ARG_ENABLE([embed],, - [AS_HELP_STRING([[--enable-embed[=TYPE]]], - [Enable building of embedded SAPI library TYPE is either - 'shared' or 'static'. [TYPE=shared]])], - [no], - [no]) - AC_MSG_CHECKING([for embedded SAPI library support]) if test "$PHP_EMBED" != "no"; then @@ -33,6 +26,9 @@ if test "$PHP_EMBED" != "no"; then [php_embed.c], [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1]) PHP_INSTALL_HEADERS([sapi/embed], [php_embed.h]) + + dnl Link the CLI objects into libphp for do_php_cli(). + PHP_SAPI_OBJS="$PHP_SAPI_OBJS $PHP_CLI_SHARED_OBJS" ]) else AC_MSG_RESULT([no]) diff --git a/sapi/embed/config.w32 b/sapi/embed/config.w32 index 394982126cad..3ac4668bce8c 100644 --- a/sapi/embed/config.w32 +++ b/sapi/embed/config.w32 @@ -6,5 +6,8 @@ var PHP_EMBED_PGO = false; if (PHP_EMBED != "no") { SAPI('embed', 'php_embed.c', 'php' + PHP_VERSION + 'embed.lib', '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1'); + ADD_SOURCES("sapi/cli", "php_cli.c php_http_parser.c php_cli_server.c ps_title.c php_cli_process_title.c", "embed", undefined, true); + ADD_FLAG("LIBS_EMBED", "ws2_32.lib"); + ADD_FLAG("LIBS_EMBED", "shell32.lib"); PHP_INSTALL_HEADERS("sapi/embed", "php_embed.h"); } diff --git a/sapi/embed/config0.m4 b/sapi/embed/config0.m4 new file mode 100644 index 000000000000..f1c05b8ea01a --- /dev/null +++ b/sapi/embed/config0.m4 @@ -0,0 +1,6 @@ +PHP_ARG_ENABLE([embed],, + [AS_HELP_STRING([[--enable-embed[=TYPE]]], + [Enable building of embedded SAPI library TYPE is either + 'shared' or 'static'. [TYPE=shared]])], + [no], + [no]) diff --git a/sapi/embed/php_embed.c b/sapi/embed/php_embed.c index 75249f97fc87..96534c18a698 100644 --- a/sapi/embed/php_embed.c +++ b/sapi/embed/php_embed.c @@ -28,10 +28,6 @@ static const char HARDCODED_INI[] = "max_execution_time=0\n" "max_input_time=-1\n\0"; -#if defined(PHP_WIN32) && defined(ZTS) -ZEND_TSRMLS_CACHE_DEFINE() -#endif - static char* php_embed_read_cookies(void) { return NULL; From f93c48725d19803eb628d0024b90be33446a8e6c Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 28 Jul 2026 11:25:11 +0200 Subject: [PATCH 06/12] Add NEWS entries --- NEWS | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/NEWS b/NEWS index e9e5b76cea69..c19ccfdc58fe 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,17 @@ PHP NEWS - Date: . Fixed leak on double DatePeriod::__construct() call. (ilutov) +- GD: + . Upgrade libgd. (CVE-2026-9672) (Pierre Joye) + +- PGSQL: + . Fixed GHSA-7qpv-r5mr-78m4 (SQL injection via E'...' backslash breakout). + (CVE-2026-17543) (ilutov) + +- Phar: + . Fixed GHSA-vc5h-9ppw-p5f3 (Crash via recursive symlinks). (CVE-2026-7260) + (Jakub Zelenka) + 02 Jul 2026, PHP 8.2.32 - OpenSSL: From 4f83876af75d43b24cf3ed567394451f42e6bca1 Mon Sep 17 00:00:00 2001 From: "Recep A." <44113214+recepasan@users.noreply.github.com> Date: Wed, 27 May 2026 21:48:08 +0300 Subject: [PATCH 07/12] Fix out-of-bounds write in ext-bcmath bccomp() via bc_str2num() When a fraction is truncated to a caller-supplied scale and then re-trimmed of trailing zeros, str_scale was reduced but fractional_end was not, so bc_copy_and_toggle_bcd() copied more bytes than were reserved by bc_new_num_nonzeroed(). With small numbers allocated from the 256-byte stack arena this is a stack-based OOB write reachable via bccomp($num1, $num2, $scale), e.g. bccomp("1.901", "0", 2). Keep fractional_end in sync with str_scale so the copy length matches the reserved length. Fixes GHSA-x692-q9x7-8c3f --- ext/bcmath/libbcmath/src/str2num.c | 1 + ext/bcmath/tests/GHSA-x692-q9x7-8c3f.phpt | 13 +++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 ext/bcmath/tests/GHSA-x692-q9x7-8c3f.phpt diff --git a/ext/bcmath/libbcmath/src/str2num.c b/ext/bcmath/libbcmath/src/str2num.c index bd9a44a24050..3b99065b43fb 100644 --- a/ext/bcmath/libbcmath/src/str2num.c +++ b/ext/bcmath/libbcmath/src/str2num.c @@ -181,6 +181,7 @@ bool bc_str2num(bc_num *num, const char *str, const char *end, size_t scale, siz if (str_scale > 0) { const char *fractional_new_end = bc_skip_zero_reverse(fractional_end, fractional_ptr); str_scale -= fractional_end - fractional_new_end; /* fractional_end >= fractional_new_end */ + fractional_end = fractional_new_end; } } } else { diff --git a/ext/bcmath/tests/GHSA-x692-q9x7-8c3f.phpt b/ext/bcmath/tests/GHSA-x692-q9x7-8c3f.phpt new file mode 100644 index 000000000000..b735958bd07a --- /dev/null +++ b/ext/bcmath/tests/GHSA-x692-q9x7-8c3f.phpt @@ -0,0 +1,13 @@ +--TEST-- +GHSA-x692-q9x7-8c3f: bccomp() out-of-bounds write +--CREDITS-- +Recep Asan (recepasan) +--FILE-- + +--EXPECT-- +int(1) From 56b8d1040d83a4731e7b286b890cffef559de87b Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 28 Jul 2026 12:04:27 +0200 Subject: [PATCH 08/12] Fix string freeing in phar_follow_one_link() Faulty merge on my part. --- ext/phar/util.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/ext/phar/util.c b/ext/phar/util.c index 37373c26bfac..b93bf6940dea 100644 --- a/ext/phar/util.c +++ b/ext/phar/util.c @@ -71,15 +71,11 @@ static phar_entry_info *phar_follow_one_link(phar_entry_info *entry) link = phar_get_link_location(entry); if (NULL != (link_entry = zend_hash_find_ptr(&(entry->phar->manifest), entry->symlink)) || NULL != (link_entry = zend_hash_find_ptr(&(entry->phar->manifest), link))) { - if (link != entry->symlink) { - efree(link); - } + zend_string_release(link); return link_entry; } - if (link != entry->symlink) { - efree(link); - } + zend_string_release(link); return NULL; } From f6d2a834af763152bc0b6c86c8fa4dcc5e3b0636 Mon Sep 17 00:00:00 2001 From: Sergey Panteleev Date: Tue, 28 Jul 2026 13:26:34 +0300 Subject: [PATCH 09/12] PHP-8.2 is now for PHP 8.2.34-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 c19ccfdc58fe..a22154027d39 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.2.33 +?? ??? ????, PHP 8.2.34 + + +30 Jul 2026, PHP 8.2.33 - Date: . Fixed leak on double DatePeriod::__construct() call. (ilutov) diff --git a/Zend/zend.h b/Zend/zend.h index 029a6ecf33bd..4c9d67d54877 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.2.33-dev" +#define ZEND_VERSION "4.2.34-dev" #define ZEND_ENGINE_3 diff --git a/configure.ac b/configure.ac index 95e2f680dfab..157402e8a467 100644 --- a/configure.ac +++ b/configure.ac @@ -17,7 +17,7 @@ dnl Basic autoconf initialization, generation of config.nice. dnl ---------------------------------------------------------------------------- AC_PREREQ([2.68]) -AC_INIT([PHP],[8.2.33-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.2.34-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 f56fd7802d2b..0109d8aa77a3 100644 --- a/main/php_version.h +++ b/main/php_version.h @@ -2,7 +2,7 @@ /* edit configure.ac to change version number */ #define PHP_MAJOR_VERSION 8 #define PHP_MINOR_VERSION 2 -#define PHP_RELEASE_VERSION 33 +#define PHP_RELEASE_VERSION 34 #define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.2.33-dev" -#define PHP_VERSION_ID 80233 +#define PHP_VERSION "8.2.34-dev" +#define PHP_VERSION_ID 80234 From c290dfdb0dbc71845028ffdd4dfadac67d036e0f Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Tue, 28 Jul 2026 13:44:00 +0100 Subject: [PATCH 10/12] streams: various small refactoring for stream error API (#22847) - Use const when possible - Use bool or size_t type rather than int type --- main/streams/php_stream_errors.h | 21 +++++----- main/streams/stream_errors.c | 68 ++++++++++++++++---------------- 2 files changed, 46 insertions(+), 43 deletions(-) diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index 878350f972ac..da7a4cc84e9d 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -93,8 +93,8 @@ typedef struct { /* Error operation management */ PHPAPI php_stream_error_operation *php_stream_error_operation_begin(void); -PHPAPI void php_stream_error_operation_end(php_stream_context *context); -PHPAPI void php_stream_error_operation_end_for_stream(php_stream *stream); +PHPAPI void php_stream_error_operation_end(const php_stream_context *context); +PHPAPI void php_stream_error_operation_end_for_stream(const php_stream *stream); PHPAPI void php_stream_error_operation_abort(void); /* State cleanup function */ @@ -108,35 +108,36 @@ PHPAPI void php_stream_error_clear_stored(void); /* Wrapper error reporting functions */ PHPAPI void php_stream_wrapper_error_with_name(const char *wrapper_name, - php_stream_context *context, const char *docref, int options, int severity, + const php_stream_context *context, const char *docref, int options, int severity, bool terminating, zend_enum_StreamErrorCode code, const char *fmt, ...) ZEND_ATTRIBUTE_FORMAT(printf, 8, 9); -PHPAPI void php_stream_wrapper_error(php_stream_wrapper *wrapper, php_stream_context *context, +PHPAPI void php_stream_wrapper_error( + const php_stream_wrapper *wrapper, const php_stream_context *context, const char *docref, int options, int severity, bool terminating, zend_enum_StreamErrorCode code, const char *fmt, ...) ZEND_ATTRIBUTE_FORMAT(printf, 8, 9); -PHPAPI void php_stream_error(php_stream *stream, const char *docref, int severity, +PHPAPI void php_stream_error(const php_stream *stream, const char *docref, int severity, bool terminating, zend_enum_StreamErrorCode code, const char *fmt, ...) ZEND_ATTRIBUTE_FORMAT(printf, 6, 7); /* Legacy wrapper error log functions */ PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper, - php_stream_context *context, int options, int severity, bool terminating, + const php_stream_context *context, int options, int severity, bool terminating, zend_enum_StreamErrorCode code, const char *fmt, ...) ZEND_ATTRIBUTE_FORMAT(printf, 7, 8); PHPAPI void php_stream_display_wrapper_name_errors(const char *wrapper_name, - php_stream_context *context, zend_enum_StreamErrorCode code, + const php_stream_context *context, zend_enum_StreamErrorCode code, const char *caption); -PHPAPI void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, - php_stream_context *context, zend_enum_StreamErrorCode code, +PHPAPI void php_stream_display_wrapper_errors(const php_stream_wrapper *wrapper, + const php_stream_context *context, zend_enum_StreamErrorCode code, const char *caption); PHPAPI void php_stream_tidy_wrapper_name_error_log(const char *wrapper_name); -PHPAPI void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper); +PHPAPI void php_stream_tidy_wrapper_error_log(const php_stream_wrapper *wrapper); /* Convenience macros - code argument is the bare case name (e.g. RenameFailed) */ #define php_stream_wrapper_warn(wrapper, context, options, code, ...) \ diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index 40056f0856e7..4e59e275cf9a 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -33,7 +33,7 @@ static zend_class_entry *php_ce_stream_exception; static void php_stream_error_entry_free(php_stream_error_entry *entry); /* Helper to create a single StreamError object from an entry */ -static void php_stream_error_create_object(zval *zv, php_stream_error_entry *entry) +static void php_stream_error_create_object(zval *zv, const php_stream_error_entry *entry) { object_init_ex(zv, php_ce_stream_error); @@ -69,11 +69,11 @@ static void php_stream_error_create_object(zval *zv, php_stream_error_entry *ent } /* Create array of StreamError objects from error chain */ -static void php_stream_error_create_array(zval *zv, php_stream_error_entry *first) +static void php_stream_error_create_array(zval *zv, const php_stream_error_entry *first) { array_init(zv); - php_stream_error_entry *entry = first; + const php_stream_error_entry *entry = first; while (entry) { zval error_obj; php_stream_error_create_object(&error_obj, entry); @@ -113,7 +113,7 @@ static php_stream_error_store php_stream_auto_decide_error_store_mode(php_stream } } -static php_stream_error_mode php_stream_get_error_mode(php_stream_context *context) +static php_stream_error_mode php_stream_get_error_mode(const php_stream_context *context) { if (!context) { return PHP_STREAM_ERROR_MODE_ERROR; @@ -143,7 +143,7 @@ static php_stream_error_mode php_stream_get_error_mode(php_stream_context *conte } static php_stream_error_store php_stream_get_error_store_mode( - php_stream_context *context, php_stream_error_mode error_mode) + const php_stream_context *context, php_stream_error_mode error_mode) { if (!context) { return php_stream_auto_decide_error_store_mode(error_mode); @@ -178,9 +178,9 @@ static php_stream_error_store php_stream_get_error_store_mode( /* Helper functions */ -static bool php_stream_has_terminating_error(php_stream_error_operation *op) +static bool php_stream_has_terminating_error(const php_stream_error_operation *op) { - php_stream_error_entry *entry = op->first_error; + const php_stream_error_entry *entry = op->first_error; while (entry) { if (entry->terminating) { return true; @@ -205,7 +205,7 @@ static inline php_stream_error_operation *php_stream_get_operation_at_depth(uint static inline php_stream_error_operation *php_stream_get_parent_operation(void) { - php_stream_error_state *state = &FG(stream_error_state); + const php_stream_error_state *state = &FG(stream_error_state); if (state->operation_depth <= 1) { return NULL; @@ -265,7 +265,7 @@ PHPAPI void php_stream_error_state_cleanup(void) PHPAPI void php_stream_error_get_last(zval *return_value) { - php_stream_error_state *state = &FG(stream_error_state); + const php_stream_error_state *state = &FG(stream_error_state); if (!state->stored_errors) { ZVAL_EMPTY_ARRAY(return_value); @@ -359,7 +359,7 @@ static void php_stream_error_add(zend_enum_StreamErrorCode code, const char *wra /* Error reporting */ -static void php_stream_call_error_handler(zval *handler, zval *errors_array) +static void php_stream_call_error_handler(const zval *handler, zval *errors_array) { zend_fcall_info_cache fcc; char *is_callable_error = NULL; @@ -375,7 +375,7 @@ static void php_stream_call_error_handler(zval *handler, zval *errors_array) zend_call_known_fcc(&fcc, NULL, 1, errors_array, NULL); } -static void php_stream_throw_exception_with_errors(php_stream_error_operation *op) +static void php_stream_throw_exception_with_errors(const php_stream_error_operation *op) { if (!op->first_error) { return; @@ -401,7 +401,7 @@ static void php_stream_throw_exception_with_errors(php_stream_error_operation *o zend_throw_exception_object(&ex); } -static void php_stream_report_errors(php_stream_context *context, php_stream_error_operation *op, +static void php_stream_report_errors(const php_stream_context *context, const php_stream_error_operation *op, php_stream_error_mode error_mode, bool is_terminating) { switch (error_mode) { @@ -426,7 +426,7 @@ static void php_stream_report_errors(php_stream_context *context, php_stream_err } /* Call user error handler if set */ - zval *handler + const zval *handler = context ? php_stream_context_get_option(context, "stream", "error_handler") : NULL; if (handler) { @@ -441,7 +441,7 @@ static void php_stream_report_errors(php_stream_context *context, php_stream_err /* Error storage */ -PHPAPI void php_stream_error_operation_end(php_stream_context *context) +PHPAPI void php_stream_error_operation_end(const php_stream_context *context) { php_stream_error_state *state = &FG(stream_error_state); php_stream_error_operation *op = state->current_operation; @@ -533,7 +533,7 @@ PHPAPI void php_stream_error_operation_end(php_stream_context *context) op->error_count = 0; } -PHPAPI void php_stream_error_operation_end_for_stream(php_stream *stream) +PHPAPI void php_stream_error_operation_end_for_stream(const php_stream *stream) { php_stream_error_state *state = &FG(stream_error_state); php_stream_error_operation *op = state->current_operation; @@ -551,7 +551,7 @@ PHPAPI void php_stream_error_operation_end_for_stream(php_stream *stream) return; } - php_stream_context *context = PHP_STREAM_CONTEXT(stream); + const php_stream_context *context = PHP_STREAM_CONTEXT(stream); php_stream_error_operation_end(context); } @@ -575,7 +575,7 @@ PHPAPI void php_stream_error_operation_abort(void) /* Wrapper error reporting */ -static void php_stream_wrapper_error_internal(const char *wrapper_name, php_stream_context *context, +static void php_stream_wrapper_error_internal(const char *wrapper_name, const php_stream_context *context, const char *docref, int severity, bool terminating, zend_enum_StreamErrorCode code, zend_string *message) { @@ -592,7 +592,7 @@ static void php_stream_wrapper_error_internal(const char *wrapper_name, php_stre } PHPAPI void php_stream_wrapper_error_with_name(const char *wrapper_name, - php_stream_context *context, const char *docref, int options, int severity, + const php_stream_context *context, const char *docref, int options, int severity, bool terminating, zend_enum_StreamErrorCode code, const char *fmt, ...) { if (!(options & REPORT_ERRORS)) { @@ -608,7 +608,9 @@ PHPAPI void php_stream_wrapper_error_with_name(const char *wrapper_name, wrapper_name, context, docref, severity, terminating, code, message); } -PHPAPI void php_stream_wrapper_error(php_stream_wrapper *wrapper, php_stream_context *context, +PHPAPI void php_stream_wrapper_error( + const php_stream_wrapper *wrapper, + const php_stream_context *context, const char *docref, int options, int severity, bool terminating, zend_enum_StreamErrorCode code, const char *fmt, ...) { @@ -629,7 +631,7 @@ PHPAPI void php_stream_wrapper_error(php_stream_wrapper *wrapper, php_stream_con /* Stream error reporting */ -PHPAPI void php_stream_error(php_stream *stream, const char *docref, int severity, +PHPAPI void php_stream_error(const php_stream *stream, const char *docref, int severity, bool terminating, zend_enum_StreamErrorCode code, const char *fmt, ...) { va_list args; @@ -640,7 +642,7 @@ PHPAPI void php_stream_error(php_stream *stream, const char *docref, int severit const char *wrapper_name = stream->wrapper ? stream->wrapper->wops->label : "stream"; - php_stream_context *context = PHP_STREAM_CONTEXT(stream); + const php_stream_context *context = PHP_STREAM_CONTEXT(stream); php_stream_wrapper_error_internal(wrapper_name, context, docref, severity, terminating, code, message); @@ -694,7 +696,7 @@ static void php_stream_wrapper_log_store_error(zend_string *message, zend_enum_S } PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper, - php_stream_context *context, int options, int severity, bool terminating, + const php_stream_context *context, int options, int severity, bool terminating, zend_enum_StreamErrorCode code, const char *fmt, ...) { va_list args; @@ -722,12 +724,12 @@ static zend_llist *php_stream_get_wrapper_errors_list(const char *wrapper_name) } PHPAPI void php_stream_display_wrapper_name_errors(const char *wrapper_name, - php_stream_context *context, zend_enum_StreamErrorCode code, + const php_stream_context *context, zend_enum_StreamErrorCode code, const char *caption) { char *msg; char errstr[256]; - int free_msg = 0; + bool free_msg = false; if (EG(exception)) { return; @@ -737,9 +739,7 @@ PHPAPI void php_stream_display_wrapper_name_errors(const char *wrapper_name, zend_llist *err_list = php_stream_get_wrapper_errors_list(wrapper_name); if (err_list) { size_t l = 0; - int brlen; - int i; - int count = (int) zend_llist_count(err_list); + size_t brlen; const char *br; php_stream_error_entry **err_entry_p; zend_llist_position pos; @@ -752,7 +752,9 @@ PHPAPI void php_stream_display_wrapper_name_errors(const char *wrapper_name, br = "\n"; } - for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p; + size_t i = 0; + const size_t count = zend_llist_count(err_list); + for (i = 0, err_entry_p = zend_llist_get_first_ex(err_list, &pos); err_entry_p; err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) { l += ZSTR_LEN((*err_entry_p)->message); if (i < count - 1) { @@ -769,7 +771,7 @@ PHPAPI void php_stream_display_wrapper_name_errors(const char *wrapper_name, } } - free_msg = 1; + free_msg = true; } else { if (!strcmp(wrapper_name, php_plain_files_wrapper.wops->label)) { msg = php_socket_strerror_s(errno, errstr, sizeof(errstr)); @@ -791,8 +793,8 @@ PHPAPI void php_stream_display_wrapper_name_errors(const char *wrapper_name, } } -PHPAPI void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, - php_stream_context *context, zend_enum_StreamErrorCode code, +PHPAPI void php_stream_display_wrapper_errors(const php_stream_wrapper *wrapper, + const php_stream_context *context, zend_enum_StreamErrorCode code, const char *caption) { if (wrapper) { @@ -808,7 +810,7 @@ PHPAPI void php_stream_tidy_wrapper_name_error_log(const char *wrapper_name) } } -PHPAPI void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper) +PHPAPI void php_stream_tidy_wrapper_error_log(const php_stream_wrapper *wrapper) { if (wrapper) { const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); @@ -822,7 +824,7 @@ PHP_METHOD(StreamException, getErrors) { ZEND_PARSE_PARAMETERS_NONE(); - zval *errors = zend_read_property( + const zval *errors = zend_read_property( php_ce_stream_exception, Z_OBJ_P(ZEND_THIS), ZEND_STRL("errors"), 1, NULL); RETURN_COPY(errors); From 081106ea44f9e93df66291191d58c30828bea7aa Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Tue, 28 Jul 2026 21:12:10 +0800 Subject: [PATCH 11/12] ext/gmp: Add gmp_powm_sec() (#22852) Expose mpz_powm_sec() to the userland, namely gmp_powm_sec(). --- NEWS | 1 + UPGRADING | 3 +++ ext/gmp/config.m4 | 2 +- ext/gmp/config.w32 | 3 +++ ext/gmp/gmp.c | 33 +++++++++++++++++++++++ ext/gmp/gmp.stub.php | 4 +++ ext/gmp/gmp_arginfo.h | 16 +++++++++++- ext/gmp/tests/bug80560.phpt | 3 +++ ext/gmp/tests/gmp_powm_sec.phpt | 46 +++++++++++++++++++++++++++++++++ 9 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 ext/gmp/tests/gmp_powm_sec.phpt diff --git a/NEWS b/NEWS index 48864929c225..65bd87faba82 100644 --- a/NEWS +++ b/NEWS @@ -20,6 +20,7 @@ PHP NEWS . Made php-cli functionality available in embed builds. (henderkes) - GMP: + . Added gmp_powm_sec(). (Weilin Du) . Added gmp_prevprime(). (Weilin Du, David Carlier) . Fixed GMP power and shift operators to reject GMP right operands outside the unsigned long range instead of silently truncating them. (Weilin Du) diff --git a/UPGRADING b/UPGRADING index 3d9cfdc6aeb9..7c9473d07b7d 100644 --- a/UPGRADING +++ b/UPGRADING @@ -272,6 +272,8 @@ PHP 8.6 UPGRADE NOTES . finfo_file() now works with remote streams. - GMP: + . Added gmp_powm_sec() for side-channel quiet modular exponentiation. + Requires GNU MP 5.0.0 or later. . Added gmp_prevprime() to get the largest prime smaller than the given number. A ValueError is thrown if no such prime exists. This function is available only when PHP is built against GNU MP 6.3.0 or later; it is not @@ -428,6 +430,7 @@ PHP 8.6 UPGRADE NOTES ======================================== - GMP: + . gmp_powm_sec() . gmp_prevprime() - Intl: diff --git a/ext/gmp/config.m4 b/ext/gmp/config.m4 index c4ffc2d42d4d..f6f2a4a0e9a3 100644 --- a/ext/gmp/config.m4 +++ b/ext/gmp/config.m4 @@ -22,7 +22,7 @@ if test "$PHP_GMP" != "no"; then LIBS="$LIBS $GMP_LIBS" gmp_check=no AC_CHECK_HEADER([gmp.h], [AC_CHECK_FUNC([__gmpz_rootrem], [gmp_check=yes])]) - AC_CHECK_FUNCS([__gmpz_prevprime]) + AC_CHECK_FUNCS([__gmpz_powm_sec __gmpz_prevprime]) CFLAGS=$CFLAGS_SAVED LIBS=$LIBS_SAVED diff --git a/ext/gmp/config.w32 b/ext/gmp/config.w32 index d83048ebe9c6..fc052440417c 100644 --- a/ext/gmp/config.w32 +++ b/ext/gmp/config.w32 @@ -5,6 +5,9 @@ ARG_WITH("gmp", "Include GNU MP support.", "no"); if (PHP_GMP != "no") { if (CHECK_LIB("mpir_a.lib", "gmp", PHP_GMP) && CHECK_HEADER("gmp.h", "CFLAGS_GMP", PHP_GMP + ";" + PHP_PHP_BUILD + "\\include\\mpir")) { + if (GREP_HEADER("gmp.h", "mpz_powm_sec", PHP_GMP + ";" + PHP_PHP_BUILD + "\\include\\mpir")) { + AC_DEFINE('HAVE___GMPZ_POWM_SEC', 1, "Define to 1 if GMP has the 'mpz_powm_sec' function."); + } if (GREP_HEADER("gmp.h", "mpz_prevprime", PHP_GMP + ";" + PHP_PHP_BUILD + "\\include\\mpir")) { AC_DEFINE('HAVE___GMPZ_PREVPRIME', 1, "Define to 1 if GMP has the 'mpz_prevprime' function."); } diff --git a/ext/gmp/gmp.c b/ext/gmp/gmp.c index 9ade8eade215..bf0a77666b6f 100644 --- a/ext/gmp/gmp.c +++ b/ext/gmp/gmp.c @@ -1217,6 +1217,39 @@ ZEND_FUNCTION(gmp_powm) } /* }}} */ +#ifdef HAVE___GMPZ_POWM_SEC +/* {{{ Raise base to power exp and take result modulo mod using a side-channel quiet algorithm */ +ZEND_FUNCTION(gmp_powm_sec) +{ + mpz_ptr gmpnum_base, gmpnum_exp, gmpnum_mod, gmpnum_result; + + ZEND_PARSE_PARAMETERS_START(3, 3) + GMP_Z_PARAM_INTO_MPZ_PTR(gmpnum_base) + GMP_Z_PARAM_INTO_MPZ_PTR(gmpnum_exp) + GMP_Z_PARAM_INTO_MPZ_PTR(gmpnum_mod) + ZEND_PARSE_PARAMETERS_END(); + + if (mpz_sgn(gmpnum_exp) <= 0) { + zend_argument_value_error(2, "must be greater than 0"); + RETURN_THROWS(); + } + + if (UNEXPECTED(!mpz_odd_p(gmpnum_mod))) { + /* Zero is an even modulus, but report it like gmp_powm() does. */ + if (!mpz_cmp_ui(gmpnum_mod, 0)) { + zend_argument_error(zend_ce_division_by_zero_error, 3, "Modulo by zero"); + } else { + zend_argument_value_error(3, "must be odd"); + } + RETURN_THROWS(); + } + + INIT_GMP_RETVAL(gmpnum_result); + mpz_powm_sec(gmpnum_result, gmpnum_base, gmpnum_exp, gmpnum_mod); +} +/* }}} */ +#endif + /* {{{ Takes integer part of square root of a */ ZEND_FUNCTION(gmp_sqrt) { diff --git a/ext/gmp/gmp.stub.php b/ext/gmp/gmp.stub.php index d3c603310a4a..f2ea15481748 100644 --- a/ext/gmp/gmp.stub.php +++ b/ext/gmp/gmp.stub.php @@ -125,6 +125,10 @@ function gmp_pow(GMP|int|string $num, int $exponent): GMP {} function gmp_powm(GMP|int|string $num, GMP|int|string $exponent, GMP|int|string $modulus): GMP {} +#ifdef HAVE___GMPZ_POWM_SEC +function gmp_powm_sec(GMP|int|string $num, GMP|int|string $exponent, GMP|int|string $modulus): GMP {} +#endif + function gmp_perfect_square(GMP|int|string $num): bool {} function gmp_perfect_power(GMP|int|string $num): bool {} diff --git a/ext/gmp/gmp_arginfo.h b/ext/gmp/gmp_arginfo.h index 957fdc8d7765..60eec94bb5cf 100644 --- a/ext/gmp/gmp_arginfo.h +++ b/ext/gmp/gmp_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit gmp.stub.php instead. - * Stub hash: 9d651cc4ba238a496ebe8302fe3e0f985d48d769 */ + * Stub hash: 57d016aed930bb41ff4357917e3ae8abd612e973 */ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_gmp_init, 0, 1, GMP, 0) ZEND_ARG_TYPE_MASK(0, num, MAY_BE_LONG|MAY_BE_STRING, NULL) @@ -91,6 +91,14 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_gmp_powm, 0, 3, GMP, 0) ZEND_ARG_OBJ_TYPE_MASK(0, modulus, GMP, MAY_BE_LONG|MAY_BE_STRING, NULL) ZEND_END_ARG_INFO() +#if defined(HAVE___GMPZ_POWM_SEC) +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_gmp_powm_sec, 0, 3, GMP, 0) + ZEND_ARG_OBJ_TYPE_MASK(0, num, GMP, MAY_BE_LONG|MAY_BE_STRING, NULL) + ZEND_ARG_OBJ_TYPE_MASK(0, exponent, GMP, MAY_BE_LONG|MAY_BE_STRING, NULL) + ZEND_ARG_OBJ_TYPE_MASK(0, modulus, GMP, MAY_BE_LONG|MAY_BE_STRING, NULL) +ZEND_END_ARG_INFO() +#endif + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_gmp_perfect_square, 0, 1, _IS_BOOL, 0) ZEND_ARG_OBJ_TYPE_MASK(0, num, GMP, MAY_BE_LONG|MAY_BE_STRING, NULL) ZEND_END_ARG_INFO() @@ -224,6 +232,9 @@ ZEND_FUNCTION(gmp_root); ZEND_FUNCTION(gmp_rootrem); ZEND_FUNCTION(gmp_pow); ZEND_FUNCTION(gmp_powm); +#if defined(HAVE___GMPZ_POWM_SEC) +ZEND_FUNCTION(gmp_powm_sec); +#endif ZEND_FUNCTION(gmp_perfect_square); ZEND_FUNCTION(gmp_perfect_power); ZEND_FUNCTION(gmp_prob_prime); @@ -283,6 +294,9 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(gmp_rootrem, arginfo_gmp_rootrem) ZEND_FE(gmp_pow, arginfo_gmp_pow) ZEND_FE(gmp_powm, arginfo_gmp_powm) +#if defined(HAVE___GMPZ_POWM_SEC) + ZEND_FE(gmp_powm_sec, arginfo_gmp_powm_sec) +#endif ZEND_FE(gmp_perfect_square, arginfo_gmp_perfect_square) ZEND_FE(gmp_perfect_power, arginfo_gmp_perfect_power) ZEND_FE(gmp_prob_prime, arginfo_gmp_prob_prime) diff --git a/ext/gmp/tests/bug80560.phpt b/ext/gmp/tests/bug80560.phpt index c5ccc050070c..85f4dff96be4 100644 --- a/ext/gmp/tests/bug80560.phpt +++ b/ext/gmp/tests/bug80560.phpt @@ -63,6 +63,9 @@ $functions2 = [ $functions3 = [ 'gmp_powm', ]; +if (function_exists('gmp_powm_sec')) { + $functions3[] = 'gmp_powm_sec'; +} echo 'Explicit base with gmp_init:', \PHP_EOL; echo 'Hexadecimal', \PHP_EOL; diff --git a/ext/gmp/tests/gmp_powm_sec.phpt b/ext/gmp/tests/gmp_powm_sec.phpt new file mode 100644 index 000000000000..2c66ff25a5e5 --- /dev/null +++ b/ext/gmp/tests/gmp_powm_sec.phpt @@ -0,0 +1,46 @@ +--TEST-- +gmp_powm_sec() +--EXTENSIONS-- +gmp +--SKIPIF-- + +--FILE-- +getMessage(), \PHP_EOL; + } +} + +try { + var_dump(gmp_powm_sec(4, 13, 0)); +} catch (\DivisionByZeroError $e) { + echo $e::class, ": ", $e->getMessage(), \PHP_EOL; +} + +try { + var_dump(gmp_powm_sec(4, 13, 496)); +} catch (\ValueError $e) { + echo $e::class, ": ", $e->getMessage(), \PHP_EOL; +} + +echo "Done\n"; +?> +--EXPECT-- +string(3) "445" +string(1) "5" +ValueError: gmp_powm_sec(): Argument #2 ($exponent) must be greater than 0 +ValueError: gmp_powm_sec(): Argument #2 ($exponent) must be greater than 0 +DivisionByZeroError: gmp_powm_sec(): Argument #3 ($modulus) Modulo by zero +ValueError: gmp_powm_sec(): Argument #3 ($modulus) must be odd +Done From bb05e82fef0760e054e4c7fed3fccbb4149a331d Mon Sep 17 00:00:00 2001 From: Matteo Beccati Date: Tue, 28 Jul 2026 10:09:26 +0000 Subject: [PATCH 12/12] [ci skip] Update NEWS for 8.6.0beta1 --- NEWS | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 65bd87faba82..7d85ff104dcd 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.6.0alpha3 +?? ??? ????, PHP 8.6.0beta1 + + +30 Jul 2026, PHP 8.6.0alpha3 - Core: . Implemented partial function application RFC. (Arnaud)