aboutsummaryrefslogtreecommitdiffhomepage
path: root/infra/cifuzz/docker.py
blob: 9621c7994793bb02557e1fd32af136960ed9834c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Module for dealing with docker."""
import logging
import os
import sys

# pylint: disable=wrong-import-position,import-error
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import utils

BASE_BUILDER_TAG = 'gcr.io/oss-fuzz-base/base-builder'
BASE_RUNNER_TAG = 'gcr.io/oss-fuzz-base/base-runner'
MSAN_LIBS_BUILDER_TAG = 'gcr.io/oss-fuzz-base/msan-libs-builder'
PROJECT_TAG_PREFIX = 'gcr.io/oss-fuzz/'

# Default fuzz configuration.
DEFAULT_ENGINE = 'libfuzzer'
DEFAULT_ARCHITECTURE = 'x86_64'
_DEFAULT_DOCKER_RUN_ARGS = [
    '--cap-add', 'SYS_PTRACE', '-e', 'FUZZING_ENGINE=' + DEFAULT_ENGINE, '-e',
    'ARCHITECTURE=' + DEFAULT_ARCHITECTURE, '-e', 'CIFUZZ=True'
]

EXTERNAL_PROJECT_IMAGE = 'external-project'

_DEFAULT_DOCKER_RUN_COMMAND = [
    'docker',
    'run',
    '--rm',
    '--privileged',
]


def get_project_image_name(project):
  """Returns the name of the project builder image for |project_name|."""
  # TODO(ochang): We may need unique names to support parallel fuzzing.
  if project:
    return PROJECT_TAG_PREFIX + project

  return EXTERNAL_PROJECT_IMAGE


def delete_images(images):
  """Deletes |images|."""
  command = ['docker', 'rmi', '-f'] + images
  utils.execute(command)
  utils.execute(['docker', 'builder', 'prune', '-f'])


def get_base_docker_run_args(workspace, sanitizer='address', language='c++'):
  """Returns arguments that should be passed to every invocation of 'docker
  run'."""
  docker_args = _DEFAULT_DOCKER_RUN_ARGS.copy()
  docker_args += [
      '-e', f'SANITIZER={sanitizer}', '-e', f'FUZZING_LANGUAGE={language}',
      '-e', 'OUT=' + workspace.out
  ]
  docker_container = utils.get_container_name()
  logging.info('Docker container: %s.', docker_container)
  if docker_container:
    # Don't map specific volumes if in a docker container, it breaks when
    # running a sibling container.
    docker_args += ['--volumes-from', docker_container]
  else:
    docker_args += _get_args_mapping_host_path_to_container(workspace.workspace)
  return docker_args, docker_container


def get_base_docker_run_command(workspace, sanitizer='address', language='c++'):
  """Returns part of the command that should be used everytime 'docker run' is
  invoked."""
  docker_args, docker_container = get_base_docker_run_args(
      workspace, sanitizer, language)
  command = _DEFAULT_DOCKER_RUN_COMMAND.copy() + docker_args
  return command, docker_container


def _get_args_mapping_host_path_to_container(host_path, container_path=None):
  """Get arguments to docker run that will map |host_path| a path on the host to
  a path in the container. If |container_path| is specified, that path is mapped
  to. If not, then |host_path| is mapped to itself in the container."""
  # WARNING: Do not use this function when running in production (and
  # --volumes-from) is used for mapping volumes. It will break production.
  container_path = host_path if container_path is None else container_path
  return ['-v', f'{host_path}:{container_path}']


class Workspace:
  """Class representing the workspace directory."""

  def __init__(self, config):
    self.workspace = config.workspace

  def initialize_dir(self, directory):  # pylint: disable=no-self-use
    """Creates directory if it doesn't already exist, otherwise does nothing."""
    os.makedirs(directory, exist_ok=True)

  @property
  def out(self):
    """The out directory used for storing the fuzzer build built by
    build_fuzzers."""
    # Don't use 'out' because it needs to be used by artifacts.
    return os.path.join(self.workspace, 'build-out')

  @property
  def work(self):
    """The directory used as the work directory for the fuzzer build/run."""
    return os.path.join(self.workspace, 'work')

  @property
  def artifacts(self):
    """The directory used to store artifacts for download by CI-system users."""
    # This is hardcoded by a lot of clients, so we need to use this.
    return os.path.join(self.workspace, 'out', 'artifacts')

  @property
  def clusterfuzz_build(self):
    """The directory where builds from ClusterFuzz are stored."""
    return os.path.join(self.workspace, 'cifuzz-prev-build')

  @property
  def clusterfuzz_coverage(self):
    """The directory where builds from ClusterFuzz are stored."""
    return os.path.join(self.workspace, 'cifuzz-prev-coverage')

  @property
  def coverage_report(self):
    """The directory where coverage reports generated by cifuzz are put."""
    return os.path.join(self.workspace, 'cifuzz-coverage')

  @property
  def corpora(self):
    """The directory where corpora from ClusterFuzz are stored."""
    return os.path.join(self.workspace, 'cifuzz-corpus')