Skip to content

Commit b7fe15f

Browse files
committed
[internal/LibFuzzer] Add option to set cwd 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 is retrieved from the FUZZ_TARGET_CUSTOM_CWD env var.
1 parent 1051ec6 commit b7fe15f

4 files changed

Lines changed: 53 additions & 4 deletions

File tree

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

Lines changed: 14 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,9 @@ 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 = environment.get_value('FUZZ_TARGET_CUSTOM_CWD')
1140+
11361141
is_android = environment.is_android()
11371142
is_fuchsia = environment.platform() == 'FUCHSIA'
11381143

@@ -1141,6 +1146,12 @@ def get_runner(fuzzer_path, temp_dir=None, use_minijail=None):
11411146
os.chmod(fuzzer_path, 0o755)
11421147

11431148
is_chromeos_system_job = environment.is_chromeos_system_job()
1149+
1150+
if cwd and (use_minijail or is_chromeos_system_job or is_fuchsia or
1151+
is_android):
1152+
logs.warning('Custom cwd is only supported for standard LibFuzzerRunner '
1153+
'and will be ignored.')
1154+
11441155
if is_chromeos_system_job:
11451156
minijail_chroot = minijail.ChromeOSChroot(build_dir)
11461157
elif use_minijail:
@@ -1185,7 +1196,7 @@ def get_runner(fuzzer_path, temp_dir=None, use_minijail=None):
11851196
elif is_android:
11861197
runner = AndroidLibFuzzerRunner(fuzzer_path, build_dir)
11871198
else:
1188-
runner = LibFuzzerRunner(fuzzer_path)
1199+
runner = LibFuzzerRunner(fuzzer_path, cwd=cwd)
11891200

11901201
return runner
11911202

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: 18 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,21 @@ 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_custom_cwd(self, _):
190+
"""Test that FUZZ_TARGET_CUSTOM_CWD is passed to LibFuzzerRunner."""
191+
environment.set_value('FUZZ_TARGET_CUSTOM_CWD', '/custom/cwd')
192+
environment.set_value('USE_MINIJAIL', False)
193+
runner = libfuzzer.get_runner('/fake/path')
194+
self.assertIsInstance(runner, libfuzzer.LibFuzzerRunner)
195+
self.assertEqual(runner.cwd, '/custom/cwd')
196+
197+
180198
if __name__ == '__main__':
181199
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)