Commit 4a4ac3bcb08 for php.net

commit 4a4ac3bcb0893d27aa20da321ef562ac5bbf1431
Author: Kamil Tekiela <tekiela246@gmail.com>
Date:   Mon Sep 21 17:20:55 2026 +0100

    Fix GH-22854 (Assertion failure at link_errno_read in ext/mysqli/mysqli_prop.c) (#20117)

diff --git a/NEWS b/NEWS
index 54b6a5d174e..639a784a5a9 100644
--- a/NEWS
+++ b/NEWS
@@ -38,6 +38,10 @@ PHP                                                                        NEWS
   . Fixed bug GH-23106 (mb_strpos() reads past the end of a haystack ending in
     a truncated UTF-8 sequence). (Lazizbek Ergashev)

+- MySQLi:
+  . Fix GH-22854: Fixed failed assertion when accessing mysqli property after
+    failed reconnection. (Kamil Tekiela)
+
 - Opcache:
   . Fixed OSS-Fuzz #546798343 (Heap-buffer-overflow in optimizer with
     FCCs and inlining). (ndossche)
diff --git a/ext/mysqli/mysqli_nonapi.c b/ext/mysqli/mysqli_nonapi.c
index 6f2aa50117a..bec7047571e 100644
--- a/ext/mysqli/mysqli_nonapi.c
+++ b/ext/mysqli/mysqli_nonapi.c
@@ -134,6 +134,8 @@ void mysqli_common_connect(INTERNAL_FUNCTION_PARAMETERS, bool is_real_connect, b
 	{
 		/* already connected, we should close the connection */
 		php_mysqli_close(mysql, MYSQLI_CLOSE_IMPLICIT, mysqli_resource->status);
+		/* the link is not connected anymore, even if the new connection fails */
+		mysqli_resource->status = MYSQLI_STATUS_INITIALIZED;
 	}

 	if (strlen(SAFE_STR(hostname)) > 2 && !strncasecmp(hostname, "p:", 2)) {
diff --git a/ext/mysqli/mysqli_prop.c b/ext/mysqli/mysqli_prop.c
index 6d86a6ad4ca..a6165ab668c 100644
--- a/ext/mysqli/mysqli_prop.c
+++ b/ext/mysqli/mysqli_prop.c
@@ -43,6 +43,12 @@ if (!obj->ptr || !(MY_MYSQL *)((MYSQLI_RESOURCE *)(obj->ptr))->ptr) { \
 } else { \
 	CHECK_STATUS(statusval, quiet);\
 	p = (MYSQL *)((MY_MYSQL *)((MYSQLI_RESOURCE *)(obj->ptr))->ptr)->mysql;\
+	if (!p) { \
+		if (!quiet) { \
+			zend_throw_error(NULL, "%s object is not fully initialized", ZSTR_VAL(obj->zo.ce->name)); \
+		} \
+		return FAILURE; \
+	} \
 }

 #define MYSQLI_GET_RESULT(statusval) \
diff --git a/ext/mysqli/tests/gh22854.phpt b/ext/mysqli/tests/gh22854.phpt
new file mode 100644
index 00000000000..25714a8a962
--- /dev/null
+++ b/ext/mysqli/tests/gh22854.phpt
@@ -0,0 +1,83 @@
+--TEST--
+GH-22854 (Assertion failure at link_errno_read in ext/mysqli/mysqli_prop.c)
+--EXTENSIONS--
+mysqli
+--SKIPIF--
+<?php
+require_once 'skipifconnectfailure.inc';
+?>
+--FILE--
+<?php
+include 'connect.inc';
+
+mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
+
+function read_properties(mysqli $mysqli, array $properties): void {
+    foreach ($properties as $property) {
+        try {
+            $mysqli->$property;
+            echo "$property: no error\n";
+        } catch (Error $e) {
+            echo "$property: " . $e->getMessage() . "\n";
+        }
+    }
+
+    // var_dump() reads every property in quiet mode
+    ob_start();
+    var_dump($mysqli);
+    ob_end_clean();
+    echo "var_dump() done\n";
+}
+
+$links = mysqli_get_links_stats()['total'];
+
+echo "connect()\n";
+$mysqli = new mysqli();
+$mysqli->connect($host, $user, $passwd, $db, $port, $socket);
+echo 'Success... ' . $mysqli->host_info . "\n";
+try {
+    $mysqli->connect($host, $user, $passwd, $db.'wrong', $port, $socket);
+} catch (mysqli_sql_exception $e) {
+    echo "Error: " . $e->getMessage() . "\n";
+}
+read_properties($mysqli, ['host_info', 'affected_rows', 'error_list', 'errno', 'error']);
+unset($mysqli);
+
+echo "\nreal_connect()\n";
+$mysqli = new mysqli();
+$mysqli->connect($host, $user, $passwd, $db, $port, $socket);
+echo 'Success... ' . $mysqli->host_info . "\n";
+try {
+    $mysqli->real_connect($host, $user, $passwd, $db.'wrong', $port, $socket);
+} catch (mysqli_sql_exception $e) {
+    echo "Error: " . $e->getMessage() . "\n";
+}
+read_properties($mysqli, ['host_info', 'server_info', 'affected_rows', 'error_list']);
+unset($mysqli);
+
+// A failed reconnect must leave the link count balanced (it used to be decremented twice)
+var_dump(mysqli_get_links_stats()['total'] === $links);
+
+print "done!";
+?>
+--EXPECTF--
+connect()
+Success... %s via %s
+Error: Unknown database '%s'
+host_info: Property access is not allowed yet
+affected_rows: Property access is not allowed yet
+error_list: Property access is not allowed yet
+errno: mysqli object is not fully initialized
+error: mysqli object is not fully initialized
+var_dump() done
+
+real_connect()
+Success... %s via %s
+Error: Unknown database '%s'
+host_info: Property access is not allowed yet
+server_info: Property access is not allowed yet
+affected_rows: Property access is not allowed yet
+error_list: Property access is not allowed yet
+var_dump() done
+bool(true)
+done!