Skip to content

Commit 9786eac

Browse files
committed
Merge branch 'PHP-8.0' into PHP-8.1
* PHP-8.0: Fix clobering of operand by error handler in assignment to string offset
2 parents 10cfe9f + 09547c6 commit 9786eac

File tree

6 files changed

+177
-82
lines changed

6 files changed

+177
-82
lines changed

Zend/tests/str_offset_006.phpt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
string offset 006 indirect string modification by error handler
3+
--FILE--
4+
<?php
5+
set_error_handler(function($code, $msg) {
6+
echo "Err: $msg\n";
7+
$GLOBALS['a']=null;
8+
});
9+
$a[$y]=$a.=($y);
10+
var_dump($a);
11+
?>
12+
--EXPECT--
13+
Err: Undefined variable $y
14+
Err: Undefined variable $y
15+
Err: String offset cast occurred
16+
NULL

Zend/tests/str_offset_007.phpt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
string offset 007 indirect string modification by error handler
3+
--FILE--
4+
<?php
5+
set_error_handler(function($code, $msg) {
6+
echo "Err: $msg\n";
7+
$GLOBALS['a']='';
8+
});
9+
$a=['a'];
10+
$a[0][$d]='b';
11+
var_dump($a);
12+
?>
13+
--EXPECT--
14+
Err: Undefined variable $d
15+
Err: String offset cast occurred
16+
string(0) ""

Zend/zend_execute.c

Lines changed: 106 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1498,45 +1498,41 @@ static zend_never_inline zend_long zend_check_string_offset(zval *dim, int type
14981498
zend_long offset;
14991499

15001500
try_again:
1501-
if (UNEXPECTED(Z_TYPE_P(dim) != IS_LONG)) {
1502-
switch(Z_TYPE_P(dim)) {
1503-
case IS_STRING:
1504-
{
1505-
bool trailing_data = false;
1506-
/* For BC reasons we allow errors so that we can warn on leading numeric string */
1507-
if (IS_LONG == is_numeric_string_ex(Z_STRVAL_P(dim), Z_STRLEN_P(dim), &offset, NULL,
1508-
/* allow errors */ true, NULL, &trailing_data)) {
1509-
if (UNEXPECTED(trailing_data) && type != BP_VAR_UNSET) {
1510-
zend_error(E_WARNING, "Illegal string offset \"%s\"", Z_STRVAL_P(dim));
1511-
}
1512-
return offset;
1501+
switch(Z_TYPE_P(dim)) {
1502+
case IS_LONG:
1503+
return Z_LVAL_P(dim);
1504+
case IS_STRING:
1505+
{
1506+
bool trailing_data = false;
1507+
/* For BC reasons we allow errors so that we can warn on leading numeric string */
1508+
if (IS_LONG == is_numeric_string_ex(Z_STRVAL_P(dim), Z_STRLEN_P(dim), &offset, NULL,
1509+
/* allow errors */ true, NULL, &trailing_data)) {
1510+
if (UNEXPECTED(trailing_data) && type != BP_VAR_UNSET) {
1511+
zend_error(E_WARNING, "Illegal string offset \"%s\"", Z_STRVAL_P(dim));
15131512
}
1514-
zend_illegal_string_offset(dim);
1515-
return 0;
1513+
return offset;
15161514
}
1517-
case IS_UNDEF:
1518-
ZVAL_UNDEFINED_OP2();
1519-
ZEND_FALLTHROUGH;
1520-
case IS_DOUBLE:
1521-
case IS_NULL:
1522-
case IS_FALSE:
1523-
case IS_TRUE:
1524-
zend_error(E_WARNING, "String offset cast occurred");
1525-
break;
1526-
case IS_REFERENCE:
1527-
dim = Z_REFVAL_P(dim);
1528-
goto try_again;
1529-
default:
1530-
zend_illegal_string_offset(dim);
1531-
return 0;
1515+
zend_illegal_string_offset(dim);
1516+
return 0;
15321517
}
1533-
1534-
offset = zval_get_long_func(dim, /* is_strict */ false);
1535-
} else {
1536-
offset = Z_LVAL_P(dim);
1518+
case IS_UNDEF:
1519+
ZVAL_UNDEFINED_OP2();
1520+
ZEND_FALLTHROUGH;
1521+
case IS_DOUBLE:
1522+
case IS_NULL:
1523+
case IS_FALSE:
1524+
case IS_TRUE:
1525+
zend_error(E_WARNING, "String offset cast occurred");
1526+
break;
1527+
case IS_REFERENCE:
1528+
dim = Z_REFVAL_P(dim);
1529+
goto try_again;
1530+
default:
1531+
zend_illegal_string_offset(dim);
1532+
return 0;
15371533
}
15381534

1539-
return offset;
1535+
return zval_get_long_func(dim, /* is_strict */ false);
15401536
}
15411537

15421538
ZEND_API ZEND_COLD void zend_wrong_string_offset_error(void)
@@ -1672,17 +1668,43 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim,
16721668
zend_uchar c;
16731669
size_t string_len;
16741670
zend_long offset;
1671+
zend_string *s;
16751672

1676-
offset = zend_check_string_offset(dim, BP_VAR_W EXECUTE_DATA_CC);
1677-
/* Illegal offset assignment */
1678-
if (UNEXPECTED(EG(exception) != NULL)) {
1679-
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
1680-
ZVAL_UNDEF(EX_VAR(opline->result.var));
1673+
/* separate string */
1674+
if (Z_REFCOUNTED_P(str) && Z_REFCOUNT_P(str) == 1) {
1675+
s = Z_STR_P(str);
1676+
} else {
1677+
s = zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0);
1678+
ZSTR_H(s) = ZSTR_H(Z_STR_P(str));
1679+
ZVAL_NEW_STR(str, s);
1680+
}
1681+
1682+
if (EXPECTED(Z_TYPE_P(dim) == IS_LONG)) {
1683+
offset = Z_LVAL_P(dim);
1684+
} else {
1685+
/* The string may be destroyed while throwing the notice.
1686+
* Temporarily increase the refcount to detect this situation. */
1687+
if (!(GC_FLAGS(s) & IS_ARRAY_IMMUTABLE)) {
1688+
GC_ADDREF(s);
1689+
}
1690+
offset = zend_check_string_offset(dim, BP_VAR_W EXECUTE_DATA_CC);
1691+
if (!(GC_FLAGS(s) & IS_ARRAY_IMMUTABLE) && GC_DELREF(s) == 0) {
1692+
zend_string_efree(s);
1693+
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
1694+
ZVAL_NULL(EX_VAR(opline->result.var));
1695+
}
1696+
return;
1697+
}
1698+
/* Illegal offset assignment */
1699+
if (UNEXPECTED(EG(exception) != NULL)) {
1700+
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
1701+
ZVAL_UNDEF(EX_VAR(opline->result.var));
1702+
}
1703+
return;
16811704
}
1682-
return;
16831705
}
16841706

1685-
if (offset < -(zend_long)Z_STRLEN_P(str)) {
1707+
if (UNEXPECTED(offset < -(zend_long)ZSTR_LEN(s))) {
16861708
/* Error on negative offset */
16871709
zend_error(E_WARNING, "Illegal string offset " ZEND_LONG_FMT, offset);
16881710
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
@@ -1691,9 +1713,28 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim,
16911713
return;
16921714
}
16931715

1694-
if (Z_TYPE_P(value) != IS_STRING) {
1716+
if (offset < 0) { /* Handle negative offset */
1717+
offset += (zend_long)ZSTR_LEN(s);
1718+
}
1719+
1720+
if (UNEXPECTED(Z_TYPE_P(value) != IS_STRING)) {
1721+
/* The string may be destroyed while throwing the notice.
1722+
* Temporarily increase the refcount to detect this situation. */
1723+
if (!(GC_FLAGS(s) & IS_ARRAY_IMMUTABLE)) {
1724+
GC_ADDREF(s);
1725+
}
1726+
if (UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) {
1727+
zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC);
1728+
}
16951729
/* Convert to string, just the time to pick the 1st byte */
16961730
zend_string *tmp = zval_try_get_string_func(value);
1731+
if (!(GC_FLAGS(s) & IS_ARRAY_IMMUTABLE) && GC_DELREF(s) == 0) {
1732+
zend_string_efree(s);
1733+
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
1734+
ZVAL_NULL(EX_VAR(opline->result.var));
1735+
}
1736+
return;
1737+
}
16971738
if (UNEXPECTED(!tmp)) {
16981739
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
16991740
ZVAL_UNDEF(EX_VAR(opline->result.var));
@@ -1709,7 +1750,7 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim,
17091750
c = (zend_uchar)Z_STRVAL_P(value)[0];
17101751
}
17111752

1712-
if (string_len != 1) {
1753+
if (UNEXPECTED(string_len != 1)) {
17131754
if (string_len == 0) {
17141755
/* Error on empty input string */
17151756
zend_throw_error(NULL, "Cannot assign an empty string to a string offset");
@@ -1719,24 +1760,34 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim,
17191760
return;
17201761
}
17211762

1763+
/* The string may be destroyed while throwing the notice.
1764+
* Temporarily increase the refcount to detect this situation. */
1765+
if (!(GC_FLAGS(s) & IS_ARRAY_IMMUTABLE)) {
1766+
GC_ADDREF(s);
1767+
}
17221768
zend_error(E_WARNING, "Only the first byte will be assigned to the string offset");
1769+
if (!(GC_FLAGS(s) & IS_ARRAY_IMMUTABLE) && GC_DELREF(s) == 0) {
1770+
zend_string_efree(s);
1771+
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
1772+
ZVAL_NULL(EX_VAR(opline->result.var));
1773+
}
1774+
return;
1775+
}
1776+
/* Illegal offset assignment */
1777+
if (UNEXPECTED(EG(exception) != NULL)) {
1778+
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
1779+
ZVAL_UNDEF(EX_VAR(opline->result.var));
1780+
}
1781+
return;
1782+
}
17231783
}
17241784

1725-
if (offset < 0) { /* Handle negative offset */
1726-
offset += (zend_long)Z_STRLEN_P(str);
1727-
}
1728-
1729-
if ((size_t)offset >= Z_STRLEN_P(str)) {
1785+
if ((size_t)offset >= ZSTR_LEN(s)) {
17301786
/* Extend string if needed */
1731-
zend_long old_len = Z_STRLEN_P(str);
1732-
ZVAL_NEW_STR(str, zend_string_extend(Z_STR_P(str), (size_t)offset + 1, 0));
1787+
zend_long old_len = ZSTR_LEN(s);
1788+
ZVAL_NEW_STR(str, zend_string_extend(s, (size_t)offset + 1, 0));
17331789
memset(Z_STRVAL_P(str) + old_len, ' ', offset - old_len);
17341790
Z_STRVAL_P(str)[offset+1] = 0;
1735-
} else if (!Z_REFCOUNTED_P(str)) {
1736-
ZVAL_NEW_STR(str, zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0));
1737-
} else if (Z_REFCOUNT_P(str) > 1) {
1738-
Z_DELREF_P(str);
1739-
ZVAL_NEW_STR(str, zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0));
17401791
} else {
17411792
zend_string_forget_hash_val(Z_STR_P(str));
17421793
}

Zend/zend_vm_def.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2592,8 +2592,8 @@ ZEND_VM_C_LABEL(try_assign_dim_array):
25922592
FREE_OP_DATA();
25932593
UNDEF_RESULT();
25942594
} else {
2595-
dim = GET_OP2_ZVAL_PTR(BP_VAR_R);
2596-
value = GET_OP_DATA_ZVAL_PTR_DEREF(BP_VAR_R);
2595+
dim = GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R);
2596+
value = GET_OP_DATA_ZVAL_PTR_UNDEF(BP_VAR_R);
25972597
zend_assign_to_string_offset(object_ptr, dim, value OPLINE_CC EXECUTE_DATA_CC);
25982598
FREE_OP_DATA();
25992599
}

0 commit comments

Comments
 (0)