diff options
-rw-r--r-- | UPGRADING | 13 | ||||
-rw-r--r-- | Zend/tests/bug72107.phpt | 14 | ||||
-rw-r--r-- | Zend/tests/dynamic_call_005.phpt | 29 | ||||
-rw-r--r-- | Zend/tests/dynamic_call_006.phpt | 48 | ||||
-rw-r--r-- | Zend/tests/dynamic_call_007.phpt | 17 | ||||
-rw-r--r-- | Zend/zend_API.h | 1 | ||||
-rw-r--r-- | Zend/zend_builtin_functions.c | 24 | ||||
-rw-r--r-- | Zend/zend_compile.h | 1 | ||||
-rw-r--r-- | Zend/zend_execute.c | 6 | ||||
-rw-r--r-- | Zend/zend_execute_API.c | 16 | ||||
-rw-r--r-- | Zend/zend_vm_def.h | 2 | ||||
-rw-r--r-- | Zend/zend_vm_execute.h | 6 | ||||
-rw-r--r-- | ext/mbstring/mbstring.c | 7 | ||||
-rw-r--r-- | ext/standard/array.c | 9 | ||||
-rw-r--r-- | ext/standard/assert.c | 4 | ||||
-rw-r--r-- | ext/standard/string.c | 7 |
16 files changed, 189 insertions, 15 deletions
@@ -18,6 +18,7 @@ PHP 7.1 UPGRADE NOTES ======================================== 1. Backward Incompatible Changes ======================================== + - Core: . 'void' can no longer be used as the name of a class, interface, or trait. This applies to declarations, class_alias() and use statements. @@ -27,6 +28,18 @@ PHP 7.1 UPGRADE NOTES (RFC: https://wiki.php.net/rfc/invalid_strings_in_arithmetic) . The ASCII 0x7F Delete control character is no longer permitted in unquoted identifiers in source code. + . The following functions may no longer be called dynamically using $func(), + call_user_func(), array_map() or similar: + . extract() + . compact() + . get_defined_vars() + . func_get_args() + . func_get_arg() + . func_num_args() + . parse_str() with one argument + . mb_parse_str() with one argument + . assert() with a string argument + (RFC: https://wiki.php.net/rfc/forbid_dynamic_scope_introspection) - JSON: . When calling json_encode with JSON_UNESCAPED_UNICODE option, U+2028 and diff --git a/Zend/tests/bug72107.phpt b/Zend/tests/bug72107.phpt new file mode 100644 index 0000000000..3f4c46cf70 --- /dev/null +++ b/Zend/tests/bug72107.phpt @@ -0,0 +1,14 @@ +--TEST-- +Bug #72107: Segfault when using func_get_args as error handler +--FILE-- +<?php +set_error_handler('func_get_args'); +function test($a) { + echo $undef; +} +test(1); +?> +--EXPECTF-- +Warning: Cannot call func_get_args() dynamically in %s on line %d + +Notice: Undefined variable: undef in %s on line %d diff --git a/Zend/tests/dynamic_call_005.phpt b/Zend/tests/dynamic_call_005.phpt new file mode 100644 index 0000000000..840e298b82 --- /dev/null +++ b/Zend/tests/dynamic_call_005.phpt @@ -0,0 +1,29 @@ +--TEST-- +Dynamic calls to scope introspection functions are forbidden +--FILE-- +<?php + +function test_calls($func) { + $i = 1; + + array_map($func, [['i' => new stdClass]]); + var_dump($i); + + $func(['i' => new stdClass]); + var_dump($i); + + call_user_func($func, ['i' => new stdClass]); + var_dump($i); +} +test_calls('extract'); + +?> +--EXPECTF-- +Warning: Cannot call extract() dynamically in %s on line %d +int(1) + +Warning: Cannot call extract() dynamically in %s on line %d +int(1) + +Warning: Cannot call extract() dynamically in %s on line %d +int(1) diff --git a/Zend/tests/dynamic_call_006.phpt b/Zend/tests/dynamic_call_006.phpt new file mode 100644 index 0000000000..058e22fda0 --- /dev/null +++ b/Zend/tests/dynamic_call_006.phpt @@ -0,0 +1,48 @@ +--TEST-- +Dynamic calls to scope introspection functions are forbidden (function variations) +--FILE-- +<?php +function test() { + $func = 'extract'; + $func(['a' => 'b']); + + $func = 'compact'; + $func(['a']); + + $func = 'parse_str'; + $func('a=b'); + + $func = 'get_defined_vars'; + $func(); + + $func = 'assert'; + $func('1==2'); + + $func = 'func_get_args'; + $func(); + + $func = 'func_get_arg'; + $func(1); + + $func = 'func_num_args'; + $func(); +} +test(); + +?> +--EXPECTF-- +Warning: Cannot call extract() dynamically in %s on line %d + +Warning: Cannot call compact() dynamically in %s on line %d + +Warning: Cannot call parse_str() with a single argument dynamically in %s on line %d + +Warning: Cannot call get_defined_vars() dynamically in %s on line %d + +Warning: Cannot call assert() with string argument dynamically in %s on line %d + +Warning: Cannot call func_get_args() dynamically in %s on line %d + +Warning: Cannot call func_get_arg() dynamically in %s on line %d + +Warning: Cannot call func_num_args() dynamically in %s on line %d diff --git a/Zend/tests/dynamic_call_007.phpt b/Zend/tests/dynamic_call_007.phpt new file mode 100644 index 0000000000..61ae182914 --- /dev/null +++ b/Zend/tests/dynamic_call_007.phpt @@ -0,0 +1,17 @@ +--TEST-- +Dynamic calls to scope introspection functions are forbidden (misoptimization) +--FILE-- +<?php + +function test() { + $i = 1; + array_map('extract', [['i' => new stdClass]]); + $i += 1; + var_dump($i); +} +test(); + +?> +--EXPECTF-- +Warning: Cannot call extract() dynamically in %s on line %d +int(2) diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 91ba73cae7..ff93a53139 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -543,6 +543,7 @@ ZEND_API void zend_attach_symbol_table(zend_execute_data *execute_data); ZEND_API void zend_detach_symbol_table(zend_execute_data *execute_data); ZEND_API int zend_set_local_var(zend_string *name, zval *value, int force); ZEND_API int zend_set_local_var_str(const char *name, size_t len, zval *value, int force); +ZEND_API int zend_forbid_dynamic_call(const char *func_name); ZEND_API zend_string *zend_find_alias_name(zend_class_entry *ce, zend_string *name); ZEND_API zend_string *zend_resolve_method_name(zend_class_entry *ce, zend_function *f); diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index 04827cfb67..5ae536f425 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -491,12 +491,16 @@ ZEND_FUNCTION(func_num_args) { zend_execute_data *ex = EX(prev_execute_data); - if (!(ZEND_CALL_INFO(ex) & ZEND_CALL_CODE)) { - RETURN_LONG(ZEND_CALL_NUM_ARGS(ex)); - } else { + if (ZEND_CALL_INFO(ex) & ZEND_CALL_CODE) { zend_error(E_WARNING, "func_num_args(): Called from the global scope - no function context"); RETURN_LONG(-1); } + + if (zend_forbid_dynamic_call("func_num_args()") == FAILURE) { + RETURN_LONG(-1); + } + + RETURN_LONG(ZEND_CALL_NUM_ARGS(ex)); } /* }}} */ @@ -524,6 +528,10 @@ ZEND_FUNCTION(func_get_arg) RETURN_FALSE; } + if (zend_forbid_dynamic_call("func_get_arg()") == FAILURE) { + RETURN_FALSE; + } + arg_count = ZEND_CALL_NUM_ARGS(ex); if ((zend_ulong)requested_offset >= arg_count) { @@ -558,6 +566,10 @@ ZEND_FUNCTION(func_get_args) RETURN_FALSE; } + if (zend_forbid_dynamic_call("func_get_args()") == FAILURE) { + RETURN_FALSE; + } + arg_count = ZEND_CALL_NUM_ARGS(ex); array_init_size(return_value, arg_count); @@ -2024,8 +2036,12 @@ ZEND_FUNCTION(get_defined_functions) Returns an associative array of names and values of all currently defined variable names (variables in the current scope) */ ZEND_FUNCTION(get_defined_vars) { - zend_array *symbol_table = zend_rebuild_symbol_table(); + zend_array *symbol_table; + if (zend_forbid_dynamic_call("get_defined_vars()") == FAILURE) { + return; + } + symbol_table = zend_rebuild_symbol_table(); if (UNEXPECTED(symbol_table == NULL)) { return; } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 0de475da11..72215a20d1 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -473,6 +473,7 @@ struct _zend_execute_data { #define ZEND_CALL_RELEASE_THIS (1 << 6) #define ZEND_CALL_ALLOCATED (1 << 7) #define ZEND_CALL_GENERATOR (1 << 8) +#define ZEND_CALL_DYNAMIC (1 << 9) #define ZEND_CALL_INFO_SHIFT 16 diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 5fb0003986..893f113976 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -2691,7 +2691,7 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_string(zend_s init_func_run_time_cache(&fbc->op_array); } - return zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION, + return zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_DYNAMIC, fbc, num_args, called_scope, NULL); } /* }}} */ @@ -2701,7 +2701,7 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_object(zval * zend_function *fbc; zend_class_entry *called_scope; zend_object *object; - uint32_t call_info = ZEND_CALL_NESTED_FUNCTION; + uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_DYNAMIC; if (EXPECTED(Z_OBJ_HANDLER_P(function, get_closure)) && EXPECTED(Z_OBJ_HANDLER_P(function, get_closure)(function, &called_scope, &fbc, &object) == SUCCESS)) { @@ -2734,7 +2734,7 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_array(zend_ar zend_function *fbc; zend_class_entry *called_scope; zend_object *object; - uint32_t call_info = ZEND_CALL_NESTED_FUNCTION; + uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_DYNAMIC; if (zend_hash_num_elements(function) == 2) { zval *obj; diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 9817a5f88e..dba10e3564 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -756,7 +756,7 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) / fci->object = (func->common.fn_flags & ZEND_ACC_STATIC) ? NULL : fci_cache->object; - call = zend_vm_stack_push_call_frame(ZEND_CALL_TOP_FUNCTION, + call = zend_vm_stack_push_call_frame(ZEND_CALL_TOP_FUNCTION | ZEND_CALL_DYNAMIC, func, fci->param_count, fci_cache->called_scope, fci->object); if (fci->object && (!EG(objects_store).object_buckets || @@ -1691,6 +1691,20 @@ ZEND_API int zend_set_local_var_str(const char *name, size_t len, zval *value, i } /* }}} */ +ZEND_API int zend_forbid_dynamic_call(const char *func_name) /* {{{ */ +{ + zend_execute_data *ex = EG(current_execute_data); + ZEND_ASSERT(ex != NULL && ex->func != NULL); + + if (ZEND_CALL_INFO(ex) & ZEND_CALL_DYNAMIC) { + zend_error(E_WARNING, "Cannot call %s dynamically", func_name); + return FAILURE; + } + + return SUCCESS; +} +/* }}} */ + /* * Local variables: * tab-width: 4 diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 93ff58e807..4ac2057d27 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3435,7 +3435,7 @@ ZEND_VM_HANDLER(118, ZEND_INIT_USER_CALL, CONST, CONST|TMPVAR|CV, NUM) zend_class_entry *called_scope; zend_object *object; zend_execute_data *call; - uint32_t call_info = ZEND_CALL_NESTED_FUNCTION; + uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_DYNAMIC; SAVE_OPLINE(); function_name = GET_OP2_ZVAL_PTR(BP_VAR_R); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 1b673b8c2a..9f6130c059 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -5460,7 +5460,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_CONS zend_class_entry *called_scope; zend_object *object; zend_execute_data *call; - uint32_t call_info = ZEND_CALL_NESTED_FUNCTION; + uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_DYNAMIC; SAVE_OPLINE(); function_name = EX_CONSTANT(opline->op2); @@ -9279,7 +9279,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_CV_H zend_class_entry *called_scope; zend_object *object; zend_execute_data *call; - uint32_t call_info = ZEND_CALL_NESTED_FUNCTION; + uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_DYNAMIC; SAVE_OPLINE(); function_name = _get_zval_ptr_cv_BP_VAR_R(execute_data, opline->op2.var); @@ -11187,7 +11187,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_TMPV zend_class_entry *called_scope; zend_object *object; zend_execute_data *call; - uint32_t call_info = ZEND_CALL_NESTED_FUNCTION; + uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_DYNAMIC; SAVE_OPLINE(); function_name = _get_zval_ptr_var(opline->op2.var, execute_data, &free_op2); diff --git a/ext/mbstring/mbstring.c b/ext/mbstring/mbstring.c index 514c4fc1b6..423f357eae 100644 --- a/ext/mbstring/mbstring.c +++ b/ext/mbstring/mbstring.c @@ -2097,8 +2097,13 @@ PHP_FUNCTION(mb_parse_str) detected = _php_mb_encoding_handler_ex(&info, track_vars_array, encstr); } else { zval tmp; - zend_array *symbol_table = zend_rebuild_symbol_table(); + zend_array *symbol_table; + if (zend_forbid_dynamic_call("mb_parse_str() with a single argument") == FAILURE) { + efree(encstr); + return; + } + symbol_table = zend_rebuild_symbol_table(); ZVAL_ARR(&tmp, symbol_table); detected = _php_mb_encoding_handler_ex(&info, &tmp, encstr); } diff --git a/ext/standard/array.c b/ext/standard/array.c index d8ae982c85..7ee505601e 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -1805,6 +1805,10 @@ PHP_FUNCTION(extract) } } + if (zend_forbid_dynamic_call("extract()") == FAILURE) { + return; + } + symbol_table = zend_rebuild_symbol_table(); #if 0 if (!symbol_table) { @@ -1969,8 +1973,11 @@ PHP_FUNCTION(compact) return; } - symbol_table = zend_rebuild_symbol_table(); + if (zend_forbid_dynamic_call("compact()") == FAILURE) { + return; + } + symbol_table = zend_rebuild_symbol_table(); if (UNEXPECTED(symbol_table == NULL)) { return; } diff --git a/ext/standard/assert.c b/ext/standard/assert.c index 2cb6285f4e..016c90e02e 100644 --- a/ext/standard/assert.c +++ b/ext/standard/assert.c @@ -165,6 +165,10 @@ PHP_FUNCTION(assert) zval retval; int old_error_reporting = 0; /* shut up gcc! */ + if (zend_forbid_dynamic_call("assert() with string argument") == FAILURE) { + RETURN_FALSE; + } + myeval = Z_STRVAL_P(assertion); if (ASSERTG(quiet_eval)) { diff --git a/ext/standard/string.c b/ext/standard/string.c index b2fcfa0ec3..1305e7f920 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -4605,8 +4605,13 @@ PHP_FUNCTION(parse_str) if (arrayArg == NULL) { zval tmp; - zend_array *symbol_table = zend_rebuild_symbol_table(); + zend_array *symbol_table; + if (zend_forbid_dynamic_call("parse_str() with a single argument") == FAILURE) { + efree(res); + return; + } + symbol_table = zend_rebuild_symbol_table(); ZVAL_ARR(&tmp, symbol_table); sapi_module.treat_data(PARSE_STRING, res, &tmp); } else { |