def test_get_command_line_for_application(self): """Test get_command_line_for_application.""" self._setup_env(job_type='job') self.assertIsNotNone(build_manager.setup_build()) fuzz_inputs = os.environ['FUZZ_INPUTS'] file_to_run = os.path.join(fuzz_inputs, 'file_to_run') os.environ['APP_ARGS'] = '%TESTCASE% %TESTCASE_FILE_URL%' command_line = tests.get_command_line_for_application(file_to_run) app_path = os.environ['APP_PATH'] worker_fuzz_inputs = file_host.rebase_to_worker_root(fuzz_inputs) worker_file_to_run = os.path.join(worker_fuzz_inputs, 'file_to_run') self.assertEqual( command_line, '%s %s %s' % (app_path, worker_file_to_run, utils.file_path_to_file_url(worker_file_to_run))) launcher_path = '/path/to/launcher' os.environ['LAUNCHER_PATH'] = launcher_path command_line = tests.get_command_line_for_application(file_to_run) self.assertEqual( command_line, '%s %s %s %s' % (launcher_path, app_path, file_to_run, file_to_run))
def get_impact_on_build(build_type, current_version, testcase, testcase_file_path): """Return impact and additional trace on a prod build given build_type.""" build = build_manager.setup_production_build(build_type) if not build: raise BuildFailedException( 'Build setup failed for %s' % build_type.capitalize()) app_path = environment.get_value('APP_PATH') if not app_path: raise AppFailedException() version = build.revision if version == current_version: return Impact(current_version, likely=False) command = tests.get_command_line_for_application( testcase_file_path, app_path=app_path, needs_http=testcase.http_flag) result = tests.test_for_crash_with_retries( testcase, testcase_file_path, environment.get_value('TEST_TIMEOUT'), http_flag=testcase.http_flag) if result.is_crash(): symbolized_crash_stacktrace = result.get_stacktrace(symbolized=True) unsymbolized_crash_stacktrace = result.get_stacktrace(symbolized=False) stacktrace = utils.get_crash_stacktrace_output( command, symbolized_crash_stacktrace, unsymbolized_crash_stacktrace, build_type) return Impact(version, likely=False, extra_trace=stacktrace) return Impact()
def _check_fixed_for_custom_binary(testcase, job_type, testcase_file_path): """Simplified fixed check for test cases using custom binaries.""" revision = environment.get_value('APP_REVISION') # Update comments to reflect bot information and clean up old comments. testcase_id = testcase.key.id() testcase = data_handler.get_testcase_by_id(testcase_id) data_handler.update_testcase_comment(testcase, data_types.TaskState.STARTED) build_manager.setup_build() app_path = environment.get_value('APP_PATH') if not app_path: testcase = data_handler.get_testcase_by_id(testcase_id) data_handler.update_testcase_comment( testcase, data_types.TaskState.ERROR, 'Build setup failed for custom binary') build_fail_wait = environment.get_value('FAIL_WAIT') tasks.add_task( 'progression', testcase_id, job_type, wait_time=build_fail_wait) return testcase = data_handler.get_testcase_by_id(testcase.key.id()) test_timeout = environment.get_value('TEST_TIMEOUT', 10) result = tests.test_for_crash_with_retries( testcase, testcase_file_path, test_timeout, http_flag=testcase.http_flag) _log_output(revision, result) # If this still crashes on the most recent build, it's not fixed. The task # will be rescheduled by a cron job and re-attempted eventually. if result.is_crash(): command = tests.get_command_line_for_application( testcase_file_path, app_path=app_path, needs_http=testcase.http_flag) symbolized_crash_stacktrace = result.get_stacktrace(symbolized=True) unsymbolized_crash_stacktrace = result.get_stacktrace(symbolized=False) stacktrace = utils.get_crash_stacktrace_output( command, symbolized_crash_stacktrace, unsymbolized_crash_stacktrace) testcase.last_tested_crash_stacktrace = data_handler.filter_stacktrace( stacktrace) _update_completion_metadata( testcase, revision, is_crash=True, message='still crashes on latest custom build') return # Retry once on another bot to confirm our results and in case this bot is in # a bad state which we didn't catch through our usual means. if data_handler.is_first_retry_for_task(testcase, reset_after_retry=True): tasks.add_task('progression', testcase_id, job_type) _update_completion_metadata(testcase, revision) return # The bug is fixed. testcase.fixed = 'Yes' testcase.open = False _update_completion_metadata( testcase, revision, message='fixed on latest custom build') _add_issue_comment_with_fixed_range(testcase)
def run(self, file_path=None, gestures=None, arguments=None, timeout=None, log_command=False, use_fresh_profile=False): """Run the test.""" if file_path is None: file_path = self.file_path if gestures is None: gestures = self.gestures if arguments is None: arguments = self.arguments # TODO(mbarbella): Dynamic timeout adjustment. if timeout is None: timeout = self.timeout needs_http = self.testcase.http_flag profile_index = self._get_profile_index() if use_fresh_profile and environment.get_value('USER_PROFILE_ARG'): shell.remove_directory(tests.get_user_profile_directory(profile_index)) # For Android, we need to sync our local testcases directory with the one on # the device. if environment.platform() == 'ANDROID': android.device.push_testcases_to_device() elif environment.is_trusted_host(): from bot.untrusted_runner import file_host file_host.push_testcases_to_worker() # If we need to write a command line file, only do so if the arguments have # changed. arguments_changed = arguments != self._previous_arguments self._previous_arguments = arguments command = tests.get_command_line_for_application( file_to_run=file_path, app_args=arguments, needs_http=needs_http, user_profile_index=profile_index, write_command_line_file=arguments_changed) if log_command: logs.log('Executing command: %s' % command) return_code, crash_time, output = process_handler.run_process( command, timeout=timeout, gestures=gestures) self._release_profile(profile_index) return CrashResult(return_code, crash_time, output)
def _run_libfuzzer_testcase(testcase, testcase_file_path): """Run libFuzzer testcase, and return the CrashResult.""" # Cleanup any existing application instances and temp directories. process_handler.cleanup_stale_processes() shell.clear_temp_directory() if environment.is_trusted_host(): from bot.untrusted_runner import file_host file_host.copy_file_to_worker( testcase_file_path, file_host.rebase_to_worker_root(testcase_file_path)) test_timeout = environment.get_value('TEST_TIMEOUT', process_handler.DEFAULT_TEST_TIMEOUT) repro_command = tests.get_command_line_for_application( file_to_run=testcase_file_path, needs_http=testcase.http_flag) return_code, crash_time, output = process_handler.run_process( repro_command, timeout=test_timeout) return CrashResult(return_code, crash_time, output)
def _setup_application_path(self, build_dir=None, app_path='APP_PATH', build_update=False): """Sets up APP_PATH environment variables for revision build.""" logs.log('Setup application path.') if not build_dir: build_dir = self.build_dir # Make sure to initialize so that we don't carry stale values # in case of errors. app_path can be APP_PATH or APP_PATH_DEBUG. environment.set_value(app_path, '') environment.set_value('APP_DIR', '') environment.set_value('BUILD_DIR', build_dir) environment.set_value('GN_ARGS_PATH', '') environment.set_value('LLVM_SYMBOLIZER_PATH', environment.get_default_tool_path('llvm-symbolizer')) # Initialize variables. fuzzer_directory = environment.get_value('FUZZER_DIR') search_directories = [build_dir] if fuzzer_directory: search_directories.append(fuzzer_directory) set_environment_vars(search_directories, app_path=app_path) absolute_file_path = environment.get_value(app_path) app_directory = environment.get_value('APP_DIR') if not absolute_file_path: return # Set the symlink if needed. symbolic_link_target = environment.get_value('SYMBOLIC_LINK') if symbolic_link_target: os.system('mkdir --parents %s' % os.path.dirname(symbolic_link_target)) os.system('rm %s' % symbolic_link_target) os.system('ln -s %s %s' % (app_directory, symbolic_link_target)) # Android specific initialization. if environment.platform() == 'ANDROID': # Prepare device for app install. android.device.initialize_device() # On Android, we may need to write a command line file. We do this in # advance so that we do not have to write this to the device multiple # times. # TODO(mbarbella): Build code should not depend on fuzzing. from fuzzing import tests tests.get_command_line_for_application(write_command_line_file=True) # Install the app if it does not exist. android.device.install_application_if_needed(absolute_file_path, build_update) return if not build_update: return # The following hacks are only applicable in Chromium. if utils.is_chromium(): return # Chromium specific workaround for missing ICU data file in root directory. # Copy it from relative folders. See crbug.com/741603. root_icu_data_file_path = os.path.join(app_directory, ICU_DATA_FILENAME) find_icu_data_file_path = utils.find_binary_path(app_directory, ICU_DATA_FILENAME) if find_icu_data_file_path and not os.path.exists(root_icu_data_file_path): shell.copy_file(find_icu_data_file_path, root_icu_data_file_path)
def execute_task(testcase_id, job_type): """Run analyze task.""" # Reset redzones. environment.reset_current_memory_tool_options(redzone_size=128) # Unset window location size and position properties so as to use default. environment.set_value('WINDOW_ARG', '') # Locate the testcase associated with the id. testcase = data_handler.get_testcase_by_id(testcase_id) if not testcase: return data_handler.update_testcase_comment(testcase, data_types.TaskState.STARTED) metadata = data_types.TestcaseUploadMetadata.query( data_types.TestcaseUploadMetadata.testcase_id == int( testcase_id)).get() if not metadata: logs.log_error('Testcase %s has no associated upload metadata.' % testcase_id) testcase.key.delete() return is_lsan_enabled = environment.get_value('LSAN') if is_lsan_enabled: # Creates empty local blacklist so all leaks will be visible to uploader. leak_blacklist.create_empty_local_blacklist() # Store the bot name and timestamp in upload metadata. bot_name = environment.get_value('BOT_NAME') metadata.bot_name = bot_name metadata.timestamp = datetime.datetime.utcnow() metadata.put() # Adjust the test timeout, if user has provided one. if metadata.timeout: environment.set_value('TEST_TIMEOUT', metadata.timeout) # Adjust the number of retries, if user has provided one. if metadata.retries is not None: environment.set_value('CRASH_RETRIES', metadata.retries) # Setup testcase and get absolute testcase path. file_list, _, testcase_file_path = setup.setup_testcase(testcase) if not file_list: return # Set up a custom or regular build based on revision. build_manager.setup_build(testcase.crash_revision) # Check if we have an application path. If not, our build failed # to setup correctly. app_path = environment.get_value('APP_PATH') if not app_path: data_handler.update_testcase_comment(testcase, data_types.TaskState.ERROR, 'Build setup failed') if data_handler.is_first_retry_for_task(testcase): build_fail_wait = environment.get_value('FAIL_WAIT') tasks.add_task('analyze', testcase_id, job_type, wait_time=build_fail_wait) else: close_invalid_testcase_and_update_status(testcase, metadata, 'Build setup failed') return # Update initial testcase information. testcase.absolute_path = testcase_file_path testcase.job_type = job_type testcase.binary_flag = utils.is_binary_file(testcase_file_path) testcase.queue = tasks.default_queue() testcase.crash_state = '' # Set initial testcase metadata fields (e.g. build url, etc). data_handler.set_initial_testcase_metadata(testcase) # Update minimized arguments and use ones provided during user upload. if not testcase.minimized_arguments: minimized_arguments = environment.get_value('APP_ARGS') or '' additional_command_line_flags = testcase.get_metadata( 'uploaded_additional_args') if additional_command_line_flags: minimized_arguments += ' %s' % additional_command_line_flags environment.set_value('APP_ARGS', minimized_arguments) testcase.minimized_arguments = minimized_arguments # Update other fields not set at upload time. testcase.crash_revision = environment.get_value('APP_REVISION') data_handler.set_initial_testcase_metadata(testcase) testcase.put() # Initialize some variables. gestures = testcase.gestures http_flag = testcase.http_flag test_timeout = environment.get_value('TEST_TIMEOUT') # Get the crash output. result = tests.test_for_crash_with_retries(testcase, testcase_file_path, test_timeout, http_flag=http_flag, compare_crash=False) # If we don't get a crash, try enabling http to see if we can get a crash. # Skip engine fuzzer jobs (e.g. libFuzzer, AFL) for which http testcase paths # are not applicable. if (not result.is_crash() and not http_flag and not environment.is_engine_fuzzer_job()): result_with_http = tests.test_for_crash_with_retries( testcase, testcase_file_path, test_timeout, http_flag=True, compare_crash=False) if result_with_http.is_crash(): logs.log('Testcase needs http flag for crash.') http_flag = True result = result_with_http # Refresh our object. testcase = data_handler.get_testcase_by_id(testcase_id) if not testcase: return # Set application command line with the correct http flag. application_command_line = (tests.get_command_line_for_application( testcase_file_path, needs_http=http_flag)) # Get the crash data. crashed = result.is_crash() crash_time = result.get_crash_time() state = result.get_symbolized_data() unsymbolized_crash_stacktrace = result.get_stacktrace(symbolized=False) # Get crash info object with minidump info. Also, re-generate unsymbolized # stacktrace if needed. crash_info, _ = (crash_uploader.get_crash_info_and_stacktrace( application_command_line, state.crash_stacktrace, gestures)) if crash_info: testcase.minidump_keys = crash_info.store_minidump() if not crashed: # Could not reproduce the crash. log_message = ('Testcase didn\'t crash in %d seconds (with retries)' % test_timeout) data_handler.update_testcase_comment(testcase, data_types.TaskState.FINISHED, log_message) # For an unreproducible testcase, retry once on another bot to confirm # our results and in case this bot is in a bad state which we didn't catch # through our usual means. if data_handler.is_first_retry_for_task(testcase): testcase.status = 'Unreproducible, retrying' testcase.put() tasks.add_task('analyze', testcase_id, job_type) return # In the general case, we will not attempt to symbolize if we do not detect # a crash. For user uploads, we should symbolize anyway to provide more # information about what might be happening. crash_stacktrace_output = utils.get_crash_stacktrace_output( application_command_line, state.crash_stacktrace, unsymbolized_crash_stacktrace) testcase.crash_stacktrace = data_handler.filter_stacktrace( crash_stacktrace_output) close_invalid_testcase_and_update_status(testcase, metadata, 'Unreproducible') # A non-reproducing testcase might still impact production branches. # Add the impact task to get that information. task_creation.create_impact_task_if_needed(testcase) return # Update http flag and re-run testcase to store dependencies (for bundled # archives only). testcase.http_flag = http_flag if not store_testcase_dependencies_from_bundled_testcase_archive( metadata, testcase, testcase_file_path): return # Update testcase crash parameters. testcase.crash_type = state.crash_type testcase.crash_address = state.crash_address testcase.crash_state = state.crash_state # Try to guess if the bug is security or not. security_flag = crash_analyzer.is_security_issue(state.crash_stacktrace, state.crash_type, state.crash_address) testcase.security_flag = security_flag # If it is, guess the severity. if security_flag: testcase.security_severity = severity_analyzer.get_security_severity( state.crash_type, state.crash_stacktrace, job_type, bool(gestures)) log_message = ('Testcase crashed in %d seconds (r%d)' % (crash_time, testcase.crash_revision)) data_handler.update_testcase_comment(testcase, data_types.TaskState.FINISHED, log_message) # See if we have to ignore this crash. if crash_analyzer.ignore_stacktrace(state.crash_state, state.crash_stacktrace): close_invalid_testcase_and_update_status(testcase, metadata, 'Irrelavant') return # Test for reproducibility. one_time_crasher_flag = not tests.test_for_reproducibility( testcase_file_path, state.crash_state, security_flag, test_timeout, http_flag, gestures) testcase.one_time_crasher_flag = one_time_crasher_flag # Check to see if this is a duplicate. project_name = data_handler.get_project_name(job_type) existing_testcase = data_handler.find_testcase(project_name, state.crash_type, state.crash_state, security_flag) if existing_testcase: # If the existing test case is unreproducible and we are, replace the # existing test case with this one. if existing_testcase.one_time_crasher_flag and not one_time_crasher_flag: duplicate_testcase = existing_testcase original_testcase = testcase else: duplicate_testcase = testcase original_testcase = existing_testcase metadata.status = 'Duplicate' metadata.duplicate_of = existing_testcase.key.id() duplicate_testcase.status = 'Duplicate' duplicate_testcase.duplicate_of = original_testcase.key.id() duplicate_testcase.put() # Set testcase and metadata status if not set already. if testcase.status != 'Duplicate': testcase.status = 'Processed' metadata.status = 'Confirmed' # Add new leaks to global blacklist to avoid detecting duplicates. # Only add if testcase has a direct leak crash and if it's reproducible. if is_lsan_enabled: leak_blacklist.add_crash_to_global_blacklist_if_needed(testcase) # Add application specific information in the trace. crash_stacktrace_output = utils.get_crash_stacktrace_output( application_command_line, state.crash_stacktrace, unsymbolized_crash_stacktrace) testcase.crash_stacktrace = data_handler.filter_stacktrace( crash_stacktrace_output) # Update the testcase values. testcase.put() # Update the upload metadata. metadata.security_flag = security_flag metadata.put() # Create tasks to # 1. Minimize testcase (minimize). # 2. Find regression range (regression). # 3. Find testcase impact on production branches (impact). # 4. Check whether testcase is fixed (progression). # 5. Get second stacktrace from another job in case of # one-time crashers (stack). task_creation.create_tasks(testcase)
def execute_task(testcase_id, job_type): """Execute a symbolize command.""" # Locate the testcase associated with the id. testcase = data_handler.get_testcase_by_id(testcase_id) # We should atleast have a symbolized debug or release build. if not build_manager.has_symbolized_builds(): return data_handler.update_testcase_comment(testcase, data_types.TaskState.STARTED) # Setup testcase and its dependencies. file_list, _, testcase_file_path = setup.setup_testcase(testcase) if not file_list: return # Initialize variables. build_fail_wait = environment.get_value('FAIL_WAIT') old_crash_stacktrace = data_handler.get_stacktrace(testcase) sym_crash_type = testcase.crash_type sym_crash_address = testcase.crash_address sym_crash_state = testcase.crash_state sym_redzone = DEFAULT_REDZONE warmup_timeout = environment.get_value('WARMUP_TIMEOUT') # Decide which build revision to use. if testcase.crash_stacktrace == 'Pending': # This usually happen when someone clicked the 'Update stacktrace from # trunk' button on the testcase details page. In this case, we are forced # to use trunk. No revision -> trunk build. build_revision = None else: build_revision = testcase.crash_revision # Set up a custom or regular build based on revision. build_manager.setup_build(build_revision) # Get crash revision used in setting up build. crash_revision = environment.get_value('APP_REVISION') if not environment.get_value('APP_PATH'): testcase = data_handler.get_testcase_by_id(testcase_id) data_handler.update_testcase_comment(testcase, data_types.TaskState.ERROR, 'Build setup failed') tasks.add_task('symbolize', testcase_id, job_type, wait_time=build_fail_wait) return # ASAN tool settings (if the tool is used). # See if we can get better stacks with higher redzone sizes. # A UAF might actually turn out to be OOB read/write with a bigger redzone. if environment.tool_matches('ASAN', job_type) and testcase.security_flag: redzone = MAX_REDZONE while redzone >= MIN_REDZONE: environment.reset_current_memory_tool_options(testcase.redzone) process_handler.terminate_stale_application_instances() command = tests.get_command_line_for_application( testcase_file_path, needs_http=testcase.http_flag) return_code, crash_time, output = (process_handler.run_process( command, timeout=warmup_timeout, gestures=testcase.gestures)) crash_result = CrashResult(return_code, crash_time, output) if crash_result.is_crash() and 'AddressSanitizer' in output: state = crash_result.get_symbolized_data() security_flag = crash_result.is_security_issue() if (not crash_analyzer.ignore_stacktrace( state.crash_stacktrace) and security_flag == testcase.security_flag and state.crash_type == testcase.crash_type and (state.crash_type != sym_crash_type or state.crash_state != sym_crash_state)): logs.log( 'Changing crash parameters.\nOld : %s, %s, %s' % (sym_crash_type, sym_crash_address, sym_crash_state)) sym_crash_type = state.crash_type sym_crash_address = state.crash_address sym_crash_state = state.crash_state sym_redzone = redzone old_crash_stacktrace = state.crash_stacktrace logs.log( '\nNew : %s, %s, %s' % (sym_crash_type, sym_crash_address, sym_crash_state)) break redzone /= 2 # We should have atleast a symbolized debug or a release build. symbolized_builds = build_manager.setup_symbolized_builds(crash_revision) if (not symbolized_builds or (not environment.get_value('APP_PATH') and not environment.get_value('APP_PATH_DEBUG'))): testcase = data_handler.get_testcase_by_id(testcase_id) data_handler.update_testcase_comment(testcase, data_types.TaskState.ERROR, 'Build setup failed') tasks.add_task('symbolize', testcase_id, job_type, wait_time=build_fail_wait) return # Increase malloc_context_size to get all stack frames. Default is 30. environment.reset_current_memory_tool_options(sym_redzone, STACK_FRAME_COUNT, symbolize_inline_frames=True) # TSAN tool settings (if the tool is used). if environment.tool_matches('TSAN', job_type): environment.set_tsan_max_history_size() # Do the symbolization if supported by this application. result, sym_crash_stacktrace = (get_symbolized_stacktraces( testcase_file_path, testcase, old_crash_stacktrace, sym_crash_state)) # Update crash parameters. testcase = data_handler.get_testcase_by_id(testcase_id) testcase.crash_type = sym_crash_type testcase.crash_address = sym_crash_address testcase.crash_state = sym_crash_state testcase.crash_stacktrace = ( data_handler.filter_stacktrace(sym_crash_stacktrace)) if not result: data_handler.update_testcase_comment( testcase, data_types.TaskState.ERROR, 'Unable to reproduce crash, skipping ' 'stacktrace update.') else: # Switch build url to use the less-optimized symbolized build with better # stacktrace. build_url = environment.get_value('BUILD_URL') if build_url: testcase.set_metadata('build_url', build_url, update_testcase=False) data_handler.update_testcase_comment(testcase, data_types.TaskState.FINISHED) testcase.symbolized = True testcase.crash_revision = crash_revision testcase.put() # We might have updated the crash state. See if we need to marked as duplicate # based on other testcases. data_handler.handle_duplicate_entry(testcase) task_creation.create_blame_task_if_needed(testcase) # Switch current directory before builds cleanup. root_directory = environment.get_value('ROOT_DIR') os.chdir(root_directory) # Cleanup symbolized builds which are space-heavy. symbolized_builds.delete()
def get_symbolized_stacktraces(testcase_file_path, testcase, old_crash_stacktrace, expected_state): """Use the symbolized builds to generate an updated stacktrace.""" # Initialize variables. app_path = environment.get_value('APP_PATH') app_path_debug = environment.get_value('APP_PATH_DEBUG') long_test_timeout = environment.get_value('WARMUP_TIMEOUT') retry_limit = environment.get_value('FAIL_RETRIES') symbolized = False debug_build_stacktrace = '' release_build_stacktrace = old_crash_stacktrace # Symbolize using the debug build first so that the debug build stacktrace # comes after the more important release build stacktrace. if app_path_debug: for _ in xrange(retry_limit): process_handler.terminate_stale_application_instances() command = tests.get_command_line_for_application( testcase_file_path, app_path=app_path_debug, needs_http=testcase.http_flag) return_code, crash_time, output = (process_handler.run_process( command, timeout=long_test_timeout, gestures=testcase.gestures)) crash_result = CrashResult(return_code, crash_time, output) if crash_result.is_crash(): state = crash_result.get_symbolized_data() if crash_analyzer.ignore_stacktrace(state.crash_stacktrace): continue unsymbolized_crash_stacktrace = crash_result.get_stacktrace( symbolized=False) debug_build_stacktrace = utils.get_crash_stacktrace_output( command, state.crash_stacktrace, unsymbolized_crash_stacktrace, build_type='debug') symbolized = True break # Symbolize using the release build. if app_path: for _ in xrange(retry_limit): process_handler.terminate_stale_application_instances() command = tests.get_command_line_for_application( testcase_file_path, app_path=app_path, needs_http=testcase.http_flag) return_code, crash_time, output = (process_handler.run_process( command, timeout=long_test_timeout, gestures=testcase.gestures)) crash_result = CrashResult(return_code, crash_time, output) if crash_result.is_crash(): state = crash_result.get_symbolized_data() if crash_analyzer.ignore_stacktrace(state.crash_stacktrace): continue if state.crash_state != expected_state: continue # Release stack's security flag has to match the symbolized release # stack's security flag. security_flag = crash_result.is_security_issue() if security_flag != testcase.security_flag: continue unsymbolized_crash_stacktrace = crash_result.get_stacktrace( symbolized=False) release_build_stacktrace = utils.get_crash_stacktrace_output( command, state.crash_stacktrace, unsymbolized_crash_stacktrace, build_type='release') symbolized = True break stacktrace = release_build_stacktrace if debug_build_stacktrace: stacktrace += '\n\n' + debug_build_stacktrace return symbolized, stacktrace
def find_fixed_range(testcase_id, job_type): """Attempt to find the revision range where a testcase was fixed.""" deadline = tasks.get_task_completion_deadline() testcase = data_handler.get_testcase_by_id(testcase_id) if not testcase: return if testcase.fixed: logs.log_error('Fixed range is already set as %s, skip.' % testcase.fixed) return # Setup testcase and its dependencies. file_list, _, testcase_file_path = setup.setup_testcase(testcase) if not file_list: return # Custom binaries are handled as special cases. if build_manager.is_custom_binary(): _check_fixed_for_custom_binary(testcase, job_type, testcase_file_path) return release_build_bucket_path = environment.get_value( 'RELEASE_BUILD_BUCKET_PATH') revision_list = build_manager.get_revisions_list(release_build_bucket_path, testcase=testcase) if not revision_list: testcase = data_handler.get_testcase_by_id(testcase_id) data_handler.update_testcase_comment(testcase, data_types.TaskState.ERROR, 'Failed to fetch revision list') tasks.add_task('progression', testcase_id, job_type) return # Use min, max_index to mark the start and end of revision list that is used # for bisecting the progression range. Set start to the revision where noticed # the crash. Set end to the trunk revision. Also, use min, max from past run # if it timed out. min_revision = testcase.get_metadata('last_progression_min') max_revision = testcase.get_metadata('last_progression_max') last_tested_revision = testcase.get_metadata('last_tested_crash_revision') known_crash_revision = last_tested_revision or testcase.crash_revision if not min_revision: min_revision = known_crash_revision if not max_revision: max_revision = revisions.get_last_revision_in_list(revision_list) min_index = revisions.find_min_revision_index(revision_list, min_revision) if min_index is None: raise errors.BuildNotFoundError(min_revision, job_type) max_index = revisions.find_max_revision_index(revision_list, max_revision) if max_index is None: raise errors.BuildNotFoundError(max_revision, job_type) data_handler.update_testcase_comment(testcase, data_types.TaskState.STARTED, 'r%d' % max_revision) # If there have been no updates since the build the crash was discovered in, # and this is not a redo testcase, then there is no work to do. is_redo_testcase = bool(testcase.get_metadata('progression_pending')) if not is_redo_testcase and testcase.crash_revision == max_revision: _update_completion_metadata(testcase, max_revision) return # Set progression status to true. testcase.set_metadata('progression_pending', True) # Check to see if this testcase is still crashing now. If it is, then just # bail out. result = _testcase_reproduces_in_revision(testcase, testcase_file_path, job_type, max_revision) if result.is_crash(): logs.log('Found crash with same signature on latest revision r%d.' % max_revision) app_path = environment.get_value('APP_PATH') command = tests.get_command_line_for_application( testcase_file_path, app_path=app_path, needs_http=testcase.http_flag) symbolized_crash_stacktrace = result.get_stacktrace(symbolized=True) unsymbolized_crash_stacktrace = result.get_stacktrace(symbolized=False) stacktrace = utils.get_crash_stacktrace_output( command, symbolized_crash_stacktrace, unsymbolized_crash_stacktrace) testcase.last_tested_crash_stacktrace = data_handler.filter_stacktrace( stacktrace) _update_completion_metadata(testcase, max_revision, is_crash=True) # Since we've verified that the test case is still crashing, clear out any # metadata indicating potential flake from previous runs. task_creation.mark_unreproducible_if_flaky(testcase, False) # For chromium project, save latest crash information for later upload # to chromecrash/. state = result.get_symbolized_data() crash_uploader.save_crash_info_if_needed(testcase_id, max_revision, job_type, state.crash_type, state.crash_address, state.frames) return # Don't burden NFS server with caching these random builds. environment.set_value('CACHE_STORE', False) # Verify that we do crash in the min revision. This is assumed to be true # while we are doing the bisect. result = _testcase_reproduces_in_revision(testcase, testcase_file_path, job_type, min_revision) if result and not result.is_crash(): # Retry once on another bot to confirm our result. if data_handler.is_first_retry_for_task(testcase, reset_after_retry=True): tasks.add_task('progression', testcase_id, job_type) error_message = ( 'Known crash revision %d did not crash, will retry on another bot to ' 'confirm result' % known_crash_revision) data_handler.update_testcase_comment(testcase, data_types.TaskState.ERROR, error_message) _update_completion_metadata(testcase, max_revision) return testcase = data_handler.get_testcase_by_id(testcase_id) _clear_progression_pending(testcase) error_message = ('Known crash revision %d did not crash' % known_crash_revision) data_handler.update_testcase_comment(testcase, data_types.TaskState.ERROR, error_message) task_creation.mark_unreproducible_if_flaky(testcase, True) return # Start a binary search to find last non-crashing revision. At this point, we # know that we do crash in the min_revision, and do not crash in max_revision. while time.time() < deadline: min_revision = revision_list[min_index] max_revision = revision_list[max_index] # If the min and max revisions are one apart this is as much as we can # narrow the range. if max_index - min_index == 1: _save_fixed_range(testcase_id, min_revision, max_revision) return # Test the middle revision of our range. middle_index = (min_index + max_index) / 2 middle_revision = revision_list[middle_index] testcase = data_handler.get_testcase_by_id(testcase_id) log_message = 'Testing r%d (current range %d:%d)' % ( middle_revision, min_revision, max_revision) data_handler.update_testcase_comment(testcase, data_types.TaskState.WIP, log_message) try: result = _testcase_reproduces_in_revision(testcase, testcase_file_path, job_type, middle_revision) except errors.BadBuildError: # Skip this revision. del revision_list[middle_index] max_index -= 1 continue if result.is_crash(): min_index = middle_index else: max_index = middle_index _save_current_fixed_range_indices(testcase_id, revision_list[min_index], revision_list[max_index]) # If we've broken out of the loop, we've exceeded the deadline. Recreate the # task to pick up where we left off. testcase = data_handler.get_testcase_by_id(testcase_id) error_message = ('Timed out, current range r%d:r%d' % (revision_list[min_index], revision_list[max_index])) data_handler.update_testcase_comment(testcase, data_types.TaskState.ERROR, error_message) tasks.add_task('progression', testcase_id, job_type)
def execute_task(testcase_id, job_type): """Attempt to minimize a given testcase.""" # Get deadline to finish this task. deadline = tasks.get_task_completion_deadline() # Locate the testcase associated with the id. testcase = data_handler.get_testcase_by_id(testcase_id) if not testcase: return # Update comments to reflect bot information. data_handler.update_testcase_comment(testcase, data_types.TaskState.STARTED) # Setup testcase and its dependencies. file_list, input_directory, testcase_file_path = setup.setup_testcase( testcase) if not file_list: return # Initialize variables. max_timeout = environment.get_value('TEST_TIMEOUT', 10) app_arguments = environment.get_value('APP_ARGS') # Set up a custom or regular build based on revision. last_tested_crash_revision = testcase.get_metadata( 'last_tested_crash_revision') crash_revision = last_tested_crash_revision or testcase.crash_revision build_manager.setup_build(crash_revision) # Check if we have an application path. If not, our build failed # to setup correctly. app_path = environment.get_value('APP_PATH') if not app_path: logs.log_error('Unable to setup build for minimization.') build_fail_wait = environment.get_value('FAIL_WAIT') if environment.get_value('ORIGINAL_JOB_NAME'): _skip_minimization(testcase, 'Failed to setup build for overridden job.') else: # Only recreate task if this isn't an overriden job. It's possible that a # revision exists for the original job, but doesn't exist for the # overriden job. tasks.add_task( 'minimize', testcase_id, job_type, wait_time=build_fail_wait) return if environment.is_libfuzzer_job(): do_libfuzzer_minimization(testcase, testcase_file_path) return max_threads = utils.maximum_parallel_processes_allowed() # Prepare the test case runner. crash_retries = environment.get_value('CRASH_RETRIES') warmup_timeout = environment.get_value('WARMUP_TIMEOUT') required_arguments = environment.get_value('REQUIRED_APP_ARGS', '') # Add any testcase-specific required arguments if needed. additional_required_arguments = testcase.get_metadata( 'additional_required_app_args') if additional_required_arguments: required_arguments = '%s %s' % (required_arguments, additional_required_arguments) test_runner = TestRunner(testcase, testcase_file_path, file_list, input_directory, app_arguments, required_arguments, max_threads, deadline) # Verify the crash with a long timeout. warmup_crash_occurred = False result = test_runner.run(timeout=warmup_timeout, log_command=True) if result.is_crash(): warmup_crash_occurred = True logs.log('Warmup crash occurred in %d seconds.' % result.crash_time) saved_unsymbolized_crash_state, flaky_stack, crash_times = ( check_for_initial_crash(test_runner, crash_retries, testcase)) # If the warmup crash occurred but we couldn't reproduce this in with # multiple processes running in parallel, try to minimize single threaded. if (len(crash_times) < tests.REPRODUCIBILITY_FACTOR * crash_retries and warmup_crash_occurred and max_threads > 1): logs.log('Attempting to continue single-threaded.') max_threads = 1 test_runner = TestRunner(testcase, testcase_file_path, file_list, input_directory, app_arguments, required_arguments, max_threads, deadline) saved_unsymbolized_crash_state, flaky_stack, crash_times = ( check_for_initial_crash(test_runner, crash_retries, testcase)) if flaky_stack: testcase.flaky_stack = flaky_stack testcase.put() if len(crash_times) < tests.REPRODUCIBILITY_FACTOR * crash_retries: if not crash_times: # We didn't crash at all, so try again. This might be a legitimately # unreproducible test case, so it will get marked as such after being # retried on other bots. testcase = data_handler.get_testcase_by_id(testcase_id) data_handler.update_testcase_comment(testcase, data_types.TaskState.ERROR, 'Unable to reproduce crash') task_creation.mark_unreproducible_if_flaky(testcase, True) else: # We reproduced this crash at least once. It's too flaky to minimize, but # maybe we'll have more luck in the other jobs. testcase = data_handler.get_testcase_by_id(testcase_id) testcase.minimized_keys = 'NA' error_message = ( 'Unable to reproduce crash reliably, skipping ' 'minimization (crashed %d/%d)' % (len(crash_times), crash_retries)) data_handler.update_testcase_comment(testcase, data_types.TaskState.ERROR, error_message) create_additional_tasks(testcase) return # If we've made it this far, the test case appears to be reproducible. Clear # metadata from previous runs had it been marked as potentially flaky. task_creation.mark_unreproducible_if_flaky(testcase, False) test_runner.set_test_expectations(testcase.security_flag, flaky_stack, saved_unsymbolized_crash_state) # Use the max crash time unless this would be greater than the max timeout. test_timeout = min(max(crash_times), max_timeout) + 1 logs.log('Using timeout %d (was %d)' % (test_timeout, max_timeout)) test_runner.timeout = test_timeout logs.log('Starting minimization.') if should_attempt_phase(testcase, MinimizationPhase.GESTURES): gestures = minimize_gestures(test_runner, testcase) # We can't call check_deadline_exceeded_and_store_partial_minimized_testcase # at this point because we do not have a test case to store. testcase = data_handler.get_testcase_by_id(testcase.key.id()) if testcase.security_flag and len(testcase.gestures) != len(gestures): # Re-run security severity analysis since gestures affect the severity. testcase.security_severity = severity_analyzer.get_security_severity( testcase.crash_type, data_handler.get_stacktrace(testcase), job_type, bool(gestures)) testcase.gestures = gestures testcase.set_metadata('minimization_phase', MinimizationPhase.MAIN_FILE) if time.time() > test_runner.deadline: tasks.add_task('minimize', testcase.key.id(), job_type) return # Minimize the main file. data = utils.get_file_contents_with_fatal_error_on_failure(testcase_file_path) if should_attempt_phase(testcase, MinimizationPhase.MAIN_FILE): data = minimize_main_file(test_runner, testcase_file_path, data) if check_deadline_exceeded_and_store_partial_minimized_testcase( deadline, testcase_id, job_type, input_directory, file_list, data, testcase_file_path): return testcase.set_metadata('minimization_phase', MinimizationPhase.FILE_LIST) # Minimize the file list. if should_attempt_phase(testcase, MinimizationPhase.FILE_LIST): if environment.get_value('MINIMIZE_FILE_LIST', True): file_list = minimize_file_list(test_runner, file_list, input_directory, testcase_file_path) if check_deadline_exceeded_and_store_partial_minimized_testcase( deadline, testcase_id, job_type, input_directory, file_list, data, testcase_file_path): return else: logs.log('Skipping minimization of file list.') testcase.set_metadata('minimization_phase', MinimizationPhase.RESOURCES) # Minimize any files remaining in the file list. if should_attempt_phase(testcase, MinimizationPhase.RESOURCES): if environment.get_value('MINIMIZE_RESOURCES', True): for dependency in file_list: minimize_resource(test_runner, dependency, input_directory, testcase_file_path) if check_deadline_exceeded_and_store_partial_minimized_testcase( deadline, testcase_id, job_type, input_directory, file_list, data, testcase_file_path): return else: logs.log('Skipping minimization of resources.') testcase.set_metadata('minimization_phase', MinimizationPhase.ARGUMENTS) if should_attempt_phase(testcase, MinimizationPhase.ARGUMENTS): app_arguments = minimize_arguments(test_runner, app_arguments) # Arguments must be stored here in case we time out below. testcase.minimized_arguments = app_arguments testcase.put() if check_deadline_exceeded_and_store_partial_minimized_testcase( deadline, testcase_id, job_type, input_directory, file_list, data, testcase_file_path): return command = tests.get_command_line_for_application( testcase_file_path, app_args=app_arguments, needs_http=testcase.http_flag) last_crash_result = test_runner.last_failing_result store_minimized_testcase(testcase, input_directory, file_list, data, testcase_file_path) finalize_testcase( testcase_id, command, last_crash_result, flaky_stack=flaky_stack)
def do_libfuzzer_minimization(testcase, testcase_file_path): """Use libFuzzer's built-in minimizer where appropriate.""" is_overriden_job = bool(environment.get_value('ORIGINAL_JOB_NAME')) def handle_unreproducible(): # Be more lenient with marking testcases as unreproducible when this is a # job override. if is_overriden_job: _skip_minimization(testcase, 'Unreproducible on overridden job.') else: task_creation.mark_unreproducible_if_flaky(testcase, True) timeout = environment.get_value('LIBFUZZER_MINIMIZATION_TIMEOUT', 180) rounds = environment.get_value('LIBFUZZER_MINIMIZATION_ROUNDS', 10) current_testcase_path = testcase_file_path last_crash_result = None # Get initial crash state. initial_crash_result = _run_libfuzzer_testcase(testcase, testcase_file_path) if not initial_crash_result.is_crash(): logs.log_warn('Did not crash. Output:\n' + initial_crash_result.get_stacktrace(symbolized=True)) handle_unreproducible() return if testcase.security_flag != initial_crash_result.is_security_issue(): logs.log_warn('Security flag does not match.') handle_unreproducible() return task_creation.mark_unreproducible_if_flaky(testcase, False) expected_state = initial_crash_result.get_symbolized_data() logs.log('Initial crash state: %s\n' % expected_state.crash_state) # We attempt minimization multiple times in case one round results in an # incorrect state, or runs into another issue such as a slow unit. for round_number in range(1, rounds + 1): logs.log('Minimizing round %d.' % round_number) output_file_path, crash_result = _run_libfuzzer_tool( 'minimize', testcase, current_testcase_path, timeout, expected_state.crash_state, set_dedup_flags=True) if output_file_path: last_crash_result = crash_result current_testcase_path = output_file_path if not last_crash_result: repro_command = tests.get_command_line_for_application( file_to_run=testcase_file_path, needs_http=testcase.http_flag) _skip_minimization( testcase, 'LibFuzzer minimization failed.', crash_result=initial_crash_result, command=repro_command) return logs.log('LibFuzzer minimization succeeded.') if utils.is_oss_fuzz(): # Scrub the testcase of non-essential data. cleansed_testcase_path = do_libfuzzer_cleanse( testcase, current_testcase_path, expected_state.crash_state) if cleansed_testcase_path: current_testcase_path = cleansed_testcase_path # Finalize the test case if we were able to reproduce it. repro_command = tests.get_command_line_for_application( file_to_run=current_testcase_path, needs_http=testcase.http_flag) finalize_testcase(testcase.key.id(), repro_command, last_crash_result) # Clean up after we're done. shell.clear_testcase_directories()
def _run_libfuzzer_tool(tool_name, testcase, testcase_file_path, timeout, expected_crash_state, set_dedup_flags=False): """Run libFuzzer tool to either minimize or cleanse.""" memory_tool_options_var = environment.get_current_memory_tool_var() saved_memory_tool_options = environment.get_value(memory_tool_options_var) def _set_dedup_flags(): """Allow libFuzzer to do its own crash comparison during minimization.""" memory_tool_options = environment.get_memory_tool_options( memory_tool_options_var) memory_tool_options['symbolize'] = 1 memory_tool_options['dedup_token_length'] = 3 environment.set_memory_tool_options(memory_tool_options_var, memory_tool_options) def _unset_dedup_flags(): """Reset memory tool options.""" # This is needed so that when we re-run, we can symbolize ourselves # (ignoring inline frames). environment.set_value(memory_tool_options_var, saved_memory_tool_options) output_file_path = get_temporary_file_name(testcase_file_path) rebased_output_file_path = output_file_path if environment.is_trusted_host(): from bot.untrusted_runner import file_host file_host.copy_file_to_worker( testcase_file_path, file_host.rebase_to_worker_root(testcase_file_path)) rebased_output_file_path = file_host.rebase_to_worker_root(output_file_path) arguments = environment.get_value('APP_ARGS', '') arguments += (' --cf-{tool_name}-timeout={timeout} ' '--cf-{tool_name}-to={output_file_path}').format( tool_name=tool_name, output_file_path=rebased_output_file_path, timeout=timeout) command = tests.get_command_line_for_application( file_to_run=testcase_file_path, app_args=arguments, needs_http=testcase.http_flag) logs.log('Executing command: %s' % command) if set_dedup_flags: _set_dedup_flags() # A small buffer is added to the timeout to allow the current test to # finish, and file to be written. Since we should terminate beforehand, a # long delay only slows fuzzing in cases where it's necessary. _, _, output = process_handler.run_process(command, timeout=timeout + 60) if environment.is_trusted_host(): from bot.untrusted_runner import file_host file_host.copy_file_from_worker(rebased_output_file_path, output_file_path) if set_dedup_flags: _unset_dedup_flags() if not os.path.exists(output_file_path): logs.log_warn('LibFuzzer %s run failed.' % tool_name, output=output) return None, None # Ensure that the crash parameters match. It's possible that we will # minimize/cleanse to an unrelated bug, such as a timeout. crash_result = _run_libfuzzer_testcase(testcase, output_file_path) state = crash_result.get_symbolized_data() security_flag = crash_result.is_security_issue() if (security_flag != testcase.security_flag or state.crash_state != expected_crash_state): logs.log_warn('Ignoring unrelated crash.\n' 'State: %s (expected %s)\n' 'Security: %s (expected %s)\n' 'Output: %s\n' % (state.crash_state, expected_crash_state, security_flag, testcase.security_flag, state.crash_stacktrace)) return None, None with open(output_file_path, 'rb') as file_handle: minimized_keys = blobs.write_blob(file_handle) testcase.minimized_keys = minimized_keys testcase.put() return output_file_path, crash_result
def execute_task(testcase_id, job_type): """Run a test case with a second job type to generate a second stack trace.""" # Locate the testcase associated with the id. testcase = data_handler.get_testcase_by_id(testcase_id) data_handler.update_testcase_comment(testcase, data_types.TaskState.STARTED) # Setup testcase and its dependencies. file_list, _, testcase_file_path = setup.setup_testcase(testcase) if not file_list: return # Initialize timeout values. test_timeout = environment.get_value('TEST_TIMEOUT', 10) # Set up a custom or regular build. We explicitly omit the crash revision # since we want to test against the latest build here. build_manager.setup_build() # Check if we have an application path. If not, our build failed to setup # correctly. app_path = environment.get_value('APP_PATH') if not app_path: testcase = data_handler.get_testcase_by_id(testcase_id) data_handler.update_testcase_comment(testcase, data_types.TaskState.ERROR, 'Build setup failed') return # TSAN tool settings (if the tool is used). if environment.tool_matches('TSAN', job_type): environment.set_tsan_max_history_size() command = tests.get_command_line_for_application( testcase_file_path, app_path=app_path, needs_http=testcase.http_flag) result = tests.test_for_crash_with_retries(testcase, testcase_file_path, test_timeout, http_flag=testcase.http_flag, compare_crash=False) # Get revision information. revision = environment.get_value('APP_REVISION') # If a crash occurs, then we add the second stacktrace information. if result.is_crash(): state = result.get_symbolized_data() security_flag = result.is_security_issue() one_time_crasher_flag = not tests.test_for_reproducibility( testcase_file_path, state.crash_state, security_flag, test_timeout, testcase.http_flag, testcase.gestures) # Attach a header to indicate information on reproducibility flag. if one_time_crasher_flag: crash_stacktrace_header = 'Unreliable' else: crash_stacktrace_header = 'Fully reproducible' crash_stacktrace_header += (' crash found using %s job.\n\n' % job_type) unsymbolized_crash_stacktrace = result.get_stacktrace(symbolized=False) stacktrace = utils.get_crash_stacktrace_output( command, state.crash_stacktrace, unsymbolized_crash_stacktrace) crash_stacktrace = data_handler.filter_stacktrace( '%s%s' % (crash_stacktrace_header, stacktrace)) else: crash_stacktrace = 'No crash found using %s job.' % job_type # Decide which stacktrace to update this stacktrace with. testcase = data_handler.get_testcase_by_id(testcase_id) if testcase.last_tested_crash_stacktrace == 'Pending': # This case happens when someone clicks 'Update last tested stacktrace using # trunk build' button. testcase.last_tested_crash_stacktrace = crash_stacktrace testcase.set_metadata('last_tested_crash_revision', revision, update_testcase=False) else: # Default case when someone defines |SECOND_STACK_JOB_TYPE| in the job # type. This helps to test the unreproducible crash with a different memory # debugging tool to get a second stacktrace (e.g. running TSAN on a flaky # crash found in ASAN build). testcase.second_crash_stacktrace = crash_stacktrace testcase.set_metadata('second_crash_stacktrace_revision', revision, update_testcase=False) data_handler.update_testcase_comment(testcase, data_types.TaskState.FINISHED)