From 4a79a5a59a7051bf0fffb0277193ece9d8721f26 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 24 Oct 2024 22:02:17 +0200 Subject: [PATCH 01/76] Fix GHSA-5hqh-c84r-qjcv: Integer overflow in the dblib quoter causing OOB writes --- ext/pdo_dblib/dblib_driver.c | 10 ++++++-- ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt | 24 ++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt diff --git a/ext/pdo_dblib/dblib_driver.c b/ext/pdo_dblib/dblib_driver.c index 02ec2466a05fb..d7620307aa85b 100644 --- a/ext/pdo_dblib/dblib_driver.c +++ b/ext/pdo_dblib/dblib_driver.c @@ -148,7 +148,7 @@ static zend_string* dblib_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquo bool use_national_character_set = 0; size_t i; char *q; - size_t quotedlen = 0; + size_t quotedlen = 0, extralen = 0; zend_string *quoted_str; if (H->assume_national_character_set_strings) { @@ -163,7 +163,7 @@ static zend_string* dblib_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquo /* Detect quoted length, adding extra char for doubled single quotes */ for (i = 0; i < ZSTR_LEN(unquoted); i++) { - if (ZSTR_VAL(unquoted)[i] == '\'') ++quotedlen; + if (ZSTR_VAL(unquoted)[i] == '\'') ++extralen; ++quotedlen; } @@ -171,6 +171,12 @@ static zend_string* dblib_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquo if (use_national_character_set) { ++quotedlen; /* N prefix */ } + + if (UNEXPECTED(quotedlen > ZSTR_MAX_LEN - extralen)) { + return NULL; + } + + quotedlen += extralen; quoted_str = zend_string_alloc(quotedlen, 0); q = ZSTR_VAL(quoted_str); if (use_national_character_set) { diff --git a/ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt b/ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt new file mode 100644 index 0000000000000..431c61951ee2a --- /dev/null +++ b/ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt @@ -0,0 +1,24 @@ +--TEST-- +GHSA-5hqh-c84r-qjcv (Integer overflow in the dblib quoter causing OOB writes) +--EXTENSIONS-- +pdo_dblib +--SKIPIF-- + +--INI-- +memory_limit=-1 +--FILE-- +quote(str_repeat("'", 2147483646))); + +?> +--EXPECT-- +bool(false) From 7a25e7728de2218dfba88c234a924c4a1ed31140 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 24 Oct 2024 22:02:36 +0200 Subject: [PATCH 02/76] Fix GHSA-5hqh-c84r-qjcv: Integer overflow in the firebird quoter causing OOB writes --- ext/pdo_firebird/firebird_driver.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ext/pdo_firebird/firebird_driver.c b/ext/pdo_firebird/firebird_driver.c index 03c9f1b7741c5..a192a0adb10ce 100644 --- a/ext/pdo_firebird/firebird_driver.c +++ b/ext/pdo_firebird/firebird_driver.c @@ -664,7 +664,7 @@ static zend_long firebird_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) /* /* called by the PDO SQL parser to add quotes to values that are copied into SQL */ static zend_string* firebird_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquoted, enum pdo_param_type paramtype) { - int qcount = 0; + size_t qcount = 0; char const *co, *l, *r; char *c; size_t quotedlen; @@ -678,6 +678,10 @@ static zend_string* firebird_handle_quoter(pdo_dbh_t *dbh, const zend_string *un /* count the number of ' characters */ for (co = ZSTR_VAL(unquoted); (co = strchr(co,'\'')); qcount++, co++); + if (UNEXPECTED(ZSTR_LEN(unquoted) + 2 > ZSTR_MAX_LEN - qcount)) { + return NULL; + } + quotedlen = ZSTR_LEN(unquoted) + qcount + 2; quoted_str = zend_string_alloc(quotedlen, 0); c = ZSTR_VAL(quoted_str); From d7fe40868ea7bb6c3c58563bca80c783a8ba02ec Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Fri, 8 Nov 2024 23:43:47 +0100 Subject: [PATCH 03/76] Fix GHSA-c5f2-jwm7-mmq2: stream HTTP fulluri CRLF injection --- ext/standard/http_fopen_wrapper.c | 18 ++++++++---- .../tests/http/ghsa-c5f2-jwm7-mmq2.phpt | 28 +++++++++++++++++++ 2 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 ext/standard/tests/http/ghsa-c5f2-jwm7-mmq2.phpt diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index dca176ad3f526..2b9c7a06298a4 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -183,6 +183,11 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, return NULL; } + /* Should we send the entire path in the request line, default to no. */ + if (context && (tmpzval = php_stream_context_get_option(context, "http", "request_fulluri")) != NULL) { + request_fulluri = zend_is_true(tmpzval); + } + use_ssl = resource->scheme && (ZSTR_LEN(resource->scheme) > 4) && ZSTR_VAL(resource->scheme)[4] == 's'; /* choose default ports */ if (use_ssl && resource->port == 0) @@ -201,6 +206,13 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, } } + if (request_fulluri && (strchr(path, '\n') != NULL || strchr(path, '\r') != NULL)) { + php_stream_wrapper_log_error(wrapper, options, "HTTP wrapper full URI path does not allow CR or LF characters"); + php_url_free(resource); + zend_string_release(transport_string); + return NULL; + } + if (context && (tmpzval = php_stream_context_get_option(context, wrapper->wops->label, "timeout")) != NULL) { double d = zval_get_double(tmpzval); #ifndef PHP_WIN32 @@ -381,12 +393,6 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, smart_str_appends(&req_buf, "GET "); } - /* Should we send the entire path in the request line, default to no. */ - if (!request_fulluri && context && - (tmpzval = php_stream_context_get_option(context, "http", "request_fulluri")) != NULL) { - request_fulluri = zend_is_true(tmpzval); - } - if (request_fulluri) { /* Ask for everything */ smart_str_appends(&req_buf, path); diff --git a/ext/standard/tests/http/ghsa-c5f2-jwm7-mmq2.phpt b/ext/standard/tests/http/ghsa-c5f2-jwm7-mmq2.phpt new file mode 100644 index 0000000000000..e7dd194dbbe6f --- /dev/null +++ b/ext/standard/tests/http/ghsa-c5f2-jwm7-mmq2.phpt @@ -0,0 +1,28 @@ +--TEST-- +GHSA-c5f2-jwm7-mmq2 (Configuring a proxy in a stream context might allow for CRLF injection in URIs) +--INI-- +allow_url_fopen=1 +--CONFLICTS-- +server +--FILE-- + ['proxy' => 'tcp://' . $host, 'request_fulluri' => true]]); +echo file_get_contents("http://$host/$userinput", false, $context); +?> +--EXPECTF-- +Warning: file_get_contents(http://localhost:%d/index.php HTTP/1.1 +Host: localhost:%d + +GET /index2.php HTTP/1.1 +Host: localhost:%d + +GET /index.php): Failed to open stream: HTTP wrapper full URI path does not allow CR or LF characters in %s on line %d From fba659abb9de4d313a8ffb512e6238988db05a94 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 26 Sep 2024 22:22:27 +0200 Subject: [PATCH 04/76] Fix GHSA-g665-fm4p-vhff: OOB access in ldap_escape --- ext/ldap/ldap.c | 20 ++++++++++++++-- ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt | 28 ++++++++++++++++++++++ ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt | 29 +++++++++++++++++++++++ 3 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt create mode 100644 ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c index e33201f10d154..7adc3753a0342 100644 --- a/ext/ldap/ldap.c +++ b/ext/ldap/ldap.c @@ -3701,13 +3701,23 @@ static zend_string* php_ldap_do_escape(const bool *map, const char *value, size_ zend_string *ret; for (i = 0; i < valuelen; i++) { - len += (map[(unsigned char) value[i]]) ? 3 : 1; + size_t addend = (map[(unsigned char) value[i]]) ? 3 : 1; + if (len > ZSTR_MAX_LEN - addend) { + return NULL; + } + len += addend; } /* Per RFC 4514, a leading and trailing space must be escaped */ if ((flags & PHP_LDAP_ESCAPE_DN) && (value[0] == ' ')) { + if (len > ZSTR_MAX_LEN - 2) { + return NULL; + } len += 2; } if ((flags & PHP_LDAP_ESCAPE_DN) && ((valuelen > 1) && (value[valuelen - 1] == ' '))) { + if (len > ZSTR_MAX_LEN - 2) { + return NULL; + } len += 2; } @@ -3774,7 +3784,13 @@ PHP_FUNCTION(ldap_escape) php_ldap_escape_map_set_chars(map, ignores, ignoreslen, 0); } - RETURN_NEW_STR(php_ldap_do_escape(map, value, valuelen, flags)); + zend_string *result = php_ldap_do_escape(map, value, valuelen, flags); + if (UNEXPECTED(!result)) { + zend_argument_value_error(1, "is too long"); + RETURN_THROWS(); + } + + RETURN_NEW_STR(result); } #ifdef STR_TRANSLATION diff --git a/ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt b/ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt new file mode 100644 index 0000000000000..8e2c4fb160de3 --- /dev/null +++ b/ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt @@ -0,0 +1,28 @@ +--TEST-- +GHSA-g665-fm4p-vhff (OOB access in ldap_escape) +--EXTENSIONS-- +ldap +--INI-- +memory_limit=-1 +--SKIPIF-- + +--FILE-- +getMessage(), "\n"; +} + +try { + ldap_escape(str_repeat("#", 1431655758).' ', "", LDAP_ESCAPE_DN); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +ldap_escape(): Argument #1 ($value) is too long +ldap_escape(): Argument #1 ($value) is too long diff --git a/ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt b/ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt new file mode 100644 index 0000000000000..a69597084be6c --- /dev/null +++ b/ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt @@ -0,0 +1,29 @@ +--TEST-- +GHSA-g665-fm4p-vhff (OOB access in ldap_escape) +--EXTENSIONS-- +ldap +--INI-- +memory_limit=-1 +--SKIPIF-- + +--FILE-- +getMessage(), "\n"; +} + +// would allocate a string of length 2 +try { + ldap_escape(str_repeat("*", 1431655766), "", LDAP_ESCAPE_FILTER); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +ldap_escape(): Argument #1 ($value) is too long +ldap_escape(): Argument #1 ($value) is too long From c5954553008d85f9da8d32e086216fe25e30c0a6 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Tue, 8 Oct 2024 16:17:53 +0100 Subject: [PATCH 05/76] Fix GHSA-h35g-vwh6-m678: Mysqlnd - various heap buffer over-reads This fixes issues causing buffer over-read that leak heap content: - RESP packet field default left over for COM_LIST - RESP packet upsert filename - OK packet message - RESP packet for stmt row data - ps_fetch_from_1_to_8_bytes - ps_fetch_float - ps_fetch_double - ps_fetch_time - ps_fetch_date - ps_fetch_datetime - ps_fetch_string - ps_fetch_bit - RESP packet for query row data (just possible overflow on 32bit) It also adds various protocol tests using a new fake server. --- ext/mysqli/tests/fake_server.inc | 856 ++++++++++++++++++ .../ghsa-h35g-vwh6-m678-auth-message.phpt | 38 + ext/mysqli/tests/ghsa-h35g-vwh6-m678-def.phpt | 47 + .../tests/ghsa-h35g-vwh6-m678-filename.phpt | 43 + ...hsa-h35g-vwh6-m678-query-len-overflow.phpt | 48 + .../ghsa-h35g-vwh6-m678-stmt-row-bit.phpt | 53 ++ .../ghsa-h35g-vwh6-m678-stmt-row-date.phpt | 53 ++ ...ghsa-h35g-vwh6-m678-stmt-row-datetime.phpt | 53 ++ .../ghsa-h35g-vwh6-m678-stmt-row-double.phpt | 53 ++ .../ghsa-h35g-vwh6-m678-stmt-row-float.phpt | 53 ++ .../ghsa-h35g-vwh6-m678-stmt-row-int.phpt | 53 ++ ...ghsa-h35g-vwh6-m678-stmt-row-no-space.phpt | 53 ++ .../ghsa-h35g-vwh6-m678-stmt-row-string.phpt | 53 ++ .../ghsa-h35g-vwh6-m678-stmt-row-time.phpt | 53 ++ .../tests/protocol_query_row_fetch_data.phpt | 74 ++ .../tests/protocol_stmt_row_fetch_data.phpt | 91 ++ ext/mysqlnd/mysqlnd_ps_codec.c | 68 ++ ext/mysqlnd/mysqlnd_wireprotocol.c | 71 +- 18 files changed, 1792 insertions(+), 21 deletions(-) create mode 100644 ext/mysqli/tests/fake_server.inc create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-def.phpt create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-filename.phpt create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-query-len-overflow.phpt create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-bit.phpt create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-date.phpt create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-datetime.phpt create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-double.phpt create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-float.phpt create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-int.phpt create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-no-space.phpt create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-string.phpt create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-time.phpt create mode 100644 ext/mysqli/tests/protocol_query_row_fetch_data.phpt create mode 100644 ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt diff --git a/ext/mysqli/tests/fake_server.inc b/ext/mysqli/tests/fake_server.inc new file mode 100644 index 0000000000000..b02fabc584c5d --- /dev/null +++ b/ext/mysqli/tests/fake_server.inc @@ -0,0 +1,856 @@ + [ + 'type' => '03', + 'charset' => '3f00', + 'length' => '0b000000', + 'flags' => '0110', + 'decimal' => '00', + 'query_data_packet_length' => '080000', + 'query_data_value' => '023134', + 'stmt_data_packet_length' => '0b0000', + 'stmt_data_value' => '0e000000' + ], + 'fltval' => [ + 'type' => '04', + 'charset' => '3f00', + 'length' => '0c000000', + 'flags' => '0110', + 'decimal' => '1f', + 'query_data_packet_length' => '090000', + 'query_data_value' => '03322e33', + 'stmt_data_packet_length' => '0b0000', + 'stmt_data_value' => '33331340', + ], + 'dblval' => [ + 'type' => '05', + 'charset' => '3f00', + 'length' => '16000000', + 'flags' => '0110', + 'decimal' => '1f', + 'query_data_packet_length' => '090000', + 'query_data_value' => '03312e32', + 'stmt_data_packet_length' => '0f0000', + 'stmt_data_value' => '333333333333f33f' + ], + 'datval' => [ + 'type' => '0a', + 'charset' => '3f00', + 'length' => '0a000000', + 'flags' => '8110', + 'decimal' => '00', + 'query_data_packet_length' => '100000', + 'query_data_value' => '0a323031342d31322d3135', + 'stmt_data_packet_length' => '0c0000', + 'stmt_data_value' => '04de070c0f' + ], + 'timval' => [ + 'type' => '0b', + 'charset' => '3f00', + 'length' => '0a000000', + 'flags' => '8110', + 'decimal' => '00', + 'query_data_packet_length' => '0e0000', + 'query_data_value' => '0831333a30303a3032', + 'stmt_data_packet_length' => '100000', + 'stmt_data_value' => '080000000000150801' + ], + 'dtival' => [ + 'type' => '0c', + 'charset' => '3f00', + 'length' => '13000000', + 'flags' => '8110', + 'decimal' => '00', + 'query_data_packet_length' => '190000', + 'query_data_value' => '13323031342d31322d31362031333a30303a3031', + 'stmt_data_packet_length' => '0f0000', + 'stmt_data_value' => '07de070c100d0001' + ], + 'bitval' => [ + 'type' => '10', + 'charset' => '3f00', + 'length' => '40000000', + 'flags' => '2110', + 'decimal' => '00', + 'query_data_packet_length' => '0e0000', + 'query_data_value' => '080808080808080808', + 'stmt_data_packet_length' => '100000', + 'stmt_data_value' => '080808080808080808' + ], + 'strval' => [ + 'type' => 'fd', + 'charset' => 'e000', + 'length' => 'c8000000', + 'flags' => '0110', + 'decimal' => '00', + 'query_data_packet_length' => '0a0000', + 'query_data_value' => '0474657374', + 'stmt_data_packet_length' => '0c0000', + 'stmt_data_value' => '0474657374' + ], + ]; +} + +function my_mysqli_data_field(string $field): array +{ + $fields = my_mysqli_data_fields(); + if (!isset($fields[$field])) { + throw new Exception("Unknown field $field"); + } + return $fields[$field]; +} + + + +class my_mysqli_fake_packet_item +{ + public function __construct(public string|null $name, public string $value, public bool $is_hex = true) + { + } +} + +class my_mysqli_fake_packet +{ + private array $data = array(); + + public function __get(string $name) + { + foreach ($this->data as $item) { + if ($item->name === $name) { + return $item->value; + } + } + return null; + } + + public function __set(string $name, string|my_mysqli_fake_packet_item $value) + { + if ($value instanceof my_mysqli_fake_packet_item) { + if ($value->name === null) { + $value->name = $name; + } + } else { + $value = new my_mysqli_fake_packet_item($name, $value, true); + } + + for ($i = 0; $i < count($this->data); $i++) { + if ($this->data[$i]->name === $name) { + $this->data[$i] = $value; + return; + } + } + + $this->data[] = $value; + } + + public function to_bytes(): string + { + $bytes = ''; + foreach ($this->data as $item) { + $bytes .= $item->is_hex ? hex2bin($item->value) : $item->value; + } + return $bytes; + } +} + +class my_mysqli_fake_packet_generator +{ + public static function create_packet_item(int|string $value, bool $is_hex = false, string $format = 'v'): my_mysqli_fake_packet_item + { + if (is_string($value)) { + $packed_value = $value; + } else { + $packed_value = pack($format, $value); + } + return new my_mysqli_fake_packet_item(null, $packed_value, $is_hex); + } + + public function server_ok(): my_mysqli_fake_packet + { + $packet = new my_mysqli_fake_packet(); + $packet->packet_length = "070000"; + $packet->packet_number = "02"; + $packet->header = "00"; // OK + $packet->affected_rows = "00"; + $packet->last_insert_id = "00"; + $packet->server_status = "0200"; + $packet->warning_count = "0000"; + return $packet; + } + + public function server_greetings(): my_mysqli_fake_packet + { + $packet = new my_mysqli_fake_packet(); + $packet->packet_length = "580000"; + $packet->packet_number = "00"; + $packet->proto_version = "0a"; + $packet->version = self::create_packet_item('5.5.5-10.5.18-MariaDB' . chr(0)); + $packet->thread_id = "03000000"; + $packet->salt = "473e3f6047257c67"; + $packet->filler = "00"; + $packet->server_capabilities = self::create_packet_item(0b1111011111111110); + $packet->server_character_set = "08"; + $packet->server_status = self::create_packet_item(0b000000000000010); + $packet->extended_server_capabilities = self::create_packet_item(0b1000000111111111); + $packet->auth_plugin = "15"; + $packet->unused = "000000000000"; + $packet->mariadb_extended_server_capabilities = self::create_packet_item(0b1111, false, 'V'); + $packet->mariadb_extended_server_capabilities_salt = "6c6b55463f49335f686c643100"; + $packet->mariadb_extended_server_capabilities_auth_plugin = self::create_packet_item('mysql_native_password'); + + return $packet; + } + + public function server_tabular_query_response(): array + { + $qr1 = new my_mysqli_fake_packet(); + $qr1->packet_length = "010000"; + $qr1->packet_number = "01"; + $qr1->field_count = "01"; + + $qr2 = new my_mysqli_fake_packet(); + $qr2->packet_length = "190000"; + $qr2->packet_number = "02"; + $qr2->catalog_length_plus_name = "0164"; + $qr2->db_length_plus_name = "0164"; + $qr2->table_length_plus_name = "0164"; + $qr2->original_t = "0164"; + $qr2->name_length_plus_name = "0164"; + $qr2->original_n = "0164"; + $qr2->canary = "0c"; + $qr2->charset = "3f00"; + $qr2->length = "0b000000"; + $qr2->type = "03"; + $qr2->flags = "0350"; + $qr2->decimals = "000000"; + + $qr3 = new my_mysqli_fake_packet(); + $qr3->full = "05000003fe00002200"; + + $qr4 = new my_mysqli_fake_packet(); + $qr4->full = "0400000401350174"; + + $qr5 = new my_mysqli_fake_packet(); + $qr5->full = "05000005fe00002200"; + + return [$qr1, $qr2, $qr3, $qr4, $qr5]; + } + + public function server_upsert_query_response(): array + { + $qr1 = new my_mysqli_fake_packet(); + $qr1->packet_length = "010000"; + $qr1->packet_number = "01"; + $qr1->field_count = "00"; // UPSERT + $qr1->affected_rows = "00"; + $qr1->affected_rows = "00"; + $qr1->last_insert_id = "00"; + $qr1->server_status = "0000"; + $qr1->warning_count = "0000"; + $qr1->len = "01"; + $qr1->filename = "65"; + $qr1->packet_length = sprintf("%02x0000", strlen($qr1->to_bytes())-4); + + return [$qr1]; + } + + public function server_stmt_prepare_response_start($num_field): my_mysqli_fake_packet + { + $pr1 = new my_mysqli_fake_packet(); + $pr1->packet_length = "0c0000"; + $pr1->packet_number = "01"; + $pr1->response_code = '00'; // OK + $pr1->statement_id = '01000000'; + $pr1->num_fields = $num_field; + $pr1->num_params = '0000'; + $pr1->filler = '00'; + $pr1->warnings = '0000'; + + return $pr1; + } + + public function server_stmt_prepare_response_end($packer_number): my_mysqli_fake_packet + { + $pr3 = new my_mysqli_fake_packet(); + $pr3->packet_length = "050000"; + $pr3->packet_number = $packer_number; + $pr3->packet_type = 'fe'; // EOF + $pr3->warnings = '0000'; + $pr3->server_status = '0200'; + + return $pr3; + } + + public function server_stmt_prepare_items_response(): array + { + $pr1 = $this->server_stmt_prepare_response_start('0100'); + + $pr2 = new my_mysqli_fake_packet(); + $pr2->packet_length = "300000"; + $pr2->packet_number = "02"; + $pr2->catalogue_len = '03'; + $pr2->catalogue = '646566'; // def + $pr2->db_len = '08'; + $pr2->db = '7068705f74657374'; // php_test + $pr2->table_len = '05'; + $pr2->table = '6974656d73'; // items + $pr2->orig_table_len = '05'; + $pr2->orig_table = '6974656d73'; // items + $pr2->name_len = '04'; + $pr2->name = '6974656d'; + $pr2->orig_name_len = '04'; + $pr2->orig_name = '6974656d'; + $pr2->something = '0c'; + $pr2->charset = 'e000'; + $pr2->length = 'c8000000'; + $pr2->field_type = 'fd'; // FIELD_TYPE_VAR_STRING + $pr2->flags = '0110'; + $pr2->decimal = '00'; + $pr2->padding = '0000'; + + $pr3 = $this->server_stmt_prepare_response_end('03'); + + return [$pr1, $pr2, $pr3]; + } + + public function server_stmt_prepare_data_response_field($packet_number, $field_name): my_mysqli_fake_packet + { + if (strlen($field_name) != 6) { + throw new Exception("Invalid field length - only 6 is allowed"); + } + + $field = my_mysqli_data_field($field_name); + + $pr = new my_mysqli_fake_packet(); + $pr->packet_length = "320000"; + $pr->packet_number = $packet_number; + $pr->catalogue_len = '03'; + $pr->catalogue = bin2hex('def'); + $pr->db_len = '08'; + $pr->db = bin2hex('php_test'); + $pr->table_len = '04'; + $pr->table = bin2hex('data'); + $pr->orig_table_len = '04'; + $pr->orig_table = bin2hex('data'); + $pr->name_len = '06'; + $pr->name = bin2hex($field_name); + $pr->orig_name_len = '06'; + $pr->orig_name = bin2hex($field_name); + $pr->something = '0c'; + $pr->charset = $field['charset']; + $pr->length = $field['length']; + $pr->field_type = $field['type']; + $pr->flags = $field['flags']; + $pr->decimal = $field['decimal']; + $pr->padding = '0000'; + + return $pr; + } + + public function server_stmt_prepare_data_response(string $field_name): array + { + $pr1 = $this->server_stmt_prepare_response_start('0200'); + + $pr2 = $this->server_stmt_prepare_data_response_field('02', 'strval'); + $pr3 = $this->server_stmt_prepare_data_response_field('03', $field_name); + + $pr4 = $this->server_stmt_prepare_response_end('04'); + + return [$pr1, $pr2, $pr3, $pr4]; + } + + public function server_stmt_execute_items_response(): array + { + $pr1 = new my_mysqli_fake_packet(); + $pr1->packet_length = "010000"; + $pr1->packet_number = "01"; + $pr1->num_fields = '01'; + + $pr2 = new my_mysqli_fake_packet(); + $pr2->packet_length = "300000"; + $pr2->packet_number = "02"; + $pr2->catalogue_len = '03'; + $pr2->catalogue = '646566'; // def + $pr2->db_len = '08'; + $pr2->db = '7068705f74657374'; // php_test + $pr2->table_len = '05'; + $pr2->table = '6974656d73'; // items + $pr2->orig_table_len = '05'; + $pr2->orig_table = '6974656d73'; // items + $pr2->name_len = '04'; + $pr2->name = '6974656d'; + $pr2->orig_name_len = '04'; + $pr2->orig_name = '6974656d'; + $pr2->something = '0c'; + $pr2->charset = 'e000'; + $pr2->length = 'c8000000'; + $pr2->field_type = 'fd'; // FIELD_TYPE_VAR_STRING + $pr2->flags = '0110'; + $pr2->decimal = '00'; + $pr2->padding = '0000'; + + $pr3 = new my_mysqli_fake_packet(); + $pr3->packet_length = "050000"; + $pr3->packet_number = "03"; + $pr3->packet_type = 'fe'; // EOF + $pr3->warnings = '0000'; + $pr3->server_status = '2200'; + + $pr4 = new my_mysqli_fake_packet(); + $pr4->packet_length = "070000"; + $pr4->packet_number = "04"; + $pr4->packet_type = '00'; // OK + $pr4->affected_rows = '00'; + $pr4->row_data_len = '04'; + $pr4->row_data = '74657374'; // item + + $pr5 = new my_mysqli_fake_packet(); + $pr5->full = '05000005fe00002200'; + + return [$pr1, $pr2, $pr3, $pr4, $pr5]; + } + + private function server_execute_data_response_start(string $field_name): array + { + $pr1 = new my_mysqli_fake_packet(); + $pr1->packet_length = "010000"; + $pr1->packet_number = "01"; + $pr1->num_fields = '02'; + + $pr2 = new my_mysqli_fake_packet(); + $pr2->packet_length = "320000"; + $pr2->packet_number = "02"; + $pr2->catalogue_len = '03'; + $pr2->catalogue = '646566'; // def + $pr2->db_len = '08'; + $pr2->db = '7068705f74657374'; // php_test + $pr2->table_len = '04'; + $pr2->table = bin2hex('data'); + $pr2->orig_table_len = '04'; + $pr2->orig_table = bin2hex('data'); + $pr2->name_len = '06'; + $pr2->name = bin2hex('strval'); + $pr2->orig_name_len = '06'; + $pr2->orig_name = bin2hex('strval'); + $pr2->something = '0c'; + $pr2->charset = 'e000'; + $pr2->length = 'c8000000'; + $pr2->field_type = 'fd'; // FIELD_TYPE_VAR_STRING + $pr2->flags = '0110'; + $pr2->decimal = '00'; + $pr2->padding = '0000'; + + $field = my_mysqli_data_field($field_name); + + $pr3 = new my_mysqli_fake_packet(); + $pr3->packet_length = "320000"; + $pr3->packet_number = "03"; + $pr3->catalogue_len = '03'; + $pr3->catalogue = '646566'; // def + $pr3->db_len = '08'; + $pr3->db = '7068705f74657374'; // php_test + $pr3->table_len = '04'; + $pr3->table = bin2hex('data'); + $pr3->orig_table_len = '04'; + $pr3->orig_table = bin2hex('data'); + $pr3->name_len = '06'; + $pr3->name = bin2hex($field_name); + $pr3->orig_name_len = '06'; + $pr3->orig_name = bin2hex($field_name); + $pr3->something = '0c'; + $pr3->charset = $field['charset']; + $pr3->length = $field['length']; + $pr3->field_type = $field['type']; + $pr3->flags = $field['flags']; + $pr3->decimal = $field['decimal']; + $pr3->padding = '0000'; + + $pr4 = new my_mysqli_fake_packet(); + $pr4->packet_length = "050000"; + $pr4->packet_number = "04"; + $pr4->packet_type = 'fe'; // EOF + $pr4->warnings = '0000'; + $pr4->server_status = '2200'; + + return [$field, $pr1, $pr2, $pr3, $pr4]; + } + + private function server_execute_data_response_end(): my_mysqli_fake_packet + { + $pr6 = new my_mysqli_fake_packet(); + $pr6->packet_length = '050000'; + $pr6->packet_number = "06"; + $pr6->packet_type = 'fe'; // EOF + $pr6->warnings = '0000'; + $pr6->server_status = '2200'; + + return $pr6; + } + + public function server_stmt_execute_data_response(string $field_name): array + { + [$field, $pr1, $pr2, $pr3, $pr4] = $this->server_execute_data_response_start($field_name); + + $pr5 = new my_mysqli_fake_packet(); + $pr5->packet_length = $field['stmt_data_packet_length']; + $pr5->packet_number = "05"; + $pr5->packet_type = '00'; // OK + $pr5->affected_rows = '00'; + $pr5->row_field1_len = '04'; + $pr5->row_field1_data = '74657374'; // test + $pr5->row_field2 = $field['stmt_data_value']; + + return [$pr1, $pr2, $pr3, $pr4, $pr5, $this->server_execute_data_response_end()]; + } + + public function server_query_execute_data_response(string $field_name): array + { + [$field, $pr1, $pr2, $pr3, $pr4] = $this->server_execute_data_response_start($field_name); + + $pr5 = new my_mysqli_fake_packet(); + $pr5->packet_length = $field['query_data_packet_length']; + $pr5->packet_number = "05"; + $pr5->row_field1_len = '04'; + $pr5->row_field1_data = '74657374'; // test + $pr5->row_field2 = $field['query_data_value']; + + return [$pr1, $pr2, $pr3, $pr4, $pr5, $this->server_execute_data_response_end()]; + } +} + +class my_mysqli_fake_server_conn +{ + private $conn; + public $packet_generator; + + public function __construct($socket) + { + $this->packet_generator = new my_mysqli_fake_packet_generator(); + $this->conn = stream_socket_accept($socket); + if ($this->conn) { + fprintf(STDERR, "[*] Connection established\n"); + } else { + fprintf(STDERR, "[*] Failed to establish connection\n"); + } + } + + public function packets_to_bytes(array $packets): string + { + return implode('', array_map(fn($s) => $s->to_bytes(), $packets)); + } + + public function send($payload, $message = null): void + { + if ($message) { + fprintf(STDERR, "[*] Sending - %s: %s\n", $message, bin2hex($payload)); + } + fwrite($this->conn, $payload); + } + + public function read($bytes_len = 1024) + { + // wait 10ms to fill the buffer + usleep(10000); + $data = fread($this->conn, $bytes_len); + if ($data) { + fprintf(STDERR, "[*] Received: %s\n", bin2hex($data)); + } + } + + public function close() + { + fclose($this->conn); + } + + public function send_server_greetings() + { + $this->send($this->packet_generator->server_greetings()->to_bytes(), "Server Greeting"); + } + + public function send_server_ok() + { + $this->send($this->packet_generator->server_ok()->to_bytes(), "Server OK"); + } + + public function send_server_tabular_query_response(): void + { + $packets = $this->packet_generator->server_tabular_query_response(); + $this->send($this->packets_to_bytes($packets), "Tabular response"); + } + + public function send_server_stmt_prepare_items_response(): void + { + $packets = $this->packet_generator->server_stmt_prepare_items_response(); + $this->send($this->packets_to_bytes($packets), "Stmt prepare items"); + } + + + public function send_server_stmt_prepare_data_response(string $field_name): void + { + $packets = $this->packet_generator->server_stmt_prepare_data_response($field_name); + $this->send($this->packets_to_bytes($packets), "Stmt prepare data $field_name"); + } + + public function send_server_stmt_execute_items_response(): void + { + $packets = $this->packet_generator->server_stmt_execute_items_response(); + $this->send($this->packets_to_bytes($packets), "Stmt execute items"); + } + + public function send_server_stmt_execute_data_response(string $field_name): void + { + $packets = $this->packet_generator->server_stmt_execute_data_response($field_name); + $this->send($this->packets_to_bytes($packets), "Stmt execute data $field_name"); + } + + public function send_server_query_execute_data_response(string $field_name): void + { + $packets = $this->packet_generator->server_query_execute_data_response($field_name); + $this->send($this->packets_to_bytes($packets), "Query execute data $field_name"); + } +} + +class my_mysqli_fake_server_process +{ + public function __construct(private $process, private array $pipes) {} + + public function terminate(bool $wait = false) + { + if ($wait) { + $this->wait(); + } + proc_terminate($this->process); + } + + public function wait() + { + echo fgets($this->pipes[1]); + } +} + +function my_mysqli_test_tabular_response_def_over_read(my_mysqli_fake_server_conn $conn): void +{ + $rh = $conn->packet_generator->server_tabular_query_response(); + + // Length of the packet is modified to include the next added data + $rh[1]->packet_length = "1e0000"; + + // We add a length field encoded on 4 bytes which evaluates to 65536. If the process crashes because + // the heap has been overread, lower this value. + $rh[1]->extra_def_size = "fd000001"; # 65536 + + // Filler + $rh[1]->extra_def_data = "aa"; + + $trrh = $conn->packets_to_bytes($rh); + + $conn->send_server_greetings(); + $conn->read(); + $conn->send_server_ok(); + $conn->read(); + $conn->send($trrh, "Malicious Tabular Response [Extract heap through buffer over-read]"); + $conn->read(65536); +} + +function my_mysqli_test_upsert_response_filename_over_read(my_mysqli_fake_server_conn $conn): void +{ + $rh = $conn->packet_generator->server_upsert_query_response(); + + // Set extra length to overread + $rh[0]->len = "fa"; + + $trrh = $conn->packets_to_bytes($rh); + + $conn->send_server_greetings(); + $conn->read(); + $conn->send_server_ok(); + $conn->read(); + $conn->send($trrh, "Malicious Tabular Response [Extract heap through buffer over-read]"); + $conn->read(65536); +} + +function my_mysqli_test_auth_response_message_over_read(my_mysqli_fake_server_conn $conn): void +{ + $p = $conn->packet_generator->server_ok(); + $p->packet_length = "090000"; + $p->message_len = "fcff"; + + $conn->send_server_greetings(); + $conn->read(); + $conn->send($p->to_bytes(), "Malicious OK Auth Response [Extract heap through buffer over-read]"); + $conn->read(); +} + +function my_mysqli_test_stmt_response_row_over_read_string(my_mysqli_fake_server_conn $conn): void +{ + $rh = $conn->packet_generator->server_stmt_execute_items_response(); + + // Set extra length to overread + $rh[3]->row_data_len = "fa"; + + $conn->send_server_greetings(); + $conn->read(); + $conn->send_server_ok(); + $conn->read(); + $conn->send_server_stmt_prepare_items_response(); + $conn->read(); + $conn->send($conn->packets_to_bytes($rh), "Malicious Stmt Response for items [Extract heap through buffer over-read]"); + $conn->read(65536); +} + +function my_mysqli_test_stmt_response_row_over_read_two_fields( + my_mysqli_fake_server_conn $conn, + string $field_name, + string $row_field1_len = '06' +): void { + $rh = $conn->packet_generator->server_stmt_execute_data_response($field_name); + + // Set extra length to overread by two bytes + $rh[4]->row_field1_len = $row_field1_len; + + $conn->send_server_greetings(); + $conn->read(); + $conn->send_server_ok(); + $conn->read(); + $conn->send_server_stmt_prepare_data_response($field_name); + $conn->read(); + $conn->send( + $conn->packets_to_bytes($rh), + "Malicious Stmt Response for data $field_name [Extract heap through buffer over-read]" + ); + $conn->read(65536); +} + +function my_mysqli_test_stmt_response_row_over_read_int(my_mysqli_fake_server_conn $conn): void +{ + my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'intval'); +} + +function my_mysqli_test_stmt_response_row_over_read_float(my_mysqli_fake_server_conn $conn): void +{ + my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'fltval'); +} + +function my_mysqli_test_stmt_response_row_over_read_double(my_mysqli_fake_server_conn $conn): void +{ + my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'dblval'); +} + +function my_mysqli_test_stmt_response_row_over_read_date(my_mysqli_fake_server_conn $conn): void +{ + my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'datval'); +} + +function my_mysqli_test_stmt_response_row_over_read_time(my_mysqli_fake_server_conn $conn): void +{ + my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'timval', '0c'); +} + +function my_mysqli_test_stmt_response_row_over_read_datetime(my_mysqli_fake_server_conn $conn): void +{ + my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'dtival'); +} + +function my_mysqli_test_stmt_response_row_no_space(my_mysqli_fake_server_conn $conn): void +{ + my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'strval', '09'); +} + +function my_mysqli_test_stmt_response_row_over_read_bit(my_mysqli_fake_server_conn $conn): void +{ + my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'bitval'); +} + +function my_mysqli_test_stmt_response_row_read_two_fields(my_mysqli_fake_server_conn $conn): void +{ + $conn->send_server_greetings(); + $conn->read(); + $conn->send_server_ok(); + $conn->read(); + $field_names = array_keys(my_mysqli_data_fields()); + foreach ($field_names as $field_name) { + $conn->send_server_stmt_prepare_data_response($field_name); + $conn->read(65536); + $conn->send_server_stmt_execute_data_response($field_name); + $conn->read(65536); + } +} + +function my_mysqli_test_query_response_row_length_overflow(my_mysqli_fake_server_conn $conn): void +{ + $rh = $conn->packet_generator->server_query_execute_data_response('strval'); + + // Set extra length to overread by two bytes + $rh[4]->row_field2 = 'fefefefefe'; + + $conn->send_server_greetings(); + $conn->read(); + $conn->send_server_ok(); + $conn->read(); + $conn->send($conn->packets_to_bytes($rh), "Malicious Query Response for data strval field [length overflow]"); + $conn->read(65536); +} + +function my_mysqli_test_query_response_row_read_two_fields(my_mysqli_fake_server_conn $conn): void +{ + $conn->send_server_greetings(); + $conn->read(); + $conn->send_server_ok(); + $conn->read(); + $field_names = array_keys(my_mysqli_data_fields()); + foreach ($field_names as $field_name) { + $conn->send_server_query_execute_data_response($field_name); + $conn->read(); + } +} + +function run_fake_server(string $test_function, $port = 33305): void +{ + $address = '127.0.0.1'; + + $socket = @stream_socket_server("tcp://$address:$port", $errno, $errstr); + if (!$socket) { + die("Failed to create socket: $errstr ($errno)\n"); + } + echo "[*] Server started\n"; + + try { + $conn = new my_mysqli_fake_server_conn($socket); + $test_function_name = 'my_mysqli_test_' . $test_function; + call_user_func($test_function_name, $conn); + $conn->close(); + } catch (Exception $e) { + fprintf(STDERR, "[!] Exception: " . $e->getMessage() . "\n"); + } + + fclose($socket); + + echo "[*] Server finished\n"; +} + + +function run_fake_server_in_background($test_function, $port = 33305): my_mysqli_fake_server_process +{ + $command = [PHP_BINARY, '-n', __FILE__, 'mysqli_fake_server', $test_function, $port]; + + $descriptorspec = array( + 0 => array("pipe", "r"), + 1 => array("pipe", "w"), + 2 => STDERR, + ); + + $process = proc_open($command, $descriptorspec, $pipes); + + if (is_resource($process)) { + return new my_mysqli_fake_server_process($process, $pipes); + } else { + throw new Exception("Failed to start server process"); + } +} + +if (isset($argv) && $argc > 2 && $argv[1] == 'mysqli_fake_server') { + run_fake_server($argv[2], $argv[3] ?? '33305'); +} diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt new file mode 100644 index 0000000000000..db54a6c0177a1 --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt @@ -0,0 +1,38 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - auth message buffer over-read) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +try { + $conn = new mysqli( $servername, $username, $password, "", $port ); + $info = mysqli_info($conn); + var_dump($info); +} catch (Exception $e) { + echo $e->getMessage() . PHP_EOL; +} + +$process->terminate(); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Malicious OK Auth Response [Extract heap through buffer over-read]: 0900000200000002000000fcff + +Warning: mysqli::__construct(): OK packet message length is past the packet size in %s on line %d +Unknown error while trying to connect via tcp://127.0.0.1:50001 +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-def.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-def.phpt new file mode 100644 index 0000000000000..77f2232eca687 --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-def.phpt @@ -0,0 +1,47 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - tabular default) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +echo "[*] Running query on the fake server...\n"; + +$result = $conn->query("SELECT * from users"); + +if ($result) { + $all_fields = $result->fetch_fields(); + var_dump($result->fetch_all(MYSQLI_ASSOC)); + var_dump(get_object_vars($all_fields[0])["def"]); +} + +$conn->close(); + +$process->terminate(); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Running query on the fake server... +[*] Received: 140000000353454c454354202a2066726f6d207573657273 +[*] Sending - Malicious Tabular Response [Extract heap through buffer over-read]: 01000001011e0000020164016401640164016401640c3f000b000000030350000000fd000001aa05000003fe00002200040000040135017405000005fe00002200 + +Warning: mysqli::query(): Protocol error. Server sent default for unsupported field list (mysqlnd_wireprotocol.c:%d) in %s on line %d +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-filename.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-filename.phpt new file mode 100644 index 0000000000000..0b4db8ccece95 --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-filename.phpt @@ -0,0 +1,43 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - upsert filename buffer over-read) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); +echo "[*] Running query on the fake server...\n"; + +$result = $conn->query("SELECT * from users"); +$info = mysqli_info($conn); + +var_dump($info); + +$process->terminate(); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Running query on the fake server... +[*] Received: 140000000353454c454354202a2066726f6d207573657273 +[*] Sending - Malicious Tabular Response [Extract heap through buffer over-read]: 0900000100000000000000fa65 + +Warning: mysqli::query(): RSET_HEADER packet additional data length is past 249 bytes the packet size in %s on line %d + +Warning: mysqli::query(): Error reading result set's header in %s on line %d +NULL +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-query-len-overflow.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-query-len-overflow.phpt new file mode 100644 index 0000000000000..f141a79bdaa85 --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-query-len-overflow.phpt @@ -0,0 +1,48 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row no space for the field) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +echo "[*] Query the fake server...\n"; +$sql = "SELECT strval, strval FROM data"; + +$result = $conn->query($sql); + +if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row['strval']); + } +} +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Query the fake server... +[*] Received: 200000000353454c4543542073747276616c2c2073747276616c2046524f4d2064617461 +[*] Sending - Malicious Query Response for data strval field [length overflow]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe000022000a0000050474657374fefefefefe05000006fe00002200 + +Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after end of packet in %s on line %d +[*] Received: 0100000001 +[*] Server finished +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-bit.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-bit.phpt new file mode 100644 index 0000000000000..e43518217eb63 --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-bit.phpt @@ -0,0 +1,53 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row bit buffer over-read) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +echo "[*] Preparing statement on the fake server...\n"; +$stmt = $conn->prepare("SELECT bitval, timval FROM data"); + +$stmt->execute(); +$result = $stmt->get_result(); + +// Fetch and display the results +if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row["bitval"]); + } +} +$stmt->close(); +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Preparing statement on the fake server... +[*] Received: 200000001653454c4543542062697476616c2c2074696d76616c2046524f4d2064617461 +[*] Sending - Stmt prepare data bitval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Malicious Stmt Response for data bitval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe00002200100000050000067465737408080808080808080805000006fe00002200 + +Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d +[*] Received: 0500000019010000000100000001 +[*] Server finished +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-date.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-date.phpt new file mode 100644 index 0000000000000..76158e940d09d --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-date.phpt @@ -0,0 +1,53 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row date buffer over-read) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +echo "[*] Preparing statement on the fake server...\n"; +$stmt = $conn->prepare("SELECT strval, datval FROM data"); + +$stmt->execute(); +$result = $stmt->get_result(); + +// Fetch and display the results +if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row["datval"]); + } +} +$stmt->close(); +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Preparing statement on the fake server... +[*] Received: 200000001653454c4543542073747276616c2c2064617476616c2046524f4d2064617461 +[*] Sending - Stmt prepare data datval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664617476616c0664617476616c0c3f000a0000000a811000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Malicious Stmt Response for data datval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664617476616c0664617476616c0c3f000a0000000a811000000005000004fe000022000c0000050000067465737404de070c0f05000006fe00002200 + +Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d +[*] Received: 0500000019010000000100000001 +[*] Server finished +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-datetime.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-datetime.phpt new file mode 100644 index 0000000000000..f53d5b83bd432 --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-datetime.phpt @@ -0,0 +1,53 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row datetime buffer over-read) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +echo "[*] Preparing statement on the fake server...\n"; +$stmt = $conn->prepare("SELECT strval, dtival FROM data"); + +$stmt->execute(); +$result = $stmt->get_result(); + +// Fetch and display the results +if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row["dtival"]); + } +} +$stmt->close(); +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Preparing statement on the fake server... +[*] Received: 200000001653454c4543542073747276616c2c2064746976616c2046524f4d2064617461 +[*] Sending - Stmt prepare data dtival: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664746976616c0664746976616c0c3f00130000000c811000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Malicious Stmt Response for data dtival [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664746976616c0664746976616c0c3f00130000000c811000000005000004fe000022000f0000050000067465737407de070c100d000105000006fe00002200 + +Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d +[*] Received: 0500000019010000000100000001 +[*] Server finished +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-double.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-double.phpt new file mode 100644 index 0000000000000..03c9b045d7375 --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-double.phpt @@ -0,0 +1,53 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row double buffer over-read) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +echo "[*] Preparing statement on the fake server...\n"; +$stmt = $conn->prepare("SELECT strval, dblval FROM data"); + +$stmt->execute(); +$result = $stmt->get_result(); + +// Fetch and display the results +if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row["dblval"]); + } +} +$stmt->close(); +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Preparing statement on the fake server... +[*] Received: 200000001653454c4543542073747276616c2c2064626c76616c2046524f4d2064617461 +[*] Sending - Stmt prepare data dblval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664626c76616c0664626c76616c0c3f00160000000501101f000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Malicious Stmt Response for data dblval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664626c76616c0664626c76616c0c3f00160000000501101f000005000004fe000022000f00000500000674657374333333333333f33f05000006fe00002200 + +Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d +[*] Received: 0500000019010000000100000001 +[*] Server finished +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-float.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-float.phpt new file mode 100644 index 0000000000000..b1ec9aa51eca1 --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-float.phpt @@ -0,0 +1,53 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row int buffer over-read) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +echo "[*] Preparing statement on the fake server...\n"; +$stmt = $conn->prepare("SELECT strval, fltval FROM data"); + +$stmt->execute(); +$result = $stmt->get_result(); + +// Fetch and display the results +if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row["fltval"]); + } +} +$stmt->close(); +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Preparing statement on the fake server... +[*] Received: 200000001653454c4543542073747276616c2c20666c7476616c2046524f4d2064617461 +[*] Sending - Stmt prepare data fltval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106666c7476616c06666c7476616c0c3f000c0000000401101f000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Malicious Stmt Response for data fltval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106666c7476616c06666c7476616c0c3f000c0000000401101f000005000004fe000022000b000005000006746573743333134005000006fe00002200 + +Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d +[*] Received: 0500000019010000000100000001 +[*] Server finished +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-int.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-int.phpt new file mode 100644 index 0000000000000..426d9ea7b3f9b --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-int.phpt @@ -0,0 +1,53 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row int buffer over-read) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +echo "[*] Preparing statement on the fake server...\n"; +$stmt = $conn->prepare("SELECT strval, intval FROM data"); + +$stmt->execute(); +$result = $stmt->get_result(); + +// Fetch and display the results +if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row["intval"]); + } +} +$stmt->close(); +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Preparing statement on the fake server... +[*] Received: 200000001653454c4543542073747276616c2c20696e7476616c2046524f4d2064617461 +[*] Sending - Stmt prepare data intval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106696e7476616c06696e7476616c0c3f000b00000003011000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Malicious Stmt Response for data intval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106696e7476616c06696e7476616c0c3f000b00000003011000000005000004fe000022000b000005000006746573740e00000005000006fe00002200 + +Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d +[*] Received: 0500000019010000000100000001 +[*] Server finished +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-no-space.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-no-space.phpt new file mode 100644 index 0000000000000..6db6952d42a15 --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-no-space.phpt @@ -0,0 +1,53 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row no space for the field) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +echo "[*] Preparing statement on the fake server...\n"; +$stmt = $conn->prepare("SELECT strval, strval FROM data"); + +$stmt->execute(); +$result = $stmt->get_result(); + +// Fetch and display the results +if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row["strval"]); + } +} +$stmt->close(); +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Preparing statement on the fake server... +[*] Received: 200000001653454c4543542073747276616c2c2073747276616c2046524f4d2064617461 +[*] Sending - Stmt prepare data strval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Malicious Stmt Response for data strval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe000022000c00000500000974657374047465737405000006fe00002200 + +Warning: mysqli_result::fetch_assoc(): Malformed server packet. No packet space left for the field in %s on line %d +[*] Received: 0500000019010000000100000001 +[*] Server finished +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-string.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-string.phpt new file mode 100644 index 0000000000000..55bad4cc544aa --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-string.phpt @@ -0,0 +1,53 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row string buffer over-read) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +echo "[*] Preparing statement on the fake server...\n"; +$stmt = $conn->prepare("SELECT item FROM items"); + +$stmt->execute(); +$result = $stmt->get_result(); + +// Fetch and display the results +if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row["item"]); + } +} +$stmt->close(); +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Preparing statement on the fake server... +[*] Received: 170000001653454c454354206974656d2046524f4d206974656d73 +[*] Sending - Stmt prepare items: 0c0000010001000000010000000000003000000203646566087068705f74657374056974656d73056974656d73046974656d046974656d0ce000c8000000fd011000000005000003fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Malicious Stmt Response for items [Extract heap through buffer over-read]: 01000001013000000203646566087068705f74657374056974656d73056974656d73046974656d046974656d0ce000c8000000fd011000000005000003fe00002200070000040000fa7465737405000005fe00002200 + +Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d +[*] Received: 0500000019010000000100000001 +[*] Server finished +done! diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-time.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-time.phpt new file mode 100644 index 0000000000000..06918c375f31a --- /dev/null +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-time.phpt @@ -0,0 +1,53 @@ +--TEST-- +GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row time buffer over-read) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +echo "[*] Preparing statement on the fake server...\n"; +$stmt = $conn->prepare("SELECT strval, timval FROM data"); + +$stmt->execute(); +$result = $stmt->get_result(); + +// Fetch and display the results +if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row["timval"]); + } +} +$stmt->close(); +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECTF-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Preparing statement on the fake server... +[*] Received: 200000001653454c4543542073747276616c2c2074696d76616c2046524f4d2064617461 +[*] Sending - Stmt prepare data timval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610674696d76616c0674696d76616c0c3f000a0000000b811000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Malicious Stmt Response for data timval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610674696d76616c0674696d76616c0c3f000a0000000b811000000005000004fe000022001000000500000c7465737408000000000015080105000006fe00002200 + +Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d +[*] Received: 0500000019010000000100000001 +[*] Server finished +done! diff --git a/ext/mysqli/tests/protocol_query_row_fetch_data.phpt b/ext/mysqli/tests/protocol_query_row_fetch_data.phpt new file mode 100644 index 0000000000000..524fe5e587c63 --- /dev/null +++ b/ext/mysqli/tests/protocol_query_row_fetch_data.phpt @@ -0,0 +1,74 @@ +--TEST-- +MySQL protocol - statement row data fetch) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +function my_query($conn, $field) +{ + $sql = "SELECT strval, $field FROM data"; + + $result = $conn->query($sql); + + if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row[$field]); + } + } +} + +foreach (my_mysqli_data_fields() as $field_name => $field) { + my_query($conn, $field_name); +} + +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECT-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Received: 200000000353454c4543542073747276616c2c20696e7476616c2046524f4d2064617461 +[*] Sending - Query execute data intval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106696e7476616c06696e7476616c0c3f000b00000003011000000005000004fe0000220008000005047465737402313405000006fe00002200 +string(2) "14" +[*] Received: 200000000353454c4543542073747276616c2c20666c7476616c2046524f4d2064617461 +[*] Sending - Query execute data fltval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106666c7476616c06666c7476616c0c3f000c0000000401101f000005000004fe0000220009000005047465737403322e3305000006fe00002200 +string(3) "2.3" +[*] Received: 200000000353454c4543542073747276616c2c2064626c76616c2046524f4d2064617461 +[*] Sending - Query execute data dblval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664626c76616c0664626c76616c0c3f00160000000501101f000005000004fe0000220009000005047465737403312e3205000006fe00002200 +string(3) "1.2" +[*] Received: 200000000353454c4543542073747276616c2c2064617476616c2046524f4d2064617461 +[*] Sending - Query execute data datval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664617476616c0664617476616c0c3f000a0000000a811000000005000004fe000022001000000504746573740a323031342d31322d313505000006fe00002200 +string(10) "2014-12-15" +[*] Received: 200000000353454c4543542073747276616c2c2074696d76616c2046524f4d2064617461 +[*] Sending - Query execute data timval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610674696d76616c0674696d76616c0c3f000a0000000b811000000005000004fe000022000e00000504746573740831333a30303a303205000006fe00002200 +string(8) "13:00:02" +[*] Received: 200000000353454c4543542073747276616c2c2064746976616c2046524f4d2064617461 +[*] Sending - Query execute data dtival: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664746976616c0664746976616c0c3f00130000000c811000000005000004fe0000220019000005047465737413323031342d31322d31362031333a30303a303105000006fe00002200 +string(19) "2014-12-16 13:00:01" +[*] Received: 200000000353454c4543542073747276616c2c2062697476616c2046524f4d2064617461 +[*] Sending - Query execute data bitval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe000022000e000005047465737408080808080808080805000006fe00002200 +string(18) "578721382704613384" +[*] Received: 200000000353454c4543542073747276616c2c2073747276616c2046524f4d2064617461 +[*] Sending - Query execute data strval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe000022000a0000050474657374047465737405000006fe00002200 +string(4) "test" +[*] Received: 0100000001 +[*] Server finished +done! diff --git a/ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt b/ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt new file mode 100644 index 0000000000000..d461ec24b8c07 --- /dev/null +++ b/ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt @@ -0,0 +1,91 @@ +--TEST-- +MySQL protocol - statement row data fetch) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +$conn = new mysqli($servername, $username, $password, "", $port); + +function my_query($conn, $field) +{ + $stmt = $conn->prepare("SELECT strval, $field FROM data"); + + $stmt->execute(); + $result = $stmt->get_result(); + + if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + var_dump($row[$field]); + } + } +} + +foreach (my_mysqli_data_fields() as $field_name => $field) { + my_query($conn, $field_name); +} + +$conn->close(); + +$process->terminate(true); + +print "done!"; +?> +--EXPECT-- +[*] Server started +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31 +[*] Sending - Server OK: 0700000200000002000000 +[*] Received: 200000001653454c4543542073747276616c2c20696e7476616c2046524f4d2064617461 +[*] Sending - Stmt prepare data intval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106696e7476616c06696e7476616c0c3f000b00000003011000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Stmt execute data intval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106696e7476616c06696e7476616c0c3f000b00000003011000000005000004fe000022000b000005000004746573740e00000005000006fe00002200 +int(14) +[*] Received: 050000001901000000200000001653454c4543542073747276616c2c20666c7476616c2046524f4d2064617461 +[*] Sending - Stmt prepare data fltval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106666c7476616c06666c7476616c0c3f000c0000000401101f000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Stmt execute data fltval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106666c7476616c06666c7476616c0c3f000c0000000401101f000005000004fe000022000b000005000004746573743333134005000006fe00002200 +float(2.3) +[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2064626c76616c2046524f4d2064617461 +[*] Sending - Stmt prepare data dblval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664626c76616c0664626c76616c0c3f00160000000501101f000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Stmt execute data dblval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664626c76616c0664626c76616c0c3f00160000000501101f000005000004fe000022000f00000500000474657374333333333333f33f05000006fe00002200 +float(1.2) +[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2064617476616c2046524f4d2064617461 +[*] Sending - Stmt prepare data datval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664617476616c0664617476616c0c3f000a0000000a811000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Stmt execute data datval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664617476616c0664617476616c0c3f000a0000000a811000000005000004fe000022000c0000050000047465737404de070c0f05000006fe00002200 +string(10) "2014-12-15" +[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2074696d76616c2046524f4d2064617461 +[*] Sending - Stmt prepare data timval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610674696d76616c0674696d76616c0c3f000a0000000b811000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Stmt execute data timval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610674696d76616c0674696d76616c0c3f000a0000000b811000000005000004fe00002200100000050000047465737408000000000015080105000006fe00002200 +string(8) "21:08:01" +[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2064746976616c2046524f4d2064617461 +[*] Sending - Stmt prepare data dtival: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664746976616c0664746976616c0c3f00130000000c811000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Stmt execute data dtival: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664746976616c0664746976616c0c3f00130000000c811000000005000004fe000022000f0000050000047465737407de070c100d000105000006fe00002200 +string(19) "2014-12-16 13:00:01" +[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2062697476616c2046524f4d2064617461 +[*] Sending - Stmt prepare data bitval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Stmt execute data bitval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe00002200100000050000047465737408080808080808080805000006fe00002200 +int(578721382704613384) +[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2073747276616c2046524f4d2064617461 +[*] Sending - Stmt prepare data strval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe00000200 +[*] Received: 0a00000017010000000001000000 +[*] Sending - Stmt execute data strval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe000022000c00000500000474657374047465737405000006fe00002200 +string(4) "test" +[*] Received: 0500000019010000000100000001 +[*] Server finished +done! diff --git a/ext/mysqlnd/mysqlnd_ps_codec.c b/ext/mysqlnd/mysqlnd_ps_codec.c index 3b38d86273b68..796516b310281 100644 --- a/ext/mysqlnd/mysqlnd_ps_codec.c +++ b/ext/mysqlnd/mysqlnd_ps_codec.c @@ -50,11 +50,46 @@ struct st_mysqlnd_perm_bind mysqlnd_ps_fetch_functions[MYSQL_TYPE_LAST + 1]; #define MYSQLND_PS_SKIP_RESULT_W_LEN -1 #define MYSQLND_PS_SKIP_RESULT_STR -2 +static inline void ps_fetch_over_read_error(const zend_uchar ** row) +{ + php_error_docref(NULL, E_WARNING, "Malformed server packet. Field length pointing after the end of packet"); + *row = NULL; +} + +static inline bool ps_fetch_is_packet_over_read_with_variable_length(const unsigned int pack_len, + const zend_uchar ** row, const zend_uchar *p, unsigned int length) +{ + if (pack_len == 0) { + return false; + } + size_t length_len = *row - p; + if (length_len > pack_len || length > pack_len - length_len) { + ps_fetch_over_read_error(row); + return true; + } + return false; +} + +static inline bool ps_fetch_is_packet_over_read_with_static_length(const unsigned int pack_len, + const zend_uchar ** row, unsigned int length) +{ + if (pack_len > 0 && length > pack_len) { + ps_fetch_over_read_error(row); + return true; + } + return false; +} + + /* {{{ ps_fetch_from_1_to_8_bytes */ void ps_fetch_from_1_to_8_bytes(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len, const zend_uchar ** row, unsigned int byte_count) { + if (UNEXPECTED(ps_fetch_is_packet_over_read_with_static_length(pack_len, row, byte_count))) { + return; + } + bool is_bit = field->type == MYSQL_TYPE_BIT; DBG_ENTER("ps_fetch_from_1_to_8_bytes"); DBG_INF_FMT("zv=%p byte_count=%u", zv, byte_count); @@ -174,6 +209,11 @@ ps_fetch_float(zval * zv, const MYSQLND_FIELD * const field, const unsigned int float fval; double dval; DBG_ENTER("ps_fetch_float"); + + if (UNEXPECTED(ps_fetch_is_packet_over_read_with_static_length(pack_len, row, 4))) { + return; + } + float4get(fval, *row); (*row)+= 4; DBG_INF_FMT("value=%f", fval); @@ -196,6 +236,11 @@ ps_fetch_double(zval * zv, const MYSQLND_FIELD * const field, const unsigned int { double value; DBG_ENTER("ps_fetch_double"); + + if (UNEXPECTED(ps_fetch_is_packet_over_read_with_static_length(pack_len, row, 8))) { + return; + } + float8get(value, *row); ZVAL_DOUBLE(zv, value); (*row)+= 8; @@ -211,9 +256,14 @@ ps_fetch_time(zval * zv, const MYSQLND_FIELD * const field, const unsigned int p { struct st_mysqlnd_time t; zend_ulong length; /* First byte encodes the length */ + const zend_uchar *p = *row; DBG_ENTER("ps_fetch_time"); if ((length = php_mysqlnd_net_field_length(row))) { + if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) { + return; + } + const zend_uchar * to = *row; t.time_type = MYSQLND_TIMESTAMP_TIME; @@ -256,9 +306,14 @@ ps_fetch_date(zval * zv, const MYSQLND_FIELD * const field, const unsigned int p { struct st_mysqlnd_time t = {0}; zend_ulong length; /* First byte encodes the length*/ + const zend_uchar *p = *row; DBG_ENTER("ps_fetch_date"); if ((length = php_mysqlnd_net_field_length(row))) { + if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) { + return; + } + const zend_uchar * to = *row; t.time_type = MYSQLND_TIMESTAMP_DATE; @@ -288,9 +343,14 @@ ps_fetch_datetime(zval * zv, const MYSQLND_FIELD * const field, const unsigned i { struct st_mysqlnd_time t; zend_ulong length; /* First byte encodes the length*/ + const zend_uchar *p = *row; DBG_ENTER("ps_fetch_datetime"); if ((length = php_mysqlnd_net_field_length(row))) { + if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) { + return; + } + const zend_uchar * to = *row; t.time_type = MYSQLND_TIMESTAMP_DATETIME; @@ -332,7 +392,11 @@ ps_fetch_datetime(zval * zv, const MYSQLND_FIELD * const field, const unsigned i static void ps_fetch_string(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len, const zend_uchar ** row) { + const zend_uchar *p = *row; const zend_ulong length = php_mysqlnd_net_field_length(row); + if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) { + return; + } DBG_ENTER("ps_fetch_string"); DBG_INF_FMT("len = " ZEND_ULONG_FMT, length); DBG_INF("copying from the row buffer"); @@ -348,7 +412,11 @@ ps_fetch_string(zval * zv, const MYSQLND_FIELD * const field, const unsigned int static void ps_fetch_bit(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len, const zend_uchar ** row) { + const zend_uchar *p = *row; const zend_ulong length = php_mysqlnd_net_field_length(row); + if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) { + return; + } ps_fetch_from_1_to_8_bytes(zv, field, pack_len, row, length); } /* }}} */ diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c index fed191c74fa52..a75c70450104a 100644 --- a/ext/mysqlnd/mysqlnd_wireprotocol.c +++ b/ext/mysqlnd/mysqlnd_wireprotocol.c @@ -721,7 +721,14 @@ php_mysqlnd_auth_response_read(MYSQLND_CONN_DATA * conn, void * _packet) /* There is a message */ if (packet->header.size > (size_t) (p - buf) && (net_len = php_mysqlnd_net_field_length(&p))) { - packet->message_len = MIN(net_len, buf_len - (p - begin)); + /* p can get past packet size when getting field length so it needs to be checked first + * and after that it can be checked that the net_len is not greater than the packet size */ + if ((p - buf) > packet->header.size || packet->header.size - (p - buf) < net_len) { + DBG_ERR_FMT("OK packet message length is past the packet size"); + php_error_docref(NULL, E_WARNING, "OK packet message length is past the packet size"); + DBG_RETURN(FAIL); + } + packet->message_len = net_len; packet->message = mnd_pestrndup((char *)p, packet->message_len, FALSE); } else { packet->message = NULL; @@ -1105,6 +1112,17 @@ php_mysqlnd_rset_header_read(MYSQLND_CONN_DATA * conn, void * _packet) BAIL_IF_NO_MORE_DATA; /* Check for additional textual data */ if (packet->header.size > (size_t) (p - buf) && (len = php_mysqlnd_net_field_length(&p))) { + /* p can get past packet size when getting field length so it needs to be checked first + * and after that it can be checked that the len is not greater than the packet size */ + if ((p - buf) > packet->header.size || packet->header.size - (p - buf) < len) { + size_t local_file_name_over_read = ((p - buf) - packet->header.size) + len; + DBG_ERR_FMT("RSET_HEADER packet additional data length is past %zu bytes the packet size", + local_file_name_over_read); + php_error_docref(NULL, E_WARNING, + "RSET_HEADER packet additional data length is past %zu bytes the packet size", + local_file_name_over_read); + DBG_RETURN(FAIL); + } packet->info_or_local_file.s = mnd_emalloc(len + 1); memcpy(packet->info_or_local_file.s, p, len); packet->info_or_local_file.s[len] = '\0'; @@ -1255,23 +1273,16 @@ php_mysqlnd_rset_field_read(MYSQLND_CONN_DATA * conn, void * _packet) meta->flags |= NUM_FLAG; } - - /* - def could be empty, thus don't allocate on the root. - NULL_LENGTH (0xFB) comes from COM_FIELD_LIST when the default value is NULL. - Otherwise the string is length encoded. - */ + /* COM_FIELD_LIST is no longer supported so def should not be present */ if (packet->header.size > (size_t) (p - buf) && (len = php_mysqlnd_net_field_length(&p)) && len != MYSQLND_NULL_LENGTH) { - BAIL_IF_NO_MORE_DATA; - DBG_INF_FMT("Def found, length " ZEND_ULONG_FMT, len); - meta->def = packet->memory_pool->get_chunk(packet->memory_pool, len + 1); - memcpy(meta->def, p, len); - meta->def[len] = '\0'; - meta->def_length = len; - p += len; + DBG_ERR_FMT("Protocol error. Server sent default for unsupported field list"); + php_error_docref(NULL, E_WARNING, + "Protocol error. Server sent default for unsupported field list (mysqlnd_wireprotocol.c:%u)", + __LINE__); + DBG_RETURN(FAIL); } root_ptr = meta->root = packet->memory_pool->get_chunk(packet->memory_pool, total_len); @@ -1434,8 +1445,10 @@ php_mysqlnd_rowp_read_binary_protocol(MYSQLND_ROW_BUFFER * row_buffer, zval * fi const unsigned int field_count, const MYSQLND_FIELD * const fields_metadata, const bool as_int_or_float, MYSQLND_STATS * const stats) { - unsigned int i; - const zend_uchar * p = row_buffer->ptr; + unsigned int i, j; + size_t rbs = row_buffer->size; + const zend_uchar * rbp = row_buffer->ptr; + const zend_uchar * p = rbp; const zend_uchar * null_ptr; zend_uchar bit; zval *current_field, *end_field, *start_field; @@ -1468,7 +1481,21 @@ php_mysqlnd_rowp_read_binary_protocol(MYSQLND_ROW_BUFFER * row_buffer, zval * fi statistic = STAT_BINARY_TYPE_FETCHED_NULL; } else { enum_mysqlnd_field_types type = fields_metadata[i].type; - mysqlnd_ps_fetch_functions[type].func(current_field, &fields_metadata[i], 0, &p); + size_t row_position = p - rbp; + if (rbs <= row_position) { + for (j = 0, current_field = start_field; j < i; current_field++, j++) { + zval_ptr_dtor(current_field); + } + php_error_docref(NULL, E_WARNING, "Malformed server packet. No packet space left for the field"); + DBG_RETURN(FAIL); + } + mysqlnd_ps_fetch_functions[type].func(current_field, &fields_metadata[i], rbs - row_position, &p); + if (p == NULL) { + for (j = 0, current_field = start_field; j < i; current_field++, j++) { + zval_ptr_dtor(current_field); + } + DBG_RETURN(FAIL); + } if (MYSQLND_G(collect_statistics)) { switch (fields_metadata[i].type) { @@ -1525,7 +1552,7 @@ php_mysqlnd_rowp_read_text_protocol(MYSQLND_ROW_BUFFER * row_buffer, zval * fiel unsigned int field_count, const MYSQLND_FIELD * fields_metadata, bool as_int_or_float, MYSQLND_STATS * stats) { - unsigned int i; + unsigned int i, j; zval *current_field, *end_field, *start_field; zend_uchar * p = row_buffer->ptr; const size_t data_size = row_buffer->size; @@ -1546,9 +1573,11 @@ php_mysqlnd_rowp_read_text_protocol(MYSQLND_ROW_BUFFER * row_buffer, zval * fiel /* NULL or NOT NULL, this is the question! */ if (len == MYSQLND_NULL_LENGTH) { ZVAL_NULL(current_field); - } else if ((p + len) > packet_end) { - php_error_docref(NULL, E_WARNING, "Malformed server packet. Field length pointing %zu" - " bytes after end of packet", (p + len) - packet_end - 1); + } else if (p > packet_end || len > packet_end - p) { + php_error_docref(NULL, E_WARNING, "Malformed server packet. Field length pointing after end of packet"); + for (j = 0, current_field = start_field; j < i; current_field++, j++) { + zval_ptr_dtor(current_field); + } DBG_RETURN(FAIL); } else { struct st_mysqlnd_perm_bind perm_bind = From a21e48a93a24e0fc7769936cd211b20766d437e4 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 18 Nov 2024 11:05:43 +0100 Subject: [PATCH 06/76] Make MySQLnd protocol stmt test work on 32bit --- ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt b/ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt index d461ec24b8c07..af16a9eb2d05f 100644 --- a/ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt +++ b/ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt @@ -40,7 +40,7 @@ $process->terminate(true); print "done!"; ?> ---EXPECT-- +--EXPECTF-- [*] Server started [*] Connection established [*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 @@ -80,7 +80,7 @@ string(19) "2014-12-16 13:00:01" [*] Sending - Stmt prepare data bitval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe00000200 [*] Received: 0a00000017010000000001000000 [*] Sending - Stmt execute data bitval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe00002200100000050000047465737408080808080808080805000006fe00002200 -int(578721382704613384) +%s578721382704613384%s [*] Received: 050000001901000000200000001653454c4543542073747276616c2c2073747276616c2046524f4d2064617461 [*] Sending - Stmt prepare data strval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe00000200 [*] Received: 0a00000017010000000001000000 From d37a20c4a24a70dbbcfd8724cd2ad5f1b005bd2a Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 18 Nov 2024 15:54:30 +0100 Subject: [PATCH 07/76] Fix MySQLnd possible buffer over read in auth_protocol --- ext/mysqlnd/mysqlnd_wireprotocol.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c index a75c70450104a..19debe98089da 100644 --- a/ext/mysqlnd/mysqlnd_wireprotocol.c +++ b/ext/mysqlnd/mysqlnd_wireprotocol.c @@ -447,8 +447,31 @@ php_mysqlnd_greet_read(MYSQLND_CONN_DATA * conn, void * _packet) if (packet->server_capabilities & CLIENT_PLUGIN_AUTH) { BAIL_IF_NO_MORE_DATA; /* The server is 5.5.x and supports authentication plugins */ - packet->auth_protocol = estrdup((char *)p); - p+= strlen(packet->auth_protocol) + 1; /* eat the '\0' */ + size_t remaining_size = packet->header.size - (size_t)(p - buf); + if (remaining_size == 0) { + /* Might be better to fail but this will fail anyway */ + packet->auth_protocol = estrdup(""); + } else { + /* Check if NUL present */ + char *null_terminator = memchr(p, '\0', remaining_size); + size_t auth_protocol_len; + if (null_terminator) { + /* If present, do basically estrdup */ + auth_protocol_len = null_terminator - (char *)p; + } else { + /* If not present, copy the rest of the buffer */ + auth_protocol_len = remaining_size; + } + char *auth_protocol = emalloc(auth_protocol_len + 1); + memcpy(auth_protocol, p, auth_protocol_len); + auth_protocol[auth_protocol_len] = '\0'; + packet->auth_protocol = auth_protocol; + + p += auth_protocol_len; + if (null_terminator) { + p++; + } + } } DBG_INF_FMT("proto=%u server=%s thread_id=%u", From f3ade203d7ca2e70f09e4e29b6fe7f614fcc0821 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Fri, 8 Nov 2024 22:04:21 +0100 Subject: [PATCH 08/76] Fix GHSA-r977-prxv-hc43 Move the bound check upwards. Since this doesn't generate output we can check the bound first. --- ext/standard/filters.c | 7 ++++--- ext/standard/tests/filters/ghsa-r977-prxv-hc43.phpt | 12 ++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 ext/standard/tests/filters/ghsa-r977-prxv-hc43.phpt diff --git a/ext/standard/filters.c b/ext/standard/filters.c index b390ac7b0a212..9b54b3deab18a 100644 --- a/ext/standard/filters.c +++ b/ext/standard/filters.c @@ -996,6 +996,9 @@ static php_conv_err_t php_conv_qprint_decode_convert(php_conv_qprint_decode *ins } break; case 5: { + if (icnt == 0) { + goto out; + } if (!inst->lbchars && lb_cnt == 1 && *ps == '\n') { /* auto-detect soft line breaks, found network line break */ lb_cnt = lb_ptr = 0; @@ -1009,15 +1012,13 @@ static php_conv_err_t php_conv_qprint_decode_convert(php_conv_qprint_decode *ins /* soft line break */ lb_cnt = lb_ptr = 0; scan_stat = 0; - } else if (icnt > 0) { + } else { if (*ps == (unsigned char)inst->lbchars[lb_cnt]) { lb_cnt++; ps++, icnt--; } else { scan_stat = 6; /* no break for short-cut */ } - } else { - goto out; } } break; diff --git a/ext/standard/tests/filters/ghsa-r977-prxv-hc43.phpt b/ext/standard/tests/filters/ghsa-r977-prxv-hc43.phpt new file mode 100644 index 0000000000000..8fdcce8ff22e0 --- /dev/null +++ b/ext/standard/tests/filters/ghsa-r977-prxv-hc43.phpt @@ -0,0 +1,12 @@ +--TEST-- +GHSA-r977-prxv-hc43: Single byte overread with convert.quoted-printable-decode filter +--FILE-- + +--EXPECT-- +string(8190) "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAX" From f18d429b2008a3e7addce2d9444019077b9eda1c Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 9 Nov 2024 15:29:52 +0100 Subject: [PATCH 09/76] Fix GHSA-4w77-75f9-2c8w --- sapi/cli/php_cli_server.c | 2 ++ sapi/cli/tests/ghsa-4w77-75f9-2c8w.phpt | 41 +++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 sapi/cli/tests/ghsa-4w77-75f9-2c8w.phpt diff --git a/sapi/cli/php_cli_server.c b/sapi/cli/php_cli_server.c index 422576e96abfc..753196f5f79cd 100644 --- a/sapi/cli/php_cli_server.c +++ b/sapi/cli/php_cli_server.c @@ -1944,6 +1944,8 @@ static void php_cli_server_client_populate_request_info(const php_cli_server_cli request_info->auth_user = request_info->auth_password = request_info->auth_digest = NULL; if (NULL != (val = zend_hash_str_find(&client->request.headers, "content-type", sizeof("content-type")-1))) { request_info->content_type = Z_STRVAL_P(val); + } else { + request_info->content_type = NULL; } } /* }}} */ diff --git a/sapi/cli/tests/ghsa-4w77-75f9-2c8w.phpt b/sapi/cli/tests/ghsa-4w77-75f9-2c8w.phpt new file mode 100644 index 0000000000000..2c8aeff12d594 --- /dev/null +++ b/sapi/cli/tests/ghsa-4w77-75f9-2c8w.phpt @@ -0,0 +1,41 @@ +--TEST-- +GHSA-4w77-75f9-2c8w (Heap-Use-After-Free in sapi_read_post_data Processing in CLI SAPI Interface) +--INI-- +allow_url_fopen=1 +--SKIPIF-- + +--FILE-- + [ + "method" => "POST", + "header" => "Content-Type: application/x-www-form-urlencoded", + "content" => "AAAAA", + ], +]; +$context = stream_context_create($options); + +echo file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS . "/", context: $context); + +$options = [ + "http" => [ + "method" => "POST", + ], +]; +$context = stream_context_create($options); + +echo file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS . "/", context: $context); +?> +--EXPECT-- +string(5) "AAAAA" +string(0) "" From 78c201a31004bce631340cdbbd995d6e5cc888ae Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 18 Nov 2024 16:59:19 +0100 Subject: [PATCH 10/76] Update NEWS with security fixes info --- NEWS | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 56f3e942334c0..80c0e8ae79b77 100644 --- a/NEWS +++ b/NEWS @@ -46,9 +46,11 @@ PHP NEWS 21 Nov 2024, PHP 8.2.26 -- Cli: +- CLI: . Fixed bug GH-16373 (Shebang is not skipped for router script in cli-server started through shebang). (ilutov) + . Fixed bug GHSA-4w77-75f9-2c8w (Heap-Use-After-Free in sapi_read_post_data + Processing in CLI SAPI Interface). (nielsdos) - COM: . Fixed out of bound writes to SafeArray data. (cmb) @@ -123,10 +125,18 @@ PHP NEWS . Fixed segfaults and other issues related to operator overloading with GMP objects. (Girgias) +- LDAP: + . Fixed bug GHSA-g665-fm4p-vhff (OOB access in ldap_escape). (CVE-2024-8932) + (nielsdos) + - MBstring: . Fixed bug GH-16361 (mb_substr overflow on start/length arguments). (David Carlier) +- MySQLnd: + . Fixed bug GHSA-h35g-vwh6-m678 (Leak partial content of the heap through + heap buffer over-read). (CVE-2024-8929) (Jakub Zelenka) + - OpenSSL: . Fixed bug GH-16357 (openssl may modify member types of certificate arrays). (cmb) @@ -135,7 +145,15 @@ PHP NEWS . Fix various memory leaks on error conditions in openssl_x509_parse(). (nielsdos) -- PDO_ODBC: +- PDO DBLIB: + . Fixed bug GHSA-5hqh-c84r-qjcv (Integer overflow in the dblib quoter causing + OOB writes). (CVE-2024-11236) (nielsdos) + +- PDO Firebird: + . Fixed bug GHSA-5hqh-c84r-qjcv (Integer overflow in the firebird quoter + causing OOB writes). (CVE-2024-11236) (nielsdos) + +- PDO ODBC: . Fixed bug GH-16450 (PDO_ODBC can inject garbage into field values). (cmb) - Phar: @@ -180,6 +198,12 @@ PHP NEWS . Fixed bug GH-16293 (Failed assertion when throwing in assert() callback with bail enabled). (ilutov) +- Streams: + . Fixed bug GHSA-c5f2-jwm7-mmq2 (Configuring a proxy in a stream context + might allow for CRLF injection in URIs). (CVE-2024-11234) (Jakub Zelenka) + . Fixed bug GHSA-r977-prxv-hc43 (Single byte overread with + convert.quoted-printable-decode filter). (CVE-2024-11233) (nielsdos) + - SysVMsg: . Fixed bug GH-16592 (msg_send() crashes when a type does not properly serialized). (David Carlier / cmb) From 9acf0a40094c1be40ea45dd355a224594ac7c1c8 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 20 Nov 2024 13:41:39 +0100 Subject: [PATCH 11/76] [skip ci] Backport GA root workflow changes --- .github/workflows/root.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/root.yml b/.github/workflows/root.yml index f526e9bea30d5..cefabd0394a46 100644 --- a/.github/workflows/root.yml +++ b/.github/workflows/root.yml @@ -46,12 +46,17 @@ jobs: matrix: branch: ${{ fromJson(needs.GENERATE_MATRIX.outputs.branches) }} with: - asan_ubuntu_version: '20.04' + asan_ubuntu_version: ${{ + (((matrix.branch.version[0] == 8 && matrix.branch.version[1] >= 5) || matrix.branch.version[0] >= 9) && '24.04') + || '20.04' }} branch: ${{ matrix.branch.ref }} community_verify_type_inference: ${{ (matrix.branch.version[0] == 8 && matrix.branch.version[1] >= 4) || matrix.branch.version[0] >= 9 }} libmysqlclient_with_mysqli: ${{ (matrix.branch.version[0] == 8 && matrix.branch.version[1] == 1) }} run_alpine: ${{ (matrix.branch.version[0] == 8 && matrix.branch.version[1] >= 4) || matrix.branch.version[0] >= 9 }} run_macos_arm64: ${{ (matrix.branch.version[0] == 8 && matrix.branch.version[1] >= 4) || matrix.branch.version[0] >= 9 }} - ubuntu_version: ${{ ((matrix.branch.version[0] == 8 && matrix.branch.version[1] >= 3) || matrix.branch.version[0] >= 9) && '22.04' || '20.04' }} + ubuntu_version: ${{ + (((matrix.branch.version[0] == 8 && matrix.branch.version[1] >= 5) || matrix.branch.version[0] >= 9) && '24.04') + || ((matrix.branch.version[0] == 8 && matrix.branch.version[1] >= 3) && '22.04') + || '20.04' }} windows_version: ${{ ((matrix.branch.version[0] == 8 && matrix.branch.version[1] >= 4) || matrix.branch.version[0] >= 9) && '2022' || '2019' }} secrets: inherit From c70b97d8eb833faaff74e5caa4b44e180b7ed49b Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 18 Nov 2024 11:05:43 +0100 Subject: [PATCH 12/76] Make MySQLnd protocol stmt test work on 32bit Closes GH-16869. --- ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt b/ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt index d461ec24b8c07..af16a9eb2d05f 100644 --- a/ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt +++ b/ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt @@ -40,7 +40,7 @@ $process->terminate(true); print "done!"; ?> ---EXPECT-- +--EXPECTF-- [*] Server started [*] Connection established [*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 @@ -80,7 +80,7 @@ string(19) "2014-12-16 13:00:01" [*] Sending - Stmt prepare data bitval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe00000200 [*] Received: 0a00000017010000000001000000 [*] Sending - Stmt execute data bitval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe00002200100000050000047465737408080808080808080805000006fe00002200 -int(578721382704613384) +%s578721382704613384%s [*] Received: 050000001901000000200000001653454c4543542073747276616c2c2073747276616c2046524f4d2064617461 [*] Sending - Stmt prepare data strval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe00000200 [*] Received: 0a00000017010000000001000000 From e23ac8341a7c755f1061134fc3a6ef4788f9595c Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 20 Nov 2024 13:53:05 +0100 Subject: [PATCH 13/76] [skip ci] Don't test mysqli with libmysqlclient 8.4 There are compile errors with 8.4 that we are no longer fixing. --- .github/workflows/nightly.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index b87b7389ef02d..c4187ab5a796b 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -814,12 +814,14 @@ jobs: with: withMysqli: ${{ inputs.libmysqlclient_with_mysqli }} - name: Build mysql-8.4 + if: ${{ !inputs.libmysqlclient_with_mysqli }} uses: ./.github/actions/build-libmysqlclient with: configurationParameters: ${{ !inputs.libmysqlclient_with_mysqli && '--enable-werror' || '' }} libmysql: mysql-8.4.0-linux-glibc2.28-x86_64.tar.xz withMysqli: ${{ inputs.libmysqlclient_with_mysqli }} - name: Test mysql-8.4 + if: ${{ !inputs.libmysqlclient_with_mysqli }} uses: ./.github/actions/test-libmysqlclient with: withMysqli: ${{ inputs.libmysqlclient_with_mysqli }} From aca88baf5f89c0f4ebc08232c5c6d1ac0a949a8e Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Fri, 15 Nov 2024 16:23:36 +0100 Subject: [PATCH 14/76] Move FreeBSD build to GitHub actions with QEMU Closes GH-16822 --- .cirrus.yml | 28 -------- .github/actions/freebsd/action.yml | 102 +++++++++++++++++++++++++++++ .github/workflows/nightly.yml | 10 +++ .github/workflows/push.yml | 8 +++ 4 files changed, 120 insertions(+), 28 deletions(-) delete mode 100644 .cirrus.yml create mode 100644 .github/actions/freebsd/action.yml diff --git a/.cirrus.yml b/.cirrus.yml deleted file mode 100644 index b4bc63538bea7..0000000000000 --- a/.cirrus.yml +++ /dev/null @@ -1,28 +0,0 @@ -env: - CIRRUS_CLONE_DEPTH: 1 - -freebsd_task: - name: FREEBSD_DEBUG_NTS - freebsd_instance: - image_family: freebsd-13-3 - env: - ARCH: amd64 - install_script: - #- sed -i -e 's/quarterly/latest/g' /etc/pkg/FreeBSD.conf - #- pkg upgrade -y - - kldload accf_http - - pkg install -y autoconf bison gmake re2c icu libiconv png freetype2 enchant2 bzip2 krb5 t1lib gmp tidyp libsodium libzip libxml2 libxslt openssl oniguruma pkgconf webp libavif - script: - - ./buildconf -f - - ./configure CFLAGS="-Wno-strict-prototypes -Wno-unused-but-set-variable -Wno-single-bit-bitfield-constant-conversion -Wno-unused-result" --prefix=/usr/local --enable-debug --enable-option-checking=fatal --enable-fpm --with-pdo-sqlite --without-pear --with-bz2 --with-avif --with-jpeg --with-webp --with-freetype --enable-gd --enable-exif --with-zip --with-zlib --enable-soap --enable-xmlreader --with-xsl --with-libxml --enable-shmop --enable-pcntl --enable-mbstring --with-curl --enable-sockets --with-openssl --with-iconv=/usr/local --enable-bcmath --enable-calendar --enable-ftp --with-kerberos --with-ffi --enable-zend-test --enable-dl-test=shared --enable-intl --with-mhash --with-sodium --enable-werror --with-config-file-path=/etc --with-config-file-scan-dir=/etc/php.d - - gmake -j2 - - mkdir /etc/php.d - - gmake install - - echo opcache.enable_cli=1 > /etc/php.d/opcache.ini - - echo opcache.protect_memory=1 >> /etc/php.d/opcache.ini - # Specify opcache.preload_user as we're running as root. - - echo opcache.preload_user=root >> /etc/php.d/opcache.ini - tests_script: - - export SKIP_IO_CAPTURE_TESTS=1 - - export CI_NO_IPV6=1 - - sapi/cli/php run-tests.php -P -q -j2 -g FAIL,XFAIL,BORK,WARN,LEAK,XLEAK,SKIP --offline --show-diff --show-slow 1000 --set-timeout 120 -d zend_extension=opcache.so diff --git a/.github/actions/freebsd/action.yml b/.github/actions/freebsd/action.yml new file mode 100644 index 0000000000000..1abc4b81992d3 --- /dev/null +++ b/.github/actions/freebsd/action.yml @@ -0,0 +1,102 @@ +name: FreeBSD +runs: + using: composite + steps: + - name: FreeBSD + uses: vmactions/freebsd-vm@v1 + with: + release: '13.3' + usesh: true + # Temporarily disable sqlite, as FreeBSD ships it with disabled double quotes. We'll need to fix our tests. + # https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=269889 + prepare: | + cd $GITHUB_WORKSPACE + + kldload accf_http + pkg install -y \ + autoconf \ + bison \ + gmake \ + re2c \ + icu \ + libiconv \ + png \ + freetype2 \ + enchant2 \ + bzip2 \ + t1lib \ + gmp \ + tidyp \ + libsodium \ + libzip \ + libxml2 \ + libxslt \ + openssl \ + oniguruma \ + pkgconf \ + webp \ + libavif \ + `#sqlite3` \ + curl + + ./buildconf -f + ./configure \ + --prefix=/usr/local \ + --enable-debug \ + --enable-option-checking=fatal \ + --enable-fpm \ + `#--with-pdo-sqlite` \ + --without-sqlite3 \ + --without-pdo-sqlite \ + --without-pear \ + --with-bz2 \ + --with-avif \ + --with-jpeg \ + --with-webp \ + --with-freetype \ + --enable-gd \ + --enable-exif \ + --with-zip \ + --with-zlib \ + --enable-soap \ + --enable-xmlreader \ + --with-xsl \ + --with-libxml \ + --enable-shmop \ + --enable-pcntl \ + --enable-mbstring \ + --with-curl \ + --enable-sockets \ + --with-openssl \ + --with-iconv=/usr/local \ + --enable-bcmath \ + --enable-calendar \ + --enable-ftp \ + --with-ffi \ + --enable-zend-test \ + --enable-dl-test=shared \ + --enable-intl \ + --with-mhash \ + --with-sodium \ + --with-config-file-path=/etc \ + --with-config-file-scan-dir=/etc/php.d + gmake -j2 + mkdir /etc/php.d + gmake install > /dev/null + echo opcache.enable_cli=1 > /etc/php.d/opcache.ini + echo opcache.protect_memory=1 >> /etc/php.d/opcache.ini + echo opcache.preload_user=root >> /etc/php.d/opcache.ini + run: | + cd $GITHUB_WORKSPACE + + export SKIP_IO_CAPTURE_TESTS=1 + export CI_NO_IPV6=1 + export STACK_LIMIT_DEFAULTS_CHECK=1 + sapi/cli/php run-tests.php \ + -P -q -j2 \ + -g FAIL,BORK,LEAK,XLEAK \ + --offline \ + --show-diff \ + --show-slow 1000 \ + --set-timeout 120 \ + -d zend_extension=opcache.so diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index c4187ab5a796b..90e9a1d7b760c 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -981,3 +981,13 @@ jobs: run: .github/scripts/windows/build.bat - name: Test run: .github/scripts/windows/test.bat + FREEBSD: + name: FREEBSD + runs-on: ubuntu-latest + steps: + - name: git checkout + uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch }} + - name: FreeBSD + uses: ./.github/actions/freebsd diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index b1f84e4022479..5264a3d290fac 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -170,3 +170,11 @@ jobs: run: .github/scripts/windows/build.bat - name: Test run: .github/scripts/windows/test.bat + FREEBSD: + name: FREEBSD + runs-on: ubuntu-latest + steps: + - name: git checkout + uses: actions/checkout@v4 + - name: FreeBSD + uses: ./.github/actions/freebsd From 02ef371eabaa29d96e6ba1a261e528b388a9c216 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 20 Nov 2024 14:25:54 +0100 Subject: [PATCH 15/76] [skip ci] Add --no-progress to FreeBSD build --- .github/actions/freebsd/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/freebsd/action.yml b/.github/actions/freebsd/action.yml index 1abc4b81992d3..ffa8187680121 100644 --- a/.github/actions/freebsd/action.yml +++ b/.github/actions/freebsd/action.yml @@ -95,6 +95,7 @@ runs: sapi/cli/php run-tests.php \ -P -q -j2 \ -g FAIL,BORK,LEAK,XLEAK \ + --no-progress \ --offline \ --show-diff \ --show-slow 1000 \ From b3ab930a2f5c17bdcf1363c47860103ce8168f83 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 20 Nov 2024 14:28:40 +0100 Subject: [PATCH 16/76] Enable -Werror for FreeBSD build --- .github/actions/freebsd/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/freebsd/action.yml b/.github/actions/freebsd/action.yml index ffa8187680121..f7c89a6961eb4 100644 --- a/.github/actions/freebsd/action.yml +++ b/.github/actions/freebsd/action.yml @@ -78,6 +78,7 @@ runs: --enable-intl \ --with-mhash \ --with-sodium \ + --enable-werror \ --with-config-file-path=/etc \ --with-config-file-scan-dir=/etc/php.d gmake -j2 From 3656a84c3909c93a8cad5df01daf9c057b2e6ee9 Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Thu, 7 Nov 2024 13:53:20 +0100 Subject: [PATCH 17/76] Skip file_put_contents_variation7.phpt on Windows While the test obviously succeeds on Windows, it may occasionally conflict with file_put_contents_variation7-win32.phpt[1], so we skip it like we do for many other of these tests which have win32 pendants. [1] Closes GH-16722. --- ext/standard/tests/file/file_put_contents_variation7.phpt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ext/standard/tests/file/file_put_contents_variation7.phpt b/ext/standard/tests/file/file_put_contents_variation7.phpt index 6e380dc4f653e..b62a85d14361e 100644 --- a/ext/standard/tests/file/file_put_contents_variation7.phpt +++ b/ext/standard/tests/file/file_put_contents_variation7.phpt @@ -2,6 +2,11 @@ Test file_put_contents() function : usage variation - various absolute and relative paths --CREDITS-- Dave Kelsey +--SKIPIF-- + --FILE-- Date: Tue, 19 Nov 2024 19:29:32 +0100 Subject: [PATCH 18/76] Fix GH-16851: JIT_G(enabled) not set correctly on other threads There doesn't seem to be a thread post-startup hook that runs after zend_startup_cb() that could be used for this this fix is similar to accel_startup_ok() as seen here: https://github.com/php/php-src/blob/fc1db70f106525e81f9a24539340b7cf2e82e844/ext/opcache/ZendAccelerator.c#L2631-L2634 Closes GH-16853. --- NEWS | 2 ++ ext/opcache/ZendAccelerator.c | 2 ++ ext/opcache/jit/zend_jit.c | 9 +++++++++ ext/opcache/jit/zend_jit.h | 2 ++ 4 files changed, 15 insertions(+) diff --git a/NEWS b/NEWS index 80c0e8ae79b77..7a460ba142486 100644 --- a/NEWS +++ b/NEWS @@ -27,6 +27,8 @@ PHP NEWS - Opcache: . Fixed bug GH-16770 (Tracing JIT type mismatch when returning UNDEF). (nielsdos, Dmitry) + . Fixed bug GH-16851 (JIT_G(enabled) not set correctly on other threads). + (dktapps) - OpenSSL: . Prevent unexpected array entry conversion when reading key. (nielsdos) diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 9bcd035c35295..1afcff2d1c243 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -3283,6 +3283,8 @@ static zend_result accel_post_startup(void) if (JIT_G(buffer_size) != 0) { zend_accel_error(ACCEL_LOG_WARNING, "Could not enable JIT!"); } + } else { + zend_jit_startup_ok = true; } } #endif diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 4e1c8e290bb32..8e81e187ec628 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -103,6 +103,8 @@ typedef struct _zend_jit_stub { #define JIT_STUB(name, offset, adjustment) \ {JIT_STUB_PREFIX #name, zend_jit_ ## name ## _stub, offset, adjustment} +bool zend_jit_startup_ok = false; + zend_ulong zend_jit_profile_counter = 0; int zend_jit_profile_counter_rid = -1; @@ -5096,6 +5098,13 @@ static void zend_jit_reset_counters(void) ZEND_EXT_API void zend_jit_activate(void) { +#ifdef ZTS + if (!zend_jit_startup_ok) { + JIT_G(enabled) = 0; + JIT_G(on) = 0; + return; + } +#endif zend_jit_profile_counter = 0; if (JIT_G(on)) { if (JIT_G(trigger) == ZEND_JIT_ON_HOT_COUNTERS) { diff --git a/ext/opcache/jit/zend_jit.h b/ext/opcache/jit/zend_jit.h index d22422181af9c..f0a6a81cf6ca9 100644 --- a/ext/opcache/jit/zend_jit.h +++ b/ext/opcache/jit/zend_jit.h @@ -91,6 +91,8 @@ typedef struct _zend_jit_trace_rec zend_jit_trace_rec; typedef struct _zend_jit_trace_stack_frame zend_jit_trace_stack_frame; typedef struct _sym_node zend_sym_node; +extern bool zend_jit_startup_ok; + typedef struct _zend_jit_globals { bool enabled; bool on; From 83ca37483c87f1de9384a6977f069589606c8640 Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Wed, 20 Nov 2024 19:45:36 +0100 Subject: [PATCH 19/76] Revert "Merge branch 'PHP-8.3' into PHP-8.4" This reverts commit ae62779386fe2a736412873ca6931296101529d5, reversing changes made to 19e685ecc467a0f1dd5413f033fc6311e118473d. This was a bad merge; I'll have a look shortly. --- NEWS | 6 ------ ext/opcache/ZendAccelerator.c | 2 -- ext/opcache/jit/zend_jit.c | 9 --------- ext/opcache/jit/zend_jit.h | 2 -- 4 files changed, 19 deletions(-) diff --git a/NEWS b/NEWS index 7505ebcd39f29..337fc0b44fdb2 100644 --- a/NEWS +++ b/NEWS @@ -1,11 +1,5 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.4.2 - -- Opcache: - . Fixed bug GH-16851 (JIT_G(enabled) not set correctly on other threads). - (dktapps) - 21 Nov 2024, PHP 8.4.1 - BcMath: diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 374fbcb1f9438..e0f8cda2298e8 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -3282,8 +3282,6 @@ static zend_result accel_post_startup(void) zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Could not enable JIT: could not use reserved buffer!"); } else { zend_jit_startup(ZSMMG(reserved), jit_size, reattached); - } else { - zend_jit_startup_ok = true; } } #endif diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 0c6ab6c5cbc8c..54ffd79a35871 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -69,8 +69,6 @@ zend_jit_globals jit_globals; #define JIT_STUB_PREFIX "JIT$$" #define TRACE_PREFIX "TRACE-" -bool zend_jit_startup_ok = false; - zend_ulong zend_jit_profile_counter = 0; int zend_jit_profile_counter_rid = -1; @@ -3636,13 +3634,6 @@ static void zend_jit_reset_counters(void) void zend_jit_activate(void) { -#ifdef ZTS - if (!zend_jit_startup_ok) { - JIT_G(enabled) = 0; - JIT_G(on) = 0; - return; - } -#endif zend_jit_profile_counter = 0; if (JIT_G(on)) { if (JIT_G(trigger) == ZEND_JIT_ON_HOT_COUNTERS) { diff --git a/ext/opcache/jit/zend_jit.h b/ext/opcache/jit/zend_jit.h index 9178d340a0ede..0ce6c1a4409a2 100644 --- a/ext/opcache/jit/zend_jit.h +++ b/ext/opcache/jit/zend_jit.h @@ -100,8 +100,6 @@ typedef struct _zend_jit_trace_rec zend_jit_trace_rec; typedef struct _zend_jit_trace_stack_frame zend_jit_trace_stack_frame; typedef struct _sym_node zend_sym_node; -extern bool zend_jit_startup_ok; - typedef struct _zend_jit_globals { bool enabled; bool on; From da81b5c8d220257f79b70afd8a3e7e437c4f4869 Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Wed, 20 Nov 2024 23:24:43 +0100 Subject: [PATCH 20/76] Reapply "Merge branch 'PHP-8.3' into PHP-8.4" This reverts commit 83ca37483c87f1de9384a6977f069589606c8640, and fixes the previous bad merge. --- NEWS | 6 ++++++ ext/opcache/ZendAccelerator.c | 1 + ext/opcache/jit/zend_jit.c | 9 +++++++++ ext/opcache/jit/zend_jit.h | 2 ++ 4 files changed, 18 insertions(+) diff --git a/NEWS b/NEWS index 337fc0b44fdb2..7505ebcd39f29 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,11 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| +?? ??? ????, PHP 8.4.2 + +- Opcache: + . Fixed bug GH-16851 (JIT_G(enabled) not set correctly on other threads). + (dktapps) + 21 Nov 2024, PHP 8.4.1 - BcMath: diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index e0f8cda2298e8..bf536c08153e2 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -3282,6 +3282,7 @@ static zend_result accel_post_startup(void) zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Could not enable JIT: could not use reserved buffer!"); } else { zend_jit_startup(ZSMMG(reserved), jit_size, reattached); + zend_jit_startup_ok = true; } } #endif diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 54ffd79a35871..0c6ab6c5cbc8c 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -69,6 +69,8 @@ zend_jit_globals jit_globals; #define JIT_STUB_PREFIX "JIT$$" #define TRACE_PREFIX "TRACE-" +bool zend_jit_startup_ok = false; + zend_ulong zend_jit_profile_counter = 0; int zend_jit_profile_counter_rid = -1; @@ -3634,6 +3636,13 @@ static void zend_jit_reset_counters(void) void zend_jit_activate(void) { +#ifdef ZTS + if (!zend_jit_startup_ok) { + JIT_G(enabled) = 0; + JIT_G(on) = 0; + return; + } +#endif zend_jit_profile_counter = 0; if (JIT_G(on)) { if (JIT_G(trigger) == ZEND_JIT_ON_HOT_COUNTERS) { diff --git a/ext/opcache/jit/zend_jit.h b/ext/opcache/jit/zend_jit.h index 0ce6c1a4409a2..9178d340a0ede 100644 --- a/ext/opcache/jit/zend_jit.h +++ b/ext/opcache/jit/zend_jit.h @@ -100,6 +100,8 @@ typedef struct _zend_jit_trace_rec zend_jit_trace_rec; typedef struct _zend_jit_trace_stack_frame zend_jit_trace_stack_frame; typedef struct _sym_node zend_sym_node; +extern bool zend_jit_startup_ok; + typedef struct _zend_jit_globals { bool enabled; bool on; From 9ee607823eae02996f4d2f17d778041b76ec3e19 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Fri, 22 Nov 2024 08:56:14 +0100 Subject: [PATCH 21/76] [ci skip] UPGRADING fixes (#16893) It seems at one point these were (partially) rewritten, changing their meaning. This patch fixes this. --- UPGRADING | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/UPGRADING b/UPGRADING index 31b51a2c5fb9f..3bab45da53e74 100644 --- a/UPGRADING +++ b/UPGRADING @@ -127,12 +127,12 @@ PHP 8.4 UPGRADE NOTES ValueErrors if it is not an array of class names. . XMLReader: . Passing an invalid character encoding to XMLReader::open() or - XMLReader::XML() now throws a ValueError. - . Passing a string containing null bytes previously emitted a - warning and now throws a ValueError as well. + XMLReader::XML() now throws a ValueError. Passing an encoding + containing null bytes previously emitted a warning and now throws + a ValueError as well. . XMLWriter: - . Passing a string containing null bytes previously emitted a - warning and now throws a ValueError as well. + . Passing an encoding containing null bytes previously emitted a + warning and now throws a ValueError. . XSL: . XSLTProcessor::setParameter() will now throw a ValueError when its arguments contain null bytes. This never actually worked correctly in From 9d39ff764ed6b2f81f997b6bc0b8bc7122589ce5 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 23 Nov 2024 15:58:48 +0100 Subject: [PATCH 22/76] Fix GH-16906: Reloading document can cause UAF in iterator Closes GH-16909. --- NEWS | 2 ++ ext/dom/php_dom.c | 6 ++++++ ext/dom/php_dom.h | 1 + ext/dom/tests/gh16906.phpt | 17 +++++++++++++++++ 4 files changed, 26 insertions(+) create mode 100644 ext/dom/tests/gh16906.phpt diff --git a/NEWS b/NEWS index e6f1e5a9339e3..30d156a354017 100644 --- a/NEWS +++ b/NEWS @@ -21,6 +21,8 @@ PHP NEWS - DOM: . Fixed bug GH-16777 (Calling the constructor again on a DOM object after it is in a document causes UAF). (nielsdos) + . Fixed bug GH-16906 (Reloading document can cause UAF in iterator). + (nielsdos) - FPM: . Fixed GH-16432 (PHP-FPM 8.2 SIGSEGV in fpm_get_status). (Jakub Zelenka) diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index 5c7aacefba5d0..7ec107dd712e3 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -1018,6 +1018,10 @@ void dom_namednode_iter(dom_object *basenode, int ntype, dom_object *intern, xml mapptr->baseobj = basenode; mapptr->nodetype = ntype; mapptr->ht = ht; + if (EXPECTED(doc != NULL)) { + mapptr->dict = doc->dict; + xmlDictReference(doc->dict); + } const xmlChar* tmp; @@ -1128,6 +1132,7 @@ void dom_nnodemap_objects_free_storage(zend_object *object) /* {{{ */ if (!Z_ISUNDEF(objmap->baseobj_zv)) { zval_ptr_dtor(&objmap->baseobj_zv); } + xmlDictFree(objmap->dict); efree(objmap); intern->ptr = NULL; } @@ -1158,6 +1163,7 @@ zend_object *dom_nnodemap_objects_new(zend_class_entry *class_type) objmap->cached_length = -1; objmap->cached_obj = NULL; objmap->cached_obj_index = 0; + objmap->dict = NULL; return &intern->std; } diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h index fe0c5471c6ca4..2bccb2d5692d5 100644 --- a/ext/dom/php_dom.h +++ b/ext/dom/php_dom.h @@ -89,6 +89,7 @@ typedef struct _dom_nnodemap_object { php_libxml_cache_tag cache_tag; dom_object *cached_obj; zend_long cached_obj_index; + xmlDictPtr dict; bool free_local : 1; bool free_ns : 1; } dom_nnodemap_object; diff --git a/ext/dom/tests/gh16906.phpt b/ext/dom/tests/gh16906.phpt new file mode 100644 index 0000000000000..791ca13b390e0 --- /dev/null +++ b/ext/dom/tests/gh16906.phpt @@ -0,0 +1,17 @@ +--TEST-- +GH-16906 (Reloading document can cause UAF in iterator) +--EXTENSIONS-- +dom +--FILE-- +loadXML(''); +$list = $doc->getElementsByTagName('strong'); +$doc->load(__DIR__."/book.xml"); +var_dump($list); +?> +--EXPECT-- +object(DOMNodeList)#2 (1) { + ["length"]=> + int(0) +} From 866d8d7ce68ea4434f3393ca390926f68243c098 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 24 Nov 2024 18:45:22 +0000 Subject: [PATCH 23/76] Update code owners for mysqli, mysqlnd and pdo_mysql [ci skip] (#16921) --- .github/CODEOWNERS | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0c064cf935047..ba67073c6afb3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -30,7 +30,8 @@ /ext/json @bukka /ext/libxml @nielsdos /ext/mbstring @alexdowad @youkidearitai -/ext/mysqlnd @SakiTakamachi +/ext/mysqli @bukka @kamil-tekiela +/ext/mysqlnd @bukka @kamil-tekiela @SakiTakamachi /ext/odbc @NattyNarwhal /ext/opcache @dstogov /ext/openssl @bukka @@ -38,7 +39,7 @@ /ext/pdo @SakiTakamachi /ext/pdo_dblib @SakiTakamachi /ext/pdo_firebird @SakiTakamachi -/ext/pdo_mysql @SakiTakamachi +/ext/pdo_mysql @kamil-tekiela @SakiTakamachi /ext/pdo_odbc @NattyNarwhal @SakiTakamachi /ext/pdo_pgsql @devnexen @SakiTakamachi /ext/pdo_sqlite @SakiTakamachi From 51f5539914ae62ef8568ea1ed302dceda897c439 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 24 Nov 2024 20:13:47 +0100 Subject: [PATCH 24/76] Change port for mysqli fake server auth message test --- ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt index db54a6c0177a1..279aec6a2cba1 100644 --- a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt +++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt @@ -6,7 +6,7 @@ mysqli Date: Sun, 24 Nov 2024 23:48:27 +0100 Subject: [PATCH 25/76] Increase MySQLi fake server read timeout for ASAN job --- ext/mysqli/tests/fake_server.inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/mysqli/tests/fake_server.inc b/ext/mysqli/tests/fake_server.inc index b02fabc584c5d..1127f6c00e3f9 100644 --- a/ext/mysqli/tests/fake_server.inc +++ b/ext/mysqli/tests/fake_server.inc @@ -552,8 +552,8 @@ class my_mysqli_fake_server_conn public function read($bytes_len = 1024) { - // wait 10ms to fill the buffer - usleep(10000); + // wait 20ms to fill the buffer + usleep(20000); $data = fread($this->conn, $bytes_len); if ($data) { fprintf(STDERR, "[*] Received: %s\n", bin2hex($data)); From ba8e3e1d798e12fba7a999967ae63e49b4ef22f4 Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Mon, 25 Nov 2024 10:29:41 +0300 Subject: [PATCH 26/76] Update IR IR commit: ff7ee6c1e6090ba0ba7b47cb77939518304fab6b --- ext/opcache/jit/ir/ir.c | 24 ++++++++++++++++++++++++ ext/opcache/jit/ir/ir.h | 2 ++ ext/opcache/jit/ir/ir_aarch64.dasc | 8 ++++++++ ext/opcache/jit/ir/ir_cfg.c | 16 +++------------- ext/opcache/jit/ir/ir_fold.h | 28 ++++++++++++++++++++++++++++ ext/opcache/jit/ir/ir_private.h | 19 +++++++++++++++++++ ext/opcache/jit/ir/ir_sccp.c | 4 ++-- 7 files changed, 86 insertions(+), 15 deletions(-) diff --git a/ext/opcache/jit/ir/ir.c b/ext/opcache/jit/ir/ir.c index 5da5beaae4e50..2ee00416b7dc3 100644 --- a/ext/opcache/jit/ir/ir.c +++ b/ext/opcache/jit/ir/ir.c @@ -1092,6 +1092,22 @@ void ir_set_op(ir_ctx *ctx, ir_ref ref, int32_t n, ir_ref val) ir_insn_set_op(insn, n, val); } +ir_ref ir_get_op(ir_ctx *ctx, ir_ref ref, int32_t n) +{ + ir_insn *insn = &ctx->ir_base[ref]; + +#ifdef IR_DEBUG + if (n > 3) { + int32_t count; + + IR_ASSERT(IR_OP_HAS_VAR_INPUTS(ir_op_flags[insn->op])); + count = insn->inputs_count; + IR_ASSERT(n <= count); + } +#endif + return ir_insn_op(insn, n); +} + ir_ref ir_param(ir_ctx *ctx, ir_type type, ir_ref region, const char *name, int pos) { return ir_emit(ctx, IR_OPT(IR_PARAM, type), region, ir_str(ctx, name), pos); @@ -2820,6 +2836,10 @@ void _ir_VSTORE(ir_ctx *ctx, ir_ref var, ir_ref val) } } else if (insn->op == IR_VLOAD) { if (insn->op2 == var) { + if (ref == val) { + /* dead STORE */ + return; + } break; } } else if (insn->op == IR_GUARD || insn->op == IR_GUARD_NOT) { @@ -2910,6 +2930,10 @@ void _ir_STORE(ir_ctx *ctx, ir_ref addr, ir_ref val) } } else if (insn->op == IR_LOAD) { if (insn->op2 == addr) { + if (ref == val) { + /* dead STORE */ + return; + } break; } type2 = insn->type; diff --git a/ext/opcache/jit/ir/ir.h b/ext/opcache/jit/ir/ir.h index cf7580dc7496a..86e6dd51a9865 100644 --- a/ext/opcache/jit/ir/ir.h +++ b/ext/opcache/jit/ir/ir.h @@ -720,6 +720,8 @@ IR_ALWAYS_INLINE void ir_set_op3(ir_ctx *ctx, ir_ref ref, ir_ref val) ctx->ir_base[ref].op3 = val; } +ir_ref ir_get_op(ir_ctx *ctx, ir_ref ref, int32_t n); + IR_ALWAYS_INLINE ir_ref ir_insn_op(const ir_insn *insn, int32_t n) { const ir_ref *p = insn->ops + n; diff --git a/ext/opcache/jit/ir/ir_aarch64.dasc b/ext/opcache/jit/ir/ir_aarch64.dasc index 11c0f320dd2b1..d9d0041c01bce 100644 --- a/ext/opcache/jit/ir/ir_aarch64.dasc +++ b/ext/opcache/jit/ir/ir_aarch64.dasc @@ -5247,6 +5247,14 @@ static void ir_emit_tls(ir_ctx *ctx, ir_ref def, ir_insn *insn) | ldr Rx(reg), [Rx(reg), #insn->op2] | ldr Rx(reg), [Rx(reg), #insn->op3] || } +||# elif defined(__MUSL__) +|| if (insn->op3 == IR_NULL) { +| ldr Rx(reg), [Rx(reg), #insn->op2] +|| } else { +| ldr Rx(reg), [Rx(reg), #-8] +| ldr Rx(reg), [Rx(reg), #insn->op2] +| ldr Rx(reg), [Rx(reg), #insn->op3] +|| } ||# else ||//??? IR_ASSERT(insn->op2 <= LDR_STR_PIMM64); | ldr Rx(reg), [Rx(reg), #insn->op2] diff --git a/ext/opcache/jit/ir/ir_cfg.c b/ext/opcache/jit/ir/ir_cfg.c index c2893dcf292d6..0a36d5d9880d7 100644 --- a/ext/opcache/jit/ir/ir_cfg.c +++ b/ext/opcache/jit/ir/ir_cfg.c @@ -59,7 +59,7 @@ IR_ALWAYS_INLINE void _ir_add_predecessors(const ir_insn *insn, ir_worklist *wor int ir_build_cfg(ir_ctx *ctx) { - ir_ref n, *p, ref, start, end, next; + ir_ref n, *p, ref, start, end; uint32_t b; ir_insn *insn; ir_worklist worklist; @@ -145,18 +145,8 @@ int ir_build_cfg(ir_ctx *ctx) start = ref; /* Skip control nodes untill BB end */ while (1) { - use_list = &ctx->use_lists[ref]; - n = use_list->count; - next = IR_UNUSED; - for (p = &ctx->use_edges[use_list->refs]; n > 0; p++, n--) { - next = *p; - insn = &ctx->ir_base[next]; - if ((ir_op_flags[insn->op] & IR_OP_FLAG_CONTROL) && insn->op1 == ref) { - break; - } - } - IR_ASSERT(next != IR_UNUSED); - ref = next; + ref = ir_next_control(ctx, ref); + insn = &ctx->ir_base[ref]; if (IR_IS_BB_END(insn->op)) { break; } diff --git a/ext/opcache/jit/ir/ir_fold.h b/ext/opcache/jit/ir/ir_fold.h index 2e65ae3119f17..b23ea832df962 100644 --- a/ext/opcache/jit/ir/ir_fold.h +++ b/ext/opcache/jit/ir/ir_fold.h @@ -1513,6 +1513,34 @@ IR_FOLD(NOT(UGT)) IR_FOLD_NEXT; } +IR_FOLD(EQ(SUB, C_U8)) +IR_FOLD(EQ(SUB, C_U16)) +IR_FOLD(EQ(SUB, C_U32)) +IR_FOLD(EQ(SUB, C_U64)) +IR_FOLD(EQ(SUB, C_I8)) +IR_FOLD(EQ(SUB, C_I16)) +IR_FOLD(EQ(SUB, C_I32)) +IR_FOLD(EQ(SUB, C_I64)) +IR_FOLD(EQ(SUB, C_ADDR)) +IR_FOLD(NE(SUB, C_U8)) +IR_FOLD(NE(SUB, C_U16)) +IR_FOLD(NE(SUB, C_U32)) +IR_FOLD(NE(SUB, C_U64)) +IR_FOLD(NE(SUB, C_I8)) +IR_FOLD(NE(SUB, C_I16)) +IR_FOLD(NE(SUB, C_I32)) +IR_FOLD(NE(SUB, C_I64)) +IR_FOLD(NE(SUB, C_ADDR)) +{ + /* (a - b) == 0 => a == b */ + if (ctx->use_lists && ctx->use_lists[op1].count == 1 && op2_insn->val.u64 == 0) { + op1 = op1_insn->op1; + op2 = op1_insn->op2; + IR_FOLD_RESTART; + } + IR_FOLD_NEXT; +} + IR_FOLD(ADD(_, C_U8)) IR_FOLD(ADD(_, C_U16)) IR_FOLD(ADD(_, C_U32)) diff --git a/ext/opcache/jit/ir/ir_private.h b/ext/opcache/jit/ir/ir_private.h index 064d713b20147..f88a1574a286e 100644 --- a/ext/opcache/jit/ir/ir_private.h +++ b/ext/opcache/jit/ir/ir_private.h @@ -1032,6 +1032,25 @@ void ir_use_list_replace_all(ir_ctx *ctx, ir_ref ref, ir_ref use, ir_ref new_use void ir_use_list_replace_one(ir_ctx *ctx, ir_ref ref, ir_ref use, ir_ref new_use); bool ir_use_list_add(ir_ctx *ctx, ir_ref to, ir_ref new_use); +IR_ALWAYS_INLINE ir_ref ir_next_control(const ir_ctx *ctx, ir_ref ref) +{ + ir_use_list *use_list = &ctx->use_lists[ref]; + ir_ref n = use_list->count; + ir_ref *p; + + IR_ASSERT(ir_op_flags[ctx->ir_base[ref].op] & IR_OP_FLAG_CONTROL); + for (p = &ctx->use_edges[use_list->refs]; n > 0; p++, n--) { + ir_ref next = *p; + ir_insn *insn = &ctx->ir_base[next]; + + if ((ir_op_flags[insn->op] & IR_OP_FLAG_CONTROL) && insn->op1 == ref) { + return next; + } + } + IR_ASSERT(0); + return IR_UNUSED; +} + /*** Modification helpers ***/ #define MAKE_NOP(_insn) do { \ ir_insn *__insn = _insn; \ diff --git a/ext/opcache/jit/ir/ir_sccp.c b/ext/opcache/jit/ir/ir_sccp.c index c5665873aa989..3705df45901e0 100644 --- a/ext/opcache/jit/ir/ir_sccp.c +++ b/ext/opcache/jit/ir/ir_sccp.c @@ -2213,7 +2213,7 @@ int ir_sccp(ir_ctx *ctx) if (!may_benefit) { IR_MAKE_BOTTOM(i); if (insn->op == IR_FP2FP || insn->op == IR_FP2INT || insn->op == IR_TRUNC - || insn->op == IR_ZEXT || insn->op == IR_SEXT) { + || insn->op == IR_ZEXT || insn->op == IR_SEXT || insn->op == IR_EQ || insn->op == IR_NE) { ir_bitqueue_add(&worklist2, i); } } else if (!ir_sccp_fold(ctx, _values, i, insn->opt, insn->op1, insn->op2, insn->op3)) { @@ -2222,7 +2222,7 @@ int ir_sccp(ir_ctx *ctx) } else if (_values[i].optx == IR_BOTTOM) { insn = &ctx->ir_base[i]; if (insn->op == IR_FP2FP || insn->op == IR_FP2INT || insn->op == IR_TRUNC - || insn->op == IR_ZEXT || insn->op == IR_SEXT) { + || insn->op == IR_ZEXT || insn->op == IR_SEXT || insn->op == IR_EQ || insn->op == IR_NE) { ir_bitqueue_add(&worklist2, i); } } From cfcf5cfde8a1d69b14c00c8e83c17d529e698729 Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Thu, 21 Nov 2024 17:51:49 +0100 Subject: [PATCH 27/76] Fix GH-16890: array_sum() with GMP can loose precision (LLP64) We must use `mpz_fits_si_p()` instead of `mpz_fits_slong_p()` since the latter is not suitable for LLP64 data models. libgmp, however, does not define `mpz_fits_si_p()` (which is an mpir addition), so we use `mpz_fits_slong_p()` there which should be fine. Closes GH-16891. --- NEWS | 4 ++++ ext/gmp/gmp.c | 6 +++++- ext/gmp/tests/gh16890.phpt | 11 +++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 ext/gmp/tests/gh16890.phpt diff --git a/NEWS b/NEWS index 30d156a354017..cf40cc748ccf0 100644 --- a/NEWS +++ b/NEWS @@ -30,6 +30,10 @@ PHP NEWS - GD: . Fixed GH-16776 (imagecreatefromstring overflow). (David Carlier) +- GMP: + . Fixed bug GH-16890 (array_sum() with GMP can loose precision (LLP64)). + (cmb) + - Hash: . Fixed GH-16711: Segfault in mhash(). (Girgias) diff --git a/ext/gmp/gmp.c b/ext/gmp/gmp.c index 994e95409ad86..dfb762e717c55 100644 --- a/ext/gmp/gmp.c +++ b/ext/gmp/gmp.c @@ -32,6 +32,10 @@ /* Needed for gmp_random() */ #include "ext/random/php_random.h" +#ifndef mpz_fits_si_p +# define mpz_fits_si_p mpz_fits_slong_p +#endif + #define GMP_ROUND_ZERO 0 #define GMP_ROUND_PLUSINF 1 #define GMP_ROUND_MINUSINF 2 @@ -292,7 +296,7 @@ static zend_result gmp_cast_object(zend_object *readobj, zval *writeobj, int typ return SUCCESS; case _IS_NUMBER: gmpnum = GET_GMP_OBJECT_FROM_OBJ(readobj)->num; - if (mpz_fits_slong_p(gmpnum)) { + if (mpz_fits_si_p(gmpnum)) { ZVAL_LONG(writeobj, mpz_get_si(gmpnum)); } else { ZVAL_DOUBLE(writeobj, mpz_get_d(gmpnum)); diff --git a/ext/gmp/tests/gh16890.phpt b/ext/gmp/tests/gh16890.phpt new file mode 100644 index 0000000000000..08fc060559625 --- /dev/null +++ b/ext/gmp/tests/gh16890.phpt @@ -0,0 +1,11 @@ +--TEST-- +GH-16890 (array_sum() with GMP can loose precision (LLP64)) +--EXTENSIONS-- +gmp +--FILE-- + +--EXPECT-- +bool(true) From b263f351c46125156db356a2cfb840c06a8174af Mon Sep 17 00:00:00 2001 From: Ayesh Karunaratne Date: Sun, 24 Nov 2024 21:30:24 +0700 Subject: [PATCH 28/76] CI: FreeBSD on VM - set `copyback: false` The `vmactions/freebsd-vm` GitHub action rsyncs the work dir to to the VM. This adds a lot of log output due to `rsync -v` usage. Once the tests are compelte, the action copies the files _back_ by running `rsync` in reverse. However, we do not need these files back because we do not run any other steps that need access to the post-test files. Setting `copyback: false` disables this, and cuts the log size by about 5,000 lines. Closes Closes GH-16916. --- .github/actions/freebsd/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/freebsd/action.yml b/.github/actions/freebsd/action.yml index 1abc4b81992d3..ce9ba24451582 100644 --- a/.github/actions/freebsd/action.yml +++ b/.github/actions/freebsd/action.yml @@ -7,6 +7,7 @@ runs: with: release: '13.3' usesh: true + copyback: false # Temporarily disable sqlite, as FreeBSD ships it with disabled double quotes. We'll need to fix our tests. # https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=269889 prepare: | From 12ef3da381140aa9094ce7fa9f3330ed85791c29 Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Tue, 19 Nov 2024 00:57:03 +0100 Subject: [PATCH 29/76] Fix potential conflict of copy_variation5-win32.phpt copy_variation1.phpt also creates and deletes a file copy.tmp in the same folder, so conflicts may occur[1]. We apply a quick fix. [1] Closes GH-16854. --- .../tests/file/copy_variation5-win32.phpt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ext/standard/tests/file/copy_variation5-win32.phpt b/ext/standard/tests/file/copy_variation5-win32.phpt index d3f75262a1857..af352dbe07322 100644 --- a/ext/standard/tests/file/copy_variation5-win32.phpt +++ b/ext/standard/tests/file/copy_variation5-win32.phpt @@ -22,9 +22,9 @@ fclose($file_handle); $dest_files = array( /* Checking case sensitiveness */ - "COPY.tmp", - "COPY.TMP", - "CopY.TMP" + "COPY5.tmp", + "COPY5.TMP", + "CopY5.TMP" ); echo "Size of the source file before copy operation => "; @@ -80,25 +80,25 @@ Size of the source file before copy operation => int(1500) -- Iteration 1 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/COPY.tmp +Destination file name => %s/COPY5.tmp Size of source file => int(1500) Size of destination file => int(1500) -- Iteration 2 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/COPY.TMP +Destination file name => %s/COPY5.TMP Size of source file => int(1500) Size of destination file => int(1500) -- Iteration 3 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/CopY.TMP +Destination file name => %s/CopY5.TMP Size of source file => int(1500) Size of destination file => int(1500) -Warning: unlink(%s/COPY.TMP): No such file or directory in %s on line %d +Warning: unlink(%s/COPY5.TMP): No such file or directory in %s on line %d -Warning: unlink(%s/CopY.TMP): No such file or directory in %s on line %d +Warning: unlink(%s/CopY5.TMP): No such file or directory in %s on line %d *** Done *** From 99f5653ebbc6d139d1ad4b60126003ed7d242e29 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 24 Nov 2024 15:55:27 +0100 Subject: [PATCH 30/76] Fix GH-16908: _ZendTestMagicCallForward does not handle references well This testing code was never meant to be used this way, but fixing this will at least stop fuzzers from complaining about this, so might still be worthwhile. Closes GH-16919. --- ext/zend_test/test.c | 7 +++++-- ext/zend_test/tests/gh16908.phpt | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 ext/zend_test/tests/gh16908.phpt diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 100ef25800f9d..b6f833c231f20 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -902,9 +902,12 @@ static ZEND_METHOD(_ZendTestMagicCallForward, __call) ZEND_IGNORE_VALUE(arguments); - zval func; + zval func, rv; ZVAL_STR(&func, name); - call_user_function(NULL, NULL, &func, return_value, 0, NULL); + call_user_function(NULL, NULL, &func, &rv, 0, NULL); + + ZVAL_COPY_DEREF(return_value, &rv); + zval_ptr_dtor(&rv); } PHP_INI_BEGIN() diff --git a/ext/zend_test/tests/gh16908.phpt b/ext/zend_test/tests/gh16908.phpt new file mode 100644 index 0000000000000..670cfa579a801 --- /dev/null +++ b/ext/zend_test/tests/gh16908.phpt @@ -0,0 +1,20 @@ +--TEST-- +GH-16908 (_ZendTestMagicCallForward does not handle references well) +--EXTENSIONS-- +zend_test +--FILE-- +foo()->x ??= 42; +?> +--EXPECTF-- +Notice: Only variable references should be returned by reference in %s on line %d + +Notice: Only variable references should be returned by reference in %s on line %d + +Fatal error: Uncaught Error: Attempt to assign property "x" on null in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d From de96b43d2ad0ce7fabf983b23eabc5bc82dd8b88 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 24 Nov 2024 21:02:35 +0100 Subject: [PATCH 31/76] Fix GH-16902: Set of opcache tests fail zts+aarch64 (8.2-8.3) Closes GH-16925. --- NEWS | 1 + ext/opcache/jit/zend_jit_arm64.dasc | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/NEWS b/NEWS index 7a460ba142486..c199250730258 100644 --- a/NEWS +++ b/NEWS @@ -29,6 +29,7 @@ PHP NEWS (nielsdos, Dmitry) . Fixed bug GH-16851 (JIT_G(enabled) not set correctly on other threads). (dktapps) + . Fixed bug GH-16902 (Set of opcache tests fail zts+aarch64). (nielsdos) - OpenSSL: . Prevent unexpected array entry conversion when reading key. (nielsdos) diff --git a/ext/opcache/jit/zend_jit_arm64.dasc b/ext/opcache/jit/zend_jit_arm64.dasc index f911c8331163f..1e31c075d6b5d 100644 --- a/ext/opcache/jit/zend_jit_arm64.dasc +++ b/ext/opcache/jit/zend_jit_arm64.dasc @@ -504,7 +504,11 @@ static bool logical_immediate_p(uint64_t value, uint32_t reg_size) ||#else | .long 0xd53bd051 // TODO: hard-coded: mrs TMP3, tpidr_el0 || if (tsrm_ls_cache_tcb_offset == 0) { +||#ifdef __MUSL__ +| ldr TMP3, [TMP3, #-8] +||#else | ldr TMP3, [TMP3, #0] +||#endif | MEM_ACCESS_64_WITH_UOFFSET_64 ldr, TMP3, TMP3, tsrm_tls_index, TMP1 | MEM_ACCESS_64_WITH_UOFFSET_64 ldr, reg, TMP3, tsrm_tls_offset, TMP1 || } else { @@ -2790,6 +2794,20 @@ static int zend_jit_setup(void) /* Index is offset by 1 on FreeBSD (https://github.com/freebsd/freebsd-src/blob/22ca6db50f4e6bd75a141f57cf953d8de6531a06/lib/libc/gen/tls.c#L88) */ tsrm_tls_index = (tlsdesc->index + 1) * 8; } +# elif defined(__MUSL__) + if (tsrm_ls_cache_tcb_offset == 0) { + size_t **where; + + __asm__( + "adrp %0, :tlsdesc:_tsrm_ls_cache\n" + "add %0, %0, :tlsdesc_lo12:_tsrm_ls_cache\n" + : "=r" (where)); + /* See https://github.com/ARM-software/abi-aa/blob/2a70c42d62e9c3eb5887fa50b71257f20daca6f9/aaelf64/aaelf64.rst */ + size_t *tlsdesc = where[1]; + + tsrm_tls_offset = tlsdesc[1]; + tsrm_tls_index = tlsdesc[0] * 8; + } # else ZEND_ASSERT(tsrm_ls_cache_tcb_offset != 0); # endif From f4ca6d27947917df4ef62c5ef178a1a658d2aa37 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 24 Nov 2024 19:40:23 +0100 Subject: [PATCH 32/76] Fix GH-16902: Set of opcache tests fail zts+aarch64 (8.4+) Accompanying IR PR: https://github.com/dstogov/ir/pull/95 Closes GH-16924. --- ext/opcache/jit/zend_jit_ir.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 6245852c7b840..9a10a23b2148c 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -3233,6 +3233,20 @@ static void zend_jit_setup(void) /* Index is offset by 1 on FreeBSD (https://github.com/freebsd/freebsd-src/blob/22ca6db50f4e6bd75a141f57cf953d8de6531a06/lib/libc/gen/tls.c#L88) */ tsrm_tls_index = (tlsdesc->index + 1) * 8; } +# elif defined(__MUSL__) + if (tsrm_ls_cache_tcb_offset == 0) { + size_t **where; + + __asm__( + "adrp %0, :tlsdesc:_tsrm_ls_cache\n" + "add %0, %0, :tlsdesc_lo12:_tsrm_ls_cache\n" + : "=r" (where)); + /* See https://github.com/ARM-software/abi-aa/blob/2a70c42d62e9c3eb5887fa50b71257f20daca6f9/aaelf64/aaelf64.rst */ + size_t *tlsdesc = where[1]; + + tsrm_tls_offset = tlsdesc[1]; + tsrm_tls_index = tlsdesc[0] * 8; + } # else ZEND_ASSERT(tsrm_ls_cache_tcb_offset != 0); # endif From ff95138e38d1182932c36354c1ba6d175567ff45 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 25 Nov 2024 20:33:14 +0100 Subject: [PATCH 33/76] Fix test expectation for PHP 8.3+ --- ext/zend_test/tests/gh16908.phpt | 2 -- 1 file changed, 2 deletions(-) diff --git a/ext/zend_test/tests/gh16908.phpt b/ext/zend_test/tests/gh16908.phpt index 670cfa579a801..becb2775c7efa 100644 --- a/ext/zend_test/tests/gh16908.phpt +++ b/ext/zend_test/tests/gh16908.phpt @@ -12,8 +12,6 @@ $cls->foo()->x ??= 42; --EXPECTF-- Notice: Only variable references should be returned by reference in %s on line %d -Notice: Only variable references should be returned by reference in %s on line %d - Fatal error: Uncaught Error: Attempt to assign property "x" on null in %s:%d Stack trace: #0 {main} From 0b5de3014a38bec7acc8088177eb5d41e2077113 Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Mon, 25 Nov 2024 12:34:15 +0100 Subject: [PATCH 34/76] Use another for bug51056.phpt This port is already used by san_ipv6_peer_matching.phpt, wo we choose another port which is not explicitly used in our test suite. The proper solution would be to use ephemeral ports[1], but our OpenSSL `ServerClientTestCase` does not support this yet. [1] Closes GH-16871. --- ext/standard/tests/streams/bug51056.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/standard/tests/streams/bug51056.phpt b/ext/standard/tests/streams/bug51056.phpt index abb87fcb037c8..e011405298c24 100644 --- a/ext/standard/tests/streams/bug51056.phpt +++ b/ext/standard/tests/streams/bug51056.phpt @@ -4,7 +4,7 @@ Bug #51056 (fread() on blocking stream will block even if data is available) Date: Tue, 19 Nov 2024 20:38:24 -0300 Subject: [PATCH 35/76] [skip ci] chore: update minimum required PHP version. Closes GH-16868. --- run-tests.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run-tests.php b/run-tests.php index 5587c6c0aeb17..8d4c8340e0d41 100755 --- a/run-tests.php +++ b/run-tests.php @@ -26,7 +26,7 @@ /* Let there be no top-level code beyond this point: * Only functions and classes, thanks! * - * Minimum required PHP version: 7.4.0 + * Minimum required PHP version: 8.0.0 */ function show_usage(): void From e75061b5127788a692b8de6097d970dbac3b210e Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Mon, 18 Nov 2024 22:54:28 +0100 Subject: [PATCH 36/76] Fix GH-16849: Error dialog causes process to hang If `_DEBUG` is set, assertion failures and errors are directed to a debug message window by default[1]. That causes a process to hang, since these dialogs are modal. While we already cater to assertion failures, errors have apparently been overlooked. We choose a minimal fix for BC reasons; although passing `0` as `reportMode` is undocumented, it obviously works fine for a long time. We may consider to improve on this for the `master` branch. [1] Closes GH-16850. --- NEWS | 3 +++ main/main.c | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index c199250730258..d7c37b39d3a1c 100644 --- a/NEWS +++ b/NEWS @@ -47,6 +47,9 @@ PHP NEWS . Fixed bug GH-16808 (Segmentation fault in RecursiveIteratorIterator ->current() with a xml element input). (nielsdos) +- Windows: + . Fixed bug GH-16849 (Error dialog causes process to hang). (cmb) + 21 Nov 2024, PHP 8.2.26 - CLI: diff --git a/main/main.c b/main/main.c index 3e03951e87755..b38ef8d2ecf2f 100644 --- a/main/main.c +++ b/main/main.c @@ -2070,8 +2070,9 @@ zend_result php_module_startup(sapi_module_struct *sf, zend_module_entry *additi _set_invalid_parameter_handler(old_invalid_parameter_handler); } - /* Disable the message box for assertions.*/ + /* Disable the message box for assertions and errors.*/ _CrtSetReportMode(_CRT_ASSERT, 0); + _CrtSetReportMode(_CRT_ERROR, 0); #else php_os = PHP_OS; #endif From f086eaa7b871bd95612333e616828f0e2f45ed31 Mon Sep 17 00:00:00 2001 From: David Warner Date: Sun, 17 Nov 2024 22:21:08 +1100 Subject: [PATCH 37/76] Add Windows Server 2025 build number Closes GH-16838. --- NEWS | 1 + ext/standard/info.c | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 88f27025ac8c6..e2216670399ed 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,7 @@ PHP NEWS - Windows: . Fixed bug GH-16849 (Error dialog causes process to hang). (cmb) + . Windows Server 2025 is now properly reported. (cmb) 21 Nov 2024, PHP 8.4.1 diff --git a/ext/standard/info.c b/ext/standard/info.c index 8a706ef62eb57..4ed33f32eabcf 100644 --- a/ext/standard/info.c +++ b/ext/standard/info.c @@ -271,7 +271,9 @@ static char* php_get_windows_name() major = "Windows 10"; } } else { - if (osvi.dwBuildNumber >= 20348) { + if (osvi.dwBuildNumber >= 26100) { + major = "Windows Server 2025"; + } else if (osvi.dwBuildNumber >= 20348) { major = "Windows Server 2022"; } else if (osvi.dwBuildNumber >= 19042) { major = "Windows Server, version 20H2"; From c310be09ed2aa9a26299f6ee266a4ef082fd5b63 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Thu, 10 Oct 2024 16:39:36 +0200 Subject: [PATCH 38/76] Fix setRawValueWithoutLazyInitialization() and skipLazyInitialization() on initialized proxy Normally, accesses to properties marked as lazy trigger the object's initialization, or forward to a real instance if the object is an initialized proxy. The purpose of ReflectionProperty::setRawValueWithoutLazyInitialization() and ReflectionProperty::skipLazyInitialization() is to bypass auto-initialization, so that some properties can be initialized without triggering initialization. However, when the object is an initialized proxy, these methods would unexpectedly update the proxy. Here I make sure that these methods have an effect on the real instance, when the object is an initialized proxy. Fixes GH-16344 --- NEWS | 4 +++ ...WithoutLazyInitialization_initialized.phpt | 31 ++++++++++++++++++- ...LazyInitialization_initialized_object.phpt | 31 ++++++++++++++++++- ext/reflection/php_reflection.c | 12 +++++-- 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index e2216670399ed..0a9f3bf699686 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,10 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.4.2 +- Core: + . Fixed bug GH-16344 (setRawValueWithoutLazyInitialization() and + skipLazyInitialization() may change initialized proxy). (Arnaud) + - DOM: . Fixed bug GH-16906 (Reloading document can cause UAF in iterator). (nielsdos) diff --git a/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_initialized.phpt b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_initialized.phpt index caa5211f371a5..25580b32ae2cc 100644 --- a/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_initialized.phpt +++ b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_initialized.phpt @@ -17,6 +17,7 @@ function test(string $name, object $obj) { $reflector->initializeLazyObject($obj); $reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, 'test'); + var_dump($obj->a); var_dump($obj); } @@ -33,9 +34,22 @@ $obj = $reflector->newLazyProxy(function () { test('Proxy', $obj); +$real = new C('foo'); +$obj = $reflector->newLazyProxy(function () use ($real) { + return $real; +}); +$reflector->initializeLazyObject($obj); +$reflector->resetAsLazyProxy($real, function () { + return new C('bar'); +}); +$reflector->initializeLazyObject($real); + +test('Nested Proxy', $obj); + ?> --EXPECTF-- # Ghost +string(4) "test" object(C)#%d (2) { ["a"]=> string(4) "test" @@ -43,12 +57,27 @@ object(C)#%d (2) { NULL } # Proxy +string(4) "test" lazy proxy object(C)#%d (1) { ["instance"]=> object(C)#%d (2) { ["a"]=> - NULL + string(4) "test" ["b"]=> NULL } } +# Nested Proxy +string(4) "test" +lazy proxy object(C)#%d (1) { + ["instance"]=> + lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["a"]=> + string(4) "test" + ["b"]=> + NULL + } + } +} diff --git a/Zend/tests/lazy_objects/skipLazyInitialization_initialized_object.phpt b/Zend/tests/lazy_objects/skipLazyInitialization_initialized_object.phpt index bf8ff2094ca15..8c2a52de53511 100644 --- a/Zend/tests/lazy_objects/skipLazyInitialization_initialized_object.phpt +++ b/Zend/tests/lazy_objects/skipLazyInitialization_initialized_object.phpt @@ -36,6 +36,19 @@ $obj = $reflector->newLazyProxy(function () { test('Proxy', $obj); +$real = new C('foo'); +$obj = $reflector->newLazyProxy(function () use ($real) { + return $real; +}); +$reflector->initializeLazyObject($obj); +$reflector->resetAsLazyProxy($real, function () { + var_dump("initializer"); + return new C('bar'); +}); +$reflector->initializeLazyObject($real); + +test('Nested Proxy', $obj); + ?> --EXPECTF-- # Ghost @@ -48,7 +61,7 @@ object(C)#%d (2) { NULL } # Proxy -int(1) +int(2) bool(true) lazy proxy object(C)#%d (1) { ["instance"]=> @@ -59,3 +72,19 @@ lazy proxy object(C)#%d (1) { NULL } } +string(11) "initializer" +# Nested Proxy +int(2) +bool(true) +lazy proxy object(C)#%d (1) { + ["instance"]=> + lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["a"]=> + int(2) + ["b"]=> + NULL + } + } +} diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 8bf9a7ccebe41..da1ef2cdffe73 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -6228,6 +6228,11 @@ ZEND_METHOD(ReflectionProperty, setRawValueWithoutLazyInitialization) RETURN_THROWS(); } + while (zend_object_is_lazy_proxy(object) + && zend_lazy_object_initialized(object)) { + object = zend_lazy_object_get_instance(object); + } + zval *var_ptr = OBJ_PROP(object, ref->prop->offset); bool prop_was_lazy = Z_PROP_FLAG_P(var_ptr) & IS_PROP_LAZY; @@ -6271,7 +6276,10 @@ ZEND_METHOD(ReflectionProperty, skipLazyInitialization) RETURN_THROWS(); } - bool prop_was_lazy = (Z_PROP_FLAG_P(OBJ_PROP(object, ref->prop->offset)) & IS_PROP_LAZY); + while (zend_object_is_lazy_proxy(object) + && zend_lazy_object_initialized(object)) { + object = zend_lazy_object_get_instance(object); + } zval *src = &object->ce->default_properties_table[OBJ_PROP_TO_NUM(ref->prop->offset)]; zval *dst = OBJ_PROP(object, ref->prop->offset); @@ -6286,7 +6294,7 @@ ZEND_METHOD(ReflectionProperty, skipLazyInitialization) ZVAL_COPY_PROP(dst, src); /* Object becomes non-lazy if this was the last lazy prop */ - if (prop_was_lazy && zend_object_is_lazy(object) + if (zend_object_is_lazy(object) && !zend_lazy_object_initialized(object)) { if (zend_lazy_object_decr_lazy_props(object)) { zend_lazy_object_realize(object); From 7892a0ec8a60995106560093f973ad665b0b3415 Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Tue, 26 Nov 2024 16:59:57 +0300 Subject: [PATCH 39/76] Fix GH-16913: 8.4 function JIT memory corruption (#16943) with '#' will be ignored, and an empty message aborts the commit. --- ext/opcache/jit/zend_jit_ir.c | 37 +++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 9a10a23b2148c..0331d0e7b6e0d 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -11523,6 +11523,32 @@ static int zend_jit_rope(zend_jit_ctx *jit, const zend_op *opline, uint32_t op2_ return 1; } +static int zend_jit_zval_copy_deref_reg(zend_jit_ctx *jit, zend_jit_addr res_addr, uint32_t res_info, zend_jit_addr val_addr, ir_ref type, ir_ref *values) +{ + ir_ref if_type, val; + + if (res_info == MAY_BE_LONG) { + if_type = ir_IF(ir_EQ(type, ir_CONST_U32(IS_LONG))); + ir_IF_TRUE(if_type); + val = jit_ZVAL_ADDR(jit, val_addr); + ir_END_PHI_list(*values, val); + ir_IF_FALSE(if_type); + val = ir_ADD_OFFSET(jit_Z_PTR(jit, val_addr), offsetof(zend_reference, val)); + ir_END_PHI_list(*values, val); + } else if (res_info == MAY_BE_DOUBLE) { + if_type = ir_IF(ir_EQ(type, ir_CONST_U32(IS_DOUBLE))); + ir_IF_TRUE(if_type); + val = jit_ZVAL_ADDR(jit, val_addr); + ir_END_PHI_list(*values, val); + ir_IF_FALSE(if_type); + val = ir_ADD_OFFSET(jit_Z_PTR(jit, val_addr), offsetof(zend_reference, val)); + ir_END_PHI_list(*values, val); + } else { + ZEND_UNREACHABLE(); + } + return 1; +} + static int zend_jit_zval_copy_deref(zend_jit_ctx *jit, zend_jit_addr res_addr, zend_jit_addr val_addr, ir_ref type) { ir_ref if_refcounted, if_reference, if_refcounted2, ptr, val2, ptr2, type2; @@ -14253,9 +14279,16 @@ static int zend_jit_fetch_obj(zend_jit_ctx *jit, } ir_END_list(end_inputs); } else { - if (((res_info & MAY_BE_GUARD) && JIT_G(current_frame) && prop_info) - || Z_MODE(res_addr) == IS_REG) { + if ((res_info & MAY_BE_GUARD) && JIT_G(current_frame) && prop_info) { ir_END_PHI_list(end_values, jit_ZVAL_ADDR(jit, prop_addr)); + } else if ((res_info & MAY_BE_GUARD) && Z_MODE(res_addr) == IS_REG) { + ir_END_PHI_list(end_values, jit_ZVAL_ADDR(jit, prop_addr)); + } else if (Z_MODE(res_addr) == IS_REG) { + prop_type_ref = jit_Z_TYPE_INFO(jit, prop_addr); + + if (!zend_jit_zval_copy_deref_reg(jit, res_addr, res_info & ~MAY_BE_GUARD, prop_addr, prop_type_ref, &end_values)) { + return 0; + } } else { prop_type_ref = jit_Z_TYPE_INFO(jit, prop_addr); From d1b9d7ee831ddacabf51b2e3c434398890f710f0 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Tue, 26 Nov 2024 15:40:46 +0000 Subject: [PATCH 40/76] Fixed CS --- ext/filter/logical_filters.c | 45 ++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/ext/filter/logical_filters.c b/ext/filter/logical_filters.c index 7ac64444fa7f9..0ce2498f3a3a2 100644 --- a/ext/filter/logical_filters.c +++ b/ext/filter/logical_filters.c @@ -925,13 +925,13 @@ void php_filter_validate_ip(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ if (flags & FILTER_FLAG_GLOBAL_RANGE) { if ( - (ip[0] == 100 && ip[1] >= 64 && ip[1] <= 127 ) || - (ip[0] == 192 && ip[1] == 0 && ip[2] == 0 ) || - (ip[0] == 192 && ip[1] == 0 && ip[2] == 2 ) || - (ip[0] == 198 && ip[1] >= 18 && ip[1] <= 19 ) || - (ip[0] == 198 && ip[1] == 51 && ip[2] == 100 ) || - (ip[0] == 203 && ip[1] == 0 && ip[2] == 113 ) - ) { + (ip[0] == 100 && ip[1] >= 64 && ip[1] <= 127 ) || + (ip[0] == 192 && ip[1] == 0 && ip[2] == 0 ) || + (ip[0] == 192 && ip[1] == 0 && ip[2] == 2 ) || + (ip[0] == 198 && ip[1] >= 18 && ip[1] <= 19 ) || + (ip[0] == 198 && ip[1] == 51 && ip[2] == 100 ) || + (ip[0] == 203 && ip[1] == 0 && ip[2] == 113 ) + ) { RETURN_VALIDATION_FAILED } } @@ -952,23 +952,24 @@ void php_filter_validate_ip(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ } } if (flags & FILTER_FLAG_NO_RES_RANGE || flags & FILTER_FLAG_GLOBAL_RANGE) { - if ((ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0 - && ip[4] == 0 && ip[5] == 0 && ip[6] == 0 && (ip[7] == 0 || ip[7] == 1)) - || (ip[0] == 0x5f) - || (ip[0] >= 0xfe80 && ip[0] <= 0xfebf) - || (ip[0] == 0x2001 && (ip[1] == 0x0db8 || (ip[1] >= 0x0010 && ip[1] <= 0x001f))) - || (ip[0] == 0x3ff3) - ) { - RETURN_VALIDATION_FAILED - } + if ( + (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0 && ip[4] == 0 && ip[5] == 0 && ip[6] == 0 && (ip[7] == 0 || ip[7] == 1)) || + (ip[0] == 0x5f) || + (ip[0] >= 0xfe80 && ip[0] <= 0xfebf) || + (ip[0] == 0x2001 && (ip[1] == 0x0db8 || (ip[1] >= 0x0010 && ip[1] <= 0x001f))) || + (ip[0] == 0x3ff3) + ) { + RETURN_VALIDATION_FAILED + } } if (flags & FILTER_FLAG_GLOBAL_RANGE) { - if ((ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0 && ip[4] == 0 && ip[5] == 0xffff) || - (ip[0] == 0x0100 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0) || - (ip[0] == 0x2001 && ip[1] <= 0x01ff) || - (ip[0] == 0x2001 && ip[1] == 0x0002 && ip[2] == 0) || - (ip[0] >= 0xfc00 && ip[0] <= 0xfdff) - ) { + if ( + (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0 && ip[4] == 0 && ip[5] == 0xffff) || + (ip[0] == 0x0100 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0) || + (ip[0] == 0x2001 && ip[1] <= 0x01ff) || + (ip[0] == 0x2001 && ip[1] == 0x0002 && ip[2] == 0) || + (ip[0] >= 0xfc00 && ip[0] <= 0xfdff) + ) { RETURN_VALIDATION_FAILED } } From 18674e39ad12918ebbcd835df4e9f4c92eb7dc82 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 25 Nov 2024 21:24:38 +0100 Subject: [PATCH 41/76] Fix is_zend_ptr() huge block comparison We should compare the block memory, not the block metadata (See zend_mm_add_huge_block). This caused random test failure for ext/ffi/tests/gh14626.phpt when the malloc() performed by the FFI code lies close to the block metadata, and the size of the block is large enough. This was reported by https://github.com/php/php-src/issues/16902#issuecomment-2498310452 Closes GH-16938. --- NEWS | 1 + Zend/zend_alloc.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index d7c37b39d3a1c..cfddddad95cbc 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,7 @@ PHP NEWS (nielsdos) . Fixed bug GH-16630 (UAF in lexer with encoding translation and heredocs). (nielsdos) + . Fix is_zend_ptr() huge block comparison. (nielsdos) - FPM: . Fixed GH-16432 (PHP-FPM 8.2 SIGSEGV in fpm_get_status). (Jakub Zelenka) diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index e86f2961cfac9..b4db2f0b03cb7 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -2457,8 +2457,8 @@ ZEND_API bool is_zend_ptr(const void *ptr) zend_mm_huge_list *block = AG(mm_heap)->huge_list; while (block) { - if (ptr >= (void*)block - && ptr < (void*)((char*)block + block->size)) { + if (ptr >= block->ptr + && ptr < (void*)((char*)block->ptr + block->size)) { return 1; } block = block->next; From de30ba50426f1828a3385e350bf808faf50bcf51 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 25 Nov 2024 22:02:49 +0100 Subject: [PATCH 42/76] Fix GH-16879: JIT dead code skipping does not update call_level We intend to execute `MATCH_ERROR` in the VM and return to trace a hot function in BB1. We generate a tail handler and skip all remaining oplines of BB0. That means the `INIT_FCALL` in BB0 is missed and `call_level` is not increased to 1. This leads to the assertion failure. This patch fixes the issue by updating the `call_level` for the skipped oplines. Closes GH-16939. --- NEWS | 2 ++ ext/opcache/jit/zend_jit.c | 29 ++++++++++++++++++++++++++++- ext/opcache/tests/jit/gh16879.phpt | 22 ++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 ext/opcache/tests/jit/gh16879.phpt diff --git a/NEWS b/NEWS index 348e58cc10fe1..8ff774ee5a4ef 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,8 @@ PHP NEWS . Fixed bug GH-16851 (JIT_G(enabled) not set correctly on other threads). (dktapps) . Fixed bug GH-16902 (Set of opcache tests fail zts+aarch64). (nielsdos) + . Fixed bug GH-16879 (JIT dead code skipping does not update call_level). + (nielsdos) - Windows: . Fixed bug GH-16849 (Error dialog causes process to hang). (cmb) diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 0c6ab6c5cbc8c..cc22e7375a8c1 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -2576,7 +2576,34 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op } /* THROW and EXIT may be used in the middle of BB */ /* don't generate code for the rest of BB */ - i = end; + + /* Skip current opline for call_level computation + * Don't include last opline because end of loop already checks call level of last opline */ + i++; + for (; i < end; i++) { + opline = op_array->opcodes + i; + switch (opline->opcode) { + case ZEND_INIT_FCALL: + case ZEND_INIT_FCALL_BY_NAME: + case ZEND_INIT_NS_FCALL_BY_NAME: + case ZEND_INIT_METHOD_CALL: + case ZEND_INIT_DYNAMIC_CALL: + case ZEND_INIT_STATIC_METHOD_CALL: + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: + case ZEND_INIT_USER_CALL: + case ZEND_NEW: + call_level++; + break; + case ZEND_DO_FCALL: + case ZEND_DO_ICALL: + case ZEND_DO_UCALL: + case ZEND_DO_FCALL_BY_NAME: + case ZEND_CALLABLE_CONVERT: + call_level--; + break; + } + } + opline = op_array->opcodes + i; break; /* stackless execution */ case ZEND_INCLUDE_OR_EVAL: diff --git a/ext/opcache/tests/jit/gh16879.phpt b/ext/opcache/tests/jit/gh16879.phpt new file mode 100644 index 0000000000000..7a17fd34135b2 --- /dev/null +++ b/ext/opcache/tests/jit/gh16879.phpt @@ -0,0 +1,22 @@ +--TEST-- +GH-16879 (JIT dead code skipping does not update call_level) +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit_buffer_size=32M +opcache.jit=1235 +opcache.jit_hot_func=1 +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught UnhandledMatchError: Unhandled match case 0 in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d From d31de85f5f70541a4051668b7720c44047a6573a Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Tue, 26 Nov 2024 21:44:15 +0300 Subject: [PATCH 43/76] Avoid possible spill conflict (one of the problem that caused GH-16821) (#16947) --- ext/opcache/jit/zend_jit_ir.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 0331d0e7b6e0d..6c55f404f9b6c 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -1306,6 +1306,13 @@ static bool zend_jit_spilling_may_cause_conflict(zend_jit_ctx *jit, int var, ir_ && (jit->ssa->cfg.blocks[jit->ssa->vars[jit->ssa->ops[jit->ssa->vars[var].definition].op1_use].definition_phi->block].flags & ZEND_BB_LOOP_HEADER)) { /* Avoid moving spill store out of loop */ return 1; + } else if (jit->ssa->vars[var].definition >= 0 + && jit->ssa->ops[jit->ssa->vars[var].definition].op1_def == var + && jit->ssa->ops[jit->ssa->vars[var].definition].op1_use >= 0 + && jit->ssa->ops[jit->ssa->vars[var].definition].op2_use >= 0 + && jit->ra[jit->ssa->ops[jit->ssa->vars[var].definition].op2_use].ref == val) { + /* Avoid spill conflict between of ASSIGN.op1_def and ASSIGN.op1_use */ + return 1; } return 0; } From a80f0b515a22bde29b6fffbf26ae74025822bbdf Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 10 Nov 2024 14:04:50 +0100 Subject: [PATCH 44/76] Fix various memory leaks in curl mime handling Closes GH-16745. --- NEWS | 3 +++ ext/curl/interface.c | 39 ++++++++++++++++++++++++--------------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/NEWS b/NEWS index cfddddad95cbc..c09900ebd6b8d 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,9 @@ PHP NEWS (nielsdos) . Fix is_zend_ptr() huge block comparison. (nielsdos) +- Curl: + . Fix various memory leaks in curl mime handling. (nielsdos) + - FPM: . Fixed GH-16432 (PHP-FPM 8.2 SIGSEGV in fpm_get_status). (Jakub Zelenka) diff --git a/ext/curl/interface.c b/ext/curl/interface.c index 4884ddc8228a1..6798a384c7785 100644 --- a/ext/curl/interface.c +++ b/ext/curl/interface.c @@ -1381,7 +1381,7 @@ static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpo postval = Z_STR_P(prop); if (php_check_open_basedir(ZSTR_VAL(postval))) { - return FAILURE; + goto out_string; } prop = zend_read_property(curl_CURLFile_class, Z_OBJ_P(current), "mime", sizeof("mime")-1, 0, &rv); @@ -1407,15 +1407,18 @@ static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpo seekfunc = NULL; } + part = curl_mime_addpart(mime); + if (part == NULL) { + if (stream) { + php_stream_close(stream); + } + goto out_string; + } + cb_arg = emalloc(sizeof *cb_arg); cb_arg->filename = zend_string_copy(postval); cb_arg->stream = stream; - part = curl_mime_addpart(mime); - if (part == NULL) { - zend_string_release_ex(string_key, 0); - return FAILURE; - } if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK || (form_error = curl_mime_data_cb(part, filesize, read_cb, seekfunc, free_cb, cb_arg)) != CURLE_OK || (form_error = curl_mime_filename(part, filename ? filename : ZSTR_VAL(postval))) != CURLE_OK @@ -1449,8 +1452,7 @@ static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpo prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "postname", sizeof("postname")-1, 0, &rv); if (EG(exception)) { - zend_string_release_ex(string_key, 0); - return FAILURE; + goto out_string; } ZVAL_DEREF(prop); ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING); @@ -1459,8 +1461,7 @@ static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpo prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "mime", sizeof("mime")-1, 0, &rv); if (EG(exception)) { - zend_string_release_ex(string_key, 0); - return FAILURE; + goto out_string; } ZVAL_DEREF(prop); ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING); @@ -1469,8 +1470,7 @@ static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpo prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "data", sizeof("data")-1, 0, &rv); if (EG(exception)) { - zend_string_release_ex(string_key, 0); - return FAILURE; + goto out_string; } ZVAL_DEREF(prop); ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING); @@ -1483,8 +1483,7 @@ static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpo part = curl_mime_addpart(mime); if (part == NULL) { - zend_string_release_ex(string_key, 0); - return FAILURE; + goto out_string; } if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK || (form_error = curl_mime_data(part, ZSTR_VAL(postval), ZSTR_LEN(postval))) != CURLE_OK @@ -1540,7 +1539,7 @@ static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpo SAVE_CURL_ERROR(ch, error); if (error != CURLE_OK) { - return FAILURE; + goto out_mime; } if ((*ch->clone) == 1) { @@ -1556,6 +1555,16 @@ static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpo SAVE_CURL_ERROR(ch, error); return error == CURLE_OK ? SUCCESS : FAILURE; + +out_string: + zend_string_release_ex(string_key, false); +out_mime: +#if LIBCURL_VERSION_NUM >= 0x073800 /* 7.56.0 */ + curl_mime_free(mime); +#else + curl_formfree(first); +#endif + return FAILURE; } /* }}} */ From ed556939dfc61dc5f20346905155f36b14a2e3a1 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 26 Nov 2024 21:22:12 +0100 Subject: [PATCH 45/76] Extract call_level conditions out to separate functions (#16949) These are repeated a couple of times, so centralise it in 2 functions to reduce repetition and make updating this less error-prone. --- ext/opcache/jit/zend_jit.c | 77 +++++++++++++++++--------------- ext/opcache/jit/zend_jit_trace.c | 51 +++++---------------- 2 files changed, 51 insertions(+), 77 deletions(-) diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index cc22e7375a8c1..558faae22d0a0 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -727,6 +727,38 @@ ZEND_EXT_API void zend_jit_status(zval *ret) add_assoc_zval(ret, "jit", &stats); } +static bool zend_jit_inc_call_level(uint8_t opcode) +{ + switch (opcode) { + case ZEND_INIT_FCALL: + case ZEND_INIT_FCALL_BY_NAME: + case ZEND_INIT_NS_FCALL_BY_NAME: + case ZEND_INIT_METHOD_CALL: + case ZEND_INIT_DYNAMIC_CALL: + case ZEND_INIT_STATIC_METHOD_CALL: + case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: + case ZEND_INIT_USER_CALL: + case ZEND_NEW: + return true; + default: + return false; + } +} + +static bool zend_jit_dec_call_level(uint8_t opcode) +{ + switch (opcode) { + case ZEND_DO_FCALL: + case ZEND_DO_ICALL: + case ZEND_DO_UCALL: + case ZEND_DO_FCALL_BY_NAME: + case ZEND_CALLABLE_CONVERT: + return true; + default: + return false; + } +} + static zend_string *zend_jit_func_name(const zend_op_array *op_array) { smart_str buf = {0}; @@ -1463,17 +1495,8 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op for (i = ssa->cfg.blocks[b].start; i <= end; i++) { zend_ssa_op *ssa_op = ssa->ops ? &ssa->ops[i] : NULL; opline = op_array->opcodes + i; - switch (opline->opcode) { - case ZEND_INIT_FCALL: - case ZEND_INIT_FCALL_BY_NAME: - case ZEND_INIT_NS_FCALL_BY_NAME: - case ZEND_INIT_METHOD_CALL: - case ZEND_INIT_DYNAMIC_CALL: - case ZEND_INIT_STATIC_METHOD_CALL: - case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: - case ZEND_INIT_USER_CALL: - case ZEND_NEW: - call_level++; + if (zend_jit_inc_call_level(opline->opcode)) { + call_level++; } if (JIT_G(opt_level) >= ZEND_JIT_LEVEL_INLINE) { @@ -2582,25 +2605,10 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op i++; for (; i < end; i++) { opline = op_array->opcodes + i; - switch (opline->opcode) { - case ZEND_INIT_FCALL: - case ZEND_INIT_FCALL_BY_NAME: - case ZEND_INIT_NS_FCALL_BY_NAME: - case ZEND_INIT_METHOD_CALL: - case ZEND_INIT_DYNAMIC_CALL: - case ZEND_INIT_STATIC_METHOD_CALL: - case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: - case ZEND_INIT_USER_CALL: - case ZEND_NEW: - call_level++; - break; - case ZEND_DO_FCALL: - case ZEND_DO_ICALL: - case ZEND_DO_UCALL: - case ZEND_DO_FCALL_BY_NAME: - case ZEND_CALLABLE_CONVERT: - call_level--; - break; + if (zend_jit_inc_call_level(opline->opcode)) { + call_level++; + } else if (zend_jit_dec_call_level(opline->opcode)) { + call_level--; } } opline = op_array->opcodes + i; @@ -2715,13 +2723,8 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op } } done: - switch (opline->opcode) { - case ZEND_DO_FCALL: - case ZEND_DO_ICALL: - case ZEND_DO_UCALL: - case ZEND_DO_FCALL_BY_NAME: - case ZEND_CALLABLE_CONVERT: - call_level--; + if (zend_jit_dec_call_level(opline->opcode)) { + call_level--; } } zend_jit_bb_end(&ctx, b); diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index a8ea08cd1733c..c7c470330f060 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -1164,28 +1164,13 @@ static const zend_op *zend_jit_trace_find_init_fcall_op(zend_jit_trace_rec *p, c if (opline) { while (opline > op_array->opcodes) { opline--; - switch (opline->opcode) { - case ZEND_INIT_FCALL: - case ZEND_INIT_FCALL_BY_NAME: - case ZEND_INIT_NS_FCALL_BY_NAME: - case ZEND_INIT_METHOD_CALL: - case ZEND_INIT_DYNAMIC_CALL: - case ZEND_INIT_STATIC_METHOD_CALL: - case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: - case ZEND_INIT_USER_CALL: - case ZEND_NEW: - if (call_level == 0) { - return opline; - } - call_level--; - break; - case ZEND_DO_FCALL: - case ZEND_DO_ICALL: - case ZEND_DO_UCALL: - case ZEND_DO_FCALL_BY_NAME: - case ZEND_CALLABLE_CONVERT: - call_level++; - break; + if (zend_jit_inc_call_level(opline->opcode)) { + if (call_level == 0) { + return opline; + } + call_level--; + } else if (zend_jit_dec_call_level(opline->opcode)) { + call_level++; } } } @@ -4394,17 +4379,8 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par frame_flags = 0; - switch (opline->opcode) { - case ZEND_INIT_FCALL: - case ZEND_INIT_FCALL_BY_NAME: - case ZEND_INIT_NS_FCALL_BY_NAME: - case ZEND_INIT_METHOD_CALL: - case ZEND_INIT_DYNAMIC_CALL: - case ZEND_INIT_STATIC_METHOD_CALL: - case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: - case ZEND_INIT_USER_CALL: - case ZEND_NEW: - frame->call_level++; + if (zend_jit_inc_call_level(opline->opcode)) { + frame->call_level++; } if (JIT_G(opt_level) >= ZEND_JIT_LEVEL_INLINE) { @@ -6426,13 +6402,8 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par done: polymorphic_side_trace = 0; - switch (opline->opcode) { - case ZEND_DO_FCALL: - case ZEND_DO_ICALL: - case ZEND_DO_UCALL: - case ZEND_DO_FCALL_BY_NAME: - case ZEND_CALLABLE_CONVERT: - frame->call_level--; + if (zend_jit_dec_call_level(opline->opcode)) { + frame->call_level--; } if (ra) { From ae84b81bfaa40dfd241e25d5dc8fae6cf9585c68 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Mon, 15 Jul 2024 07:16:25 -0400 Subject: [PATCH 46/76] Backport GH-14962 to stable versions Alpine CI regularly fails because of the sorting order of these tests. See https://github.com/php/php-src/pull/14962#issuecomment-2498799881 Closes GH-16950. --- ext/dba/tests/dba_flatfile.phpt | 4 ++-- ext/dba/tests/dba_gdbm.phpt | 12 ++++++------ ext/dba/tests/dba_inifile.phpt | 8 ++++---- ext/dba/tests/dba_ndbm.phpt | 12 ++++++------ ext/dba/tests/dba_qdbm.phpt | 8 ++++---- ext/dba/tests/dba_tcadb.phpt | 4 ++-- ext/dba/tests/setup/setup_dba_tests.inc | 19 +++++++++++++++++-- ext/pgsql/tests/80_bug14383.phpt | 4 ++-- 8 files changed, 43 insertions(+), 28 deletions(-) diff --git a/ext/dba/tests/dba_flatfile.phpt b/ext/dba/tests/dba_flatfile.phpt index 9d989e9069b8d..1061e0a00e704 100644 --- a/ext/dba/tests/dba_flatfile.phpt +++ b/ext/dba/tests/dba_flatfile.phpt @@ -29,12 +29,12 @@ bool(true) bool(true) Try to remove key 1 again bool(false) +[key10]name10: Content String 10 +[key30]name30: Content String 30 key2: Content String 2 key4: Another Content String key5: The last content string name9: Content String 9 -[key10]name10: Content String 10 -[key30]name30: Content String 30 Total keys: 6 Key 1 exists? N Key 2 exists? Y diff --git a/ext/dba/tests/dba_gdbm.phpt b/ext/dba/tests/dba_gdbm.phpt index 480e6063b5212..7e3d43a4f1709 100644 --- a/ext/dba/tests/dba_gdbm.phpt +++ b/ext/dba/tests/dba_gdbm.phpt @@ -35,12 +35,12 @@ bool(true) bool(true) Try to remove key 1 again bool(false) -key4: Another Content String +[key10]name10: Content String 10 +[key30]name30: Content String 30 key2: Content String 2 +key4: Another Content String key5: The last content string -[key10]name10: Content String 10 name9: Content String 9 -[key30]name30: Content String 30 Total keys: 6 Key 1 exists? N Key 2 exists? Y @@ -81,12 +81,12 @@ bool(true) bool(true) Try to remove key 1 again bool(false) -key4: Another Content String +[key10]name10: Content String 10 +[key30]name30: Content String 30 key2: Content String 2 +key4: Another Content String key5: The last content string -[key10]name10: Content String 10 name9: Content String 9 -[key30]name30: Content String 30 Total keys: 6 Key 1 exists? N Key 2 exists? Y diff --git a/ext/dba/tests/dba_inifile.phpt b/ext/dba/tests/dba_inifile.phpt index 06be22c085ff6..c2d638a747581 100644 --- a/ext/dba/tests/dba_inifile.phpt +++ b/ext/dba/tests/dba_inifile.phpt @@ -30,14 +30,14 @@ bool(true) bool(true) Try to remove key 1 again bool(false) -key2: Content String 2 -key4: Another Content String -key5: The last content string -name9: Content String 9 [key10]: [key10]name10: Content String 10 [key30]: [key30]name30: Content String 30 +key2: Content String 2 +key4: Another Content String +key5: The last content string +name9: Content String 9 Total keys: 8 Key 1 exists? N Key 2 exists? Y diff --git a/ext/dba/tests/dba_ndbm.phpt b/ext/dba/tests/dba_ndbm.phpt index dcf368ff1a36f..730932966cfef 100644 --- a/ext/dba/tests/dba_ndbm.phpt +++ b/ext/dba/tests/dba_ndbm.phpt @@ -36,12 +36,12 @@ bool(true) bool(true) Try to remove key 1 again bool(false) -key4: Another Content String +[key10]name10: Content String 10 +[key30]name30: Content String 30 key2: Content String 2 +key4: Another Content String key5: The last content string -[key10]name10: Content String 10 name9: Content String 9 -[key30]name30: Content String 30 Total keys: 6 Key 1 exists? N Key 2 exists? Y @@ -82,12 +82,12 @@ bool(true) bool(true) Try to remove key 1 again bool(false) -key4: Another Content String +[key10]name10: Content String 10 +[key30]name30: Content String 30 key2: Content String 2 +key4: Another Content String key5: The last content string -[key10]name10: Content String 10 name9: Content String 9 -[key30]name30: Content String 30 Total keys: 6 Key 1 exists? N Key 2 exists? Y diff --git a/ext/dba/tests/dba_qdbm.phpt b/ext/dba/tests/dba_qdbm.phpt index e4321e7dc3750..fad229c368ee4 100644 --- a/ext/dba/tests/dba_qdbm.phpt +++ b/ext/dba/tests/dba_qdbm.phpt @@ -35,12 +35,12 @@ bool(true) bool(true) Try to remove key 1 again bool(false) +[key10]name10: Content String 10 +[key30]name30: Content String 30 key2: Content String 2 key4: Another Content String key5: The last content string name9: Content String 9 -[key10]name10: Content String 10 -[key30]name30: Content String 30 Total keys: 6 Key 1 exists? N Key 2 exists? Y @@ -81,12 +81,12 @@ bool(true) bool(true) Try to remove key 1 again bool(false) +[key10]name10: Content String 10 +[key30]name30: Content String 30 key2: Content String 2 key4: Another Content String key5: The last content string name9: Content String 9 -[key10]name10: Content String 10 -[key30]name30: Content String 30 Total keys: 6 Key 1 exists? N Key 2 exists? Y diff --git a/ext/dba/tests/dba_tcadb.phpt b/ext/dba/tests/dba_tcadb.phpt index 6459c5b372ea7..24900073c9cc8 100644 --- a/ext/dba/tests/dba_tcadb.phpt +++ b/ext/dba/tests/dba_tcadb.phpt @@ -30,12 +30,12 @@ bool(true) bool(true) Try to remove key 1 again bool(false) +[key10]name10: Content String 10 +[key30]name30: Content String 30 key2: Content String 2 key4: Another Content String key5: The last content string name9: Content String 9 -[key10]name10: Content String 10 -[key30]name30: Content String 30 Total keys: 6 Key 1 exists? N Key 2 exists? Y diff --git a/ext/dba/tests/setup/setup_dba_tests.inc b/ext/dba/tests/setup/setup_dba_tests.inc index 3e79ed3d54ce1..2ffac29e69782 100644 --- a/ext/dba/tests/setup/setup_dba_tests.inc +++ b/ext/dba/tests/setup/setup_dba_tests.inc @@ -102,14 +102,29 @@ function run_standard_tests_ex(string $handler, string $name, LockFlag $lock, bo echo 'Try to remove key 1 again', \PHP_EOL; var_dump(dba_delete("key1", $db_writer)); - // Fetch data + // Fetch and sort data. We sort to guarantee that the output is + // consistent across invocations and architectures. When iterating + // with firstkey() and nextkey(), several engines (GDBM, LMDB, + // QDBM) make no promise about the iteration order. Others (TCADB, + // DBM) explicitly state that the order is arbitrary. With GDBM at + // least, the order appears platform-dependent -- we have a report + // in Github issue 14786. GDBM's own test suite sorts this output, + // suggesting that sorting is a reasonable workaround for the issue. + $output = []; + $key = dba_firstkey($db_writer); $total_keys = 0; while ($key) { - echo $key, ': ', dba_fetch($key, $db_writer), \PHP_EOL; + $output[] = $key . ': ' . dba_fetch($key, $db_writer) . \PHP_EOL; $key = dba_nextkey($db_writer); $total_keys++; } + + sort($output, SORT_STRING); + foreach ($output as $line) { + echo $line; + } + echo 'Total keys: ', $total_keys, \PHP_EOL; for ($i = 1; $i < 6; $i++) { echo "Key $i exists? ", dba_exists("key$i", $db_writer) ? 'Y' : 'N', \PHP_EOL; diff --git a/ext/pgsql/tests/80_bug14383.phpt b/ext/pgsql/tests/80_bug14383.phpt index f17af830d7041..c14b2f414d9bd 100644 --- a/ext/pgsql/tests/80_bug14383.phpt +++ b/ext/pgsql/tests/80_bug14383.phpt @@ -39,12 +39,12 @@ bool(true) bool(true) Try to remove key 1 again bool(false) +[key10]name10: Content String 10 +[key30]name30: Content String 30 key2: Content String 2 key4: Another Content String key5: The last content string name9: Content String 9 -[key10]name10: Content String 10 -[key30]name30: Content String 30 Total keys: 6 Key 1 exists? N Key 2 exists? Y From 97b03186c4e6964ad8683dc8b225e4dcc4de3199 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 26 Nov 2024 21:19:03 +0100 Subject: [PATCH 47/76] Fix GH-15208: Segfault with breakpoint map and phpdbg_clear() It crashes because it's gonna try accessing the breakpoint which was cleared by user code in `phpdbg_clear();`. Not all breakpoint data was properly cleaned. Closes GH-16953. --- NEWS | 4 ++++ sapi/phpdbg/phpdbg.c | 1 + sapi/phpdbg/tests/gh15208.phpt | 15 +++++++++++++++ 3 files changed, 20 insertions(+) create mode 100644 sapi/phpdbg/tests/gh15208.phpt diff --git a/NEWS b/NEWS index c09900ebd6b8d..d4dea493bca0a 100644 --- a/NEWS +++ b/NEWS @@ -47,6 +47,10 @@ PHP NEWS . Fixed bug GH-16695 (phar:// tar parser and zero-length file header blocks). (nielsdos, Hans Krentel) +- PHPDBG: + . Fixed bug GH-15208 (Segfault with breakpoint map and phpdbg_clear()). + (nielsdos) + - SimpleXML: . Fixed bug GH-16808 (Segmentation fault in RecursiveIteratorIterator ->current() with a xml element input). (nielsdos) diff --git a/sapi/phpdbg/phpdbg.c b/sapi/phpdbg/phpdbg.c index d9cc8f5e891e0..4e685d0894a8b 100644 --- a/sapi/phpdbg/phpdbg.c +++ b/sapi/phpdbg/phpdbg.c @@ -369,6 +369,7 @@ PHP_FUNCTION(phpdbg_clear) zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_OPLINE]); zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE]); zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD]); + zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP]); zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_COND]); } /* }}} */ diff --git a/sapi/phpdbg/tests/gh15208.phpt b/sapi/phpdbg/tests/gh15208.phpt new file mode 100644 index 0000000000000..4fa63a61c5262 --- /dev/null +++ b/sapi/phpdbg/tests/gh15208.phpt @@ -0,0 +1,15 @@ +--TEST-- +GH-15208 (Segfault with breakpoint map and phpdbg_clear()) +--PHPDBG-- +r +q +--FILE-- + +--EXPECTF-- +[Successful compilation of %s] +prompt> [Breakpoint #0 added at foo::bar] +[Script ended normally] +prompt> From b89d7ff92ac7ce4fdfc7656d5312d59b48dedcc3 Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Wed, 27 Nov 2024 00:43:45 +0300 Subject: [PATCH 48/76] Fix GH-16821: runtime error: member access within misaligned address when running phpseclib tests (#16951) --- ext/opcache/jit/zend_jit_ir.c | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 6c55f404f9b6c..e4b68d23520c8 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -17381,8 +17381,15 @@ static void jit_frameless_icall2(zend_jit_ctx *jit, const zend_op *opline, uint3 jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, NULL); /* Set OP1 to UNDEF in case FREE_OP2() throws. */ - if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) != 0 && (opline->op2_type & (IS_VAR|IS_TMP_VAR)) != 0) { + if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) != 0 + && (opline->op2_type & (IS_VAR|IS_TMP_VAR)) != 0 + && (op2_info & MAY_BE_RC1) + && (op2_info & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY))) { jit_set_Z_TYPE_INFO(jit, op1_addr, IS_UNDEF); + if (JIT_G(current_frame)) { + SET_STACK_TYPE(JIT_G(current_frame)->stack, + EX_VAR_TO_NUM(opline->op1.var), IS_UNKNOWN, 1); + } } jit_FREE_OP(jit, opline->op2_type, opline->op2, op2_info, NULL); zend_jit_check_exception(jit); @@ -17455,18 +17462,34 @@ static void jit_frameless_icall3(zend_jit_ctx *jit, const zend_op *opline, uint3 jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, NULL); /* Set OP1 to UNDEF in case FREE_OP2() throws. */ + bool op1_undef = false; if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) - && ((opline->op2_type & (IS_VAR|IS_TMP_VAR)) - || (op_data_type & (IS_VAR|IS_TMP_VAR)))) { + && (((opline->op2_type & (IS_VAR|IS_TMP_VAR)) + && (op2_info & MAY_BE_RC1) + && (op2_info & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY))) + || ((op_data_type & (IS_VAR|IS_TMP_VAR)) + && (op1_data_info & MAY_BE_RC1) + && (op1_data_info & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY))))) { + op1_undef = true; jit_set_Z_TYPE_INFO(jit, op1_addr, IS_UNDEF); + if (JIT_G(current_frame)) { + SET_STACK_TYPE(JIT_G(current_frame)->stack, + EX_VAR_TO_NUM(opline->op1.var), IS_UNKNOWN, 1); + } } jit_FREE_OP(jit, opline->op2_type, opline->op2, op2_info, NULL); - /* If OP1 is a TMP|VAR, we don't need to set OP2 to UNDEF on free because + /* If OP1 is set to UNDEF, we don't need to set OP2 to UNDEF on free because * zend_fetch_debug_backtrace aborts when it encounters the first UNDEF TMP|VAR. */ - if (!(opline->op1_type & (IS_VAR|IS_TMP_VAR)) + if (!op1_undef && (opline->op2_type & (IS_VAR|IS_TMP_VAR)) != 0 - && (op_data_type & (IS_VAR|IS_TMP_VAR)) != 0) { + && (op_data_type & (IS_VAR|IS_TMP_VAR)) != 0 + && (op1_data_info & MAY_BE_RC1) + && (op1_data_info & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY))) { jit_set_Z_TYPE_INFO(jit, op2_addr, IS_UNDEF); + if (JIT_G(current_frame)) { + SET_STACK_TYPE(JIT_G(current_frame)->stack, + EX_VAR_TO_NUM(opline->op2.var), IS_UNKNOWN, 1); + } } jit_FREE_OP(jit, (opline+1)->op1_type, (opline+1)->op1, op1_data_info, NULL); zend_jit_check_exception(jit); From 56fb910d9c11aeb613caa1830947c31d529daa1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Tue, 26 Nov 2024 22:44:25 +0100 Subject: [PATCH 49/76] Fix the 1st parameter type casing of pg_set_chunked_rows_size() --- ext/pgsql/pgsql.stub.php | 2 +- ext/pgsql/pgsql_arginfo.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/pgsql/pgsql.stub.php b/ext/pgsql/pgsql.stub.php index f507de2e062a2..ec7e2614ce0a3 100644 --- a/ext/pgsql/pgsql.stub.php +++ b/ext/pgsql/pgsql.stub.php @@ -968,7 +968,7 @@ function pg_put_copy_end(PgSql\Connection $connection, ?string $error = null): i function pg_socket_poll($socket, int $read, int $write, int $timeout = -1): int {} #ifdef HAVE_PG_SET_CHUNKED_ROWS_SIZE - function pg_set_chunked_rows_size(Pgsql\Connection $connection, int $size): bool {} + function pg_set_chunked_rows_size(PgSql\Connection $connection, int $size): bool {} #endif } diff --git a/ext/pgsql/pgsql_arginfo.h b/ext/pgsql/pgsql_arginfo.h index 182dea8d221a8..ab38b6a7a8bfa 100644 --- a/ext/pgsql/pgsql_arginfo.h +++ b/ext/pgsql/pgsql_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 0b89a48c27c6682542312391f10a3ab8fb719ef8 */ + * Stub hash: 14b0bdd019480b850940b2c2b012b5f6d51746b8 */ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_pg_connect, 0, 1, PgSql\\Connection, MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, connection_string, IS_STRING, 0) @@ -490,7 +490,7 @@ ZEND_END_ARG_INFO() #if defined(HAVE_PG_SET_CHUNKED_ROWS_SIZE) ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pg_set_chunked_rows_size, 0, 2, _IS_BOOL, 0) - ZEND_ARG_OBJ_INFO(0, connection, Pgsql\\Connection, 0) + ZEND_ARG_OBJ_INFO(0, connection, PgSql\\Connection, 0) ZEND_ARG_TYPE_INFO(0, size, IS_LONG, 0) ZEND_END_ARG_INFO() #endif From 45140e527f4eabf167e857f46ee9e0851ef6ac8b Mon Sep 17 00:00:00 2001 From: David Carlier Date: Mon, 25 Nov 2024 18:11:59 +0000 Subject: [PATCH 50/76] Revert "ext/gmp: gmp_pow fix FPE with large values." This reverts commit e0a0e216a909dc4ee4ea7c113a5f41d49525f02e. --- NEWS | 2 - ext/gmp/gmp.c | 38 +++++++++------ ext/gmp/tests/gmp_pow.phpt | 2 - ext/gmp/tests/gmp_pow_32bits.phpt | 77 ------------------------------- ext/gmp/tests/gmp_pow_fpe.phpt | 35 ++++---------- 5 files changed, 35 insertions(+), 119 deletions(-) delete mode 100644 ext/gmp/tests/gmp_pow_32bits.phpt diff --git a/NEWS b/NEWS index d4dea493bca0a..d4e9d62944845 100644 --- a/NEWS +++ b/NEWS @@ -134,8 +134,6 @@ PHP NEWS . Fixed bug GH-16411 (gmp_export() can cause overflow). (cmb) . Fixed bug GH-16501 (gmp_random_bits() can cause overflow). (David Carlier) - . Fixed gmp_pow() overflow bug with large base/exponents. - (David Carlier) . Fixed segfaults and other issues related to operator overloading with GMP objects. (Girgias) diff --git a/ext/gmp/gmp.c b/ext/gmp/gmp.c index c9603c8fb21e8..fc5464e90542d 100644 --- a/ext/gmp/gmp.c +++ b/ext/gmp/gmp.c @@ -1350,27 +1350,39 @@ ZEND_FUNCTION(gmp_pow) RETURN_THROWS(); } - double powmax = log((double)ZEND_LONG_MAX); - if (Z_TYPE_P(base_arg) == IS_LONG && Z_LVAL_P(base_arg) >= 0) { INIT_GMP_RETVAL(gmpnum_result); - if ((log(Z_LVAL_P(base_arg)) * exp) > powmax) { - zend_value_error("base and exponent overflow"); - RETURN_THROWS(); + if (exp >= INT_MAX) { + mpz_t base_num, exp_num, mod; + mpz_init(base_num); + mpz_init(exp_num); + mpz_init(mod); + mpz_set_si(base_num, Z_LVAL_P(base_arg)); + mpz_set_si(exp_num, exp); + mpz_set_ui(mod, UINT_MAX); + mpz_powm(gmpnum_result, base_num, exp_num, mod); + mpz_clear(mod); + mpz_clear(exp_num); + mpz_clear(base_num); + } else { + mpz_ui_pow_ui(gmpnum_result, Z_LVAL_P(base_arg), exp); } - mpz_ui_pow_ui(gmpnum_result, Z_LVAL_P(base_arg), exp); } else { mpz_ptr gmpnum_base; - zend_ulong gmpnum; FETCH_GMP_ZVAL(gmpnum_base, base_arg, temp_base, 1); INIT_GMP_RETVAL(gmpnum_result); - gmpnum = mpz_get_ui(gmpnum_base); - if ((log(gmpnum) * exp) > powmax) { - FREE_GMP_TEMP(temp_base); - zend_value_error("base and exponent overflow"); - RETURN_THROWS(); + if (exp >= INT_MAX) { + mpz_t exp_num, mod; + mpz_init(exp_num); + mpz_init(mod); + mpz_set_si(exp_num, exp); + mpz_set_ui(mod, UINT_MAX); + mpz_powm(gmpnum_result, gmpnum_base, exp_num, mod); + mpz_clear(mod); + mpz_clear(exp_num); + } else { + mpz_pow_ui(gmpnum_result, gmpnum_base, exp); } - mpz_pow_ui(gmpnum_result, gmpnum_base, exp); FREE_GMP_TEMP(temp_base); } } diff --git a/ext/gmp/tests/gmp_pow.phpt b/ext/gmp/tests/gmp_pow.phpt index 1d77bd5e96c80..f42e44e31abed 100644 --- a/ext/gmp/tests/gmp_pow.phpt +++ b/ext/gmp/tests/gmp_pow.phpt @@ -2,8 +2,6 @@ gmp_pow() basic tests --EXTENSIONS-- gmp ---SKIPIF-- - --FILE-- ---FILE-- -getMessage() . "\n"; -} -var_dump(gmp_strval(gmp_pow("-2",10))); -try { - gmp_pow(20,10); -} catch (ValueError $exception) { - echo $exception->getMessage() . "\n"; -} -try { - gmp_pow(50,10); -} catch (ValueError $exception) { - echo $exception->getMessage() . "\n"; -} -try { - gmp_pow(50,-5); -} catch (ValueError $exception) { - echo $exception->getMessage() . "\n"; -} -try { - $n = gmp_init("20"); - gmp_pow($n,10); -} catch (ValueError $exception) { - echo $exception->getMessage() . "\n"; -} -try { - $n = gmp_init("-20"); - gmp_pow($n,10); -} catch (ValueError $exception) { - echo $exception->getMessage() . "\n"; -} -try { - var_dump(gmp_pow(2,array())); -} catch (TypeError $e) { - echo $e->getMessage(), "\n"; -} - -try { - var_dump(gmp_pow(array(),10)); -} catch (\TypeError $e) { - echo $e->getMessage() . \PHP_EOL; -} - -echo "Done\n"; -?> ---EXPECT-- -string(4) "1024" -string(4) "1024" -string(5) "-2048" -string(4) "1024" -string(1) "1" -gmp_pow(): Argument #2 ($exponent) must be greater than or equal to 0 -string(4) "1024" -base and exponent overflow -base and exponent overflow -gmp_pow(): Argument #2 ($exponent) must be greater than or equal to 0 -base and exponent overflow -base and exponent overflow -gmp_pow(): Argument #2 ($exponent) must be of type int, array given -gmp_pow(): Argument #1 ($num) must be of type GMP|string|int, array given -Done diff --git a/ext/gmp/tests/gmp_pow_fpe.phpt b/ext/gmp/tests/gmp_pow_fpe.phpt index 248922e80514d..d564853799c8d 100644 --- a/ext/gmp/tests/gmp_pow_fpe.phpt +++ b/ext/gmp/tests/gmp_pow_fpe.phpt @@ -6,30 +6,15 @@ gmp getMessage() . PHP_EOL; -} -try { - gmp_pow(256, PHP_INT_MAX); -} catch (\ValueError $e) { - echo $e->getMessage() . PHP_EOL; -} - -try { - gmp_pow(gmp_add(gmp_mul(gmp_init(PHP_INT_MAX), gmp_init(PHP_INT_MAX)), 3), 256); -} catch (\ValueError $e) { - echo $e->getMessage() . PHP_EOL; -} -try { - gmp_pow(gmp_init(PHP_INT_MAX), 256); -} catch (\ValueError $e) { - echo $e->getMessage(); -} +var_dump(gmp_pow($g, PHP_INT_MAX)); +var_dump(gmp_pow(256, PHP_INT_MAX)); ?> --EXPECTF-- -base and exponent overflow -base and exponent overflow -base and exponent overflow -base and exponent overflow +object(GMP)#2 (1) { + ["num"]=> + string(%d) "%s" +} +object(GMP)#2 (1) { + ["num"]=> + string(%d) "%s" +} From 7e8d6f941c18d7f753755d7a24637640cb88c11b Mon Sep 17 00:00:00 2001 From: David Carlier Date: Mon, 25 Nov 2024 18:12:22 +0000 Subject: [PATCH 51/76] Revert "ext/gmp: gmp_pow fix FPE with large values." This reverts commit d70b7811b0248a36b06d70a04e350801a1fede8e. --- NEWS | 2 -- ext/gmp/gmp.c | 29 ++--------------------------- ext/gmp/tests/gmp_pow_fpe.phpt | 20 -------------------- 3 files changed, 2 insertions(+), 49 deletions(-) delete mode 100644 ext/gmp/tests/gmp_pow_fpe.phpt diff --git a/NEWS b/NEWS index d4e9d62944845..65c8f8cae595a 100644 --- a/NEWS +++ b/NEWS @@ -129,8 +129,6 @@ PHP NEWS (nielsdos) - GMP: - . Fixed floating point exception bug with gmp_pow when using - large exposant values. (David Carlier). . Fixed bug GH-16411 (gmp_export() can cause overflow). (cmb) . Fixed bug GH-16501 (gmp_random_bits() can cause overflow). (David Carlier) diff --git a/ext/gmp/gmp.c b/ext/gmp/gmp.c index fc5464e90542d..f31d6ab810232 100644 --- a/ext/gmp/gmp.c +++ b/ext/gmp/gmp.c @@ -1352,37 +1352,12 @@ ZEND_FUNCTION(gmp_pow) if (Z_TYPE_P(base_arg) == IS_LONG && Z_LVAL_P(base_arg) >= 0) { INIT_GMP_RETVAL(gmpnum_result); - if (exp >= INT_MAX) { - mpz_t base_num, exp_num, mod; - mpz_init(base_num); - mpz_init(exp_num); - mpz_init(mod); - mpz_set_si(base_num, Z_LVAL_P(base_arg)); - mpz_set_si(exp_num, exp); - mpz_set_ui(mod, UINT_MAX); - mpz_powm(gmpnum_result, base_num, exp_num, mod); - mpz_clear(mod); - mpz_clear(exp_num); - mpz_clear(base_num); - } else { - mpz_ui_pow_ui(gmpnum_result, Z_LVAL_P(base_arg), exp); - } + mpz_ui_pow_ui(gmpnum_result, Z_LVAL_P(base_arg), exp); } else { mpz_ptr gmpnum_base; FETCH_GMP_ZVAL(gmpnum_base, base_arg, temp_base, 1); INIT_GMP_RETVAL(gmpnum_result); - if (exp >= INT_MAX) { - mpz_t exp_num, mod; - mpz_init(exp_num); - mpz_init(mod); - mpz_set_si(exp_num, exp); - mpz_set_ui(mod, UINT_MAX); - mpz_powm(gmpnum_result, gmpnum_base, exp_num, mod); - mpz_clear(mod); - mpz_clear(exp_num); - } else { - mpz_pow_ui(gmpnum_result, gmpnum_base, exp); - } + mpz_pow_ui(gmpnum_result, gmpnum_base, exp); FREE_GMP_TEMP(temp_base); } } diff --git a/ext/gmp/tests/gmp_pow_fpe.phpt b/ext/gmp/tests/gmp_pow_fpe.phpt deleted file mode 100644 index d564853799c8d..0000000000000 --- a/ext/gmp/tests/gmp_pow_fpe.phpt +++ /dev/null @@ -1,20 +0,0 @@ ---TEST-- -gmp_pow() floating point exception ---EXTENSIONS-- -gmp ---FILE-- - ---EXPECTF-- -object(GMP)#2 (1) { - ["num"]=> - string(%d) "%s" -} -object(GMP)#2 (1) { - ["num"]=> - string(%d) "%s" -} From 5fd53a8b93e167b990be331838cea32c6d4c0cac Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 27 Nov 2024 19:53:09 +0000 Subject: [PATCH 52/76] [skip ci] NEWS --- NEWS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS b/NEWS index 65c8f8cae595a..5dbe5571c4a2b 100644 --- a/NEWS +++ b/NEWS @@ -25,6 +25,10 @@ PHP NEWS - GD: . Fixed GH-16776 (imagecreatefromstring overflow). (David Carlier) +- GMP: + . Revert gmp_pow() overly restrictive overflow checks. + (David Carlier) + - Hash: . Fixed GH-16711: Segfault in mhash(). (Girgias) From 3c70f5e976a5b1c7a537a5fe0a05db5a533874b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Wed, 27 Nov 2024 23:05:14 +0100 Subject: [PATCH 53/76] Fix some odbc_*() parameter types --- ext/odbc/odbc.stub.php | 9 +++------ ext/odbc/odbc_arginfo.h | 8 ++++---- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/ext/odbc/odbc.stub.php b/ext/odbc/odbc.stub.php index f1eb2a890b3c3..8a54e913e88ed 100644 --- a/ext/odbc/odbc.stub.php +++ b/ext/odbc/odbc.stub.php @@ -351,18 +351,15 @@ function odbc_exec(Odbc\Connection $odbc, string $query): Odbc\Result|false {} function odbc_do(Odbc\Connection $odbc, string $query): Odbc\Result|false {} #ifdef PHP_ODBC_HAVE_FETCH_HASH - /** @param resource $statement */ - function odbc_fetch_object($statement, ?int $row = null): stdClass|false {} + function odbc_fetch_object(Odbc\Result $statement, ?int $row = null): stdClass|false {} - /** @param resource $statement */ - function odbc_fetch_array($statement, ?int $row = null): array|false {} + function odbc_fetch_array(Odbc\Result $statement, ?int $row = null): array|false {} #endif /** - * @param resource $statement * @param array $array */ - function odbc_fetch_into($statement, &$array, ?int $row = null): int|false {} + function odbc_fetch_into(Odbc\Result $statement, &$array, ?int $row = null): int|false {} function odbc_fetch_row(Odbc\Result $statement, ?int $row = null): bool {} diff --git a/ext/odbc/odbc_arginfo.h b/ext/odbc/odbc_arginfo.h index d586f5a948edc..91df4da846d49 100644 --- a/ext/odbc/odbc_arginfo.h +++ b/ext/odbc/odbc_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 30ed66d5e97f6615a461d39f40f85a18ba618711 */ + * Stub hash: efd913e4fcacb2949dc5392857032ab9c59c818d */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_odbc_close_all, 0, 0, IS_VOID, 0) ZEND_END_ARG_INFO() @@ -41,18 +41,18 @@ ZEND_END_ARG_INFO() #if defined(PHP_ODBC_HAVE_FETCH_HASH) ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_odbc_fetch_object, 0, 1, stdClass, MAY_BE_FALSE) - ZEND_ARG_INFO(0, statement) + ZEND_ARG_OBJ_INFO(0, statement, Odbc\\Result, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, row, IS_LONG, 1, "null") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_odbc_fetch_array, 0, 1, MAY_BE_ARRAY|MAY_BE_FALSE) - ZEND_ARG_INFO(0, statement) + ZEND_ARG_OBJ_INFO(0, statement, Odbc\\Result, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, row, IS_LONG, 1, "null") ZEND_END_ARG_INFO() #endif ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_odbc_fetch_into, 0, 2, MAY_BE_LONG|MAY_BE_FALSE) - ZEND_ARG_INFO(0, statement) + ZEND_ARG_OBJ_INFO(0, statement, Odbc\\Result, 0) ZEND_ARG_INFO(1, array) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, row, IS_LONG, 1, "null") ZEND_END_ARG_INFO() From d165670d049b7451753721d77f9dc0ca0799093d Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Wed, 27 Nov 2024 22:56:55 +0100 Subject: [PATCH 54/76] Fix GH-16932: wrong FPM status output Closes GH-16974 Co-authored-by: James Lucas --- sapi/fpm/fpm/fpm_request.c | 2 +- sapi/fpm/tests/gh16932-scoreboard-reset.phpt | 56 ++++++++++++++++++++ sapi/fpm/tests/response.inc | 25 ++++++++- 3 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 sapi/fpm/tests/gh16932-scoreboard-reset.phpt diff --git a/sapi/fpm/fpm/fpm_request.c b/sapi/fpm/fpm/fpm_request.c index 92c6fa8f2cca9..f07c6979f7064 100644 --- a/sapi/fpm/fpm/fpm_request.c +++ b/sapi/fpm/fpm/fpm_request.c @@ -201,7 +201,7 @@ void fpm_request_end(void) fpm_scoreboard_proc_release(proc); /* memory_peak */ - fpm_scoreboard_update_commit(0, 0, 0, 0, 0, 0, 0, proc->memory, FPM_SCOREBOARD_ACTION_SET, NULL); + fpm_scoreboard_update_commit(-1, -1, -1, -1, -1, -1, -1, proc->memory, FPM_SCOREBOARD_ACTION_SET, NULL); } void fpm_request_finished(void) diff --git a/sapi/fpm/tests/gh16932-scoreboard-reset.phpt b/sapi/fpm/tests/gh16932-scoreboard-reset.phpt new file mode 100644 index 0000000000000..18092a53f7aff --- /dev/null +++ b/sapi/fpm/tests/gh16932-scoreboard-reset.phpt @@ -0,0 +1,56 @@ +--TEST-- +FPM: GH-16932 - scoreboard fields are reset after the request +--EXTENSIONS-- +pcntl +--SKIPIF-- + +--FILE-- +start(extensions: ['pcntl']); +$tester->expectLogStartNotices(); +$tester->request(); +$tester->request(); +$tester->request(); +$tester->request(); +$tester + ->request(uri: '/status', query: 'json') + ->expectJsonBodyPatternForStatusField('accepted conn', '5'); +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +?> +Done +--EXPECT-- +Done +--CLEAN-- + diff --git a/sapi/fpm/tests/response.inc b/sapi/fpm/tests/response.inc index b784df3164f46..aefd2e027c67d 100644 --- a/sapi/fpm/tests/response.inc +++ b/sapi/fpm/tests/response.inc @@ -148,6 +148,29 @@ class Response extends BaseResponse return $this; } + /** + * Expect status field with value that matches the supplied pattern. + * + * @param string $fieldName + * @param string $pattern + * + * @return Response + */ + public function expectJsonBodyPatternForStatusField(string $fieldName, string $pattern): Response + { + $rawData = $this->getBody('application/json'); + $data = json_decode($rawData, true); + if (preg_match('|' . $pattern . '|', $data[$fieldName]) > 0) { + return $this; + } + + $this->error( + "Field $fieldName did not match pattern $pattern in status data '$rawData'" + ); + + return $this; + } + /** * Expect that one of the processes in json status process list has a field with value that * matches the supplied pattern. @@ -167,7 +190,7 @@ class Response extends BaseResponse ); } foreach ($data['processes'] as $process) { - if (preg_match('|' . $pattern . '|', $process[$fieldName]) !== false) { + if (preg_match('|' . $pattern . '|', $process[$fieldName]) > 0) { return $this; } } From e6997535f7842308cab24b458c68926208f55e41 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Thu, 28 Nov 2024 09:02:21 +0100 Subject: [PATCH 55/76] Update NEWS for GH-16932 fix --- NEWS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS b/NEWS index abf1535603931..c8d1ee0a87f26 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,9 @@ PHP NEWS . Fixed bug GH-16906 (Reloading document can cause UAF in iterator). (nielsdos) +- FPM: + . Fixed bug GH-16932 (wrong FPM status output). (Jakub Zelenka, James Lucas) + - GMP: . Fixed bug GH-16890 (array_sum() with GMP can loose precision (LLP64)). (cmb) From fb919e885a14ab1422c08ba8f3dbb9d04e70cbd4 Mon Sep 17 00:00:00 2001 From: Pierrick Charron Date: Thu, 28 Nov 2024 11:01:12 -0500 Subject: [PATCH 56/76] PHP-8.4 is now for PHP 8.4.2-dev --- Zend/zend.h | 2 +- configure.ac | 2 +- main/php_version.h | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Zend/zend.h b/Zend/zend.h index 815d6ca58a5da..62c0137092e43 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.4.1-dev" +#define ZEND_VERSION "4.4.2-dev" #define ZEND_ENGINE_3 diff --git a/configure.ac b/configure.ac index cab7e839a7763..142f8824ba78c 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.4.1-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.4.2-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 51abf80ba6911..5b63c499c4eee 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 4 -#define PHP_RELEASE_VERSION 1 +#define PHP_RELEASE_VERSION 2 #define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.4.1-dev" -#define PHP_VERSION_ID 80401 +#define PHP_VERSION "8.4.2-dev" +#define PHP_VERSION_ID 80402 From e1b45347903387470d0b78fa112084de8532aa11 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 23 Nov 2024 15:28:58 +0100 Subject: [PATCH 57/76] Fix GH-16905: Internal iterator functions can't handle UNDEF properties Closes GH-16907. --- NEWS | 4 ++ ext/standard/array.c | 100 +++++++++++++------------- ext/standard/tests/array/gh16905.phpt | 92 ++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 51 deletions(-) create mode 100644 ext/standard/tests/array/gh16905.phpt diff --git a/NEWS b/NEWS index 5dbe5571c4a2b..d3ec3c0e6279d 100644 --- a/NEWS +++ b/NEWS @@ -59,6 +59,10 @@ PHP NEWS . Fixed bug GH-16808 (Segmentation fault in RecursiveIteratorIterator ->current() with a xml element input). (nielsdos) +- Standard: + . Fixed bug GH-16905 (Internal iterator functions can't handle UNDEF + properties). (nielsdos) + - Windows: . Fixed bug GH-16849 (Error dialog causes process to hang). (cmb) diff --git a/ext/standard/array.c b/ext/standard/array.c index d4e3742bb71fa..a1a9293408481 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -1029,11 +1029,50 @@ static inline HashTable *get_ht_for_iap(zval *zv, bool separate) { return zobj->handlers->get_properties(zobj); } +static zval *php_array_iter_seek_current(HashTable *array, bool forward_direction) +{ + zval *entry; + + while (true) { + if ((entry = zend_hash_get_current_data(array)) == NULL) { + return NULL; + } + + ZVAL_DEINDIRECT(entry); + + /* Possible with an uninitialized typed property */ + if (UNEXPECTED(Z_TYPE_P(entry) == IS_UNDEF)) { + zend_result result; + if (forward_direction) { + result = zend_hash_move_forward(array); + } else { + result = zend_hash_move_backwards(array); + } + if (result != SUCCESS) { + return NULL; + } + } else { + break; + } + } + + return entry; +} + +static void php_array_iter_return_current(zval *return_value, HashTable *array, bool forward_direction) +{ + zval *entry = php_array_iter_seek_current(array, forward_direction); + if (EXPECTED(entry)) { + RETURN_COPY_DEREF(entry); + } else { + RETURN_FALSE; + } +} + /* {{{ Advances array argument's internal pointer to the last element and return it */ PHP_FUNCTION(end) { zval *array_zv; - zval *entry; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY_OR_OBJECT_EX(array_zv, 0, 1) @@ -1047,15 +1086,7 @@ PHP_FUNCTION(end) zend_hash_internal_pointer_end(array); if (USED_RET()) { - if ((entry = zend_hash_get_current_data(array)) == NULL) { - RETURN_FALSE; - } - - if (Z_TYPE_P(entry) == IS_INDIRECT) { - entry = Z_INDIRECT_P(entry); - } - - RETURN_COPY_DEREF(entry); + php_array_iter_return_current(return_value, array, false); } } /* }}} */ @@ -1064,7 +1095,6 @@ PHP_FUNCTION(end) PHP_FUNCTION(prev) { zval *array_zv; - zval *entry; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY_OR_OBJECT_EX(array_zv, 0, 1) @@ -1078,15 +1108,7 @@ PHP_FUNCTION(prev) zend_hash_move_backwards(array); if (USED_RET()) { - if ((entry = zend_hash_get_current_data(array)) == NULL) { - RETURN_FALSE; - } - - if (Z_TYPE_P(entry) == IS_INDIRECT) { - entry = Z_INDIRECT_P(entry); - } - - RETURN_COPY_DEREF(entry); + php_array_iter_return_current(return_value, array, false); } } /* }}} */ @@ -1095,7 +1117,6 @@ PHP_FUNCTION(prev) PHP_FUNCTION(next) { zval *array_zv; - zval *entry; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY_OR_OBJECT_EX(array_zv, 0, 1) @@ -1109,15 +1130,7 @@ PHP_FUNCTION(next) zend_hash_move_forward(array); if (USED_RET()) { - if ((entry = zend_hash_get_current_data(array)) == NULL) { - RETURN_FALSE; - } - - if (Z_TYPE_P(entry) == IS_INDIRECT) { - entry = Z_INDIRECT_P(entry); - } - - RETURN_COPY_DEREF(entry); + php_array_iter_return_current(return_value, array, true); } } /* }}} */ @@ -1126,7 +1139,6 @@ PHP_FUNCTION(next) PHP_FUNCTION(reset) { zval *array_zv; - zval *entry; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY_OR_OBJECT_EX(array_zv, 0, 1) @@ -1140,15 +1152,7 @@ PHP_FUNCTION(reset) zend_hash_internal_pointer_reset(array); if (USED_RET()) { - if ((entry = zend_hash_get_current_data(array)) == NULL) { - RETURN_FALSE; - } - - if (Z_TYPE_P(entry) == IS_INDIRECT) { - entry = Z_INDIRECT_P(entry); - } - - RETURN_COPY_DEREF(entry); + php_array_iter_return_current(return_value, array, true); } } /* }}} */ @@ -1157,22 +1161,13 @@ PHP_FUNCTION(reset) PHP_FUNCTION(current) { zval *array_zv; - zval *entry; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY_OR_OBJECT(array_zv) ZEND_PARSE_PARAMETERS_END(); HashTable *array = get_ht_for_iap(array_zv, /* separate */ false); - if ((entry = zend_hash_get_current_data(array)) == NULL) { - RETURN_FALSE; - } - - if (Z_TYPE_P(entry) == IS_INDIRECT) { - entry = Z_INDIRECT_P(entry); - } - - RETURN_COPY_DEREF(entry); + php_array_iter_return_current(return_value, array, true); } /* }}} */ @@ -1186,7 +1181,10 @@ PHP_FUNCTION(key) ZEND_PARSE_PARAMETERS_END(); HashTable *array = get_ht_for_iap(array_zv, /* separate */ false); - zend_hash_get_current_key_zval(array, return_value); + zval *entry = php_array_iter_seek_current(array, true); + if (EXPECTED(entry)) { + zend_hash_get_current_key_zval(array, return_value); + } } /* }}} */ diff --git a/ext/standard/tests/array/gh16905.phpt b/ext/standard/tests/array/gh16905.phpt new file mode 100644 index 0000000000000..89d11575789e4 --- /dev/null +++ b/ext/standard/tests/array/gh16905.phpt @@ -0,0 +1,92 @@ +--TEST-- +GH-16905 (Internal iterator functions can't handle UNDEF properties) +--FILE-- +b = 1; +$x->c = 2; + +var_dump(reset($x)); +var_dump(current($x)); +var_dump(end($x)); + +var_dump(reset($x)); +var_dump(next($x)); + +var_dump(end($x)); +var_dump(prev($x)); + +var_dump(key($x)); +var_dump(current($x)); + +$x = new TestAllUndef; +var_dump(key($x)); +var_dump(current($x)); + +$x->a = 1; +var_dump(key($x)); +var_dump(current($x)); +reset($x); +var_dump(key($x)); +var_dump(current($x)); + +?> +--EXPECTF-- +Deprecated: reset(): Calling reset() on an object is deprecated in %s on line %d +int(1) + +Deprecated: current(): Calling current() on an object is deprecated in %s on line %d +int(1) + +Deprecated: end(): Calling end() on an object is deprecated in %s on line %d +int(2) + +Deprecated: reset(): Calling reset() on an object is deprecated in %s on line %d +int(1) + +Deprecated: next(): Calling next() on an object is deprecated in %s on line %d +int(2) + +Deprecated: end(): Calling end() on an object is deprecated in %s on line %d +int(2) + +Deprecated: prev(): Calling prev() on an object is deprecated in %s on line %d +int(1) + +Deprecated: key(): Calling key() on an object is deprecated in %s on line %d +string(1) "b" + +Deprecated: current(): Calling current() on an object is deprecated in %s on line %d +int(1) + +Deprecated: key(): Calling key() on an object is deprecated in %s on line %d +NULL + +Deprecated: current(): Calling current() on an object is deprecated in %s on line %d +bool(false) + +Deprecated: key(): Calling key() on an object is deprecated in %s on line %d +NULL + +Deprecated: current(): Calling current() on an object is deprecated in %s on line %d +bool(false) + +Deprecated: reset(): Calling reset() on an object is deprecated in %s on line %d + +Deprecated: key(): Calling key() on an object is deprecated in %s on line %d +string(1) "a" + +Deprecated: current(): Calling current() on an object is deprecated in %s on line %d +int(1) From 6fca900cc7b84a0fa913e779e09ecfd0b4c4c7db Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Fri, 29 Nov 2024 02:39:33 +0300 Subject: [PATCH 58/76] Update IR IR commit: 7c26e26126123beac8dbaf811d5eac8d789d584f --- ext/opcache/jit/ir/ir.c | 4 +- ext/opcache/jit/ir/ir_aarch64.dasc | 8 ++++ ext/opcache/jit/ir/ir_gcm.c | 53 +++++++++++++++++++++- ext/opcache/jit/ir/ir_sccp.c | 71 ++++++++++++++++++++++++------ ext/opcache/jit/ir/ir_x86.dasc | 39 +++++++++++++--- 5 files changed, 152 insertions(+), 23 deletions(-) diff --git a/ext/opcache/jit/ir/ir.c b/ext/opcache/jit/ir/ir.c index 2ee00416b7dc3..815551a9b7d53 100644 --- a/ext/opcache/jit/ir/ir.c +++ b/ext/opcache/jit/ir/ir.c @@ -1110,11 +1110,13 @@ ir_ref ir_get_op(ir_ctx *ctx, ir_ref ref, int32_t n) ir_ref ir_param(ir_ctx *ctx, ir_type type, ir_ref region, const char *name, int pos) { + IR_ASSERT(ctx->ir_base[region].op == IR_START); return ir_emit(ctx, IR_OPT(IR_PARAM, type), region, ir_str(ctx, name), pos); } ir_ref ir_var(ir_ctx *ctx, ir_type type, ir_ref region, const char *name) { + IR_ASSERT(IR_IS_BB_START(ctx->ir_base[region].op)); return ir_emit(ctx, IR_OPT(IR_VAR, type), region, ir_str(ctx, name), IR_UNUSED); } @@ -1963,7 +1965,7 @@ ir_ref _ir_VAR(ir_ctx *ctx, ir_type type, const char* name) ir_ref ref = ctx->control; while (1) { - IR_ASSERT(ctx->control); + IR_ASSERT(ref); if (IR_IS_BB_START(ctx->ir_base[ref].op)) { break; } diff --git a/ext/opcache/jit/ir/ir_aarch64.dasc b/ext/opcache/jit/ir/ir_aarch64.dasc index d9d0041c01bce..27595ad31248d 100644 --- a/ext/opcache/jit/ir/ir_aarch64.dasc +++ b/ext/opcache/jit/ir/ir_aarch64.dasc @@ -3731,6 +3731,10 @@ static void ir_emit_vload(ir_ctx *ctx, ir_ref def, ir_insn *insn) int32_t offset; ir_mem mem; + if (ctx->use_lists[def].count == 1) { + /* dead load */ + return; + } IR_ASSERT(var_insn->op == IR_VAR); fp = (ctx->flags & IR_USE_FRAME_POINTER) ? IR_REG_FRAME_POINTER : IR_REG_STACK_POINTER; offset = IR_SPILL_POS_TO_OFFSET(var_insn->op3); @@ -4128,6 +4132,10 @@ static void ir_emit_block_begin(ir_ctx *ctx, ir_ref def, ir_insn *insn) dasm_State **Dst = &data->dasm_state; ir_reg def_reg = IR_REG_NUM(ctx->regs[def][0]); + if (ctx->use_lists[def].count == 1) { + /* dead load */ + return; + } | mov Rx(def_reg), sp if (IR_REG_SPILLED(ctx->regs[def][0])) { diff --git a/ext/opcache/jit/ir/ir_gcm.c b/ext/opcache/jit/ir/ir_gcm.c index 0d816ab88e229..ed1cd7e39be78 100644 --- a/ext/opcache/jit/ir/ir_gcm.c +++ b/ext/opcache/jit/ir/ir_gcm.c @@ -890,9 +890,11 @@ int ir_schedule(ir_ctx *ctx) /* Topological sort according dependencies inside each basic block */ for (b = 1, bb = ctx->cfg_blocks + 1; b <= ctx->cfg_blocks_count; b++, bb++) { + ir_ref start; + IR_ASSERT(!(bb->flags & IR_BB_UNREACHABLE)); /* Schedule BB start */ - i = bb->start; + start = i = bb->start; _xlat[i] = bb->start = insns_count; insn = &ctx->ir_base[i]; if (insn->op == IR_CASE_VAL) { @@ -904,12 +906,15 @@ int ir_schedule(ir_ctx *ctx) i = _next[i]; insn = &ctx->ir_base[i]; if (bb->flags & (IR_BB_HAS_PHI|IR_BB_HAS_PI|IR_BB_HAS_PARAM|IR_BB_HAS_VAR)) { + int count = 0; + /* Schedule PARAM, VAR, PI */ while (insn->op == IR_PARAM || insn->op == IR_VAR || insn->op == IR_PI) { _xlat[i] = insns_count; insns_count += 1; i = _next[i]; insn = &ctx->ir_base[i]; + count++; } /* Schedule PHIs */ while (insn->op == IR_PHI) { @@ -926,6 +931,52 @@ int ir_schedule(ir_ctx *ctx) } i = _next[i]; insn = &ctx->ir_base[i]; + count++; + } + /* Schedule remaining PHIs */ + if (UNEXPECTED(count < ctx->use_lists[start].count - 1)) { + ir_use_list *use_list = &ctx->use_lists[start]; + ir_ref *p, count = use_list->count; + ir_ref phis = _prev[i]; + + for (p = &ctx->use_edges[use_list->refs]; count > 0; p++, count--) { + ir_ref use = *p; + if (!_xlat[use]) { + ir_insn *use_insn = &ctx->ir_base[use]; + if (use_insn->op == IR_PARAM + || use_insn->op == IR_VAR + || use_insn->op == IR_PI + || use_insn->op == IR_PHI) { + if (_prev[use] != phis) { + /* remove "use" */ + _prev[_next[use]] = _prev[use]; + _next[_prev[use]] = _next[use]; + /* insert "use" after "phis" */ + _prev[use] = phis; + _next[use] = _next[phis]; + _prev[_next[phis]] = use; + _next[phis] = use; + } + phis = use; + _xlat[use] = insns_count; + if (use_insn->op == IR_PHI) { + ir_ref *q; + /* Reuse "n" from MERGE and skip first input */ + insns_count += ir_insn_inputs_to_len(n + 1); + for (j = n, q = use_insn->ops + 2; j > 0; q++, j--) { + ir_ref input = *q; + if (input < IR_TRUE) { + consts_count += ir_count_constant(_xlat, input); + } + } + } else { + insns_count += 1; + } + } + } + } + i = _next[phis]; + insn = &ctx->ir_base[i]; } } if (bb->successors_count > 1) { diff --git a/ext/opcache/jit/ir/ir_sccp.c b/ext/opcache/jit/ir/ir_sccp.c index 3705df45901e0..05577f05b31ff 100644 --- a/ext/opcache/jit/ir/ir_sccp.c +++ b/ext/opcache/jit/ir/ir_sccp.c @@ -255,7 +255,7 @@ static bool ir_is_dead_load_ex(ir_ctx *ctx, ir_ref ref, uint32_t flags, ir_insn { if ((flags & (IR_OP_FLAG_MEM|IR_OP_FLAG_MEM_MASK)) == (IR_OP_FLAG_MEM|IR_OP_FLAG_MEM_LOAD)) { return ctx->use_lists[ref].count == 1; - } else if (insn->op == IR_ALLOCA) { + } else if (insn->op == IR_ALLOCA || insn->op == IR_BLOCK_BEGIN) { return ctx->use_lists[ref].count == 1; } return 0; @@ -644,8 +644,13 @@ static void ir_sccp_remove_unfeasible_merge_inputs(ir_ctx *ctx, ir_insn *_values next_insn = use_insn; } else if (use_insn->op != IR_NOP) { IR_ASSERT(use_insn->op1 == ref); - use_insn->op1 = prev; - ir_use_list_add(ctx, prev, use); + IR_ASSERT(use_insn->op == IR_VAR); + ir_ref region = prev; + while (!IR_IS_BB_START(ctx->ir_base[region].op)) { + region = ctx->ir_base[region].op1; + } + use_insn->op1 = region; + ir_use_list_add(ctx, region, use); p = &ctx->use_edges[use_list->refs + k]; } } @@ -1240,6 +1245,22 @@ static void ir_merge_blocks(ir_ctx *ctx, ir_ref end, ir_ref begin, ir_bitqueue * } } +static void ir_remove_unused_vars(ir_ctx *ctx, ir_ref start, ir_ref end) +{ + ir_use_list *use_list = &ctx->use_lists[start]; + ir_ref *p, use, n = use_list->count; + + for (p = &ctx->use_edges[use_list->refs]; n > 0; p++, n--) { + use = *p; + if (use != end) { + ir_insn *use_insn = &ctx->ir_base[use]; + IR_ASSERT(use_insn->op == IR_VAR); + IR_ASSERT(ctx->use_lists[use].count == 0); + MAKE_NOP(use_insn); + } + } +} + static bool ir_try_remove_empty_diamond(ir_ctx *ctx, ir_ref ref, ir_insn *insn, ir_bitqueue *worklist) { if (insn->inputs_count == 2) { @@ -1289,8 +1310,12 @@ static bool ir_try_remove_empty_diamond(ir_ctx *ctx, ir_ref ref, ir_insn *insn, ir_ref next_ref = ctx->use_edges[ctx->use_lists[ref].refs]; ir_insn *next = &ctx->ir_base[next_ref]; - IR_ASSERT(ctx->use_lists[start1_ref].count == 1); - IR_ASSERT(ctx->use_lists[start2_ref].count == 1); + if (ctx->use_lists[start1_ref].count != 1) { + ir_remove_unused_vars(ctx, start1_ref, end1_ref); + } + if (ctx->use_lists[start2_ref].count != 1) { + ir_remove_unused_vars(ctx, start2_ref, end2_ref); + } next->op1 = root->op1; ir_use_list_replace_one(ctx, root->op1, root_ref, next_ref); @@ -1331,7 +1356,9 @@ static bool ir_try_remove_empty_diamond(ir_ctx *ctx, ir_ref ref, ir_insn *insn, if (start->op != IR_CASE_VAL && start->op != IR_CASE_DEFAULT) { return 0; } - IR_ASSERT(ctx->use_lists[start_ref].count == 1); + if (ctx->use_lists[start_ref].count != 1) { + ir_remove_unused_vars(ctx, start_ref, end_ref); + } if (!root_ref) { root_ref = start->op1; if (ctx->use_lists[root_ref].count != count) { @@ -1454,8 +1481,12 @@ static bool ir_optimize_phi(ir_ctx *ctx, ir_ref merge_ref, ir_insn *merge, ir_re } next = &ctx->ir_base[next_ref]; - IR_ASSERT(ctx->use_lists[start1_ref].count == 1); - IR_ASSERT(ctx->use_lists[start2_ref].count == 1); + if (ctx->use_lists[start1_ref].count != 1) { + ir_remove_unused_vars(ctx, start1_ref, end1_ref); + } + if (ctx->use_lists[start2_ref].count != 1) { + ir_remove_unused_vars(ctx, start2_ref, end2_ref); + } insn->op = ( (is_less ? cond->op1 : cond->op2) @@ -1540,8 +1571,12 @@ static bool ir_optimize_phi(ir_ctx *ctx, ir_ref merge_ref, ir_insn *merge, ir_re } next = &ctx->ir_base[next_ref]; - IR_ASSERT(ctx->use_lists[start1_ref].count == 1); - IR_ASSERT(ctx->use_lists[start2_ref].count == 1); + if (ctx->use_lists[start1_ref].count != 1) { + ir_remove_unused_vars(ctx, start1_ref, end1_ref); + } + if (ctx->use_lists[start2_ref].count != 1) { + ir_remove_unused_vars(ctx, start2_ref, end2_ref); + } insn->op = IR_ABS; insn->inputs_count = 1; @@ -1605,8 +1640,12 @@ static bool ir_optimize_phi(ir_ctx *ctx, ir_ref merge_ref, ir_insn *merge, ir_re } next = &ctx->ir_base[next_ref]; - IR_ASSERT(ctx->use_lists[start1_ref].count == 1); - IR_ASSERT(ctx->use_lists[start2_ref].count == 1); + if (ctx->use_lists[start1_ref].count != 1) { + ir_remove_unused_vars(ctx, start1_ref, end1_ref); + } + if (ctx->use_lists[start2_ref].count != 1) { + ir_remove_unused_vars(ctx, start2_ref, end2_ref); + } insn->op = IR_COND; insn->inputs_count = 3; @@ -2126,9 +2165,13 @@ static void ir_optimize_merge(ir_ctx *ctx, ir_ref merge_ref, ir_insn *merge, ir_ ir_ref next_ref = ctx->use_edges[use_list->refs + 1]; ir_insn *next = &ctx->ir_base[next_ref]; - IR_ASSERT(next->op != IR_PHI); - if (phi->op == IR_PHI) { + if (next->op == IR_PHI) { + SWAP_REFS(phi_ref, next_ref); + SWAP_INSNS(phi, next); + } + + if (phi->op == IR_PHI && next->op != IR_PHI) { if (next->op == IR_IF && next->op1 == merge_ref && ctx->use_lists[phi_ref].count == 1) { if (next->op2 == phi_ref) { if (ir_try_split_if(ctx, next_ref, next, worklist)) { diff --git a/ext/opcache/jit/ir/ir_x86.dasc b/ext/opcache/jit/ir/ir_x86.dasc index 1fa7001198c94..284e1480d3835 100644 --- a/ext/opcache/jit/ir/ir_x86.dasc +++ b/ext/opcache/jit/ir/ir_x86.dasc @@ -1149,8 +1149,10 @@ int ir_get_target_constraints(ir_ctx *ctx, ir_ref ref, ir_target_constraints *co } else { flags = IR_DEF_REUSES_OP1_REG | IR_USE_MUST_BE_IN_REG | IR_OP1_SHOULD_BE_IN_REG | IR_OP2_SHOULD_BE_IN_REG; } - if (IR_IS_CONST_REF(insn->op2) && insn->op1 != insn->op2) { - n = ir_add_const_tmp_reg(ctx, insn->op2, 2, n, constraints); + if (IR_IS_CONST_REF(insn->op2)) { + if (insn->op1 != insn->op2) { + n = ir_add_const_tmp_reg(ctx, insn->op2, 2, n, constraints); + } } else if (ir_rule(ctx, insn->op2) == IR_STATIC_ALLOCA) { constraints->tmp_regs[n] = IR_TMP_REG(2, IR_ADDR, IR_LOAD_SUB_REF, IR_DEF_SUB_REF); n++; @@ -1223,9 +1225,11 @@ op2_const: } else if (ir_rule(ctx, insn->op1) & IR_FUSED) { flags = IR_USE_MUST_BE_IN_REG | IR_OP2_MUST_BE_IN_REG; } - if (IR_IS_CONST_REF(insn->op2) && insn->op1 != insn->op2) { - flags = IR_USE_MUST_BE_IN_REG | IR_OP1_SHOULD_BE_IN_REG; - n = ir_add_const_tmp_reg(ctx, insn->op2, 2, n, constraints); + if (IR_IS_CONST_REF(insn->op2)) { + if (insn->op1 != insn->op2) { + flags = IR_USE_MUST_BE_IN_REG | IR_OP1_SHOULD_BE_IN_REG; + n = ir_add_const_tmp_reg(ctx, insn->op2, 2, n, constraints); + } } else if (ir_rule(ctx, insn->op2) == IR_STATIC_ALLOCA) { constraints->tmp_regs[n] = IR_TMP_REG(2, IR_ADDR, IR_LOAD_SUB_REF, IR_DEF_SUB_REF); n++; @@ -3360,10 +3364,23 @@ static ir_mem ir_fuse_addr(ir_ctx *ctx, ir_ref root, ir_ref ref) offset_insn = insn; break; case IR_LEA_IB_O: - base_reg_ref = insn->op1 * sizeof(ir_ref) + 1; - index_reg_ref = insn->op1 * sizeof(ir_ref) + 2; + op1_insn = &ctx->ir_base[insn->op1]; offset_insn = insn; scale = 1; + if (ir_rule(ctx, op1_insn->op2) == IR_STATIC_ALLOCA) { + offset = IR_SPILL_POS_TO_OFFSET(ctx->ir_base[op1_insn->op2].op3); + base_reg = (ctx->flags & IR_USE_FRAME_POINTER) ? IR_REG_FRAME_POINTER : IR_REG_STACK_POINTER; + base_reg_ref = IR_UNUSED; + index_reg_ref = insn->op1 * sizeof(ir_ref) + 1; + } else if (ir_rule(ctx, op1_insn->op1) == IR_STATIC_ALLOCA) { + offset = IR_SPILL_POS_TO_OFFSET(ctx->ir_base[op1_insn->op1].op3); + base_reg = (ctx->flags & IR_USE_FRAME_POINTER) ? IR_REG_FRAME_POINTER : IR_REG_STACK_POINTER; + base_reg_ref = IR_UNUSED; + index_reg_ref = insn->op1 * sizeof(ir_ref) + 2; + } else { + base_reg_ref = insn->op1 * sizeof(ir_ref) + 1; + index_reg_ref = insn->op1 * sizeof(ir_ref) + 2; + } break; case IR_LEA_OB_SI: index_reg_ref = insn->op2 * sizeof(ir_ref) + 1; @@ -7463,6 +7480,10 @@ static void ir_emit_vload(ir_ctx *ctx, ir_ref def, ir_insn *insn) ir_reg fp; ir_mem mem; + if (ctx->use_lists[def].count == 1) { + /* dead load */ + return; + } IR_ASSERT(var_insn->op == IR_VAR); fp = (ctx->flags & IR_USE_FRAME_POINTER) ? IR_REG_FRAME_POINTER : IR_REG_STACK_POINTER; mem = IR_MEM_BO(fp, IR_SPILL_POS_TO_OFFSET(var_insn->op3)); @@ -7909,6 +7930,10 @@ static void ir_emit_block_begin(ir_ctx *ctx, ir_ref def, ir_insn *insn) dasm_State **Dst = &data->dasm_state; ir_reg def_reg = IR_REG_NUM(ctx->regs[def][0]); + if (ctx->use_lists[def].count == 1) { + /* dead load */ + return; + } | mov Ra(def_reg), Ra(IR_REG_RSP) if (IR_REG_SPILLED(ctx->regs[def][0])) { From 69765d9220779a8667908ca455b6495750a35c0d Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Fri, 25 Oct 2024 23:32:07 +0200 Subject: [PATCH 59/76] Fix network connect poll interuption handling When connecting to socket, it is possible to get EINTR. In such case, there should be an another attempt to connect if we are not over the timeout. The timeout should be adjusted accordingly in that case. This fixes https://github.com/phpredis/phpredis/issues/1881 Closes GH-16606 --- NEWS | 3 ++ main/network.c | 108 ++++++++++++++++++++++++++++++++----------------- 2 files changed, 74 insertions(+), 37 deletions(-) diff --git a/NEWS b/NEWS index d3ec3c0e6279d..dadbb0241da20 100644 --- a/NEWS +++ b/NEWS @@ -63,6 +63,9 @@ PHP NEWS . Fixed bug GH-16905 (Internal iterator functions can't handle UNDEF properties). (nielsdos) +- Streams: + . Fixed network connect poll interuption handling. (Jakub Zelenka) + - Windows: . Fixed bug GH-16849 (Error dialog causes process to hang). (cmb) diff --git a/main/network.c b/main/network.c index d6922064561fb..021513f8ff76a 100644 --- a/main/network.c +++ b/main/network.c @@ -299,6 +299,35 @@ typedef int php_non_blocking_flags_t; fcntl(sock, F_SETFL, save) #endif +#if HAVE_GETTIMEOFDAY +/* Subtract times */ +static inline void sub_times(struct timeval a, struct timeval b, struct timeval *result) +{ + result->tv_usec = a.tv_usec - b.tv_usec; + if (result->tv_usec < 0L) { + a.tv_sec--; + result->tv_usec += 1000000L; + } + result->tv_sec = a.tv_sec - b.tv_sec; + if (result->tv_sec < 0L) { + result->tv_sec++; + result->tv_usec -= 1000000L; + } +} + +static inline void php_network_set_limit_time(struct timeval *limit_time, + struct timeval *timeout) +{ + gettimeofday(limit_time, NULL); + limit_time->tv_sec += timeout->tv_sec; + limit_time->tv_usec += timeout->tv_usec; + if (limit_time->tv_usec >= 1000000) { + limit_time->tv_usec -= 1000000; + limit_time->tv_sec++; + } +} +#endif + /* Connect to a socket using an interruptible connect with optional timeout. * Optionally, the connect can be made asynchronously, which will implicitly * enable non-blocking mode on the socket. @@ -351,25 +380,53 @@ PHPAPI int php_network_connect_socket(php_socket_t sockfd, * expected when a connection is actively refused. This way, * php_pollfd_for will return a mask with POLLOUT if the connection * is successful and with POLLPRI otherwise. */ - if ((n = php_pollfd_for(sockfd, POLLOUT|POLLPRI, timeout)) == 0) { + int events = POLLOUT|POLLPRI; #else - if ((n = php_pollfd_for(sockfd, PHP_POLLREADABLE|POLLOUT, timeout)) == 0) { + int events = PHP_POLLREADABLE|POLLOUT; +#endif + struct timeval working_timeout; +#if HAVE_GETTIMEOFDAY + struct timeval limit_time, time_now; +#endif + if (timeout) { + memcpy(&working_timeout, timeout, sizeof(working_timeout)); +#if HAVE_GETTIMEOFDAY + php_network_set_limit_time(&limit_time, &working_timeout); #endif - error = PHP_TIMEOUT_ERROR_VALUE; } - if (n > 0) { - len = sizeof(error); - /* - BSD-derived systems set errno correctly - Solaris returns -1 from getsockopt in case of error - */ - if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (char*)&error, &len) != 0) { + while (true) { + n = php_pollfd_for(sockfd, events, timeout ? &working_timeout : NULL); + if (n < 0) { + if (errno == EINTR) { +#if HAVE_GETTIMEOFDAY + if (timeout) { + gettimeofday(&time_now, NULL); + + if (!timercmp(&time_now, &limit_time, <)) { + /* time limit expired; no need for another poll */ + error = PHP_TIMEOUT_ERROR_VALUE; + break; + } else { + /* work out remaining time */ + sub_times(limit_time, time_now, &working_timeout); + } + } +#endif + continue; + } ret = -1; + } else if (n == 0) { + error = PHP_TIMEOUT_ERROR_VALUE; + } else { + len = sizeof(error); + /* BSD-derived systems set errno correctly. + * Solaris returns -1 from getsockopt in case of error. */ + if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (char*)&error, &len) != 0) { + ret = -1; + } } - } else { - /* whoops: sockfd has disappeared */ - ret = -1; + break; } ok: @@ -392,22 +449,6 @@ PHPAPI int php_network_connect_socket(php_socket_t sockfd, } /* }}} */ -/* {{{ sub_times */ -static inline void sub_times(struct timeval a, struct timeval b, struct timeval *result) -{ - result->tv_usec = a.tv_usec - b.tv_usec; - if (result->tv_usec < 0L) { - a.tv_sec--; - result->tv_usec += 1000000L; - } - result->tv_sec = a.tv_sec - b.tv_sec; - if (result->tv_sec < 0L) { - result->tv_sec++; - result->tv_usec -= 1000000L; - } -} -/* }}} */ - /* Bind to a local IP address. * Returns the bound socket, or -1 on failure. * */ @@ -777,7 +818,6 @@ PHPAPI php_socket_t php_network_accept_incoming(php_socket_t srvsock, } /* }}} */ - /* Connect to a remote host using an interruptible connect with optional timeout. * Optionally, the connect can be made asynchronously, which will implicitly * enable non-blocking mode on the socket. @@ -809,13 +849,7 @@ php_socket_t php_network_connect_socket_to_host(const char *host, unsigned short if (timeout) { memcpy(&working_timeout, timeout, sizeof(working_timeout)); #if HAVE_GETTIMEOFDAY - gettimeofday(&limit_time, NULL); - limit_time.tv_sec += working_timeout.tv_sec; - limit_time.tv_usec += working_timeout.tv_usec; - if (limit_time.tv_usec >= 1000000) { - limit_time.tv_usec -= 1000000; - limit_time.tv_sec++; - } + php_network_set_limit_time(&limit_time, &working_timeout); #endif } From d17ed3445d088825b520ca76f9f1ea24ba5939d7 Mon Sep 17 00:00:00 2001 From: Saki Takamachi Date: Fri, 29 Nov 2024 21:28:38 +0900 Subject: [PATCH 60/76] Fixed GH-16978: Avoid unnecessary padding with leading zeros (#16988) Fixed an issue where leading zeros were padded beyond the allocated memory. fixes #16978 closes #16988 --- NEWS | 4 ++++ ext/bcmath/libbcmath/src/div.c | 1 + ext/bcmath/tests/gh16978.phpt | 12 ++++++++++++ 3 files changed, 17 insertions(+) create mode 100644 ext/bcmath/tests/gh16978.phpt diff --git a/NEWS b/NEWS index f8f96db6881bc..c18859f8f649b 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,10 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.4.2 +- BcMath: + . Fixed bug GH-16978 (Avoid unnecessary padding with leading zeros) + (Saki Takamachi) + - Core: . Fixed bug GH-16344 (setRawValueWithoutLazyInitialization() and skipLazyInitialization() may change initialized proxy). (Arnaud) diff --git a/ext/bcmath/libbcmath/src/div.c b/ext/bcmath/libbcmath/src/div.c index 9c8344fe771a8..e9377fcfc4279 100644 --- a/ext/bcmath/libbcmath/src/div.c +++ b/ext/bcmath/libbcmath/src/div.c @@ -436,6 +436,7 @@ bool bc_divide(bc_num numerator, bc_num divisor, bc_num *quot, size_t scale) numerator_bottom_extension = 0; numeratorend -= scale_diff > numerator_top_extension ? scale_diff - numerator_top_extension : 0; } + numerator_top_extension = MIN(numerator_top_extension, scale); } else { numerator_bottom_extension += scale - numerator_scale; } diff --git a/ext/bcmath/tests/gh16978.phpt b/ext/bcmath/tests/gh16978.phpt new file mode 100644 index 0000000000000..4bb19a9be6bbe --- /dev/null +++ b/ext/bcmath/tests/gh16978.phpt @@ -0,0 +1,12 @@ +--TEST-- +GH-16978 Stack buffer overflow ext/bcmath/libbcmath/src/div.c:464:12 in bc_divide +--EXTENSIONS-- +bcmath +--FILE-- + +--EXPECT-- +0.0000000000 +0.0 From 5ed438fe0756cc611a7bf73b68fb6196f836544b Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Fri, 29 Nov 2024 16:28:27 +0300 Subject: [PATCH 61/76] Update IR IR commit: 673308a039eed5a2fdf4a2783b3dd3d6010a8c19 --- ext/opcache/jit/ir/ir.c | 13 +++++++++---- ext/opcache/jit/ir/ir_emit.c | 5 ++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/ext/opcache/jit/ir/ir.c b/ext/opcache/jit/ir/ir.c index 815551a9b7d53..b21f8ce7d9519 100644 --- a/ext/opcache/jit/ir/ir.c +++ b/ext/opcache/jit/ir/ir.c @@ -1176,7 +1176,7 @@ void ir_build_def_use_lists(ir_ctx *ctx) use_list->count = 0; } - edges = ir_mem_malloc(edges_count * sizeof(ir_ref)); + edges = ir_mem_malloc(IR_ALIGNED_SIZE(edges_count * sizeof(ir_ref), 4096)); for (i = IR_UNUSED + 1, insn = ctx->ir_base + i; i < ctx->insns_count;) { n = insn->inputs_count; for (j = n, p = insn->ops + 1; j > 0; j--, p++) { @@ -1245,7 +1245,7 @@ void ir_build_def_use_lists(ir_ctx *ctx) } ctx->use_edges_count = edges_count; - edges = ir_mem_malloc(edges_count * sizeof(ir_ref)); + edges = ir_mem_malloc(IR_ALIGNED_SIZE(edges_count * sizeof(ir_ref), 4096)); for (use_list = lists + ctx->insns_count - 1; use_list != lists; use_list--) { n = use_list->refs; if (n) { @@ -1356,8 +1356,13 @@ bool ir_use_list_add(ir_ctx *ctx, ir_ref to, ir_ref ref) use_list->count++; return 0; } else { - /* Reallocate the whole edges buffer (this is inefficient) */ - ctx->use_edges = ir_mem_realloc(ctx->use_edges, (ctx->use_edges_count + use_list->count + 1) * sizeof(ir_ref)); + size_t old_size = IR_ALIGNED_SIZE(ctx->use_edges_count * sizeof(ir_ref), 4096); + size_t new_size = IR_ALIGNED_SIZE((ctx->use_edges_count + use_list->count + 1) * sizeof(ir_ref), 4096); + + if (old_size < new_size) { + /* Reallocate the whole edges buffer (this is inefficient) */ + ctx->use_edges = ir_mem_realloc(ctx->use_edges, new_size); + } memcpy(ctx->use_edges + ctx->use_edges_count, ctx->use_edges + use_list->refs, use_list->count * sizeof(ir_ref)); use_list->refs = ctx->use_edges_count; ctx->use_edges[use_list->refs + use_list->count] = ref; diff --git a/ext/opcache/jit/ir/ir_emit.c b/ext/opcache/jit/ir/ir_emit.c index ea39830da08c9..83fc242a20c11 100644 --- a/ext/opcache/jit/ir/ir_emit.c +++ b/ext/opcache/jit/ir/ir_emit.c @@ -566,6 +566,9 @@ static int ir_parallel_copy(ir_ctx *ctx, ir_copy *copies, int count, ir_reg tmp_ if (IR_IS_TYPE_INT(type)) { #ifdef IR_HAVE_SWAP_INT if (pred[from] == to) { + if (ir_type_size[types[to]] > ir_type_size[type]) { + type = types[to]; + } ir_emit_swap(ctx, type, to, from); IR_REGSET_EXCL(todo, from); loc[to] = from; @@ -579,7 +582,7 @@ static int ir_parallel_copy(ir_ctx *ctx, ir_copy *copies, int count, ir_reg tmp_ loc[to] = tmp_reg; } else { #ifdef IR_HAVE_SWAP_FP - if (pred[from] == to) { + if (pred[from] == to && types[to] == type) { ir_emit_swap_fp(ctx, type, to, from); IR_REGSET_EXCL(todo, from); loc[to] = from; From 9bae8933a3f4fee78cdd6c9b2f592ab8d62e9cf1 Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Fri, 29 Nov 2024 15:06:32 +0100 Subject: [PATCH 62/76] Fix GH-16991: Getting typeinfo of non DISPATCH variant segfaults We must not assume that any `VARIANT` implements `IDispatch`. Closes GH-16992. --- NEWS | 4 ++++ ext/com_dotnet/com_typeinfo.c | 2 +- ext/com_dotnet/tests/gh16991.phpt | 10 ++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 ext/com_dotnet/tests/gh16991.phpt diff --git a/NEWS b/NEWS index dadbb0241da20..eca09704cfc59 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,10 @@ PHP NEWS . Fixed jdtogregorian overflow. (David Carlier) . Fixed cal_to_jd julian_days argument overflow. (David Carlier) +- COM: + . Fixed bug GH-16991 (Getting typeinfo of non DISPATCH variant segfaults). + (cmb) + - Core: . Fail early in *nix configuration build script. (hakre) . Fixed bug GH-16727 (Opcache bad signal 139 crash in ZTS bookworm diff --git a/ext/com_dotnet/com_typeinfo.c b/ext/com_dotnet/com_typeinfo.c index ccdcc3ff7e8c8..e120dc5446989 100644 --- a/ext/com_dotnet/com_typeinfo.c +++ b/ext/com_dotnet/com_typeinfo.c @@ -331,7 +331,7 @@ ITypeInfo *php_com_locate_typeinfo(zend_string *type_lib_name, php_com_dotnet_ob if (obj->typeinfo) { ITypeInfo_AddRef(obj->typeinfo); return obj->typeinfo; - } else { + } else if (V_VT(&obj->v) == VT_DISPATCH) { IDispatch_GetTypeInfo(V_DISPATCH(&obj->v), 0, LANG_NEUTRAL, &typeinfo); if (typeinfo) { return typeinfo; diff --git a/ext/com_dotnet/tests/gh16991.phpt b/ext/com_dotnet/tests/gh16991.phpt new file mode 100644 index 0000000000000..3623f1f3c4a63 --- /dev/null +++ b/ext/com_dotnet/tests/gh16991.phpt @@ -0,0 +1,10 @@ +--TEST-- +GH-16991 (Getting typeinfo of non DISPATCH variant segfaults) +--EXTENSIONS-- +com_dotnet +--FILE-- + +--EXPECTF-- +Warning: com_print_typeinfo(): Unable to find typeinfo using the parameters supplied in %s on line %d From f1fc4e8ff7075781ffed916eee05a6e240b5185f Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 27 Nov 2024 20:31:37 +0100 Subject: [PATCH 63/76] Fix GH-16957: Assertion failure in array_shift with self-referencing array We have an RC1 violation because we're immediately dereferencing and copying the resulting array in the test case. Instead, transfer the lifetime using RETVAL_COPY_VALUE and unwrap only after the internal iterator is reset. Closes GH-16970. --- NEWS | 2 ++ ext/standard/array.c | 10 +++++-- ext/standard/tests/array/gh16957.phpt | 41 +++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 ext/standard/tests/array/gh16957.phpt diff --git a/NEWS b/NEWS index aba1f7a76ca14..f535cf7187c7a 100644 --- a/NEWS +++ b/NEWS @@ -76,6 +76,8 @@ PHP NEWS - Standard: . Fixed bug GH-16905 (Internal iterator functions can't handle UNDEF properties). (nielsdos) + . Fixed bug GH-16957 (Assertion failure in array_shift with + self-referencing array). (nielsdos) - Streams: . Fixed network connect poll interuption handling. (Jakub Zelenka) diff --git a/ext/standard/array.c b/ext/standard/array.c index 73ba7e5d7a488..7382e1e9f8bd9 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -3511,7 +3511,8 @@ PHP_FUNCTION(array_shift) } idx++; } - RETVAL_COPY_DEREF(val); + RETVAL_COPY_VALUE(val); + ZVAL_UNDEF(val); /* Delete the first value */ zend_hash_packed_del_val(Z_ARRVAL_P(stack), val); @@ -3565,7 +3566,8 @@ PHP_FUNCTION(array_shift) } idx++; } - RETVAL_COPY_DEREF(val); + RETVAL_COPY_VALUE(val); + ZVAL_UNDEF(val); /* Delete the first value */ zend_hash_del_bucket(Z_ARRVAL_P(stack), p); @@ -3589,6 +3591,10 @@ PHP_FUNCTION(array_shift) } zend_hash_internal_pointer_reset(Z_ARRVAL_P(stack)); + + if (Z_ISREF_P(return_value)) { + zend_unwrap_reference(return_value); + } } /* }}} */ diff --git a/ext/standard/tests/array/gh16957.phpt b/ext/standard/tests/array/gh16957.phpt new file mode 100644 index 0000000000000..a716228249e7d --- /dev/null +++ b/ext/standard/tests/array/gh16957.phpt @@ -0,0 +1,41 @@ +--TEST-- +GH-16957 (Assertion failure in array_shift with self-referencing array) +--FILE-- + 1, 300 => 'two'); +var_dump($shifted = array_shift($new_array2)); +var_dump($new_array2); +var_dump($new_array2 === $shifted); +?> +--EXPECT-- +array(2) { + [0]=> + int(1) + [1]=> + string(3) "two" +} +array(2) { + [0]=> + int(1) + [1]=> + string(3) "two" +} +bool(true) +array(2) { + [0]=> + int(1) + [1]=> + string(3) "two" +} +array(2) { + [0]=> + int(1) + [1]=> + string(3) "two" +} +bool(true) From 94fa2a4ce103eaa3ac745e2f3d920b81bd60dc18 Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Fri, 29 Nov 2024 19:43:07 +0100 Subject: [PATCH 64/76] Fix potential OOB read in zend_dirname() on Windows Only on Windows `IS_SLASH_P()` may read the previous byte, and so may in unlikely cases read one byte out of bounds. Since `IS_SLASH_P()` is in a public header (albeit not likely to be used by external extensions or SAPIs), we introduce `IS_SLASH_P_EX()` which accepts a second argument to prevent that OOB read. It should be noted that the PHP userland function `dirname()` is not affected by this issue, since it does not call `zend_dirname()` on Windows. Closes GH-16995. --- NEWS | 1 + Zend/zend_compile.c | 6 +++--- Zend/zend_virtual_cwd.h | 5 +++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index eca09704cfc59..84fdaf00b4876 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,7 @@ PHP NEWS . Fixed bug GH-16630 (UAF in lexer with encoding translation and heredocs). (nielsdos) . Fix is_zend_ptr() huge block comparison. (nielsdos) + . Fixed potential OOB read in zend_dirname() on Windows. (cmb) - Curl: . Fix various memory leaks in curl mime handling. (nielsdos) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 38d378a4175bb..52b3417234cf7 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1997,7 +1997,7 @@ ZEND_API size_t zend_dirname(char *path, size_t len) } /* Strip trailing slashes */ - while (end >= path && IS_SLASH_P(end)) { + while (end >= path && IS_SLASH_P_EX(end, end == path)) { end--; } if (end < path) { @@ -2008,7 +2008,7 @@ ZEND_API size_t zend_dirname(char *path, size_t len) } /* Strip filename */ - while (end >= path && !IS_SLASH_P(end)) { + while (end >= path && !IS_SLASH_P_EX(end, end == path)) { end--; } if (end < path) { @@ -2019,7 +2019,7 @@ ZEND_API size_t zend_dirname(char *path, size_t len) } /* Strip slashes which came before the file name */ - while (end >= path && IS_SLASH_P(end)) { + while (end >= path && IS_SLASH_P_EX(end, end == path)) { end--; } if (end < path) { diff --git a/Zend/zend_virtual_cwd.h b/Zend/zend_virtual_cwd.h index 728e3ba69d888..28cbf6300b8ec 100644 --- a/Zend/zend_virtual_cwd.h +++ b/Zend/zend_virtual_cwd.h @@ -73,8 +73,11 @@ typedef unsigned short mode_t; #define DEFAULT_SLASH '\\' #define DEFAULT_DIR_SEPARATOR ';' #define IS_SLASH(c) ((c) == '/' || (c) == '\\') +// IS_SLASH_P() may read the previous char on Windows, which may be OOB; use IS_SLASH_P_EX() instead #define IS_SLASH_P(c) (*(c) == '/' || \ (*(c) == '\\' && !IsDBCSLeadByte(*(c-1)))) +#define IS_SLASH_P_EX(c, first_byte) (*(c) == '/' || \ + (*(c) == '\\' && ((first_byte) || !IsDBCSLeadByte(*(c-1))))) /* COPY_WHEN_ABSOLUTE is 2 under Win32 because by chance both regular absolute paths in the file system and UNC paths need copying of two characters */ @@ -98,7 +101,9 @@ typedef unsigned short mode_t; #endif #define IS_SLASH(c) ((c) == '/') +// IS_SLASH_P() may read the previous char on Windows, which may be OOB; use IS_SLASH_P_EX() instead #define IS_SLASH_P(c) (*(c) == '/') +#define IS_SLASH_P_EX(c, first_byte) IS_SLASH_P(c) #endif From aab784263d13dc56e8744260a0bc504435b012ef Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 30 Nov 2024 12:22:42 +0100 Subject: [PATCH 65/76] Fix GH-16998: UBSAN warning in rfc1867 The "else branch" of `next_line` can reset the `buf_begin` field to NULL, causing the next invocation to pass NULL to `memchr` with a 0 length. When UBSAN is enabled this causes an UBSAN abort. Real world impact is likely none because of the 0 length. To fix this, don't set the pointer to NULL, which means that the `memchr` will return NULL and since `self->bytes_in_buffer < self->bufsize` we return NULL and request more data through `fill_buffer`. That function will reset `buf_begin` and `bytes_in_buffer` so that the next invocation works fine. I chose this solution so we have an invariant that `buf_begin` is never NULL, which makes reasoning easier. An alternative solution is keeping the NULLing of `buf_begin` and add an extra check at the top of `next_line`, but I didn't like special casing this. Closes GH-17000. --- NEWS | 3 +++ main/rfc1867.c | 2 +- tests/basic/gh16998.phpt | 49 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 tests/basic/gh16998.phpt diff --git a/NEWS b/NEWS index 84fdaf00b4876..9a0893ac43ef0 100644 --- a/NEWS +++ b/NEWS @@ -60,6 +60,9 @@ PHP NEWS . Fixed bug GH-15208 (Segfault with breakpoint map and phpdbg_clear()). (nielsdos) +- SAPI: + . Fixed bug GH-16998 (UBSAN warning in rfc1867). (nielsdos) + - SimpleXML: . Fixed bug GH-16808 (Segmentation fault in RecursiveIteratorIterator ->current() with a xml element input). (nielsdos) diff --git a/main/rfc1867.c b/main/rfc1867.c index 12794c414b342..fbfd6e78f9994 100644 --- a/main/rfc1867.c +++ b/main/rfc1867.c @@ -341,8 +341,8 @@ static char *next_line(multipart_buffer *self) } /* return entire buffer as a partial line */ line[self->bufsize] = 0; - self->buf_begin = ptr; self->bytes_in_buffer = 0; + /* Let fill_buffer() handle the reset of self->buf_begin */ } return line; diff --git a/tests/basic/gh16998.phpt b/tests/basic/gh16998.phpt new file mode 100644 index 0000000000000..8bfcbbda99dd0 --- /dev/null +++ b/tests/basic/gh16998.phpt @@ -0,0 +1,49 @@ +--TEST-- +GH-16998 (UBSAN warning in rfc1867) +--SKIPIF-- + +--FILE-- + '1', + 'CONTENT_TYPE' => "multipart/form-data; boundary=", + 'CONTENT_LENGTH' => strlen($body), + 'REQUEST_METHOD' => 'POST', + 'SCRIPT_FILENAME' => __DIR__ . '/GHSA-9pqp-7h25-4f32.inc', +]); +$spec = [ + 0 => ['pipe', 'r'], + 1 => STDOUT, + 2 => STDOUT, +]; +$pipes = []; +$handle = proc_open($cmd, $spec, $pipes, getcwd(), $env); +fwrite($pipes[0], $body); +proc_close($handle); +?> +--EXPECTF-- +X-Powered-By: PHP/%s +Content-type: text/html; charset=UTF-8 + +Hello world +array(0) { +} From 73ebc92617090fdd901592196ba708f0403a6002 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 27 Nov 2024 19:12:24 +0000 Subject: [PATCH 66/76] Fix GH-16959: snmpget modifies the `object_id` (as array). Instead of modifying the zval, we use the zend_try_get_string. close GH-16969 --- NEWS | 4 ++ ext/snmp/snmp.c | 80 +++++++++++++++++++++++++++---------- ext/snmp/tests/gh16959.phpt | 69 ++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 20 deletions(-) create mode 100644 ext/snmp/tests/gh16959.phpt diff --git a/NEWS b/NEWS index 9a0893ac43ef0..61c53db1378b5 100644 --- a/NEWS +++ b/NEWS @@ -67,6 +67,10 @@ PHP NEWS . Fixed bug GH-16808 (Segmentation fault in RecursiveIteratorIterator ->current() with a xml element input). (nielsdos) +- SNMP: + . Fixed bug GH-16959 (snmget modifies the object_id array). + (David Carlier) + - Standard: . Fixed bug GH-16905 (Internal iterator functions can't handle UNDEF properties). (nielsdos) diff --git a/ext/snmp/snmp.c b/ext/snmp/snmp.c index 299d2d8fd08c8..dbf1aa553d88a 100644 --- a/ext/snmp/snmp.c +++ b/ext/snmp/snmp.c @@ -626,6 +626,31 @@ static void php_snmp_internal(INTERNAL_FUNCTION_PARAMETERS, int st, } /* }}} */ +static void php_snmp_zend_string_release_from_char_pointer(char *ptr) { + if (ptr) { + zend_string *pptr = (zend_string *)(ptr - XtOffsetOf(zend_string, val)); + zend_string_release(pptr); + } +} + +static void php_free_objid_query(struct objid_query *objid_query, HashTable* oid_ht, const HashTable *value_ht, int st) { + if (oid_ht) { + uint32_t count = zend_hash_num_elements(oid_ht); + + for (uint32_t i = 0; i < count; i ++) { + snmpobjarg *arg = &objid_query->vars[i]; + if (!arg->oid) { + break; + } + if (value_ht) { + php_snmp_zend_string_release_from_char_pointer(arg->value); + } + php_snmp_zend_string_release_from_char_pointer(arg->oid); + } + } + efree(objid_query->vars); +} + /* {{{ php_snmp_parse_oid * * OID parser (and type, value for SNMP_SET command) @@ -674,10 +699,15 @@ static bool php_snmp_parse_oid( return false; } objid_query->vars = (snmpobjarg *)safe_emalloc(sizeof(snmpobjarg), zend_hash_num_elements(oid_ht), 0); + memset(objid_query->vars, 0, sizeof(snmpobjarg) * zend_hash_num_elements(oid_ht)); objid_query->array_output = (st & SNMP_CMD_SET) == 0; ZEND_HASH_FOREACH_VAL(oid_ht, tmp_oid) { - convert_to_string(tmp_oid); - objid_query->vars[objid_query->count].oid = Z_STRVAL_P(tmp_oid); + zend_string *tmp = zval_try_get_string(tmp_oid); + if (!tmp) { + php_free_objid_query(objid_query, oid_ht, value_ht, st); + return false; + } + objid_query->vars[objid_query->count].oid = ZSTR_VAL(tmp); if (st & SNMP_CMD_SET) { if (type_str) { pptr = ZSTR_VAL(type_str); @@ -701,18 +731,24 @@ static bool php_snmp_parse_oid( } } if (idx_type < type_ht->nNumUsed) { - convert_to_string(tmp_type); - if (Z_STRLEN_P(tmp_type) != 1) { + zend_string *type = zval_try_get_string(tmp_type); + if (!type) { + php_free_objid_query(objid_query, oid_ht, value_ht, st); + return false; + } + size_t len = ZSTR_LEN(type); + char ptype = *ZSTR_VAL(type); + zend_string_release(type); + if (len != 1) { zend_value_error("Type must be a single character"); - efree(objid_query->vars); + php_free_objid_query(objid_query, oid_ht, value_ht, st); return false; } - pptr = Z_STRVAL_P(tmp_type); - objid_query->vars[objid_query->count].type = *pptr; + objid_query->vars[objid_query->count].type = ptype; idx_type++; } else { - php_error_docref(NULL, E_WARNING, "'%s': no type set", Z_STRVAL_P(tmp_oid)); - efree(objid_query->vars); + php_error_docref(NULL, E_WARNING, "'%s': no type set", ZSTR_VAL(tmp)); + php_free_objid_query(objid_query, oid_ht, value_ht, st); return false; } } @@ -738,12 +774,16 @@ static bool php_snmp_parse_oid( } } if (idx_value < value_ht->nNumUsed) { - convert_to_string(tmp_value); - objid_query->vars[objid_query->count].value = Z_STRVAL_P(tmp_value); + zend_string *tmp = zval_try_get_string(tmp_value); + if (!tmp) { + php_free_objid_query(objid_query, oid_ht, value_ht, st); + return false; + } + objid_query->vars[objid_query->count].value = ZSTR_VAL(tmp); idx_value++; } else { - php_error_docref(NULL, E_WARNING, "'%s': no value set", Z_STRVAL_P(tmp_oid)); - efree(objid_query->vars); + php_error_docref(NULL, E_WARNING, "'%s': no value set", ZSTR_VAL(tmp)); + php_free_objid_query(objid_query, oid_ht, value_ht, st); return false; } } @@ -756,14 +796,14 @@ static bool php_snmp_parse_oid( if (st & SNMP_CMD_WALK) { if (objid_query->count > 1) { php_snmp_error(object, PHP_SNMP_ERRNO_OID_PARSING_ERROR, "Multi OID walks are not supported!"); - efree(objid_query->vars); + php_free_objid_query(objid_query, oid_ht, value_ht, st); return false; } objid_query->vars[0].name_length = MAX_NAME_LEN; if (strlen(objid_query->vars[0].oid)) { /* on a walk, an empty string means top of tree - no error */ if (!snmp_parse_oid(objid_query->vars[0].oid, objid_query->vars[0].name, &(objid_query->vars[0].name_length))) { php_snmp_error(object, PHP_SNMP_ERRNO_OID_PARSING_ERROR, "Invalid object identifier: %s", objid_query->vars[0].oid); - efree(objid_query->vars); + php_free_objid_query(objid_query, oid_ht, value_ht, st); return false; } } else { @@ -775,7 +815,7 @@ static bool php_snmp_parse_oid( objid_query->vars[objid_query->offset].name_length = MAX_OID_LEN; if (!snmp_parse_oid(objid_query->vars[objid_query->offset].oid, objid_query->vars[objid_query->offset].name, &(objid_query->vars[objid_query->offset].name_length))) { php_snmp_error(object, PHP_SNMP_ERRNO_OID_PARSING_ERROR, "Invalid object identifier: %s", objid_query->vars[objid_query->offset].oid); - efree(objid_query->vars); + php_free_objid_query(objid_query, oid_ht, value_ht, st); return false; } } @@ -1252,12 +1292,12 @@ static void php_snmp(INTERNAL_FUNCTION_PARAMETERS, int st, int version) if (session_less_mode) { if (!netsnmp_session_init(&session, version, a1, a2, timeout, retries)) { - efree(objid_query.vars); + php_free_objid_query(&objid_query, oid_ht, value_ht, st); netsnmp_session_free(&session); RETURN_FALSE; } if (version == SNMP_VERSION_3 && !netsnmp_session_set_security(session, a3, a4, a5, a6, a7, NULL, NULL)) { - efree(objid_query.vars); + php_free_objid_query(&objid_query, oid_ht, value_ht, st); netsnmp_session_free(&session); /* Warning message sent already, just bail out */ RETURN_FALSE; @@ -1268,7 +1308,7 @@ static void php_snmp(INTERNAL_FUNCTION_PARAMETERS, int st, int version) session = snmp_object->session; if (!session) { zend_throw_error(NULL, "Invalid or uninitialized SNMP object"); - efree(objid_query.vars); + php_free_objid_query(&objid_query, oid_ht, value_ht, st); RETURN_THROWS(); } @@ -1294,7 +1334,7 @@ static void php_snmp(INTERNAL_FUNCTION_PARAMETERS, int st, int version) php_snmp_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU, st, session, &objid_query); - efree(objid_query.vars); + php_free_objid_query(&objid_query, oid_ht, value_ht, st); if (session_less_mode) { netsnmp_session_free(&session); diff --git a/ext/snmp/tests/gh16959.phpt b/ext/snmp/tests/gh16959.phpt new file mode 100644 index 0000000000000..ce647b15b9dac --- /dev/null +++ b/ext/snmp/tests/gh16959.phpt @@ -0,0 +1,69 @@ +--TEST-- +snmpget() modifies object_id array source +--EXTENSIONS-- +snmp +--SKIPIF-- + +--FILE-- + 077, -066 => -066, -0345 => -0345, 0 => 0 +); +var_dump($bad_object_ids); +var_dump(snmpget($hostname, "", $bad_object_ids) === false); +// The array should remain unmodified +var_dump($bad_object_ids); +try { + snmpget($hostname, "", [0 => new stdClass()]); +} catch (Throwable $e) { + echo $e->getMessage() . PHP_EOL; +} + +try { + snmp2_set($hostname, $communityWrite, $bad_object_ids, array(new stdClass()), array(null)); +} catch (Throwable $e) { + echo $e->getMessage() . PHP_EOL; +} +try { + snmp2_set($hostname, $communityWrite, $bad_object_ids, array("toolongtype"), array(null)); +} catch (Throwable $e) { + echo $e->getMessage() . PHP_EOL; +} +try { + snmp2_set($hostname, $communityWrite, $bad_object_ids, array(str_repeat("onetoomuch", random_int(1, 1))), array(null)); +} catch (Throwable $e) { + echo $e->getMessage(); +} +?> +--EXPECTF-- +array(4) { + [63]=> + int(63) + [-54]=> + int(-54) + [-229]=> + int(-229) + [0]=> + int(0) +} + +Warning: snmpget(): Invalid object identifier: -54 in %s on line %d +bool(true) +array(4) { + [63]=> + int(63) + [-54]=> + int(-54) + [-229]=> + int(-229) + [0]=> + int(0) +} +Object of class stdClass could not be converted to string +Object of class stdClass could not be converted to string +Type must be a single character +Type must be a single character From d7a37cc9ada85d2ae7d21e71dffd9bbd9c3b43b9 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 1 Dec 2024 15:18:44 +0100 Subject: [PATCH 67/76] Add missing backslash for TokenList interfaces in stub (#17009) Discovered while trying to generate class synopses. --- ext/dom/php_dom.stub.php | 2 +- ext/dom/php_dom_arginfo.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/dom/php_dom.stub.php b/ext/dom/php_dom.stub.php index f46550a012c0c..81713403e0e09 100644 --- a/ext/dom/php_dom.stub.php +++ b/ext/dom/php_dom.stub.php @@ -2096,7 +2096,7 @@ public function saveXmlFile(string $filename, int $options = 0): int|false {} * @not-serializable * @strict-properties */ - final class TokenList implements IteratorAggregate, Countable + final class TokenList implements \IteratorAggregate, \Countable { /** @implementation-alias Dom\Node::__construct */ private function __construct() {} diff --git a/ext/dom/php_dom_arginfo.h b/ext/dom/php_dom_arginfo.h index 1481c39bc1e03..ea42d6de49801 100644 --- a/ext/dom/php_dom_arginfo.h +++ b/ext/dom/php_dom_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 55ab8f866af63bd2edf96839d35bc8aba88e37ca */ + * Stub hash: d8a9d33a072c3c9e3798be5eee1833163a18f441 */ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_dom_import_simplexml, 0, 1, DOMAttr|DOMElement, 0) ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 0) @@ -3525,13 +3525,13 @@ static zend_class_entry *register_class_Dom_XMLDocument(zend_class_entry *class_ return class_entry; } -static zend_class_entry *register_class_Dom_TokenList(zend_class_entry *class_entry_Dom_IteratorAggregate, zend_class_entry *class_entry_Dom_Countable) +static zend_class_entry *register_class_Dom_TokenList(zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_Countable) { zend_class_entry ce, *class_entry; INIT_NS_CLASS_ENTRY(ce, "Dom", "TokenList", class_Dom_TokenList_methods); class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE); - zend_class_implements(class_entry, 2, class_entry_Dom_IteratorAggregate, class_entry_Dom_Countable); + zend_class_implements(class_entry, 2, class_entry_IteratorAggregate, class_entry_Countable); zval property_length_default_value; ZVAL_UNDEF(&property_length_default_value); From 03bb112fb277f45abff861550a379dff2a56ea6d Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Mon, 2 Dec 2024 13:30:26 +0300 Subject: [PATCH 68/76] Fix GH-16984: function JIT overflow bug (#17015) --- ext/opcache/jit/zend_jit_ir.c | 4 +-- ext/opcache/tests/jit/gh16984.phpt | 41 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 ext/opcache/tests/jit/gh16984.phpt diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index e4b68d23520c8..beab53894a1b8 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -7204,9 +7204,9 @@ static int zend_jit_cmp(zend_jit_ctx *jit, while (n) { n--; - ir_IF_TRUE(end_inputs->refs[n]); + jit_IF_TRUE_FALSE_ex(jit, end_inputs->refs[n], label); ir_END_list(true_inputs); - ir_IF_FALSE(end_inputs->refs[n]); + jit_IF_TRUE_FALSE_ex(jit, end_inputs->refs[n], label2); ir_END_list(false_inputs); } ir_MERGE_list(true_inputs); diff --git a/ext/opcache/tests/jit/gh16984.phpt b/ext/opcache/tests/jit/gh16984.phpt new file mode 100644 index 0000000000000..8432959c41027 --- /dev/null +++ b/ext/opcache/tests/jit/gh16984.phpt @@ -0,0 +1,41 @@ +--TEST-- +GH-16984 (function JIT overflow bug) +--EXTENSIONS-- +opcache +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit_buffer_size=32M +opcache.jit=function +--FILE-- +foo($value); + if ($val <= PHP_INT_MAX) { + $test->integer = $val; + } +} + +function main() { + $test = new Test; + foo($test, 9223372036854775806); + foo($test, 9223372036854775807); // Also reproduces without this call, but this imitates the psalm code + var_dump($test->integer); +} + +main(); +?> +--EXPECT-- +int(9223372036854775807) From c2d3734e899cd457d04f5d6ed93f467ec23fdd01 Mon Sep 17 00:00:00 2001 From: divinity76 Date: Sat, 21 Sep 2024 08:44:02 +0200 Subject: [PATCH 69/76] Fix GH-15964: printf() can strip sign of -INF We need to cater to negative infinity explicitly. Co-authored-by: Christoph M. Becker Closes GH-15965. --- NEWS | 1 + ext/standard/formatted_print.c | 7 ++++--- tests/strings/002.phpt | 4 ++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 675d7dfd37a30..e572109d26714 100644 --- a/NEWS +++ b/NEWS @@ -15,6 +15,7 @@ PHP NEWS skipLazyInitialization() may change initialized proxy). (Arnaud) . Fix is_zend_ptr() huge block comparison. (nielsdos) . Fixed potential OOB read in zend_dirname() on Windows. (cmb) + . Fixed bug GH-15964 (printf() can strip sign of -INF). (divinity76, cmb) - Curl: . Fix various memory leaks in curl mime handling. (nielsdos) diff --git a/ext/standard/formatted_print.c b/ext/standard/formatted_print.c index ba0f73d9a9c22..8d8c09f443c04 100644 --- a/ext/standard/formatted_print.c +++ b/ext/standard/formatted_print.c @@ -246,9 +246,10 @@ php_sprintf_appenddouble(zend_string **buffer, size_t *pos, } if (zend_isinf(number)) { - is_negative = (number<0); - php_sprintf_appendstring(buffer, pos, "INF", 3, 0, padding, - alignment, 3, is_negative, 0, always_sign); + is_negative = (number<0); + char *str = is_negative ? "-INF" : "INF"; + php_sprintf_appendstring(buffer, pos, str, strlen(str), 0, padding, + alignment, strlen(str), is_negative, 0, always_sign); return; } diff --git a/tests/strings/002.phpt b/tests/strings/002.phpt index 54630836b1632..6284e9bf5d339 100644 --- a/tests/strings/002.phpt +++ b/tests/strings/002.phpt @@ -44,6 +44,8 @@ try { } catch(\ValueError $e) { print('Error found: '.$e->getMessage()."\n"); } +printf("printf test 31:%.17g\n", INF); +printf("printf test 32:%.17g\n", -INF); vprintf("vprintf test 1:%2\$-2d %1\$2d\n", array(1, 2)); @@ -83,4 +85,6 @@ printf test 27:3 1 2 printf test 28:02 1 printf test 29:2 1 printf test 30:Error found: Argument number specifier must be greater than zero and less than 2147483647 +printf test 31:INF +printf test 32:-INF vprintf test 1:2 1 From 1a6f8a0b79cea1019b12fc42d96a45a55fe01385 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 30 Nov 2024 19:16:51 +0100 Subject: [PATCH 70/76] Fix some MariaDB test failures Allow other wording too such that these mysqli tests pass. Closes GH-17004. --- ext/mysqli/tests/063.phpt | 4 ++-- ext/mysqli/tests/bug71863.phpt | 2 +- ext/mysqli/tests/mysqli_stmt_datatype_change.phpt | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/mysqli/tests/063.phpt b/ext/mysqli/tests/063.phpt index 32166b287df36..a0a657de002e6 100644 --- a/ext/mysqli/tests/063.phpt +++ b/ext/mysqli/tests/063.phpt @@ -29,6 +29,6 @@ require_once('skipifconnectfailure.inc'); $mysql->close(); ?> ---EXPECT-- +--EXPECTF-- string(3) "foo" -Unknown column 'invalid' in 'field list' +Unknown column 'invalid' in '%r(SELECT|field list)%r' diff --git a/ext/mysqli/tests/bug71863.phpt b/ext/mysqli/tests/bug71863.phpt index 6b8f66b42a5d5..ef6e5130271af 100644 --- a/ext/mysqli/tests/bug71863.phpt +++ b/ext/mysqli/tests/bug71863.phpt @@ -30,4 +30,4 @@ if (!mysqli_query($link, "DROP TABLE IF EXISTS test_bug_71863")) mysqli_close($link); ?> --EXPECTF-- -%AUnknown column 'owner_id' in 'where clause' +%AUnknown column 'owner_id' in '%r(WHERE|where clause)%r' diff --git a/ext/mysqli/tests/mysqli_stmt_datatype_change.phpt b/ext/mysqli/tests/mysqli_stmt_datatype_change.phpt index 3ab05504bbef4..ebf5073176317 100644 --- a/ext/mysqli/tests/mysqli_stmt_datatype_change.phpt +++ b/ext/mysqli/tests/mysqli_stmt_datatype_change.phpt @@ -65,7 +65,7 @@ if (!mysqli_query($link, "DROP TABLE IF EXISTS type_change")) mysqli_close($link); ?> ---EXPECT-- +--EXPECTF-- bool(true) bool(true) ---- Row 1 @@ -80,7 +80,7 @@ NULL ALTER bool(true) bool(false) -string(34) "Unknown column 'a' in 'field list'" +string(%d) "Unknown column 'a' in '%r(SELECT|field list)%r'" ---- Row 1 bool(false) int(2) From 50264b03a073e8ab3157cda288dd3b8322346381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Tue, 3 Dec 2024 00:46:28 +0100 Subject: [PATCH 71/76] Fix GH-16990 "dba_list() is now zero-indexed instead of using resource ids" closes GH-17005 --- NEWS | 4 ++++ ext/dba/dba.c | 6 ++--- ext/dba/tests/gh16990.phpt | 46 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 ext/dba/tests/gh16990.phpt diff --git a/NEWS b/NEWS index e572109d26714..bd6e28a0c7ada 100644 --- a/NEWS +++ b/NEWS @@ -20,6 +20,10 @@ PHP NEWS - Curl: . Fix various memory leaks in curl mime handling. (nielsdos) +- DBA: + . Fixed bug GH-16990 (dba_list() is now zero-indexed instead of using + resource ids) (kocsismate) + - DOM: . Fixed bug GH-16906 (Reloading document can cause UAF in iterator). (nielsdos) diff --git a/ext/dba/dba.c b/ext/dba/dba.c index f094fb1f612b1..e4986e1cd2321 100644 --- a/ext/dba/dba.c +++ b/ext/dba/dba.c @@ -1293,9 +1293,9 @@ PHP_FUNCTION(dba_list) zval *zv; ZEND_HASH_MAP_FOREACH_VAL(&DBA_G(connections), zv) { - dba_info *info = Z_DBA_INFO_P(zv); - if (info) { - add_next_index_str(return_value, zend_string_copy(info->path)); + dba_connection *connection = Z_DBA_CONNECTION_P(zv); + if (connection->info) { + add_index_str(return_value, connection->std.handle, zend_string_copy(connection->info->path)); } } ZEND_HASH_FOREACH_END(); } diff --git a/ext/dba/tests/gh16990.phpt b/ext/dba/tests/gh16990.phpt new file mode 100644 index 0000000000000..f3191904722ba --- /dev/null +++ b/ext/dba/tests/gh16990.phpt @@ -0,0 +1,46 @@ +--TEST-- +GH-16990 (dba_list() is now zero-indexed instead of using resource ids) +--EXTENSIONS-- +dba +--CONFLICTS-- +dba +--SKIPIF-- + +--FILE-- + +--CLEAN-- + +--EXPECTF-- +array(2) { + [2]=> + string(%d) "%s%etest1.dbm" + [4]=> + string(%d) "%s%etest2.dbm" +} From f04a9f466f69737a7c548fb2a553e5ff5c96915e Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Tue, 3 Dec 2024 13:01:31 +0300 Subject: [PATCH 72/76] Update IR IR commit: 1a41bddcf0a41b9a3866d00b57591b3684c88443 --- ext/opcache/jit/ir/ir.c | 6 ++++++ ext/opcache/jit/ir/ir.h | 3 +++ ext/opcache/jit/ir/ir_private.h | 7 ------- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/ext/opcache/jit/ir/ir.c b/ext/opcache/jit/ir/ir.c index b21f8ce7d9519..c45b1efc21eeb 100644 --- a/ext/opcache/jit/ir/ir.c +++ b/ext/opcache/jit/ir/ir.c @@ -1139,6 +1139,12 @@ ir_ref ir_bind(ir_ctx *ctx, ir_ref var, ir_ref def) return def; } +ir_ref ir_binding_find(const ir_ctx *ctx, ir_ref ref) +{ + ir_ref var = ir_hashtab_find(ctx->binding, ref); + return (var != (ir_ref)IR_INVALID_VAL) ? var : 0; +} + /* Batch construction of def->use edges */ #if 0 void ir_build_def_use_lists(ir_ctx *ctx) diff --git a/ext/opcache/jit/ir/ir.h b/ext/opcache/jit/ir/ir.h index 86e6dd51a9865..c4f0926e08589 100644 --- a/ext/opcache/jit/ir/ir.h +++ b/ext/opcache/jit/ir/ir.h @@ -743,7 +743,10 @@ ir_ref ir_fold3(ir_ctx *ctx, uint32_t opt, ir_ref op1, ir_ref op2, ir_ref op3); ir_ref ir_param(ir_ctx *ctx, ir_type type, ir_ref region, const char *name, int pos); ir_ref ir_var(ir_ctx *ctx, ir_type type, ir_ref region, const char *name); + +/* IR Binding */ ir_ref ir_bind(ir_ctx *ctx, ir_ref var, ir_ref def); +ir_ref ir_binding_find(const ir_ctx *ctx, ir_ref ref); /* Def -> Use lists */ void ir_build_def_use_lists(ir_ctx *ctx); diff --git a/ext/opcache/jit/ir/ir_private.h b/ext/opcache/jit/ir/ir_private.h index f88a1574a286e..fe4a79426862f 100644 --- a/ext/opcache/jit/ir/ir_private.h +++ b/ext/opcache/jit/ir/ir_private.h @@ -1013,13 +1013,6 @@ IR_ALWAYS_INLINE uint32_t ir_insn_len(const ir_insn *insn) #define IR_RESERVED_FLAG_1 (1U<<31) -/*** IR Binding ***/ -IR_ALWAYS_INLINE ir_ref ir_binding_find(const ir_ctx *ctx, ir_ref ref) -{ - ir_ref var = ir_hashtab_find(ctx->binding, ref); - return (var != (ir_ref)IR_INVALID_VAL) ? var : 0; -} - /*** IR Use Lists ***/ struct _ir_use_list { ir_ref refs; /* index in ir_ctx->use_edges[] array */ From 7fbeee0c2f8548a2532b701257b29175840596ce Mon Sep 17 00:00:00 2001 From: Sergey Panteleev Date: Tue, 3 Dec 2024 16:48:02 +0300 Subject: [PATCH 73/76] PHP-8.2 is now for PHP 8.2.28-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 61c53db1378b5..f7565624ad7b0 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.2.27 +?? ??? ????, PHP 8.2.28 + + +19 Dec 2024, PHP 8.2.27 - Calendar: . Fixed jdtogregorian overflow. (David Carlier) diff --git a/Zend/zend.h b/Zend/zend.h index d99a625b84186..7deb59f432199 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.2.27-dev" +#define ZEND_VERSION "4.2.28-dev" #define ZEND_ENGINE_3 diff --git a/configure.ac b/configure.ac index 9329358ba498d..4d63418347ef3 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.27-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.2.28-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 932a4a103e74a..d035df8c52919 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 27 +#define PHP_RELEASE_VERSION 28 #define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.2.27-dev" -#define PHP_VERSION_ID 80227 +#define PHP_VERSION "8.2.28-dev" +#define PHP_VERSION_ID 80228 From d3bf67d44102869f340a7be0e12f4f09de0edbcf Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Mon, 16 Dec 2024 13:33:23 -0400 Subject: [PATCH 74/76] [ci skip] Port NEWS entries from 8.3.15 into 8.4.2 There was some confusion with the release branching, so these entries didn't get included. All these changes should be in 8.4.2. --- NEWS | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index bd6e28a0c7ada..84f967b9fa8a9 100644 --- a/NEWS +++ b/NEWS @@ -6,18 +6,30 @@ PHP NEWS . Fixed bug GH-16978 (Avoid unnecessary padding with leading zeros) (Saki Takamachi) +- Calendar: + . Fixed jdtogregorian overflow. (David Carlier) + . Fixed cal_to_jd julian_days argument overflow. (David Carlier) + - COM: . Fixed bug GH-16991 (Getting typeinfo of non DISPATCH variant segfaults). (cmb) - Core: + . Fail early in *nix configuration build script. (hakre) . Fixed bug GH-16344 (setRawValueWithoutLazyInitialization() and skipLazyInitialization() may change initialized proxy). (Arnaud) + . Fixed bug GH-16727 (Opcache bad signal 139 crash in ZTS bookworm + (frankenphp)). (nielsdos) + . Fixed bug GH-16799 (Assertion failure at Zend/zend_vm_execute.h:7469). + (nielsdos) + . Fixed bug GH-16630 (UAF in lexer with encoding translation and heredocs). + (nielsdos) . Fix is_zend_ptr() huge block comparison. (nielsdos) . Fixed potential OOB read in zend_dirname() on Windows. (cmb) . Fixed bug GH-15964 (printf() can strip sign of -INF). (divinity76, cmb) - Curl: + . Fixed bug GH-16802 (open_basedir bypass using curl extension). (nielsdos) . Fix various memory leaks in curl mime handling. (nielsdos) - DBA: @@ -25,16 +37,25 @@ PHP NEWS resource ids) (kocsismate) - DOM: + . Fixed bug GH-16777 (Calling the constructor again on a DOM object after it + is in a document causes UAF). (nielsdos) . Fixed bug GH-16906 (Reloading document can cause UAF in iterator). (nielsdos) - FPM: + . Fixed GH-16432 (PHP-FPM 8.2 SIGSEGV in fpm_get_status). (Jakub Zelenka) . Fixed bug GH-16932 (wrong FPM status output). (Jakub Zelenka, James Lucas) +- GD: + . Fixed GH-16776 (imagecreatefromstring overflow). (David Carlier) + - GMP: . Fixed bug GH-16890 (array_sum() with GMP can loose precision (LLP64)). (cmb) +- Hash: + . Fixed GH-16711: Segfault in mhash(). (Girgias) + - Opcache: . Fixed bug GH-16851 (JIT_G(enabled) not set correctly on other threads). (dktapps) @@ -42,13 +63,32 @@ PHP NEWS . Fixed bug GH-16879 (JIT dead code skipping does not update call_level). (nielsdos) -- SAPI: - . Fixed bug GH-16998 (UBSAN warning in rfc1867). (nielsdos) +- OpenSSL: + . Prevent unexpected array entry conversion when reading key. (nielsdos) + . Fix various memory leaks related to openssl exports. (nielsdos) + . Fix memory leak in php_openssl_pkey_from_zval(). (nielsdos) + +- PDO: + . Fixed memory leak of `setFetchMode()`. (SakiTakamachi) + +- Phar: + . Fixed bug GH-16695 (phar:// tar parser and zero-length file header blocks). + (nielsdos, Hans Krentel) - PHPDBG: . Fixed bug GH-15208 (Segfault with breakpoint map and phpdbg_clear()). (nielsdos) +- SAPI: + . Fixed bug GH-16998 (UBSAN warning in rfc1867). (nielsdos) + +- SimpleXML: + . Fixed bug GH-16808 (Segmentation fault in RecursiveIteratorIterator + ->current() with a xml element input). (nielsdos) + +- SOAP: + . Fix make check being invoked in ext/soap. (Ma27) + - Standard: . Fixed bug GH-16905 (Internal iterator functions can't handle UNDEF properties). (nielsdos) From 5d2520648a5aa7798bd1d63cd2445c61210da543 Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Tue, 17 Dec 2024 11:29:42 -0400 Subject: [PATCH 75/76] Update versions for PHP 8.4.2 --- Zend/zend.h | 2 +- configure.ac | 2 +- main/php_version.h | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Zend/zend.h b/Zend/zend.h index 62c0137092e43..eac87afc99ee4 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.4.2-dev" +#define ZEND_VERSION "4.4.2" #define ZEND_ENGINE_3 diff --git a/configure.ac b/configure.ac index 142f8824ba78c..4a3f2a010e611 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.4.2-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.4.2],[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 5b63c499c4eee..b5568497a586b 100644 --- a/main/php_version.h +++ b/main/php_version.h @@ -3,6 +3,6 @@ #define PHP_MAJOR_VERSION 8 #define PHP_MINOR_VERSION 4 #define PHP_RELEASE_VERSION 2 -#define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.4.2-dev" +#define PHP_EXTRA_VERSION "" +#define PHP_VERSION "8.4.2" #define PHP_VERSION_ID 80402 From 9327bec388094ecf44f2bd6db9fe8e6518849b15 Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Tue, 17 Dec 2024 11:31:31 -0400 Subject: [PATCH 76/76] Update NEWS for PHP 8.4.2 --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 84f967b9fa8a9..88522ad7ad9a5 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,6 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.4.2 +19 Dec 2024, PHP 8.4.2 - BcMath: . Fixed bug GH-16978 (Avoid unnecessary padding with leading zeros)