]> granicus.if.org Git - php/commitdiff
Don't check for throwing calls in sccp function evaluation
authorNikita Popov <nikita.ppv@gmail.com>
Fri, 27 Nov 2020 16:40:34 +0000 (17:40 +0100)
committerNikita Popov <nikita.ppv@gmail.com>
Fri, 27 Nov 2020 16:49:51 +0000 (17:49 +0100)
We only need to reject functions that could warn (or have runtime
dependent behavior). If a function can throw in some cases, just
let it and discard the result.

ext/opcache/Optimizer/sccp.c

index 9f122296f5d9c8a74b33edbf8ce3d95d404380e3..973c1763e363a8e3db1cc5ba56bbd0efcec5ba2f 100644 (file)
@@ -781,253 +781,174 @@ static inline int ct_eval_array_key_exists(zval *result, zval *op1, zval *op2) {
        return SUCCESS;
 }
 
-/* The functions chosen here are simple to implement and either likely to affect a branch,
- * or just happened to be commonly used with constant operands in WP (need to test other
- * applications as well, of course). */
-static inline int ct_eval_func_call(
-               zval *result, zend_string *name, uint32_t num_args, zval **args) {
-       uint32_t i;
-       zend_execute_data *execute_data, *prev_execute_data;
-       zend_function *func;
-       bool overflow;
-
-       if (num_args == 0) {
-               if (zend_string_equals_literal(name, "php_sapi_name")
-                               || zend_string_equals_literal(name, "imagetypes")
-                               || zend_string_equals_literal(name, "phpversion")) {
-                       /* pass */
-               } else {
-                       return FAILURE;
-               }
-       } else if (num_args == 1) {
-               if (zend_string_equals_literal(name, "chr")) {
-                       zend_long c;
-                       if (Z_TYPE_P(args[0]) != IS_LONG) {
-                               return FAILURE;
-                       }
-
-                       c = Z_LVAL_P(args[0]) & 0xff;
-                       ZVAL_CHAR(result, c);
-                       return SUCCESS;
-               } else if (zend_string_equals_literal(name, "count")) {
-                       if (Z_TYPE_P(args[0]) != IS_ARRAY) {
-                               return FAILURE;
-                       }
-
-                       ZVAL_LONG(result, zend_hash_num_elements(Z_ARRVAL_P(args[0])));
-                       return SUCCESS;
-               } else if (zend_string_equals_literal(name, "ini_get")) {
-                       zend_ini_entry *ini_entry;
-
-                       if (Z_TYPE_P(args[0]) != IS_STRING) {
-                               return FAILURE;
-                       }
-
-                       ini_entry = zend_hash_find_ptr(EG(ini_directives), Z_STR_P(args[0]));
-                       if (!ini_entry) {
-                               ZVAL_FALSE(result);
-                       } else if (ini_entry->modifiable != ZEND_INI_SYSTEM) {
-                               return FAILURE;
-                       } else if (ini_entry->value) {
-                               ZVAL_STR_COPY(result, ini_entry->value);
-                       } else {
-                               ZVAL_EMPTY_STRING(result);
-                       }
-                       return SUCCESS;
-               } else if (zend_string_equals_literal(name, "trim")
-                               || zend_string_equals_literal(name, "rtrim")
-                               || zend_string_equals_literal(name, "ltrim")
-                               || zend_string_equals_literal(name, "str_split")
-                               || zend_string_equals_literal(name, "preg_quote")
-                               || zend_string_equals_literal(name, "base64_encode")
-                               || zend_string_equals_literal(name, "base64_decode")
-                               || zend_string_equals_literal(name, "urlencode")
-                               || zend_string_equals_literal(name, "urldecode")
-                               || zend_string_equals_literal(name, "rawurlencode")
-                               || zend_string_equals_literal(name, "rawurldecode")
-                               || zend_string_equals_literal(name, "php_uname")) {
-                       if (Z_TYPE_P(args[0]) != IS_STRING) {
-                               return FAILURE;
-                       }
-                       /* pass */
-               } else if (zend_string_equals_literal(name, "array_keys")
-                               || zend_string_equals_literal(name, "array_values")) {
-                       if (Z_TYPE_P(args[0]) != IS_ARRAY) {
-                               return FAILURE;
-                       }
-                       /* pass */
-               } else if (zend_string_equals_literal(name, "array_flip")) {
+static zend_bool can_ct_eval_func_call(zend_string *name, uint32_t num_args, zval **args) {
+       /* Functions that can be evaluated independently of what the arguments are.
+        * It's okay if these functions throw on invalid arguments, but they should not warn. */
+       if (false
+               || zend_string_equals_literal(name, "array_diff")
+               || zend_string_equals_literal(name, "array_diff_assoc")
+               || zend_string_equals_literal(name, "array_diff_key")
+               || zend_string_equals_literal(name, "array_key_exists")
+               || zend_string_equals_literal(name, "array_keys")
+               || zend_string_equals_literal(name, "array_merge")
+               || zend_string_equals_literal(name, "array_merge_recursive")
+               || zend_string_equals_literal(name, "array_replace")
+               || zend_string_equals_literal(name, "array_replace_recursive")
+               || zend_string_equals_literal(name, "array_values")
+               || zend_string_equals_literal(name, "base64_decode")
+               || zend_string_equals_literal(name, "base64_encode")
+               || zend_string_equals_literal(name, "imagetypes")
+               || zend_string_equals_literal(name, "in_array")
+               || zend_string_equals_literal(name, "ltrim")
+               || zend_string_equals_literal(name, "php_sapi_name")
+               || zend_string_equals_literal(name, "php_uname")
+               || zend_string_equals_literal(name, "phpversion")
+               || zend_string_equals_literal(name, "pow")
+               || zend_string_equals_literal(name, "preg_quote")
+               || zend_string_equals_literal(name, "rawurldecode")
+               || zend_string_equals_literal(name, "rawurlencode")
+               || zend_string_equals_literal(name, "rtrim")
+               || zend_string_equals_literal(name, "serialize")
+               || zend_string_equals_literal(name, "str_contains")
+               || zend_string_equals_literal(name, "str_ends_with")
+               || zend_string_equals_literal(name, "str_split")
+               || zend_string_equals_literal(name, "str_split")
+               || zend_string_equals_literal(name, "str_starts_with")
+               || zend_string_equals_literal(name, "strpos")
+               || zend_string_equals_literal(name, "substr")
+               || zend_string_equals_literal(name, "trim")
+               || zend_string_equals_literal(name, "urldecode")
+               || zend_string_equals_literal(name, "urlencode")
+               || zend_string_equals_literal(name, "version_compare")
+       ) {
+               return true;
+       }
+
+       /* For the following functions we need to check arguments to prevent warnings during
+        * evaluation. */
+       if (num_args == 1) {
+               if (zend_string_equals_literal(name, "array_flip")) {
                        zval *entry;
 
                        if (Z_TYPE_P(args[0]) != IS_ARRAY) {
-                               return FAILURE;
+                               return false;
                        }
                        ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(args[0]), entry) {
+                               /* Throws warning for non int/string values. */
                                if (Z_TYPE_P(entry) != IS_LONG && Z_TYPE_P(entry) != IS_STRING) {
-                                       return FAILURE;
+                                       return false;
                                }
                        } ZEND_HASH_FOREACH_END();
-                       /* pass */
-               } else if (zend_string_equals_literal(name, "implode")) {
+                       return true;
+               }
+               if (zend_string_equals_literal(name, "implode")) {
                        zval *entry;
 
                        if (Z_TYPE_P(args[0]) != IS_ARRAY) {
-                               return FAILURE;
+                               return false;
                        }
 
                        ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(args[0]), entry) {
+                               /* May throw warning during conversion to string. */
                                if (Z_TYPE_P(entry) > IS_STRING) {
-                                       return FAILURE;
+                                       return false;
                                }
                        } ZEND_HASH_FOREACH_END();
-                       /* pass */
-               } else if (zend_string_equals_literal(name, "serialize")) {
-                       /* pass */
-               } else {
-                       return FAILURE;
+                       return true;
                }
-       } else if (num_args == 2) {
-               if (zend_string_equals_literal(name, "in_array")) {
-                       if (Z_TYPE_P(args[1]) != IS_ARRAY) {
-                               return FAILURE;
-                       }
-                       /* pass */
-               } else if (zend_string_equals_literal(name, "str_split")) {
-                       if (Z_TYPE_P(args[0]) != IS_STRING
-                                       || Z_TYPE_P(args[1]) != IS_LONG
-                                       || Z_LVAL_P(args[1]) <= 0) {
-                               return FAILURE;
-                       }
-                       /* pass */
-               } else if (zend_string_equals_literal(name, "array_key_exists")) {
-                       if (Z_TYPE_P(args[1]) != IS_ARRAY
-                                       || (Z_TYPE_P(args[0]) != IS_LONG
-                                               && Z_TYPE_P(args[0]) != IS_STRING
-                                               && Z_TYPE_P(args[0]) != IS_NULL)) {
-                               return FAILURE;
-                       }
-                       /* pass */
-               } else if (zend_string_equals_literal(name, "trim")
-                               || zend_string_equals_literal(name, "rtrim")
-                               || zend_string_equals_literal(name, "ltrim")
-                               || zend_string_equals_literal(name, "preg_quote")) {
-                       if (Z_TYPE_P(args[0]) != IS_STRING
-                                       || Z_TYPE_P(args[1]) != IS_STRING) {
-                               return FAILURE;
-                       }
-                       /* pass */
-               } else if (zend_string_equals_literal(name, "str_repeat")) {
-                       if (Z_TYPE_P(args[0]) != IS_STRING
-                                       || Z_TYPE_P(args[1]) != IS_LONG
-                                       || zend_safe_address(Z_STRLEN_P(args[0]), Z_LVAL_P(args[1]), 0, &overflow) > 64 * 1024
-                                       || overflow) {
-                               return FAILURE;
-                       }
-                       /* pass */
-               } else if (zend_string_equals_literal(name, "array_merge")
-                               || zend_string_equals_literal(name, "array_replace")
-                               || zend_string_equals_literal(name, "array_merge_recursive")
-                               || zend_string_equals_literal(name, "array_replace_recursive")
-                               || zend_string_equals_literal(name, "array_diff")
-                               || zend_string_equals_literal(name, "array_diff_assoc")
-                               || zend_string_equals_literal(name, "array_diff_key")) {
-                       for (i = 0; i < num_args; i++) {
-                               if (Z_TYPE_P(args[i]) != IS_ARRAY) {
-                                       return FAILURE;
-                               }
-                       }
-                       /* pass */
+               return false;
+       }
+
+       if (num_args == 2) {
+               if (zend_string_equals_literal(name, "str_repeat")) {
+                       /* Avoid creating overly large strings at compile-time. */
+                       bool overflow;
+                       return Z_TYPE_P(args[0]) == IS_STRING
+                               && Z_TYPE_P(args[1]) == IS_LONG
+                               && zend_safe_address(Z_STRLEN_P(args[0]), Z_LVAL_P(args[1]), 0, &overflow) < 64 * 1024
+                               && !overflow;
                } else if (zend_string_equals_literal(name, "implode")) {
                        zval *entry;
 
                        if ((Z_TYPE_P(args[0]) != IS_STRING || Z_TYPE_P(args[1]) != IS_ARRAY)
                                        && (Z_TYPE_P(args[0]) != IS_ARRAY || Z_TYPE_P(args[1]) != IS_STRING)) {
-                               return FAILURE;
+                               return false;
                        }
 
+                       /* May throw warning during conversion to string. */
                        if (Z_TYPE_P(args[0]) == IS_ARRAY) {
                                ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(args[0]), entry) {
                                        if (Z_TYPE_P(entry) > IS_STRING) {
-                                               return FAILURE;
+                                               return false;
                                        }
                                } ZEND_HASH_FOREACH_END();
                        } else {
                                ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(args[1]), entry) {
                                        if (Z_TYPE_P(entry) > IS_STRING) {
-                                               return FAILURE;
+                                               return false;
                                        }
                                } ZEND_HASH_FOREACH_END();
                        }
-                       /* pass */
-               } else if (zend_string_equals_literal(name, "strpos")
-                               || zend_string_equals_literal(name, "str_contains")
-                               || zend_string_equals_literal(name, "str_starts_with")
-                               || zend_string_equals_literal(name, "str_ends_with")
-                               || zend_string_equals_literal(name, "version_compare")) {
-                       if (Z_TYPE_P(args[0]) != IS_STRING
-                                       || Z_TYPE_P(args[1]) != IS_STRING) {
-                               return FAILURE;
-                       }
-                       /* pass */
-               } else if (zend_string_equals_literal(name, "substr")) {
-                       if (Z_TYPE_P(args[0]) != IS_STRING
-                                       || Z_TYPE_P(args[1]) != IS_LONG) {
-                               return FAILURE;
-                       }
-                       /* pass */
-               } else if (zend_string_equals_literal(name, "pow")) {
-                       if ((Z_TYPE_P(args[0]) != IS_LONG && Z_TYPE_P(args[0]) != IS_DOUBLE)
-                                       || (Z_TYPE_P(args[1]) != IS_LONG && Z_TYPE_P(args[1]) != IS_DOUBLE)) {
-                               return FAILURE;
-                       }
-                       /* pass */
-               } else {
-                       return FAILURE;
+                       return true;
                }
-       } else if (num_args == 3) {
-               if (zend_string_equals_literal(name, "in_array")) {
-                       if (Z_TYPE_P(args[1]) != IS_ARRAY
-                               || (Z_TYPE_P(args[2]) != IS_FALSE
-                                       && Z_TYPE_P(args[2]) != IS_TRUE)) {
+               return false;
+       }
+
+       return false;
+}
+
+/* The functions chosen here are simple to implement and either likely to affect a branch,
+ * or just happened to be commonly used with constant operands in WP (need to test other
+ * applications as well, of course). */
+static inline int ct_eval_func_call(
+               zval *result, zend_string *name, uint32_t num_args, zval **args) {
+       uint32_t i;
+       zend_execute_data *execute_data, *prev_execute_data;
+       zend_function *func = zend_hash_find_ptr(CG(function_table), name);
+       if (!func || func->type != ZEND_INTERNAL_FUNCTION) {
+               return FAILURE;
+       }
+
+       if (num_args == 1) {
+               /* Handle a few functions for which we manually implement evaluation here. */
+               if (zend_string_equals_literal(name, "chr")) {
+                       zend_long c;
+                       if (Z_TYPE_P(args[0]) != IS_LONG) {
                                return FAILURE;
                        }
-                       /* pass */
-               } else if (zend_string_equals_literal(name, "array_merge")
-                               || zend_string_equals_literal(name, "array_replace")
-                               || zend_string_equals_literal(name, "array_merge_recursive")
-                               || zend_string_equals_literal(name, "array_replace_recursive")
-                               || zend_string_equals_literal(name, "array_diff")
-                               || zend_string_equals_literal(name, "array_diff_assoc")
-                               || zend_string_equals_literal(name, "array_diff_key")) {
-                       for (i = 0; i < num_args; i++) {
-                               if (Z_TYPE_P(args[i]) != IS_ARRAY) {
-                                       return FAILURE;
-                               }
+
+                       c = Z_LVAL_P(args[0]) & 0xff;
+                       ZVAL_CHAR(result, c);
+                       return SUCCESS;
+               } else if (zend_string_equals_literal(name, "count")) {
+                       if (Z_TYPE_P(args[0]) != IS_ARRAY) {
+                               return FAILURE;
                        }
-                       /* pass */
-               } else if (zend_string_equals_literal(name, "version_compare")) {
-                       if (Z_TYPE_P(args[0]) != IS_STRING
-                                       || Z_TYPE_P(args[1]) != IS_STRING
-                                       || Z_TYPE_P(args[2]) != IS_STRING) {
+
+                       ZVAL_LONG(result, zend_hash_num_elements(Z_ARRVAL_P(args[0])));
+                       return SUCCESS;
+               } else if (zend_string_equals_literal(name, "ini_get")) {
+                       zend_ini_entry *ini_entry;
+
+                       if (Z_TYPE_P(args[0]) != IS_STRING) {
                                return FAILURE;
                        }
-                       /* pass */
-               } else if (zend_string_equals_literal(name, "substr")) {
-                       if (Z_TYPE_P(args[0]) != IS_STRING
-                                       || Z_TYPE_P(args[1]) != IS_LONG
-                                       || Z_TYPE_P(args[2]) != IS_LONG) {
+
+                       ini_entry = zend_hash_find_ptr(EG(ini_directives), Z_STR_P(args[0]));
+                       if (!ini_entry) {
+                               ZVAL_FALSE(result);
+                       } else if (ini_entry->modifiable != ZEND_INI_SYSTEM) {
                                return FAILURE;
+                       } else if (ini_entry->value) {
+                               ZVAL_STR_COPY(result, ini_entry->value);
+                       } else {
+                               ZVAL_EMPTY_STRING(result);
                        }
-                       /* pass */
-               } else {
-                       return FAILURE;
+                       return SUCCESS;
                }
-       } else {
-               return FAILURE;
        }
 
-       func = zend_hash_find_ptr(CG(function_table), name);
-       if (!func || func->type != ZEND_INTERNAL_FUNCTION) {
+       if (!can_ct_eval_func_call(name, num_args, args)) {
                return FAILURE;
        }