andrey Wed, 22 Sep 2010 11:38:49 +0000 Revision: http://svn.php.net/viewvc?view=revision&revision=303671
Log: Fix for Bug #52891 Wrong data inserted with mysqli/mysqlnd when using bind_param,value>LONG_MAX Bug: http://bugs.php.net/52891 (Assigned) Wrong data inserted with mysqli/mysqlnd when using bind_param,value>LONG_MAX Changed paths: U php/php-src/branches/PHP_5_3/ext/mysqlnd/mysqlnd_ps.c U php/php-src/branches/PHP_5_3/ext/mysqlnd/mysqlnd_ps_codec.c U php/php-src/trunk/ext/mysqlnd/mysqlnd_ps.c U php/php-src/trunk/ext/mysqlnd/mysqlnd_ps_codec.c
Modified: php/php-src/branches/PHP_5_3/ext/mysqlnd/mysqlnd_ps.c =================================================================== --- php/php-src/branches/PHP_5_3/ext/mysqlnd/mysqlnd_ps.c 2010-09-22 11:12:32 UTC (rev 303670) +++ php/php-src/branches/PHP_5_3/ext/mysqlnd/mysqlnd_ps.c 2010-09-22 11:38:49 UTC (rev 303671) @@ -482,10 +482,17 @@ /* close the statement here, the connection has been closed */ } stmt->state = MYSQLND_STMT_PREPARED; + stmt->send_types_to_server = 1; } else { + /* + stmt->send_types_to_server has already been set to 0 in + mysqlnd_stmt_execute_generate_request / mysqlnd_stmt_execute_store_params + In case there is a situation in which binding was done for integer and the + value is > LONG_MAX or < LONG_MIN, there is string conversion and we have + to resend the types. Next execution will also need to resend the type. + */ SET_EMPTY_ERROR(stmt->error_info); SET_EMPTY_ERROR(stmt->conn->error_info); - stmt->send_types_to_server = 0; stmt->upsert_status = conn->upsert_status; stmt->state = MYSQLND_STMT_EXECUTED; if (conn->last_query_type == QUERY_UPSERT || conn->last_query_type == QUERY_LOAD_LOCAL) { Modified: php/php-src/branches/PHP_5_3/ext/mysqlnd/mysqlnd_ps_codec.c =================================================================== --- php/php-src/branches/PHP_5_3/ext/mysqlnd/mysqlnd_ps_codec.c 2010-09-22 11:12:32 UTC (rev 303670) +++ php/php-src/branches/PHP_5_3/ext/mysqlnd/mysqlnd_ps_codec.c 2010-09-22 11:38:49 UTC (rev 303671) @@ -608,12 +608,46 @@ size_t data_size = 0; zval **copies = NULL;/* if there are different types */ enum_func_status ret = FAIL; + int resend_types_next_time = 0; DBG_ENTER("mysqlnd_stmt_execute_store_params"); /* 1. Store type information */ + /* + check if need to send the types even if stmt->send_types_to_server is 0. This is because + if we send "i" (42) then the type will be int and the server will expect int. However, if next + time we try to send > LONG_MAX, the conversion to string will send a string and the server + won't expect it and interpret the value as 0. Thus we need to resend the types, if any such values + occur, and force resend for the next execution. + + */ + for (i = 0; i < stmt->param_count; i++) { + if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_NULL && + (stmt->param_bind[i].type == MYSQL_TYPE_LONG || stmt->param_bind[i].type == MYSQL_TYPE_LONGLONG)) + { + /* always copy the var, because we do many conversions */ + if (PASS != mysqlnd_stmt_copy_it(&copies, stmt->param_bind[i].zv, stmt->param_count, i TSRMLS_CC)) { + SET_OOM_ERROR(stmt->error_info); + goto end; + } + /* + if it doesn't fit in a long send it as a string. + Check bug #52891 : Wrong data inserted with mysqli/mysqlnd when using bind_param, value > LONG_MAX + */ + { + zval *tmp_data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; + convert_to_double_ex(&tmp_data); + if (Z_DVAL_P(tmp_data) > LONG_MAX || Z_DVAL_P(tmp_data) < LONG_MIN) { + stmt->send_types_to_server = resend_types_next_time = 1; + } + } + } + } + + int1store(*p, stmt->send_types_to_server); + (*p)++; + if (stmt->send_types_to_server) { - /* 2 bytes per type, and leave 20 bytes for future use */ if (left < ((stmt->param_count * 2) + 20)) { unsigned int offset = *p - *buf; @@ -631,16 +665,40 @@ *p = *buf + offset; } for (i = 0; i < stmt->param_count; i++) { + short current_type = stmt->param_bind[i].type; /* our types are not unsigned */ #if SIZEOF_LONG==8 - if (stmt->param_bind[i].type == MYSQL_TYPE_LONG) { - stmt->param_bind[i].type = MYSQL_TYPE_LONGLONG; + if (current_type == MYSQL_TYPE_LONG) { + current_type = MYSQL_TYPE_LONGLONG; } #endif - int2store(*p, stmt->param_bind[i].type); + if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_NULL && (current_type == MYSQL_TYPE_LONG || current_type == MYSQL_TYPE_LONGLONG)) { + /* + if it doesn't fit in a long send it as a string. + Check bug #52891 : Wrong data inserted with mysqli/mysqlnd when using bind_param, value > LONG_MAX + */ + { + zval *tmp_data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; + convert_to_double_ex(&tmp_data); + if (Z_DVAL_P(tmp_data) > LONG_MAX || Z_DVAL_P(tmp_data) < LONG_MIN) { + convert_to_string_ex(&tmp_data); + current_type = MYSQL_TYPE_VAR_STRING; + /* + don't change stmt->param_bind[i].type to MYSQL_TYPE_VAR_STRING + we force convert_to_long_ex in all cases, thus the type will be right in the next switch. + if the type is however not long, then we will do a goto in the next switch. + We want to preserve the original bind type given by the user. Thus, we do these hacks. + */ + } else { + convert_to_long_ex(&tmp_data); + } + } + } + int2store(*p, current_type); *p+= 2; } } + stmt->send_types_to_server = resend_types_next_time; /* 2. Store data */ /* 2.1 Calculate how much space we need */ @@ -654,9 +712,11 @@ for (j = i + 1; j < stmt->param_count; j++) { if (stmt->param_bind[j].zv == the_var) { /* Double binding of the same zval, make a copy */ - if (PASS != mysqlnd_stmt_copy_it(&copies, the_var, stmt->param_count, i TSRMLS_CC)) { - SET_OOM_ERROR(stmt->error_info); - goto end; + if (!copies || !copies[i]) { + if (PASS != mysqlnd_stmt_copy_it(&copies, the_var, stmt->param_count, i TSRMLS_CC)) { + SET_OOM_ERROR(stmt->error_info); + goto end; + } } break; } @@ -674,23 +734,25 @@ } } break; -#if SIZEOF_LONG==8 case MYSQL_TYPE_LONGLONG: + { + zval *tmp_data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; + if (Z_TYPE_P(tmp_data) == IS_STRING) { + goto use_string; + } + convert_to_long_ex(&tmp_data); + } data_size += 8; -#elif SIZEOF_LONG==4 + break; case MYSQL_TYPE_LONG: - data_size += 4; -#else -#error "Should not happen" -#endif - if (Z_TYPE_P(the_var) != IS_LONG) { - if (!copies || !copies[i]) { - if (PASS != mysqlnd_stmt_copy_it(&copies, the_var, stmt->param_count, i TSRMLS_CC)) { - SET_OOM_ERROR(stmt->error_info); - goto end; - } + { + zval *tmp_data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; + if (Z_TYPE_P(tmp_data) == IS_STRING) { + goto use_string; } + convert_to_long_ex(&tmp_data); } + data_size += 4; break; case MYSQL_TYPE_LONG_BLOB: if (!(stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED)) { @@ -703,6 +765,7 @@ } break; case MYSQL_TYPE_VAR_STRING: +use_string: data_size += 8; /* max 8 bytes for size */ #if MYSQLND_UNICODE if (Z_TYPE_P(the_var) != IS_STRING || Z_TYPE_P(the_var) == IS_UNICODE) @@ -757,7 +820,7 @@ /* 2.3 Store the actual data */ for (i = 0; i < stmt->param_count; i++) { - zval *data = copies && copies[i]? copies[i]: stmt->param_bind[i].zv; + zval *data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; /* Handle long data */ if (stmt->param_bind[i].zv && Z_TYPE_P(data) == IS_NULL) { (*buf + null_byte_offset)[i/8] |= (zend_uchar) (1 << (i & 7)); @@ -768,21 +831,22 @@ float8store(*p, Z_DVAL_P(data)); (*p) += 8; break; -#if SIZEOF_LONG==8 case MYSQL_TYPE_LONGLONG: - convert_to_long_ex(&data); + if (Z_TYPE_P(data) == IS_STRING) { + goto send_string; + } + /* data has alreade been converted to long */ int8store(*p, Z_LVAL_P(data)); (*p) += 8; break; -#elif SIZEOF_LONG==4 case MYSQL_TYPE_LONG: - convert_to_long_ex(&data); + if (Z_TYPE_P(data) == IS_STRING) { + goto send_string; + } + /* data has alreade been converted to long */ int4store(*p, Z_LVAL_P(data)); (*p) += 4; break; -#else -#error "Should not happen" -#endif case MYSQL_TYPE_LONG_BLOB: if (stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED) { stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED; @@ -791,7 +855,9 @@ *p = php_mysqlnd_net_store_length(*p, 0); } break; - case MYSQL_TYPE_VAR_STRING:{ + case MYSQL_TYPE_VAR_STRING: +send_string: + { unsigned int len = Z_STRLEN_P(data); /* to is after p. The latter hasn't been moved */ *p = php_mysqlnd_net_store_length(*p, len); @@ -855,10 +921,6 @@ memset(p, 0, null_count); p += null_count; - - int1store(p, stmt->send_types_to_server); - p++; - ret = mysqlnd_stmt_execute_store_params(s, &cmd_buffer, &p, &cmd_buffer_length, null_byte_offset TSRMLS_CC); *free_buffer = (cmd_buffer != stmt->execute_cmd_buffer.buffer); Modified: php/php-src/trunk/ext/mysqlnd/mysqlnd_ps.c =================================================================== --- php/php-src/trunk/ext/mysqlnd/mysqlnd_ps.c 2010-09-22 11:12:32 UTC (rev 303670) +++ php/php-src/trunk/ext/mysqlnd/mysqlnd_ps.c 2010-09-22 11:38:49 UTC (rev 303671) @@ -482,10 +482,17 @@ /* close the statement here, the connection has been closed */ } stmt->state = MYSQLND_STMT_PREPARED; + stmt->send_types_to_server = 1; } else { + /* + stmt->send_types_to_server has already been set to 0 in + mysqlnd_stmt_execute_generate_request / mysqlnd_stmt_execute_store_params + In case there is a situation in which binding was done for integer and the + value is > LONG_MAX or < LONG_MIN, there is string conversion and we have + to resend the types. Next execution will also need to resend the type. + */ SET_EMPTY_ERROR(stmt->error_info); SET_EMPTY_ERROR(stmt->conn->error_info); - stmt->send_types_to_server = 0; stmt->upsert_status = conn->upsert_status; stmt->state = MYSQLND_STMT_EXECUTED; if (conn->last_query_type == QUERY_UPSERT || conn->last_query_type == QUERY_LOAD_LOCAL) { Modified: php/php-src/trunk/ext/mysqlnd/mysqlnd_ps_codec.c =================================================================== --- php/php-src/trunk/ext/mysqlnd/mysqlnd_ps_codec.c 2010-09-22 11:12:32 UTC (rev 303670) +++ php/php-src/trunk/ext/mysqlnd/mysqlnd_ps_codec.c 2010-09-22 11:38:49 UTC (rev 303671) @@ -608,12 +608,46 @@ size_t data_size = 0; zval **copies = NULL;/* if there are different types */ enum_func_status ret = FAIL; + int resend_types_next_time = 0; DBG_ENTER("mysqlnd_stmt_execute_store_params"); /* 1. Store type information */ + /* + check if need to send the types even if stmt->send_types_to_server is 0. This is because + if we send "i" (42) then the type will be int and the server will expect int. However, if next + time we try to send > LONG_MAX, the conversion to string will send a string and the server + won't expect it and interpret the value as 0. Thus we need to resend the types, if any such values + occur, and force resend for the next execution. + + */ + for (i = 0; i < stmt->param_count; i++) { + if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_NULL && + (stmt->param_bind[i].type == MYSQL_TYPE_LONG || stmt->param_bind[i].type == MYSQL_TYPE_LONGLONG)) + { + /* always copy the var, because we do many conversions */ + if (PASS != mysqlnd_stmt_copy_it(&copies, stmt->param_bind[i].zv, stmt->param_count, i TSRMLS_CC)) { + SET_OOM_ERROR(stmt->error_info); + goto end; + } + /* + if it doesn't fit in a long send it as a string. + Check bug #52891 : Wrong data inserted with mysqli/mysqlnd when using bind_param, value > LONG_MAX + */ + { + zval *tmp_data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; + convert_to_double_ex(&tmp_data); + if (Z_DVAL_P(tmp_data) > LONG_MAX || Z_DVAL_P(tmp_data) < LONG_MIN) { + stmt->send_types_to_server = resend_types_next_time = 1; + } + } + } + } + + int1store(*p, stmt->send_types_to_server); + (*p)++; + if (stmt->send_types_to_server) { - /* 2 bytes per type, and leave 20 bytes for future use */ if (left < ((stmt->param_count * 2) + 20)) { unsigned int offset = *p - *buf; @@ -631,16 +665,40 @@ *p = *buf + offset; } for (i = 0; i < stmt->param_count; i++) { + short current_type = stmt->param_bind[i].type; /* our types are not unsigned */ #if SIZEOF_LONG==8 - if (stmt->param_bind[i].type == MYSQL_TYPE_LONG) { - stmt->param_bind[i].type = MYSQL_TYPE_LONGLONG; + if (current_type == MYSQL_TYPE_LONG) { + current_type = MYSQL_TYPE_LONGLONG; } #endif - int2store(*p, stmt->param_bind[i].type); + if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_NULL && (current_type == MYSQL_TYPE_LONG || current_type == MYSQL_TYPE_LONGLONG)) { + /* + if it doesn't fit in a long send it as a string. + Check bug #52891 : Wrong data inserted with mysqli/mysqlnd when using bind_param, value > LONG_MAX + */ + { + zval *tmp_data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; + convert_to_double_ex(&tmp_data); + if (Z_DVAL_P(tmp_data) > LONG_MAX || Z_DVAL_P(tmp_data) < LONG_MIN) { + convert_to_string_ex(&tmp_data); + current_type = MYSQL_TYPE_VAR_STRING; + /* + don't change stmt->param_bind[i].type to MYSQL_TYPE_VAR_STRING + we force convert_to_long_ex in all cases, thus the type will be right in the next switch. + if the type is however not long, then we will do a goto in the next switch. + We want to preserve the original bind type given by the user. Thus, we do these hacks. + */ + } else { + convert_to_long_ex(&tmp_data); + } + } + } + int2store(*p, current_type); *p+= 2; } } + stmt->send_types_to_server = resend_types_next_time; /* 2. Store data */ /* 2.1 Calculate how much space we need */ @@ -654,9 +712,11 @@ for (j = i + 1; j < stmt->param_count; j++) { if (stmt->param_bind[j].zv == the_var) { /* Double binding of the same zval, make a copy */ - if (PASS != mysqlnd_stmt_copy_it(&copies, the_var, stmt->param_count, i TSRMLS_CC)) { - SET_OOM_ERROR(stmt->error_info); - goto end; + if (!copies || !copies[i]) { + if (PASS != mysqlnd_stmt_copy_it(&copies, the_var, stmt->param_count, i TSRMLS_CC)) { + SET_OOM_ERROR(stmt->error_info); + goto end; + } } break; } @@ -674,23 +734,25 @@ } } break; -#if SIZEOF_LONG==8 case MYSQL_TYPE_LONGLONG: + { + zval *tmp_data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; + if (Z_TYPE_P(tmp_data) == IS_STRING) { + goto use_string; + } + convert_to_long_ex(&tmp_data); + } data_size += 8; -#elif SIZEOF_LONG==4 + break; case MYSQL_TYPE_LONG: - data_size += 4; -#else -#error "Should not happen" -#endif - if (Z_TYPE_P(the_var) != IS_LONG) { - if (!copies || !copies[i]) { - if (PASS != mysqlnd_stmt_copy_it(&copies, the_var, stmt->param_count, i TSRMLS_CC)) { - SET_OOM_ERROR(stmt->error_info); - goto end; - } + { + zval *tmp_data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; + if (Z_TYPE_P(tmp_data) == IS_STRING) { + goto use_string; } + convert_to_long_ex(&tmp_data); } + data_size += 4; break; case MYSQL_TYPE_LONG_BLOB: if (!(stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED)) { @@ -703,6 +765,7 @@ } break; case MYSQL_TYPE_VAR_STRING: +use_string: data_size += 8; /* max 8 bytes for size */ #if MYSQLND_UNICODE if (Z_TYPE_P(the_var) != IS_STRING || Z_TYPE_P(the_var) == IS_UNICODE) @@ -757,7 +820,7 @@ /* 2.3 Store the actual data */ for (i = 0; i < stmt->param_count; i++) { - zval *data = copies && copies[i]? copies[i]: stmt->param_bind[i].zv; + zval *data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; /* Handle long data */ if (stmt->param_bind[i].zv && Z_TYPE_P(data) == IS_NULL) { (*buf + null_byte_offset)[i/8] |= (zend_uchar) (1 << (i & 7)); @@ -768,21 +831,22 @@ float8store(*p, Z_DVAL_P(data)); (*p) += 8; break; -#if SIZEOF_LONG==8 case MYSQL_TYPE_LONGLONG: - convert_to_long_ex(&data); + if (Z_TYPE_P(data) == IS_STRING) { + goto send_string; + } + /* data has alreade been converted to long */ int8store(*p, Z_LVAL_P(data)); (*p) += 8; break; -#elif SIZEOF_LONG==4 case MYSQL_TYPE_LONG: - convert_to_long_ex(&data); + if (Z_TYPE_P(data) == IS_STRING) { + goto send_string; + } + /* data has alreade been converted to long */ int4store(*p, Z_LVAL_P(data)); (*p) += 4; break; -#else -#error "Should not happen" -#endif case MYSQL_TYPE_LONG_BLOB: if (stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED) { stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED; @@ -791,7 +855,9 @@ *p = php_mysqlnd_net_store_length(*p, 0); } break; - case MYSQL_TYPE_VAR_STRING:{ + case MYSQL_TYPE_VAR_STRING: +send_string: + { unsigned int len = Z_STRLEN_P(data); /* to is after p. The latter hasn't been moved */ *p = php_mysqlnd_net_store_length(*p, len); @@ -855,10 +921,6 @@ memset(p, 0, null_count); p += null_count; - - int1store(p, stmt->send_types_to_server); - p++; - ret = mysqlnd_stmt_execute_store_params(s, &cmd_buffer, &p, &cmd_buffer_length, null_byte_offset TSRMLS_CC); *free_buffer = (cmd_buffer != stmt->execute_cmd_buffer.buffer);
-- PHP CVS Mailing List (http://www.php.net/) To unsubscribe, visit: http://www.php.net/unsub.php