Skip to content

Commit 92b6bce

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 adc2382 commit 92b6bce

File tree

1 file changed

+48
-34
lines changed

1 file changed

+48
-34
lines changed

Zend/zend_operators.c

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1840,78 +1840,85 @@ ZEND_API zend_result ZEND_FASTCALL shift_right_function(zval *result, zval *op1,
18401840
ZEND_API zend_result ZEND_FASTCALL concat_function(zval *result, zval *op1, zval *op2) /* {{{ */
18411841
{
18421842
zval *orig_op1 = op1;
1843-
zval op1_copy, op2_copy;
1844-
1845-
ZVAL_UNDEF(&op1_copy);
1846-
ZVAL_UNDEF(&op2_copy);
1843+
zend_string *op1_string, *op2_string = NULL;
1844+
bool op1_string_from_func = false;
1845+
bool op2_string_from_func = false;
18471846

18481847
do {
18491848
if (UNEXPECTED(Z_TYPE_P(op1) != IS_STRING)) {
18501849
if (Z_ISREF_P(op1)) {
18511850
op1 = Z_REFVAL_P(op1);
1852-
if (Z_TYPE_P(op1) == IS_STRING) break;
1851+
if (Z_TYPE_P(op1) == IS_STRING) {
1852+
op1_string = Z_STR_P(op1);
1853+
break;
1854+
}
18531855
}
18541856
ZEND_TRY_BINARY_OBJECT_OPERATION(ZEND_CONCAT);
1855-
ZVAL_STR(&op1_copy, zval_get_string_func(op1));
1857+
op1_string = zval_get_string_func(op1);
18561858
if (UNEXPECTED(EG(exception))) {
1857-
zval_ptr_dtor_str(&op1_copy);
18581859
if (orig_op1 != result) {
18591860
ZVAL_UNDEF(result);
18601861
}
18611862
return FAILURE;
18621863
}
1864+
op1_string_from_func = true;
18631865
if (result == op1) {
18641866
if (UNEXPECTED(op1 == op2)) {
1865-
op2 = &op1_copy;
1867+
op2_string = op1_string;
18661868
}
18671869
}
1868-
op1 = &op1_copy;
1869-
}
1870+
} else {
1871+
op1_string = Z_STR_P(op1);
1872+
}
18701873
} while (0);
18711874
do {
1872-
if (UNEXPECTED(Z_TYPE_P(op2) != IS_STRING)) {
1875+
if (UNEXPECTED(!op2_string && Z_TYPE_P(op2) != IS_STRING)) {
18731876
if (Z_ISREF_P(op2)) {
18741877
op2 = Z_REFVAL_P(op2);
1875-
if (Z_TYPE_P(op2) == IS_STRING) break;
1878+
if (Z_TYPE_P(op2) == IS_STRING) {
1879+
op2_string = Z_STR_P(op2);
1880+
break;
1881+
}
18761882
}
18771883
ZEND_TRY_BINARY_OP2_OBJECT_OPERATION(ZEND_CONCAT);
1878-
ZVAL_STR(&op2_copy, zval_get_string_func(op2));
1884+
op2_string = zval_get_string_func(op2);
18791885
if (UNEXPECTED(EG(exception))) {
1880-
zval_ptr_dtor_str(&op1_copy);
1881-
zval_ptr_dtor_str(&op2_copy);
1886+
if (op1_string_from_func) zend_string_release(op1_string);
18821887
if (orig_op1 != result) {
18831888
ZVAL_UNDEF(result);
18841889
}
18851890
return FAILURE;
18861891
}
1887-
op2 = &op2_copy;
1892+
op2_string_from_func = true;
1893+
} else if (!op2_string) {
1894+
op2_string = Z_STR_P(op2);
18881895
}
18891896
} while (0);
18901897

1891-
if (UNEXPECTED(Z_STRLEN_P(op1) == 0)) {
1892-
if (EXPECTED(result != op2)) {
1898+
if (UNEXPECTED(ZSTR_LEN(op1_string) == 0)) {
1899+
if (EXPECTED(op2_string_from_func || result != op2)) {
18931900
if (result == orig_op1) {
18941901
i_zval_ptr_dtor(result);
18951902
}
1896-
ZVAL_COPY(result, op2);
1903+
ZVAL_STR_COPY(result, op2_string);
18971904
}
1898-
} else if (UNEXPECTED(Z_STRLEN_P(op2) == 0)) {
1899-
if (EXPECTED(result != op1)) {
1905+
} else if (UNEXPECTED(ZSTR_LEN(op2_string) == 0)) {
1906+
if (EXPECTED(op1_string_from_func || result != op1)) {
19001907
if (result == orig_op1) {
19011908
i_zval_ptr_dtor(result);
19021909
}
1903-
ZVAL_COPY(result, op1);
1910+
ZVAL_STR_COPY(result, op1_string);
19041911
}
19051912
} else {
1906-
size_t op1_len = Z_STRLEN_P(op1);
1907-
size_t op2_len = Z_STRLEN_P(op2);
1913+
size_t op1_len = ZSTR_LEN(op1_string);
1914+
size_t op2_len = ZSTR_LEN(op2_string);
19081915
size_t result_len = op1_len + op2_len;
19091916
zend_string *result_str;
19101917

19111918
if (UNEXPECTED(op1_len > ZSTR_MAX_LEN - op2_len)) {
1919+
if (op1_string_from_func) zend_string_release(op1_string);
1920+
if (op2_string_from_func) zend_string_release(op2_string);
19121921
zend_throw_error(NULL, "String size overflow");
1913-
zval_ptr_dtor_str(&op1_copy);
1914-
zval_ptr_dtor_str(&op2_copy);
19151922
if (orig_op1 != result) {
19161923
ZVAL_UNDEF(result);
19171924
}
@@ -1920,26 +1927,33 @@ ZEND_API zend_result ZEND_FASTCALL concat_function(zval *result, zval *op1, zval
19201927

19211928
if (result == op1 && Z_REFCOUNTED_P(result)) {
19221929
/* special case, perform operations on result */
1923-
result_str = zend_string_extend(Z_STR_P(result), result_len, 0);
1930+
result_str = zend_string_extend(op1_string, result_len, 0);
1931+
/* account for the case where result == op1 == op2 and the realloc is done */
1932+
if (op1_string == op2_string) {
1933+
op2_string = result_str;
1934+
}
1935+
if (op1_string_from_func) {
1936+
i_zval_ptr_dtor(result);
1937+
/* we will reuse the memory of op1_string */
1938+
op1_string_from_func = false;
1939+
}
19241940
} else {
19251941
result_str = zend_string_alloc(result_len, 0);
1926-
memcpy(ZSTR_VAL(result_str), Z_STRVAL_P(op1), op1_len);
1942+
memcpy(ZSTR_VAL(result_str), ZSTR_VAL(op1_string), op1_len);
19271943
if (result == orig_op1) {
19281944
i_zval_ptr_dtor(result);
19291945
}
19301946
}
19311947

1932-
/* This has to happen first to account for the cases where result == op1 == op2 and
1933-
* the realloc is done. In this case this line will also update Z_STRVAL_P(op2) to
1934-
* point to the new string. The first op2_len bytes of result will still be the same. */
19351948
ZVAL_NEW_STR(result, result_str);
19361949

1937-
memcpy(ZSTR_VAL(result_str) + op1_len, Z_STRVAL_P(op2), op2_len);
1950+
memcpy(ZSTR_VAL(result_str) + op1_len, ZSTR_VAL(op2_string), op2_len);
19381951
ZSTR_VAL(result_str)[result_len] = '\0';
19391952
}
19401953

1941-
zval_ptr_dtor_str(&op1_copy);
1942-
zval_ptr_dtor_str(&op2_copy);
1954+
if (op1_string_from_func) zend_string_release(op1_string);
1955+
if (op2_string_from_func) zend_string_release(op2_string);
1956+
19431957
return SUCCESS;
19441958
}
19451959
/* }}} */

0 commit comments

Comments
 (0)