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 glob import glob 23 from subprocess import call 24 from tempfile import mkdtemp 25 26 sys.path.append(os.path.dirname(os.path.dirname( 27 os.path.realpath(__file__)))) 28 29 from common.common import FatalError 30 from common.common import GetEnvVariableOrError 31 from common.common import GetJackClassPath 32 from common.common import RetCode 33 from common.common import RunCommand 34 35 36 # 37 # Tester class. 38 # 39 40 41 class DexFuzzTester(object): 42 """Tester that feeds JFuzz programs into DexFuzz testing.""" 43 44 def __init__(self, num_tests, num_inputs, device, dexer, debug_info): 45 """Constructor for the tester. 46 47 Args: 48 num_tests: int, number of tests to run 49 num_inputs: int, number of JFuzz programs to generate 50 device: string, target device serial number (or None) 51 dexer: string, defines dexer 52 debug_info: boolean, if True include debugging info 53 """ 54 self._num_tests = num_tests 55 self._num_inputs = num_inputs 56 self._device = device 57 self._save_dir = None 58 self._results_dir = None 59 self._dexfuzz_dir = None 60 self._inputs_dir = None 61 self._dexfuzz_env = None 62 self._dexer = dexer 63 self._debug_info = debug_info 64 65 def __enter__(self): 66 """On entry, enters new temp directory after saving current directory. 67 68 Raises: 69 FatalError: error when temp directory cannot be constructed 70 """ 71 self._save_dir = os.getcwd() 72 self._results_dir = mkdtemp(dir='/tmp/') 73 self._dexfuzz_dir = mkdtemp(dir=self._results_dir) 74 self._inputs_dir = mkdtemp(dir=self._dexfuzz_dir) 75 if self._results_dir is None or self._dexfuzz_dir is None or \ 76 self._inputs_dir is None: 77 raise FatalError('Cannot obtain temp directory') 78 self._dexfuzz_env = os.environ.copy() 79 self._dexfuzz_env['ANDROID_DATA'] = self._dexfuzz_dir 80 top = GetEnvVariableOrError('ANDROID_BUILD_TOP') 81 self._dexfuzz_env['PATH'] = (top + '/art/tools/bisection_search:' + 82 self._dexfuzz_env['PATH']) 83 android_root = GetEnvVariableOrError('ANDROID_HOST_OUT') 84 self._dexfuzz_env['ANDROID_ROOT'] = android_root 85 self._dexfuzz_env['LD_LIBRARY_PATH'] = android_root + '/lib' 86 os.chdir(self._dexfuzz_dir) 87 os.mkdir('divergent_programs') 88 os.mkdir('bisection_outputs') 89 return self 90 91 def __exit__(self, etype, evalue, etraceback): 92 """On exit, re-enters previously saved current directory and cleans up.""" 93 os.chdir(self._save_dir) 94 # TODO: detect divergences or shutil.rmtree(self._results_dir) 95 96 def Run(self): 97 """Feeds JFuzz programs into DexFuzz testing.""" 98 print() 99 print('**\n**** J/DexFuzz Testing\n**') 100 print() 101 print('#Tests :', self._num_tests) 102 print('Device :', self._device) 103 print('Directory :', self._results_dir) 104 print() 105 self.GenerateJFuzzPrograms() 106 self.RunDexFuzz() 107 108 def CompileOnHost(self): 109 """Compiles Test.java into classes.dex using either javac/dx,d8 or jack. 110 111 Raises: 112 FatalError: error when compilation fails 113 """ 114 if self._dexer == 'dx' or self._dexer == 'd8': 115 dbg = '-g' if self._debug_info else '-g:none' 116 if RunCommand(['javac', '--release=8', dbg, 'Test.java'], 117 out=None, err='jerr.txt', timeout=30) != RetCode.SUCCESS: 118 print('Unexpected error while running javac') 119 raise FatalError('Unexpected error while running javac') 120 cfiles = glob('*.class') 121 dx = 'dx' if self._dexer == 'dx' else 'd8-compat-dx' 122 if RunCommand([dx, '--dex', '--output=classes.dex'] + cfiles, 123 out=None, err='dxerr.txt', timeout=30) != RetCode.SUCCESS: 124 print('Unexpected error while running dx') 125 raise FatalError('Unexpected error while running dx') 126 # Cleanup on success (nothing to see). 127 for cfile in cfiles: 128 os.unlink(cfile) 129 os.unlink('jerr.txt') 130 os.unlink('dxerr.txt') 131 132 elif self._dexer == 'jack': 133 jack_args = ['-cp', GetJackClassPath(), '--output-dex', '.', 'Test.java'] 134 if RunCommand(['jack'] + jack_args, out=None, err='jackerr.txt', 135 timeout=30) != RetCode.SUCCESS: 136 print('Unexpected error while running Jack') 137 raise FatalError('Unexpected error while running Jack') 138 # Cleanup on success (nothing to see). 139 os.unlink('jackerr.txt') 140 else: 141 raise FatalError('Unknown dexer: ' + self._dexer) 142 143 def GenerateJFuzzPrograms(self): 144 """Generates JFuzz programs. 145 146 Raises: 147 FatalError: error when generation fails 148 """ 149 os.chdir(self._inputs_dir) 150 for i in range(1, self._num_inputs + 1): 151 if RunCommand(['jfuzz'], out='Test.java', err=None) != RetCode.SUCCESS: 152 print('Unexpected error while running JFuzz') 153 raise FatalError('Unexpected error while running JFuzz') 154 self.CompileOnHost() 155 shutil.move('Test.java', '../Test' + str(i) + '.java') 156 shutil.move('classes.dex', 'classes' + str(i) + '.dex') 157 158 def RunDexFuzz(self): 159 """Starts the DexFuzz testing.""" 160 os.chdir(self._dexfuzz_dir) 161 dexfuzz_args = ['--inputs=' + self._inputs_dir, 162 '--execute', 163 '--execute-class=Test', 164 '--repeat=' + str(self._num_tests), 165 '--quiet', 166 '--dump-output', 167 '--dump-verify', 168 '--interpreter', 169 '--optimizing', 170 '--bisection-search'] 171 if self._device is not None: 172 dexfuzz_args += ['--device=' + self._device, '--allarm'] 173 else: 174 dexfuzz_args += ['--host'] # Assume host otherwise. 175 cmd = ['dexfuzz'] + dexfuzz_args 176 print('**** Running ****\n\n', cmd, '\n') 177 call(cmd, env=self._dexfuzz_env) 178 print('\n**** Results (report.log) ****\n') 179 call(['tail', '-n 24', 'report.log']) 180 181 182 def main(): 183 # Handle arguments. 184 parser = argparse.ArgumentParser() 185 parser.add_argument('--num_tests', default=1000, type=int, 186 help='number of tests to run (default: 1000)') 187 parser.add_argument('--num_inputs', default=10, type=int, 188 help='number of JFuzz program to generate (default: 10)') 189 parser.add_argument('--device', help='target device serial number') 190 parser.add_argument('--dexer', default='dx', type=str, 191 help='defines dexer as dx, d8, or jack (default: dx)') 192 parser.add_argument('--debug_info', default=False, action='store_true', 193 help='include debugging info') 194 args = parser.parse_args() 195 # Run the DexFuzz tester. 196 with DexFuzzTester(args.num_tests, 197 args.num_inputs, 198 args.device, 199 args.dexer, args.debug_info) as fuzzer: 200 fuzzer.Run() 201 202 if __name__ == '__main__': 203 main() 204