• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 logging
17import os
18import posixpath
19import re
20import stat
21import subprocess
22import sys
23
24import helper
25
26ALLOWED_FUZZ_TARGET_EXTENSIONS = ['', '.exe']
27FUZZ_TARGET_SEARCH_STRING = 'LLVMFuzzerTestOneInput'
28VALID_TARGET_NAME = re.compile(r'^[a-zA-Z0-9_-]+$')
29
30# Location of google cloud storage for latest OSS-Fuzz builds.
31GCS_BASE_URL = 'https://storage.googleapis.com/'
32
33
34def chdir_to_root():
35  """Changes cwd to OSS-Fuzz root directory."""
36  # Change to oss-fuzz main directory so helper.py runs correctly.
37  if os.getcwd() != helper.OSS_FUZZ_DIR:
38    os.chdir(helper.OSS_FUZZ_DIR)
39
40
41def execute(command, location=None, check_result=False):
42  """ Runs a shell command in the specified directory location.
43
44  Args:
45    command: The command as a list to be run.
46    location: The directory the command is run in.
47    check_result: Should an exception be thrown on failed command.
48
49  Returns:
50    stdout, stderr, error code.
51
52  Raises:
53    RuntimeError: running a command resulted in an error.
54  """
55
56  if not location:
57    location = os.getcwd()
58  process = subprocess.Popen(command,
59                             stdout=subprocess.PIPE,
60                             stderr=subprocess.PIPE,
61                             cwd=location)
62  out, err = process.communicate()
63  out = out.decode('utf-8', errors='ignore')
64  err = err.decode('utf-8', errors='ignore')
65  if err:
66    logging.debug('Stderr of command \'%s\' is %s.', ' '.join(command), err)
67  if check_result and process.returncode:
68    raise RuntimeError(
69        'Executing command \'{0}\' failed with error: {1}.'.format(
70            ' '.join(command), err))
71  return out, err, process.returncode
72
73
74def get_fuzz_targets(path):
75  """Get list of fuzz targets in a directory.
76
77  Args:
78    path: A path to search for fuzz targets in.
79
80  Returns:
81    A list of paths to fuzzers or an empty list if None.
82  """
83  if not os.path.exists(path):
84    return []
85  fuzz_target_paths = []
86  for root, _, fuzzers in os.walk(path):
87    for fuzzer in fuzzers:
88      file_path = os.path.join(root, fuzzer)
89      if is_fuzz_target_local(file_path):
90        fuzz_target_paths.append(file_path)
91
92  return fuzz_target_paths
93
94
95def get_container_name():
96  """Gets the name of the current docker container you are in.
97
98  Returns:
99    Container name or None if not in a container.
100  """
101  result = subprocess.run(  # pylint: disable=subprocess-run-check
102      ['systemd-detect-virt', '-c'],
103      stdout=subprocess.PIPE).stdout
104  if b'docker' not in result:
105    return None
106  with open('/etc/hostname') as file_handle:
107    return file_handle.read().strip()
108
109
110def is_fuzz_target_local(file_path):
111  """Returns whether |file_path| is a fuzz target binary (local path).
112  Copied from clusterfuzz src/python/bot/fuzzers/utils.py
113  with slight modifications.
114  """
115  filename, file_extension = os.path.splitext(os.path.basename(file_path))
116  if not VALID_TARGET_NAME.match(filename):
117    # Check fuzz target has a valid name (without any special chars).
118    return False
119
120  if file_extension not in ALLOWED_FUZZ_TARGET_EXTENSIONS:
121    # Ignore files with disallowed extensions (to prevent opening e.g. .zips).
122    return False
123
124  if not os.path.exists(file_path) or not os.access(file_path, os.X_OK):
125    return False
126
127  if filename.endswith('_fuzzer'):
128    return True
129
130  if os.path.exists(file_path) and not stat.S_ISREG(os.stat(file_path).st_mode):
131    return False
132
133  with open(file_path, 'rb') as file_handle:
134    return file_handle.read().find(FUZZ_TARGET_SEARCH_STRING.encode()) != -1
135
136
137def binary_print(string):
138  """Print that can print a binary string."""
139  if isinstance(string, bytes):
140    string += b'\n'
141  else:
142    string += '\n'
143  sys.stdout.buffer.write(string)
144  sys.stdout.flush()
145
146
147def url_join(*url_parts):
148  """Joins URLs together using the POSIX join method.
149
150  Args:
151    url_parts: Sections of a URL to be joined.
152
153  Returns:
154    Joined URL.
155  """
156  return posixpath.join(*url_parts)
157
158
159def gs_url_to_https(url):
160  """Converts |url| from a GCS URL (beginning with 'gs://') to an HTTPS one."""
161  return url_join(GCS_BASE_URL, remove_prefix(url, 'gs://'))
162
163
164def remove_prefix(string, prefix):
165  """Returns |string| without the leading substring |prefix|."""
166  # Match behavior of removeprefix from python3.9:
167  # https://www.python.org/dev/peps/pep-0616/
168  if string.startswith(prefix):
169    return string[len(prefix):]
170
171  return string
172