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 glob import glob 23from subprocess import call 24from tempfile import mkdtemp 25 26sys.path.append(os.path.dirname(os.path.dirname( 27 os.path.realpath(__file__)))) 28 29from common.common import FatalError 30from common.common import GetEnvVariableOrError 31from common.common import GetJackClassPath 32from common.common import RetCode 33from common.common import RunCommand 34 35 36# 37# Tester class. 38# 39 40 41class 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 182def 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 202if __name__ == '__main__': 203 main() 204