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 17 import argparse 18 import os 19 import shutil 20 import sys 21 22 from subprocess import call 23 from tempfile import mkdtemp 24 25 sys.path.append(os.path.dirname(os.path.dirname( 26 os.path.realpath(__file__)))) 27 28 from common.common import FatalError 29 from common.common import GetEnvVariableOrError 30 from common.common import GetJackClassPath 31 from common.common import RetCode 32 from common.common import RunCommand 33 34 35 # 36 # Tester class. 37 # 38 39 40 class 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 148 def 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 161 if __name__ == '__main__': 162 main() 163