Skip to content

Commit 3f31ee8

Browse files
committed
[internal/LibFuzzer] Add option to set cwd to BUILD_DIR based on environment
Add optional cwd argument to LibFuzzerRunner class which passes to parent ProcessRunner class to subprocess.Popen with cwd argument. When a LibFuzzerRunner is requested with get_runner() the cwd can be set to BUILD_DIR using FUZZ_TARGET_CWD_IS_BUILD_DIR boolean env var.
1 parent 1051ec6 commit 3f31ee8

4 files changed

Lines changed: 64 additions & 4 deletions

File tree

src/clusterfuzz/_internal/bot/fuzzers/libfuzzer.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,14 +299,16 @@ class LibFuzzerRunner(new_process.ModifierProcessRunnerMixin,
299299
new_process.UnicodeProcessRunner, LibFuzzerCommon):
300300
"""libFuzzer runner (when minijail is not used)."""
301301

302-
def __init__(self, executable_path, default_args=None):
302+
def __init__(self, executable_path, default_args=None, cwd=None):
303303
"""Inits the LibFuzzerRunner.
304304
305305
Args:
306306
executable_path: Path to the fuzzer executable.
307307
default_args: Default arguments to always pass to the fuzzer.
308+
cwd: Optional current working directory for the process.
308309
"""
309-
super().__init__(executable_path=executable_path, default_args=default_args)
310+
super().__init__(
311+
executable_path=executable_path, default_args=default_args, cwd=cwd)
310312

311313
def fuzz(self,
312314
corpus_directories,
@@ -1133,6 +1135,10 @@ def get_runner(fuzzer_path, temp_dir=None, use_minijail=None):
11331135
temp_dir = fuzzer_utils.get_temp_dir()
11341136

11351137
build_dir = environment.get_value('BUILD_DIR')
1138+
1139+
cwd = build_dir if environment.get_value(
1140+
'FUZZ_TARGET_CWD_IS_BUILD_DIR') else None
1141+
11361142
is_android = environment.is_android()
11371143
is_fuchsia = environment.platform() == 'FUCHSIA'
11381144

@@ -1141,6 +1147,12 @@ def get_runner(fuzzer_path, temp_dir=None, use_minijail=None):
11411147
os.chmod(fuzzer_path, 0o755)
11421148

11431149
is_chromeos_system_job = environment.is_chromeos_system_job()
1150+
1151+
if cwd and (use_minijail or is_chromeos_system_job or is_fuchsia or
1152+
is_android):
1153+
logs.warning('FUZZ_TARGET_CWD_IS_BUILD_DIR is only supported for standard '
1154+
'LibFuzzerRunner and will be ignored.')
1155+
11441156
if is_chromeos_system_job:
11451157
minijail_chroot = minijail.ChromeOSChroot(build_dir)
11461158
elif use_minijail:
@@ -1185,7 +1197,7 @@ def get_runner(fuzzer_path, temp_dir=None, use_minijail=None):
11851197
elif is_android:
11861198
runner = AndroidLibFuzzerRunner(fuzzer_path, build_dir)
11871199
else:
1188-
runner = LibFuzzerRunner(fuzzer_path)
1200+
runner = LibFuzzerRunner(fuzzer_path, cwd=cwd)
11891201

11901202
return runner
11911203

src/clusterfuzz/_internal/system/new_process.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,12 +246,14 @@ class ProcessRunner:
246246
executable_path: Path to the executable to be run.
247247
default_args: An optional sequence of arguments that are always passed to
248248
the executable when run.
249+
cwd: Optional current working directory for the process.
249250
"""
250251

251-
def __init__(self, executable_path, default_args=None):
252+
def __init__(self, executable_path, default_args=None, cwd=None):
252253
"""Inits ProcessRunner."""
253254
self._executable_path = executable_path
254255
self._default_args = []
256+
self.cwd = cwd
255257

256258
if default_args:
257259
self.default_args.extend(default_args)
@@ -332,6 +334,10 @@ def run(self,
332334
if extra_env is not None:
333335
env.update(extra_env)
334336

337+
if self.cwd and 'cwd' not in popen_args:
338+
logs.info(f'Executing process with custom cwd: {self.cwd}')
339+
popen_args['cwd'] = self.cwd
340+
335341
return ChildProcess(
336342
subprocess.Popen(
337343
command,

src/clusterfuzz/_internal/tests/core/bot/fuzzers/libFuzzer/libfuzzer_test.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
import os
1818
import shutil
1919
import unittest
20+
from unittest import mock
2021

2122
from clusterfuzz._internal.bot.fuzzers import engine_common
2223
from clusterfuzz._internal.bot.fuzzers import libfuzzer
2324
from clusterfuzz._internal.bot.fuzzers import strategy_selection
2425
from clusterfuzz._internal.bot.fuzzers.libFuzzer import fuzzer
2526
from clusterfuzz._internal.fuzzing import strategy
27+
from clusterfuzz._internal.system import environment
2628
from clusterfuzz._internal.tests.test_libs import helpers as test_helpers
2729

2830
TESTDATA_PATH = os.path.join(os.path.dirname(__file__), 'libfuzzer_test_data')
@@ -177,5 +179,31 @@ def do_strategy(self, *args, **kwargs):
177179
libfuzzer.should_set_fork_flag(existing_arguments, MockPool()))
178180

179181

182+
class GetRunnerTest(unittest.TestCase):
183+
"""Tests for get_runner."""
184+
185+
def setUp(self):
186+
test_helpers.patch_environ(self)
187+
188+
@mock.patch('os.chmod')
189+
def test_cwd_is_build_dir(self, _):
190+
"""Test that FUZZ_TARGET_CWD_IS_BUILD_DIR causes cwd to be set to BUILD_DIR"""
191+
environment.set_value('BUILD_DIR', '/build/dir')
192+
environment.set_value('FUZZ_TARGET_CWD_IS_BUILD_DIR', True)
193+
environment.set_value('USE_MINIJAIL', False)
194+
runner = libfuzzer.get_runner('/fake/path')
195+
self.assertIsInstance(runner, libfuzzer.LibFuzzerRunner)
196+
self.assertEqual(runner.cwd, '/build/dir')
197+
198+
@mock.patch('os.chmod')
199+
def test_no_default_cwd(self, _):
200+
"""Test that cwd is None when FUZZ_TARGET_CWD_IS_BUILD_DIR is not set."""
201+
environment.set_value('BUILD_DIR', '/build/dir')
202+
environment.set_value('USE_MINIJAIL', False)
203+
runner = libfuzzer.get_runner('fake/path')
204+
self.assertIsInstance(runner, libfuzzer.LibFuzzerRunner)
205+
self.assertIsNone(runner.cwd)
206+
207+
180208
if __name__ == '__main__':
181209
unittest.main()

src/clusterfuzz/_internal/tests/core/system/new_process_test.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,20 @@ def test_results_timeout(self):
142142
self.assertLess(abs(results.time_executed - 0.5), self.TIME_ERROR)
143143
self.assertTrue(results.timed_out)
144144

145+
def test_cwd(self):
146+
"""Test that cwd is passed to Popen."""
147+
with mock.patch('subprocess.Popen') as mock_popen:
148+
runner = new_process.ProcessRunner(
149+
'/test/path', default_args=['-arg1'], cwd='/working/dir')
150+
runner.run()
151+
mock_popen.assert_called_with(
152+
['/test/path', '-arg1'],
153+
env=mock.ANY,
154+
stdin=mock.ANY,
155+
stdout=mock.ANY,
156+
stderr=mock.ANY,
157+
cwd='/working/dir')
158+
145159
def test_timeout(self):
146160
"""Tests timeout signals."""
147161
with mock.patch('subprocess.Popen', mock_popen_factory(1.0, '',

0 commit comments

Comments
 (0)