diff --git a/src/clusterfuzz/_internal/bot/fuzzers/libfuzzer.py b/src/clusterfuzz/_internal/bot/fuzzers/libfuzzer.py index c8d188c755b..952b06cbe7d 100644 --- a/src/clusterfuzz/_internal/bot/fuzzers/libfuzzer.py +++ b/src/clusterfuzz/_internal/bot/fuzzers/libfuzzer.py @@ -299,14 +299,16 @@ class LibFuzzerRunner(new_process.ModifierProcessRunnerMixin, new_process.UnicodeProcessRunner, LibFuzzerCommon): """libFuzzer runner (when minijail is not used).""" - def __init__(self, executable_path, default_args=None): + def __init__(self, executable_path, default_args=None, cwd=None): """Inits the LibFuzzerRunner. Args: executable_path: Path to the fuzzer executable. default_args: Default arguments to always pass to the fuzzer. + cwd: Optional current working directory for the process. """ - super().__init__(executable_path=executable_path, default_args=default_args) + super().__init__( + executable_path=executable_path, default_args=default_args, cwd=cwd) def fuzz(self, corpus_directories, @@ -1133,6 +1135,10 @@ def get_runner(fuzzer_path, temp_dir=None, use_minijail=None): temp_dir = fuzzer_utils.get_temp_dir() build_dir = environment.get_value('BUILD_DIR') + + cwd = build_dir if environment.get_value( + 'FUZZ_TARGET_CWD_IS_BUILD_DIR') else None + is_android = environment.is_android() is_fuchsia = environment.platform() == 'FUCHSIA' @@ -1141,6 +1147,7 @@ def get_runner(fuzzer_path, temp_dir=None, use_minijail=None): os.chmod(fuzzer_path, 0o755) is_chromeos_system_job = environment.is_chromeos_system_job() + if is_chromeos_system_job: minijail_chroot = minijail.ChromeOSChroot(build_dir) elif use_minijail: @@ -1185,7 +1192,11 @@ def get_runner(fuzzer_path, temp_dir=None, use_minijail=None): elif is_android: runner = AndroidLibFuzzerRunner(fuzzer_path, build_dir) else: - runner = LibFuzzerRunner(fuzzer_path) + runner = LibFuzzerRunner(fuzzer_path, cwd=cwd) + + if cwd and not isinstance(runner, LibFuzzerRunner): + logs.warning('FUZZ_TARGET_CWD_IS_BUILD_DIR is only supported for standard ' + 'LibFuzzerRunner and will be ignored.') return runner diff --git a/src/clusterfuzz/_internal/system/new_process.py b/src/clusterfuzz/_internal/system/new_process.py index e1585216d75..8a49dda19bc 100644 --- a/src/clusterfuzz/_internal/system/new_process.py +++ b/src/clusterfuzz/_internal/system/new_process.py @@ -246,12 +246,14 @@ class ProcessRunner: executable_path: Path to the executable to be run. default_args: An optional sequence of arguments that are always passed to the executable when run. + cwd: Optional current working directory for the process. """ - def __init__(self, executable_path, default_args=None): + def __init__(self, executable_path, default_args=None, cwd=None): """Inits ProcessRunner.""" self._executable_path = executable_path self._default_args = [] + self.cwd = cwd if default_args: self.default_args.extend(default_args) @@ -332,6 +334,14 @@ def run(self, if extra_env is not None: env.update(extra_env) + if self.cwd: + if 'cwd' in popen_args: + logs.warning(f'Detected two cwd arguments for popen ({self.cwd} and ' + f'{popen_args["cwd"]}). Using {popen_args["cwd"]}') + else: + logs.info(f'Executing process with custom cwd: {self.cwd}') + popen_args['cwd'] = self.cwd + return ChildProcess( subprocess.Popen( command, diff --git a/src/clusterfuzz/_internal/tests/core/bot/fuzzers/libFuzzer/libfuzzer_test.py b/src/clusterfuzz/_internal/tests/core/bot/fuzzers/libFuzzer/libfuzzer_test.py index 5ae921ca57d..3c718b55ae1 100644 --- a/src/clusterfuzz/_internal/tests/core/bot/fuzzers/libFuzzer/libfuzzer_test.py +++ b/src/clusterfuzz/_internal/tests/core/bot/fuzzers/libFuzzer/libfuzzer_test.py @@ -17,12 +17,14 @@ import os import shutil import unittest +from unittest import mock from clusterfuzz._internal.bot.fuzzers import engine_common from clusterfuzz._internal.bot.fuzzers import libfuzzer from clusterfuzz._internal.bot.fuzzers import strategy_selection from clusterfuzz._internal.bot.fuzzers.libFuzzer import fuzzer from clusterfuzz._internal.fuzzing import strategy +from clusterfuzz._internal.system import environment from clusterfuzz._internal.tests.test_libs import helpers as test_helpers TESTDATA_PATH = os.path.join(os.path.dirname(__file__), 'libfuzzer_test_data') @@ -177,5 +179,35 @@ def do_strategy(self, *args, **kwargs): libfuzzer.should_set_fork_flag(existing_arguments, MockPool())) +class GetRunnerTest(unittest.TestCase): + """Tests for get_runner.""" + + def setUp(self): + test_helpers.patch_environ(self) + + @mock.patch('os.chmod') + def test_cwd_is_build_dir(self, _): + """Test that FUZZ_TARGET_CWD_IS_BUILD_DIR causes cwd to be set to BUILD_DIR""" + environment.set_value('BUILD_DIR', '/build/dir') + environment.set_value('FUZZ_TARGET_CWD_IS_BUILD_DIR', True) + environment.set_value('USE_MINIJAIL', False) + + runner = libfuzzer.get_runner('/fake/path') + + self.assertIsInstance(runner, libfuzzer.LibFuzzerRunner) + self.assertEqual(runner.cwd, '/build/dir') + + @mock.patch('os.chmod') + def test_no_default_cwd(self, _): + """Test that cwd is None when FUZZ_TARGET_CWD_IS_BUILD_DIR is not set.""" + environment.set_value('BUILD_DIR', '/build/dir') + environment.set_value('USE_MINIJAIL', False) + + runner = libfuzzer.get_runner('fake/path') + + self.assertIsInstance(runner, libfuzzer.LibFuzzerRunner) + self.assertIsNone(runner.cwd) + + if __name__ == '__main__': unittest.main() diff --git a/src/clusterfuzz/_internal/tests/core/system/new_process_test.py b/src/clusterfuzz/_internal/tests/core/system/new_process_test.py index 09c95a2586a..1f247934478 100644 --- a/src/clusterfuzz/_internal/tests/core/system/new_process_test.py +++ b/src/clusterfuzz/_internal/tests/core/system/new_process_test.py @@ -142,6 +142,22 @@ def test_results_timeout(self): self.assertLess(abs(results.time_executed - 0.5), self.TIME_ERROR) self.assertTrue(results.timed_out) + def test_cwd(self): + """Test that cwd is passed to Popen.""" + with mock.patch('subprocess.Popen') as mock_popen: + runner = new_process.ProcessRunner( + '/test/path', default_args=['-arg1'], cwd='/working/dir') + + runner.run() + + mock_popen.assert_called_with( + ['/test/path', '-arg1'], + env=mock.ANY, + stdin=mock.ANY, + stdout=mock.ANY, + stderr=mock.ANY, + cwd='/working/dir') + def test_timeout(self): """Tests timeout signals.""" with mock.patch('subprocess.Popen', mock_popen_factory(1.0, '',