From e902b870aac8f9243b855ab2207428b88179c2a5 Mon Sep 17 00:00:00 2001 From: Robert Fancsik Date: Fri, 5 Jul 2019 12:21:48 +0200 Subject: [PATCH] Limit the call stack size for native/builtin functions as well (#2935) VM_RECURSION_LIMIT only prevented the recursion of interpreted codeblocks but native/builtin function calls can also create stack overflow due to the too deep recursion. This patch fixes #2905. Co-authored-by: Gabor Loki loki@inf.u-szeged.hu JerryScript-DCO-1.0-Signed-off-by: Robert Fancsik frobert@inf.u-szeged.hu --- jerry-core/CMakeLists.txt | 10 ++--- jerry-core/config.h | 8 ++-- jerry-core/ecma/base/ecma-init-finalize.c | 6 +-- .../ecma/operations/ecma-function-object.c | 40 +++++++++++++++++-- jerry-core/jcontext/jcontext.h | 6 +-- jerry-core/vm/vm.c | 21 ++-------- tests/jerry/regression-test-issue-2905.js | 29 ++++++++++++++ tests/jerry/vm-recursion-limit.js | 4 +- tools/build.py | 12 +++--- tools/run-tests.py | 18 ++++----- 10 files changed, 100 insertions(+), 54 deletions(-) create mode 100644 tests/jerry/regression-test-issue-2905.js diff --git a/jerry-core/CMakeLists.txt b/jerry-core/CMakeLists.txt index a9b913c90..f7fc2c682 100644 --- a/jerry-core/CMakeLists.txt +++ b/jerry-core/CMakeLists.txt @@ -40,7 +40,7 @@ set(FEATURE_VALGRIND OFF CACHE BOOL "Enable Valgrind suppor set(FEATURE_VM_EXEC_STOP OFF CACHE BOOL "Enable VM execution stopping?") set(JERRY_GLOBAL_HEAP_SIZE "512" CACHE STRING "Size of memory heap, in kilobytes") set(JERRY_REGEXP_RECURSION_LIMIT "0" CACHE STRING "Limit of regexp recursion depth") -set(JERRY_VM_RECURSION_LIMIT "0" CACHE STRING "Limit of VM recursion depth") +set(JERRY_CALL_STACK_LIMIT "0" CACHE STRING "Limit of function call recursion depth") # Option overrides if(USING_MSVC) @@ -103,7 +103,7 @@ message(STATUS "FEATURE_VALGRIND " ${FEATURE_VALGRIND}) message(STATUS "FEATURE_VM_EXEC_STOP " ${FEATURE_VM_EXEC_STOP}) message(STATUS "JERRY_GLOBAL_HEAP_SIZE " ${JERRY_GLOBAL_HEAP_SIZE}) message(STATUS "JERRY_REGEXP_RECURSION_LIMIT " ${JERRY_REGEXP_RECURSION_LIMIT}) -message(STATUS "JERRY_VM_RECURSION_LIMIT " ${JERRY_VM_RECURSION_LIMIT}) +message(STATUS "JERRY_CALL_STACK_LIMIT " ${JERRY_CALL_STACK_LIMIT}) # Include directories set(INCLUDE_CORE_PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") @@ -293,9 +293,9 @@ if(JERRY_REGEXP_RECURSION_LIMIT) set(DEFINES_JERRY ${DEFINES_JERRY} JERRY_REGEXP_RECURSION_LIMIT=${JERRY_REGEXP_RECURSION_LIMIT}) endif() -# VM recursion depth limit -if(JERRY_VM_RECURSION_LIMIT) - set(DEFINES_JERRY ${DEFINES_JERRY} JERRY_VM_RECURSION_LIMIT=${JERRY_VM_RECURSION_LIMIT}) +# Function call recursion depth limit +if(JERRY_CALL_STACK_LIMIT) + set(DEFINES_JERRY ${DEFINES_JERRY} JERRY_CALL_STACK_LIMIT=${JERRY_CALL_STACK_LIMIT}) endif() # RegExp byte-code dumps diff --git a/jerry-core/config.h b/jerry-core/config.h index 96bc6be65..835521c7b 100644 --- a/jerry-core/config.h +++ b/jerry-core/config.h @@ -436,7 +436,7 @@ #endif /* !defined (JERRY_VM_EXEC_STOP) */ /** - * Set the VM execution recursion limit. + * Set the function call recursion limit. * * Allowed values: * 0: Disable recursion limit check. @@ -447,9 +447,9 @@ * * Default value: 0 */ -#ifndef JERRY_VM_RECURSION_LIMIT -# define JERRY_VM_RECURSION_LIMIT 0 -#endif /* !defined (JERRY_VM_RECURSION_LIMIT) */ +#ifndef JERRY_CALL_STACK_LIMIT +# define JERRY_CALL_STACK_LIMIT 0 +#endif /* !defined (JERRY_CALL_STACK_LIMIT) */ /** diff --git a/jerry-core/ecma/base/ecma-init-finalize.c b/jerry-core/ecma/base/ecma-init-finalize.c index 27b1c5b04..040456087 100644 --- a/jerry-core/ecma/base/ecma-init-finalize.c +++ b/jerry-core/ecma/base/ecma-init-finalize.c @@ -44,9 +44,9 @@ ecma_init (void) JERRY_CONTEXT (status_flags) &= (uint32_t) ~ECMA_STATUS_HIGH_SEV_GC; #endif /* ENABLED (JERRY_PROPRETY_HASHMAP) */ -#if defined (JERRY_VM_RECURSION_LIMIT) && (JERRY_VM_RECURSION_LIMIT != 0) - JERRY_CONTEXT (vm_recursion_counter) = JERRY_VM_RECURSION_LIMIT; -#endif /* defined (JERRY_VM_RECURSION_LIMIT) && (JERRY_VM_RECURSION_LIMIT != 0) */ +#if defined (JERRY_CALL_STACK_LIMIT) && (JERRY_CALL_STACK_LIMIT != 0) + JERRY_CONTEXT (function_call_counter) = JERRY_CALL_STACK_LIMIT; +#endif /* defined (JERRY_CALL_STACK_LIMIT) && (JERRY_CALL_STACK_LIMIT != 0) */ #if ENABLED (JERRY_ES2015_BUILTIN_PROMISE) ecma_job_queue_init (); diff --git a/jerry-core/ecma/operations/ecma-function-object.c b/jerry-core/ecma/operations/ecma-function-object.c index 49165f8d3..2bbb27035 100644 --- a/jerry-core/ecma/operations/ecma-function-object.c +++ b/jerry-core/ecma/operations/ecma-function-object.c @@ -705,6 +705,17 @@ ecma_op_function_call (ecma_object_t *func_obj_p, /**< Function object */ || ecma_get_object_type (func_obj_p) == ECMA_OBJECT_TYPE_BOUND_FUNCTION || !ecma_op_function_has_construct_flag (arguments_list_p)); +#if defined (JERRY_CALL_STACK_LIMIT) && (JERRY_CALL_STACK_LIMIT != 0) + if (JERRY_UNLIKELY (JERRY_CONTEXT (function_call_counter) == 0)) + { + return ecma_raise_range_error (ECMA_ERR_MSG ("Maximum call stack size is exceeded.")); + } + else + { + JERRY_CONTEXT (function_call_counter)--; + } +#endif /* defined (JERRY_CALL_STACK_LIMIT) && (JERRY_CALL_STACK_LIMIT != 0) */ + switch (ecma_get_object_type (func_obj_p)) { case ECMA_OBJECT_TYPE_FUNCTION: @@ -713,10 +724,16 @@ ecma_op_function_call (ecma_object_t *func_obj_p, /**< Function object */ { JERRY_ASSERT (!ecma_op_function_has_construct_flag (arguments_list_p)); - return ecma_builtin_dispatch_call (func_obj_p, - this_arg_value, - arguments_list_p, - arguments_list_len); + ecma_value_t ret_value = ecma_builtin_dispatch_call (func_obj_p, + this_arg_value, + arguments_list_p, + arguments_list_len); + +#if defined (JERRY_CALL_STACK_LIMIT) && (JERRY_CALL_STACK_LIMIT != 0) + JERRY_CONTEXT (function_call_counter)++; +#endif /* defined (JERRY_CALL_STACK_LIMIT) && (JERRY_CALL_STACK_LIMIT != 0) */ + + return ret_value; } /* Entering Function Code (ECMA-262 v5, 10.4.3) */ @@ -806,6 +823,10 @@ ecma_op_function_call (ecma_object_t *func_obj_p, /**< Function object */ ecma_free_value (this_binding); } +#if defined (JERRY_CALL_STACK_LIMIT) && (JERRY_CALL_STACK_LIMIT != 0) + JERRY_CONTEXT (function_call_counter)++; +#endif /* defined (JERRY_CALL_STACK_LIMIT) && (JERRY_CALL_STACK_LIMIT != 0) */ + return ret_value; } case ECMA_OBJECT_TYPE_EXTERNAL_FUNCTION: @@ -816,6 +837,9 @@ ecma_op_function_call (ecma_object_t *func_obj_p, /**< Function object */ this_arg_value, arguments_list_p, arguments_list_len); +#if defined (JERRY_CALL_STACK_LIMIT) && (JERRY_CALL_STACK_LIMIT != 0) + JERRY_CONTEXT (function_call_counter)++; +#endif /* defined (JERRY_CALL_STACK_LIMIT) && (JERRY_CALL_STACK_LIMIT != 0) */ if (JERRY_UNLIKELY (ecma_is_value_error_reference (ret_value))) { @@ -864,6 +888,10 @@ ecma_op_function_call (ecma_object_t *func_obj_p, /**< Function object */ ecma_deref_object (local_env_p); } +#if defined (JERRY_CALL_STACK_LIMIT) && (JERRY_CALL_STACK_LIMIT != 0) + JERRY_CONTEXT (function_call_counter)++; +#endif /* defined (JERRY_CALL_STACK_LIMIT) && (JERRY_CALL_STACK_LIMIT != 0) */ + return ret_value; } #endif /* ENABLED (JERRY_ES2015_ARROW_FUNCTION) */ @@ -874,6 +902,10 @@ ecma_op_function_call (ecma_object_t *func_obj_p, /**< Function object */ } } +#if defined (JERRY_CALL_STACK_LIMIT) && (JERRY_CALL_STACK_LIMIT != 0) + JERRY_CONTEXT (function_call_counter)++; +#endif /* defined (JERRY_CALL_STACK_LIMIT) && (JERRY_CALL_STACK_LIMIT != 0) */ + JERRY_CONTEXT (status_flags) &= (uint32_t) ~ECMA_STATUS_DIRECT_EVAL; ecma_extended_object_t *ext_function_p; diff --git a/jerry-core/jcontext/jcontext.h b/jerry-core/jcontext/jcontext.h index f10988fe7..5b027a65a 100644 --- a/jerry-core/jcontext/jcontext.h +++ b/jerry-core/jcontext/jcontext.h @@ -170,9 +170,9 @@ struct jerry_context_t * ECMAScript execution should be stopped */ #endif /* ENABLED (JERRY_VM_EXEC_STOP) */ -#if defined (JERRY_VM_RECURSION_LIMIT) && (JERRY_VM_RECURSION_LIMIT != 0) - uint32_t vm_recursion_counter; /**< VM recursion counter */ -#endif /* defined (JERRY_VM_RECURSION_LIMIT) && (JERRY_VM_RECURSION_LIMIT != 0) */ +#if defined (JERRY_CALL_STACK_LIMIT) && (JERRY_CALL_STACK_LIMIT != 0) + uint32_t function_call_counter; /**< Function call recursion counter */ +#endif /* defined (JERRY_CALL_STACK_LIMIT) && (JERRY_CALL_STACK_LIMIT != 0) */ #if ENABLED (JERRY_DEBUGGER) uint8_t debugger_send_buffer[JERRY_DEBUGGER_TRANSPORT_MAX_BUFFER_SIZE]; /**< buffer for sending messages */ diff --git a/jerry-core/vm/vm.c b/jerry-core/vm/vm.c index 9fde9d364..0330a4595 100644 --- a/jerry-core/vm/vm.c +++ b/jerry-core/vm/vm.c @@ -46,9 +46,9 @@ /* * Check VM recursion depth limit */ -#if defined (JERRY_VM_RECURSION_LIMIT) && (JERRY_VM_RECURSION_LIMIT != 0) -JERRY_STATIC_ASSERT (JERRY_VM_RECURSION_LIMIT > 0, vm_recursion_limit_must_be_greater_than_zero); -#endif /* defined (JERRY_VM_RECURSION_LIMIT) && (JERRY_VM_RECURSION_LIMIT != 0) */ +#if defined (JERRY_CALL_STACK_LIMIT) && (JERRY_CALL_STACK_LIMIT != 0) +JERRY_STATIC_ASSERT (JERRY_CALL_STACK_LIMIT > 0, function_call_recursion_limit_must_be_greater_than_zero); +#endif /* defined (JERRY_CALL_STACK_LIMIT) && (JERRY_CALL_STACK_LIMIT != 0) */ /** * Get the value of object[property]. @@ -3605,10 +3605,6 @@ vm_execute (vm_frame_ctx_t *frame_ctx_p, /**< frame context */ } #endif /* ENABLED (JERRY_DEBUGGER) */ -#if defined (JERRY_VM_RECURSION_LIMIT) && (JERRY_VM_RECURSION_LIMIT != 0) - JERRY_CONTEXT (vm_recursion_counter)++; -#endif /* defined (JERRY_VM_RECURSION_LIMIT) && (JERRY_VM_RECURSION_LIMIT != 0) */ - JERRY_CONTEXT (vm_top_context_p) = prev_context_p; return completion_value; } @@ -3629,17 +3625,6 @@ vm_run (const ecma_compiled_code_t *bytecode_header_p, /**< byte-code data heade const ecma_value_t *arg_list_p, /**< arguments list */ ecma_length_t arg_list_len) /**< length of arguments list */ { -#if defined (JERRY_VM_RECURSION_LIMIT) && (JERRY_VM_RECURSION_LIMIT != 0) - if (JERRY_UNLIKELY (JERRY_CONTEXT (vm_recursion_counter) == 0)) - { - return ecma_raise_range_error (ECMA_ERR_MSG ("VM recursion limit is exceeded.")); - } - else - { - JERRY_CONTEXT (vm_recursion_counter)--; - } -#endif /* defined (JERRY_VM_RECURSION_LIMIT) && (JERRY_VM_RECURSION_LIMIT != 0) */ - ecma_value_t *literal_p; vm_frame_ctx_t frame_ctx; uint32_t call_stack_size; diff --git a/tests/jerry/regression-test-issue-2905.js b/tests/jerry/regression-test-issue-2905.js new file mode 100644 index 000000000..4a59b48a8 --- /dev/null +++ b/tests/jerry/regression-test-issue-2905.js @@ -0,0 +1,29 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var func = function () { + foo.prototype = new Array(1, 2, 3); + function foo() {} + var f = new foo(); + f.length = f; + try { + var a = "Using f will give an error: " + f; + assert(false); + } catch (e) { + assert(e instanceof RangeError); + } +}; + +func(); diff --git a/tests/jerry/vm-recursion-limit.js b/tests/jerry/vm-recursion-limit.js index d3ee4ac2e..3fac580a0 100644 --- a/tests/jerry/vm-recursion-limit.js +++ b/tests/jerry/vm-recursion-limit.js @@ -13,7 +13,7 @@ // limitations under the License. /* Note: if the tests suite vm-recursion-limit changes, this variable must be changed as well */ -var limit = 1000; +var limit = 100; var counter = 0; function f () { @@ -26,5 +26,5 @@ try { assert (false); } catch (e) { assert (e instanceof RangeError); - assert (counter === (limit - 1)); + assert (counter === limit); } diff --git a/tools/build.py b/tools/build.py index fbc7bc23d..11187f647 100755 --- a/tools/build.py +++ b/tools/build.py @@ -130,8 +130,8 @@ def get_arguments(): help=devhelp('enable regexp strict mode (%(choices)s)')) coregrp.add_argument('--regexp-recursion-limit', metavar='N', type=int, help='regexp recursion depth limit') - coregrp.add_argument('--vm-recursion-limit', metavar='N', type=int, - help='VM recursion depth limit') + coregrp.add_argument('--call-stack-limit', metavar='N', type=int, + help='Function call recursion depth limit') coregrp.add_argument('--show-opcodes', metavar='X', choices=['ON', 'OFF'], type=str.upper, help=devhelp('enable parser byte-code dumps (%(choices)s)')) coregrp.add_argument('--show-regexp-opcodes', metavar='X', choices=['ON', 'OFF'], type=str.upper, @@ -156,9 +156,9 @@ def get_arguments(): parser.print_help() sys.exit(0) - if arguments.vm_recursion_limit: - if arguments.vm_recursion_limit < 0: - print ('Configuration error: VM recursion limit must be greater or equal than 0') + if arguments.call_stack_limit: + if arguments.call_stack_limit < 0: + print ('Configuration error: Function call recursion limit must be greater or equal than 0') sys.exit(1) return arguments @@ -207,7 +207,7 @@ def generate_build_options(arguments): build_options_append('FEATURE_PROFILE', arguments.profile) build_options_append('FEATURE_REGEXP_STRICT_MODE', arguments.regexp_strict_mode) build_options_append('JERRY_REGEXP_RECURSION_LIMIT', arguments.regexp_recursion_limit) - build_options_append('JERRY_VM_RECURSION_LIMIT', arguments.vm_recursion_limit) + build_options_append('JERRY_CALL_STACK_LIMIT', arguments.call_stack_limit) build_options_append('FEATURE_PARSER_DUMP', arguments.show_opcodes) build_options_append('FEATURE_REGEXP_DUMP', arguments.show_regexp_opcodes) build_options_append('FEATURE_SNAPSHOT_EXEC', arguments.snapshot_exec) diff --git a/tools/run-tests.py b/tools/run-tests.py index 8dd2a5020..f304479b3 100755 --- a/tools/run-tests.py +++ b/tools/run-tests.py @@ -36,7 +36,7 @@ def skip_if(condition, desc): OPTIONS_PROFILE_MIN = ['--profile=minimal'] OPTIONS_PROFILE_ES51 = [] # NOTE: same as ['--profile=es5.1'] OPTIONS_PROFILE_ES2015 = ['--profile=es2015-subset'] -OPTIONS_VM_RECURSION_LIMIT = ['--vm-recursion-limit=1000'] +OPTIONS_CALL_STACK_LIMIT = ['--call-stack-limit=100'] OPTIONS_DEBUG = ['--debug'] OPTIONS_SNAPSHOT = ['--snapshot-save=on', '--snapshot-exec=on', '--jerry-cmdline-snapshot=on'] OPTIONS_UNITTESTS = ['--unittests=on', '--jerry-cmdline=off', '--error-messages=on', @@ -68,22 +68,22 @@ JERRY_UNITTESTS_OPTIONS = [ # Test options for jerry-tests JERRY_TESTS_OPTIONS = [ Options('jerry_tests-es5.1', - OPTIONS_PROFILE_ES51 + OPTIONS_VM_RECURSION_LIMIT), + OPTIONS_PROFILE_ES51 + OPTIONS_CALL_STACK_LIMIT), Options('jerry_tests-es5.1-snapshot', - OPTIONS_PROFILE_ES51 + OPTIONS_SNAPSHOT + OPTIONS_VM_RECURSION_LIMIT, + OPTIONS_PROFILE_ES51 + OPTIONS_SNAPSHOT + OPTIONS_CALL_STACK_LIMIT, ['--snapshot']), Options('jerry_tests-es5.1-debug', - OPTIONS_PROFILE_ES51 + OPTIONS_DEBUG + OPTIONS_VM_RECURSION_LIMIT), + OPTIONS_PROFILE_ES51 + OPTIONS_DEBUG + OPTIONS_CALL_STACK_LIMIT), Options('jerry_tests-es5.1-debug-snapshot', - OPTIONS_PROFILE_ES51 + OPTIONS_SNAPSHOT + OPTIONS_DEBUG + OPTIONS_VM_RECURSION_LIMIT, + OPTIONS_PROFILE_ES51 + OPTIONS_SNAPSHOT + OPTIONS_DEBUG + OPTIONS_CALL_STACK_LIMIT, ['--snapshot']), Options('jerry_tests-es5.1-debug-cpointer_32bit', - OPTIONS_PROFILE_ES51 + OPTIONS_DEBUG + OPTIONS_VM_RECURSION_LIMIT + OPTIONS_PROFILE_ES51 + OPTIONS_DEBUG + OPTIONS_CALL_STACK_LIMIT + ['--cpointer-32bit=on', '--mem-heap=1024']), Options('jerry_tests-es5.1-debug-external_context', - OPTIONS_PROFILE_ES51 + OPTIONS_DEBUG + OPTIONS_VM_RECURSION_LIMIT + ['--external-context=on']), + OPTIONS_PROFILE_ES51 + OPTIONS_DEBUG + OPTIONS_CALL_STACK_LIMIT + ['--external-context=on']), Options('jerry_tests-es2015_subset-debug', - OPTIONS_PROFILE_ES2015 + OPTIONS_DEBUG + OPTIONS_VM_RECURSION_LIMIT), + OPTIONS_PROFILE_ES2015 + OPTIONS_DEBUG + OPTIONS_CALL_STACK_LIMIT), ] # Test options for jerry-test-suite @@ -160,7 +160,7 @@ JERRY_BUILDOPTIONS = [ Options('buildoption_test-regexp_recursion_limit', ['--regexp-recursion-limit=1000']), Options('buildoption_test-vm_recursion_limit', - OPTIONS_VM_RECURSION_LIMIT), + OPTIONS_CALL_STACK_LIMIT), Options('buildoption_test-single-source', ['--cmake-param=-DENABLE_ALL_IN_ONE_SOURCE=ON']), ]