1# Copyright 2020 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Utilities for OSS-Fuzz infrastructure.""" 15 16import os 17import re 18import stat 19import subprocess 20 21import helper 22 23ALLOWED_FUZZ_TARGET_EXTENSIONS = ['', '.exe'] 24FUZZ_TARGET_SEARCH_STRING = 'LLVMFuzzerTestOneInput' 25VALID_TARGET_NAME = re.compile(r'^[a-zA-Z0-9_-]+$') 26 27 28def chdir_to_root(): 29 """Changes cwd to OSS-Fuzz root directory.""" 30 # Change to oss-fuzz main directory so helper.py runs correctly. 31 if os.getcwd() != helper.OSSFUZZ_DIR: 32 os.chdir(helper.OSSFUZZ_DIR) 33 34 35def execute(command, location=None, check_result=False): 36 """ Runs a shell command in the specified directory location. 37 38 Args: 39 command: The command as a list to be run. 40 location: The directory the command is run in. 41 check_result: Should an exception be thrown on failed command. 42 43 Returns: 44 The stdout of the command, the error code. 45 46 Raises: 47 RuntimeError: running a command resulted in an error. 48 """ 49 50 if not location: 51 location = os.getcwd() 52 process = subprocess.Popen(command, stdout=subprocess.PIPE, cwd=location) 53 out, err = process.communicate() 54 if check_result and (process.returncode or err): 55 raise RuntimeError('Error: %s\n Command: %s\n Return code: %s\n Out: %s' % 56 (err, command, process.returncode, out)) 57 if out is not None: 58 out = out.decode('ascii').rstrip() 59 return out, process.returncode 60 61 62def get_fuzz_targets(path): 63 """Get list of fuzz targets in a directory. 64 65 Args: 66 path: A path to search for fuzz targets in. 67 68 Returns: 69 A list of paths to fuzzers or an empty list if None. 70 """ 71 if not os.path.exists(path): 72 return [] 73 fuzz_target_paths = [] 74 for root, _, _ in os.walk(path): 75 for filename in os.listdir(path): 76 file_path = os.path.join(root, filename) 77 if is_fuzz_target_local(file_path): 78 fuzz_target_paths.append(file_path) 79 80 return fuzz_target_paths 81 82 83def get_container_name(): 84 """Gets the name of the current docker container you are in. 85 /proc/self/cgroup can be used to check control groups e.g. Docker. 86 See: https://docs.docker.com/config/containers/runmetrics/ for more info. 87 88 Returns: 89 Container name or None if not in a container. 90 """ 91 with open('/proc/self/cgroup') as file_handle: 92 if 'docker' not in file_handle.read(): 93 return None 94 with open('/etc/hostname') as file_handle: 95 return file_handle.read().strip() 96 97 98def is_fuzz_target_local(file_path): 99 """Returns whether |file_path| is a fuzz target binary (local path). 100 Copied from clusterfuzz src/python/bot/fuzzers/utils.py 101 with slight modifications. 102 """ 103 filename, file_extension = os.path.splitext(os.path.basename(file_path)) 104 if not VALID_TARGET_NAME.match(filename): 105 # Check fuzz target has a valid name (without any special chars). 106 return False 107 108 if file_extension not in ALLOWED_FUZZ_TARGET_EXTENSIONS: 109 # Ignore files with disallowed extensions (to prevent opening e.g. .zips). 110 return False 111 112 if not os.path.exists(file_path) or not os.access(file_path, os.X_OK): 113 return False 114 115 if filename.endswith('_fuzzer'): 116 return True 117 118 if os.path.exists(file_path) and not stat.S_ISREG(os.stat(file_path).st_mode): 119 return False 120 121 with open(file_path, 'rb') as file_handle: 122 return file_handle.read().find(FUZZ_TARGET_SEARCH_STRING.encode()) != -1 123