From 6b9c924d08149c94ec0f88ae4ca1abf7e75900fb Mon Sep 17 00:00:00 2001 From: Robert Fancsik Date: Fri, 1 Feb 2019 15:32:26 +0100 Subject: [PATCH] Add recursion limit for VM (#2737) This patch adds posibility to supervise the VM call stack to avoid aborts/crashes due to the recursion calls. JerryScript-DCO-1.0-Signed-off-by: Robert Fancsik frobert@inf.u-szeged.hu --- jerry-core/CMakeLists.txt | 7 ++++++ jerry-core/ecma/base/ecma-init-finalize.c | 4 +++ jerry-core/jcontext/jcontext.h | 4 +++ jerry-core/vm/vm.c | 22 +++++++++++++++++ tests/jerry/vm-recursion-limit.js | 30 +++++++++++++++++++++++ tools/build.py | 8 ++++++ tools/run-tests.py | 18 ++++++++------ 7 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 tests/jerry/vm-recursion-limit.js diff --git a/jerry-core/CMakeLists.txt b/jerry-core/CMakeLists.txt index 40f383bbd..cd4901afc 100644 --- a/jerry-core/CMakeLists.txt +++ b/jerry-core/CMakeLists.txt @@ -40,6 +40,7 @@ set(FEATURE_VALGRIND OFF CACHE BOOL "Enable Valgrind support?") set(FEATURE_VM_EXEC_STOP OFF CACHE BOOL "Enable VM execution stopping?") set(MEM_HEAP_SIZE_KB "512" CACHE STRING "Size of memory heap, in kilobytes") set(REGEXP_RECURSION_LIMIT "0" CACHE STRING "Limit of regexp recursion depth") +set(VM_RECURSION_LIMIT "0" CACHE STRING "Limit of VM recursion depth") # Option overrides if(USING_MSVC) @@ -96,6 +97,7 @@ message(STATUS "FEATURE_VALGRIND " ${FEATURE_VALGRIND}) message(STATUS "FEATURE_VM_EXEC_STOP " ${FEATURE_VM_EXEC_STOP}) message(STATUS "MEM_HEAP_SIZE_KB " ${MEM_HEAP_SIZE_KB}) message(STATUS "REGEXP_RECURSION_LIMIT " ${REGEXP_RECURSION_LIMIT}) +message(STATUS "VM_RECURSION_LIMIT " ${VM_RECURSION_LIMIT}) # Include directories set(INCLUDE_CORE_PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") @@ -235,6 +237,11 @@ if(REGEXP_RECURSION_LIMIT) set(DEFINES_JERRY ${DEFINES_JERRY} REGEXP_RECURSION_LIMIT=${REGEXP_RECURSION_LIMIT}) endif() +# VM recursion depth limit +if(VM_RECURSION_LIMIT) + set(DEFINES_JERRY ${DEFINES_JERRY} VM_RECURSION_LIMIT=${VM_RECURSION_LIMIT}) +endif() + # RegExp byte-code dumps if(FEATURE_REGEXP_DUMP) set(DEFINES_JERRY ${DEFINES_JERRY} REGEXP_DUMP_BYTE_CODE) diff --git a/jerry-core/ecma/base/ecma-init-finalize.c b/jerry-core/ecma/base/ecma-init-finalize.c index e92781d66..2dba5920e 100644 --- a/jerry-core/ecma/base/ecma-init-finalize.c +++ b/jerry-core/ecma/base/ecma-init-finalize.c @@ -44,6 +44,10 @@ ecma_init (void) JERRY_CONTEXT (status_flags) &= (uint32_t) ~ECMA_STATUS_HIGH_SEV_GC; #endif /* !CONFIG_ECMA_PROPERTY_HASHMAP_DISABLE */ +#ifdef VM_RECURSION_LIMIT + JERRY_CONTEXT (vm_recursion_counter) = VM_RECURSION_LIMIT; +#endif /* VM_RECURSION_LIMIT */ + #ifndef CONFIG_DISABLE_ES2015_PROMISE_BUILTIN ecma_job_queue_init (); #endif /* CONFIG_DISABLE_ES2015_PROMISE_BUILTIN */ diff --git a/jerry-core/jcontext/jcontext.h b/jerry-core/jcontext/jcontext.h index 7a0dfb1b3..3f75dbea6 100644 --- a/jerry-core/jcontext/jcontext.h +++ b/jerry-core/jcontext/jcontext.h @@ -136,6 +136,10 @@ struct jerry_context_t * ECMAScript execution should be stopped */ #endif /* JERRY_VM_EXEC_STOP */ +#ifdef VM_RECURSION_LIMIT + uint32_t vm_recursion_counter; /**< VM recursion counter */ +#endif /* VM_RECURSION_LIMIT */ + #ifdef JERRY_DEBUGGER uint8_t debugger_send_buffer[JERRY_DEBUGGER_TRANSPORT_MAX_BUFFER_SIZE]; /**< buffer for sending messages */ uint8_t debugger_receive_buffer[JERRY_DEBUGGER_TRANSPORT_MAX_BUFFER_SIZE]; /**< buffer for receiving messages */ diff --git a/jerry-core/vm/vm.c b/jerry-core/vm/vm.c index 51c14c2b8..a87f7aa91 100644 --- a/jerry-core/vm/vm.c +++ b/jerry-core/vm/vm.c @@ -42,6 +42,13 @@ * @{ */ +/* + * Check VM recursion depth limit + */ +#ifdef VM_RECURSION_LIMIT +JERRY_STATIC_ASSERT (VM_RECURSION_LIMIT > 0, vm_recursion_limit_must_be_greater_than_zero); +#endif /* VM_RECURSION_LIMIT */ + /** * Get the value of object[property]. * @@ -3515,6 +3522,10 @@ vm_execute (vm_frame_ctx_t *frame_ctx_p, /**< frame context */ } #endif /* JERRY_DEBUGGER */ +#ifdef VM_RECURSION_LIMIT + JERRY_CONTEXT (vm_recursion_counter)++; +#endif /* VM_RECURSION_LIMIT */ + JERRY_CONTEXT (vm_top_context_p) = frame_ctx_p->prev_context_p; return completion_value; } @@ -3535,6 +3546,17 @@ 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 */ { +#ifdef VM_RECURSION_LIMIT + 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 /* VM_RECURSION_LIMIT */ + ecma_value_t *literal_p; vm_frame_ctx_t frame_ctx; uint32_t call_stack_size; diff --git a/tests/jerry/vm-recursion-limit.js b/tests/jerry/vm-recursion-limit.js new file mode 100644 index 000000000..d3ee4ac2e --- /dev/null +++ b/tests/jerry/vm-recursion-limit.js @@ -0,0 +1,30 @@ +// 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. + +/* Note: if the tests suite vm-recursion-limit changes, this variable must be changed as well */ +var limit = 1000; +var counter = 0; + +function f () { + counter++; + return f (); +} + +try { + f (); + assert (false); +} catch (e) { + assert (e instanceof RangeError); + assert (counter === (limit - 1)); +} diff --git a/tools/build.py b/tools/build.py index 08df9c275..60106c071 100755 --- a/tools/build.py +++ b/tools/build.py @@ -128,6 +128,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('--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, @@ -152,6 +154,11 @@ 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') + sys.exit(1) + return arguments def generate_build_options(arguments): @@ -197,6 +204,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('REGEXP_RECURSION_LIMIT', arguments.regexp_recursion_limit) + build_options_append('VM_RECURSION_LIMIT', arguments.vm_recursion_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 8c5a2fea6..33cc1674a 100755 --- a/tools/run-tests.py +++ b/tools/run-tests.py @@ -36,6 +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_DEBUG = ['--debug'] OPTIONS_SNAPSHOT = ['--snapshot-save=on', '--snapshot-exec=on', '--jerry-cmdline-snapshot=on'] OPTIONS_UNITTESTS = ['--unittests=on', '--jerry-cmdline=off', '--error-messages=on', @@ -67,21 +68,22 @@ JERRY_UNITTESTS_OPTIONS = [ # Test options for jerry-tests JERRY_TESTS_OPTIONS = [ Options('jerry_tests-es5.1', - OPTIONS_PROFILE_ES51), + OPTIONS_PROFILE_ES51 + OPTIONS_VM_RECURSION_LIMIT), Options('jerry_tests-es5.1-snapshot', - OPTIONS_PROFILE_ES51 + OPTIONS_SNAPSHOT, + OPTIONS_PROFILE_ES51 + OPTIONS_SNAPSHOT + OPTIONS_VM_RECURSION_LIMIT, ['--snapshot']), Options('jerry_tests-es5.1-debug', - OPTIONS_PROFILE_ES51 + OPTIONS_DEBUG), + OPTIONS_PROFILE_ES51 + OPTIONS_DEBUG + OPTIONS_VM_RECURSION_LIMIT), Options('jerry_tests-es5.1-debug-snapshot', - OPTIONS_PROFILE_ES51 + OPTIONS_SNAPSHOT + OPTIONS_DEBUG, + OPTIONS_PROFILE_ES51 + OPTIONS_SNAPSHOT + OPTIONS_DEBUG + OPTIONS_VM_RECURSION_LIMIT, ['--snapshot']), Options('jerry_tests-es5.1-debug-cpointer_32bit', - OPTIONS_PROFILE_ES51 + OPTIONS_DEBUG + ['--cpointer-32bit=on', '--mem-heap=1024']), + OPTIONS_PROFILE_ES51 + OPTIONS_DEBUG + OPTIONS_VM_RECURSION_LIMIT + + ['--cpointer-32bit=on', '--mem-heap=1024']), Options('jerry_tests-es5.1-debug-external_context', - OPTIONS_PROFILE_ES51 + OPTIONS_DEBUG + ['--external-context=on']), + OPTIONS_PROFILE_ES51 + OPTIONS_DEBUG + OPTIONS_VM_RECURSION_LIMIT + ['--external-context=on']), Options('jerry_tests-es2015_subset-debug', - OPTIONS_PROFILE_ES2015 + OPTIONS_DEBUG), + OPTIONS_PROFILE_ES2015 + OPTIONS_DEBUG + OPTIONS_VM_RECURSION_LIMIT), ] # Test options for jerry-test-suite @@ -156,6 +158,8 @@ JERRY_BUILDOPTIONS = [ ['--jerry-cmdline-snapshot=on']), Options('buildoption_test-regexp_recursion_limit', ['--regexp-recursion-limit=1000']), + Options('buildoption_test-vm_recursion_limit', + OPTIONS_VM_RECURSION_LIMIT), ] def get_arguments():