Skip to content

Fix handling of large compressed packets #6144

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions ext/mysqli/tests/mysqli_real_connect_compression_error.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
--TEST--
Bug #80107 mysqli_query() fails for ~16 MB long query when compression is enabled
--SKIPIF--
<?php
require_once('skipif.inc');
require_once('skipifemb.inc');
require_once("connect.inc");
$link = @my_mysqli_connect($host, $user, $passwd, $db, $port, $socket);
if (!$link) {
die(sprintf("skip Can't connect to MySQL Server - [%d] %s", mysqli_connect_errno(), mysqli_connect_error()));
}
$result = $link->query("SHOW VARIABLES LIKE 'max_allowed_packet'");
if ($result->fetch_assoc()['Value'] < 0xffffff) {
die('skip max_allowed_packet is less than 0xffffff');
}
?>
--FILE--
<?php

require_once("connect.inc");

$data_size = 16777174;

// Insert with compression disabled:

$mysqli = mysqli_init();
$result = my_mysqli_real_connect($mysqli, $host, $user, $passwd, $db, $port, $socket);
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test (`blob` LONGBLOB NOT NULL)");

$data = str_repeat("x", $data_size);
$mysqli->query("INSERT INTO $db.test(`blob`) VALUE ('$data')");

var_dump(mysqli_error_list($mysqli));
$mysqli->close();

// Insert with compression enabled:

$mysqli = mysqli_init();
$result = my_mysqli_real_connect($mysqli, $host, $user, $passwd, $db, $port, $socket, MYSQLI_CLIENT_COMPRESS);

$data = str_repeat("x", $data_size);
$mysqli->query("INSERT INTO $db.test(`blob`) VALUE ('$data')");

var_dump(mysqli_error_list($mysqli));
$mysqli->close();

?>
--CLEAN--
<?php
require_once("clean_table.inc");
?>
--EXPECT--
array(0) {
}
array(0) {
}
104 changes: 64 additions & 40 deletions ext/mysqlnd/mysqlnd_protocol_frame_codec.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,56 @@ MYSQLND_METHOD(mysqlnd_pfc, reset)(MYSQLND_PFC * const pfc, MYSQLND_STATS * cons
#define RESTORE_HEADER_SIZE(buffer, safe_storage) STORE_HEADER_SIZE((safe_storage), (buffer))


static ssize_t write_compressed_packet(
const MYSQLND_PFC *pfc, MYSQLND_VIO *vio,
MYSQLND_STATS *conn_stats, MYSQLND_ERROR_INFO *error_info,
zend_uchar *uncompressed_payload, size_t to_be_sent, zend_uchar *compress_buf) {
DBG_ENTER("write_compressed_packet");
/* here we need to compress the data and then write it, first comes the compressed header */
size_t tmp_complen = to_be_sent;
size_t payload_size;
if (PASS == pfc->data->m.encode((compress_buf + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE), &tmp_complen,
uncompressed_payload, to_be_sent))
{
int3store(compress_buf + MYSQLND_HEADER_SIZE, to_be_sent);
payload_size = tmp_complen;
} else {
int3store(compress_buf + MYSQLND_HEADER_SIZE, 0);
memcpy(compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, uncompressed_payload, to_be_sent);
payload_size = to_be_sent;
}

int3store(compress_buf, payload_size);
int1store(compress_buf + 3, pfc->data->compressed_envelope_packet_no);
DBG_INF_FMT("writing "MYSQLND_SZ_T_SPEC" bytes to the network", payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE);

ssize_t bytes_sent = vio->data->m.network_write(vio, compress_buf, payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, conn_stats, error_info);
pfc->data->compressed_envelope_packet_no++;
#ifdef WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY
if (res == Z_OK) {
size_t decompressed_size = left + MYSQLND_HEADER_SIZE;
zend_uchar * decompressed_data = mnd_malloc(decompressed_size);
int error = pfc->data->m.decode(decompressed_data, decompressed_size,
compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, payload_size);
if (error == Z_OK) {
int i;
DBG_INF("success decompressing");
for (i = 0 ; i < decompressed_size; i++) {
if (i && (i % 30 == 0)) {
printf("\n\t\t");
}
printf("%.2X ", (int)*((char*)&(decompressed_data[i])));
DBG_INF_FMT("%.2X ", (int)*((char*)&(decompressed_data[i])));
}
} else {
DBG_INF("error decompressing");
}
mnd_free(decompressed_data);
}
#endif /* WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY */
DBG_RETURN(bytes_sent);
}

/* {{{ mysqlnd_pfc::send */
/*
IMPORTANT : It's expected that buffer has place in the beginning for MYSQLND_HEADER_SIZE !!!!
Expand Down Expand Up @@ -90,53 +140,27 @@ MYSQLND_METHOD(mysqlnd_pfc, send)(MYSQLND_PFC * const pfc, MYSQLND_VIO * const v
DBG_INF_FMT("packet_no=%u", pfc->data->packet_no);
#ifdef MYSQLND_COMPRESSION_ENABLED
if (pfc->data->compressed == TRUE) {
/* here we need to compress the data and then write it, first comes the compressed header */
size_t tmp_complen = to_be_sent;
size_t payload_size;
zend_uchar * uncompressed_payload = p; /* should include the header */

STORE_HEADER_SIZE(safe_storage, uncompressed_payload);
int3store(uncompressed_payload, to_be_sent);
int1store(uncompressed_payload + 3, pfc->data->packet_no);
if (PASS == pfc->data->m.encode((compress_buf + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE), &tmp_complen,
uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE))
{
int3store(compress_buf + MYSQLND_HEADER_SIZE, to_be_sent + MYSQLND_HEADER_SIZE);
payload_size = tmp_complen;
if (to_be_sent <= MYSQLND_MAX_PACKET_SIZE - MYSQLND_HEADER_SIZE) {
bytes_sent = write_compressed_packet(
pfc, vio, conn_stats, error_info,
uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE, compress_buf);
} else {
int3store(compress_buf + MYSQLND_HEADER_SIZE, 0);
memcpy(compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE);
payload_size = to_be_sent + MYSQLND_HEADER_SIZE;
/* The uncompressed size including the header would overflow. Split into two
* compressed packets. The size of the first one is relatively arbitrary here. */
const size_t split_off_bytes = 8192;
bytes_sent = write_compressed_packet(
pfc, vio, conn_stats, error_info,
uncompressed_payload, split_off_bytes, compress_buf);
bytes_sent = write_compressed_packet(
pfc, vio, conn_stats, error_info,
uncompressed_payload + split_off_bytes,
to_be_sent + MYSQLND_HEADER_SIZE - split_off_bytes, compress_buf);
}
RESTORE_HEADER_SIZE(uncompressed_payload, safe_storage);

int3store(compress_buf, payload_size);
int1store(compress_buf + 3, pfc->data->packet_no);
DBG_INF_FMT("writing "MYSQLND_SZ_T_SPEC" bytes to the network", payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE);
bytes_sent = vio->data->m.network_write(vio, compress_buf, payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, conn_stats, error_info);
pfc->data->compressed_envelope_packet_no++;
#ifdef WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY
if (res == Z_OK) {
size_t decompressed_size = left + MYSQLND_HEADER_SIZE;
zend_uchar * decompressed_data = mnd_malloc(decompressed_size);
int error = pfc->data->m.decode(decompressed_data, decompressed_size,
compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, payload_size);
if (error == Z_OK) {
int i;
DBG_INF("success decompressing");
for (i = 0 ; i < decompressed_size; i++) {
if (i && (i % 30 == 0)) {
printf("\n\t\t");
}
printf("%.2X ", (int)*((char*)&(decompressed_data[i])));
DBG_INF_FMT("%.2X ", (int)*((char*)&(decompressed_data[i])));
}
} else {
DBG_INF("error decompressing");
}
mnd_free(decompressed_data);
}
#endif /* WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY */
} else
#endif /* MYSQLND_COMPRESSION_ENABLED */
{
Expand Down