Skip to content

Commit

Permalink
PDO MySQL: Fix leak with libmysqlclient and multiple rowsets
Browse files Browse the repository at this point in the history
stmt->column_count gets reset before the next_rowset handler is
invoked, so we need to fetch the value from the result set instead.

Arguably PDO should be separating the destruction of the previous
result set and the switch to the next result set more cleanly...
  • Loading branch information
nikic committed Dec 11, 2020
1 parent 54a63d9 commit c927c83
Showing 1 changed file with 29 additions and 46 deletions.
75 changes: 29 additions & 46 deletions ext/pdo_mysql/mysql_statement.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,43 @@

#ifdef PDO_USE_MYSQLND
# define pdo_mysql_stmt_execute_prepared(stmt) pdo_mysql_stmt_execute_prepared_mysqlnd(stmt)
# define pdo_free_bound_result(res) zval_ptr_dtor(res.zv)
#else
# define pdo_mysql_stmt_execute_prepared(stmt) pdo_mysql_stmt_execute_prepared_libmysql(stmt)
# define pdo_free_bound_result(res) efree(res.buffer)
#endif


static void pdo_mysql_free_result(pdo_mysql_stmt *S)
{
if (S->result) {
#ifndef PDO_USE_MYSQLND
if (S->bound_result) {
/* We can't use stmt->column_count here, because it gets reset before the
* next_rowset handler is called. */
unsigned column_count = mysql_num_fields(S->result);
for (unsigned i = 0; i < column_count; i++) {
efree(S->bound_result[i].buffer);
}

efree(S->bound_result);
efree(S->out_null);
efree(S->out_length);
S->bound_result = NULL;
}
#endif

mysql_free_result(S->result);
S->result = NULL;
}
}

static int pdo_mysql_stmt_dtor(pdo_stmt_t *stmt) /* {{{ */
{
pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;

PDO_DBG_ENTER("pdo_mysql_stmt_dtor");
PDO_DBG_INF_FMT("stmt=%p", S->stmt);
if (S->result) {
/* free the resource */
mysql_free_result(S->result);
S->result = NULL;
}

pdo_mysql_free_result(S);
if (S->einfo.errmsg) {
pefree(S->einfo.errmsg, stmt->dbh->is_persistent);
S->einfo.errmsg = NULL;
Expand All @@ -68,18 +86,6 @@ static int pdo_mysql_stmt_dtor(pdo_stmt_t *stmt) /* {{{ */
if (S->in_length) {
efree(S->in_length);
}

if (S->bound_result)
{
int i;
for (i = 0; i < stmt->column_count; i++) {
pdo_free_bound_result(S->bound_result[i]);
}

efree(S->bound_result);
efree(S->out_null);
efree(S->out_length);
}
#endif

if (!S->done && !Z_ISUNDEF(stmt->database_object_handle)
Expand Down Expand Up @@ -209,15 +215,6 @@ static int pdo_mysql_stmt_execute_prepared_libmysql(pdo_stmt_t *stmt) /* {{{ */
if (S->result) {
int calc_max_length = H->buffered && S->max_length == 1;
S->fields = mysql_fetch_fields(S->result);
if (S->bound_result) {
int i;
for (i = 0; i < stmt->column_count; i++) {
efree(S->bound_result[i].buffer);
}
efree(S->bound_result);
efree(S->out_null);
efree(S->out_length);
}

php_pdo_stmt_set_column_count(stmt, (int)mysql_num_fields(S->result));
S->bound_result = ecalloc(stmt->column_count, sizeof(MYSQL_BIND));
Expand Down Expand Up @@ -306,12 +303,7 @@ static int pdo_mysql_stmt_execute_prepared_mysqlnd(pdo_stmt_t *stmt) /* {{{ */
PDO_DBG_RETURN(0);
}

if (S->result) {
/* TODO: add a test to check if we really have zvals here... */
mysql_free_result(S->result);
S->result = NULL;
}

pdo_mysql_free_result(S);
PDO_DBG_RETURN(pdo_mysql_stmt_after_execute_prepared(stmt));
}
/* }}} */
Expand All @@ -330,10 +322,7 @@ static int pdo_mysql_stmt_execute(pdo_stmt_t *stmt) /* {{{ */
}

/* ensure that we free any previous unfetched results */
if (S->result) {
mysql_free_result(S->result);
S->result = NULL;
}
pdo_mysql_free_result(S);

if (mysql_real_query(H->server, stmt->active_query_string, stmt->active_query_stringlen) != 0) {
pdo_mysql_error_stmt(stmt);
Expand All @@ -355,10 +344,7 @@ static int pdo_mysql_stmt_next_rowset(pdo_stmt_t *stmt) /* {{{ */
if (S->stmt) {
mysql_stmt_free_result(S->stmt);
}
if (S->result) {
mysql_free_result(S->result);
S->result = NULL;
}
pdo_mysql_free_result(S);

#ifdef PDO_USE_MYSQLND
if (S->stmt) {
Expand Down Expand Up @@ -845,10 +831,7 @@ static int pdo_mysql_stmt_cursor_closer(pdo_stmt_t *stmt) /* {{{ */
PDO_DBG_INF_FMT("stmt=%p", S->stmt);

S->done = 1;
if (S->result) {
mysql_free_result(S->result);
S->result = NULL;
}
pdo_mysql_free_result(S);
if (S->stmt) {
mysql_stmt_free_result(S->stmt);
}
Expand Down

0 comments on commit c927c83

Please sign in to comment.