Skip to content

Commit 23ffbd9

Browse files
committed
Fix #97836: Segfault in concat_function
The following sequence of actions was happening which caused a null pointer dereference: 1. debug_backtrace() returns an array 2. The concatenation to $c will transform the array to a string via `zval_get_string_func` for op2 and output a warning. Note that zval op1 is of type string due to the first do-while sequence. 3. The warning of an implicit "array to string conversion" triggers the ob_start callback to run. This code transform $c (==op1) to a long. 4. The code below the 2 do-while sequences assume that both op1 and op2 are strings, but this is no longer the case. A dereference of the string will therefore result in a null pointer dereference. The solution used here is to work with the zend_string directly instead of with the ops.
1 parent e0af7c3 commit 23ffbd9

File tree

1 file changed

+78
-45
lines changed

1 file changed

+78
-45
lines changed

Zend/zend_operators.c

Lines changed: 78 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1939,109 +1939,142 @@ ZEND_API zend_result ZEND_FASTCALL shift_right_function(zval *result, zval *op1,
19391939

19401940
ZEND_API zend_result ZEND_FASTCALL concat_function(zval *result, zval *op1, zval *op2) /* {{{ */
19411941
{
1942-
zval *orig_op1 = op1;
1943-
zval op1_copy, op2_copy;
1944-
1945-
ZVAL_UNDEF(&op1_copy);
1946-
ZVAL_UNDEF(&op2_copy);
1942+
zval *orig_op1 = op1;
1943+
zend_string *op1_string, *op2_string = NULL;
1944+
bool free_op1_string = false;
1945+
bool free_op2_string = false;
19471946

19481947
do {
1949-
if (UNEXPECTED(Z_TYPE_P(op1) != IS_STRING)) {
1948+
if (EXPECTED(Z_TYPE_P(op1) == IS_STRING)) {
1949+
op1_string = Z_STR_P(op1);
1950+
} else {
19501951
if (Z_ISREF_P(op1)) {
19511952
op1 = Z_REFVAL_P(op1);
1952-
if (Z_TYPE_P(op1) == IS_STRING) break;
1953+
if (Z_TYPE_P(op1) == IS_STRING) {
1954+
op1_string = Z_STR_P(op1);
1955+
break;
1956+
}
19531957
}
19541958
ZEND_TRY_BINARY_OBJECT_OPERATION(ZEND_CONCAT);
1955-
ZVAL_STR(&op1_copy, zval_get_string_func(op1));
1959+
op1_string = zval_get_string_func(op1);
19561960
if (UNEXPECTED(EG(exception))) {
1957-
zval_ptr_dtor_str(&op1_copy);
1961+
zend_string_release(op1_string);
19581962
if (orig_op1 != result) {
19591963
ZVAL_UNDEF(result);
19601964
}
19611965
return FAILURE;
19621966
}
1967+
free_op1_string = true;
19631968
if (result == op1) {
19641969
if (UNEXPECTED(op1 == op2)) {
1965-
op2 = &op1_copy;
1970+
op2_string = op1_string;
19661971
}
19671972
}
1968-
op1 = &op1_copy;
19691973
}
19701974
} while (0);
19711975
do {
1972-
if (UNEXPECTED(Z_TYPE_P(op2) != IS_STRING)) {
1973-
if (Z_ISREF_P(op2)) {
1974-
op2 = Z_REFVAL_P(op2);
1975-
if (Z_TYPE_P(op2) == IS_STRING) break;
1976-
}
1977-
ZEND_TRY_BINARY_OP2_OBJECT_OPERATION(ZEND_CONCAT);
1978-
ZVAL_STR(&op2_copy, zval_get_string_func(op2));
1979-
if (UNEXPECTED(EG(exception))) {
1980-
zval_ptr_dtor_str(&op1_copy);
1981-
zval_ptr_dtor_str(&op2_copy);
1982-
if (orig_op1 != result) {
1983-
ZVAL_UNDEF(result);
1976+
if (!op2_string) {
1977+
if (EXPECTED(Z_TYPE_P(op2) == IS_STRING)) {
1978+
op2_string = Z_STR_P(op2);
1979+
} else {
1980+
if (Z_ISREF_P(op2)) {
1981+
op2 = Z_REFVAL_P(op2);
1982+
if (Z_TYPE_P(op2) == IS_STRING) {
1983+
op2_string = Z_STR_P(op2);
1984+
break;
1985+
}
19841986
}
1985-
return FAILURE;
1987+
/* hold an additional reference because a userland function could free this */
1988+
if (!free_op1_string) {
1989+
op1_string = zend_string_copy(op1_string);
1990+
free_op1_string = true;
1991+
}
1992+
ZEND_TRY_BINARY_OP2_OBJECT_OPERATION(ZEND_CONCAT);
1993+
op2_string = zval_get_string_func(op2);
1994+
if (UNEXPECTED(EG(exception))) {
1995+
zend_string_release(op1_string);
1996+
zend_string_release(op2_string);
1997+
if (orig_op1 != result) {
1998+
ZVAL_UNDEF(result);
1999+
}
2000+
return FAILURE;
2001+
}
2002+
free_op2_string = true;
19862003
}
1987-
op2 = &op2_copy;
19882004
}
19892005
} while (0);
19902006

1991-
if (UNEXPECTED(Z_STRLEN_P(op1) == 0)) {
1992-
if (EXPECTED(result != op2)) {
2007+
if (UNEXPECTED(ZSTR_LEN(op1_string) == 0)) {
2008+
if (EXPECTED(free_op2_string || result != op2)) {
19932009
if (result == orig_op1) {
19942010
i_zval_ptr_dtor(result);
19952011
}
1996-
ZVAL_COPY(result, op2);
2012+
if (free_op2_string) {
2013+
ZVAL_STR(result, op2_string);
2014+
free_op2_string = false;
2015+
} else {
2016+
ZVAL_STR_COPY(result, op2_string);
2017+
}
19972018
}
1998-
} else if (UNEXPECTED(Z_STRLEN_P(op2) == 0)) {
1999-
if (EXPECTED(result != op1)) {
2019+
} else if (UNEXPECTED(ZSTR_LEN(op2_string) == 0)) {
2020+
if (EXPECTED(free_op1_string || result != op1)) {
20002021
if (result == orig_op1) {
20012022
i_zval_ptr_dtor(result);
20022023
}
2003-
ZVAL_COPY(result, op1);
2024+
if (free_op1_string) {
2025+
ZVAL_STR(result, op1_string);
2026+
free_op1_string = false;
2027+
} else {
2028+
ZVAL_STR_COPY(result, op1_string);
2029+
}
20042030
}
20052031
} else {
2006-
size_t op1_len = Z_STRLEN_P(op1);
2007-
size_t op2_len = Z_STRLEN_P(op2);
2032+
size_t op1_len = ZSTR_LEN(op1_string);
2033+
size_t op2_len = ZSTR_LEN(op2_string);
20082034
size_t result_len = op1_len + op2_len;
20092035
zend_string *result_str;
20102036
uint32_t flags = ZSTR_GET_COPYABLE_CONCAT_PROPERTIES_BOTH(Z_STR_P(op1), Z_STR_P(op2));
20112037

20122038
if (UNEXPECTED(op1_len > ZSTR_MAX_LEN - op2_len)) {
2039+
if (free_op1_string) zend_string_release(op1_string);
2040+
if (free_op2_string) zend_string_release(op2_string);
20132041
zend_throw_error(NULL, "String size overflow");
2014-
zval_ptr_dtor_str(&op1_copy);
2015-
zval_ptr_dtor_str(&op2_copy);
20162042
if (orig_op1 != result) {
20172043
ZVAL_UNDEF(result);
20182044
}
20192045
return FAILURE;
20202046
}
20212047

2022-
if (result == op1 && Z_REFCOUNTED_P(result)) {
2048+
if (result == op1) {
2049+
if (free_op1_string) {
2050+
i_zval_ptr_dtor(result);
2051+
free_op1_string = false;
2052+
}
20232053
/* special case, perform operations on result */
2024-
result_str = zend_string_extend(Z_STR_P(result), result_len, 0);
2054+
result_str = zend_string_extend(op1_string, result_len, 0);
2055+
/* account for the case where result_str == op1_string == op2_string and the realloc is done */
2056+
if (op1_string == op2_string) {
2057+
if (free_op2_string) zend_string_release(op2_string);
2058+
op2_string = result_str;
2059+
free_op2_string = false;
2060+
}
20252061
} else {
20262062
result_str = zend_string_alloc(result_len, 0);
2027-
memcpy(ZSTR_VAL(result_str), Z_STRVAL_P(op1), op1_len);
2063+
memcpy(ZSTR_VAL(result_str), ZSTR_VAL(op1_string), op1_len);
20282064
if (result == orig_op1) {
20292065
i_zval_ptr_dtor(result);
20302066
}
20312067
}
20322068
GC_ADD_FLAGS(result_str, flags);
20332069

2034-
/* This has to happen first to account for the cases where result == op1 == op2 and
2035-
* the realloc is done. In this case this line will also update Z_STRVAL_P(op2) to
2036-
* point to the new string. The first op2_len bytes of result will still be the same. */
20372070
ZVAL_NEW_STR(result, result_str);
2038-
2039-
memcpy(ZSTR_VAL(result_str) + op1_len, Z_STRVAL_P(op2), op2_len);
2071+
memcpy(ZSTR_VAL(result_str) + op1_len, ZSTR_VAL(op2_string), op2_len);
20402072
ZSTR_VAL(result_str)[result_len] = '\0';
20412073
}
20422074

2043-
zval_ptr_dtor_str(&op1_copy);
2044-
zval_ptr_dtor_str(&op2_copy);
2075+
if (free_op1_string) zend_string_release(op1_string);
2076+
if (free_op2_string) zend_string_release(op2_string);
2077+
20452078
return SUCCESS;
20462079
}
20472080
/* }}} */

0 commit comments

Comments
 (0)