File php7-CVE-2024-8929.patch of Package php7.37962
Index: php-7.4.33/ext/mysqlnd/mysqlnd_ps_codec.c
===================================================================
--- php-7.4.33.orig/ext/mysqlnd/mysqlnd_ps_codec.c
+++ php-7.4.33/ext/mysqlnd/mysqlnd_ps_codec.c
@@ -52,11 +52,45 @@ struct st_mysqlnd_perm_bind mysqlnd_ps_f
#define MYSQLND_PS_SKIP_RESULT_W_LEN -1
#define MYSQLND_PS_SKIP_RESULT_STR -2
+static inline void ps_fetch_over_read_error(const zend_uchar ** row)
+{
+ php_error_docref(NULL, E_WARNING, "Malformed server packet. Field length pointing after the end of packet");
+ *row = NULL;
+}
+
+static inline int ps_fetch_is_packet_over_read_with_variable_length(const unsigned int pack_len,
+ const zend_uchar ** row, const zend_uchar *p, unsigned int length)
+{
+ if (pack_len == 0) {
+ return 0;
+ }
+ size_t length_len = *row - p;
+ if (length_len > pack_len || length > pack_len - length_len) {
+ ps_fetch_over_read_error(row);
+ return 1;
+ }
+ return 0;
+}
+
+static inline int ps_fetch_is_packet_over_read_with_static_length(const unsigned int pack_len,
+ const zend_uchar ** row, unsigned int length)
+{
+ if (pack_len > 0 && length > pack_len) {
+ ps_fetch_over_read_error(row);
+ return 1;
+ }
+ return 0;
+}
+
/* {{{ ps_fetch_from_1_to_8_bytes */
void
ps_fetch_from_1_to_8_bytes(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len,
const zend_uchar ** row, unsigned int byte_count)
{
+ if (UNEXPECTED(ps_fetch_is_packet_over_read_with_static_length(pack_len, row, byte_count))) {
+ return;
+ }
+
char tmp[22];
size_t tmp_len = 0;
zend_bool is_bit = field->type == MYSQL_TYPE_BIT;
@@ -178,6 +212,11 @@ ps_fetch_float(zval * zv, const MYSQLND_
float fval;
double dval;
DBG_ENTER("ps_fetch_float");
+
+ if (UNEXPECTED(ps_fetch_is_packet_over_read_with_static_length(pack_len, row, 4))) {
+ return;
+ }
+
float4get(fval, *row);
(*row)+= 4;
DBG_INF_FMT("value=%f", fval);
@@ -200,6 +239,11 @@ ps_fetch_double(zval * zv, const MYSQLND
{
double value;
DBG_ENTER("ps_fetch_double");
+
+ if (UNEXPECTED(ps_fetch_is_packet_over_read_with_static_length(pack_len, row, 8))) {
+ return;
+ }
+
float8get(value, *row);
ZVAL_DOUBLE(zv, value);
(*row)+= 8;
@@ -216,9 +260,14 @@ ps_fetch_time(zval * zv, const MYSQLND_F
struct st_mysqlnd_time t;
zend_ulong length; /* First byte encodes the length*/
char * value;
+ const zend_uchar *p = *row;
DBG_ENTER("ps_fetch_time");
if ((length = php_mysqlnd_net_field_length(row))) {
+ if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) {
+ return;
+ }
+
const zend_uchar * to = *row;
t.time_type = MYSQLND_TIMESTAMP_TIME;
@@ -273,9 +322,14 @@ ps_fetch_date(zval * zv, const MYSQLND_F
struct st_mysqlnd_time t = {0};
zend_ulong length; /* First byte encodes the length*/
char * value;
+ const zend_uchar *p = *row;
DBG_ENTER("ps_fetch_date");
if ((length = php_mysqlnd_net_field_length(row))) {
+ if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) {
+ return;
+ }
+
const zend_uchar * to = *row;
t.time_type = MYSQLND_TIMESTAMP_DATE;
@@ -310,9 +364,14 @@ ps_fetch_datetime(zval * zv, const MYSQL
struct st_mysqlnd_time t;
zend_ulong length; /* First byte encodes the length*/
char * value;
+ const zend_uchar *p = *row;
DBG_ENTER("ps_fetch_datetime");
if ((length = php_mysqlnd_net_field_length(row))) {
+ if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) {
+ return;
+ }
+
const zend_uchar * to = *row;
t.time_type = MYSQLND_TIMESTAMP_DATETIME;
@@ -371,7 +430,11 @@ ps_fetch_string(zval * zv, const MYSQLND
For now just copy, before we make it possible
to write \0 to the row buffer
*/
+ const zend_uchar *p = *row;
const zend_ulong length = php_mysqlnd_net_field_length(row);
+ if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) {
+ return;
+ }
DBG_ENTER("ps_fetch_string");
DBG_INF_FMT("len = %lu", length);
DBG_INF("copying from the row buffer");
@@ -387,7 +450,11 @@ ps_fetch_string(zval * zv, const MYSQLND
static void
ps_fetch_bit(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len, const zend_uchar ** row)
{
+ const zend_uchar *p = *row;
const zend_ulong length = php_mysqlnd_net_field_length(row);
+ if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) {
+ return;
+ }
ps_fetch_from_1_to_8_bytes(zv, field, pack_len, row, length);
}
/* }}} */
Index: php-7.4.33/ext/mysqlnd/mysqlnd_result.c
===================================================================
--- php-7.4.33.orig/ext/mysqlnd/mysqlnd_result.c
+++ php-7.4.33/ext/mysqlnd/mysqlnd_result.c
@@ -505,7 +505,7 @@ mysqlnd_query_read_result_set_header(MYS
if (FAIL == (ret = result->m.read_result_metadata(result, conn))) {
/* For PS, we leave them in Prepared state */
if (!stmt && conn->current_result) {
- mnd_efree(conn->current_result);
+ conn->current_result->m.free_result(conn->current_result, TRUE);
conn->current_result = NULL;
}
DBG_ERR("Error occurred while reading metadata");
Index: php-7.4.33/ext/mysqlnd/mysqlnd_wireprotocol.c
===================================================================
--- php-7.4.33.orig/ext/mysqlnd/mysqlnd_wireprotocol.c
+++ php-7.4.33/ext/mysqlnd/mysqlnd_wireprotocol.c
@@ -715,7 +715,14 @@ php_mysqlnd_auth_response_read(MYSQLND_C
/* There is a message */
if (packet->header.size > (size_t) (p - buf) && (net_len = php_mysqlnd_net_field_length(&p))) {
- packet->message_len = MIN(net_len, buf_len - (p - begin));
+ /* p can get past packet size when getting field length so it needs to be checked first
+ * and after that it can be checked that the net_len is not greater than the packet size */
+ if ((p - buf) > packet->header.size || packet->header.size - (p - buf) < net_len) {
+ DBG_ERR_FMT("OK packet message length is past the packet size");
+ php_error_docref(NULL, E_WARNING, "OK packet message length is past the packet size");
+ DBG_RETURN(FAIL);
+ }
+ packet->message_len = net_len;
packet->message = mnd_pestrndup((char *)p, packet->message_len, FALSE);
} else {
packet->message = NULL;
@@ -1113,6 +1120,17 @@ php_mysqlnd_rset_header_read(MYSQLND_CON
BAIL_IF_NO_MORE_DATA;
/* Check for additional textual data */
if (packet->header.size > (size_t) (p - buf) && (len = php_mysqlnd_net_field_length(&p))) {
+ /* p can get past packet size when getting field length so it needs to be checked first
+ * and after that it can be checked that the len is not greater than the packet size */
+ if ((p - buf) > packet->header.size || packet->header.size - (p - buf) < len) {
+ size_t local_file_name_over_read = ((p - buf) - packet->header.size) + len;
+ DBG_ERR_FMT("RSET_HEADER packet additional data length is past %zu bytes the packet size",
+ local_file_name_over_read);
+ php_error_docref(NULL, E_WARNING,
+ "RSET_HEADER packet additional data length is past %zu bytes the packet size",
+ local_file_name_over_read);
+ DBG_RETURN(FAIL);
+ }
packet->info_or_local_file.s = mnd_emalloc(len + 1);
if (packet->info_or_local_file.s) {
memcpy(packet->info_or_local_file.s, p, len);
@@ -1272,22 +1290,16 @@ php_mysqlnd_rset_field_read(MYSQLND_CONN
}
- /*
- def could be empty, thus don't allocate on the root.
- NULL_LENGTH (0xFB) comes from COM_FIELD_LIST when the default value is NULL.
- Otherwise the string is length encoded.
- */
+ /* COM_FIELD_LIST is no longer supported so def should not be present */
if (packet->header.size > (size_t) (p - buf) &&
(len = php_mysqlnd_net_field_length(&p)) &&
len != MYSQLND_NULL_LENGTH)
{
- BAIL_IF_NO_MORE_DATA;
- DBG_INF_FMT("Def found, length %lu", len);
- meta->def = packet->memory_pool->get_chunk(packet->memory_pool, len + 1);
- memcpy(meta->def, p, len);
- meta->def[len] = '\0';
- meta->def_length = len;
- p += len;
+ DBG_ERR_FMT("Protocol error. Server sent default for unsupported field list");
+ php_error_docref(NULL, E_WARNING,
+ "Protocol error. Server sent default for unsupported field list (mysqlnd_wireprotocol.c:%u)",
+ __LINE__);
+ DBG_RETURN(FAIL);
}
root_ptr = meta->root = packet->memory_pool->get_chunk(packet->memory_pool, total_len);
@@ -1462,8 +1474,10 @@ php_mysqlnd_rowp_read_binary_protocol(MY
const unsigned int field_count, const MYSQLND_FIELD * const fields_metadata,
const zend_bool as_int_or_float, MYSQLND_STATS * const stats)
{
- unsigned int i;
- const zend_uchar * p = row_buffer->ptr;
+ unsigned int i, j;
+ size_t rbs = row_buffer->size;
+ const zend_uchar * rbp = row_buffer->ptr;
+ const zend_uchar * p = rbp;
const zend_uchar * null_ptr;
zend_uchar bit;
zval *current_field, *end_field, *start_field;
@@ -1496,7 +1510,21 @@ php_mysqlnd_rowp_read_binary_protocol(MY
statistic = STAT_BINARY_TYPE_FETCHED_NULL;
} else {
enum_mysqlnd_field_types type = fields_metadata[i].type;
- mysqlnd_ps_fetch_functions[type].func(current_field, &fields_metadata[i], 0, &p);
+ size_t row_position = p - rbp;
+ if (rbs <= row_position) {
+ for (j = 0, current_field = start_field; j < i; current_field++, j++) {
+ zval_ptr_dtor(current_field);
+ }
+ php_error_docref(NULL, E_WARNING, "Malformed server packet. No packet space left for the field");
+ DBG_RETURN(FAIL);
+ }
+ mysqlnd_ps_fetch_functions[type].func(current_field, &fields_metadata[i], rbs - row_position, &p);
+ if (p == NULL) {
+ for (j = 0, current_field = start_field; j < i; current_field++, j++) {
+ zval_ptr_dtor(current_field);
+ }
+ DBG_RETURN(FAIL);
+ }
if (MYSQLND_G(collect_statistics)) {
switch (fields_metadata[i].type) {
@@ -1553,7 +1581,7 @@ php_mysqlnd_rowp_read_text_protocol_aux(
unsigned int field_count, const MYSQLND_FIELD * fields_metadata,
zend_bool as_int_or_float, MYSQLND_STATS * stats)
{
- unsigned int i;
+ unsigned int i, j;
zval *current_field, *end_field, *start_field;
zend_uchar * p = row_buffer->ptr;
const size_t data_size = row_buffer->size;
@@ -1574,9 +1602,11 @@ php_mysqlnd_rowp_read_text_protocol_aux(
/* NULL or NOT NULL, this is the question! */
if (len == MYSQLND_NULL_LENGTH) {
ZVAL_NULL(current_field);
- } else if ((p + len) > packet_end) {
- php_error_docref(NULL, E_WARNING, "Malformed server packet. Field length pointing "MYSQLND_SZ_T_SPEC
- " bytes after end of packet", (p + len) - packet_end - 1);
+ } else if (p > packet_end || len > packet_end - p) {
+ php_error_docref(NULL, E_WARNING, "Malformed server packet. Field length pointing after end of packet");
+ for (j = 0, current_field = start_field; j < i; current_field++, j++) {
+ zval_ptr_dtor(current_field);
+ }
DBG_RETURN(FAIL);
} else {
#if defined(MYSQLND_STRING_TO_INT_CONVERSION)