From 6191e3ab22fa165f1fbee427d46cac9dec4fcccf Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 2 May 2026 05:35:29 +0100 Subject: [PATCH 1/3] ext/mysqlnd: Fix persistent free of non-persistent connect_attr key. set_client_option_2d() built the temporary key string with the connection's persistent flag but always released it with persistent=1. On a duplicate-key update of the connect_attr hash, zend_hash_update() does not retain the passed key, so the caller-owned non-persistent string was freed via free() instead of efree(), tripping the IS_STR_PERSISTENT assertion in debug builds and mismatching allocators in release. Reachable by retrying mysqli_real_connect() on a handle whose first connect failed, since mysqlnd re-adds _client_name and _server_host on every connect attempt. --- ext/mysqlnd/mysqlnd_connection.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ext/mysqlnd/mysqlnd_connection.c b/ext/mysqlnd/mysqlnd_connection.c index 2f09ac5ee837..9e15b59c11fa 100644 --- a/ext/mysqlnd/mysqlnd_connection.c +++ b/ext/mysqlnd/mysqlnd_connection.c @@ -1572,17 +1572,16 @@ MYSQLND_METHOD(mysqlnd_conn_data, set_client_option_2d)(MYSQLND_CONN_DATA * cons zval attrz; zend_string *str; + str = zend_string_init(key, strlen(key), conn->persistent); if (conn->persistent) { - str = zend_string_init(key, strlen(key), 1); GC_MAKE_PERSISTENT_LOCAL(str); ZVAL_NEW_STR(&attrz, zend_string_init(value, strlen(value), 1)); GC_MAKE_PERSISTENT_LOCAL(Z_COUNTED(attrz)); } else { - str = zend_string_init(key, strlen(key), 0); ZVAL_NEW_STR(&attrz, zend_string_init(value, strlen(value), 0)); } zend_hash_update(conn->options->connect_attr, str, &attrz); - zend_string_release_ex(str, 1); + zend_string_release_ex(str, conn->persistent); } break; default: From 653bc645ba14c4381f3241eea8206f093f6ade1d Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 2 May 2026 05:35:30 +0100 Subject: [PATCH 2/3] add test --- .../tests/mysqli_real_connect_retry_attr.phpt | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 ext/mysqli/tests/mysqli_real_connect_retry_attr.phpt diff --git a/ext/mysqli/tests/mysqli_real_connect_retry_attr.phpt b/ext/mysqli/tests/mysqli_real_connect_retry_attr.phpt new file mode 100644 index 000000000000..7f7731807428 --- /dev/null +++ b/ext/mysqli/tests/mysqli_real_connect_retry_attr.phpt @@ -0,0 +1,26 @@ +--TEST-- +mysqli_real_connect() retry on same handle does not corrupt mysqlnd connect_attr +--EXTENSIONS-- +mysqli +--SKIPIF-- + +--FILE-- + +--EXPECT-- +done! From a573791079f7c19709eaae55d39aa9d0eec87d33 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 2 May 2026 06:32:27 +0100 Subject: [PATCH 3/3] apply @alecpl suggestion --- ext/mysqlnd/mysqlnd_connection.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ext/mysqlnd/mysqlnd_connection.c b/ext/mysqlnd/mysqlnd_connection.c index 9e15b59c11fa..2402ad6fc8f4 100644 --- a/ext/mysqlnd/mysqlnd_connection.c +++ b/ext/mysqlnd/mysqlnd_connection.c @@ -1573,12 +1573,10 @@ MYSQLND_METHOD(mysqlnd_conn_data, set_client_option_2d)(MYSQLND_CONN_DATA * cons zend_string *str; str = zend_string_init(key, strlen(key), conn->persistent); + ZVAL_NEW_STR(&attrz, zend_string_init(value, strlen(value), conn->persistent)); if (conn->persistent) { GC_MAKE_PERSISTENT_LOCAL(str); - ZVAL_NEW_STR(&attrz, zend_string_init(value, strlen(value), 1)); GC_MAKE_PERSISTENT_LOCAL(Z_COUNTED(attrz)); - } else { - ZVAL_NEW_STR(&attrz, zend_string_init(value, strlen(value), 0)); } zend_hash_update(conn->options->connect_attr, str, &attrz); zend_string_release_ex(str, conn->persistent);