mirror of
https://github.com/php/php-src.git
synced 2025-08-16 05:58:45 +02:00
Forbid dynamic calls to scope introspection functions
Per RFC: https://wiki.php.net/rfc/forbid_dynamic_scope_introspection
This commit is contained in:
parent
674297c7e4
commit
91f5940329
16 changed files with 189 additions and 15 deletions
13
UPGRADING
13
UPGRADING
|
@ -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
|
||||
|
|
14
Zend/tests/bug72107.phpt
Normal file
14
Zend/tests/bug72107.phpt
Normal file
|
@ -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
|
29
Zend/tests/dynamic_call_005.phpt
Normal file
29
Zend/tests/dynamic_call_005.phpt
Normal file
|
@ -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)
|
48
Zend/tests/dynamic_call_006.phpt
Normal file
48
Zend/tests/dynamic_call_006.phpt
Normal file
|
@ -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
|
17
Zend/tests/dynamic_call_007.phpt
Normal file
17
Zend/tests/dynamic_call_007.phpt
Normal file
|
@ -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)
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue