-
Notifications
You must be signed in to change notification settings - Fork 623
Add Mutator Plugins as a Strategy #286
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
84882e0
894d3ba
56c52f6
92fb0e0
00bd0a1
a33ba61
6e309a2
088e2f8
cbbbaed
8b0956b
9018d32
9dd22d0
87ee234
3bb1279
66b8752
c88acd8
deffd5f
7b53d97
021d73d
2e2d487
6344c0d
246a445
8d7d710
b8ed1b2
7a26dc2
8e6f954
bb0ca8c
2939d6c
9ae33fb
50fbd32
ed1b0af
dd9d9dc
8c0cf2c
27646a4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Placeholder for mutator plugins. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -104,7 +104,8 @@ def fuzz(self, | |
| corpus_directories, | ||
| fuzz_timeout, | ||
| artifact_prefix=None, | ||
| additional_args=None): | ||
| additional_args=None, | ||
| extra_env=None): | ||
| """Running fuzzing command. | ||
|
|
||
| Args: | ||
|
|
@@ -116,6 +117,8 @@ def fuzz(self, | |
| timeouts, slow units) | ||
| additional_args: A sequence of additional arguments to be passed to the | ||
| executable. | ||
| extra_env: A dictionary containing environment variables and their values. | ||
| These will be added to the environment of the new process. | ||
|
|
||
| Returns: | ||
| A process.ProcessResult. | ||
|
|
@@ -147,7 +150,8 @@ def fuzz(self, | |
| timeout=fuzz_timeout - self.SIGTERM_WAIT_TIME, | ||
| terminate_before_kill=True, | ||
| terminate_wait_time=self.SIGTERM_WAIT_TIME, | ||
| max_stdout_len=MAX_OUTPUT_LEN) | ||
| max_stdout_len=MAX_OUTPUT_LEN, | ||
| extra_env=extra_env) | ||
|
|
||
| def merge(self, | ||
| corpus_directories, | ||
|
|
@@ -309,14 +313,15 @@ def fuzz(self, | |
| corpus_directories, | ||
| fuzz_timeout, | ||
| artifact_prefix=None, | ||
| additional_args=None): | ||
| additional_args=None, | ||
| extra_env=None): | ||
| """LibFuzzerCommon.fuzz override.""" | ||
| additional_args = copy.copy(additional_args) | ||
| if additional_args is None: | ||
| additional_args = [] | ||
|
|
||
| return LibFuzzerCommon.fuzz(self, corpus_directories, fuzz_timeout, | ||
| artifact_prefix, additional_args) | ||
| artifact_prefix, additional_args, extra_env) | ||
|
|
||
|
|
||
| class MinijailLibFuzzerRunner(engine_common.MinijailEngineFuzzerRunner, | ||
|
|
@@ -383,11 +388,12 @@ def fuzz(self, | |
| corpus_directories, | ||
| fuzz_timeout, | ||
| artifact_prefix=None, | ||
| additional_args=None): | ||
| additional_args=None, | ||
| extra_env=None): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looks like extra_env is not passed to LibFuzzerCommon.fuzz
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also need a test that shows LD_PRELOAD set to that path.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think this was fixed in a more recent upload.
I'll see how easy this is but I already test for this implicitly in the integration test I added. |
||
| """LibFuzzerCommon.fuzz override.""" | ||
| corpus_directories = self._get_chroot_corpus_paths(corpus_directories) | ||
| return LibFuzzerCommon.fuzz(self, corpus_directories, fuzz_timeout, | ||
| artifact_prefix, additional_args) | ||
| artifact_prefix, additional_args, extra_env) | ||
|
|
||
| def merge(self, | ||
| corpus_directories, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| # Copyright 2019 Google LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Getting and using custom mutator plugins for libFuzzer.""" | ||
|
|
||
| import os | ||
|
|
||
| from base import utils | ||
| from google_cloud_utils import storage | ||
| from metrics import logs | ||
| from system import archive | ||
| from system import environment | ||
| from system import shell | ||
|
|
||
| MUTATOR_SHARED_OBJECT_FILENAME = 'mutator-plugin.so' | ||
| ARCHIVES_SUBDIR_NAME = 'archives' | ||
| PLUGINS_SUBDIR_NAME = 'plugins' | ||
|
|
||
|
|
||
| def _get_mutator_plugins_bucket_url(): | ||
| """Returns the url of the mutator plugin's cloud storage bucket.""" | ||
| return 'gs://%s' % environment.get_value('MUTATOR_PLUGINS_BUCKET') | ||
|
|
||
|
|
||
| def _get_mutator_plugins_subdir(subdir): | ||
| """Returns the path of the subdirectory named |subdir| in | ||
| MUTATOR_PLUGINS_DIR.""" | ||
| return os.path.join(environment.get_value('MUTATOR_PLUGINS_DIR'), subdir) | ||
|
|
||
|
|
||
| def _get_mutator_plugins_archives_dir(): | ||
| """Returns path of the archives subdirectory in MUTATOR_PLUGINS_DIR""" | ||
| return _get_mutator_plugins_subdir(ARCHIVES_SUBDIR_NAME) | ||
|
|
||
|
|
||
| def _get_mutator_plugins_unpacked_dir(): | ||
| """Returns path of the unpacked plugins subdirectory in MUTATOR_PLUGINS_DIR""" | ||
| return _get_mutator_plugins_subdir(PLUGINS_SUBDIR_NAME) | ||
|
|
||
|
|
||
| def _get_mutator_plugins_from_bucket(): | ||
| """Returns list of the mutator plugin archives in the mutator plugin storage | ||
| bucket.""" | ||
| return storage.list_blobs(_get_mutator_plugins_bucket_url()) | ||
|
|
||
|
|
||
| def _download_mutator_plugin_archive(mutator_plugin_archive): | ||
| """Downloads the |mutator_plugin_archive| from the mutator plugin storage | ||
| bucket to the plugin archives directory. Returns the path that the archive was | ||
| downloaded to.""" | ||
| file_path = os.path.join(_get_mutator_plugins_archives_dir(), | ||
| mutator_plugin_archive) | ||
| url = '%s/%s' % (_get_mutator_plugins_bucket_url(), mutator_plugin_archive) | ||
| if not storage.copy_file_from(url, file_path): | ||
| logs.log_error( | ||
| 'Failed to copy plugin archive from %s to %s' % (url, file_path)) | ||
| return None | ||
|
|
||
| return file_path | ||
|
|
||
|
|
||
| def _unpack_mutator_plugin(mutator_plugin_archive_path): | ||
| """Unpacks |mutator_plugin_archive_path| in the unpacked plugins directory and | ||
| returns the path it was unpacked into.""" | ||
| mutator_plugin_name = os.path.basename( | ||
| os.path.splitext(mutator_plugin_archive_path)[0]) | ||
| unpacked_plugin_dir = os.path.join(_get_mutator_plugins_unpacked_dir(), | ||
| mutator_plugin_name) | ||
| archive.unpack(mutator_plugin_archive_path, unpacked_plugin_dir) | ||
| return unpacked_plugin_dir | ||
|
jonathanmetzman marked this conversation as resolved.
|
||
|
|
||
|
|
||
| def _extract_name_from_archive(plugin_archive_filename): | ||
| """Parses |plugin_archive_filename| which should be named using the schema: | ||
| '$NAME-$JOB-$FUZZ_TARGET.zip'. Returns tuple containing | ||
|
jonathanmetzman marked this conversation as resolved.
jonathanmetzman marked this conversation as resolved.
|
||
| ($NAME, $JOB-$FUZZ_TARGET).""" | ||
| # TODO(metzman): Get rid of this when we create an upload page for custom | ||
| # mutator plugins. | ||
| plugin_archive_name = os.path.splitext(plugin_archive_filename)[0] | ||
| idx = plugin_archive_name.index('-') | ||
| plugin_name = plugin_archive_name[:idx] | ||
| return plugin_name, plugin_archive_name[idx + 1:] | ||
|
|
||
|
|
||
| class PluginGetter(object): | ||
| """Class that gets a usable mutator plugin for |fuzzer_binary_name| from | ||
| GCS.""" | ||
|
|
||
| def __init__(self, fuzzer_binary_name): | ||
| self.fuzzer_binary_name = fuzzer_binary_name | ||
| self.job_name = environment.get_value('JOB_NAME') | ||
| self.create_directories() | ||
|
|
||
| def create_directories(self): | ||
| """Creates directories needed to use mutator plugins.""" | ||
|
|
||
| # TODO(320): Change mutator plugin downloads so that they don't need to be | ||
| # deleted and redownloaded on each run of launcher.py. | ||
| def recreate_directory(directory_path): | ||
| shell.create_directory_if_needed( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks very hacky, to do create_directory_if_needed first and then remove_directory. Why do we need to remove and re-download plugin everytime ? Is this for every launcher instance ? We need to make sure we dont even unpack if it was unpacked once.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
What if the plugin gets updated?
Yes.
I intentionally do this. I agree it would be less wasteful to unpack once for every 5 runs of launcher.py. I believe the code did this initially, but it was suggested that I change this so that mutator plugins would more easily work on OSS-Fuzz. |
||
| directory_path, create_intermediates=True) | ||
| shell.remove_directory(directory_path, recreate=True) | ||
|
|
||
| recreate_directory(environment.get_value('MUTATOR_PLUGINS_DIR')) | ||
| recreate_directory(_get_mutator_plugins_archives_dir()) | ||
| recreate_directory(_get_mutator_plugins_unpacked_dir()) | ||
|
|
||
| def _is_plugin_usable(self, plugin_archive_filename): | ||
| """Returns True if |plugin_archive_filename| is a usable plugin for this | ||
| job, fuzz target combination.""" | ||
| _, plugin_job_and_fuzzer = _extract_name_from_archive( | ||
| plugin_archive_filename) | ||
| expected_name = '%s-%s' % (self.job_name, self.fuzzer_binary_name) | ||
| return expected_name == plugin_job_and_fuzzer | ||
|
|
||
| def get_mutator_plugin(self): | ||
| """Downloads and unpacks a usable mutator plugin for this job and fuzz | ||
| target if one is available in GCS""" | ||
| mutator_plugins = _get_mutator_plugins_from_bucket() | ||
| usable_mutator_plugins = [ | ||
| plugin_archive for plugin_archive in mutator_plugins | ||
| if self._is_plugin_usable(plugin_archive) | ||
| ] | ||
|
|
||
| # Quit if no usable plugins are available. | ||
| if not usable_mutator_plugins: | ||
| return None | ||
|
|
||
| plugin_archive_name = utils.random_element_from_list(usable_mutator_plugins) | ||
|
|
||
| # Handle a failed download. | ||
| plugin_archive_path = _download_mutator_plugin_archive(plugin_archive_name) | ||
| if not plugin_archive_path: | ||
| return None | ||
|
|
||
| _unpack_mutator_plugin(plugin_archive_path) | ||
| mutator_plugin_path = find_mutator_plugin() | ||
| if mutator_plugin_path is None: | ||
| logs.log_error('Could not find plugin in %s' % plugin_archive_path) | ||
|
|
||
| return mutator_plugin_path | ||
|
|
||
|
|
||
| def find_mutator_plugin(): | ||
| """Sets LD_PRELOAD to the path of a usable mutator plugin shared object. | ||
| This should only be called after a call to get_mutator_plugin.""" | ||
| paths = shell.get_files_list(_get_mutator_plugins_unpacked_dir()) | ||
| # This function should not be called unless there is an unpacked plugin. | ||
| for path in paths: | ||
| if os.path.basename(path) == MUTATOR_SHARED_OBJECT_FILENAME: | ||
| return path | ||
| return None | ||
|
|
||
|
|
||
| def get_mutator_plugin(fuzzer_binary_name): | ||
| """Downloads and unpacks a mutator plugin if a usable one for | ||
| |fuzzer_binary_name| is in the bucket.""" | ||
| plugin_getter = PluginGetter(fuzzer_binary_name) | ||
| return plugin_getter.get_mutator_plugin() | ||
Uh oh!
There was an error while loading. Please reload this page.