mirror of
https://github.com/php/php-src.git
synced 2025-08-16 05:58:45 +02:00
Merge branch 'PHP-8.1'
* PHP-8.1: Fix bug GH-8058 - mysqlnd segfault when prepare fails
This commit is contained in:
commit
b582427ff5
3 changed files with 57 additions and 54 deletions
40
ext/mysqli/tests/gh8058.phpt
Normal file
40
ext/mysqli/tests/gh8058.phpt
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
--TEST--
|
||||||
|
GH-8058 (NULL pointer dereference in mysqlnd package (#81706))
|
||||||
|
--EXTENSIONS--
|
||||||
|
mysqli
|
||||||
|
--SKIPIF--
|
||||||
|
<?php
|
||||||
|
require_once 'skipifconnectfailure.inc';
|
||||||
|
?>
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
require_once "connect.inc";
|
||||||
|
|
||||||
|
mysqli_report(MYSQLI_REPORT_OFF);
|
||||||
|
$mysqli = new my_mysqli($host, $user, $passwd, $db, $port, $socket);
|
||||||
|
|
||||||
|
// There should be no segfault due to NULL deref
|
||||||
|
$stmt = $mysqli->prepare("select 1,2,3");
|
||||||
|
$stmt->bind_result($a,$a,$a);
|
||||||
|
$stmt->prepare("");
|
||||||
|
$stmt->prepare("select ".str_repeat("'A',", 0x1201)."1");
|
||||||
|
unset($stmt); // trigger dtor
|
||||||
|
|
||||||
|
// There should be no memory leak
|
||||||
|
$stmt = $mysqli->prepare("select 1,2,3");
|
||||||
|
$stmt->bind_result($a,$a,$a);
|
||||||
|
$stmt->prepare("");
|
||||||
|
$stmt->prepare("select 1");
|
||||||
|
unset($stmt); // trigger dtor
|
||||||
|
|
||||||
|
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
|
||||||
|
$stmt = $mysqli->prepare("select 1,2,3");
|
||||||
|
try {
|
||||||
|
// We expect an exception to be thrown
|
||||||
|
$stmt->prepare("");
|
||||||
|
} catch (mysqli_sql_exception $e) {
|
||||||
|
var_dump($e->getMessage());
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
string(15) "Query was empty"
|
|
@ -197,18 +197,13 @@ require_once('skipifconnectfailure.inc');
|
||||||
/* stmt_affected_rows is not really meant for SELECT! */
|
/* stmt_affected_rows is not really meant for SELECT! */
|
||||||
if (mysqli_stmt_prepare($stmt, 'SELECT unknown_column FROM this_table_does_not_exist') ||
|
if (mysqli_stmt_prepare($stmt, 'SELECT unknown_column FROM this_table_does_not_exist') ||
|
||||||
mysqli_stmt_execute($stmt))
|
mysqli_stmt_execute($stmt))
|
||||||
printf("[041] The invalid SELECT statement is issued on purpose\n");
|
printf("[041] Expecting SELECT statement to fail on purpose\n");
|
||||||
|
|
||||||
if (-1 !== ($tmp = mysqli_stmt_affected_rows($stmt)))
|
if (-1 !== ($tmp = mysqli_stmt_affected_rows($stmt)))
|
||||||
printf("[042] Expecting int/-1, got %s/%s\n", gettype($tmp), $tmp);
|
printf("[042] Expecting int/-1, got %s/%s\n", gettype($tmp), $tmp);
|
||||||
|
|
||||||
if ($IS_MYSQLND) {
|
if (true !== ($tmp = mysqli_stmt_store_result($stmt)))
|
||||||
if (false !== ($tmp = mysqli_stmt_store_result($stmt)))
|
printf("[043] Expecting boolean/true, got %s/%s\n", gettype($tmp), $tmp);
|
||||||
printf("[043] Expecting boolean/false, got %s\%s\n", gettype($tmp), $tmp);
|
|
||||||
} else {
|
|
||||||
if (true !== ($tmp = mysqli_stmt_store_result($stmt)))
|
|
||||||
printf("[043] Libmysql does not care if the previous statement was bogus, expecting boolean/true, got %s\%s\n", gettype($tmp), $tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (0 !== ($tmp = mysqli_stmt_num_rows($stmt)))
|
if (0 !== ($tmp = mysqli_stmt_num_rows($stmt)))
|
||||||
printf("[044] Expecting int/0, got %s/%s\n", gettype($tmp), $tmp);
|
printf("[044] Expecting int/0, got %s/%s\n", gettype($tmp), $tmp);
|
||||||
|
|
|
@ -369,12 +369,10 @@ mysqlnd_stmt_prepare_read_eof(MYSQLND_STMT * s)
|
||||||
|
|
||||||
/* {{{ mysqlnd_stmt::prepare */
|
/* {{{ mysqlnd_stmt::prepare */
|
||||||
static enum_func_status
|
static enum_func_status
|
||||||
MYSQLND_METHOD(mysqlnd_stmt, prepare)(MYSQLND_STMT * const s, const char * const query, const size_t query_len)
|
MYSQLND_METHOD(mysqlnd_stmt, prepare)(MYSQLND_STMT * s, const char * const query, const size_t query_len)
|
||||||
{
|
{
|
||||||
MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
|
MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
|
||||||
MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
|
MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
|
||||||
MYSQLND_STMT * s_to_prepare = s;
|
|
||||||
MYSQLND_STMT_DATA * stmt_to_prepare = stmt;
|
|
||||||
|
|
||||||
DBG_ENTER("mysqlnd_stmt::prepare");
|
DBG_ENTER("mysqlnd_stmt::prepare");
|
||||||
if (!stmt || !conn) {
|
if (!stmt || !conn) {
|
||||||
|
@ -390,25 +388,15 @@ MYSQLND_METHOD(mysqlnd_stmt, prepare)(MYSQLND_STMT * const s, const char * const
|
||||||
SET_EMPTY_ERROR(conn->error_info);
|
SET_EMPTY_ERROR(conn->error_info);
|
||||||
|
|
||||||
if (stmt->state > MYSQLND_STMT_INITTED) {
|
if (stmt->state > MYSQLND_STMT_INITTED) {
|
||||||
/* See if we have to clean the wire */
|
|
||||||
if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
|
|
||||||
/* Do implicit use_result and then flush the result */
|
|
||||||
stmt->default_rset_handler = s->m->use_result;
|
|
||||||
stmt->default_rset_handler(s);
|
|
||||||
}
|
|
||||||
/* No 'else' here please :) */
|
|
||||||
if (stmt->state > MYSQLND_STMT_WAITING_USE_OR_STORE && stmt->result) {
|
|
||||||
stmt->result->m.skip_result(stmt->result);
|
|
||||||
}
|
|
||||||
/*
|
/*
|
||||||
Create a new test statement, which we will prepare, but if anything
|
Create a new prepared statement and destroy the previous one.
|
||||||
fails, we will scrap it.
|
|
||||||
*/
|
*/
|
||||||
s_to_prepare = conn->m->stmt_init(conn);
|
s->m->dtor(s, TRUE);
|
||||||
if (!s_to_prepare) {
|
s = conn->m->stmt_init(conn);
|
||||||
|
if (!s) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
stmt_to_prepare = s_to_prepare->data;
|
stmt = s->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -422,13 +410,13 @@ MYSQLND_METHOD(mysqlnd_stmt, prepare)(MYSQLND_STMT * const s, const char * const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FAIL == mysqlnd_stmt_read_prepare_response(s_to_prepare)) {
|
if (FAIL == mysqlnd_stmt_read_prepare_response(s)) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stmt_to_prepare->param_count) {
|
if (stmt->param_count) {
|
||||||
if (FAIL == mysqlnd_stmt_skip_metadata(s_to_prepare) ||
|
if (FAIL == mysqlnd_stmt_skip_metadata(s) ||
|
||||||
FAIL == mysqlnd_stmt_prepare_read_eof(s_to_prepare))
|
FAIL == mysqlnd_stmt_prepare_read_eof(s))
|
||||||
{
|
{
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
@ -439,51 +427,31 @@ MYSQLND_METHOD(mysqlnd_stmt, prepare)(MYSQLND_STMT * const s, const char * const
|
||||||
Beware that SHOW statements bypass the PS framework and thus they send
|
Beware that SHOW statements bypass the PS framework and thus they send
|
||||||
no metadata at prepare.
|
no metadata at prepare.
|
||||||
*/
|
*/
|
||||||
if (stmt_to_prepare->field_count) {
|
if (stmt->field_count) {
|
||||||
MYSQLND_RES * result = conn->m->result_init(stmt_to_prepare->field_count);
|
MYSQLND_RES * result = conn->m->result_init(stmt->field_count);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
SET_OOM_ERROR(conn->error_info);
|
SET_OOM_ERROR(conn->error_info);
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
/* Allocate the result now as it is needed for the reading of metadata */
|
/* Allocate the result now as it is needed for the reading of metadata */
|
||||||
stmt_to_prepare->result = result;
|
stmt->result = result;
|
||||||
|
|
||||||
result->conn = conn->m->get_reference(conn);
|
result->conn = conn->m->get_reference(conn);
|
||||||
|
|
||||||
result->type = MYSQLND_RES_PS_BUF;
|
result->type = MYSQLND_RES_PS_BUF;
|
||||||
|
|
||||||
if (FAIL == result->m.read_result_metadata(result, conn) ||
|
if (FAIL == result->m.read_result_metadata(result, conn) ||
|
||||||
FAIL == mysqlnd_stmt_prepare_read_eof(s_to_prepare))
|
FAIL == mysqlnd_stmt_prepare_read_eof(s))
|
||||||
{
|
{
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stmt_to_prepare != stmt) {
|
|
||||||
/* swap */
|
|
||||||
size_t real_size = sizeof(MYSQLND_STMT) + mysqlnd_plugin_count() * sizeof(void *);
|
|
||||||
char * tmp_swap = mnd_emalloc(real_size);
|
|
||||||
memcpy(tmp_swap, s, real_size);
|
|
||||||
memcpy(s, s_to_prepare, real_size);
|
|
||||||
memcpy(s_to_prepare, tmp_swap, real_size);
|
|
||||||
mnd_efree(tmp_swap);
|
|
||||||
{
|
|
||||||
MYSQLND_STMT_DATA * tmp_swap_data = stmt_to_prepare;
|
|
||||||
stmt_to_prepare = stmt;
|
|
||||||
stmt = tmp_swap_data;
|
|
||||||
}
|
|
||||||
s_to_prepare->m->dtor(s_to_prepare, TRUE);
|
|
||||||
}
|
|
||||||
stmt->state = MYSQLND_STMT_PREPARED;
|
stmt->state = MYSQLND_STMT_PREPARED;
|
||||||
DBG_INF("PASS");
|
DBG_INF("PASS");
|
||||||
DBG_RETURN(PASS);
|
DBG_RETURN(PASS);
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
if (stmt_to_prepare != stmt && s_to_prepare) {
|
|
||||||
s_to_prepare->m->dtor(s_to_prepare, TRUE);
|
|
||||||
}
|
|
||||||
stmt->state = MYSQLND_STMT_INITTED;
|
|
||||||
|
|
||||||
DBG_INF("FAIL");
|
DBG_INF("FAIL");
|
||||||
DBG_RETURN(FAIL);
|
DBG_RETURN(FAIL);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue