Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Zend/tests/str_offset_006.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
string offset 006 indirect string modification by error handler
--FILE--
<?php
set_error_handler(function($code, $msg) {
echo "Err: $msg\n";
$GLOBALS['a']=null;
});
$a[$y]=$a.=($y);
var_dump($a);
?>
--EXPECT--
Err: Undefined variable $y
Err: Undefined variable $y
Err: String offset cast occurred
NULL
16 changes: 16 additions & 0 deletions Zend/tests/str_offset_007.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
string offset 007 indirect string modification by error handler
--FILE--
<?php
set_error_handler(function($code, $msg) {
echo "Err: $msg\n";
$GLOBALS['a']='';
});
$a=['a'];
$a[0][$d]='b';
var_dump($a);
?>
--EXPECT--
Err: Undefined variable $d
Err: String offset cast occurred
string(0) ""
159 changes: 103 additions & 56 deletions Zend/zend_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -1484,45 +1484,41 @@ static zend_never_inline zend_long zend_check_string_offset(zval *dim, int type
zend_long offset;

try_again:
if (UNEXPECTED(Z_TYPE_P(dim) != IS_LONG)) {
switch(Z_TYPE_P(dim)) {
case IS_STRING:
{
bool trailing_data = false;
/* For BC reasons we allow errors so that we can warn on leading numeric string */
if (IS_LONG == is_numeric_string_ex(Z_STRVAL_P(dim), Z_STRLEN_P(dim), &offset, NULL,
/* allow errors */ true, NULL, &trailing_data)) {
if (UNEXPECTED(trailing_data) && type != BP_VAR_UNSET) {
zend_error(E_WARNING, "Illegal string offset \"%s\"", Z_STRVAL_P(dim));
}
return offset;
switch(Z_TYPE_P(dim)) {
case IS_LONG:
return Z_LVAL_P(dim);
case IS_STRING:
{
bool trailing_data = false;
/* For BC reasons we allow errors so that we can warn on leading numeric string */
if (IS_LONG == is_numeric_string_ex(Z_STRVAL_P(dim), Z_STRLEN_P(dim), &offset, NULL,
/* allow errors */ true, NULL, &trailing_data)) {
if (UNEXPECTED(trailing_data) && type != BP_VAR_UNSET) {
zend_error(E_WARNING, "Illegal string offset \"%s\"", Z_STRVAL_P(dim));
}
zend_illegal_string_offset(dim);
return 0;
return offset;
}
case IS_UNDEF:
ZVAL_UNDEFINED_OP2();
ZEND_FALLTHROUGH;
case IS_DOUBLE:
case IS_NULL:
case IS_FALSE:
case IS_TRUE:
zend_error(E_WARNING, "String offset cast occurred");
break;
case IS_REFERENCE:
dim = Z_REFVAL_P(dim);
goto try_again;
default:
zend_illegal_string_offset(dim);
return 0;
zend_illegal_string_offset(dim);
return 0;
}

offset = zval_get_long_func(dim, /* is_strict */ false);
} else {
offset = Z_LVAL_P(dim);
case IS_UNDEF:
ZVAL_UNDEFINED_OP2();
ZEND_FALLTHROUGH;
case IS_DOUBLE:
case IS_NULL:
case IS_FALSE:
case IS_TRUE:
zend_error(E_WARNING, "String offset cast occurred");
break;
case IS_REFERENCE:
dim = Z_REFVAL_P(dim);
goto try_again;
default:
zend_illegal_string_offset(dim);
return 0;
}

return offset;
return zval_get_long_func(dim, /* is_strict */ false);
}

ZEND_API ZEND_COLD void zend_wrong_string_offset_error(void)
Expand Down Expand Up @@ -1598,17 +1594,41 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim,
zend_uchar c;
size_t string_len;
zend_long offset;
zend_string *s;

offset = zend_check_string_offset(dim, BP_VAR_W EXECUTE_DATA_CC);
/* Illegal offset assignment */
if (UNEXPECTED(EG(exception) != NULL)) {
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
ZVAL_UNDEF(EX_VAR(opline->result.var));
/* separate string */
if (Z_REFCOUNTED_P(str) && Z_REFCOUNT_P(str) == 1) {
s = Z_STR_P(str);
} else {
s = zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0);
ZSTR_H(s) = ZSTR_H(Z_STR_P(str));
ZVAL_NEW_STR(str, s);
}

if (EXPECTED(Z_TYPE_P(dim) == IS_LONG)) {
offset = Z_LVAL_P(dim);
} else {
/* The string may be destroyed while throwing the notice.
* Temporarily increase the refcount to detect this situation. */
GC_ADDREF(s);
offset = zend_check_string_offset(dim, BP_VAR_W EXECUTE_DATA_CC);
if (GC_DELREF(s) == 0) {
zend_string_efree(s);
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
ZVAL_NULL(EX_VAR(opline->result.var));
}
return;
}
/* Illegal offset assignment */
if (UNEXPECTED(EG(exception) != NULL)) {
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
ZVAL_UNDEF(EX_VAR(opline->result.var));
}
return;
}
return;
}

if (offset < -(zend_long)Z_STRLEN_P(str)) {
if (UNEXPECTED(offset < -(zend_long)ZSTR_LEN(s))) {
/* Error on negative offset */
zend_error(E_WARNING, "Illegal string offset " ZEND_LONG_FMT, offset);
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
Expand All @@ -1617,9 +1637,28 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim,
return;
}

if (Z_TYPE_P(value) != IS_STRING) {
if (offset < 0) { /* Handle negative offset */
offset += (zend_long)ZSTR_LEN(s);
}

if (UNEXPECTED(Z_TYPE_P(value) != IS_STRING)) {
zend_string *tmp;

/* The string may be destroyed while throwing the notice.
* Temporarily increase the refcount to detect this situation. */
GC_ADDREF(s);
if (UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) {
zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC);
}
/* Convert to string, just the time to pick the 1st byte */
zend_string *tmp = zval_try_get_string_func(value);
tmp = zval_try_get_string_func(value);
if (GC_DELREF(s) == 0) {
zend_string_efree(s);
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
ZVAL_NULL(EX_VAR(opline->result.var));
}
return;
}
if (UNEXPECTED(!tmp)) {
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
ZVAL_UNDEF(EX_VAR(opline->result.var));
Expand All @@ -1635,7 +1674,7 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim,
c = (zend_uchar)Z_STRVAL_P(value)[0];
}

if (string_len != 1) {
if (UNEXPECTED(string_len != 1)) {
if (string_len == 0) {
/* Error on empty input string */
zend_throw_error(NULL, "Cannot assign an empty string to a string offset");
Expand All @@ -1645,24 +1684,32 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim,
return;
}

/* The string may be destroyed while throwing the notice.
* Temporarily increase the refcount to detect this situation. */
GC_ADDREF(s);
zend_error(E_WARNING, "Only the first byte will be assigned to the string offset");
if (GC_DELREF(s) == 0) {
zend_string_efree(s);
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
ZVAL_NULL(EX_VAR(opline->result.var));
}
return;
}
/* Illegal offset assignment */
if (UNEXPECTED(EG(exception) != NULL)) {
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
ZVAL_UNDEF(EX_VAR(opline->result.var));
}
return;
}
}

if (offset < 0) { /* Handle negative offset */
offset += (zend_long)Z_STRLEN_P(str);
}

if ((size_t)offset >= Z_STRLEN_P(str)) {
if ((size_t)offset >= ZSTR_LEN(s)) {
/* Extend string if needed */
zend_long old_len = Z_STRLEN_P(str);
ZVAL_NEW_STR(str, zend_string_extend(Z_STR_P(str), (size_t)offset + 1, 0));
zend_long old_len = ZSTR_LEN(s);
ZVAL_NEW_STR(str, zend_string_extend(s, (size_t)offset + 1, 0));
memset(Z_STRVAL_P(str) + old_len, ' ', offset - old_len);
Z_STRVAL_P(str)[offset+1] = 0;
} else if (!Z_REFCOUNTED_P(str)) {
ZVAL_NEW_STR(str, zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0));
} else if (Z_REFCOUNT_P(str) > 1) {
Z_DELREF_P(str);
ZVAL_NEW_STR(str, zend_string_init(Z_STRVAL_P(str), Z_STRLEN_P(str), 0));
} else {
zend_string_forget_hash_val(Z_STR_P(str));
}
Expand Down
4 changes: 2 additions & 2 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -2601,8 +2601,8 @@ ZEND_VM_C_LABEL(try_assign_dim_array):
FREE_OP_DATA();
UNDEF_RESULT();
} else {
dim = GET_OP2_ZVAL_PTR(BP_VAR_R);
value = GET_OP_DATA_ZVAL_PTR_DEREF(BP_VAR_R);
dim = GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R);
value = GET_OP_DATA_ZVAL_PTR_UNDEF(BP_VAR_R);
zend_assign_to_string_offset(object_ptr, dim, value OPLINE_CC EXECUTE_DATA_CC);
FREE_OP_DATA();
}
Expand Down
Loading