blob: 9abdff0a62891f87f4aea4e9dbdfe5eaf6a8fa2a [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2020 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
import subprocess
class Corpus(object):
"""Represents a corpus of fuzzing inputs.
A fuzzing corpus is the set of "interesting" inputs as determined by the
individual fuzzer. See https://llvm.org/docs/LibFuzzer.html#corpus for
details on how libFuzzer uses corpora.
Attributes:
fuzzer: The Fuzzer corresponding to this object.
nspath: Path in namespace where the working corpus is stored.
"""
def __init__(self, fuzzer):
self._fuzzer = fuzzer
self._nspaths = None
@property
def fuzzer(self):
"""The Fuzzer corresponding to this object."""
return self._fuzzer
@property
def host(self):
"""Alias for fuzzer.host."""
return self.fuzzer.host
@property
def ns(self):
"""Alias for fuzzer.ns."""
return self.fuzzer.ns
@property
def nspaths(self):
"""List of paths to where the corpus is stored on device.
The first element is the mutable corpus.
"""
if not self._nspaths:
self.find_on_device()
return self._nspaths
def find_on_device(self):
data = self.ns.data('corpus')
resource = self.ns.resource('corpus')
self.ns.mkdir(data)
if self.ns.ls(resource):
self._nspaths = [data, resource]
else:
self._nspaths = [data]
def add_from_host(self, pathname):
"""Copies elements from a host directory to the corpus on a device."""
self.fuzzer.require_stopped()
if not self.host.isdir(pathname):
self.host.error('No such directory: {}'.format(pathname))
pathname = os.path.join(pathname, '*')
return self.ns.store(self.nspaths[0], pathname)
def add_from_gcs(self, gcs_url):
"""Copies corpus elements from a GCS bucket to this corpus."""
if not gcs_url.endswith('*'):
gcs_url += '/*'
with self.host.temp_dir() as temp_dir:
cmd = ['gsutil', '-m', 'cp', gcs_url, temp_dir.pathname]
try:
self.host.create_process(cmd).check_call()
except subprocess.CalledProcessError:
self.host.error(
'Failed to download corpus from GCS.',
'You can skip downloading from GCS with the "--local" flag.'
)
return self.add_from_host(temp_dir.pathname)
def measure(self):
"""Returns the number of corpus elements and corpus size as a pair."""
total_num = 0
total_size = 0
for nspath in self.nspaths:
sizes = self.ns.ls(nspath)
total_num += len(sizes)
total_size += sum(sizes.values())
return (total_num, total_size)