Skip to content

Commit f407f39

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 f407f39

File tree

1 file changed

+76
-44
lines changed

1 file changed

+76
-44
lines changed

Zend/zend_operators.c

Lines changed: 76 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1840,106 +1840,138 @@ 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_is_non_string_or_copy = false;
1845+
bool op2_is_non_string_or_copy = false;
18471846

18481847
do {
1849-
if (UNEXPECTED(Z_TYPE_P(op1) != IS_STRING)) {
1848+
if (EXPECTED(Z_TYPE_P(op1) == IS_STRING)) {
1849+
op1_string = Z_STR_P(op1);
1850+
} else {
18501851
if (Z_ISREF_P(op1)) {
18511852
op1 = Z_REFVAL_P(op1);
1852-
if (Z_TYPE_P(op1) == IS_STRING) break;
1853+
if (Z_TYPE_P(op1) == IS_STRING) {
1854+
op1_string = Z_STR_P(op1);
1855+
break;
1856+
}
18531857
}
18541858
ZEND_TRY_BINARY_OBJECT_OPERATION(ZEND_CONCAT);
1855-
ZVAL_STR(&op1_copy, zval_get_string_func(op1));
1859+
op1_string = zval_get_string_func(op1);
18561860
if (UNEXPECTED(EG(exception))) {
1857-
zval_ptr_dtor_str(&op1_copy);
1861+
zend_string_release(op1_string);
18581862
if (orig_op1 != result) {
18591863
ZVAL_UNDEF(result);
18601864
}
18611865
return FAILURE;
18621866
}
1867+
op1_is_non_string_or_copy = true;
18631868
if (result == op1) {
18641869
if (UNEXPECTED(op1 == op2)) {
1865-
op2 = &op1_copy;
1870+
op2_string = op1_string;
18661871
}
18671872
}
1868-
op1 = &op1_copy;
18691873
}
18701874
} while (0);
18711875
do {
1872-
if (UNEXPECTED(Z_TYPE_P(op2) != IS_STRING)) {
1873-
if (Z_ISREF_P(op2)) {
1874-
op2 = Z_REFVAL_P(op2);
1875-
if (Z_TYPE_P(op2) == IS_STRING) break;
1876-
}
1877-
ZEND_TRY_BINARY_OP2_OBJECT_OPERATION(ZEND_CONCAT);
1878-
ZVAL_STR(&op2_copy, zval_get_string_func(op2));
1879-
if (UNEXPECTED(EG(exception))) {
1880-
zval_ptr_dtor_str(&op1_copy);
1881-
zval_ptr_dtor_str(&op2_copy);
1882-
if (orig_op1 != result) {
1883-
ZVAL_UNDEF(result);
1876+
if (!op2_string) {
1877+
if (EXPECTED(Z_TYPE_P(op2) == IS_STRING)) {
1878+
op2_string = Z_STR_P(op2);
1879+
} else {
1880+
if (Z_ISREF_P(op2)) {
1881+
op2 = Z_REFVAL_P(op2);
1882+
if (Z_TYPE_P(op2) == IS_STRING) {
1883+
op2_string = Z_STR_P(op2);
1884+
break;
1885+
}
18841886
}
1885-
return FAILURE;
1887+
/* hold an additional reference because a userland function could free this */
1888+
if (!op1_is_non_string_or_copy) {
1889+
op1_string = zend_string_copy(op1_string);
1890+
op1_is_non_string_or_copy = true;
1891+
}
1892+
ZEND_TRY_BINARY_OP2_OBJECT_OPERATION(ZEND_CONCAT);
1893+
op2_string = zval_get_string_func(op2);
1894+
if (UNEXPECTED(EG(exception))) {
1895+
zend_string_release(op1_string);
1896+
zend_string_release(op2_string);
1897+
if (orig_op1 != result) {
1898+
ZVAL_UNDEF(result);
1899+
}
1900+
return FAILURE;
1901+
}
1902+
op2_is_non_string_or_copy = true;
18861903
}
1887-
op2 = &op2_copy;
18881904
}
18891905
} while (0);
18901906

1891-
if (UNEXPECTED(Z_STRLEN_P(op1) == 0)) {
1892-
if (EXPECTED(result != op2)) {
1907+
if (UNEXPECTED(ZSTR_LEN(op1_string) == 0)) {
1908+
if (EXPECTED(op2_is_non_string_or_copy || result != op2)) {
18931909
if (result == orig_op1) {
18941910
i_zval_ptr_dtor(result);
18951911
}
1896-
ZVAL_COPY(result, op2);
1912+
if (op2_is_non_string_or_copy) {
1913+
ZVAL_STR(result, op2_string);
1914+
op2_is_non_string_or_copy = false;
1915+
} else {
1916+
ZVAL_STR_COPY(result, op2_string);
1917+
}
18971918
}
1898-
} else if (UNEXPECTED(Z_STRLEN_P(op2) == 0)) {
1899-
if (EXPECTED(result != op1)) {
1919+
} else if (UNEXPECTED(ZSTR_LEN(op2_string) == 0)) {
1920+
if (EXPECTED(op1_is_non_string_or_copy || result != op1)) {
19001921
if (result == orig_op1) {
19011922
i_zval_ptr_dtor(result);
19021923
}
1903-
ZVAL_COPY(result, op1);
1924+
if (op1_is_non_string_or_copy) {
1925+
ZVAL_STR(result, op1_string);
1926+
op1_is_non_string_or_copy = false;
1927+
} else {
1928+
ZVAL_STR_COPY(result, op1_string);
1929+
}
19041930
}
19051931
} else {
1906-
size_t op1_len = Z_STRLEN_P(op1);
1907-
size_t op2_len = Z_STRLEN_P(op2);
1932+
size_t op1_len = ZSTR_LEN(op1_string);
1933+
size_t op2_len = ZSTR_LEN(op2_string);
19081934
size_t result_len = op1_len + op2_len;
19091935
zend_string *result_str;
19101936

19111937
if (UNEXPECTED(op1_len > ZSTR_MAX_LEN - op2_len)) {
1938+
if (op1_is_non_string_or_copy) zend_string_release(op1_string);
1939+
if (op2_is_non_string_or_copy) zend_string_release(op2_string);
19121940
zend_throw_error(NULL, "String size overflow");
1913-
zval_ptr_dtor_str(&op1_copy);
1914-
zval_ptr_dtor_str(&op2_copy);
19151941
if (orig_op1 != result) {
19161942
ZVAL_UNDEF(result);
19171943
}
19181944
return FAILURE;
19191945
}
19201946

1921-
if (result == op1 && Z_REFCOUNTED_P(result)) {
1947+
if (result == op1) {
1948+
if (op1_is_non_string_or_copy) {
1949+
i_zval_ptr_dtor(result);
1950+
op1_is_non_string_or_copy = false;
1951+
}
19221952
/* special case, perform operations on result */
1923-
result_str = zend_string_extend(Z_STR_P(result), result_len, 0);
1953+
result_str = zend_string_extend(op1_string, result_len, 0);
1954+
/* account for the case where result == op1 == op2 and the realloc is done */
1955+
if (op1_string == op2_string) {
1956+
op2_string = result_str;
1957+
op2_is_non_string_or_copy = false;
1958+
}
19241959
} else {
19251960
result_str = zend_string_alloc(result_len, 0);
1926-
memcpy(ZSTR_VAL(result_str), Z_STRVAL_P(op1), op1_len);
1961+
memcpy(ZSTR_VAL(result_str), ZSTR_VAL(op1_string), op1_len);
19271962
if (result == orig_op1) {
19281963
i_zval_ptr_dtor(result);
19291964
}
19301965
}
19311966

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. */
19351967
ZVAL_NEW_STR(result, result_str);
1936-
1937-
memcpy(ZSTR_VAL(result_str) + op1_len, Z_STRVAL_P(op2), op2_len);
1968+
memcpy(ZSTR_VAL(result_str) + op1_len, ZSTR_VAL(op2_string), op2_len);
19381969
ZSTR_VAL(result_str)[result_len] = '\0';
19391970
}
19401971

1941-
zval_ptr_dtor_str(&op1_copy);
1942-
zval_ptr_dtor_str(&op2_copy);
1972+
if (op1_is_non_string_or_copy) zend_string_release(op1_string);
1973+
if (op2_is_non_string_or_copy) zend_string_release(op2_string);
1974+
19431975
return SUCCESS;
19441976
}
19451977
/* }}} */

0 commit comments

Comments
 (0)