1#!/usr/bin/env python3.4 2# 3# Copyright (C) 2016 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import argparse 18import os 19import shutil 20import sys 21 22from subprocess import call 23from tempfile import mkdtemp 24 25sys.path.append(os.path.dirname(os.path.dirname( 26 os.path.realpath(__file__)))) 27 28from common.common import FatalError 29from common.common import GetEnvVariableOrError 30from common.common import GetJackClassPath 31from common.common import RetCode 32from common.common import RunCommand 33 34 35# 36# Tester class. 37# 38 39 40class DexFuzzTester(object): 41 """Tester that feeds JFuzz programs into DexFuzz testing.""" 42 43 def __init__(self, num_tests, num_inputs, device): 44 """Constructor for the tester. 45 46 Args: 47 num_tests: int, number of tests to run 48 num_inputs: int, number of JFuzz programs to generate 49 device: string, target device serial number (or None) 50 """ 51 self._num_tests = num_tests 52 self._num_inputs = num_inputs 53 self._device = device 54 self._save_dir = None 55 self._results_dir = None 56 self._dexfuzz_dir = None 57 self._inputs_dir = None 58 self._dexfuzz_env = None 59 60 def __enter__(self): 61 """On entry, enters new temp directory after saving current directory. 62 63 Raises: 64 FatalError: error when temp directory cannot be constructed 65 """ 66 self._save_dir = os.getcwd() 67 self._results_dir = mkdtemp(dir='/tmp/') 68 self._dexfuzz_dir = mkdtemp(dir=self._results_dir) 69 self._inputs_dir = mkdtemp(dir=self._dexfuzz_dir) 70 if self._results_dir is None or self._dexfuzz_dir is None or \ 71 self._inputs_dir is None: 72 raise FatalError('Cannot obtain temp directory') 73 self._dexfuzz_env = os.environ.copy() 74 self._dexfuzz_env['ANDROID_DATA'] = self._dexfuzz_dir 75 top = GetEnvVariableOrError('ANDROID_BUILD_TOP') 76 self._dexfuzz_env['PATH'] = (top + '/art/tools/bisection_search:' + 77 self._dexfuzz_env['PATH']) 78 android_root = GetEnvVariableOrError('ANDROID_HOST_OUT') 79 self._dexfuzz_env['ANDROID_ROOT'] = android_root 80 self._dexfuzz_env['LD_LIBRARY_PATH'] = android_root + '/lib' 81 os.chdir(self._dexfuzz_dir) 82 os.mkdir('divergent_programs') 83 os.mkdir('bisection_outputs') 84 return self 85 86 def __exit__(self, etype, evalue, etraceback): 87 """On exit, re-enters previously saved current directory and cleans up.""" 88 os.chdir(self._save_dir) 89 # TODO: detect divergences or shutil.rmtree(self._results_dir) 90 91 def Run(self): 92 """Feeds JFuzz programs into DexFuzz testing.""" 93 print() 94 print('**\n**** J/DexFuzz Testing\n**') 95 print() 96 print('#Tests :', self._num_tests) 97 print('Device :', self._device) 98 print('Directory :', self._results_dir) 99 print() 100 self.GenerateJFuzzPrograms() 101 self.RunDexFuzz() 102 103 104 def GenerateJFuzzPrograms(self): 105 """Generates JFuzz programs. 106 107 Raises: 108 FatalError: error when generation fails 109 """ 110 os.chdir(self._inputs_dir) 111 for i in range(1, self._num_inputs + 1): 112 jack_args = ['-cp', GetJackClassPath(), '--output-dex', '.', 'Test.java'] 113 if RunCommand(['jfuzz'], out='Test.java', err=None) != RetCode.SUCCESS: 114 print('Unexpected error while running JFuzz') 115 raise FatalError('Unexpected error while running JFuzz') 116 if RunCommand(['jack'] + jack_args, out=None, err='jackerr.txt', 117 timeout=30) != RetCode.SUCCESS: 118 print('Unexpected error while running Jack') 119 raise FatalError('Unexpected error while running Jack') 120 shutil.move('Test.java', '../Test' + str(i) + '.java') 121 shutil.move('classes.dex', 'classes' + str(i) + '.dex') 122 os.unlink('jackerr.txt') 123 124 def RunDexFuzz(self): 125 """Starts the DexFuzz testing.""" 126 os.chdir(self._dexfuzz_dir) 127 dexfuzz_args = ['--inputs=' + self._inputs_dir, 128 '--execute', 129 '--execute-class=Test', 130 '--repeat=' + str(self._num_tests), 131 '--quiet', 132 '--dump-output', 133 '--dump-verify', 134 '--interpreter', 135 '--optimizing', 136 '--bisection-search'] 137 if self._device is not None: 138 dexfuzz_args += ['--device=' + self._device, '--allarm'] 139 else: 140 dexfuzz_args += ['--host'] # Assume host otherwise. 141 cmd = ['dexfuzz'] + dexfuzz_args 142 print('**** Running ****\n\n', cmd, '\n') 143 call(cmd, env=self._dexfuzz_env) 144 print('\n**** Results (report.log) ****\n') 145 call(['tail', '-n 24', 'report.log']) 146 147 148def main(): 149 # Handle arguments. 150 parser = argparse.ArgumentParser() 151 parser.add_argument('--num_tests', default=1000, 152 type=int, help='number of tests to run') 153 parser.add_argument('--num_inputs', default=10, 154 type=int, help='number of JFuzz program to generate') 155 parser.add_argument('--device', help='target device serial number') 156 args = parser.parse_args() 157 # Run the DexFuzz tester. 158 with DexFuzzTester(args.num_tests, args.num_inputs, args.device) as fuzzer: 159 fuzzer.Run() 160 161if __name__ == '__main__': 162 main() 163