From 7542f8d8f81054afc085ceb4ff747eb962b86b07 Mon Sep 17 00:00:00 2001 From: Yurun Date: Wed, 28 Jun 2023 16:29:31 +0800 Subject: [PATCH 1/6] Fix MySQL Statement has a empty query result when the response field has changed, also Segmentation fault --- ext/mysqli/tests/gh11550.phpt | 74 +++++++++++++++++++++++++++ ext/mysqlnd/mysqlnd_result.c | 4 ++ ext/pdo_mysql/tests/gh11550.phpt | 85 ++++++++++++++++++++++++++++++++ 3 files changed, 163 insertions(+) create mode 100644 ext/mysqli/tests/gh11550.phpt create mode 100644 ext/pdo_mysql/tests/gh11550.phpt diff --git a/ext/mysqli/tests/gh11550.phpt b/ext/mysqli/tests/gh11550.phpt new file mode 100644 index 0000000000000..9a6066964fc18 --- /dev/null +++ b/ext/mysqli/tests/gh11550.phpt @@ -0,0 +1,74 @@ +--TEST-- +Bug GH-11550 (MySQL Statement has a empty query result when the response field has changed, also Segmentation fault) +--EXTENSIONS-- +mysqli +--SKIPIF-- + +--FILE-- +query(<<<'SQL' +DROP TABLE IF EXISTS `test` +SQL); +$link->query(<<<'SQL' +CREATE TABLE `test` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `name`(`name`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic; +SQL); +$link->query(<<<'SQL' +INSERT INTO `test` (`name`) VALUES ('test1'); +SQL); + +$link2 = new \mysqli($host, $user, $passwd, $db, $port, $socket); +$stmt = $link2->prepare('select * from test'); +var_dump('mysqli-1:', $stmt->execute(), $stmt->get_result()->fetch_all()); + +$link->query(<<<'SQL' +ALTER TABLE `test` +ADD COLUMN `a` varchar(255) NOT NULL DEFAULT ''; +SQL); + +var_dump('mysqli-2:', $stmt->execute(), $stmt->get_result()->fetch_all()); +echo 'Done'; +?> +--CLEAN-- +query('DROP TABLE IF EXISTS test_11550'); +?> +--EXPECT-- +string(9) "mysqli-1:" +bool(true) +array(1) { + [0]=> + array(2) { + [0]=> + int(1) + [1]=> + string(5) "test1" + } +} +string(9) "mysqli-2:" +bool(true) +array(1) { + [0]=> + array(3) { + [0]=> + int(1) + [1]=> + string(5) "test1" + [2]=> + string(0) "" + } +} +Done diff --git a/ext/mysqlnd/mysqlnd_result.c b/ext/mysqlnd/mysqlnd_result.c index f2b98a2bc2354..d2df678c815bb 100644 --- a/ext/mysqlnd/mysqlnd_result.c +++ b/ext/mysqlnd/mysqlnd_result.c @@ -286,6 +286,10 @@ mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn, MYSQLND_STMT * s) COM_STMT_EXECUTE (even if it is not necessary), so either this or previous branch always works. */ + if (rset_header.field_count != stmt->result->field_count) { + stmt->result->m.free_result(stmt->result, TRUE); + stmt->result = conn->m->result_init(rset_header.field_count); + } } result = stmt->result; } diff --git a/ext/pdo_mysql/tests/gh11550.phpt b/ext/pdo_mysql/tests/gh11550.phpt new file mode 100644 index 0000000000000..7402f9018d897 --- /dev/null +++ b/ext/pdo_mysql/tests/gh11550.phpt @@ -0,0 +1,85 @@ +--TEST-- +Bug GH-11550 (MySQL Statement has a empty query result when the response field has changed, also Segmentation fault) +--EXTENSIONS-- +pdo +pdo_mysql +--SKIPIF-- + +--FILE-- +exec(<<<'SQL' +DROP TABLE IF EXISTS `test` +SQL); +$pdo->exec(<<<'SQL' +CREATE TABLE `test` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `name`(`name`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic; +SQL); +$pdo->exec(<<<'SQL' +INSERT INTO `test` (`name`) VALUES ('test1'); +SQL); + +$pdo2 = MySQLPDOTest::factory(); +$stmt = $pdo2->prepare('select * from test'); +var_dump('PDO-1:', $stmt->execute(), $stmt->fetchAll()); + +$stmt->closeCursor(); // Optional. Segmentation fault (core dumped) + +$pdo->exec(<<<'SQL' +ALTER TABLE `test` +ADD COLUMN `a` varchar(255) NOT NULL DEFAULT ''; +SQL); + +var_dump('PDO-2:', $stmt->execute(), $stmt->fetchAll()); +echo 'Done'; +?> +--CLEAN-- +query('DROP TABLE IF EXISTS test_11550'); +?> +--EXPECT-- +string(6) "PDO-1:" +bool(true) +array(1) { + [0]=> + array(4) { + ["id"]=> + int(1) + [0]=> + int(1) + ["name"]=> + string(5) "test1" + [1]=> + string(5) "test1" + } +} +string(6) "PDO-2:" +bool(true) +array(1) { + [0]=> + array(6) { + ["id"]=> + int(1) + [0]=> + int(1) + ["name"]=> + string(5) "test1" + [1]=> + string(5) "test1" + ["a"]=> + string(0) "" + [2]=> + string(0) "" + } +} +Done From d5d0b418b41365eda9531e8363d6159ef56382e2 Mon Sep 17 00:00:00 2001 From: Yurun Date: Wed, 28 Jun 2023 16:30:50 +0800 Subject: [PATCH 2/6] Fix DBG_ENTER name typo --- ext/mysqlnd/mysqlnd_ps.c | 4 ++-- ext/mysqlnd/mysqlnd_result_meta.c | 2 +- ext/pdo_mysql/mysql_driver.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/mysqlnd/mysqlnd_ps.c b/ext/mysqlnd/mysqlnd_ps.c index 15ccec4522beb..54aed5185e1f6 100644 --- a/ext/mysqlnd/mysqlnd_ps.c +++ b/ext/mysqlnd/mysqlnd_ps.c @@ -1315,7 +1315,7 @@ MYSQLND_METHOD(mysqlnd_stmt, bind_one_result)(MYSQLND_STMT * const s, unsigned i MYSQLND_STMT_DATA * stmt = s? s->data : NULL; MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL; - DBG_ENTER("mysqlnd_stmt::bind_result"); + DBG_ENTER("mysqlnd_stmt::bind_one_result"); if (!stmt || !conn) { DBG_RETURN(FAIL); } @@ -1578,7 +1578,7 @@ MYSQLND_METHOD(mysqlnd_stmt, attr_get)(const MYSQLND_STMT * const s, void * const value) { MYSQLND_STMT_DATA * stmt = s? s->data : NULL; - DBG_ENTER("mysqlnd_stmt::attr_set"); + DBG_ENTER("mysqlnd_stmt::attr_get"); if (!stmt) { DBG_RETURN(FAIL); } diff --git a/ext/mysqlnd/mysqlnd_result_meta.c b/ext/mysqlnd/mysqlnd_result_meta.c index 35a84907f5f79..92896ccb408ef 100644 --- a/ext/mysqlnd/mysqlnd_result_meta.c +++ b/ext/mysqlnd/mysqlnd_result_meta.c @@ -259,7 +259,7 @@ static MYSQLND_FIELD_OFFSET MYSQLND_METHOD(mysqlnd_res_meta, field_seek)(MYSQLND_RES_METADATA * const meta, const MYSQLND_FIELD_OFFSET field_offset) { MYSQLND_FIELD_OFFSET return_value = 0; - DBG_ENTER("mysqlnd_res_meta::fetch_fields"); + DBG_ENTER("mysqlnd_res_meta::field_seek"); return_value = meta->current_field; meta->current_field = field_offset; DBG_RETURN(return_value); diff --git a/ext/pdo_mysql/mysql_driver.c b/ext/pdo_mysql/mysql_driver.c index adccb5e3d0f00..7be51708991d2 100644 --- a/ext/pdo_mysql/mysql_driver.c +++ b/ext/pdo_mysql/mysql_driver.c @@ -355,7 +355,7 @@ static bool mysql_handle_begin(pdo_dbh_t *dbh) zend_long return_value; zend_string *command; - PDO_DBG_ENTER("mysql_handle_quoter"); + PDO_DBG_ENTER("mysql_handle_begin"); PDO_DBG_INF_FMT("dbh=%p", dbh); command = zend_string_init("START TRANSACTION", strlen("START TRANSACTION"), 0); From cb227675608da8b40e488938cbeb7d227b1207d3 Mon Sep 17 00:00:00 2001 From: Yurun Date: Wed, 28 Jun 2023 18:51:17 +0800 Subject: [PATCH 3/6] Update test --- ext/pdo_mysql/tests/gh11550.phpt | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/pdo_mysql/tests/gh11550.phpt b/ext/pdo_mysql/tests/gh11550.phpt index 7402f9018d897..3241e95ada1a2 100644 --- a/ext/pdo_mysql/tests/gh11550.phpt +++ b/ext/pdo_mysql/tests/gh11550.phpt @@ -28,6 +28,7 @@ INSERT INTO `test` (`name`) VALUES ('test1'); SQL); $pdo2 = MySQLPDOTest::factory(); +$pdo2->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $stmt = $pdo2->prepare('select * from test'); var_dump('PDO-1:', $stmt->execute(), $stmt->fetchAll()); From ccac0cb499058b287d26a0b957bba785b2b9fac6 Mon Sep 17 00:00:00 2001 From: Yurun Date: Thu, 29 Jun 2023 08:34:13 +0800 Subject: [PATCH 4/6] Revert "Fix DBG_ENTER name typo" This reverts commit d5d0b418b41365eda9531e8363d6159ef56382e2. --- ext/mysqlnd/mysqlnd_ps.c | 4 ++-- ext/mysqlnd/mysqlnd_result_meta.c | 2 +- ext/pdo_mysql/mysql_driver.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/mysqlnd/mysqlnd_ps.c b/ext/mysqlnd/mysqlnd_ps.c index 54aed5185e1f6..15ccec4522beb 100644 --- a/ext/mysqlnd/mysqlnd_ps.c +++ b/ext/mysqlnd/mysqlnd_ps.c @@ -1315,7 +1315,7 @@ MYSQLND_METHOD(mysqlnd_stmt, bind_one_result)(MYSQLND_STMT * const s, unsigned i MYSQLND_STMT_DATA * stmt = s? s->data : NULL; MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL; - DBG_ENTER("mysqlnd_stmt::bind_one_result"); + DBG_ENTER("mysqlnd_stmt::bind_result"); if (!stmt || !conn) { DBG_RETURN(FAIL); } @@ -1578,7 +1578,7 @@ MYSQLND_METHOD(mysqlnd_stmt, attr_get)(const MYSQLND_STMT * const s, void * const value) { MYSQLND_STMT_DATA * stmt = s? s->data : NULL; - DBG_ENTER("mysqlnd_stmt::attr_get"); + DBG_ENTER("mysqlnd_stmt::attr_set"); if (!stmt) { DBG_RETURN(FAIL); } diff --git a/ext/mysqlnd/mysqlnd_result_meta.c b/ext/mysqlnd/mysqlnd_result_meta.c index 92896ccb408ef..35a84907f5f79 100644 --- a/ext/mysqlnd/mysqlnd_result_meta.c +++ b/ext/mysqlnd/mysqlnd_result_meta.c @@ -259,7 +259,7 @@ static MYSQLND_FIELD_OFFSET MYSQLND_METHOD(mysqlnd_res_meta, field_seek)(MYSQLND_RES_METADATA * const meta, const MYSQLND_FIELD_OFFSET field_offset) { MYSQLND_FIELD_OFFSET return_value = 0; - DBG_ENTER("mysqlnd_res_meta::field_seek"); + DBG_ENTER("mysqlnd_res_meta::fetch_fields"); return_value = meta->current_field; meta->current_field = field_offset; DBG_RETURN(return_value); diff --git a/ext/pdo_mysql/mysql_driver.c b/ext/pdo_mysql/mysql_driver.c index 7be51708991d2..adccb5e3d0f00 100644 --- a/ext/pdo_mysql/mysql_driver.c +++ b/ext/pdo_mysql/mysql_driver.c @@ -355,7 +355,7 @@ static bool mysql_handle_begin(pdo_dbh_t *dbh) zend_long return_value; zend_string *command; - PDO_DBG_ENTER("mysql_handle_begin"); + PDO_DBG_ENTER("mysql_handle_quoter"); PDO_DBG_INF_FMT("dbh=%p", dbh); command = zend_string_init("START TRANSACTION", strlen("START TRANSACTION"), 0); From 63ac2a8bda2ba5b389f278b2376d19c19b370fa1 Mon Sep 17 00:00:00 2001 From: Yurun Date: Thu, 29 Jun 2023 08:40:11 +0800 Subject: [PATCH 5/6] Update test --- ext/mysqli/tests/gh11550.phpt | 7 +++---- ext/pdo_mysql/tests/gh11550.phpt | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/ext/mysqli/tests/gh11550.phpt b/ext/mysqli/tests/gh11550.phpt index 9a6066964fc18..bddf22317e409 100644 --- a/ext/mysqli/tests/gh11550.phpt +++ b/ext/mysqli/tests/gh11550.phpt @@ -4,13 +4,12 @@ Bug GH-11550 (MySQL Statement has a empty query result when the response field h mysqli --SKIPIF-- --FILE-- query(<<<'SQL' DROP TABLE IF EXISTS `test` @@ -41,7 +40,7 @@ echo 'Done'; ?> --CLEAN-- query('DROP TABLE IF EXISTS test_11550'); diff --git a/ext/pdo_mysql/tests/gh11550.phpt b/ext/pdo_mysql/tests/gh11550.phpt index 3241e95ada1a2..ded8e51f5a1d9 100644 --- a/ext/pdo_mysql/tests/gh11550.phpt +++ b/ext/pdo_mysql/tests/gh11550.phpt @@ -5,12 +5,12 @@ pdo pdo_mysql --SKIPIF-- --FILE-- exec(<<<'SQL' DROP TABLE IF EXISTS `test` @@ -44,7 +44,7 @@ echo 'Done'; ?> --CLEAN-- query('DROP TABLE IF EXISTS test_11550'); ?> From c3d3b0e6a00d8ad004af98a6e5982f42a0ead423 Mon Sep 17 00:00:00 2001 From: Kamil Tekiela Date: Fri, 4 Aug 2023 20:38:09 +0100 Subject: [PATCH 6/6] Review fixes --- ext/mysqli/tests/gh11550.phpt | 15 ++++++--------- ext/mysqlnd/mysqlnd_result.c | 2 +- ext/pdo_mysql/tests/gh11550.phpt | 16 ++++++---------- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/ext/mysqli/tests/gh11550.phpt b/ext/mysqli/tests/gh11550.phpt index bddf22317e409..6ed02ec91d51f 100644 --- a/ext/mysqli/tests/gh11550.phpt +++ b/ext/mysqli/tests/gh11550.phpt @@ -11,11 +11,9 @@ require_once 'skipifconnectfailure.inc'; require_once 'connect.inc'; $link = new \mysqli($host, $user, $passwd, $db, $port, $socket); + $link->query(<<<'SQL' -DROP TABLE IF EXISTS `test` -SQL); -$link->query(<<<'SQL' -CREATE TABLE `test` ( +CREATE TABLE `test_gh11550` ( `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, PRIMARY KEY (`id`) USING BTREE, @@ -23,15 +21,14 @@ CREATE TABLE `test` ( ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic; SQL); $link->query(<<<'SQL' -INSERT INTO `test` (`name`) VALUES ('test1'); +INSERT INTO `test_gh11550` (`name`) VALUES ('test1'); SQL); -$link2 = new \mysqli($host, $user, $passwd, $db, $port, $socket); -$stmt = $link2->prepare('select * from test'); +$stmt = $link->prepare('select * from test_gh11550'); var_dump('mysqli-1:', $stmt->execute(), $stmt->get_result()->fetch_all()); $link->query(<<<'SQL' -ALTER TABLE `test` +ALTER TABLE `test_gh11550` ADD COLUMN `a` varchar(255) NOT NULL DEFAULT ''; SQL); @@ -43,7 +40,7 @@ echo 'Done'; require_once 'connect.inc'; $link = new \mysqli($host, $user, $passwd, $db, $port, $socket); -$link->query('DROP TABLE IF EXISTS test_11550'); +$link->query('DROP TABLE IF EXISTS test_gh11550'); ?> --EXPECT-- string(9) "mysqli-1:" diff --git a/ext/mysqlnd/mysqlnd_result.c b/ext/mysqlnd/mysqlnd_result.c index d2df678c815bb..b1a400499781a 100644 --- a/ext/mysqlnd/mysqlnd_result.c +++ b/ext/mysqlnd/mysqlnd_result.c @@ -290,8 +290,8 @@ mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn, MYSQLND_STMT * s) stmt->result->m.free_result(stmt->result, TRUE); stmt->result = conn->m->result_init(rset_header.field_count); } + result = stmt->result; } - result = stmt->result; } if (!result) { SET_OOM_ERROR(conn->error_info); diff --git a/ext/pdo_mysql/tests/gh11550.phpt b/ext/pdo_mysql/tests/gh11550.phpt index ded8e51f5a1d9..5bf0b26497ca0 100644 --- a/ext/pdo_mysql/tests/gh11550.phpt +++ b/ext/pdo_mysql/tests/gh11550.phpt @@ -12,11 +12,9 @@ MySQLPDOTest::skip(); exec(<<<'SQL' -DROP TABLE IF EXISTS `test` -SQL); -$pdo->exec(<<<'SQL' -CREATE TABLE `test` ( +CREATE TABLE `test_gh11550` ( `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, PRIMARY KEY (`id`) USING BTREE, @@ -24,18 +22,16 @@ CREATE TABLE `test` ( ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic; SQL); $pdo->exec(<<<'SQL' -INSERT INTO `test` (`name`) VALUES ('test1'); +INSERT INTO `test_gh11550` (`name`) VALUES ('test1'); SQL); -$pdo2 = MySQLPDOTest::factory(); -$pdo2->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); -$stmt = $pdo2->prepare('select * from test'); +$stmt = $pdo->prepare('select * from test_gh11550'); var_dump('PDO-1:', $stmt->execute(), $stmt->fetchAll()); $stmt->closeCursor(); // Optional. Segmentation fault (core dumped) $pdo->exec(<<<'SQL' -ALTER TABLE `test` +ALTER TABLE `test_gh11550` ADD COLUMN `a` varchar(255) NOT NULL DEFAULT ''; SQL); @@ -46,7 +42,7 @@ echo 'Done'; query('DROP TABLE IF EXISTS test_11550'); +$pdo->query('DROP TABLE IF EXISTS test_gh11550'); ?> --EXPECT-- string(6) "PDO-1:"