#!/usr/bin/env python3
#
# Copyright (C) 2016 The Android Open Source Project
#
# 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.
import argparse
import os
import shutil
import sys
from glob
import glob
from subprocess
import call
from tempfile
import mkdtemp
sys.path.append(os.path.dirname(os.path.dirname(
os.path.realpath(__file__))))
from common.common
import FatalError
from common.common
import GetEnvVariableOrError
from common.common
import RetCode
from common.common
import RunCommand
#
# Tester class.
#
class DexFuzzTester(object):
"""Tester that feeds JFuzz programs into DexFuzz testing."""
def __init__(self, num_tests, num_inputs, device, dexer, debug_info):
"""Constructor for the tester.
Args:
num_tests: int, number of tests to run
num_inputs: int, number of JFuzz programs to generate
device: string, target device serial number (
or None)
dexer: string, defines dexer
debug_info: boolean,
if True include debugging info
"""
self._num_tests = num_tests
self._num_inputs = num_inputs
self._device = device
self._save_dir =
None
self._results_dir =
None
self._dexfuzz_dir =
None
self._inputs_dir =
None
self._dexfuzz_env =
None
self._dexer = dexer
self._debug_info = debug_info
def __enter__(self):
"""On entry, enters new temp directory after saving current directory.
Raises:
FatalError: error when temp directory cannot be constructed
"""
self._save_dir = os.getcwd()
self._results_dir = mkdtemp(dir=
'/tmp/')
self._dexfuzz_dir = mkdtemp(dir=self._results_dir)
self._inputs_dir = mkdtemp(dir=self._dexfuzz_dir)
if self._results_dir
is None or self._dexfuzz_dir
is None or \
self._inputs_dir
is None:
raise FatalError(
'Cannot obtain temp directory')
self._dexfuzz_env = os.environ.copy()
self._dexfuzz_env[
'ANDROID_DATA'] = self._dexfuzz_dir
top = GetEnvVariableOrError(
'ANDROID_BUILD_TOP')
self._dexfuzz_env[
'PATH'] = (top +
'/art/tools/bisection_search:' +
self._dexfuzz_env[
'PATH'])
android_root = GetEnvVariableOrError(
'ANDROID_HOST_OUT')
self._dexfuzz_env[
'ANDROID_ROOT'] = android_root
self._dexfuzz_env[
'LD_LIBRARY_PATH'] = android_root +
'/lib'
os.chdir(self._dexfuzz_dir)
os.mkdir(
'divergent_programs')
os.mkdir(
'bisection_outputs')
return self
def __exit__(self, etype, evalue, etraceback):
"""On exit, re-enters previously saved current directory and cleans up."""
os.chdir(self._save_dir)
# TODO: detect divergences or shutil.rmtree(self._results_dir)
def Run(self):
"""Feeds JFuzz programs into DexFuzz testing."""
print()
print(
'**\n**** J/DexFuzz Testing\n**')
print()
print(
'#Tests :', self._num_tests)
print(
'Device :', self._device)
print(
'Directory :', self._results_dir)
print()
self.GenerateJFuzzPrograms()
self.RunDexFuzz()
def CompileOnHost(self):
"""Compiles Test.java into classes.dex using either javac/dx or d8.
Raises:
FatalError: error when compilation fails
"""
if self._dexer ==
'dx' or self._dexer ==
'd8':
dbg =
'-g' if self._debug_info
else '-g:none'
if RunCommand([
'javac',
'--release=8', dbg,
'Test.java'],
out=
None, err=
'jerr.txt', timeout=
30) != RetCode.SUCCESS:
print(
'Unexpected error while running javac')
raise FatalError(
'Unexpected error while running javac')
cfiles = glob(
'*.class')
dx =
'dx' if self._dexer ==
'dx' else 'd8-compat-dx'
if RunCommand([dx,
'--dex',
'--output=classes.dex'] + cfiles,
out=
None, err=
'dxerr.txt', timeout=
30) != RetCode.SUCCESS:
print(
'Unexpected error while running dx')
raise FatalError(
'Unexpected error while running dx')
# Cleanup on success (nothing to see).
for cfile
in cfiles:
os.unlink(cfile)
os.unlink(
'jerr.txt')
os.unlink(
'dxerr.txt')
else:
raise FatalError(
'Unknown dexer: ' + self._dexer)
def GenerateJFuzzPrograms(self):
"""Generates JFuzz programs.
Raises:
FatalError: error when generation fails
"""
os.chdir(self._inputs_dir)
for i
in range(
1, self._num_inputs +
1):
if RunCommand([
'jfuzz'], out=
'Test.java', err=
None) != RetCode.SUCCESS:
print(
'Unexpected error while running JFuzz')
raise FatalError(
'Unexpected error while running JFuzz')
self.CompileOnHost()
shutil.move(
'Test.java',
'../Test' + str(i) +
'.java')
shutil.move(
'classes.dex',
'classes' + str(i) +
'.dex')
def RunDexFuzz(self):
"""Starts the DexFuzz testing."""
os.chdir(self._dexfuzz_dir)
dexfuzz_args = [
'--inputs=' + self._inputs_dir,
'--execute',
'--execute-class=Test',
'--repeat=' + str(self._num_tests),
'--quiet',
'--dump-output',
'--dump-verify',
'--interpreter',
'--optimizing',
'--bisection-search']
if self._device
is not None:
dexfuzz_args += [
'--device=' + self._device,
'--allarm']
else:
dexfuzz_args += [
'--host']
# Assume host otherwise.
cmd = [
'dexfuzz'] + dexfuzz_args
print(
'**** Running ****\n\n', cmd,
'\n')
call(cmd, env=self._dexfuzz_env)
print(
'\n**** Results (report.log) ****\n')
call([
'tail',
'-n 24',
'report.log'])
def main():
# Handle arguments.
parser = argparse.ArgumentParser()
parser.add_argument(
'--num_tests', default=
1000, type=int,
help=
'number of tests to run (default: 1000)')
parser.add_argument(
'--num_inputs', default=
10, type=int,
help=
'number of JFuzz program to generate (default: 10)')
parser.add_argument(
'--device', help=
'target device serial number')
parser.add_argument(
'--dexer', default=
'dx', type=str,
help=
'defines dexer as dx or d8 (default: dx)')
parser.add_argument(
'--debug_info', default=
False, action=
'store_true',
help=
'include debugging info')
args = parser.parse_args()
# Run the DexFuzz tester.
with DexFuzzTester(args.num_tests,
args.num_inputs,
args.device,
args.dexer, args.debug_info)
as fuzzer:
fuzzer.Run()
if __name__ ==
'__main__':
main()