summaryrefslogtreecommitdiff
path: root/ext/mysqlnd
diff options
context:
space:
mode:
authorNikita Popov <nikita.ppv@gmail.com>2021-02-23 11:14:51 +0100
committerNikita Popov <nikita.ppv@gmail.com>2021-02-23 11:17:31 +0100
commit1fc4c89214c82fabbf997da58051a385d8fe50ab (patch)
tree314c8bacb123e871ce7570f4ab9eee60724c6626 /ext/mysqlnd
parent8be711be47a9d59574f12c77eca07da13e8993f3 (diff)
downloadphp-git-1fc4c89214c82fabbf997da58051a385d8fe50ab.tar.gz
Fixed bug #80761
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.
Diffstat (limited to 'ext/mysqlnd')
-rw-r--r--ext/mysqlnd/mysqlnd_wireprotocol.c62
1 files changed, 27 insertions, 35 deletions
diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c
index 9b2651b0a2..c345e5d7ec 100644
--- a/ext/mysqlnd/mysqlnd_wireprotocol.c
+++ b/ext/mysqlnd/mysqlnd_wireprotocol.c
@@ -1381,47 +1381,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 = 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) {