Skip to content

Commit

Permalink
Reland Peach Mutator (google#1576)
Browse files Browse the repository at this point in the history
* Revert "Revert "Integrate Peach Mutation strategy (google#1480)" (google#1552)"

This reverts commit 40ad6a1.

* Updated environment variable name for pit filename

* peach mutator update

* Updated peach mutator
  • Loading branch information
mpherman2 authored Mar 18, 2020
1 parent 9c68482 commit 32ce4d0
Show file tree
Hide file tree
Showing 13 changed files with 283 additions and 10 deletions.
Binary file added resources/platform/linux/peach/peach_mutator.zip
Binary file not shown.
109 changes: 109 additions & 0 deletions resources/platform/linux/peach/pits/PDF.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="utf-8"?>
<Peach xmlns="http://peachfuzzer.com/2012/Peach" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://peachfuzzer.com/2012/Peach /peach/peach.xsd">
<Defaults>
<Number signed="false" />
</Defaults>

<!-- A sequence of consecutive white-space characters are is considered as one white-space character -->
<DataModel name="WhiteSpace">
<Choice maxOccurs="1">
<Number name="NULL" valueType="hex" value="0x00" size="8"/>
<Number name="HT" valueType="hex" value="0x09" size="8"/>
<Number name="LF" valueType="hex" value="0x0A" size="8"/>
<Number name="FF" valueType="hex" value="0x0C" size="8"/>
<Number name="CR" valueType="hex" value="0x0D" size="8"/>
<Number name="SP" valueType="hex" value="0x20" size="8"/>
</Choice>
</DataModel>

<DataModel name="NewLine">
<Number valueType="hex" value="0x0A" size="8"/>
</DataModel>

<DataModel name="Space">
<Number valueType="hex" value="0x20" size="8" token="true"/>
</DataModel>

<DataModel name="GenericObj">
<Blob name="data"/>
</DataModel>

<DataModel name="NullObj" ref="GenericObj"/>
<DataModel name="BooleanObj" ref="GenericObj"/>
<DataModel name="NumericObj" ref="GenericObj"/>
<DataModel name="StringObj" ref="GenericObj"/>
<DataModel name="NameObj" ref="GenericObj"/>
<DataModel name="ArrayObj" ref="GenericObj"/>
<DataModel name="DictionaryObj" ref="GenericObj"/>
<DataModel name="StreamObj" ref="GenericObj"/>

<DataModel name="IndirectObj">
<String name="startToken" value="obj" token="true"/>
<Choice>
<Block ref="NullObj"/>
<Block ref="BooleanObj"/>
<Block ref="NumericObj"/>
<Block ref="StringObj"/>
<Block ref="NameObj"/>
<Block ref="ArrayObj"/>
<Block ref="DictionaryObj"/>
<Block ref="StreamObj"/>
</Choice>
<String name="endToken" value="endobj" token="true"/>
<!--<Block name="space3" ref="WhiteSpace" maxOccurs="10"/>-->
</DataModel>

<DataModel name="CrossReferenceTable">
<String name="startToken" value="xref" token="true"/>
<Blob name="data"/>
</DataModel>

<DataModel name="PDF">
<!-- Header -->
<Choice name="Version">
<String value="%PDF-1.0" token="true"/>
<String value="%PDF-1.1" token="true"/>
<String value="%PDF-1.2" token="true"/>
<String value="%PDF-1.3" token="true"/>
<String value="%PDF-1.4" token="true"/>
<String value="%PDF-1.5" token="true"/>
<String value="%PDF-1.6" token="true"/>
<String value="%PDF-1.7" token="true"/>
</Choice>
<Blob name="CommentAndOther"/>
<String name="startToken" value="obj" token="true"/>
<Choice>
<Block ref="NullObj"/>
<Block ref="BooleanObj"/>
<Block ref="NumericObj"/>
<Block ref="StringObj"/>
<Block ref="NameObj"/>
<Block ref="ArrayObj"/>
<Block ref="DictionaryObj"/>
<Block ref="StreamObj"/>
</Choice>
<String name="endToken" value="endobj" token="true"/>
<!-- Body -->


<!--
<Choice name="PDFObjects" minOccurs="1" maxOccurs="30000">
<Block name="Body" ref="IndirectObj"/>
</Choice>
-->

<Block name="Objs" maxOccurs="1000">
<Blob name="DummySpace"/>
<Block name="PDFObj" ref="IndirectObj"/>
</Block>


<!-- Cross-reference table -->

<Blob name="EndPdf"/>
</DataModel>


</Peach>
<!-- end -->
3 changes: 2 additions & 1 deletion src/python/bot/fuzzers/libFuzzer/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,13 @@ def prepare(self, corpus_dir, target_path, _):
A FuzzOptions object.
"""
arguments = fuzzer.get_arguments(target_path)
grammar = fuzzer.get_grammar(target_path)
strategy_pool = strategy_selection.generate_weighted_strategy_pool(
strategy_list=strategy.LIBFUZZER_STRATEGY_LIST,
use_generator=True,
engine_name=self.name)
strategy_info = libfuzzer.pick_strategies(strategy_pool, target_path,
corpus_dir, arguments)
corpus_dir, arguments, grammar)

arguments.extend(strategy_info.arguments)

Expand Down
11 changes: 11 additions & 0 deletions src/python/bot/fuzzers/libFuzzer/fuzzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@
from bot.fuzzers.libFuzzer import constants


def get_grammar(fuzzer_path):
"""Get grammar for a given fuzz target. Return none if there isn't one."""
fuzzer_options = options.get_fuzz_target_options(fuzzer_path)
if fuzzer_options:
grammar = fuzzer_options.get_grammar_options()
if grammar:
return grammar.get('grammar')

return None


def get_arguments(fuzzer_path):
"""Get arguments for a given fuzz target."""
arguments = []
Expand Down
13 changes: 13 additions & 0 deletions src/python/bot/fuzzers/libFuzzer/peach/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2020 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.
35 changes: 35 additions & 0 deletions src/python/bot/fuzzers/libFuzzer/peach/pits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright 2020 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.
"""Dictionary to keep track of pit information."""

import os

from metrics import logs
from system import environment


def get_path(grammar):
"""Return the path of the peach pit for the given grammar. Return None if the
Pit does not exist or the grammar is None."""

pit_dir = os.path.join(environment.get_platform_resources_directory(),
'peach', 'pits')
pit_path = os.path.join(pit_dir, grammar + '.xml')

if not os.path.exists(pit_path):
logs.log_error(
'Pit file for "%s" grammar is not found.' % grammar, pit_path=pit_path)
return None

return pit_path
10 changes: 8 additions & 2 deletions src/python/bot/fuzzers/libFuzzer/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,10 @@ def parse_line_for_strategy_prefix(line, strategy_name):
if not line.startswith(strategy_prefix):
return

suffix_type = strategy.LIBFUZZER_STRATEGIES_WITH_PREFIX_VALUE_TYPE[
strategy_name]
try:
strategy_value = int(line[len(strategy_prefix):])
strategy_value = suffix_type(line[len(strategy_prefix):])
stats[name_modifier(strategy_name)] = strategy_value
except (IndexError, ValueError) as e:
logs.log_error('Failed to parse strategy "%s":\n%s\n' % (line, str(e)))
Expand Down Expand Up @@ -202,7 +204,11 @@ def parse_performance_features(log_lines, strategies, arguments):

# Initialize all strategy stats as disabled by default.
for strategy_type in strategy.LIBFUZZER_STRATEGY_LIST:
stats[strategy_column_name(strategy_type.name)] = 0
if strategy.LIBFUZZER_STRATEGIES_WITH_PREFIX_VALUE_TYPE.get(
strategy_type.name) == str:
stats[strategy_column_name(strategy_type.name)] = ''
else:
stats[strategy_column_name(strategy_type.name)] = 0

# Process fuzzing strategies used.
stats.update(parse_fuzzing_strategies(log_lines, strategies))
Expand Down
84 changes: 78 additions & 6 deletions src/python/bot/fuzzers/libfuzzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import re
import shutil
import string
import sys
import tempfile

from base import retry
Expand All @@ -34,6 +35,7 @@
from bot.fuzzers import mutator_plugin
from bot.fuzzers import utils as fuzzer_utils
from bot.fuzzers.libFuzzer import constants
from bot.fuzzers.libFuzzer.peach import pits
from datastore import data_types
from fuzzing import strategy
from metrics import logs
Expand All @@ -45,6 +47,7 @@
from platforms.fuchsia.util.device import Device
from platforms.fuchsia.util.fuzzer import Fuzzer
from platforms.fuchsia.util.host import Host
from system import archive
from system import environment
from system import minijail
from system import new_process
Expand Down Expand Up @@ -77,6 +80,13 @@
# Currently matches oss-fuzz/infra/base-images/base-runner/collect_dft#L34.
DATAFLOW_TRACE_DIR_SUFFIX = '_dft'

#List of all strategies that affect LD_PRELOAD.
MUTATOR_STRATEGIES = [
strategy.PEACH_GRAMMAR_MUTATION_STRATEGY.name,
strategy.MUTATOR_PLUGIN_STRATEGY.name,
strategy.MUTATOR_PLUGIN_RADAMSA_STRATEGY.name
]


class LibFuzzerException(Exception):
"""LibFuzzer exception."""
Expand Down Expand Up @@ -1575,15 +1585,20 @@ def use_mutator_plugin(target_name, extra_env):
return True


def is_linux_asan():
"""Helper functions. Returns whether or not the current env is linux asan."""
return (environment.platform() != 'LINUX' or
environment.get_value('MEMORY_TOOL') != 'ASAN')


def use_radamsa_mutator_plugin(extra_env):
"""Decide whether to use Radamsa in process. If yes, add the path to the
radamsa shared object to LD_PRELOAD in |extra_env| and return True."""

# Radamsa will only work on LINUX ASAN jobs.
# TODO(mpherman): Include architecture info in job definition and exclude
# i386.
if environment.platform() != 'LINUX' or environment.get_value(
'MEMORY_TOOL') != 'ASAN':
# i386.
if not is_linux_asan():
return False

radamsa_path = os.path.join(environment.get_platform_resources_directory(),
Expand All @@ -1594,6 +1609,50 @@ def use_radamsa_mutator_plugin(extra_env):
return True


def use_peach_mutator(extra_env, grammar):
"""Decide whether or not to use peach mutator, and set up all of the
environment variables necessary to do so."""
# TODO(mpherman): Include architecture info in job definition and exclude
# i386.
if not is_linux_asan():
return False

if not grammar:
return False

pit_path = pits.get_path(grammar)

if not pit_path:
return False

# Set title and pit environment variables
extra_env['PIT_FILENAME'] = pit_path
extra_env['PIT_TITLE'] = grammar

# Extract zip of peach mutator code.
peach_dir = os.path.join(environment.get_platform_resources_directory(),
'peach')
unzipped = os.path.join(peach_dir, 'mutator')
source = os.path.join(peach_dir, 'peach_mutator.zip')

archive.unpack(source, unzipped, trusted=True)

# Set LD_PRELOAD.
peach_path = os.path.join(unzipped, 'peach_mutator', 'src', 'peach.so')
extra_env['LD_PRELOAD'] = peach_path

# Set Python path.
# TODO(mpherman): Change this to explicitly point to Python 2.
new_path = [
os.path.join(unzipped, 'peach_mutator', 'src'),
os.path.join(unzipped, 'peach_mutator', 'third_party', 'peach')
] + sys.path

extra_env['PYTHONPATH'] = os.pathsep.join(new_path)

return True


def is_sha1_hash(possible_hash):
"""Returns True if |possible_hash| looks like a valid sha1 hash."""
if len(possible_hash) != 40:
Expand All @@ -1617,8 +1676,15 @@ def move_mergeable_units(merge_directory, corpus_directory):
shell.move(unit_path, dest_path)


def pick_strategies(strategy_pool, fuzzer_path, corpus_directory,
existing_arguments):
def has_existing_mutator_strategy(fuzzing_strategy):
return any(strategy in fuzzing_strategy for strategy in MUTATOR_STRATEGIES)


def pick_strategies(strategy_pool,
fuzzer_path,
corpus_directory,
existing_arguments,
grammar=None):
"""Pick strategies."""
build_directory = environment.get_value('BUILD_DIR')
target_name = os.path.basename(fuzzer_path)
Expand Down Expand Up @@ -1712,7 +1778,13 @@ def pick_strategies(strategy_pool, fuzzer_path, corpus_directory,
use_mutator_plugin(target_name, extra_env)):
fuzzing_strategies.append(strategy.MUTATOR_PLUGIN_STRATEGY.name)

if (strategy.MUTATOR_PLUGIN_STRATEGY.name not in fuzzing_strategies and
if (not has_existing_mutator_strategy(fuzzing_strategies) and
strategy_pool.do_strategy(strategy.PEACH_GRAMMAR_MUTATION_STRATEGY) and
use_peach_mutator(extra_env, grammar)):
fuzzing_strategies.append(
'%s_%s' % (strategy.PEACH_GRAMMAR_MUTATION_STRATEGY.name, grammar))

if (not has_existing_mutator_strategy(fuzzing_strategies) and
strategy_pool.do_strategy(strategy.MUTATOR_PLUGIN_RADAMSA_STRATEGY) and
use_radamsa_mutator_plugin(extra_env)):
fuzzing_strategies.append(strategy.MUTATOR_PLUGIN_RADAMSA_STRATEGY.name)
Expand Down
10 changes: 9 additions & 1 deletion src/python/bot/fuzzers/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,19 @@ def get_ubsan_options(self):
"""Return a list of UBSAN_OPTIONS overrides."""
return self._get_option_section('ubsan')

def get_grammar_options(self):
"""Return a list og grammar options"""
return self._get_option_section('grammar')


def get_fuzz_target_options(fuzz_target_path):
"""Return a FuzzerOptions for the given target, or None if it does not
exist."""
options_file_path = fuzzer_utils.get_supporting_file(fuzz_target_path,
OPTIONS_FILE_EXTENSION)
options_cwd = os.path.dirname(options_file_path)

if not options_file_path:
return None

if environment.is_trusted_host():
options_file_path = fuzzer_utils.get_file_from_untrusted_worker(
Expand All @@ -171,6 +177,8 @@ def get_fuzz_target_options(fuzz_target_path):
if not os.path.exists(options_file_path):
return None

options_cwd = os.path.dirname(options_file_path)

try:
return FuzzerOptions(options_file_path, cwd=options_cwd)
except FuzzerOptionsException:
Expand Down
Loading

0 comments on commit 32ce4d0

Please sign in to comment.