Skip to content

Commit

Permalink
Fixed bug #80761
Browse files Browse the repository at this point in the history
When row data split across multiple packets, allocate a temporary
buffer that can be reallocated, and only copy into the row buffer
pool arena once we know the final size. This avoids quadratic
memory usage for very large results.

(cherry picked from commit 1fc4c89)
  • Loading branch information
nikic committed May 27, 2021
1 parent f9fd359 commit 635303a
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 35 deletions.
3 changes: 3 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ PHP NEWS
reference). (Dmitry)
. Fixed bug #80968 (JIT segfault with return from required file). (Dmitry)

- MySQLnd:
. Fixed bug #80761 (PDO uses too much memory). (Nikita)

- Standard:
. Fixed bug #81048 (phpinfo(INFO_VARIABLES) "Array to string conversion").
(cmb)
Expand Down
62 changes: 27 additions & 35 deletions ext/mysqlnd/mysqlnd_wireprotocol.c
Original file line number Diff line number Diff line change
Expand Up @@ -1391,47 +1391,39 @@ php_mysqlnd_read_row_ex(MYSQLND_PFC * pfc,
SET_CONNECTION_STATE(connection_state, CONN_QUIT_SENT);
set_packet_error(error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
} else {
/* If the packet is split in multiple chunks, allocate a temporary buffer that we can
* reallocate, and only afterwards copy it to the pool when we know the final size. */
zend_uchar *buf = NULL;
while (header.size >= MYSQLND_MAX_PACKET_SIZE) {
buf = erealloc(buf, *data_size + header.size);
p = buf + *data_size;
*data_size += header.size;

if (UNEXPECTED(PASS != (ret = pfc->data->m.receive(pfc, vio, p, header.size, stats, error_info)))) {
DBG_ERR("Empty row packet body");
SET_CONNECTION_STATE(connection_state, CONN_QUIT_SENT);
set_packet_error(error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
efree(buf);
DBG_RETURN(FAIL);
}
if (FAIL == mysqlnd_read_header(pfc, vio, &header, stats, error_info)) {
efree(buf);
DBG_RETURN(FAIL);
}
}

buffer->ptr = pool->get_chunk(pool, *data_size + header.size + prealloc_more_bytes);
if (buf) {
memcpy(buffer->ptr, buf, *data_size);
efree(buf);
}
p = (zend_uchar *) buffer->ptr + *data_size;
*data_size += header.size;
buffer->ptr = pool->get_chunk(pool, *data_size + prealloc_more_bytes);
p = buffer->ptr;

if (UNEXPECTED(PASS != (ret = pfc->data->m.receive(pfc, vio, p, header.size, stats, error_info)))) {
DBG_ERR("Empty row packet body");
SET_CONNECTION_STATE(connection_state, CONN_QUIT_SENT);
set_packet_error(error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
} else {
while (header.size >= MYSQLND_MAX_PACKET_SIZE) {
if (FAIL == mysqlnd_read_header(pfc, vio, &header, stats, error_info)) {
ret = FAIL;
break;
}

*data_size += header.size;

/* Empty packet after MYSQLND_MAX_PACKET_SIZE packet. That's ok, break */
if (!header.size) {
break;
}

/*
We have to realloc the buffer.
*/
buffer->ptr = pool->resize_chunk(pool, buffer->ptr, *data_size - header.size, *data_size + prealloc_more_bytes);
if (!buffer->ptr) {
SET_OOM_ERROR(error_info);
ret = FAIL;
break;
}
/* The position could have changed, recalculate */
p = (zend_uchar *) buffer->ptr + (*data_size - header.size);

if (PASS != (ret = pfc->data->m.receive(pfc, vio, p, header.size, stats, error_info))) {
DBG_ERR("Empty row packet body");
SET_CONNECTION_STATE(connection_state, CONN_QUIT_SENT);
set_packet_error(error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
break;
}
}
}
}
if (ret == FAIL && buffer->ptr) {
Expand Down

0 comments on commit 635303a

Please sign in to comment.