Skip to content

Commit 5e7c5a8

Browse files
committed
Merge branch 'PHP-7.3' into PHP-7.4
* PHP-7.3: Fix bug #80107: Handling of large compressed packets Bug #80107 Add test for mysqli_query() fails for ~16 MB long query when compression is enabled
2 parents f3ea88b + ecd9c42 commit 5e7c5a8

File tree

3 files changed

+124
-40
lines changed

3 files changed

+124
-40
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ PHP NEWS
55
- MySQLnd:
66
. Fixed bug #80115 (mysqlnd.debug doesn't recognize absolute paths with
77
slashes). (cmb)
8+
. Fixed bug #80107 (mysqli_query() fails for ~16 MB long query when
9+
compression is enabled). (Nikita)
810

911
- OPcache:
1012
. Fixed bug #80083 (Optimizer pass 6 removes variables used for ibm_db2 data
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
--TEST--
2+
Bug #80107 mysqli_query() fails for ~16 MB long query when compression is enabled
3+
--SKIPIF--
4+
<?php
5+
require_once('skipif.inc');
6+
require_once('skipifemb.inc');
7+
require_once("connect.inc");
8+
$link = @my_mysqli_connect($host, $user, $passwd, $db, $port, $socket);
9+
if (!$link) {
10+
die(sprintf("skip Can't connect to MySQL Server - [%d] %s", mysqli_connect_errno(), mysqli_connect_error()));
11+
}
12+
$result = $link->query("SHOW VARIABLES LIKE 'max_allowed_packet'");
13+
if ($result->fetch_assoc()['Value'] < 0xffffff) {
14+
die('skip max_allowed_packet is less than 0xffffff');
15+
}
16+
?>
17+
--FILE--
18+
<?php
19+
20+
require_once("connect.inc");
21+
22+
$data_size = 16777174;
23+
24+
// Insert with compression disabled:
25+
26+
$mysqli = mysqli_init();
27+
$result = my_mysqli_real_connect($mysqli, $host, $user, $passwd, $db, $port, $socket);
28+
$mysqli->query("DROP TABLE IF EXISTS test");
29+
$mysqli->query("CREATE TABLE test (`blob` LONGBLOB NOT NULL)");
30+
31+
$data = str_repeat("x", $data_size);
32+
$mysqli->query("INSERT INTO $db.test(`blob`) VALUE ('$data')");
33+
34+
var_dump(mysqli_error_list($mysqli));
35+
$mysqli->close();
36+
37+
// Insert with compression enabled:
38+
39+
$mysqli = mysqli_init();
40+
$result = my_mysqli_real_connect($mysqli, $host, $user, $passwd, $db, $port, $socket, MYSQLI_CLIENT_COMPRESS);
41+
42+
$data = str_repeat("x", $data_size);
43+
$mysqli->query("INSERT INTO $db.test(`blob`) VALUE ('$data')");
44+
45+
var_dump(mysqli_error_list($mysqli));
46+
$mysqli->close();
47+
48+
?>
49+
--CLEAN--
50+
<?php
51+
require_once("clean_table.inc");
52+
?>
53+
--EXPECT--
54+
array(0) {
55+
}
56+
array(0) {
57+
}

ext/mysqlnd/mysqlnd_protocol_frame_codec.c

Lines changed: 65 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,57 @@ MYSQLND_METHOD(mysqlnd_pfc, reset)(MYSQLND_PFC * const pfc, MYSQLND_STATS * cons
5050
#define STORE_HEADER_SIZE(safe_storage, buffer) COPY_HEADER((safe_storage), (buffer))
5151
#define RESTORE_HEADER_SIZE(buffer, safe_storage) STORE_HEADER_SIZE((safe_storage), (buffer))
5252

53+
#ifdef MYSQLND_COMPRESSION_ENABLED
54+
static ssize_t write_compressed_packet(
55+
const MYSQLND_PFC *pfc, MYSQLND_VIO *vio,
56+
MYSQLND_STATS *conn_stats, MYSQLND_ERROR_INFO *error_info,
57+
zend_uchar *uncompressed_payload, size_t to_be_sent, zend_uchar *compress_buf) {
58+
DBG_ENTER("write_compressed_packet");
59+
/* here we need to compress the data and then write it, first comes the compressed header */
60+
size_t tmp_complen = to_be_sent;
61+
size_t payload_size;
62+
if (PASS == pfc->data->m.encode((compress_buf + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE), &tmp_complen,
63+
uncompressed_payload, to_be_sent))
64+
{
65+
int3store(compress_buf + MYSQLND_HEADER_SIZE, to_be_sent);
66+
payload_size = tmp_complen;
67+
} else {
68+
int3store(compress_buf + MYSQLND_HEADER_SIZE, 0);
69+
memcpy(compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, uncompressed_payload, to_be_sent);
70+
payload_size = to_be_sent;
71+
}
72+
73+
int3store(compress_buf, payload_size);
74+
int1store(compress_buf + 3, pfc->data->compressed_envelope_packet_no);
75+
DBG_INF_FMT("writing "MYSQLND_SZ_T_SPEC" bytes to the network", payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE);
76+
77+
ssize_t bytes_sent = vio->data->m.network_write(vio, compress_buf, payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, conn_stats, error_info);
78+
pfc->data->compressed_envelope_packet_no++;
79+
#ifdef WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY
80+
if (res == Z_OK) {
81+
size_t decompressed_size = left + MYSQLND_HEADER_SIZE;
82+
zend_uchar * decompressed_data = mnd_malloc(decompressed_size);
83+
int error = pfc->data->m.decode(decompressed_data, decompressed_size,
84+
compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, payload_size);
85+
if (error == Z_OK) {
86+
int i;
87+
DBG_INF("success decompressing");
88+
for (i = 0 ; i < decompressed_size; i++) {
89+
if (i && (i % 30 == 0)) {
90+
printf("\n\t\t");
91+
}
92+
printf("%.2X ", (int)*((char*)&(decompressed_data[i])));
93+
DBG_INF_FMT("%.2X ", (int)*((char*)&(decompressed_data[i])));
94+
}
95+
} else {
96+
DBG_INF("error decompressing");
97+
}
98+
mnd_free(decompressed_data);
99+
}
100+
#endif /* WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY */
101+
DBG_RETURN(bytes_sent);
102+
}
103+
#endif
53104

54105
/* {{{ mysqlnd_pfc::send */
55106
/*
@@ -92,53 +143,27 @@ MYSQLND_METHOD(mysqlnd_pfc, send)(MYSQLND_PFC * const pfc, MYSQLND_VIO * const v
92143
DBG_INF_FMT("packet_no=%u", pfc->data->packet_no);
93144
#ifdef MYSQLND_COMPRESSION_ENABLED
94145
if (pfc->data->compressed == TRUE) {
95-
/* here we need to compress the data and then write it, first comes the compressed header */
96-
size_t tmp_complen = to_be_sent;
97-
size_t payload_size;
98146
zend_uchar * uncompressed_payload = p; /* should include the header */
99-
100147
STORE_HEADER_SIZE(safe_storage, uncompressed_payload);
101148
int3store(uncompressed_payload, to_be_sent);
102149
int1store(uncompressed_payload + 3, pfc->data->packet_no);
103-
if (PASS == pfc->data->m.encode((compress_buf + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE), &tmp_complen,
104-
uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE))
105-
{
106-
int3store(compress_buf + MYSQLND_HEADER_SIZE, to_be_sent + MYSQLND_HEADER_SIZE);
107-
payload_size = tmp_complen;
150+
if (to_be_sent <= MYSQLND_MAX_PACKET_SIZE - MYSQLND_HEADER_SIZE) {
151+
bytes_sent = write_compressed_packet(
152+
pfc, vio, conn_stats, error_info,
153+
uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE, compress_buf);
108154
} else {
109-
int3store(compress_buf + MYSQLND_HEADER_SIZE, 0);
110-
memcpy(compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE);
111-
payload_size = to_be_sent + MYSQLND_HEADER_SIZE;
155+
/* The uncompressed size including the header would overflow. Split into two
156+
* compressed packets. The size of the first one is relatively arbitrary here. */
157+
const size_t split_off_bytes = 8192;
158+
bytes_sent = write_compressed_packet(
159+
pfc, vio, conn_stats, error_info,
160+
uncompressed_payload, split_off_bytes, compress_buf);
161+
bytes_sent = write_compressed_packet(
162+
pfc, vio, conn_stats, error_info,
163+
uncompressed_payload + split_off_bytes,
164+
to_be_sent + MYSQLND_HEADER_SIZE - split_off_bytes, compress_buf);
112165
}
113166
RESTORE_HEADER_SIZE(uncompressed_payload, safe_storage);
114-
115-
int3store(compress_buf, payload_size);
116-
int1store(compress_buf + 3, pfc->data->packet_no);
117-
DBG_INF_FMT("writing "MYSQLND_SZ_T_SPEC" bytes to the network", payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE);
118-
bytes_sent = vio->data->m.network_write(vio, compress_buf, payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, conn_stats, error_info);
119-
pfc->data->compressed_envelope_packet_no++;
120-
#if WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY
121-
if (res == Z_OK) {
122-
size_t decompressed_size = left + MYSQLND_HEADER_SIZE;
123-
zend_uchar * decompressed_data = mnd_malloc(decompressed_size);
124-
int error = pfc->data->m.decode(decompressed_data, decompressed_size,
125-
compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, payload_size);
126-
if (error == Z_OK) {
127-
int i;
128-
DBG_INF("success decompressing");
129-
for (i = 0 ; i < decompressed_size; i++) {
130-
if (i && (i % 30 == 0)) {
131-
printf("\n\t\t");
132-
}
133-
printf("%.2X ", (int)*((char*)&(decompressed_data[i])));
134-
DBG_INF_FMT("%.2X ", (int)*((char*)&(decompressed_data[i])));
135-
}
136-
} else {
137-
DBG_INF("error decompressing");
138-
}
139-
mnd_free(decompressed_data);
140-
}
141-
#endif /* WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY */
142167
} else
143168
#endif /* MYSQLND_COMPRESSION_ENABLED */
144169
{

0 commit comments

Comments
 (0)