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