├── corpus_visualizer └── todo.txt ├── Docs └── Master_Thesis_Freingruber_Browser_Exploits.pdf ├── generators └── todo.txt ├── debugging ├── README.md ├── check_if_testcase_is_permanently_disabled.py ├── display_state_of_testcase.py ├── find_testcase_with_long_runtime.py ├── calculate_number_new_edges_of_testcase.py ├── find_template_with_wrong_number_of_lines.py ├── modify_pickle_databases.py ├── find_testcase_with_datatype.py ├── check_if_all_files_dont_trigger_an_exception.py ├── find_source_of_bug.py ├── find_testcase_with_substr.py ├── find_testcase_with_wrong_variable_name.py ├── display_coverage_map_triggered_edges.py ├── calculate_number_of_insertion_lines_of_corpus.py └── display_pickle_database_variable_operations.py ├── automation_gce ├── check_gce_instance_status.sh ├── stop_old_gce_instances.py ├── shutdown_fuzzing_16.sh ├── ensure_all_gce_instances_are_running.py ├── remove_old_not_running_gce_instances.py ├── start_fuzzing_16.sh └── start_gce_instances.sh ├── native_code ├── compile.sh ├── setup.py ├── coverage_helpers.py └── example_usage_speed_optimized_functions.py ├── bash_scripts ├── count_lines_of_code.sh ├── install_requirements_gce.sh └── prepare_system_for_fuzzing.sh ├── mutators └── implementations │ ├── mutation_do_nothing.py │ ├── mutation_insert_random_operation_at_specific_line.py │ ├── mutation_if_wrap_operations.py │ ├── mutation_insert_random_operation_from_database.py │ ├── mutation_add_variable.py │ ├── mutation_insert_random_operation.py │ ├── mutation_insert_multiple_random_operations_from_database.py │ ├── mutation_for_wrap_line.py │ ├── mutation_while_wrap_line.py │ ├── mutation_for_wrap_operations.py │ ├── mutation_change_proto.py │ ├── mutation_wrap_value_in_function.py │ ├── mutation_wrap_string_in_function.py │ ├── mutation_wrap_variable_in_function.py │ ├── mutation_wrap_value_in_function_argument.py │ ├── mutation_wrap_string_in_function_argument.py │ ├── mutation_wrap_variable_in_function_argument.py │ ├── mutation_change_prototype.py │ ├── mutation_modify_string.py │ ├── mutation_materialize_values.py │ ├── mutation_modify_number.py │ ├── mutation_replace_number.py │ └── mutation_if_wrap_line.py ├── .gitignore ├── standardizer ├── implementations │ ├── remove_shebang.py │ ├── add_possible_required_newlines.py │ ├── remove_semicolon_lines.py │ ├── rename_classes.py │ ├── rename_functions.py │ ├── rename_variables.py │ └── add_newlines.py └── standardizer_helpers.py ├── result_analysis ├── download_bucket.sh ├── check_if_crash_can_be_triggered_with_previous_files.py └── 1.1_sync_files_from_gce_bucket.py ├── watchdog.sh ├── testcase_mergers └── implementations │ ├── merge_testcase_into_JIT_compiled_function.py │ ├── merge_testcase_append.py │ └── merge_testcase_insert.py ├── minimizer ├── implementations │ ├── remove_not_referenced_functions.py │ ├── replace_throw_instructions.py │ ├── replace_strings.py │ ├── remove_body.py │ ├── remove_line_by_line.py │ ├── remove_unused_variables_from_function_headers.py │ ├── remove_not_required_function_contexts.py │ ├── ensure_tokens_are_contiguous.py │ └── remove_line_by_line_multiline.py └── minimizer_helpers.py ├── testsuite ├── implementations │ ├── test_sync.py │ └── test_corpus_quality.py └── execute_testsuite.py ├── callback_injector └── callback_injector_helpers.py ├── modes ├── fix_corpus.py ├── recalculate_state.py ├── recalculate_testcase_runtimes.py └── developer_mode.py ├── profilers └── profile_performance_of_fuzzer.sh └── commit_checker └── entities └── bug_report.py /corpus_visualizer/todo.txt: -------------------------------------------------------------------------------- 1 | TODO: Needs to be implemented -------------------------------------------------------------------------------- /Docs/Master_Thesis_Freingruber_Browser_Exploits.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freingruber/JavaScript-Raider/HEAD/Docs/Master_Thesis_Freingruber_Browser_Exploits.pdf -------------------------------------------------------------------------------- /generators/todo.txt: -------------------------------------------------------------------------------- 1 | At the moment the "generator" code is still stored in JS_FUZZER.py. 2 | For the future, I plan to separate the code and introduce generators which allow a better configuration of them. -------------------------------------------------------------------------------- /debugging/README.md: -------------------------------------------------------------------------------- 1 | # Debugging Scripts 2 | 3 | This folder contains different debugging scripts which I use / used during development. 4 | For examples scripts to detect which testcases have a specific property. 5 | Or scripts to display the state of a testcase. 6 | 7 | Some scripts maybe don't work at the moment. 8 | Others are very old and are likely not required anymore. 9 | -------------------------------------------------------------------------------- /automation_gce/check_gce_instance_status.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2022 @ReneFreingruber 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | echo "All terminated instances:" 18 | gcloud compute instances list | grep TERMINATED 19 | -------------------------------------------------------------------------------- /native_code/compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2022 @ReneFreingruber 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | rm -rf build/ 19 | rm libJSEngine.so 20 | python3 setup.py build 21 | mv build/lib.*/libJSEngine*.so libJSEngine.so 22 | 23 | -------------------------------------------------------------------------------- /bash_scripts/count_lines_of_code.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2022 @ReneFreingruber 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Just a quick command to check the number of LoC during refactoring 18 | 19 | # sudo apt install cloc 20 | cloc --exclude-dir=__pycache__,.idea,TODOs,venv ../ 21 | -------------------------------------------------------------------------------- /native_code/setup.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from distutils.core import setup, Extension 16 | MOD = "libJSEngine" 17 | setup(name=MOD, ext_modules=[Extension(MOD, sources=['libJSEngine.c'], libraries=['rt'],)], description="libJSEngine") 18 | -------------------------------------------------------------------------------- /mutators/implementations/mutation_do_nothing.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # This mutation is just used for testing purpose (to skip mutations) 18 | 19 | import utils 20 | import tagging_engine.tagging as tagging 21 | from tagging_engine.tagging import Tag 22 | 23 | 24 | def mutation_do_nothing(content, state): 25 | # utils.dbg_msg("Mutation operation: Do nothing") 26 | tagging.add_tag(Tag.MUTATION_DO_NOTHING1) 27 | return content, state 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.py[cod] 3 | *$py.class 4 | *.pyc 5 | *.pyo 6 | *.so 7 | *.pickle 8 | .Python 9 | build/ 10 | develop-eggs/ 11 | dist/ 12 | downloads/ 13 | eggs/ 14 | .eggs/ 15 | lib/ 16 | lib64/ 17 | parts/ 18 | sdist/ 19 | var/ 20 | wheels/ 21 | share/python-wheels/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | *.manifest 26 | *.spec 27 | pip-log.txt 28 | pip-delete-this-directory.txt 29 | htmlcov/ 30 | .tox/ 31 | .nox/ 32 | .coverage 33 | .coverage.* 34 | .cache 35 | nosetests.xml 36 | coverage.xml 37 | *.cover 38 | *.py,cover 39 | .hypothesis/ 40 | .pytest_cache/ 41 | cover/ 42 | *.mo 43 | *.pot 44 | *.log 45 | local_settings.py 46 | db.sqlite3 47 | db.sqlite3-journal 48 | .scrapy 49 | docs/_build/ 50 | .pybuilder/ 51 | target/ 52 | .ipynb_checkpoints 53 | profile_default/ 54 | ipython_config.py 55 | .python-version 56 | Pipfile.lock 57 | __pypackages__/ 58 | celerybeat-schedule 59 | celerybeat.pid 60 | .env 61 | .venv 62 | env/ 63 | venv/ 64 | ENV/ 65 | env.bak/ 66 | venv.bak/ 67 | .spyderproject 68 | .spyproject 69 | .ropeproject 70 | .pyre/ 71 | .pytype/ 72 | cython_debug/ 73 | .idea/ 74 | -------------------------------------------------------------------------------- /standardizer/implementations/remove_shebang.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import standardizer.standardizer_helpers as standardizer_helpers 18 | 19 | 20 | def remove_shebang(content, required_coverage): 21 | if content.startswith("#!/") is False: 22 | return content 23 | new_content = content.split("\n", 1)[1] # remove the first line 24 | if standardizer_helpers.does_code_still_trigger_coverage(new_content, required_coverage): 25 | return new_content 26 | return content 27 | -------------------------------------------------------------------------------- /standardizer/implementations/add_possible_required_newlines.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import standardizer.standardizer_helpers as standardizer_helpers 18 | 19 | 20 | def add_possible_required_newlines(code_to_minimize, required_coverage): 21 | tmp = code_to_minimize.replace(";", ";\n").replace("\n\n", "\n") 22 | if tmp != code_to_minimize: 23 | if standardizer_helpers.does_code_still_trigger_coverage(tmp, required_coverage): 24 | code_to_minimize = tmp # replacement worked 25 | return code_to_minimize 26 | -------------------------------------------------------------------------------- /result_analysis/download_bucket.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2022 @ReneFreingruber 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | 19 | # Note: This script will take forever. 20 | # Use the Python download script (>1.1_sync_files_from_gce_bucket.py<) instead. 21 | 22 | BUCKET_NAME="your-bucket-name-goes-here" 23 | 24 | gsutil auth login 25 | 26 | mkdir crashes 27 | gsutil -m cp -r gs://$BUCKET_NAME/crashes/* crashes/ 28 | 29 | mkdir stats 30 | gsutil -m cp -r gs://$BUCKET_NAME/stats/* stats/ 31 | 32 | # mkdir new_corpus_files 33 | # gsutil -m cp -r gs://$BUCKET_NAME/new_corpus_files/* new_corpus_files/ 34 | 35 | # mkdir new_behavior 36 | # gsutil -m cp -r gs://$BUCKET_NAME/new_behavior/* new_behavior/ 37 | -------------------------------------------------------------------------------- /watchdog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2022 @ReneFreingruber 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | 19 | # This script is used to restart the fuzzer in case an exception occurs 20 | # Typically the fuzzer should not throw exceptions, but if it really throws one, 21 | # it just restarts fuzzing 22 | # To enable or disable coverage, the command line must be updated 23 | 24 | if [ "$#" -ne 1 ] 25 | then 26 | echo "Output argument is missing!" 27 | exit 1 28 | fi 29 | 30 | 31 | cd /home/gce_user/fuzzer # Replace this with the correct fuzzer path on the GCE instance 32 | 33 | while true 34 | do 35 | python3 JS_FUZZER.py --output_dir $1 --resume --disable_coverage 36 | sleep 1 37 | done 38 | 39 | -------------------------------------------------------------------------------- /testcase_mergers/implementations/merge_testcase_into_JIT_compiled_function.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import utils 18 | import tagging_engine.tagging as tagging 19 | from tagging_engine.tagging import Tag 20 | 21 | 22 | 23 | def merge_testcase_into_JIT_compiled_function(testcase1_content, testcase1_state, testcase2_content, testcase2_state): 24 | # utils.dbg_msg("Merging operation: Testcase merge into JIT compiled function") 25 | # TODO: if one of the testcases has a JIT compiled function take the other testcase and embed it inside the JIT function 26 | # if both don't have a JIT function then just call merging_testcases_insert() 27 | tagging.add_tag(Tag.MERGE_TESTCASE_INTO_JIT_COMPILED_FUNCTION1) 28 | pass # TODO implement 29 | -------------------------------------------------------------------------------- /standardizer/standardizer_helpers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import native_code.coverage_helpers as coverage_helpers 18 | 19 | tmp_coverage_filepath = None 20 | 21 | 22 | # TODO: Can I remove this call and get the value from cfg? 23 | def initialize(current_coverage_filepath): 24 | global tmp_coverage_filepath 25 | tmp_coverage_filepath = current_coverage_filepath 26 | 27 | 28 | # TODO: Move this function maybe into >coverage_helpers.py< ? 29 | def does_code_still_trigger_coverage(new_code, required_coverage): 30 | global tmp_coverage_filepath 31 | triggered_coverage = coverage_helpers.extract_coverage_of_testcase(new_code, tmp_coverage_filepath) 32 | for coverage_entry in required_coverage: 33 | if coverage_entry not in triggered_coverage: 34 | return False 35 | return True 36 | -------------------------------------------------------------------------------- /debugging/check_if_testcase_is_permanently_disabled.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # If I know from result analysis (analysis of the created tagging files) that some specific testcases perform 18 | # not very good, I use this script to check if the file was permanently disabled 19 | # 20 | # Example invocation: 21 | # python3 check_if_testcase_is_permanently_disabled.py 22 | # 23 | import pickle 24 | 25 | with open("/home/user/Desktop/input/OUTPUT/disabled_files.pickle", "rb") as fobj: 26 | disabled_files = pickle.load(fobj) 27 | 28 | print("Number disabled files: %d" % len(disabled_files)) 29 | 30 | # Query specific files 31 | if "tc13789.js" in disabled_files: 32 | print("Yes") 33 | if "tc510.js" in disabled_files: 34 | print("Yes") 35 | if "tc627.js" in disabled_files: 36 | print("Yes") 37 | -------------------------------------------------------------------------------- /bash_scripts/install_requirements_gce.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2022 @ReneFreingruber 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | apt install python3 18 | apt install python3-pip 19 | apt install expect 20 | apt install python-dev libxml2-dev libxslt-dev # Maybe instead requires libxslt1-dev 21 | pip3 install progressbar 22 | pip3 install hexdump 23 | pip3 install unidecode 24 | pip3 install bitstring 25 | pip3 install --upgrade google-api-python-client # used for synchronization via GCE buckets 26 | pip3 install --upgrade google-cloud-storage # used for synchronization via GCE buckets 27 | pip3 install line_profiler # only used for debugging / finding performance bottlenecks 28 | pip3 install psutil 29 | pip3 install paramiko # used by the "create initial corpus" scripts 30 | pip3 install scrapy # used by the "create initial corpus" scripts 31 | pip3 install gitpython # used by the "create initial corpus" scripts 32 | -------------------------------------------------------------------------------- /testcase_mergers/implementations/merge_testcase_append.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | import utils 17 | import tagging_engine.tagging as tagging 18 | from tagging_engine.tagging import Tag 19 | import testcase_mergers.testcase_merger as testcase_merger 20 | 21 | 22 | 23 | def merge_testcase_append(testcase1_content, testcase1_state, testcase2_content, testcase2_state): 24 | # utils.dbg_msg("Merging operation: Testcase append") 25 | tagging.add_tag(Tag.MERGE_TESTCASE_APPEND1) 26 | return testcase_merger.merge_testcase2_into_testcase1_at_line(testcase1_content, 27 | testcase1_state, 28 | testcase2_content, 29 | testcase2_state, 30 | testcase1_state.testcase_number_of_lines) 31 | -------------------------------------------------------------------------------- /automation_gce/stop_old_gce_instances.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | import subprocess 17 | 18 | if True: 19 | print("Going to query status of all instances...") 20 | result = subprocess.run(['gcloud', 'compute', 'instances', 'list'], stdout=subprocess.PIPE) 21 | tmp = result.stdout.decode('utf-8') 22 | for line in tmp.split("\n"): 23 | if "RUNNING" in line: 24 | parts = line.split() 25 | instance_name = parts[0] 26 | instance_zone = parts[1] 27 | if instance_name == "developer-system": 28 | continue # skip my developer machine 29 | print("Going to stop %s (zone: %s)" % (instance_name, instance_zone)) 30 | 31 | stop_result = subprocess.run(['gcloud', 'compute', 'instances', 'stop', instance_name, '--zone', instance_zone, '--quiet'], stdout=subprocess.PIPE) 32 | print("Result:") 33 | print(stop_result.stdout.decode('utf-8')) 34 | -------------------------------------------------------------------------------- /mutators/implementations/mutation_insert_random_operation_at_specific_line.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import utils 18 | import tagging_engine.tagging as tagging 19 | from tagging_engine.tagging import Tag 20 | import mutators.testcase_mutators_helpers as testcase_mutators_helpers 21 | 22 | 23 | # Important: the operation should just have EXACTLY ONE LINE 24 | # Other code depends on this which updates states / line numbers 25 | # If a sequence of operations should be added another function must be used 26 | def mutation_insert_random_operation_at_specific_line(content, state, line_number): 27 | tagging.add_tag(Tag.MUTATION_INSERT_RANDOM_OPERATION_AT_SPECIFIC_LINE1) 28 | line_to_add = testcase_mutators_helpers.get_random_operation(content, state, line_number) 29 | 30 | # Now just insert the new line to the testcase & state 31 | lines = content.split("\n") 32 | lines.insert(line_number, line_to_add) 33 | new_content = "\n".join(lines) 34 | 35 | state.state_insert_line(line_number, new_content, line_to_add) 36 | return new_content, state 37 | -------------------------------------------------------------------------------- /mutators/implementations/mutation_if_wrap_operations.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import utils 18 | import tagging_engine.tagging as tagging 19 | from tagging_engine.tagging import Tag 20 | import mutators.testcase_mutators_helpers as testcase_mutators_helpers 21 | import javascript.js as js 22 | 23 | 24 | def mutation_if_wrap_operations(content, state): 25 | tagging.add_tag(Tag.MUTATION_IF_WRAP_OPERATIONS1) 26 | 27 | lines = content.split("\n") 28 | number_of_lines = len(lines) 29 | 30 | (start_line, end_line) = testcase_mutators_helpers.get_start_and_end_lines_to_wrap(content, state, lines, number_of_lines) 31 | if start_line == -1 or end_line == -1: 32 | # could not find a place to wrap code 33 | return content, state # unmodified testcase 34 | 35 | random_boolean_value = js.get_random_boolean_value(state, start_line) 36 | code_prefix = "if (%s) {" % random_boolean_value 37 | code_suffix = "}" 38 | 39 | return testcase_mutators_helpers.wrap_codelines(state, lines, start_line, end_line, code_prefix, code_suffix) 40 | -------------------------------------------------------------------------------- /mutators/implementations/mutation_insert_random_operation_from_database.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import utils 18 | import tagging_engine.tagging as tagging 19 | from tagging_engine.tagging import Tag 20 | 21 | import mutators.testcase_mutators_helpers as testcase_mutators_helpers 22 | from mutators.implementations.mutation_insert_random_operation_from_database_at_specific_line import mutation_insert_random_operation_from_database_at_specific_line 23 | 24 | 25 | def mutation_insert_random_operation_from_database(content, state): 26 | # utils.dbg_msg("Mutation operation: Insert random operation from database") 27 | tagging.add_tag(Tag.MUTATION_INSERT_RANDOM_OPERATION_FROM_DATABASE1) 28 | 29 | random_line_number = testcase_mutators_helpers.get_random_line_number_to_insert_code(state) 30 | # utils.dbg_msg("Going to insert at line: %d" % random_line_number) 31 | (new_content, result_state) = mutation_insert_random_operation_from_database_at_specific_line(content, state, random_line_number, number_of_operations=1) 32 | 33 | return new_content, result_state 34 | -------------------------------------------------------------------------------- /mutators/implementations/mutation_add_variable.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import utils 18 | import tagging_engine.tagging as tagging 19 | from tagging_engine.tagging import Tag 20 | 21 | import mutators.testcase_mutators_helpers as testcase_mutators_helpers 22 | import javascript.js_helpers as js_helpers 23 | 24 | 25 | # This mutation adds a new variable to the testcase which can be used to 26 | def mutation_add_variable(content, state): 27 | # utils.dbg_msg("Mutation operation: Add variable") 28 | tagging.add_tag(Tag.MUTATION_ADD_VARIABLE1) 29 | 30 | random_line_number = testcase_mutators_helpers.get_random_line_number_to_insert_code(state, maybe_remove_line_zero=False) 31 | random_data_type_lower_case = js_helpers.get_random_variable_type_lower_case_which_I_can_currently_instantiate() 32 | 33 | # random_data_type_lower_case = "string" # debugging code 34 | 35 | (new_content, new_state, new_variable_name) = testcase_mutators_helpers.add_variable_with_specific_data_type_in_line(content, state, random_data_type_lower_case, random_line_number) 36 | return new_content, new_state 37 | -------------------------------------------------------------------------------- /debugging/display_state_of_testcase.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # This is just a small helper utility which loads a state of a testcase and prints it. 18 | # It can be used to check if state calculation is correct. 19 | # 20 | # Example invocation: 21 | # python3 display_state_of_testcase.py 22 | # 23 | 24 | 25 | import os 26 | import sys 27 | current_dir = os.path.dirname(os.path.realpath(__file__)) 28 | base_dir = os.path.abspath(os.path.join(current_dir, '..')) 29 | if base_dir not in sys.path: sys.path.append(base_dir) 30 | import testcase_state 31 | 32 | # filepath_state = "/home/user/Desktop/input/OUTPUT/current_corpus/tc666.js.pickle" 33 | filepath_state = "/home/user/Desktop/test_new_working_dir/current_corpus/tc187.js.pickle" 34 | 35 | if os.path.isfile(filepath_state) is False: 36 | print("State file does not exist!") 37 | sys.exit(-1) 38 | 39 | state = testcase_state.load_state(filepath_state) 40 | print(state) 41 | 42 | # The code below can be used to check if copying a state works: 43 | # print("\n\n\nAfter:") 44 | # state = state.deep_copy() 45 | # print(state) 46 | -------------------------------------------------------------------------------- /minimizer/implementations/remove_not_referenced_functions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import minimizer.minimizer_helpers as minimizer_helpers 18 | 19 | 20 | def remove_not_referenced_functions(code, required_coverage): 21 | function_idx = 1 22 | code_without_not_referenced_functions = code 23 | while True: 24 | function_name = "func_%d_" % function_idx 25 | function_idx += 1 26 | if function_name not in code: 27 | break 28 | num_occurrences = code.count(function_name) 29 | if num_occurrences == 0: 30 | break # should not occur 31 | elif num_occurrences == 1: 32 | # In this case the only occurrence is the declaration of the function 33 | # The function can therefore be removed 34 | code_without_not_referenced_functions = minimizer_helpers.remove_function(code_without_not_referenced_functions, function_name) 35 | 36 | if minimizer_helpers.does_code_still_trigger_coverage(code_without_not_referenced_functions, required_coverage): 37 | return code_without_not_referenced_functions 38 | else: 39 | # Return the not modified code 40 | return code 41 | -------------------------------------------------------------------------------- /mutators/implementations/mutation_insert_random_operation.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import utils 18 | import tagging_engine.tagging as tagging 19 | from tagging_engine.tagging import Tag 20 | 21 | import mutators.testcase_mutators_helpers as testcase_mutators_helpers 22 | from mutators.implementations.mutation_insert_random_operation_at_specific_line import mutation_insert_random_operation_at_specific_line 23 | 24 | 25 | def mutation_insert_random_operation(content, state): 26 | # utils.dbg_msg("Mutation operation: Insert random operation") 27 | tagging.add_tag(Tag.MUTATION_INSERT_RANDOM_OPERATION1) 28 | 29 | random_line_number = testcase_mutators_helpers.get_random_line_number_to_insert_code(state) 30 | 31 | # possible_lines = state.lines_where_code_can_be_inserted + state.lines_where_code_with_coma_can_be_inserted 32 | # random_line_number = utils.get_random_entry(possible_lines) 33 | # utils.dbg_msg("Going to insert at line: %d" % random_line_number) 34 | # if random_line_number in state.lines_where_code_can_be_inserted: 35 | # end_line_with = ";" 36 | # else: 37 | # end_line_with = "," 38 | 39 | return mutation_insert_random_operation_at_specific_line(content, state, random_line_number) 40 | -------------------------------------------------------------------------------- /debugging/find_testcase_with_long_runtime.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # This script searches for testcases with a long runtime. 18 | # I use this to detect slow testcases to try to further optimize the minimizer to 19 | # also reduce the runtime of testcases 20 | 21 | 22 | import sys 23 | import os 24 | current_dir = os.path.dirname(os.path.realpath(__file__)) 25 | base_dir = os.path.abspath(os.path.join(current_dir, '..')) 26 | if base_dir not in sys.path: sys.path.append(base_dir) 27 | 28 | import testcase_state 29 | 30 | 31 | max_runtime = 500 32 | 33 | testcase_dir = "/home/user/Desktop/input/OUTPUT/current_corpus/" 34 | 35 | for filename in os.listdir(testcase_dir): 36 | if filename.endswith("pickle"): 37 | continue 38 | if filename.endswith(".js") is False: 39 | continue 40 | 41 | filepath = os.path.join(testcase_dir, filename) 42 | with open(filepath, 'r') as fobj: 43 | content = fobj.read().rstrip() 44 | 45 | state_filepath = filepath + ".pickle" 46 | state = testcase_state.load_state(state_filepath) 47 | 48 | if state.runtime_length_in_ms > max_runtime: 49 | print("%s has a runtime of %d ms" % (filename, state.runtime_length_in_ms)) 50 | -------------------------------------------------------------------------------- /testsuite/implementations/test_sync.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # Code to (manually) test if GCE sync via buckets works 18 | # This is some very old code, not sure if this really still works 19 | # Some function names or arguments must maybe be changed (I currently don't have a bucket to test it) 20 | 21 | import sys 22 | import os 23 | base_dir = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..')) 24 | if base_dir not in sys.path: sys.path.append(base_dir) 25 | 26 | import sync_engine.gce_bucket_sync as gce_bucket_sync 27 | 28 | 29 | gce_bucket_sync.initialize() 30 | 31 | gce_bucket_sync.save_new_corpus_file_if_not_already_exists("new_behavior.js", "test new behavior", None, None) 32 | gce_bucket_sync.save_stats("test1.result", "result after time 2a") 33 | gce_bucket_sync.save_new_corpus_file_if_not_already_exists("new_behavior3.js", "test new behavior3", None, None) 34 | 35 | content_list = gce_bucket_sync.download_new_corpus_files() 36 | for entry in content_list: 37 | print(entry) 38 | print("--------------") 39 | 40 | gce_bucket_sync._upload_file("crashes", "test1.js", "crash_content") 41 | content = gce_bucket_sync._download_file("crashes", "test1.js") 42 | print(content) 43 | -------------------------------------------------------------------------------- /mutators/implementations/mutation_insert_multiple_random_operations_from_database.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import utils 18 | import tagging_engine.tagging as tagging 19 | from tagging_engine.tagging import Tag 20 | 21 | import mutators.testcase_mutators_helpers as testcase_mutators_helpers 22 | from mutators.implementations.mutation_insert_random_operation_from_database_at_specific_line import mutation_insert_random_operation_from_database_at_specific_line 23 | 24 | 25 | def mutation_insert_multiple_random_operations_from_database(content, state): 26 | # utils.dbg_msg("Mutation operation: Insert multiple random operations from database") 27 | tagging.add_tag(Tag.MUTATION_INSERT_MULTIPLE_RANDOM_OPERATIONS_FROM_DATABASE1) 28 | 29 | random_line_number = testcase_mutators_helpers.get_random_line_number_to_insert_code(state) 30 | # utils.dbg_msg("Going to insert at line: %d" % random_line_number) 31 | 32 | number_of_mutations = utils.get_random_int(2, 5) # TODO make config variable for this, maybe 3-5? 2 is not a lot..? but maybe some bugs require less operation because too many would "destroy" a state? 33 | (new_content, result_state) = mutation_insert_random_operation_from_database_at_specific_line(content, state, random_line_number, number_of_operations=number_of_mutations) 34 | 35 | return new_content, result_state 36 | -------------------------------------------------------------------------------- /debugging/calculate_number_new_edges_of_testcase.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import sys 18 | import os 19 | current_dir = os.path.dirname(os.path.realpath(__file__)) 20 | base_dir = os.path.abspath(os.path.join(current_dir, '..')) 21 | if base_dir not in sys.path: sys.path.append(base_dir) 22 | 23 | from native_code.executor import Executor, Execution_Status 24 | import config as cfg 25 | 26 | 27 | 28 | import os 29 | 30 | 31 | 32 | testcase_dir = "/home/user/Desktop/input/OUTPUT/current_corpus" 33 | testcase_filename = "tc1234.js" 34 | 35 | fullpath = os.path.join(testcase_dir, testcase_filename) 36 | with open(fullpath, "r") as fobj: 37 | content = fobj.read() 38 | 39 | 40 | exec_engine = Executor(timeout_per_execution_in_ms=cfg.v8_timeout_per_execution_in_ms_max, enable_coverage=True) 41 | exec_engine.adjust_coverage_with_dummy_executions() 42 | 43 | result = exec_engine.execute_safe(content) 44 | 45 | if result.status == Execution_Status.SUCCESS: 46 | print("Success, edge edges: %d" % result.num_new_edges) 47 | elif result.status == Execution_Status.CRASH: 48 | print("Crash") 49 | elif result.status == Execution_Status.EXCEPTION_THROWN: 50 | print("Exception") 51 | elif result.status == Execution_Status.TIMEOUT: 52 | print("Timeout") 53 | else: 54 | print("Unkown") 55 | -------------------------------------------------------------------------------- /mutators/implementations/mutation_for_wrap_line.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import utils 18 | import tagging_engine.tagging as tagging 19 | from tagging_engine.tagging import Tag 20 | import mutators.testcase_mutators_helpers as testcase_mutators_helpers 21 | 22 | 23 | def mutation_for_wrap_line(content, state): 24 | # utils.dbg_msg("Mutation operation: FOR-wrap code line") 25 | tagging.add_tag(Tag.MUTATION_FOR_WRAP_LINE1) 26 | 27 | # Simple mutation to add FOR: 28 | # console.log("code") 29 | # => 30 | # for (_ of [0]) console.log("code") 31 | 32 | lines = content.split("\n") 33 | possibilities_top_insert_for = testcase_mutators_helpers.get_code_lines_without_forbidden_words(lines) 34 | if len(possibilities_top_insert_for) == 0: 35 | tagging.add_tag(Tag.MUTATION_FOR_WRAP_LINE2_DO_NOTHING) 36 | return content, state # nothing to modify 37 | 38 | random_line_number = utils.get_random_entry(possibilities_top_insert_for) 39 | old_line = lines[random_line_number] 40 | 41 | code_to_append = "for (_ of [0]) " 42 | 43 | new_line = code_to_append + old_line 44 | lines[random_line_number] = new_line 45 | 46 | new_content = "\n".join(lines) 47 | added_length = len(code_to_append) 48 | state.state_update_content_length(added_length, new_content) 49 | 50 | return new_content, state 51 | -------------------------------------------------------------------------------- /standardizer/implementations/remove_semicolon_lines.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import standardizer.standardizer_helpers as standardizer_helpers 18 | 19 | 20 | def remove_semicolon_lines(code_to_minimize, required_coverage): 21 | # First attempt is to remove multiple occurrences of a semicolon after each other 22 | # E.g.: 23 | # ;;;1+2;; 24 | # Will become: 25 | # ;1+2; 26 | tmp = code_to_minimize 27 | while ";;" in tmp: 28 | tmp = tmp.replace(";;", ";") 29 | 30 | if tmp != code_to_minimize: 31 | if standardizer_helpers.does_code_still_trigger_coverage(tmp, required_coverage): 32 | code_to_minimize = tmp # replacement worked 33 | 34 | 35 | # Now remove empty lines which just contain ";" 36 | # And also remove the trailing ";" from lines like: 37 | # ;1+2 38 | # => 39 | # 1+2 40 | tmp = "" 41 | for line in code_to_minimize.split("\n"): 42 | if line.strip() == ";": 43 | continue # skip these lines 44 | elif line.startswith(";"): 45 | tmp += line[1:] + "\n" # remove the ";" 46 | else: 47 | tmp += line + "\n" 48 | 49 | if tmp != code_to_minimize: 50 | if standardizer_helpers.does_code_still_trigger_coverage(tmp, required_coverage): 51 | code_to_minimize = tmp # replacement worked 52 | 53 | return code_to_minimize 54 | -------------------------------------------------------------------------------- /mutators/implementations/mutation_while_wrap_line.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # Simple mutation strategy to WHILE-loop wrap a line: 18 | # 19 | # Example: 20 | # console.log("code") 21 | # 22 | # => 23 | # 24 | # while (true) { console.log("code");break } 25 | 26 | import utils 27 | import tagging_engine.tagging as tagging 28 | from tagging_engine.tagging import Tag 29 | import mutators.testcase_mutators_helpers as testcase_mutators_helpers 30 | 31 | 32 | def mutation_while_wrap_line(content, state): 33 | # utils.dbg_msg("Mutation operation: WHILE-wrap code line") 34 | tagging.add_tag(Tag.MUTATION_WHILE_WRAP_LINE1) 35 | 36 | lines = content.split("\n") 37 | possibilities_top_insert_for = testcase_mutators_helpers.get_code_lines_without_forbidden_words(lines) 38 | if len(possibilities_top_insert_for) == 0: 39 | tagging.add_tag(Tag.MUTATION_WHILE_WRAP_LINE2_DO_NOTHING) 40 | return content, state # nothing to modify 41 | 42 | random_line_number = utils.get_random_entry(possibilities_top_insert_for) 43 | old_line = lines[random_line_number] 44 | 45 | code_to_append = "while (true) { " 46 | code_to_append_end = ";break }" 47 | 48 | new_line = code_to_append + old_line + code_to_append_end 49 | lines[random_line_number] = new_line 50 | 51 | new_content = "\n".join(lines) 52 | added_length = len(code_to_append + code_to_append_end) 53 | state.state_update_content_length(added_length, new_content) 54 | 55 | return new_content, state 56 | -------------------------------------------------------------------------------- /automation_gce/shutdown_fuzzing_16.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2022 @ReneFreingruber 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | 19 | # The content of this script must be saved in the "shutdown-script" meta information 20 | # of a GCE machine (alternatively the >start_gce_instances.sh< script will pass it. 21 | # => Then this script automatically executes when the instance is stopped (e.g. Google shuts down a preemptive machine) 22 | 23 | # The purpose of the script is to gracefully shut down fuzzing. Typically, it should be no problem to just 24 | # pull the plug because results (e.g. crashes or new behavior files) are always immediately uploaded to a bucket. 25 | # However, I also want to sync the current stats / tagging results 26 | # => This helps to get a better understanding of how many real fuzzing executions were performed 27 | # 28 | # Please note: It's not guaranteed that GCE really executes this script and it often was not executed. 29 | 30 | # TODO: 31 | # The script should maybe also be added to /etc/acpi/powerbtn.sh 32 | # to ensure that it's really everytime called, see: https://haggainuchi.com/shutdown.html 33 | 34 | # Step1: Kill all watchdogs (otherwise they would respawn the fuzzers) 35 | pkill -f watchdog.sh 36 | 37 | # Step2: Kill all fuzzer instances 38 | let all_pids=$(pgrep -i -f JS_FUZZER.py); 39 | for pid in $all_pids; do 40 | kill -2 $pid 41 | done 42 | 43 | # Step3: Wait until they are all stopped 44 | for pid in $all_pids; do 45 | while kill -0 $pid; do 46 | sleep 1 47 | done 48 | done 49 | 50 | echo "Finished!" 51 | sleep 15 52 | -------------------------------------------------------------------------------- /mutators/implementations/mutation_for_wrap_operations.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import utils 18 | import tagging_engine.tagging as tagging 19 | from tagging_engine.tagging import Tag 20 | 21 | import mutators.testcase_mutators_helpers as testcase_mutators_helpers 22 | 23 | 24 | def mutation_for_wrap_operations(content, state): 25 | tagging.add_tag(Tag.MUTATION_FOR_WRAP_OPERATIONS1) 26 | 27 | lines = content.split("\n") 28 | number_of_lines = len(lines) 29 | 30 | (start_line, end_line) = testcase_mutators_helpers.get_start_and_end_lines_to_wrap(content, state, lines, number_of_lines) 31 | if start_line == -1 or end_line == -1: 32 | # could not find a place to wrap code 33 | return content, state # unmodified testcase 34 | 35 | # Get a variable: 36 | state.number_variables = state.number_variables + 1 37 | next_free_variable_id = state.number_variables 38 | new_variable_name = "var_%d_" % next_free_variable_id 39 | 40 | number_of_iterations = utils.get_random_int(0, 60) 41 | code_prefix = "for (var %s = 0; %s < %d; ++%s) {" % (new_variable_name, new_variable_name, number_of_iterations, new_variable_name) 42 | code_suffix = "}" 43 | 44 | # TODO make the >new_variable_name< available in the state in the new code lines 45 | # Important: Attention for the line numbers because at that point the >code_prefix< line was not added 46 | 47 | return testcase_mutators_helpers.wrap_codelines(state, lines, start_line, end_line, code_prefix, code_suffix) 48 | -------------------------------------------------------------------------------- /mutators/implementations/mutation_change_proto.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import utils 18 | import tagging_engine.tagging as tagging 19 | from tagging_engine.tagging import Tag 20 | import mutators.testcase_mutators_helpers as testcase_mutators_helpers 21 | 22 | 23 | def mutation_change_proto(content, state): 24 | # utils.dbg_msg("Mutation operation: Change proto") 25 | tagging.add_tag(Tag.MUTATION_CHANGE_PROTO1) 26 | 27 | # TODO 28 | # Currently I don't return in "lhs" or "rhs" the __proto__ of a function 29 | # So code like this: 30 | # Math.abs.__proto__ = Math.sign.__proto__ 31 | # Can currently not be created. Is this required? 32 | # => has such code an effect? 33 | 34 | random_line_number = testcase_mutators_helpers.get_random_line_number_to_insert_code(state) 35 | (start_line_with, end_line_with) = testcase_mutators_helpers.get_start_and_end_line_symbols(state, random_line_number, content) 36 | 37 | (lhs, code_possibilities) = testcase_mutators_helpers.get_proto_change_lhs(state, random_line_number) 38 | rhs = testcase_mutators_helpers.get_proto_change_rhs(state, random_line_number, code_possibilities) 39 | new_code_line = "%s%s.__proto__ = %s%s" % (start_line_with, lhs, rhs, end_line_with) 40 | 41 | # Now just insert the new line to the testcase & state 42 | lines = content.split("\n") 43 | lines.insert(random_line_number, new_code_line) 44 | new_content = "\n".join(lines) 45 | 46 | state.state_insert_line(random_line_number, new_content, new_code_line) 47 | return new_content, state 48 | -------------------------------------------------------------------------------- /mutators/implementations/mutation_wrap_value_in_function.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # This mutation wraps a value in a function. The assumption is that this 18 | # can maybe trigger deeper paths in the compiler. 19 | # 20 | # Example: 21 | # let x = 0 22 | # 23 | # => 24 | # 25 | # let r0 = () => 0; let x = r0() 26 | # 27 | # from: https://github.com/tunz/js-vuln-db/blob/master/chakra/CVE-2017-11802.md 28 | # 29 | # I simplified it to: 30 | # let x = (() => 0)() 31 | 32 | import utils 33 | import tagging_engine.tagging as tagging 34 | from tagging_engine.tagging import Tag 35 | import mutators.testcase_mutators_helpers as testcase_mutators_helpers 36 | 37 | 38 | def mutation_wrap_value_in_function(content, state): 39 | # utils.dbg_msg("Mutation operation: Wrap value in function") 40 | tagging.add_tag(Tag.MUTATION_WRAP_VALUE_IN_FUNCTION1) 41 | 42 | positions_of_numbers = testcase_mutators_helpers.get_positions_of_all_numbers_in_testcase(content) 43 | if len(positions_of_numbers) == 0: 44 | tagging.add_tag(Tag.MUTATION_WRAP_VALUE_IN_FUNCTION2_DO_NOTHING) 45 | return content, state # nothing to replace 46 | 47 | (start_idx, end_idx) = utils.get_random_entry(positions_of_numbers) 48 | original_number = content[start_idx:end_idx + 1] 49 | new_code = "(() => %s)()" % original_number 50 | 51 | new_content = content[:start_idx] + new_code + content[end_idx + 1:] 52 | added_length = len(new_code) - len(original_number) 53 | state.state_update_content_length(added_length, new_content) 54 | 55 | return new_content, state 56 | -------------------------------------------------------------------------------- /mutators/implementations/mutation_wrap_string_in_function.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # This mutation wraps a string in a function. The assumption is that this 18 | # can maybe trigger deeper paths in the compiler. 19 | # 20 | # Example: 21 | # let x = "foobar" 22 | # 23 | # => 24 | # 25 | # let r0 = () => "foobar"; let x = r0() 26 | # 27 | # from: https://github.com/tunz/js-vuln-db/blob/master/chakra/CVE-2017-11802.md 28 | # 29 | # I simplified it to: 30 | # let x = (() => "foobar")() 31 | 32 | import utils 33 | import tagging_engine.tagging as tagging 34 | from tagging_engine.tagging import Tag 35 | import mutators.testcase_mutators_helpers as testcase_mutators_helpers 36 | 37 | 38 | def mutation_wrap_string_in_function(content, state): 39 | # utils.dbg_msg("Mutation operation: Wrap string in function") 40 | tagging.add_tag(Tag.MUTATION_WRAP_STRING_IN_FUNCTION1) 41 | 42 | positions_of_strings = testcase_mutators_helpers.get_positions_of_all_strings_in_testcase(content) 43 | if len(positions_of_strings) == 0: 44 | tagging.add_tag(Tag.MUTATION_WRAP_STRING_IN_FUNCTION2_DO_NOTHING) 45 | return content, state # nothing to replace 46 | 47 | (start_idx, end_idx) = utils.get_random_entry(positions_of_strings) 48 | original_string = content[start_idx:end_idx + 1] 49 | new_code = "(() => %s)()" % original_string 50 | 51 | new_content = content[:start_idx] + new_code + content[end_idx + 1:] 52 | added_length = len(new_code) - len(original_string) 53 | state.state_update_content_length(added_length, new_content) 54 | 55 | return new_content, state 56 | -------------------------------------------------------------------------------- /mutators/implementations/mutation_wrap_variable_in_function.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # This mutation wraps a variable in a function. The assumption is that this 18 | # can maybe trigger deeper paths in the compiler. 19 | # 20 | # Example: 21 | # let x = var_1_ 22 | # 23 | # => 24 | # 25 | # let r0 = () => var_1_; let x = r0() 26 | # 27 | # from: https://github.com/tunz/js-vuln-db/blob/master/chakra/CVE-2017-11802.md 28 | # 29 | # I simplified it to: 30 | # let x = (() => var_1_)() 31 | 32 | 33 | import utils 34 | import tagging_engine.tagging as tagging 35 | from tagging_engine.tagging import Tag 36 | import mutators.testcase_mutators_helpers as testcase_mutators_helpers 37 | 38 | 39 | def mutation_wrap_variable_in_function(content, state): 40 | # utils.dbg_msg("Mutation operation: Wrap variable in function") 41 | tagging.add_tag(Tag.MUTATION_WRAP_VARIABLE_IN_FUNCTION1) 42 | 43 | positions_of_variables = testcase_mutators_helpers.get_positions_of_all_variables_in_testcase_without_assignment(content) 44 | if len(positions_of_variables) == 0: 45 | tagging.add_tag(Tag.MUTATION_WRAP_VARIABLE_IN_FUNCTION2_DO_NOTHING) 46 | return content, state # nothing to replace 47 | 48 | (start_idx, end_idx) = utils.get_random_entry(positions_of_variables) 49 | original_variable = content[start_idx:end_idx + 1] 50 | new_code = "(() => %s)()" % original_variable 51 | 52 | new_content = content[:start_idx] + new_code + content[end_idx + 1:] 53 | added_length = len(new_code) - len(original_variable) 54 | state.state_update_content_length(added_length, new_content) 55 | 56 | return new_content, state 57 | -------------------------------------------------------------------------------- /mutators/implementations/mutation_wrap_value_in_function_argument.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # This mutation wraps a value in a function. The assumption is that this 18 | # can maybe trigger deeper paths in the compiler. 19 | # 20 | # Example: 21 | # let x = 1337 22 | # 23 | # => 24 | # 25 | # function inlinee() { 26 | # return inlinee.arguments[0]; 27 | # } 28 | # let x = inlinee(1337) 29 | # 30 | # => Simplified to: 31 | # let x = (function _() {return _.arguments[0]})(1337) 32 | 33 | 34 | import utils 35 | import tagging_engine.tagging as tagging 36 | from tagging_engine.tagging import Tag 37 | import mutators.testcase_mutators_helpers as testcase_mutators_helpers 38 | 39 | 40 | def mutation_wrap_value_in_function_argument(content, state): 41 | # utils.dbg_msg("Mutation operation: Wrap value in function argument") 42 | tagging.add_tag(Tag.MUTATION_WRAP_VALUE_IN_FUNCTION_ARGUMENT1) 43 | 44 | positions_of_numbers = testcase_mutators_helpers.get_positions_of_all_numbers_in_testcase(content) 45 | if len(positions_of_numbers) == 0: 46 | tagging.add_tag(Tag.MUTATION_WRAP_VALUE_IN_FUNCTION_ARGUMENT2_DO_NOTHING) 47 | return content, state # nothing to replace 48 | 49 | (start_idx, end_idx) = utils.get_random_entry(positions_of_numbers) 50 | original_number = content[start_idx:end_idx + 1] 51 | new_code = "(function _() {return _.arguments[0]})(%s)" % original_number 52 | 53 | new_content = content[:start_idx] + new_code + content[end_idx + 1:] 54 | added_length = len(new_code) - len(original_number) 55 | state.state_update_content_length(added_length, new_content) 56 | 57 | return new_content, state 58 | -------------------------------------------------------------------------------- /mutators/implementations/mutation_wrap_string_in_function_argument.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # This mutation wraps a string in a function. The assumption is that this 18 | # can maybe trigger deeper paths in the compiler. 19 | # 20 | # Example: 21 | # let x = "foobar" 22 | # 23 | # => 24 | # 25 | # function inlinee() { 26 | # return inlinee.arguments[0]; 27 | # } 28 | # let x = inlinee("foobar") 29 | # 30 | # => Simplified to: 31 | # (function _() {return _.arguments[0]})("foobar") 32 | 33 | import utils 34 | import tagging_engine.tagging as tagging 35 | from tagging_engine.tagging import Tag 36 | import mutators.testcase_mutators_helpers as testcase_mutators_helpers 37 | 38 | 39 | def mutation_wrap_string_in_function_argument(content, state): 40 | # utils.dbg_msg("Mutation operation: Wrap string in function argument") 41 | tagging.add_tag(Tag.MUTATION_WRAP_STRING_IN_FUNCTION_ARGUMENT1) 42 | 43 | positions_of_strings = testcase_mutators_helpers.get_positions_of_all_strings_in_testcase(content) 44 | if len(positions_of_strings) == 0: 45 | tagging.add_tag(Tag.MUTATION_WRAP_STRING_IN_FUNCTION_ARGUMENT2_DO_NOTHING) 46 | return content, state # nothing to replace 47 | 48 | (start_idx, end_idx) = utils.get_random_entry(positions_of_strings) 49 | original_string = content[start_idx:end_idx + 1] 50 | new_code = "(function _() {return _.arguments[0]})(%s)" % original_string 51 | 52 | new_content = content[:start_idx] + new_code + content[end_idx + 1:] 53 | added_length = len(new_code) - len(original_string) 54 | state.state_update_content_length(added_length, new_content) 55 | 56 | return new_content, state 57 | -------------------------------------------------------------------------------- /debugging/find_template_with_wrong_number_of_lines.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # I had a bug in the creation of template states. 18 | # E.g.: an entry contained for example that var_1_ has data type Array in line 20, 19 | # but the testcase just had 19 lines of code. 20 | # This originated from some funny unicode symbols which Python doesn't like 21 | # I used this script to detect these problematic testcases/templates 22 | 23 | 24 | import sys 25 | import os 26 | current_dir = os.path.dirname(os.path.realpath(__file__)) 27 | base_dir = os.path.abspath(os.path.join(current_dir, '..')) 28 | if base_dir not in sys.path: sys.path.append(base_dir) 29 | 30 | import testcase_state 31 | 32 | template_dir = "/home/user/Desktop/input/templates/" 33 | 34 | already_processed = 0 35 | for sub_dir in os.listdir(template_dir): 36 | full_path = os.path.join(template_dir, sub_dir) 37 | if os.path.isdir(full_path): 38 | for filename in os.listdir(full_path): 39 | if filename.endswith(".js") is False: 40 | continue 41 | file_fullpath = os.path.join(full_path, filename) 42 | 43 | already_processed += 1 44 | state_filepath = file_fullpath + ".pickle" 45 | state = testcase_state.load_state(state_filepath) 46 | number_of_lines = state.testcase_number_of_lines 47 | 48 | for line_number in state.lines_where_code_can_be_inserted: 49 | if line_number > number_of_lines: 50 | print("Bad entry: %s (%d already parsed)" % (state_filepath, already_processed)) 51 | break 52 | -------------------------------------------------------------------------------- /minimizer/implementations/replace_throw_instructions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # Code like: 18 | # throw "" + var_1_; 19 | # will be rewritten to: 20 | # try { throw "" + var_1_; } catch (e) {} 21 | # Important: 22 | # I don't just remove the throwing code because the way it accesses the variable is maybe required for the coverage 23 | # and then the minimization would not occur. 24 | # By wrapping it inside the try catch block, I ensure that it won't throw an exception 25 | # This is very important for testcases which contain code like: 26 | # if(condition) { 27 | # throw error 28 | # } 29 | # and condition is typically false but it's simple during fuzzing to wrap condition to true 30 | # That would result in a lot of exceptions during fuzzing and therefore I rewrite the testcase to always catch the exception 31 | 32 | 33 | import minimizer.minimizer_helpers as minimizer_helpers 34 | 35 | 36 | def replace_throw_instructions(content, required_coverage): 37 | 38 | new_content = "" 39 | code_changed = False 40 | for line in content.split("\n"): 41 | if "throw " in line: 42 | code_changed = True 43 | new_content += "try { %s } catch (e) {}\n" % line # wrap the line in a try-catch block 44 | else: 45 | new_content += line + "\n" 46 | 47 | if code_changed is False: 48 | return content # Nothing was changed, so return the unmodified code 49 | 50 | new_content = new_content.rstrip("\n") 51 | 52 | if minimizer_helpers.does_code_still_trigger_coverage(new_content, required_coverage): 53 | return new_content # modification was successful 54 | return content # not successful, so return the original code 55 | -------------------------------------------------------------------------------- /automation_gce/ensure_all_gce_instances_are_running.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | # This script will check all 30 minutes if instances were preempted / stopped 17 | # and will try to restart them 18 | # In general, I would not recommend doing this. It's more efficient to start all preemptive instances 19 | # and wait 1 day until all systems are stopped again. And then restart all systems. 20 | # However, I didn't had so much time left, so I had to force restarts all 30 minutes 21 | # (to spend all my GCE credits before they expired) 22 | 23 | import subprocess 24 | import time 25 | 26 | project_name = "your-gce-project-name" 27 | 28 | while True: 29 | 30 | print("Going to query status of all instances...") 31 | result = subprocess.run(['gcloud', 'compute', 'instances', 'list'], stdout=subprocess.PIPE) 32 | tmp = result.stdout.decode('utf-8') 33 | for line in tmp.split("\n"): 34 | if "TERMINATED" in line: 35 | parts = line.split() 36 | instance_name = parts[0] 37 | instance_zone = parts[1] 38 | if instance_name == "developer-system": 39 | continue # skip my developer machine 40 | print("Going to start %s (zone: %s)" % (instance_name, instance_zone)) 41 | start_result = subprocess.run(['gcloud', 'beta', 'compute', 'instances', 'start', '--zone', '%s' % instance_zone, '%s' % instance_name, '--project', project_name], stdout=subprocess.PIPE) 42 | print("Start result:") 43 | print(start_result.stdout.decode('utf-8')) 44 | 45 | print("Going to sleep 0.5 hour...") 46 | time.sleep(1800) # Check all 30 minutes; 3600....delay 1 hour 47 | -------------------------------------------------------------------------------- /mutators/implementations/mutation_wrap_variable_in_function_argument.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # This mutation wraps a variable in a function. The assumption is that this 18 | # can maybe trigger deeper paths in the compiler. 19 | # 20 | # Example: 21 | # let x = var_1_ 22 | # 23 | # => 24 | # 25 | # function inlinee() { 26 | # return inlinee.arguments[0]; 27 | # } 28 | # let x = inlinee(var_1_) 29 | # 30 | # => Simplified to: 31 | # let x = (function _() {return _.arguments[0]})(var_1_) 32 | # 33 | # 34 | # 35 | 36 | import utils 37 | import tagging_engine.tagging as tagging 38 | from tagging_engine.tagging import Tag 39 | import mutators.testcase_mutators_helpers as testcase_mutators_helpers 40 | 41 | 42 | def mutation_wrap_variable_in_function_argument(content, state): 43 | # utils.dbg_msg("Mutation operation: Wrap variable in function argument") 44 | tagging.add_tag(Tag.MUTATION_WRAP_VARIABLE_IN_FUNCTION_ARGUMENT1) 45 | 46 | positions_of_variables = testcase_mutators_helpers.get_positions_of_all_variables_in_testcase_without_assignment(content) 47 | if len(positions_of_variables) == 0: 48 | tagging.add_tag(Tag.MUTATION_WRAP_VARIABLE_IN_FUNCTION_ARGUMENT2_DO_NOTHING) 49 | return content, state # nothing to replace 50 | 51 | (start_idx, end_idx) = utils.get_random_entry(positions_of_variables) 52 | original_variable = content[start_idx:end_idx + 1] 53 | new_code = "(function _() {return _.arguments[0]})(%s)" % original_variable 54 | 55 | new_content = content[:start_idx] + new_code + content[end_idx + 1:] 56 | added_length = len(new_code) - len(original_variable) 57 | state.state_update_content_length(added_length, new_content) 58 | 59 | return new_content, state 60 | -------------------------------------------------------------------------------- /debugging/modify_pickle_databases.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # I saved pickle files with a different folder structure 18 | # Since I changed the folder structure, the pickle files can't be loaded anymore 19 | # because pickle stores the paths to the classes 20 | # => This script rewrites the pickle files to the new folder structure 21 | # You should not need this script anymore. 22 | 23 | import pickle 24 | 25 | import sys 26 | import os 27 | current_dir = os.path.dirname(os.path.realpath(__file__)) 28 | base_dir = os.path.abspath(os.path.join(current_dir, '..')) 29 | if base_dir not in sys.path: sys.path.append(base_dir) 30 | import testcase_state 31 | 32 | 33 | class RenameUnpickler(pickle.Unpickler): 34 | def find_class(self, module, name): 35 | if module == "initial_js_corpus.testcase_state": 36 | module = "testcase_state" 37 | print(module) 38 | return super(RenameUnpickler, self).find_class(module, name) 39 | 40 | # Code to handle Operations Pickle files: 41 | # x = open("/home/user/Desktop/input/databases/variable_operations_states_list.pickle", "rb") 42 | # y = RenameUnpickler(x).load() 43 | # with open("/home/user/Desktop/input/databases/variable_operations_states_list2.pickle", 'wb') as fout: 44 | # pickle.dump(y, fout, pickle.HIGHEST_PROTOCOL) 45 | 46 | 47 | # Code to handle corpus dirs: 48 | """ 49 | basedir = "/home/user/Desktop/input/OUTPUT/current_corpus/" 50 | for filename in os.listdir(basedir): 51 | if filename.endswith(".js.pickle") is False: 52 | continue 53 | fullpath = os.path.join(basedir, filename) 54 | with open(fullpath, "rb") as x: 55 | y = RenameUnpickler(x).load() 56 | with open(fullpath, 'wb') as fout: 57 | pickle.dump(y, fout, pickle.HIGHEST_PROTOCOL) 58 | """ -------------------------------------------------------------------------------- /automation_gce/remove_old_not_running_gce_instances.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | # I think there is a python integration for gcloud, but for the moment I'm just wrapping OS commands... 17 | 18 | # The script will remove all old fuzzer instances in a loop all 30 minutes. 19 | # This will DELETE ALL FILES on the instance - so make sure you already synchronized them (e.g.: to a GCE Bucket)! 20 | # Hint: Check afterwards if really all associated disks were removed! 21 | # TODO: Maybe I need the "--delete-disks all" flag? 22 | # But I think I correctly set the "auto delete disk" flag when creating the instances? 23 | # It seems like GCE is just ignoring them sometimes (?) 24 | 25 | import subprocess 26 | import time 27 | 28 | 29 | while True: 30 | print("Going to query status of all instances...") 31 | result = subprocess.run(['gcloud', 'compute', 'instances', 'list'], stdout=subprocess.PIPE) 32 | tmp = result.stdout.decode('utf-8') 33 | for line in tmp.split("\n"): 34 | if "TERMINATED" in line: 35 | parts = line.split() 36 | instance_name = parts[0] 37 | instance_zone = parts[1] 38 | if instance_name == "developer-system": 39 | continue # skip my developer machine 40 | print("Going to delete %s (zone: %s)" % (instance_name, instance_zone)) 41 | 42 | remove_result = subprocess.run(['gcloud', 'compute', 'instances', 'delete', instance_name, '--zone', instance_zone, '--quiet'], stdout=subprocess.PIPE) 43 | print("Result:") 44 | print(remove_result.stdout.decode('utf-8')) 45 | 46 | print("Going to sleep 0.5 hour...") 47 | time.sleep(1800) # Check all 30 minutes; 3600....delay 1 hour 48 | 49 | -------------------------------------------------------------------------------- /minimizer/implementations/replace_strings.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import native_code.speed_optimized_functions as speed_optimized_functions 18 | import minimizer.minimizer_helpers as minimizer_helpers 19 | 20 | 21 | def replace_strings(code, required_coverage): 22 | for string_character in ['"', "'", "`"]: # currently I don't try regex strings 23 | fixed_code = "" 24 | code_to_fix = code 25 | while True: 26 | index = speed_optimized_functions.get_index_of_next_symbol_not_within_string(code_to_fix, string_character) 27 | if index == -1: 28 | break 29 | rest = code_to_fix[index + 1:] 30 | end_index = speed_optimized_functions.get_index_of_next_symbol_not_within_string(rest, string_character) 31 | if end_index == -1: 32 | break 33 | code_with_removed_string = fixed_code + code_to_fix[:index] + string_character + string_character + code_to_fix[index + 1 + 1 + end_index:] # both +1 are for both string characters 34 | 35 | if minimizer_helpers.does_code_still_trigger_coverage(code_with_removed_string, required_coverage): 36 | # Minimization worked and we still trigger the new coverage 37 | fixed_code = fixed_code + code_to_fix[:index] + string_character + string_character 38 | code_to_fix = code_to_fix[index + 1 + 1 + end_index:] 39 | else: 40 | # Minimization did not work 41 | fixed_code = fixed_code + code_to_fix[:index + end_index + 1 + 1] 42 | code_to_fix = code_to_fix[index + 1 + 1 + end_index:] 43 | 44 | # Loop finished, so update the code for the next iteration 45 | code = fixed_code + code_to_fix 46 | return code 47 | -------------------------------------------------------------------------------- /bash_scripts/prepare_system_for_fuzzing.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2022 @ReneFreingruber 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | # This script is not well tested. I collected all the commands from different sources / fuzzers 19 | # It's likely possible to further increase fuzzer performance by changing these configurations to better settings 20 | 21 | sysctl -w kernel.core_pattern=core 22 | sysctl -w kernel.randomize_va_space=0 23 | sysctl -w kernel.sched_child_runs_first=1 24 | sysctl -w kernel.sched_autogroup_enabled=1 25 | sysctl -w kernel.sched_migration_cost_ns=50000000 26 | sysctl -w kernel.sched_latency_ns=250000000 27 | echo never > /sys/kernel/mm/transparent_hugepage/enabled 28 | test -e /sys/devices/system/cpu/cpufreq/scaling_governor && echo performance | tee /sys/devices/system/cpu/cpufreq/scaling_governor 29 | test -e /sys/devices/system/cpu/cpufreq/policy0/scaling_governor && echo performance | tee /sys/devices/system/cpu/cpufreq/policy*/scaling_governor 30 | test -e /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor && echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor 31 | test -e /sys/devices/system/cpu/intel_pstate/no_turbo && echo 0 > /sys/devices/system/cpu/intel_pstate/no_turbo 32 | test -e /sys/devices/system/cpu/cpufreq/boost && echo 1 > /sys/devices/system/cpu/cpufreq/boost 33 | 34 | 35 | echo "Manually disable spectre:" 36 | echo "vim /etc/default/grub" 37 | echo "Change first line to:" 38 | echo "GRUB_CMDLINE_LINUX_DEFAULT=\"noibrs noibpb nopti nospectre_v2 nospectre_v1 l1tf=off nospec_store_bypass_disable no_stf_barrier mds=off mitigations=off\"" 39 | echo "" 40 | echo "cp /etc/default/grub /etc/default/grub.conf" 41 | echo "grub2-mkconfig -o /bin/grub2.cfg" 42 | 43 | echo "" 44 | echo "Check if spectre is disabled:" 45 | cat /proc/cmdline 46 | fgrep -r '' /sys/devices/system/cpu/vulnerabilities -------------------------------------------------------------------------------- /callback_injector/callback_injector_helpers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | # Helper functions for the callback injector 17 | 18 | import config as cfg 19 | 20 | 21 | def update_IDs(code): 22 | print_id = 0 23 | while cfg.v8_temp_print_id in code: 24 | print_id += 1 25 | code = code.replace(cfg.v8_temp_print_id, cfg.v8_print_id_prefix + str(print_id) + cfg.v8_print_id_postfix, 1) 26 | return code, print_id 27 | 28 | 29 | def remove_IDs(code, max_print_id): 30 | for i in range(1, max_print_id+1): 31 | # Mark it as not printed so that the fuzzer knows that he must not fuzz the code at that location 32 | code = code.replace(cfg.v8_print_id_prefix + str(i) + cfg.v8_print_id_postfix, cfg.v8_print_id_not_printed) 33 | return code 34 | 35 | 36 | def fix_IDs(code, output, max_print_id): 37 | already_fixed_IDs = set() 38 | len_prefix = len(cfg.v8_print_id_prefix) 39 | len_negative_postfix = len(cfg.v8_print_id_postfix) * -1 40 | for printed_ID in output.split("\n"): 41 | printed_ID = printed_ID.strip() 42 | if printed_ID == "": 43 | continue 44 | if printed_ID.startswith(cfg.v8_print_id_prefix) is False: 45 | continue 46 | code = code.replace(printed_ID, cfg.v8_temp_print_id) # Mark the print for the later iterations as executed 47 | printed_ID_value = int(printed_ID[len_prefix:len_negative_postfix], 10) 48 | already_fixed_IDs.add(printed_ID_value) 49 | 50 | for i in range(1, max_print_id+1): 51 | if i in already_fixed_IDs: 52 | continue 53 | # Mark it as not printed so that the fuzzer knows that he must not fuzz the code at that location 54 | code = code.replace(cfg.v8_print_id_prefix + str(i) + cfg.v8_print_id_postfix, cfg.v8_print_id_not_printed) 55 | return code 56 | -------------------------------------------------------------------------------- /modes/fix_corpus.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # This mode can be started by passing the "--fix_corpus_mode" flag to the fuzzer. 18 | # 19 | # This mode is mainly used to re-calculate state files for testcases where a state file is missing. 20 | # This occurs when I mainly remove a state file. 21 | # E.g.: If I find a flaw in a testcase and I manually fix the testcase like variable renaming 22 | # then I remove the state file and call this function to recalculate the state files for these files 23 | 24 | 25 | import sys 26 | import os 27 | base_dir = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..')) 28 | if base_dir not in sys.path: sys.path.append(base_dir) 29 | 30 | import state_creation.create_state_file as create_state_file 31 | import testcase_state 32 | import utils 33 | import config as cfg 34 | 35 | 36 | 37 | def fix_corpus(): 38 | corpus_filepath = cfg.output_dir_current_corpus 39 | 40 | utils.msg("[i] Starting to fix corpus...") 41 | for filename in os.listdir(corpus_filepath): 42 | if filename.endswith(".js") is False: 43 | continue 44 | 45 | filepath = os.path.join(corpus_filepath, filename) 46 | if os.path.isfile(filepath) is False: 47 | continue 48 | 49 | state_filepath = filepath + ".pickle" 50 | if os.path.isfile(state_filepath): 51 | continue # state file already exists, so it must not be created 52 | 53 | utils.msg("[i] Going to fix testcase: %s" % filename) 54 | # sys.exit(-1) 55 | 56 | with open(filepath, "r") as fobj: 57 | content = fobj.read() 58 | 59 | state = create_state_file.create_state_file_safe(content) 60 | testcase_state.save_state(state, state_filepath) 61 | 62 | utils.msg("[i] Finished fixing corpus") 63 | -------------------------------------------------------------------------------- /mutators/implementations/mutation_change_prototype.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import utils 18 | import tagging_engine.tagging as tagging 19 | from tagging_engine.tagging import Tag 20 | import mutators.testcase_mutators_helpers as testcase_mutators_helpers 21 | 22 | 23 | def mutation_change_prototype(content, state): 24 | # utils.dbg_msg("Mutation operation: Change prototype") 25 | tagging.add_tag(Tag.MUTATION_CHANGE_PROTOTYPE1) 26 | 27 | # Example from: 28 | # https://github.com/tunz/js-vuln-db/blob/master/chakra/CVE-2018-0838.md 29 | # func_1_.prototype = {}; 30 | # Other examples: 31 | # func_1_.prototype = var_1_.__proto__; 32 | # func_1_.prototype = Array.prototype; 33 | # func_1_.prototype = var_1_; // This is important 34 | # func_1_.prototype = Array.__proto__; 35 | # Also from (instanceable) globals: 36 | # Array.prototype = Set.prototype; 37 | # Also for classes 38 | # If it's not instanceable then there is just "__proto__" (e.g.: Math doesn't has .prototype) 39 | 40 | random_line_number = testcase_mutators_helpers.get_random_line_number_to_insert_code(state) 41 | (start_line_with, end_line_with) = testcase_mutators_helpers.get_start_and_end_line_symbols(state, random_line_number, content) 42 | 43 | (lhs, code_possibilities) = testcase_mutators_helpers.get_prototype_change_lhs(state) 44 | rhs = testcase_mutators_helpers.get_proto_change_rhs(state, random_line_number, code_possibilities) 45 | new_code_line = "%s%s.prototype = %s%s" % (start_line_with, lhs, rhs, end_line_with) 46 | 47 | # Now just insert the new line to the testcase & state 48 | lines = content.split("\n") 49 | lines.insert(random_line_number, new_code_line) 50 | new_content = "\n".join(lines) 51 | 52 | state.state_insert_line(random_line_number, new_content, new_code_line) 53 | return new_content, state 54 | -------------------------------------------------------------------------------- /debugging/find_testcase_with_datatype.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # During development it can be useful to find testcases which use specific data types like "Intl.Locale" 18 | # For example, if mutations performed on a variable of data type "Intl.Local" often lead to exceptions, 19 | # I can search for all such testcases, then hardcode these names into the developer-mode file 20 | # and use the developer-mode to debug just Intl.Local mutations. 21 | # This scripts searches for a specific data type in all testcases in the corpus. 22 | 23 | 24 | import sys 25 | import os 26 | current_dir = os.path.dirname(os.path.realpath(__file__)) 27 | base_dir = os.path.abspath(os.path.join(current_dir, '..')) 28 | if base_dir not in sys.path: sys.path.append(base_dir) 29 | 30 | import testcase_state 31 | 32 | 33 | variable_type_to_search = "webassembly.table" 34 | 35 | testcase_dir = "/home/user/Desktop/input/OUTPUT/current_corpus/" 36 | 37 | for filename in os.listdir(testcase_dir): 38 | if filename.endswith("pickle"): 39 | continue 40 | if filename.endswith(".js") is False: 41 | continue 42 | 43 | filepath = os.path.join(testcase_dir, filename) 44 | with open(filepath, 'r') as fobj: 45 | content = fobj.read().rstrip() 46 | 47 | state_filepath = filepath + ".pickle" 48 | state = testcase_state.load_state(state_filepath) 49 | 50 | for variable_name in state.variable_types: 51 | for entry in state.variable_types[variable_name]: 52 | (line_number, variable_type) = entry 53 | if variable_type == variable_type_to_search: 54 | print(filename) 55 | break 56 | else: 57 | continue 58 | break 59 | 60 | # x = Testcase_State(1,1,1) 61 | # x.calculate_curly_bracket_offsets(content) 62 | # print(content) 63 | # (self, content): 64 | -------------------------------------------------------------------------------- /result_analysis/check_if_crash_can_be_triggered_with_previous_files.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # I store per crash also previously executed in-memory files 18 | # It can happen that a testcase crashes but the crash can't be reproduced because 19 | # one of the previously executed testcases changed a global state, which then lead to the crash 20 | # In most cases (like ~99% of crashes), the crash can be reproduced without the "previous_files". 21 | # For the others, I use this script to analyze the crash 22 | # The script basically just executes all previous files and then the crashing 23 | # to detect if the crash can be reproduced using this way. 24 | # In my analysis all such crashes were crap. So I think it's not really required to do this analysis. 25 | 26 | 27 | import os 28 | import sys 29 | sys.path.append("..") 30 | from native_code.executor import Executor, Execution_Status 31 | import hashlib 32 | import re 33 | import subprocess 34 | 35 | 36 | d8_path = "/home/user/Desktop/fuzzer/Target_v8/v8_aug_2021_without_coverage/d8" 37 | input_path_previous_files = "/home/user/Desktop/fuzzer_data/ssh_downloaded_files/fuzzer-us-west1-64/OUTPUT10/crashes/53f4c2ac250ce6b10860a5524d54d300_previous/" 38 | 39 | exec_engine = Executor(timeout_per_execution_in_ms=4000, enable_coverage=False, custom_path_d8=d8_path) 40 | exec_engine.restart_engine() 41 | 42 | 43 | # Test without "previous_files": 44 | # result = exec_engine.execute_once(testcase1) 45 | # print(result) 46 | 47 | for i in range(1, 100): 48 | fullpath = os.path.join(input_path_previous_files, "%d.js" % i) 49 | # print(fullpath) 50 | if os.path.exists(fullpath) is False: 51 | continue 52 | with open(fullpath, "r") as fobj: 53 | content = fobj.read() 54 | 55 | result = exec_engine.execute_once(content) 56 | print(result) 57 | # if result.status == Execution_Status.CRASH: 58 | # stderr = result.engine_stderr 59 | -------------------------------------------------------------------------------- /minimizer/implementations/remove_body.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | import native_code.speed_optimized_functions as speed_optimized_functions 17 | import minimizer.minimizer_helpers as minimizer_helpers 18 | 19 | 20 | def remove_body(code, required_coverage): 21 | fixed_code = "" 22 | code_to_fix = code 23 | while True: 24 | index = speed_optimized_functions.get_index_of_next_symbol_not_within_string(code_to_fix, "{") 25 | if index == -1: 26 | break 27 | rest = code_to_fix[index+1:] 28 | end_index = speed_optimized_functions.get_index_of_next_symbol_not_within_string(rest, "}") 29 | if end_index == -1: 30 | break 31 | code_with_removed_body = fixed_code + code_to_fix[:index] + "{ }" + code_to_fix[index+1+1+end_index:] # both +1 are for { and } 32 | 33 | if minimizer_helpers.does_code_still_trigger_coverage(code_with_removed_body, required_coverage): 34 | # Minimization worked and we still trigger the new coverage 35 | # Now check if I can also add a newline 36 | # The newline is important so that my fuzzer can later add code in this function by adding code between two lines 37 | code_with_removed_body = fixed_code + code_to_fix[:index] + "{\n}" + code_to_fix[index+1+1+end_index:] # both +1 are for { and } 38 | if minimizer_helpers.does_code_still_trigger_coverage(code_with_removed_body, required_coverage): 39 | fixed_code = fixed_code + code_to_fix[:index] + "{\n}" 40 | code_to_fix = code_to_fix[index+1+1+end_index:] 41 | else: 42 | fixed_code = fixed_code + code_to_fix[:index] + "{ }" 43 | code_to_fix = code_to_fix[index+1+1+end_index:] 44 | else: 45 | # modification didn't work 46 | # Minimization did not work 47 | fixed_code = fixed_code + code_to_fix[:index+end_index+1+1] 48 | code_to_fix = code_to_fix[index+1+1+end_index:] 49 | 50 | return fixed_code + code_to_fix 51 | -------------------------------------------------------------------------------- /debugging/check_if_all_files_dont_trigger_an_exception.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # I use this script sometimes when I statically modify JavaScript corpus files 18 | # (e.g.: removing not executed code to manually minimize the file) 19 | # This helps to verify that the modification was correct (e.g.: it doesn't lead to an exception in the code...) 20 | # 21 | # It's also useful when updating the JavaScript engine to ensure that all files from the corpus don't trigger 22 | # an exception in the new JavaScript version. 23 | # 24 | # Example invocation: 25 | # python3 check_if_all_files_dont_trigger_an_exception.py | tee output.log 26 | # cat output.log | grep -v "SUCCESS:" 27 | 28 | 29 | import os 30 | import sys 31 | current_dir = os.path.dirname(os.path.realpath(__file__)) 32 | base_dir = os.path.abspath(os.path.join(current_dir, '..')) 33 | if base_dir not in sys.path: sys.path.append(base_dir) 34 | from native_code.executor import Executor, Execution_Status 35 | 36 | 37 | base_path = '/home/user/Desktop/input/OUTPUT/current_corpus/' # input path 38 | 39 | # Coverage is not required to check if an exception/crash occurs 40 | exec_engine = Executor(timeout_per_execution_in_ms=5000, enable_coverage=False) 41 | count = 0 42 | for filename in os.listdir(base_path): 43 | if filename.endswith(".js"): 44 | fullpath = os.path.join(base_path, filename) 45 | with open(fullpath, "r") as fobj: 46 | content = fobj.read().strip() 47 | 48 | exec_engine.restart_engine() # restart engine before every testcase so that testcases 100% don't affect each other 49 | result = exec_engine.execute_safe(content) 50 | if result.status != Execution_Status.SUCCESS: 51 | print("Failed with file: %s" % filename) 52 | print("Status: %s" % result.get_status_str()) 53 | else: 54 | print("SUCCESS: %s" % filename) 55 | # pass 56 | count += 1 57 | if (count % 1000) == 0: 58 | print("Processed: %d files" % count) 59 | sys.stdout.flush() 60 | 61 | print("Finished!") 62 | -------------------------------------------------------------------------------- /minimizer/implementations/remove_line_by_line.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import minimizer.minimizer_helpers as minimizer_helpers 18 | 19 | 20 | def remove_line_by_line(code, required_coverage): 21 | lines = code.split("\n") 22 | lines_length = len(lines) 23 | 24 | keep_line = [True]*lines_length 25 | # I iterate backwards to start to remove the last lines 26 | # This is important because variable declarations come typically first 27 | # So removing a variable declaration if there is still code afterwards which uses the variable 28 | # can't work because it leads to an exception. 29 | # I therefore start from the end and go backwards to the first line 30 | # Note: With >variable hoisting< this can still lead to problems, but this should work most of the time 31 | # It can also create problems with functions & function calls, however, I handle them before I start with this code here 32 | for line_to_remove in range(lines_length-1, -1, -1): 33 | code_with_removed_line = "" 34 | for line_index in range(lines_length): 35 | if line_index == line_to_remove: 36 | continue # skip the add 37 | if keep_line[line_index]: 38 | code_with_removed_line += lines[line_index] + "\n" 39 | # Now >code_with_removed_line< holds the code with one line removed 40 | # Now check if it still triggers the same behavior 41 | # print("-----------------------") 42 | # print(code_with_removed_line) 43 | # print("-----------------------") 44 | # print("\n"*3) 45 | 46 | # keep_line[line_to_remove] = False 47 | # print("here1: %d line" % line_to_remove) 48 | if minimizer_helpers.does_code_still_trigger_coverage(code_with_removed_line, required_coverage): 49 | keep_line[line_to_remove] = False # The line is not important because it could be minimized away 50 | else: 51 | keep_line[line_to_remove] = True # the line is important 52 | 53 | final_code = "" 54 | for line_index in range(lines_length): 55 | if keep_line[line_index]: 56 | final_code += lines[line_index] + "\n" 57 | return final_code 58 | -------------------------------------------------------------------------------- /debugging/find_source_of_bug.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # This script is basically just a grep in all source-directories. 18 | # For example, if a crash is found during corpus generation, this script checks 19 | # from which source the crash originated. 20 | # E.g: if v8 (Chrome) crashes because of a testcase from SpiderMonkey (Firefox), 21 | # it's maybe a new bug in v8. But if v8 crashes because of a testcase from v8 regression tests 22 | # it's very likely an already known-bug. 23 | # It' therefore useful to quickly grep for the source of the testcase 24 | # 25 | # Example invocation: 26 | # python3 find_source_of_bug.py 27 | 28 | import os 29 | import os.path 30 | 31 | 32 | source_dirs = [ 33 | "ChakraCore", 34 | "javascript_snippets", 35 | "js-by-examples", 36 | "js-vuln-db", 37 | "mozilla_developer", 38 | "mozilla_developer_old", 39 | "mozilla_interactive_examples", 40 | "spidermonkey", 41 | "sputniktests", 42 | "v8_test_regex", 43 | "v8_tests", 44 | "w3resource", 45 | "w3resource_exercises", 46 | "Webkit_tests", 47 | "fuzzilli", 48 | "fuzzilli_from_samuel", 49 | "DIE_corpus", 50 | ] 51 | 52 | base_path = '.' 53 | base_path = os.path.abspath(base_path) 54 | subfolder_name = "files" 55 | 56 | for directory_name in source_dirs: 57 | fullpath = os.path.join(base_path, directory_name) 58 | 59 | if os.path.isdir(fullpath): 60 | fullpath = os.path.join(fullpath, subfolder_name) 61 | if os.path.isdir(fullpath): 62 | print("Processing folder: %s" % fullpath) 63 | for testcase_name in os.listdir(fullpath): 64 | testcase_path = os.path.join(fullpath, testcase_name) 65 | with open(testcase_path, 'r') as fobj: 66 | try: 67 | content = fobj.read().rstrip().encode('utf-8') 68 | content = content.decode("utf-8") 69 | except: 70 | continue 71 | 72 | if "someBugPattern" in content and "anotherBugPattern" in content: 73 | print("Found bug at: %s" % testcase_path) 74 | -------------------------------------------------------------------------------- /testcase_mergers/implementations/merge_testcase_insert.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import utils 18 | import tagging_engine.tagging as tagging 19 | from tagging_engine.tagging import Tag 20 | from testcase_mergers.implementations.merge_testcase_append import merge_testcase_append 21 | import testcase_mergers.testcase_merger as testcase_merger 22 | 23 | 24 | def merge_testcase_insert(testcase1_content, testcase1_state, testcase2_content, testcase2_state): 25 | # utils.dbg_msg("Merging operation: Testcase insert") 26 | tagging.add_tag(Tag.MERGE_TESTCASE_INSERT1) 27 | 28 | if len(testcase1_state.lines_where_code_can_be_inserted) == 0: 29 | # This should never occur because the "append" code line should always be available... 30 | tagging.add_tag(Tag.MERGE_TESTCASE_INSERT2_SHOULD_NOT_OCCUR) 31 | # utils.dbg_msg("Code can't be inserted at any line, so make a fallback to append") 32 | return merge_testcase_append(testcase1_content, testcase1_state, testcase2_content, testcase2_state) 33 | 34 | # utils.dbg_msg("Possible lines where testcase2 can be inserted: %s" % str(testcase1_state.lines_where_code_can_be_inserted)) 35 | 36 | # TODO: Later maybe also consider coma separated lines? 37 | possible_lines_to_insert = list(set(testcase1_state.lines_where_code_can_be_inserted) - set(testcase1_state.lines_which_are_not_executed)) 38 | if len(possible_lines_to_insert) == 0: 39 | tagging.add_tag(Tag.MERGE_TESTCASE_INSERT3_SHOULD_NOT_OCCUR) 40 | return merge_testcase_append(testcase1_content, testcase1_state, testcase2_content, testcase2_state) 41 | 42 | random_line_number = utils.get_random_entry(possible_lines_to_insert) 43 | # utils.dbg_msg("Going to insert testcase2 at line %d in testcase1" % random_line_number) 44 | return testcase_merger.merge_testcase2_into_testcase1_at_line(testcase1_content, 45 | testcase1_state, 46 | testcase2_content, 47 | testcase2_state, 48 | random_line_number) 49 | -------------------------------------------------------------------------------- /testsuite/execute_testsuite.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import utils 18 | import sys 19 | from testsuite.implementations.test_exec_engine import test_exec_engine 20 | from testsuite.implementations.test_coverage_feedback import test_coverage_feedback 21 | from testsuite.implementations.test_speed_optimized_functions import test_speed_optimized_functions 22 | from testsuite.implementations.test_corpus_quality import test_corpus_quality 23 | from testsuite.implementations.test_javascript_operations_database import test_javascript_operations_database_quality 24 | 25 | 26 | def execute_testsuite(which_tests_to_perform): 27 | which_tests_to_perform = which_tests_to_perform.lower() 28 | 29 | utils.msg("\n") 30 | utils.msg("[i] " + "=" * 100) 31 | utils.msg("[i] Going to start testsuite with argument: %s" % which_tests_to_perform) 32 | utils.msg("[i] " + "=" * 100) 33 | 34 | 35 | if which_tests_to_perform == "speed_optimized_functions" or which_tests_to_perform == "all": 36 | test_speed_optimized_functions() 37 | 38 | if which_tests_to_perform == "exec_engine" or which_tests_to_perform == "all": 39 | test_exec_engine() 40 | 41 | if which_tests_to_perform == "coverage_feedback" or which_tests_to_perform == "all": 42 | test_coverage_feedback() 43 | 44 | if which_tests_to_perform == "corpus" or which_tests_to_perform == "all": 45 | test_corpus_quality() 46 | 47 | if which_tests_to_perform == "operations_database" or which_tests_to_perform == "all": 48 | test_javascript_operations_database_quality() 49 | 50 | # if which_tests_to_perform == "state_operations" or which_tests_to_perform == "all": 51 | # TODO: test_state_operations() 52 | # => E.g.: if a state modification is correctly implemented (e.g.: adding a line or removing lines) 53 | # or adding a variable 54 | 55 | # if which_tests_to_perform == "mutations" or which_tests_to_perform == "all": 56 | # TODO: test_mutations() 57 | # => Implement testcases for all mutations 58 | 59 | # TODO: Test Minimizer? Test Standardizer? 60 | # TODO: Check if globals.pickle is correct? 61 | 62 | # Test Sync is currently not implemented, it's a standalone script 63 | 64 | utils.msg("[i] " + "=" * 100 + "\n\n") 65 | -------------------------------------------------------------------------------- /debugging/find_testcase_with_substr.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # The script is used to detect corpus files which use specific words / a substring. 18 | # For example, the current version searches for v8-specific testcases 19 | # (which may need to be rewritten to target SpiderMonkey, JSC, ...) 20 | 21 | 22 | import sys 23 | import os 24 | current_dir = os.path.dirname(os.path.realpath(__file__)) 25 | base_dir = os.path.abspath(os.path.join(current_dir, '..')) 26 | if base_dir not in sys.path: sys.path.append(base_dir) 27 | 28 | 29 | words = [ 30 | "%LiveEditPatchScript", 31 | "%IsWasmCode", 32 | "%IsAsmWasmCode", 33 | "%ConstructConsString", 34 | "%HaveSameMap", 35 | "%IsJSReceiver", 36 | "%HasSmiElements", 37 | "%HasObjectElements", 38 | "%HasDoubleElements", 39 | "%HasDictionaryElements", 40 | "%HasHoleyElements", 41 | "%HasSloppyArgumentsElements", 42 | "%HasFastProperties", 43 | "%HasPackedElement", 44 | "%ProfileCreateSnapshotDataBlob", 45 | "%NormalizeElements", 46 | "%SetWasmCompileControls", 47 | "%SetForceSlowPath", 48 | "%SetAllocationTimeout", 49 | "%ConstructDouble", 50 | "%OptimizeObjectForAddingMultipleProperties", 51 | "%RegexpHasNativeCode", 52 | "%RegexpHasBytecode", 53 | "%NewRegExpWithBacktrackLimit", 54 | "%TurbofanStaticAssert", 55 | "%DebugToggleBlockCoverage", 56 | "%StringLessThan", 57 | "%OptimizeOsr", 58 | "%IsValidSmi", 59 | "%CreateAsyncFromSyncIterator", 60 | "%HasSmiOrObjectElements", 61 | "%CreatePrivateSymbol", 62 | "%GetOptimizationStatus", 63 | "quit(", 64 | "%IsBeingInterpreted", 65 | ] 66 | 67 | testcase_dir = "/home/user/Desktop/input/OUTPUT/current_corpus/" 68 | 69 | 70 | for filename in os.listdir(testcase_dir): 71 | if filename.endswith("pickle"): 72 | continue 73 | if filename.endswith(".js") is False: 74 | continue 75 | 76 | filepath = os.path.join(testcase_dir, filename) 77 | with open(filepath, 'r') as fobj: 78 | content = fobj.read().rstrip() 79 | 80 | for word in words: 81 | if word in content: 82 | print("Word: %s in %s" % (word, filename)) 83 | break 84 | -------------------------------------------------------------------------------- /debugging/find_testcase_with_wrong_variable_name.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # This script tries to detect testcases for which variable renaming didn't work. 18 | # This mainly occurred when importing a corpus from Fuzzilli. 19 | # For example, Fuzzilli named a variable "v198" and then my fuzzer tries to rename the variable 20 | # to for example: "var_50_". The last "_" is important for me for some operations, because if 21 | # the testcase would also contain "v1981" and I would just do a stupid string-renaming in a 22 | # mutation, I would also rename "v1981" and not just "v198". I therefore use a "_" after the ID 23 | # like "var_198_". Because of this reason I try to rename all variable names, but this 24 | # sometimes doesn't work because the newly detected coverage is not triggered after renaming. 25 | # (e.g. the testcase triggers new coverage in the parsing code of the JS engine; 26 | # which is most of the times not so interesting) 27 | # In such a case I skip variable renaming and keep something like "v198" as variable name. 28 | # This script tries to detect such testcases (in a hacky way..). 29 | # I can then manually analyse them to better understand why variable renaming failed. 30 | 31 | 32 | import sys 33 | import os 34 | current_dir = os.path.dirname(os.path.realpath(__file__)) 35 | base_dir = os.path.abspath(os.path.join(current_dir, '..')) 36 | if base_dir not in sys.path: sys.path.append(base_dir) 37 | 38 | testcase_dir = "/home/user/Desktop/input/OUTPUT/current_corpus/" 39 | 40 | for filename in os.listdir(testcase_dir): 41 | if filename.endswith("pickle"): 42 | continue 43 | if filename.endswith(".js") is False: 44 | continue 45 | 46 | filepath = os.path.join(testcase_dir, filename) 47 | with open(filepath, 'r') as fobj: 48 | content = fobj.read().rstrip() 49 | 50 | for line in content.split("\n"): 51 | if "let " in line or "var " in line or "const " in line: 52 | if "var_" not in line: 53 | if line.strip().endswith("{"): 54 | continue 55 | if "let of" in line: 56 | continue 57 | print("Wrong variable name in file: %s" % filename) 58 | print(line) 59 | print("---------------") 60 | -------------------------------------------------------------------------------- /profilers/profile_performance_of_fuzzer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2022 @ReneFreingruber 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | 19 | # This script is used to find performance bottlenecks in the fuzzer 20 | # It can be started with cProfiler and Line_Profile (just change the variable below) 21 | # It's recommended to first run cProfiler to get an overview which functions are slow 22 | # Then you should annotate ("@profile") functions which are slow and restart 23 | # this script with Line_Profiler to understand which code lines of a function are slow. 24 | # The cProfiler output is passed to a Python script to better visualize the results 25 | 26 | # Note: You must adapt the paths in the below fuzzer Commands (output and template corpus) 27 | # It's recommended to perform at least 50 000 executions to get a good overview 28 | # Since the Line_profiler is a lot slower, I would just do 1000 executions 29 | 30 | # Valid entries are for now "cprofiler" and "line_profiler" 31 | profiler="cprofiler" 32 | 33 | if [ "$profiler" = "cprofiler" ] ; then 34 | echo "Starting cprofiler" 35 | 36 | rm profile.stats 37 | 38 | # The following command will create "profile.stats" (runtime: several minutes) 39 | # --skip_executions \ 40 | python3 -m cProfile \ 41 | -s cumulative \ 42 | -o profile.stats \ 43 | JS_FUZZER.py \ 44 | --output_dir /home/user/Desktop/input/OUTPUT/ \ 45 | --resume \ 46 | --seed 5 \ 47 | --disable_coverage \ 48 | --max_executions 50000 49 | 50 | # The following command will analyse the "profile.stats" output file 51 | python3 profile_analyse_output_stats.py 52 | elif [ "$profiler" = "line_profiler" ] ; then 53 | echo "Starting line_profiler" 54 | 55 | rm JS_FUZZER.py.lprof 56 | # The following command will create "JS_FUZZER.py.lprof" 57 | # Requires that a function gets annotated with "@profile" 58 | 59 | # Note: Line Profiler is very slow (e.g. instead of 200 exec/sec it drops to 5 exec/sec) 60 | # I'm therefore not making too many executions 61 | kernprof -l JS_FUZZER.py \ 62 | --output_dir /home/user/Desktop/input/OUTPUT/ \ 63 | --resume \ 64 | --seed 5 \ 65 | --skip_executions \ 66 | --max_executions 1000 67 | 68 | python3 -m line_profiler JS_FUZZER.py.lprof 69 | else 70 | echo "Unkown profiler selected" 71 | fi 72 | -------------------------------------------------------------------------------- /modes/recalculate_state.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | import os 17 | import utils 18 | import state_creation.create_state_file as create_state_file 19 | import testcase_state 20 | import config as cfg 21 | 22 | 23 | def recalculate_state(recalculate_state_for_file): 24 | output_dir_current_corpus = cfg.output_dir_current_corpus 25 | 26 | count = 0 27 | if recalculate_state_for_file == "all": 28 | for filename in os.listdir(output_dir_current_corpus): 29 | if filename.endswith(".js") is False: 30 | continue 31 | 32 | filepath = os.path.join(output_dir_current_corpus, filename) 33 | with open(filepath, "r") as fobj: 34 | content = fobj.read() 35 | 36 | count += 1 37 | state_filepath = filepath + ".pickle" 38 | # if os.path.exists(state_filepath): 39 | # continue # TODO remove later again 40 | try: 41 | os.remove(state_filepath) 42 | except: 43 | pass 44 | utils.msg("[i] Going to calculate file %s (file %d)" % (filename, count)) 45 | 46 | state = create_state_file.create_state_file_safe(content) 47 | testcase_state.save_state(state, state_filepath) 48 | utils.msg("[i] Finished state calculation of %s (file %d)" % (filename, count)) 49 | 50 | utils.exit_and_msg("[+] Finished, stopping...") 51 | 52 | if recalculate_state_for_file.startswith("tc") is False or recalculate_state_for_file.endswith(".js") is False: 53 | utils.perror("Error --recalculate_state state argument is wrong (%s)" % recalculate_state_for_file) 54 | 55 | # Handling a single file 56 | filepath = os.path.join(output_dir_current_corpus, recalculate_state_for_file) 57 | if os.path.isfile(filepath) is False: 58 | utils.perror("Error --recalculate_state file doesn't exist (%s)" % recalculate_state_for_file) 59 | with open(filepath, "r") as fobj: 60 | content = fobj.read() 61 | 62 | state_filepath = filepath + ".pickle" 63 | state = create_state_file.create_state_file_safe(content) 64 | # state = create_state_file.create_state_file(content, silently_catch_exception=False) 65 | # print("Testcase:") 66 | # print(content) 67 | # print("State:") 68 | # print(state) 69 | testcase_state.save_state(state, state_filepath) 70 | -------------------------------------------------------------------------------- /minimizer/implementations/remove_unused_variables_from_function_headers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # This function removes unused arguments from function headers. 18 | # Example: 19 | # function func_1_(var_1_, var_2_, var_3_) { 20 | # const var_4_ = [Infinity,Object]; 21 | # const var_5_ = var_4_.toLocaleString(); 22 | # } 23 | # func_1_(); 24 | # => In this case var_1_, var_2_ and var_3_ are not used and can therefore be removed 25 | 26 | import minimizer.minimizer_helpers as minimizer_helpers 27 | import testcase_helpers 28 | 29 | 30 | def remove_unused_variables_from_function_headers(content, required_coverage): 31 | variable_names_to_remove = [] 32 | # Get unused variable names: 33 | for i in range(8000): 34 | variable_name = "var_%d_" % i 35 | if content.count(variable_name) == 1: 36 | # exactly one occurrence which means it can be such a case 37 | 38 | codeline = testcase_helpers.get_first_codeline_which_contains_token(content, variable_name) 39 | if "function " in codeline or "function\t" in codeline: 40 | variable_names_to_remove.append(variable_name) 41 | 42 | if len(variable_names_to_remove) == 0: 43 | return content # nothing to do 44 | 45 | # First try to remove all variables at once 46 | content_adapted = content 47 | for variable_name in variable_names_to_remove: 48 | # important, we can't just remove the variable name, it's also maybe necessary to remove spaces or "," and again spaces 49 | content_adapted = minimizer_helpers.remove_variable_from_function_header(content_adapted, variable_name) 50 | 51 | if minimizer_helpers.does_code_still_trigger_coverage(content_adapted, required_coverage): 52 | content = content_adapted # removing was successful 53 | return content 54 | 55 | if len(variable_names_to_remove) == 1: 56 | return content 57 | 58 | # Fallback: Try to remove one variable at a time 59 | for variable_name in variable_names_to_remove: 60 | # important, we can't just remove the variable name, it's also maybe necessary to remove spaces or "," and again spaces 61 | new_content = minimizer_helpers.remove_variable_from_function_header(content, variable_name) 62 | if minimizer_helpers.does_code_still_trigger_coverage(new_content, required_coverage): 63 | content = new_content # removing was successful 64 | 65 | return content 66 | -------------------------------------------------------------------------------- /testsuite/implementations/test_corpus_quality.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import config as cfg 16 | import utils 17 | import testsuite.testsuite_helpers as helper 18 | 19 | from testsuite.testcases_for_corpus_quality import expected_operations_in_database 20 | 21 | 22 | def test_corpus_quality(): 23 | utils.msg("\n") 24 | utils.msg("[i] " + "-" * 100) 25 | utils.msg("[i] Going to check quality of the current corpus...") 26 | helper.reset_stats() 27 | 28 | testcases_from_corpus = [] 29 | for entry in cfg.corpus_js_snippets.corpus_iterator(): 30 | (code, _, _) = entry 31 | # The testcases contain code like "var_2_.pop()", but my test-dataset 32 | # contains entries like "var_TARGET_.pop()". => I therefore rename all variables 33 | # to "var_TARGET_" and then I can easily check if I have the "var_TARGET_.pop()" 34 | # operation in my corpus by just checking if the string occurs in one of the testcases 35 | code = replace_all_variables(code) 36 | testcases_from_corpus.append(code) 37 | 38 | # Now check if all these required code snippets can be found in the corpus: 39 | performed_tests = 0 40 | successful_tests = 0 41 | for entry in expected_operations_in_database: 42 | (expected_operation_code, _) = entry # the data type is ignored in the "corpus" tests 43 | performed_tests += 1 44 | does_corpus_contain_operation = False 45 | for testcase_code in testcases_from_corpus: 46 | if expected_operation_code in testcase_code: 47 | does_corpus_contain_operation = True 48 | break 49 | if does_corpus_contain_operation: 50 | successful_tests += 1 51 | 52 | # Print results: 53 | if successful_tests == performed_tests: 54 | utils.msg("[+] Corpus quality result: All %d performed checks were passed! Your corpus looks good!" % performed_tests) 55 | else: 56 | success_rate = (float(successful_tests) / performed_tests) * 100.0 57 | utils.msg("[!] Corpus quality result: %d of %d (%.2f %%) tests were successful!" % (successful_tests, performed_tests, success_rate)) 58 | 59 | utils.msg("[i] " + "-" * 100) 60 | 61 | 62 | def replace_all_variables(code): 63 | for i in range(1, 1000): # testcases have typically max. 50-100 variables, 1000 variables is already a huge over-estimation 64 | token = "var_%d_" % i 65 | code = code.replace(token, "var_TARGET_") 66 | return code 67 | -------------------------------------------------------------------------------- /native_code/coverage_helpers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | # This script contains some helper functions to extract coverage information. 17 | 18 | import config as cfg 19 | 20 | 21 | def extract_coverage_from_coverage_map_file(filepath): 22 | result = [] 23 | with open(filepath, "rb") as fobj: 24 | coverage_map_content = fobj.read() 25 | idx = 0 26 | for x in coverage_map_content: 27 | if x == 0xff: 28 | idx += 8 29 | continue 30 | b1 = x & 0b00000001 31 | b2 = x & 0b00000010 32 | b3 = x & 0b00000100 33 | b4 = x & 0b00001000 34 | b5 = x & 0b00010000 35 | b6 = x & 0b00100000 36 | b7 = x & 0b01000000 37 | b8 = x & 0b10000000 38 | 39 | if b1 == 0: 40 | result.append(idx) 41 | idx += 1 42 | if b2 == 0: 43 | result.append(idx) 44 | idx += 1 45 | if b3 == 0: 46 | result.append(idx) 47 | idx += 1 48 | if b4 == 0: 49 | result.append(idx) 50 | idx += 1 51 | if b5 == 0: 52 | result.append(idx) 53 | idx += 1 54 | if b6 == 0: 55 | result.append(idx) 56 | idx += 1 57 | if b7 == 0: 58 | result.append(idx) 59 | idx += 1 60 | if b8 == 0: 61 | result.append(idx) 62 | idx += 1 63 | return result 64 | 65 | 66 | def remove_already_known_coverage(new_coverage, already_known_coverage): 67 | return list(set(new_coverage) - set(already_known_coverage)) 68 | 69 | 70 | def extract_coverage_of_testcase(testcase_content, original_coverage_filepath): 71 | cfg.exec_engine.load_global_coverage_map_from_file(original_coverage_filepath) 72 | 73 | for i in range(0, 3): # 3 executions to ensure that the coverage really gets triggered 74 | cfg.exec_engine.execute_once(testcase_content) 75 | 76 | # Old code had execute_safe() calls... this can hang very long when there is a new coverage map used 77 | # because the indeterministic coverage will lead to several executions; 78 | # and since I'm restoring everytime the coverage map, this can take very long (maybe?) 79 | # result = cfg.exec_engine.execute_safe(testcase_content) 80 | # result = cfg.exec_engine.execute_safe(testcase_content) 81 | 82 | cfg.exec_engine.save_global_coverage_map_in_file(cfg.coverage_map_minimizer_filename) 83 | ret = extract_coverage_from_coverage_map_file(cfg.coverage_map_minimizer_filename) 84 | return ret 85 | -------------------------------------------------------------------------------- /mutators/implementations/mutation_modify_string.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # This mutation strategy tries to modify a string. 18 | # Modifying means that a string is added to the start or end. 19 | # (On the other hand, the replace string mutation strategy would replace 20 | # a string which means the original string can't be found in the result). 21 | # Example: 22 | # let var_1_ = {style: "percent"} 23 | # 24 | # => 25 | # 26 | # style: "0001-01-01T01:01+-s_0b:01.001Z"+"percent" 27 | # 28 | # => The "0001-01-01T01:01+-s_0b:01.001Z" is the new string which was added 29 | # at the start of the original string 30 | 31 | 32 | import utils 33 | import tagging_engine.tagging as tagging 34 | from tagging_engine.tagging import Tag 35 | import mutators.testcase_mutators_helpers as testcase_mutators_helpers 36 | import javascript.js as js 37 | 38 | 39 | def mutation_modify_string(content, state): 40 | # utils.dbg_msg("Mutation operation: Modify string") 41 | tagging.add_tag(Tag.MUTATION_MODIFY_STRING1) 42 | 43 | positions_of_strings = testcase_mutators_helpers.get_positions_of_all_strings_in_testcase(content) 44 | if len(positions_of_strings) == 0: 45 | tagging.add_tag(Tag.MUTATION_MODIFY_STRING2_DO_NOTHING) 46 | return content, state # nothing to replace 47 | 48 | (start_idx, end_idx) = utils.get_random_entry(positions_of_strings) 49 | 50 | if utils.get_random_bool(): 51 | # add stuff in front of string 52 | tagging.add_tag(Tag.MUTATION_MODIFY_STRING3) 53 | pos_to_insert = start_idx 54 | else: 55 | # add stuff after the string 56 | tagging.add_tag(Tag.MUTATION_MODIFY_STRING4) 57 | pos_to_insert = end_idx+1 58 | 59 | # utils.dbg_msg("Modifying string at position 0x%x" % (pos_to_insert)) 60 | if utils.get_random_bool(): 61 | tagging.add_tag(Tag.MUTATION_MODIFY_STRING5) 62 | random_string = js.get_str() 63 | else: 64 | # only 1 character 65 | tagging.add_tag(Tag.MUTATION_MODIFY_STRING6) 66 | random_char = utils.get_random_entry(js.possible_chars_all) 67 | random_string = "\""+random_char+"\"" 68 | 69 | if pos_to_insert == start_idx: 70 | # insert in front 71 | random_string = random_string+"+" 72 | else: 73 | # insert after 74 | random_string = "+"+random_string 75 | 76 | # utils.dbg_msg("New random string: %s" % (random_string)) 77 | 78 | added_content = random_string 79 | added_length = len(added_content) 80 | 81 | new_content = content[:pos_to_insert] + added_content + content[pos_to_insert:] 82 | state.state_update_content_length(added_length, new_content) 83 | 84 | return new_content, state 85 | -------------------------------------------------------------------------------- /minimizer/implementations/remove_not_required_function_contexts.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # Example: 18 | # function func_1_() { 19 | # const var_4_ = [Infinity,Object]; 20 | # const var_5_ = var_4_.toLocaleString(); 21 | # } 22 | # func_1_(); 23 | 24 | # => 25 | 26 | # const var_4_ = [Infinity,Object]; 27 | # const var_5_ = var_4_.toLocaleString(); 28 | 29 | import minimizer.minimizer_helpers as minimizer_helpers 30 | import native_code.speed_optimized_functions as speed_optimized_functions 31 | 32 | 33 | def remove_not_required_function_contexts(content, required_coverage): 34 | 35 | 36 | function_names_to_remove = [] 37 | # Get unused variable names: 38 | for i in range(100): 39 | function_name = "func_%d_" % i 40 | if content.count(function_name) == 2: # one for the definition, one for the invocation 41 | function_names_to_remove.append(function_name) 42 | 43 | for function_name in function_names_to_remove: 44 | function_invocation_code = function_name + "();" # I'm always assuming empty invocations here 45 | if function_invocation_code in content: 46 | # Try to remove it. 47 | start_of_function_declaration = content.find("function %s" % function_name) 48 | rest = content[start_of_function_declaration:] 49 | idx = speed_optimized_functions.get_index_of_next_symbol_not_within_string(rest, "{") 50 | rest2 = rest[idx + 1:] 51 | idx2 = speed_optimized_functions.get_index_of_next_symbol_not_within_string(rest2, "}") 52 | function_body = rest2[:idx2] 53 | 54 | function_body2 = "" 55 | for line in function_body.split("\n"): 56 | line = line.strip() 57 | if line == "": 58 | continue 59 | function_body2 += line + "\n" 60 | 61 | full_end_idx_of_function_body = start_of_function_declaration + idx + 1 + idx2 + 1 62 | if full_end_idx_of_function_body < len(content): 63 | if content[full_end_idx_of_function_body] == "\n": 64 | full_end_idx_of_function_body += 1 # also remove the newline after the function body 65 | 66 | full_function = content[start_of_function_declaration:full_end_idx_of_function_body] 67 | new_content = content.replace(full_function, "") # remove the function 68 | new_content = new_content.replace(function_invocation_code, function_body2.strip()) # add the function body at the previous location 69 | if minimizer_helpers.does_code_still_trigger_coverage(new_content, required_coverage): 70 | content = new_content # removing was successful 71 | return content 72 | -------------------------------------------------------------------------------- /debugging/display_coverage_map_triggered_edges.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # This script displays how many edges are triggered in the current coverage map 18 | # Runtime: 1-2 minutes because of the bit-wise file-reading 19 | # if ctrl+c is hit during processing, incomplete/partial results will be printed! 20 | # 21 | # The script is used to see how good a corpus is and to compare corpus testcases 22 | # from different sources. (e.g: the self created corpus just achieves ~10% coverage) 23 | # whereas the downloaded corpus has over 24% coverage. 24 | # 25 | # Example invocation: 26 | # python3 display_coverage_map_triggered_edges.py 27 | 28 | 29 | import os 30 | import sys 31 | current_dir = os.path.dirname(os.path.realpath(__file__)) 32 | base_dir = os.path.abspath(os.path.join(current_dir, '..')) 33 | if base_dir not in sys.path: sys.path.append(base_dir) 34 | 35 | from native_code.executor import Executor 36 | 37 | 38 | final_coverage_map_input_path = "/home/user/Desktop/input/OUTPUT_new/previous_coverage_map.map" 39 | 40 | 41 | # Dynamically extract the total number of possible edges 42 | # exec_engine = Executor(timeout_per_execution_in_ms=800, enable_coverage=True) 43 | # exec_engine.adjust_coverage_with_dummy_executions() 44 | # TOTAL_POSSIBLE_EDGES = exec_engine.total_number_possible_edges 45 | 46 | # Instead of execution of the v8 engine I can also just hardcode the number of possible edges 47 | # This can be useful if the coverage map was created for an older v8 binary 48 | TOTAL_POSSIBLE_EDGES = 596937 49 | 50 | with open(final_coverage_map_input_path, "rb") as fobj: 51 | content = fobj.read() 52 | 53 | # Hacky version, but this is a lot faster than the version which uses a python bit stream... 54 | how_many_nulls = 0 55 | for x in content: 56 | if x == 0xff: 57 | continue 58 | b1 = x & 0b00000001 59 | b2 = x & 0b00000010 60 | b3 = x & 0b00000100 61 | b4 = x & 0b00001000 62 | b5 = x & 0b00010000 63 | b6 = x & 0b00100000 64 | b7 = x & 0b01000000 65 | b8 = x & 0b10000000 66 | 67 | if b1 == 0: 68 | how_many_nulls += 1 69 | if b2 == 0: 70 | how_many_nulls += 1 71 | if b3 == 0: 72 | how_many_nulls += 1 73 | if b4 == 0: 74 | how_many_nulls += 1 75 | if b5 == 0: 76 | how_many_nulls += 1 77 | if b6 == 0: 78 | how_many_nulls += 1 79 | if b7 == 0: 80 | how_many_nulls += 1 81 | if b8 == 0: 82 | how_many_nulls += 1 83 | 84 | print("Triggered %d of %d possible edges" % (how_many_nulls, TOTAL_POSSIBLE_EDGES)) 85 | coverage_percent = float(how_many_nulls) / float(TOTAL_POSSIBLE_EDGES) 86 | coverage_percent *= 100 # convert it to percent 87 | print("Coverage: %.4f" % coverage_percent) 88 | -------------------------------------------------------------------------------- /mutators/implementations/mutation_materialize_values.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # This mutation strategy tries to materialize values to trigger deeper 18 | # paths in the compiler. Materialization basically means that a value is wrapped 19 | # in an object. Then, during compilation, one of the phases "dematerializes" the object/value. 20 | # This means that specific optimizations can't be performed before the dematerialization happens. 21 | # And that therefore optimizations after dematerialization will be triggered. 22 | 23 | # Example: 24 | # Object.is(Math.expm1(x), -0); 25 | # 26 | # => 27 | # 28 | # var var_22_ = {a: -0}; 29 | # Object.is(Math.expm1(x), var_22_.a); 30 | # 31 | # => To simplify stuff I create: 32 | # Object.is(Math.expm1(x), {_:-0}._); 33 | # 34 | # This example was from Chromium issue/exploit 880208 35 | 36 | # TODO: Check if this really leads to materialization / dematerialization! 37 | 38 | 39 | import utils 40 | import tagging_engine.tagging as tagging 41 | from tagging_engine.tagging import Tag 42 | import mutators.testcase_mutators_helpers as testcase_mutators_helpers 43 | import testcase_helpers 44 | 45 | 46 | def mutation_materialize_values(content, state): 47 | # utils.dbg_msg("Mutation operation: Materialize values") 48 | tagging.add_tag(Tag.MUTATION_MATERIALIZE_VALUE1) 49 | 50 | lines = content.split("\n") 51 | positions_of_numbers = testcase_mutators_helpers.get_positions_of_all_numbers_in_testcase(content) 52 | 53 | positions_of_numbers_filtered = [] 54 | for entry in positions_of_numbers: 55 | (start_idx, end_idx) = entry 56 | line_number = testcase_helpers.content_offset_to_line_number(lines, start_idx) 57 | line = lines[line_number] 58 | if "function " in line or "function\t" in line or "function*" in line: 59 | continue # don't inject into function definitions because I can't add a line before the definition without destroying the syntax 60 | positions_of_numbers_filtered.append(entry) 61 | 62 | if len(positions_of_numbers_filtered) == 0: 63 | tagging.add_tag(Tag.MUTATION_MATERIALIZE_VALUE2_DO_NOTHING) 64 | return content, state # nothing to materialize 65 | 66 | (start_idx, end_idx) = utils.get_random_entry(positions_of_numbers_filtered) 67 | original_value = content[start_idx:end_idx + 1] 68 | # utils.dbg_msg("Going to materialize value which starts at 0x%x and ends at 0x%x; value: %s" % (start_idx, end_idx, original_value)) 69 | 70 | materialized_object = "({_:%s})._" % original_value 71 | 72 | new_content = content[:start_idx] + materialized_object + content[end_idx + 1:] 73 | added_length = len(materialized_object) - len(original_value) 74 | state.state_update_content_length(added_length, new_content) 75 | 76 | return new_content, state 77 | -------------------------------------------------------------------------------- /debugging/calculate_number_of_insertion_lines_of_corpus.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # I use this script to calculate how many deterministic operations I will implement. 18 | # Edit: Previously I used deterministic operations, but the current fuzzer doesn't use them anymore. 19 | # E.g. in a corpus with 9500 files there are ~157 000 lines where i can insert code. 20 | # Lets assume that I try to insert 23 additional code lines in every line during deterministic fuzzing. 21 | # That would result in 157 000 * 23 = 3 611 000 required executions 22 | # Lets assume that I can perform in my slow VM 30 exec/sec 23 | # => it would require 120366 seconds which is something like 33 hours 24 | # The formula to calculate the required processing time in days is: 25 | # (((157000*23)/30.0) / 60) / 60.0 / 24.0 26 | # Whereas: 27 | # *) 157000 is the number code code lines 28 | # *) 23 is the number of deterministic operations 29 | # *) 30 is the execution time (e.g. on AWS it's 110 exec/sec) 30 | # 31 | # => I use this script to determine how many deterministic operations I want to perform to estimate the runtime 32 | # This helps me to decide which operations I want to perform "deterministically" (= always) and which I want to 33 | # just perform during fuzzing (randomly) 34 | # 35 | # Example invocation: 36 | # python3 calculate_number_of_insertion_lines_of_corpus.py 37 | # 38 | 39 | 40 | import os 41 | import sys 42 | current_dir = os.path.dirname(os.path.realpath(__file__)) 43 | base_dir = os.path.abspath(os.path.join(current_dir, '..')) 44 | if base_dir not in sys.path: sys.path.append(base_dir) 45 | 46 | 47 | import testcase_state 48 | import utils 49 | 50 | base_path = '/home/user/Desktop/input/OUTPUT/current_corpus/' # Corpus input path 51 | 52 | NUMBER_OF_DETERMINISTIC_OPERATIONS = 40 53 | EXEC_TIME = 110 54 | 55 | total_possible_insertions = 0 56 | last_testcase_id = utils.get_last_testcase_number(base_path) 57 | print("Last testcase ID: %d" % last_testcase_id) 58 | for current_id in range(1, last_testcase_id+1): 59 | filename = "tc%d.js.pickle" % current_id 60 | filepath = os.path.join(base_path, filename) 61 | filepath_state = os.path.join(base_path, filename) 62 | if os.path.isfile(filepath_state) is False: 63 | continue 64 | state = testcase_state.load_state(filepath_state) 65 | total_possible_insertions += len(state.lines_where_code_can_be_inserted) 66 | total_possible_insertions += len(state.lines_where_code_with_coma_can_be_inserted) 67 | 68 | print("In total %d possible code lines for insertion" % total_possible_insertions) 69 | 70 | runtime_in_days = (((total_possible_insertions * NUMBER_OF_DETERMINISTIC_OPERATIONS)/30.0) / 60) / 60.0 / float(EXEC_TIME) 71 | print("Estimated total runtime: %.2f days" % runtime_in_days) 72 | -------------------------------------------------------------------------------- /minimizer/implementations/ensure_tokens_are_contiguous.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # This function ensures that all tokens are contiguous. It should be 18 | # called as last minimization function (and only if the testcase was already standardized). 19 | # Other minimization functions maybe remove lines and therefore remove all occurrences of specific 20 | # variables. For example, let's assume a testcase contains var_1_, var_2_ and var_3_. 21 | # var_2_ just occurs in line 4 and the minimizer removes line 4. In this case var_3_ should be 22 | # renamed to var_2_ because the testcase doesn't contain the original var_2_ anymore. 23 | # The same applies for functions and classes. 24 | # This is implemented in this script. 25 | 26 | import minimizer.minimizer_helpers as minimizer_helpers 27 | import testcase_helpers 28 | 29 | 30 | def ensure_tokens_are_contiguous(code, required_coverage): 31 | 32 | # Handle variables: 33 | new_code = testcase_helpers.ensure_all_variable_names_are_contiguous(code) 34 | if new_code != code: 35 | if minimizer_helpers.does_code_still_trigger_coverage(new_code, required_coverage): 36 | code = new_code # Renaming was successful 37 | else: 38 | # renaming failed - however renaming is important, so let's try to fix it 39 | # Try it a second time: 40 | if minimizer_helpers.does_code_still_trigger_coverage(new_code, required_coverage): 41 | code = new_code # Renaming was successful 42 | else: 43 | # 2nd time was also not successful 44 | pass 45 | 46 | # Handle functions: 47 | new_code = testcase_helpers.ensure_all_function_names_are_contiguous(code) 48 | if new_code != code: 49 | if minimizer_helpers.does_code_still_trigger_coverage(new_code, required_coverage): 50 | code = new_code # Renaming was successful 51 | else: 52 | # renaming failed - however renaming is important, so let's try to fix it 53 | # Try it a second time: 54 | if minimizer_helpers.does_code_still_trigger_coverage(new_code, required_coverage): 55 | code = new_code # Renaming was successful 56 | else: 57 | # 2nd time was also not successful 58 | pass 59 | 60 | # Handle classes 61 | new_code = testcase_helpers.ensure_all_class_names_are_contiguous(code) 62 | if new_code != code: 63 | if minimizer_helpers.does_code_still_trigger_coverage(new_code, required_coverage): 64 | code = new_code # Renaming was successful 65 | else: 66 | # renaming failed - however renaming is important, so let's try to fix it 67 | # Try it a second time: 68 | if minimizer_helpers.does_code_still_trigger_coverage(new_code, required_coverage): 69 | code = new_code # Renaming was successful 70 | else: 71 | # 2nd time was also not successful 72 | pass 73 | 74 | return code 75 | -------------------------------------------------------------------------------- /minimizer/implementations/remove_line_by_line_multiline.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import minimizer.minimizer_helpers as minimizer_helpers 18 | 19 | 20 | def remove_line_by_line_multiline(code, required_coverage, start_symbol, end_symbol): 21 | lines = code.split("\n") 22 | lines_length = len(lines) 23 | 24 | keep_line = [True] * lines_length 25 | for possible_line_nr_to_remove in range(lines_length): 26 | line = lines[possible_line_nr_to_remove] 27 | # print("Line number: %d" % possible_line_nr_to_remove) 28 | if start_symbol in line: 29 | count_open = line.count(start_symbol) 30 | count_close = line.count(end_symbol) 31 | if count_open > count_close: 32 | # print("here1") 33 | diff = count_open - count_close 34 | # print("Diff is: %d" % diff) 35 | # go find end line (most likely next line) 36 | tmp_line_number = possible_line_nr_to_remove 37 | while True: 38 | tmp_line_number += 1 39 | if tmp_line_number >= lines_length: 40 | tmp_line_number -= 1 41 | break 42 | if keep_line[tmp_line_number] is False: 43 | continue 44 | new_line = lines[tmp_line_number] 45 | count_new_open = new_line.count(start_symbol) 46 | count_new_close = new_line.count(end_symbol) 47 | diff_new = count_new_open - count_new_close 48 | # print("diff_new is: %d" % diff_new) 49 | diff = diff + diff_new 50 | # print("diff for next iteration: %d" % diff) 51 | if diff <= 0: 52 | # found the end! 53 | break 54 | 55 | # print("End line number: %d" % tmp_line_number) 56 | code_with_removed_lines = "" 57 | for line_index in range(lines_length): 58 | if line_index == possible_line_nr_to_remove: 59 | continue # skip the add 60 | if possible_line_nr_to_remove < line_index <= tmp_line_number: 61 | continue 62 | if keep_line[line_index]: 63 | code_with_removed_lines += lines[line_index] + "\n" 64 | 65 | # print("Attempting:") 66 | # print(code_with_removed_lines) 67 | # print("-------------------") 68 | if minimizer_helpers.does_code_still_trigger_coverage(code_with_removed_lines, required_coverage): 69 | for x in range(possible_line_nr_to_remove, tmp_line_number + 1): 70 | keep_line[x] = False # The line is not important because it could be minimized away 71 | 72 | final_code = "" 73 | for line_index in range(lines_length): 74 | if keep_line[line_index]: 75 | final_code += lines[line_index] + "\n" 76 | return final_code 77 | -------------------------------------------------------------------------------- /native_code/example_usage_speed_optimized_functions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Hint: also check the script in the testsuite which contains more comprehensive tests 16 | 17 | import speed_optimized_functions 18 | 19 | 20 | 21 | def perror(msg): 22 | print("[-] ERROR: %s" % msg) 23 | raise Exception() # Raising an exception shows the stacktrace which contains the line number where a check failed 24 | # sys.exit(-1) 25 | 26 | 27 | def main(): 28 | # I re-implemented some python functions in C code to boost performance 29 | # This function tests the correctness of some of these implementations 30 | 31 | # Just some quick tests to check if the performance implementations work correctly 32 | 33 | # Get the next "{" starting symbol 34 | ret = speed_optimized_functions.get_index_of_next_symbol_not_within_string("foobar{ xxx };", "{") 35 | if ret != 6: 36 | perror("Test1: Returned index was not 6: %d" % ret) 37 | 38 | 39 | # Since there is no "{" symbol, it should return -1 40 | ret = speed_optimized_functions.get_index_of_next_symbol_not_within_string("foobar xxx };", "{") 41 | if ret != -1: 42 | perror("Test2: Returned index was not -1: %d" % ret) 43 | 44 | 45 | # Query the next "}" symbol, but it ignores "inner blocks". Since the "}" from the code is 46 | # from the block "{xxx }", it will be ignored and -1 should be returned 47 | ret = speed_optimized_functions.get_index_of_next_symbol_not_within_string("foobar{xxx };", "}") 48 | if ret != -1: 49 | perror("Test3: Returned index was not -1: %d" % ret) 50 | 51 | 52 | # Let's consider this (simplified) code: 53 | # for { 54 | # if { blabla } 55 | # else { foobar 56 | # } 57 | # xxxxx 58 | # } 59 | # SomeOtherCode; 60 | # 61 | # And then consider that the current position is after the "for {" line (it was already parsed) 62 | # In this case it should return the index of the last "}" symbol if I query for "}" 63 | code = " if { blabla }\nelse { foobar\n}\nxxxxx\n}\nSomeOtherCode;" 64 | ret = speed_optimized_functions.get_index_of_next_symbol_not_within_string(code, "}") 65 | if ret != 42: 66 | perror("Test4: Returned index was not 42: %d" % ret) 67 | symbol = code[ret] 68 | if symbol != '}': 69 | perror("Test4: Symbol is not }: %s" % symbol) 70 | 71 | 72 | # Since the "{" symbol is within a string, it should return -1 73 | ret = speed_optimized_functions.get_index_of_next_symbol_not_within_string("var x = 'foobar{xxx };'", '{') 74 | if ret != -1: 75 | perror("Test5: Returned index was not -1: %d" % ret) 76 | 77 | # Same as above, but now I add a "{" after the string "{" 78 | ret = speed_optimized_functions.get_index_of_next_symbol_not_within_string("var x = 'foobar{xxx };'; if { bla }", '{') 79 | if ret != 28: 80 | perror("Test6: Returned index was not 28: %d" % ret) 81 | 82 | 83 | # TODO: Add some more tests for other symbols, corner cases and so on.. 84 | 85 | print("[+] SUCCESS: Everything seems to work") 86 | 87 | 88 | if __name__ == "__main__": 89 | main() 90 | 91 | 92 | -------------------------------------------------------------------------------- /mutators/implementations/mutation_modify_number.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # This mutation strategy tries to modify a number. 18 | # Modifying means that a number is added to the start or end. 19 | # (On the other hand, the replace number mutation strategy would replace 20 | # a number which means the original number can't be found in the result). 21 | # Example: 22 | # var var_1_ = -123 23 | # 24 | # => 25 | # 26 | # var var_1_ = -Math.round(-0.1)>>123 27 | # 28 | # or 29 | # 30 | # var var_1_ = -123%(-~(~(-0)))+(-0)) 31 | 32 | import utils 33 | import tagging_engine.tagging as tagging 34 | from tagging_engine.tagging import Tag 35 | import mutators.testcase_mutators_helpers as testcase_mutators_helpers 36 | import javascript.js as js 37 | 38 | 39 | def mutation_modify_number(content, state): 40 | # utils.dbg_msg("Mutation operation: Modify number") 41 | tagging.add_tag(Tag.MUTATION_MODIFY_NUMBER1) 42 | 43 | prefix = "" 44 | 45 | positions_of_numbers = testcase_mutators_helpers.get_positions_of_all_numbers_in_testcase(content) 46 | if len(positions_of_numbers) == 0: 47 | tagging.add_tag(Tag.MUTATION_MODIFY_NUMBER2_DO_NOTHING) 48 | return content, state # nothing to replace 49 | 50 | (start_idx, end_idx) = utils.get_random_entry(positions_of_numbers) 51 | 52 | if utils.get_random_bool(): 53 | # add stuff in front of string 54 | tagging.add_tag(Tag.MUTATION_MODIFY_NUMBER3) 55 | pos_to_insert = start_idx 56 | else: 57 | # add stuff after the string 58 | tagging.add_tag(Tag.MUTATION_MODIFY_NUMBER4) 59 | pos_to_insert = end_idx + 1 60 | # utils.dbg_msg("Modify number at offset 0x%x" % (pos_to_insert)) 61 | 62 | possibilities = [1, 2, 3] 63 | random_choice = utils.get_random_entry(possibilities) 64 | if random_choice == 1: 65 | tagging.add_tag(Tag.MUTATION_MODIFY_NUMBER5) 66 | new_number = js.get_special_value() 67 | elif random_choice == 2: 68 | tagging.add_tag(Tag.MUTATION_MODIFY_NUMBER6) 69 | new_number = js.get_int() 70 | else: 71 | tagging.add_tag(Tag.MUTATION_MODIFY_NUMBER7) 72 | new_number = js.get_double() 73 | 74 | if utils.likely(0.2): 75 | tagging.add_tag(Tag.MUTATION_MODIFY_NUMBER8) 76 | (new_number, prefix) = testcase_mutators_helpers.decompose_number(new_number) 77 | 78 | random_math_operation = js.get_random_js_math_operation() # something like "+" or "*"" 79 | 80 | if pos_to_insert == start_idx: 81 | # insert in front 82 | added_content = new_number + random_math_operation 83 | else: 84 | # insert after 85 | added_content = random_math_operation + new_number 86 | 87 | added_length = len(added_content) 88 | 89 | new_content = content[:pos_to_insert] + added_content + content[pos_to_insert:] 90 | state.state_update_content_length(added_length, new_content) 91 | 92 | if prefix != "": 93 | # Prefix requires a state update to update all code lines 94 | new_content = prefix + new_content 95 | state.state_insert_line(0, new_content, prefix.strip()) 96 | 97 | return new_content, state 98 | -------------------------------------------------------------------------------- /modes/recalculate_testcase_runtimes.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # This mode can be started by passing the "--recalculate_testcase_runtimes_mode" flag to the fuzzer. 18 | # 19 | # Every testcase stores in it's state the runtime of the testcase. If a corpus was created 20 | # on a system different to the fuzzing system, the runtime will be wrong. 21 | # E.g.: I calculated the corpus (and especially the states) on my local system 22 | # and then switch to fuzzing to GCE instances. On my system a testcase can have a runtime of 50ms 23 | # whereas on GCE it can have a runtime of 217 ms. The runtime values which are used to calculate 24 | # upper boundaries for runtimes during fuzzing iterations can therefore be wrong. 25 | # This mode will execute every testcase and store the correct runtime in the state. 26 | # Execute it when you decide to use a new system. 27 | # 28 | # Side note: The max runtime is currently not really hardly enforced during fuzzing. 29 | # Slightly different runtimes therefore don't really hard that much (at least at the moment). 30 | 31 | 32 | import sys 33 | import os 34 | base_dir = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..')) 35 | if base_dir not in sys.path: sys.path.append(base_dir) 36 | import testcase_state 37 | import utils 38 | import config as cfg 39 | 40 | 41 | def recalculate_testcase_runtimes(): 42 | corpus_filepath = cfg.output_dir_current_corpus 43 | 44 | counter = 0 45 | exception_testcases = [] 46 | for filename in os.listdir(corpus_filepath): 47 | if filename.endswith(".js") is False: 48 | continue 49 | 50 | filepath = os.path.join(corpus_filepath, filename) 51 | if os.path.isfile(filepath) is False: 52 | continue 53 | 54 | with open(filepath, "r") as fobj: 55 | content = fobj.read() 56 | 57 | state_filepath = filepath + ".pickle" 58 | state = testcase_state.load_state(state_filepath) 59 | 60 | counter += 1 61 | utils.msg("[i] Going to recalculate runtime of testcase (counter: %d): %s" % (counter, filename)) 62 | original_runtime_length = state.runtime_length_in_ms 63 | runtimes = [] 64 | number_exceptions = 0 65 | for i in range(0, 5): # Perform 5 executions 66 | result = cfg.exec_engine.execute_once(content) 67 | if "SUCCESS" not in result.get_status_str(): 68 | number_exceptions += 1 69 | runtimes.append(result.exec_time) 70 | if number_exceptions > 1: 71 | exception_testcases.append(filename) 72 | continue 73 | 74 | runtimes.sort() 75 | runtimes.pop(0) # remove fastest execution 76 | runtimes.pop() # remove slowest execution 77 | average_runtime = float(sum(runtimes)) / len(runtimes) 78 | utils.msg("[i] New runtime %.2f (old runtime %.2f): %s" % (average_runtime, original_runtime_length, filename)) 79 | 80 | state.runtime_length_in_ms = average_runtime 81 | testcase_state.save_state(state, state_filepath) 82 | 83 | for filename in exception_testcases: 84 | utils.msg("[-] Exception testcase: %s" % filename) 85 | -------------------------------------------------------------------------------- /standardizer/implementations/rename_classes.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import utils 18 | import standardizer.standardizer_helpers as standardizer_helpers 19 | 20 | import javascript.js_renaming as js_renaming 21 | import javascript.js_helpers as js_helpers 22 | 23 | 24 | def rename_classes(content, required_coverage): 25 | class_names_to_rename = set() 26 | 27 | for class_decl in ["class ", ]: 28 | tmp = content.replace("\t", " ").replace("\n", " ").split(class_decl) 29 | if class_decl not in tmp[0]: 30 | tmp.pop(0) # remove first which doesn't start with class 31 | for part in tmp: 32 | if "{" not in part: 33 | continue 34 | idx_1 = part.index("{") 35 | class_name = part[:idx_1].strip() 36 | if " extends" in class_name: 37 | class_name = class_name.split()[0] 38 | if class_name != "" and js_helpers.contains_only_valid_token_characters(class_name): 39 | if class_name.startswith("cl_") is False: 40 | class_names_to_rename.add(class_name) 41 | 42 | class_names_to_rename = list(class_names_to_rename) 43 | # Start renaming with the longest variable name. 44 | # This helps to prevent cases where a variable name is the substring of another variable name 45 | class_names_to_rename.sort(reverse=True, key=len) 46 | 47 | last_used_class_id = 0 48 | for idx in range(200): 49 | token_name = "cl_%d_" % idx 50 | if token_name in content: 51 | last_used_class_id = idx 52 | 53 | class_id = last_used_class_id + 1 54 | for class_name in class_names_to_rename: 55 | 56 | utils.msg("[i] Attempt to rename class: %s" % class_name) 57 | renamed_successful = False 58 | 59 | new_content = js_renaming.rename_class_name_safe(content, class_name, class_id) 60 | if new_content != content and standardizer_helpers.does_code_still_trigger_coverage(new_content, required_coverage): 61 | renamed_successful = True 62 | 63 | if renamed_successful is False: 64 | if class_name != "_": 65 | # Here is a fallback to old code which renamed tokens (which is likely buggy but maybe works in some corner cases?) 66 | new_content = js_renaming.rename_class_name_old(content, class_name, class_id) 67 | if new_content != content and standardizer_helpers.does_code_still_trigger_coverage(new_content, required_coverage): 68 | renamed_successful = True 69 | 70 | if renamed_successful is False: 71 | if class_name != "_": 72 | # Last fallback is just to try replacing every token.... 73 | new_content = content.replace(class_name, "cl_%d_" % class_id) 74 | if new_content != content and standardizer_helpers.does_code_still_trigger_coverage(new_content, required_coverage): 75 | renamed_successful = True 76 | 77 | if renamed_successful: 78 | utils.msg("[+] Successfully renamed class: %s to cl_%d_" % (class_name, class_id)) 79 | content = new_content 80 | class_id += 1 81 | else: 82 | utils.msg("[-] Renaming class failed: %s" % class_name) 83 | 84 | return content 85 | -------------------------------------------------------------------------------- /mutators/implementations/mutation_replace_number.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # This mutation strategy replaces a random number with another randomly generated number. 18 | 19 | import utils 20 | import tagging_engine.tagging as tagging 21 | from tagging_engine.tagging import Tag 22 | import mutators.testcase_mutators_helpers as testcase_mutators_helpers 23 | import javascript.js as js 24 | 25 | 26 | def mutation_replace_number(content, state): 27 | # utils.dbg_msg("Mutation operation: Replace number") 28 | tagging.add_tag(Tag.MUTATION_REPLACE_NUMBER1) 29 | 30 | prefix = "" 31 | was_minus_zero = False 32 | 33 | positions_of_numbers = testcase_mutators_helpers.get_positions_of_all_numbers_in_testcase(content) 34 | if len(positions_of_numbers) == 0: 35 | tagging.add_tag(Tag.MUTATION_REPLACE_NUMBER2_DO_NOTHING) 36 | return content, state # nothing to replace 37 | 38 | (start_idx, end_idx) = utils.get_random_entry(positions_of_numbers) 39 | # utils.dbg_msg("Replacing number which starts at 0x%x and ends at 0x%x" % (start_idx, end_idx)) 40 | 41 | if utils.likely(0.2): # 20% of cases 42 | tagging.add_tag(Tag.MUTATION_REPLACE_NUMBER3) 43 | original_number = content[start_idx:end_idx + 1] 44 | if original_number == "-0": 45 | was_minus_zero = True 46 | (random_number, prefix) = testcase_mutators_helpers.decompose_number(original_number) 47 | else: 48 | if utils.likely(0.1): # 10% of cases 49 | tagging.add_tag(Tag.MUTATION_REPLACE_NUMBER4) 50 | random_number = js.get_double() 51 | else: 52 | if utils.likely(0.5): 53 | tagging.add_tag(Tag.MUTATION_REPLACE_NUMBER5) 54 | random_number = js.get_int() 55 | if utils.likely(0.1): # in 10% of cases 56 | tagging.add_tag(Tag.MUTATION_REPLACE_NUMBER6) 57 | (random_number, prefix) = testcase_mutators_helpers.decompose_number(random_number) # also decompose the self created number 58 | else: 59 | tagging.add_tag(Tag.MUTATION_REPLACE_NUMBER7) 60 | random_number = js.get_special_value() 61 | 62 | if random_number[0] != "-": # only if the number is not already negative 63 | if utils.likely(0.2) and was_minus_zero is False: # 20% of cases make it negative 64 | try: 65 | if content[start_idx - 1] != "-": # if it's not already negative. 66 | tagging.add_tag(Tag.MUTATION_REPLACE_NUMBER8) 67 | random_number = "-" + random_number # make it negative 68 | except: 69 | tagging.add_tag(Tag.MUTATION_REPLACE_NUMBER9_SHOULD_NEVER_OCCUR) 70 | random_number = "-" + random_number # make it negative 71 | 72 | new_content = content[:start_idx] + random_number + content[end_idx + 1:] 73 | added_length = len(random_number) - (end_idx - start_idx + 1) # can also become negative, but that's ok 74 | state.state_update_content_length(added_length, new_content) 75 | 76 | if prefix != "": 77 | # Prefix requires a state update to update all code lines 78 | new_content = prefix + new_content 79 | state.state_insert_line(0, new_content, prefix.strip()) 80 | 81 | return new_content, state 82 | -------------------------------------------------------------------------------- /standardizer/implementations/rename_functions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import utils 18 | import standardizer.standardizer_helpers as standardizer_helpers 19 | import javascript.js_renaming as js_renaming 20 | import javascript.js_helpers as js_helpers 21 | 22 | 23 | def rename_functions(content, required_coverage): 24 | function_names_to_rename = set() 25 | 26 | for function_decl in ["function ", "function* "]: 27 | tmp = content.replace("\t", " ").replace("\n", " ").split(function_decl) 28 | if function_decl not in tmp[0]: 29 | tmp.pop(0) # remove first which doesn't start with function 30 | for part in tmp: 31 | if "(" not in part: 32 | continue 33 | idx_1 = part.index("(") 34 | function_name = part[:idx_1].strip() 35 | if function_name != "" and js_helpers.contains_only_valid_token_characters(function_name): 36 | if function_name.startswith("func_") is False: 37 | function_names_to_rename.add(function_name) 38 | 39 | function_names_to_rename = list(function_names_to_rename) 40 | # Start renaming with the longest variable name. 41 | # This helps to prevent cases where a variable name is the substring of another variable name 42 | function_names_to_rename.sort(reverse=True, key=len) 43 | 44 | last_used_function_id = 0 45 | for idx in range(200): 46 | token_name = "func_%d_" % idx 47 | if token_name in content: 48 | last_used_function_id = idx 49 | 50 | func_id = last_used_function_id + 1 51 | for function_name in function_names_to_rename: 52 | utils.msg("[i] Attempt to rename function: %s" % function_name) 53 | renamed_successful = False 54 | # print("attempt1:") 55 | new_content = js_renaming.rename_function_name_safe(content, function_name, func_id) 56 | if new_content != content and standardizer_helpers.does_code_still_trigger_coverage(new_content, required_coverage): 57 | renamed_successful = True 58 | 59 | if renamed_successful is False: 60 | if function_name != "_": 61 | # Here is a fallback to old code which renamed tokens (which is likely buggy but maybe works in some corner cases?) 62 | new_content = js_renaming.rename_function_name_old(content, function_name, func_id) 63 | # print("attempt2:") 64 | if new_content != content and standardizer_helpers.does_code_still_trigger_coverage(new_content, required_coverage): 65 | renamed_successful = True 66 | 67 | if renamed_successful is False: 68 | if function_name != "_": 69 | # Last fallback is just to try replacing every token.... 70 | new_content = content.replace(function_name, "func_%d_" % func_id) 71 | # print("attempt3:") 72 | if new_content != content and standardizer_helpers.does_code_still_trigger_coverage(new_content, required_coverage): 73 | renamed_successful = True 74 | 75 | if renamed_successful: 76 | utils.msg("[+] Successfully renamed function: %s to func_%d_" % (function_name, func_id)) 77 | content = new_content 78 | func_id += 1 79 | else: 80 | utils.msg("[-] Renaming function failed: %s" % function_name) 81 | 82 | return content 83 | -------------------------------------------------------------------------------- /debugging/display_pickle_database_variable_operations.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import sys 18 | import os 19 | current_dir = os.path.dirname(os.path.realpath(__file__)) 20 | base_dir = os.path.abspath(os.path.join(current_dir, '..')) 21 | if base_dir not in sys.path: sys.path.append(base_dir) 22 | 23 | import pickle 24 | 25 | 26 | with open("generic_operations.pickle", 'rb') as finput: 27 | all_generic_operations_with_state = pickle.load(finput) 28 | 29 | with open("variable_operations.pickle", 'rb') as finput: 30 | all_variable_operations_with_state = pickle.load(finput) 31 | 32 | with open("variable_operations_others.pickle", 'rb') as finput: 33 | all_variable_operations_others_with_state = pickle.load(finput) 34 | 35 | with open("variable_operations_states_list.pickle", 'rb') as finput: 36 | variable_operations_states_list = pickle.load(finput) 37 | 38 | with open("variable_operations_list.pickle", 'rb') as finput: 39 | variable_operations_list = pickle.load(finput) 40 | 41 | 42 | print("Generic operations: %d" % len(all_generic_operations_with_state)) 43 | """ 44 | for operation in all_generic_operations_with_state: 45 | 46 | (code, state) = operation 47 | #print("Code (generic):%s" % code) 48 | print(code) 49 | #print(state) 50 | print("---------") 51 | print("\n"*5) 52 | """ 53 | 54 | 55 | for variable_type in all_variable_operations_with_state: 56 | print("Datatype_operation %s : %d" % (variable_type, len(all_variable_operations_with_state[variable_type]))) 57 | """ 58 | for operation in all_variable_operations_with_state[variable_type]: 59 | (code, state) = operation 60 | print("Code (%s):%s" % (variable_type,code)) 61 | #print(state) 62 | print("---------") 63 | """ 64 | 65 | print("\n"*5) 66 | for variable_type in all_variable_operations_others_with_state: 67 | print("Datatype_operation (others) %s : %d" % (variable_type, len(all_variable_operations_others_with_state[variable_type]))) 68 | """ 69 | for operation in all_variable_operations_others_with_state[variable_type]: 70 | (code, state) = operation 71 | print("Code (%s other):%s" % (variable_type,code)) 72 | #print(state) 73 | print("---------") 74 | """ 75 | 76 | 77 | 78 | # Manual checks if specific operations are in the database 79 | for operation in all_variable_operations_with_state["regexp"]: 80 | (code_index, state_index) = operation 81 | code = variable_operations_list[code_index] 82 | print(code) 83 | print("---------") 84 | # if state != None: 85 | # print("HERE") 86 | 87 | # if "new RegExp(var_TARGET_[var_1" in code: 88 | # print("Code:\n%s" % code) 89 | # print("---------") 90 | # #print(state) 91 | 92 | """ 93 | if len(code.split("\n")) == 1 or len(code.split("\n")) == 2: 94 | #print(code) 95 | #print("---------") 96 | 97 | if "var var_1_ = false;" in code: 98 | print("Code:\n%s" % code) 99 | print("---------") 100 | print(state) 101 | """ 102 | 103 | """ 104 | for operation in all_generic_operations_with_state: 105 | (code, state) = operation 106 | if len(code.split("\n")) == 1 or len(code.split("\n")) == 2: 107 | # print(code) 108 | # print("---------") 109 | 110 | if ".sign(" in code: 111 | print("Code:\n%s" % code) 112 | print("---------") 113 | """ -------------------------------------------------------------------------------- /mutators/implementations/mutation_if_wrap_line.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | import utils 17 | import tagging_engine.tagging as tagging 18 | from tagging_engine.tagging import Tag 19 | import mutators.testcase_mutators_helpers as testcase_mutators_helpers 20 | 21 | 22 | def mutation_if_wrap_line(content, state): 23 | utils.dbg_msg("Mutation operation: IF-wrap code line") 24 | tagging.add_tag(Tag.MUTATION_IF_WRAP_LINE1) 25 | 26 | # Simple mutation to add IF: 27 | # console.log("code") 28 | # => 29 | # if (1) console.log("code") 30 | 31 | # TODO: This can lead to problems if it injects inside a "(" and ")" or inside a "{" and "}" 32 | # For example, if I create an object over multiple lines and it injects the if in the property 33 | # definitions... 34 | 35 | lines = content.split("\n") 36 | possibilities_top_insert_if = testcase_mutators_helpers.get_code_lines_without_forbidden_words(lines) 37 | if len(possibilities_top_insert_if) == 0: 38 | tagging.add_tag(Tag.MUTATION_IF_WRAP_LINE2_DO_NOTHING) 39 | return content, state # nothing to modify 40 | 41 | random_line_number = utils.get_random_entry(possibilities_top_insert_if) 42 | old_line = lines[random_line_number] 43 | 44 | code_to_append = "if (1) " 45 | 46 | if utils.likely(0.1): # 10% of cases try to use a variable instead of the "1" 47 | 48 | possible_boolean_variables = [] 49 | possible_variables = [] 50 | # Check if there is a boolean variable available! 51 | for variable_name in state.variable_types: 52 | for entry in state.variable_types[variable_name]: 53 | (tmp_line_number, variable_type) = entry 54 | if tmp_line_number == random_line_number and variable_type == "boolean": 55 | possible_boolean_variables.append(variable_name) 56 | elif tmp_line_number == random_line_number: 57 | possible_variables.append(variable_name) 58 | 59 | if len(possible_boolean_variables) != 0: 60 | tagging.add_tag(Tag.MUTATION_IF_WRAP_LINE3) 61 | random_boolean_variable = utils.get_random_entry(possible_boolean_variables) 62 | code_to_append = "if (%s) " % random_boolean_variable 63 | else: 64 | # no boolean variables, then just use any other available variable... 65 | if len(possible_variables) != 0: 66 | tagging.add_tag(Tag.MUTATION_IF_WRAP_LINE4) 67 | random_variable = utils.get_random_entry(possible_variables) 68 | code_to_append = "if (%s) " % random_variable 69 | else: 70 | # Note: 71 | # Previously I just set the condition to false, however 72 | # this resulted in 80% of the cases in exceptions and just 20% were successful 73 | # From 1000 executed cases it resulted 2 times in new coverage 74 | # I therefore keep the code but make it very unlikely to occur 75 | if utils.likely(0.01): 76 | tagging.add_tag(Tag.MUTATION_IF_WRAP_LINE5_MANY_EXCEPTIONS) 77 | code_to_append = "if (false) " # make it just false (=> line will not be executed) 78 | else: 79 | tagging.add_tag(Tag.MUTATION_IF_WRAP_LINE6) 80 | code_to_append = "if (true) " 81 | 82 | new_line = code_to_append + old_line 83 | lines[random_line_number] = new_line 84 | 85 | new_content = "\n".join(lines) 86 | 87 | added_length = len(code_to_append) 88 | state.state_update_content_length(added_length, new_content) 89 | 90 | return new_content, state 91 | -------------------------------------------------------------------------------- /modes/developer_mode.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | # This mode can be started by passing the "--developer_mode" flag to the fuzzer. 17 | # 18 | # I'm using this mode during development. I add code to the mode which executes 19 | # e.g.: a mutation strategy which I currently implement. Then the developer mode 20 | # just executes this mutation strategy on a specific testcase and I implement it 21 | # (and for corner cases for different testcases) 22 | # Note: It's recommended to also specify a --seed argument to get reliable results 23 | # during development 24 | 25 | """ 26 | # Example invocation: 27 | 28 | python3 JS_FUZZER.py \ 29 | --output_dir /home/user/Desktop/input/OUTPUT \ 30 | --seed 25 \ 31 | --resume \ 32 | --developer 33 | """ 34 | 35 | 36 | import sys 37 | import os 38 | base_dir = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..')) 39 | if base_dir not in sys.path: sys.path.append(base_dir) 40 | import utils 41 | import config as cfg 42 | import testcase_state 43 | from native_code.executor import Executor, Execution_Status 44 | import mutators.database_operations as database_operations 45 | from mutators.implementations.mutation_if_wrap_operations import mutation_if_wrap_operations 46 | from mutators.implementations.mutation_modify_number import mutation_modify_number 47 | 48 | 49 | def load_testcase(): 50 | testcase_path = "/home/user/Desktop/input/OUTPUT/current_corpus/tc3770.js" 51 | state_path = testcase_path + ".pickle" 52 | with open(testcase_path, 'r') as fobj: 53 | testcase_content = fobj.read().rstrip() 54 | state = testcase_state.load_state(state_path) 55 | return testcase_content, state 56 | 57 | 58 | def start_developer_mode(): 59 | (testcase_content, state) = load_testcase() 60 | 61 | utils.print_diff_with_line_numbers("Input testcase:", testcase_content, testcase_content) 62 | # print("Input testcase:") 63 | # print("#"*30) 64 | # print(testcase_content) 65 | print("#"*30) 66 | print("\n") 67 | 68 | # print("State:") 69 | # print(state.get_summary()) 70 | 71 | number_iterations = 1 72 | for i in range(number_iterations): 73 | state_copy = state.deep_copy() 74 | 75 | # random_code = debugging_call_get_random_js_thing(testcase_content, state_copy, 2) 76 | # print("Returned random code:") 77 | # print(random_code) 78 | 79 | # parts = testcase_content.split("\n") 80 | # instr = js_parsing.parse_next_instruction('\n'.join(parts[5:])) 81 | # print("Result:") 82 | # print(instr) 83 | 84 | (result_content, result_state) = mutation_modify_number(testcase_content, state_copy) 85 | 86 | # print("Result:") 87 | # print("#"*30) 88 | # print(result_content) 89 | 90 | # utils.print_diff_with_line_numbers("Result", testcase_content, result_content) 91 | 92 | # Printing a diff in colors is sometimes buggy (e.g. counting of line numbers) 93 | # In such cases I don't print the diff by passing twice the same argument 94 | utils.print_diff_with_line_numbers("Result", result_content, result_content) 95 | 96 | # input("") 97 | # print("#"*30) 98 | # print("State:") 99 | # print(result_state.get_summary()) 100 | 101 | result = cfg.exec_engine.execute_safe(result_content) 102 | print("Execution result:") 103 | print(result) 104 | 105 | if number_iterations != 1: 106 | input("") 107 | -------------------------------------------------------------------------------- /commit_checker/entities/bug_report.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | # This script contains the class definition for a Bug Report. 17 | 18 | 19 | class Bug_Report: 20 | 21 | def __init__(self): 22 | self.number = 0 23 | self.permission_denied = False 24 | self.type = "" 25 | self.status = "" 26 | self.openedTimestamp = 0 27 | self.reporter_name = "" 28 | self.reporter_id = 0 29 | self.owner_name = "" 30 | self.owner_id = 0 31 | self.assigned_persons = [] # TODO 32 | self.summary = "" 33 | self.text = "" 34 | 35 | self.referenced_change_data = [] 36 | self.labels = [] 37 | self.fields = dict() 38 | self.components = [] 39 | self.priority = 0 40 | self.comments = [] 41 | self.print_verbose = False 42 | self.does_not_exist = False 43 | 44 | def does_bug_not_exist(self): 45 | try: 46 | if self.does_not_exist: 47 | return True 48 | return False 49 | except: 50 | return False 51 | 52 | def is_restricted(self): 53 | if self.permission_denied: 54 | return True 55 | return False 56 | 57 | def __str__(self): 58 | tmp = "" 59 | tmp += "Chromium Bug: %d ( https://bugs.chromium.org/p/v8/issues/detail?id=%d )\n" % (self.number, self.number) 60 | try: 61 | if self.does_not_exist: 62 | tmp += "Bug does not exist!\n" 63 | return tmp 64 | except: 65 | pass 66 | if self.permission_denied: 67 | tmp += "Restricted security bug!\n" 68 | return tmp 69 | 70 | tmp += "Summary: %s\n" % self.summary 71 | tmp += "Type: %s\n" % self.type 72 | tmp += "Priority: %s\n" % self.priority 73 | tmp += "Status: %s\n" % self.status 74 | tmp += "Opened Timestamp: %d\n" % self.openedTimestamp 75 | tmp += "Reported: %s (%d)\n" % (self.reporter_name, self.reporter_id) 76 | if self.owner_name == "" and self.owner_id == 0: 77 | tmp += "Owner: \n" 78 | else: 79 | tmp += "Owner: %s (%d)\n" % (self.owner_name, self.owner_id) 80 | 81 | 82 | tmp += "Components: %s\n" % ', '.join(self.components) 83 | 84 | # tmp += "Labels: %s\n" % ', '.join(self.labels) 85 | tmp += "Labels:\n" 86 | for label_name in self.labels: 87 | tmp += "\t%s\n" % label_name 88 | tmp += "\n" 89 | 90 | tmp += "Fields:\n" 91 | for field_name in self.fields: 92 | tmp += "\t%s: %s\n" % (field_name, self.fields[field_name]) 93 | tmp += "\n" 94 | 95 | tmp += "Assigned persons:\n" 96 | for entry in self.assigned_persons: 97 | (displayName, userId) = entry 98 | tmp += "\t%s (%s)\n" % (displayName, userId) 99 | tmp += "\n" 100 | 101 | if self.print_verbose: 102 | tmp += "Text:\n%s\n" % self.text 103 | pass # TODO implement when I need it 104 | """ 105 | tmp += "Comments:\n" 106 | for entry in self.comments: 107 | (sequence_number, text, comment_author_name, comment_author_id, timestamp) = entry 108 | 109 | tmp += "\tComment 110 | """ 111 | 112 | # TODO: 113 | # self.assigned_persons = [] 114 | # self.text = "" 115 | # self.referenced_change_data = [] 116 | 117 | return tmp 118 | -------------------------------------------------------------------------------- /result_analysis/1.1_sync_files_from_gce_bucket.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | # This script downloads results from the GCE bucket 18 | # Please note: You can also download files via command line (gcloud), but this is extremely slow 19 | # (I mean really, really slow..) 20 | # This script is a lot faster (but still slow...) 21 | 22 | from google.cloud import storage 23 | import os 24 | 25 | 26 | # Output folders to which the GCE bucket files should be downloaded. 27 | # These folders must already exist before the script is started 28 | crashes_out_dir = os.path.abspath("crashes") 29 | stats_out_dir = os.path.abspath("stats") 30 | 31 | # False... The script will just download discovered crashes 32 | # True... The script will also download stats files 33 | # Be aware: Depending on the runtime and number of your machines, 34 | # the stats files can easily consuming several GB of disk space 35 | also_download_stats = True 36 | 37 | gce_bucket_credential_path = "/home/user/key/your_gce_service_account_credentials.json" 38 | os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = gce_bucket_credential_path 39 | storage_client = storage.Client(gce_bucket_credential_path) 40 | bucket = storage_client.get_bucket("your_gce_bucket_name") 41 | 42 | 43 | def get_all_filenames_in_folder(folder_name): 44 | global bucket 45 | ret = [] 46 | for filename in list(bucket.list_blobs(prefix=folder_name + "/")): 47 | real_filename = filename.name.split("/", 1)[1] 48 | if real_filename == "": 49 | continue # it's the folder entry 50 | ret.append(real_filename) 51 | return ret 52 | 53 | 54 | def download_file(folder_name, filename): 55 | global bucket 56 | blob_path = "%s/%s" % (folder_name, filename) 57 | file_content = bucket.blob(blob_name=blob_path).download_as_string() 58 | return file_content.decode("utf-8") 59 | 60 | 61 | 62 | def main(): 63 | global also_download_stats 64 | 65 | print("Going to query all crash names...") 66 | all_crash_names = get_all_filenames_in_folder("crashes") 67 | 68 | idx = 0 69 | total = len(all_crash_names) 70 | for crash_name in all_crash_names: 71 | idx += 1 72 | output_fullpath = os.path.join(crashes_out_dir, crash_name) 73 | if os.path.exists(output_fullpath): 74 | continue # file must not be downloaded again 75 | 76 | file_content = download_file("crashes", crash_name) 77 | print("Downloaded file %s (%d / %d)" % (crash_name, idx, total)) 78 | with open(output_fullpath, "w") as fobj: 79 | fobj.write(file_content) 80 | 81 | print("Downloaded in total %d crash files!" % len(all_crash_names)) 82 | 83 | if also_download_stats is False: 84 | return 85 | # Below code is to download the .stats files (which can easily consume several GB! 86 | print("Going to query all stats names...") 87 | all_stats_names = get_all_filenames_in_folder("stats") 88 | 89 | idx = 0 90 | total = len(all_stats_names) 91 | for stats_name in all_stats_names: 92 | idx += 1 93 | output_fullpath = os.path.join(stats_out_dir, stats_name) 94 | if os.path.exists(output_fullpath): 95 | continue # file must not be downloaded again 96 | 97 | file_content = download_file("stats", stats_name) 98 | print("Downloaded file %s (%d / %d)" % (stats_name, idx, total)) 99 | with open(output_fullpath, "w") as fobj: 100 | fobj.write(file_content) 101 | 102 | print("Downloaded in total %d stats files!" % len(all_stats_names)) 103 | 104 | 105 | if __name__ == "__main__": 106 | main() 107 | -------------------------------------------------------------------------------- /standardizer/implementations/rename_variables.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import utils 18 | import standardizer.standardizer_helpers as standardizer_helpers 19 | import javascript.js_renaming as js_renaming 20 | import javascript.js_helpers as js_helpers 21 | 22 | 23 | def rename_variables(content, required_coverage): 24 | variable_names_to_rename = js_helpers.get_all_variable_names_in_testcase(content) 25 | 26 | # the above method (get_all_variable_names_in_testcase()) was my old code which missed a lot of cases 27 | # The following function is my new code which should catch all variable names! 28 | # Just to get sure I merge both lists (which most likely contains some wrong cases from the first function 29 | # but it doesn't matter, the runtime is just a little bit longer...) 30 | variable_names_to_rename2 = js_helpers.get_variable_name_candidates(content) 31 | for variable_name in variable_names_to_rename2: 32 | if variable_name not in variable_names_to_rename: 33 | variable_names_to_rename.append(variable_name) 34 | 35 | if len(variable_names_to_rename) == 0: 36 | return content # Nothing to rename 37 | 38 | # Start renaming with the longest variable name. 39 | # This helps to prevent cases where a variable name is the substring of another variable name 40 | variable_names_to_rename.sort(reverse=True, key=len) 41 | 42 | # Get starting ID for the new variables 43 | last_used_variable_id = 0 44 | for idx in range(8000): 45 | token_name = "var_%d_" % idx 46 | if token_name in content: 47 | last_used_variable_id = idx 48 | 49 | # Now iterate through all variables and replace them 50 | variable_id = last_used_variable_id+1 51 | for variable_name in variable_names_to_rename: 52 | utils.msg("[i] Attempting to rename variable: %s" % variable_name) 53 | 54 | renamed_successful = False 55 | 56 | for i in range(0, 2): # try 2 times to rename a variable => it's really important that the fuzzer knows the variable token names! 57 | new_content = js_renaming.rename_variable_name_safe(content, variable_name, variable_id) 58 | # print("attempt1_variable") 59 | if new_content != content and standardizer_helpers.does_code_still_trigger_coverage(new_content, required_coverage): 60 | renamed_successful = True 61 | break 62 | 63 | if renamed_successful is False: 64 | if variable_name != "_": 65 | for i in range(0, 2): 66 | # Here is a fallback to old code which renamed tokens (which is likely buggy but maybe works in some corner cases?) 67 | new_content = js_renaming.rename_variable_name_old(content, variable_name, variable_id) 68 | # print("attempt2_variable") 69 | if new_content != content and standardizer_helpers.does_code_still_trigger_coverage(new_content, required_coverage): 70 | renamed_successful = True 71 | break 72 | 73 | if renamed_successful is False: 74 | if variable_name != "_": 75 | # Last fallback is just to try replacing every token.... 76 | new_content = content.replace(variable_name, "var_%d_" % variable_id) 77 | # print("attempt3_variable") 78 | if new_content != content and standardizer_helpers.does_code_still_trigger_coverage(new_content, required_coverage): 79 | renamed_successful = True 80 | 81 | if renamed_successful: 82 | utils.msg("[+] Successfully renamed variable: %s to var_%d_" % (variable_name, variable_id)) 83 | content = new_content 84 | variable_id += 1 85 | else: 86 | utils.msg("[-] Renaming variable failed: %s" % variable_name) 87 | return content 88 | -------------------------------------------------------------------------------- /minimizer/minimizer_helpers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import native_code.speed_optimized_functions as speed_optimized_functions 18 | import native_code.coverage_helpers as coverage_helpers 19 | import testcase_helpers 20 | 21 | tmp_coverage_filepath = None 22 | 23 | 24 | def initialize(current_coverage_filepath): 25 | global tmp_coverage_filepath 26 | tmp_coverage_filepath = current_coverage_filepath 27 | 28 | 29 | def does_code_still_trigger_coverage(new_code, required_coverage): 30 | global tmp_coverage_filepath 31 | triggered_coverage = coverage_helpers.extract_coverage_of_testcase(new_code, tmp_coverage_filepath) 32 | for coverage_entry in required_coverage: 33 | if coverage_entry not in triggered_coverage: 34 | return False 35 | return True 36 | 37 | 38 | 39 | 40 | 41 | def remove_function(code, function_name): 42 | # print("REMOVING FUNCTION: %s" % function_name) 43 | if function_name not in code: 44 | return code 45 | index_function_name = code.index(function_name) 46 | right_side = code[index_function_name + len(function_name):] 47 | left_side = code[:index_function_name] 48 | 49 | # Let's first fix the right side and remove the function body 50 | index = speed_optimized_functions.get_index_of_next_symbol_not_within_string(right_side, "{") 51 | if index == -1: 52 | return code # some error case 53 | 54 | rest = right_side[index+1:] 55 | end_index = speed_optimized_functions.get_index_of_next_symbol_not_within_string(rest, "}") 56 | if end_index == -1: 57 | return code # some error case 58 | 59 | stuff_after_function = right_side[index+1+1+end_index:] # both +1 are for { and } 60 | 61 | # Now let's handle the left side which should still contain something like "\nfunction " 62 | try: 63 | newline_index = len(left_side) - left_side[::-1].index("\n") - 1 # search from the end for the last occurrence of newline (which must occur before a function declaration) 64 | stuff_before_newline = left_side[:newline_index] 65 | stuff_after_newline = left_side[newline_index+1:] 66 | if "function" not in stuff_after_newline: 67 | return code # something is wrong, so just return the not modified code 68 | 69 | return stuff_before_newline.rstrip("\n") + "\n" + stuff_after_function.lstrip("\n") # This is the good case, return everything except the function 70 | except: 71 | return code # exception means something was wrong, so just return the not modified code 72 | 73 | 74 | 75 | def remove_variable_from_function_header(content, variable_name): 76 | codeline = testcase_helpers.get_first_codeline_which_contains_token(content, variable_name) 77 | token_to_remove = variable_name 78 | idx = codeline.find(variable_name) 79 | rest = codeline[idx+len(variable_name):] 80 | rest_len = len(rest) 81 | idx = 0 82 | if rest[idx:].startswith(" "): 83 | while idx < rest_len: 84 | current_char = rest[idx] 85 | if current_char == " ": 86 | token_to_remove += current_char 87 | idx += 1 88 | continue 89 | else: 90 | break 91 | if rest[idx:].startswith(","): 92 | while idx < rest_len: 93 | current_char = rest[idx] 94 | if current_char == ",": 95 | token_to_remove += current_char 96 | idx += 1 97 | continue 98 | else: 99 | break 100 | if rest[idx:].startswith(" "): 101 | while idx < rest_len: 102 | current_char = rest[idx] 103 | if current_char == " ": 104 | token_to_remove += current_char 105 | idx += 1 106 | continue 107 | else: 108 | break 109 | new_content = content.replace(token_to_remove, "") 110 | return new_content 111 | -------------------------------------------------------------------------------- /automation_gce/start_fuzzing_16.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2022 @ReneFreingruber 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | # The content of this script must be saved in the "startup-script" meta information 19 | # of a GCE machine (alternatively the >start_gce_instances.sh< script will pass it. 20 | # => Then this script automatically starts fuzzing when a GCE machine is spawned 21 | 22 | # The script currently starts 16 fuzzing jobs. 23 | # If you want to use less/more cores, you must adapt this script 24 | # Note: This also requires OUTPUT folders for every running fuzzer, e.g. 6 cores => 6 OUTPUT directories 25 | # You must create these OUTPUT folders in the base-image of the fuzzer. 26 | 27 | # TODO: Rewrite the script to use a loop... 28 | 29 | tmux new-session -d -s "fuzzing"; 30 | 31 | tmux setw -g mouse on 32 | tmux bind h split-window -h 33 | tmux bind v split-window -v 34 | 35 | tmux rename-window -t 0 "Fuzzer1" 36 | tmux send-keys -t "fuzzing" "cd /home/gce_user/fuzzer/; ./prepare_system_for_fuzzing.sh" C-m 37 | sleep 1 # Give a second to make sure the preparation finished 38 | tmux send-keys -t "fuzzing" "cd /home/gce_user/fuzzer/; ./watchdog.sh OUTPUT" C-m 39 | 40 | sleep 1 41 | tmux new-window -t "fuzzing" 42 | tmux rename-window -t 1 "Fuzzer2" 43 | tmux send-keys -t "fuzzing" "cd /home/gce_user/fuzzer/; ./watchdog.sh OUTPUT2" C-m 44 | 45 | sleep 1 46 | tmux new-window -t "fuzzing" 47 | tmux rename-window -t 2 "Fuzzer3" 48 | tmux send-keys -t "fuzzing" "cd /home/gce_user/fuzzer/; ./watchdog.sh OUTPUT3" C-m 49 | 50 | sleep 1 51 | tmux new-window -t "fuzzing" 52 | tmux rename-window -t 3 "Fuzzer4" 53 | tmux send-keys -t "fuzzing" "cd /home/gce_user/fuzzer/; ./watchdog.sh OUTPUT4" C-m 54 | 55 | sleep 1 56 | tmux new-window -t "fuzzing" 57 | tmux rename-window -t 4 "Fuzzer5" 58 | tmux send-keys -t "fuzzing" "cd /home/gce_user/fuzzer/; ./watchdog.sh OUTPUT5" C-m 59 | 60 | sleep 1 61 | tmux new-window -t "fuzzing" 62 | tmux rename-window -t 5 "Fuzzer6" 63 | tmux send-keys -t "fuzzing" "cd /home/gce_user/fuzzer/; ./watchdog.sh OUTPUT6" C-m 64 | 65 | sleep 1 66 | tmux new-window -t "fuzzing" 67 | tmux rename-window -t 6 "Fuzzer7" 68 | tmux send-keys -t "fuzzing" "cd /home/gce_user/fuzzer/; ./watchdog.sh OUTPUT7" C-m 69 | 70 | sleep 1 71 | tmux new-window -t "fuzzing" 72 | tmux rename-window -t 7 "Fuzzer8" 73 | tmux send-keys -t "fuzzing" "cd /home/gce_user/fuzzer/; ./watchdog.sh OUTPUT8" C-m 74 | 75 | sleep 1 76 | tmux new-window -t "fuzzing" 77 | tmux rename-window -t 8 "Fuzzer9" 78 | tmux send-keys -t "fuzzing" "cd /home/gce_user/fuzzer/; ./watchdog.sh OUTPUT9" C-m 79 | 80 | sleep 1 81 | tmux new-window -t "fuzzing" 82 | tmux rename-window -t 9 "Fuzzer10" 83 | tmux send-keys -t "fuzzing" "cd /home/gce_user/fuzzer/; ./watchdog.sh OUTPUT10" C-m 84 | 85 | sleep 1 86 | tmux new-window -t "fuzzing" 87 | tmux rename-window -t 10 "Fuzzer11" 88 | tmux send-keys -t "fuzzing" "cd /home/gce_user/fuzzer/; ./watchdog.sh OUTPUT11" C-m 89 | 90 | sleep 1 91 | tmux new-window -t "fuzzing" 92 | tmux rename-window -t 11 "Fuzzer12" 93 | tmux send-keys -t "fuzzing" "cd /home/gce_user/fuzzer/; ./watchdog.sh OUTPUT12" C-m 94 | 95 | sleep 1 96 | tmux new-window -t "fuzzing" 97 | tmux rename-window -t 12 "Fuzzer13" 98 | tmux send-keys -t "fuzzing" "cd /home/gce_user/fuzzer/; ./watchdog.sh OUTPUT13" C-m 99 | 100 | sleep 1 101 | tmux new-window -t "fuzzing" 102 | tmux rename-window -t 13 "Fuzzer14" 103 | tmux send-keys -t "fuzzing" "cd /home/gce_user/fuzzer/; ./watchdog.sh OUTPUT14" C-m 104 | 105 | sleep 1 106 | tmux new-window -t "fuzzing" 107 | tmux rename-window -t 14 "Fuzzer15" 108 | tmux send-keys -t "fuzzing" "cd /home/gce_user/fuzzer/; ./watchdog.sh OUTPUT15" C-m 109 | 110 | sleep 1 111 | tmux new-window -t "fuzzing" 112 | tmux rename-window -t 15 "Fuzzer16" 113 | tmux send-keys -t "fuzzing" "cd /home/gce_user/fuzzer/; ./watchdog.sh OUTPUT16" C-m 114 | 115 | tmux new-window -t "fuzzing" 116 | tmux rename-window -t 4 "Results1" 117 | tmux send-keys -t "fuzzing" "cd /home/gce_user/fuzzer/OUTPUT/current_corpus; pwd" C-m 118 | 119 | 120 | # Go back to the first fuzzer instance 121 | tmux select-window -t 0 122 | -------------------------------------------------------------------------------- /automation_gce/start_gce_instances.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2022 @ReneFreingruber 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | # Note: you can just create 6 machines every 60 minutes (from a snapshot) according to: 19 | # https://cloud.google.com/compute/docs/disks/snapshot-best-practices 20 | # Otherwise you get the error: 21 | # Operation rate exceeded for resource [...]. Too frequent operations from the source resource. 22 | 23 | # But you can create multiple snapshots from your base-fuzzer image and then start from 24 | # multiple snapshots. 25 | # Also note that you need to increase your quota if you want to start multiple fuzzer instances! 26 | # This especially applies to the CPU quota and number of IP-Addresses quota. 27 | 28 | 29 | PROJECT_NAME="your-project-name-goes-here" 30 | 31 | BASE_NAME="fuzzer-europe-west1-" 32 | ZONE=europe-west1-b 33 | 34 | # The name of the snapshot you created from your base fuzzer image 35 | # This base image should have the fuzzer stored on the file system and all required dependencies installed 36 | # It's recommended to first test that the fuzzer can run successfully on this system for several hours 37 | # Be aware that you must create OUTPUT folders for each fuzzing instance in the base image 38 | # for details read the comments in >start_fuzzing_16.sh< 39 | SNAPSHOT_BASEIMAGE_NAME="fuzzer-baseimage-3-08-2021_one" 40 | 41 | SERVICE_ACCOUNT=123456789-compute@developer.gserviceaccount.com 42 | 43 | # Be aware that the disk size also influences the number of free inodes 44 | # The fuzzer stores a lot of small files, it's therefore typically enough to use 45 | # 15 GB of disk space to store the OS, the fuzzer and all corpus files. 46 | # You could then have something like ~5GB of free space, but no inodes and results can't be saved. 47 | # It's therefore important to use a big enough disk space to have enough free inodes! 48 | FUZZER_DISK_SIZE="45" 49 | 50 | MACHINE_TYPE=e2-standard-16 51 | 52 | # Startup and shutdown script paths are local paths from the systems on which you 53 | # execute this script (and not paths from GCE instances) 54 | # The startup script will start a watchdog and several fuzzer instances on the machine 55 | # E.g. on a 16-core system (Machine type e2-standard-16) you should start 16 fuzzing jobs 56 | # The startup script will do this 57 | # The stop script tries to save results to GCE buckets in case the machine gets preempted. 58 | # However, this often doesn't work because it's not guaranteed that the stop script gets executed 59 | STARTUP_SCRIPT_LOCAL_PATH="/home/user/Desktop/fuzzer/automation_gce/start_fuzzing_16.sh" 60 | SHUTDOWN_SCRIPT_LOCAL_PATH="/home/user/Desktop/fuzzer/automation_gce/shutdown_fuzzing_16.sh" 61 | 62 | START_ID=1 63 | NUMBER_INSTANCES_TO_START=50 64 | CURRENT_COUNTER=0 65 | 66 | read -p "Going to start $NUMBER_INSTANCES_TO_START instance(s). Are you sure (y/n)?" -n 1 -r 67 | echo 68 | if [[ $REPLY =~ ^[Yy]$ ]] 69 | then 70 | END_ID=$(( $START_ID + $NUMBER_INSTANCES_TO_START )) 71 | 72 | for (( current_id=$START_ID; current_id<$END_ID; current_id++ )) 73 | do 74 | CURRENT_COUNTER=$(( $CURRENT_COUNTER + 1 )) 75 | if (( $CURRENT_COUNTER > 5 )); then 76 | CURRENT_COUNTER=$((0)) 77 | echo "Going to sleep for 65 minutes..." 78 | sleep 3900 # sleep 65 minutes 79 | echo "After sleep!" 80 | fi 81 | 82 | CURRENT_NAME=$BASE_NAME$current_id 83 | echo "Going to start $CURRENT_NAME..." 84 | 85 | gcloud compute --project $PROJECT_NAME disks create $CURRENT_NAME --size $FUZZER_DISK_SIZE --zone $ZONE --source-snapshot $SNAPSHOT_BASEIMAGE_NAME --type "pd-balanced" 86 | 87 | gcloud beta compute --project=$PROJECT_NAME instances create $CURRENT_NAME --zone=$ZONE --machine-type=$MACHINE_TYPE --subnet=default --network-tier=PREMIUM --no-restart-on-failure --maintenance-policy=TERMINATE --preemptible --service-account=$SERVICE_ACCOUNT --scopes=https://www.googleapis.com/auth/devstorage.read_only,https://www.googleapis.com/auth/logging.write,https://www.googleapis.com/auth/monitoring.write,https://www.googleapis.com/auth/servicecontrol,https://www.googleapis.com/auth/service.management.readonly,https://www.googleapis.com/auth/trace.append --disk=name=$CURRENT_NAME,device-name=$CURRENT_NAME,mode=rw,boot=yes,auto-delete=yes --reservation-affinity=any --metadata-from-file=startup-script=$STARTUP_SCRIPT_LOCAL_PATH,shutdown-script=$SHUTDOWN_SCRIPT_LOCAL_PATH 88 | done 89 | fi -------------------------------------------------------------------------------- /standardizer/implementations/add_newlines.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 @ReneFreingruber 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | 17 | import standardizer.standardizer_helpers as standardizer_helpers 18 | 19 | 20 | # Call this function just as soon as comments are removed 21 | # TODO: The code is very similar to: 22 | # import native_code.speed_optimized_functions as speed_optimized_functions 23 | # speed_optimized_functions.get_index_of_next_symbol_not_within_string() 24 | def add_newlines(code_to_minimize, required_coverage): 25 | tmp = "" 26 | in_str_double_quote = False 27 | in_str_single_quote = False 28 | in_template_str = False 29 | in_forward_slash = False 30 | previous_backward_slash = False 31 | for line in code_to_minimize.split("\n"): 32 | for current_char in line: 33 | if current_char == '"': 34 | if previous_backward_slash: 35 | tmp += current_char 36 | previous_backward_slash = False 37 | continue 38 | tmp += current_char 39 | in_str_double_quote = not in_str_double_quote 40 | previous_backward_slash = False 41 | elif current_char == "'": 42 | if previous_backward_slash: 43 | tmp += current_char 44 | previous_backward_slash = False 45 | continue 46 | tmp += current_char 47 | in_str_single_quote = not in_str_single_quote 48 | previous_backward_slash = False 49 | elif current_char == "`": 50 | if previous_backward_slash: 51 | # `\`` === '`' // --> true 52 | tmp += current_char 53 | previous_backward_slash = False 54 | continue 55 | tmp += current_char 56 | in_template_str = not in_template_str 57 | previous_backward_slash = False 58 | elif current_char == "\\": 59 | previous_backward_slash = not previous_backward_slash 60 | tmp += current_char 61 | elif current_char == "/": 62 | if in_str_double_quote or in_str_single_quote or in_template_str or previous_backward_slash: 63 | pass 64 | else: 65 | in_forward_slash = not in_forward_slash 66 | tmp += current_char 67 | previous_backward_slash = False 68 | elif current_char == "{": 69 | if in_str_double_quote or in_str_single_quote or in_template_str or in_forward_slash: 70 | tmp += current_char 71 | else: 72 | # not in a string, so we can add a newline 73 | tmp += current_char + "\n" 74 | # Important, if the character is a {, I can't add a newline in front of the { 75 | # The reason is code like this: 76 | # return {0.1: a}; 77 | # If a newline would be added, the return would just be executed (this is the only exception of this behavior in JavaScript..) 78 | previous_backward_slash = False 79 | elif current_char == "}": 80 | if in_str_double_quote or in_str_single_quote or in_template_str or in_forward_slash: 81 | tmp += current_char 82 | else: 83 | # not in a string, so we can add a newline 84 | tmp += "\n" + current_char + "\n" 85 | previous_backward_slash = False 86 | else: 87 | tmp += current_char 88 | previous_backward_slash = False 89 | tmp += "\n" 90 | 91 | # Now remove completely empty lines 92 | minimized_code = "" 93 | for line in tmp.split("\n"): 94 | if line.strip() == "": 95 | continue 96 | minimized_code += line + "\n" 97 | 98 | 99 | if minimized_code == code_to_minimize.rstrip(): 100 | # Nothing was modified, so it must not be executed again 101 | return minimized_code 102 | 103 | if standardizer_helpers.does_code_still_trigger_coverage(minimized_code, required_coverage): 104 | # Minimization worked and we still trigger the new coverage 105 | return minimized_code 106 | else: 107 | # Something went wrong and minimization didn't work 108 | return code_to_minimize 109 | --------------------------------------------------------------------------------