Commit message: Fix bug #73065: Out-Of-Bounds Read in php_wddx_push_element of wddx.c
CVE: CVE-2016-7418
CWE: CWE-119
Vulnerability description: The php_wddx_push_element function in ext/wddx/wddx.c in PHP before 5.6.26 and 7.x before 7.0.11 allows remote attackers to cause a denial of service (invalid pointer access and out-of-bounds read) or possibly have unspecified other impact via an incorrect boolean element in a wddxPacket XML document, leading to mishandling in a wddx_deserialize call.

Project: php-src

PHP_FUNCTION(wddx_serialize_value)
{
	zval *var;
	char *comment = NULL;
	int comment_len = 0;
	wddx_packet *packet;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|s", &var, &comment, &comment_len) == FAILURE) {
		return;
	}

	packet = php_wddx_constructor();

	php_wddx_packet_start(packet, comment, comment_len);
	php_wddx_serialize_var(packet, var, NULL, 0 TSRMLS_CC);
	php_wddx_packet_end(packet);

	ZVAL_STRINGL(return_value, packet->c, packet->len, 1);
	smart_str_free(packet);
	efree(packet);
}

Project: php-src

PHP_FUNCTION(wddx_serialize_vars)
{
	int num_args, i;
	wddx_packet *packet;
	zval ***args = NULL;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "+", &args, &num_args) == FAILURE) {
		return;
	}

	packet = php_wddx_constructor();

	php_wddx_packet_start(packet, NULL, 0);
	php_wddx_add_chunk_static(packet, WDDX_STRUCT_S);

	for (i=0; i<num_args; i++) {
		if (Z_TYPE_PP(args[i]) != IS_ARRAY && Z_TYPE_PP(args[i]) != IS_OBJECT) {
			convert_to_string_ex(args[i]);
		}
		php_wddx_add_var(packet, *args[i]);
	}

	php_wddx_add_chunk_static(packet, WDDX_STRUCT_E);
	php_wddx_packet_end(packet);

	efree(args);

	ZVAL_STRINGL(return_value, packet->c, packet->len, 1);
	smart_str_free(packet);
	efree(packet);
}

Project: php-src

PHP_FUNCTION(wddx_packet_start)
{
	char *comment = NULL;
	int comment_len = 0;
	wddx_packet *packet;

	comment = NULL;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &comment, &comment_len) == FAILURE) {
		return;
	}

	packet = php_wddx_constructor();

	php_wddx_packet_start(packet, comment, comment_len);
	php_wddx_add_chunk_static(packet, WDDX_STRUCT_S);

	ZEND_REGISTER_RESOURCE(return_value, packet, le_wddx);
}

Project: php-src

PHP_FUNCTION(wddx_packet_end)
{
	zval *packet_id;
	wddx_packet *packet = NULL;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &packet_id) == FAILURE) {
		return;
	}

	ZEND_FETCH_RESOURCE(packet, wddx_packet *, &packet_id, -1, "WDDX packet ID", le_wddx);

	php_wddx_add_chunk_static(packet, WDDX_STRUCT_E);

	php_wddx_packet_end(packet);

	ZVAL_STRINGL(return_value, packet->c, packet->len, 1);

	zend_list_delete(Z_LVAL_P(packet_id));
}

Project: php-src

PHP_FUNCTION(wddx_add_vars)
{
	int num_args, i;
	zval ***args = NULL;
	zval *packet_id;
	wddx_packet *packet = NULL;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r+", &packet_id, &args, &num_args) == FAILURE) {
		return;
	}

	if (!ZEND_FETCH_RESOURCE_NO_RETURN(packet, wddx_packet *, &packet_id, -1, "WDDX packet ID", le_wddx)) {
		efree(args);
		RETURN_FALSE;
	}

	if (!packet) {
		efree(args);
		RETURN_FALSE;
	}

	for (i=0; i<num_args; i++) {
		if (Z_TYPE_PP(args[i]) != IS_ARRAY && Z_TYPE_PP(args[i]) != IS_OBJECT) {
			convert_to_string_ex(args[i]);
		}
		php_wddx_add_var(packet, (*args[i]));
	}

	efree(args);
	RETURN_TRUE;
}

Project: php-src

PHP_FUNCTION(wddx_deserialize)
{
	zval *packet;
	char *payload;
	int payload_len;
	php_stream *stream = NULL;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &packet) == FAILURE) {
		return;
	}

	if (Z_TYPE_P(packet) == IS_STRING) {
		payload 	= Z_STRVAL_P(packet);
		payload_len = Z_STRLEN_P(packet);
	} else if (Z_TYPE_P(packet) == IS_RESOURCE) {
		php_stream_from_zval(stream, &packet);
		if (stream) {
			payload_len = php_stream_copy_to_mem(stream, &payload, PHP_STREAM_COPY_ALL, 0);
		}
	} else {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Expecting parameter 1 to be a string or a stream");
		return;
	}

	if (payload_len == 0) {
		return;
	}

	php_wddx_deserialize_ex(payload, payload_len, return_value);

	if (stream) {
		pefree(payload, 0);
	}
}

Project: php-src

wddx_packet *php_wddx_constructor(void)
{
	smart_str *packet;

	packet = (smart_str *)emalloc(sizeof(smart_str));
	packet->c = NULL;

	return packet;
}

Project: php-src

int php_wddx_deserialize_ex(char *value, int vallen, zval *return_value)
{
	wddx_stack stack;
	XML_Parser parser;
	st_entry *ent;
	int retval;

	wddx_stack_init(&stack);
	parser = XML_ParserCreate("UTF-8");

	XML_SetUserData(parser, &stack);
	XML_SetElementHandler(parser, php_wddx_push_element, php_wddx_pop_element);
	XML_SetCharacterDataHandler(parser, php_wddx_process_data);

	XML_Parse(parser, value, vallen, 1);

	XML_ParserFree(parser);

	if (stack.top == 1) {
		wddx_stack_top(&stack, (void**)&ent);
		if(ent->data == NULL) {
			retval = FAILURE;
		} else {
			*return_value = *(ent->data);
			zval_copy_ctor(return_value);
			retval = SUCCESS;
		}
	} else {
		retval = FAILURE;
	}

	wddx_stack_destroy(&stack);

	return retval;
}

Project: php-src

void php_wddx_destructor(wddx_packet *packet)
{
	smart_str_free(packet);
	efree(packet);
}

Project: php-src

static void php_wddx_process_data(void *user_data, const XML_Char *s, int len)
{
	st_entry *ent;
	wddx_stack *stack = (wddx_stack *)user_data;
	TSRMLS_FETCH();

	if (!wddx_stack_is_empty(stack) && !stack->done) {
		wddx_stack_top(stack, (void**)&ent);
		switch (ent->type) {
			case ST_STRING:
				if (Z_STRLEN_P(ent->data) == 0) {
					STR_FREE(Z_STRVAL_P(ent->data));
					Z_STRVAL_P(ent->data) = estrndup(s, len);
					Z_STRLEN_P(ent->data) = len;
				} else {
					Z_STRVAL_P(ent->data) = erealloc(Z_STRVAL_P(ent->data), Z_STRLEN_P(ent->data) + len + 1);
					memcpy(Z_STRVAL_P(ent->data) + Z_STRLEN_P(ent->data), s, len);
					Z_STRLEN_P(ent->data) += len;
					Z_STRVAL_P(ent->data)[Z_STRLEN_P(ent->data)] = '\0';
				}
				break;

			case ST_BINARY:
				if (Z_STRLEN_P(ent->data) == 0) {
					STR_FREE(Z_STRVAL_P(ent->data));
					Z_STRVAL_P(ent->data) = estrndup(s, len + 1);
				} else {
					Z_STRVAL_P(ent->data) = erealloc(Z_STRVAL_P(ent->data), Z_STRLEN_P(ent->data) + len + 1);
					memcpy(Z_STRVAL_P(ent->data) + Z_STRLEN_P(ent->data), s, len);
				}
				Z_STRLEN_P(ent->data) += len;
				Z_STRVAL_P(ent->data)[Z_STRLEN_P(ent->data)] = '\0';
				break;

			case ST_NUMBER:
				Z_TYPE_P(ent->data) = IS_STRING;
				Z_STRLEN_P(ent->data) = len;
				Z_STRVAL_P(ent->data) = estrndup(s, len);
				convert_scalar_to_number(ent->data TSRMLS_CC);
				break;

			case ST_BOOLEAN:
				if(!ent->data) {
					break;
				}
				if (!strcmp(s, "true")) {
					Z_LVAL_P(ent->data) = 1;
				} else if (!strcmp(s, "false")) {
					Z_LVAL_P(ent->data) = 0;
				} else {
					zval_ptr_dtor(&ent->data);
					if (ent->varname) {
						efree(ent->varname);
						ent->varname = NULL;
					}
					ent->data = NULL;
				}
				break;

			case ST_DATETIME: {
				char *tmp;

				if (Z_TYPE_P(ent->data) == IS_STRING) {
					tmp = safe_emalloc(Z_STRLEN_P(ent->data), 1, (size_t)len + 1);
					memcpy(tmp, Z_STRVAL_P(ent->data), Z_STRLEN_P(ent->data));
					memcpy(tmp + Z_STRLEN_P(ent->data), s, len);
					len += Z_STRLEN_P(ent->data);
					efree(Z_STRVAL_P(ent->data));
					Z_TYPE_P(ent->data) = IS_LONG;
				} else {
					tmp = emalloc(len + 1);
					memcpy(tmp, s, len);
				}
				tmp[len] = '\0';

				Z_LVAL_P(ent->data) = php_parse_date(tmp, NULL);
				/* date out of range < 1969 or > 2038 */
				if (Z_LVAL_P(ent->data) == -1) {
					ZVAL_STRINGL(ent->data, tmp, len, 0);
				} else {
					efree(tmp);
				}
			}
				break;

			default:
				break;
		}
	}
}