]> granicus.if.org Git - php/commitdiff
Forbid dynamic calls to scope introspection functions
authorNikita Popov <nikic@php.net>
Sun, 24 Apr 2016 21:49:52 +0000 (23:49 +0200)
committerNikita Popov <nikic@php.net>
Tue, 24 May 2016 18:48:24 +0000 (20:48 +0200)
Per RFC:
https://wiki.php.net/rfc/forbid_dynamic_scope_introspection

16 files changed:
UPGRADING
Zend/tests/bug72107.phpt [new file with mode: 0644]
Zend/tests/dynamic_call_005.phpt [new file with mode: 0644]
Zend/tests/dynamic_call_006.phpt [new file with mode: 0644]
Zend/tests/dynamic_call_007.phpt [new file with mode: 0644]
Zend/zend_API.h
Zend/zend_builtin_functions.c
Zend/zend_compile.h
Zend/zend_execute.c
Zend/zend_execute_API.c
Zend/zend_vm_def.h
Zend/zend_vm_execute.h
ext/mbstring/mbstring.c
ext/standard/array.c
ext/standard/assert.c
ext/standard/string.c

index dcf80a60293371898a6533a78af5504042e5f856..b000d54ea39f985b7422e3a1f2bc51554190c746 100644 (file)
--- a/UPGRADING
+++ b/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
diff --git a/Zend/tests/bug72107.phpt b/Zend/tests/bug72107.phpt
new file mode 100644 (file)
index 0000000..3f4c46c
--- /dev/null
@@ -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 (file)
index 0000000..840e298
--- /dev/null
@@ -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 (file)
index 0000000..058e22f
--- /dev/null
@@ -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 (file)
index 0000000..61ae182
--- /dev/null
@@ -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)
index 91ba73cae7037b2a15a11ce5163b6eacb611233c..ff93a53139ffeae113f2eb12a15fb062c79964ea 100644 (file)
@@ -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);
index 04827cfb67e2d5066773c98786dd7340c696fa24..5ae536f425a07f6110fa09a7407d7168433be97b 100644 (file)
@@ -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;
        }
index 0de475da11ddf26751f8a222f30c730a427204a8..72215a20d16939da3d83b06089c0d6cc52a65c06 100644 (file)
@@ -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
 
index 5fb000398600211e501129481fe44094122b9610..893f113976b4befc9c49450a66303028dee10a36 100644 (file)
@@ -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;
index 9817a5f88e2dbe523b73be6defe60c11b0c05f88..dba10e3564687f9b59a5126055284994a86ed4ad 100644 (file)
@@ -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
index 93ff58e8070fe64e8daa367cec3025f3397f478f..4ac2057d27b4b1eb73dd6a43c3372ffccf2a826c 100644 (file)
@@ -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);
index 1b673b8c2a5ed0a67e886574640912801aef8522..9f6130c0597ec92d94ae606dd1b15a12609a6422 100644 (file)
@@ -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);
index 514c4fc1b6886303b911ffc5d72d0a1ba1ebc82b..423f357eaedbb5052c3e32a233283117dfc5d826 100644 (file)
@@ -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);
        }
index d8ae982c85d965c5a3bd5ae83cc8212b792e6b2c..7ee505601ea85ce184ef7adbc5f08f727d951259 100644 (file)
@@ -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;
        }
index 2cb6285f4e30e2a66908abc801c27db8cd281ff1..016c90e02e72baba3fb2364c8b4394535ef6c5f1 100644 (file)
@@ -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)) {
index b2fcfa0ec352f43222fed41ea7349e55b892f25f..1305e7f9206233ef1e4f15041bfbaf6dd50f7528 100644 (file)
@@ -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  {