blob: fe569abcf8bad6fb9e124d30c2184a316ea935e8 [file] [log] [blame]
#!/system/bin/sh
# Copyright (C) 2018 The Android Open Source Project
#
# 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.
# Abort on any error
# Global configuration
FUZZER_BASES=( "/data/nativetest64/fuzzers" "/data/nativetest/fuzzers" )
WORK_BASE="/data/local/tmp/fuzz"
LAST_FUZZ="$WORK_BASE/last_session"
#------------------------------ HELPER FUNCTIONS ------------------------------
function die() {
echo "$@" >&2
exit 1
}
# Print the usage and exit.
# Optionally takes an extra string to print.
function usage() {
die "\
Usage: $0 [-a artifacts_path] [-c corpus] [-e engine] [-l logname] [-n new_corpus] [-w workdir] fuzzer [-- fuzzer_option...]
Run a fuzzing session.
All of the information on the most recent session is symlinked at $LAST_FUZZ,
which points to the most recent session's working directory.
The working directory is standardized to contain the following directories (or symlinks,
if non-default options are provided):
artifacts/ Crashing test cases and other fuzzer output
corpus/ Input corpus
corpus_new/ Mutated test cases which exercise coverage features not represented
in the input corpus.
fuzz.log Contents of stdout from the fuzzing session.
Arguments:
fuzzer Name of the fuzzer, e.g. cxa_demangle_fuzzer, or a full path to the fuzzer
fuzzer_option Fuzzer-specific arguments
Options:
-a Path where fuzzer artifacts (logs, timeouts, crashes) should be stored
(Default: \$WORK/artifacts)
-c Path where input corpus is stored. Must not be empty.
(Default: \$WORK/corpus)
-e Fuzzer engine to use. Defaults to libFuzzer.
-l Filename to use for the fuzzer log.
(Default: fuzz.log)
-n Path to store new corpus elements
(Default: \$WORK/corpus_new)
-w Work directory to use
(Default: $WORK_BASE/\$FUZZER)
Examples:
$ adb shell fuzz IOMX
$ adb shell fuzz -e honggfuzz IOMX
$ adb shell fuzz -w /sdcard IOMX
$ adb shell fuzz -c /sdcard/corpus IOMX
$ adb shell fuzz /data/my_fuzzer -- --foo --bar
$@"
}
# Create a symlink from $1 ==> $2, if $2 is specified.
# Otherwise, ensure $1 exists and is a directory.
function symlink_or_create() {
DIR=$1
TARGET=$2
if [ -n "$TARGET" ]; then
if [ ! -d "$TARGET" ]; then
die "Directory does not exist: $TARGET"
fi
rm -rf "$DIR"
ln -sv "$TARGET" "$DIR"
elif [ ! -d "$DIR" ]; then
mkdir -p "$DIR"
fi
}
# Ensure that there is "enough space" left on the device for
# the path provided.
function ensure_space() {
FREE_SPACE=$(df $1 | tail -1 | awk '{print $4}')
TEN_MEGABYTES=$((1024*10))
if [ $FREE_SPACE -lt $TEN_MEGABYTES ]; then
die "Not enough free space available at $(realpath $1):\n$(df -h $1)"
fi
}
# Ensure that the provided directory is not empty.
function ensure_not_empty() {
if [ "$(find -H $1 -type f | wc -l)" -eq "0" ]; then
die "$1 is empty"
fi
}
#--------------------------- CHECK SYSTEM VIABILITY ---------------------------
# Make sure ASAN and coverage work
if ! sanitizer-status asan cov &>/dev/null; then
# repeat the command to show the output
die "Sanitizer Checks Failed!\n$(sanitizer-status)"
fi
#------------------------------ CHECK ARGUMENTS -------------------------------
# Determine what fuzzer we want to run, and ensure that it's on the device
OPT_ARTIFACTS=""
OPT_CORPUS=""
OPT_CORPUS_NEW=""
OPT_ENGINE="libFuzzer"
OPT_LOG="fuzz.log"
OPT_WORKDIR=""
while getopts "a:c:e:l:n:w:" o; do
case "${o}" in
a) OPT_ARTIFACTS="${OPTARG}" ;;
c) OPT_CORPUS="${OPTARG}" ;;
e) OPT_ENGINE="${OPTARG}" ;;
l) OPT_LOG="${OPTARG}" ;;
n) OPT_CORPUS_NEW="${OPTARG}" ;;
w) OPT_WORKDIR="${OPTARG}" ;;
*) usage ;;
esac
done
shift $((OPTIND-1))
if [ $# -lt 1 ]; then
usage "\nMissing arguments: fuzzer"
fi
if [ -e $1 ]; then
FUZZER_BIN="$1"
FUZZER="$(basename $FUZZER_BIN)"
else
FUZZER="${1%_fuzzer}"
for FUZZER_BASE in "${FUZZER_BASES[@]}"; do
FUZZER_BIN="${FUZZER_BASE}/${OPT_ENGINE}/${FUZZER}_fuzzer/${FUZZER}_fuzzer"
[ -e "$FUZZER_BIN" ] && break
done
fi
shift
if [ ! -e "$FUZZER_BIN" ]; then
die "Invalid fuzzer path $FUZZER_BIN: File does not exist"
fi
#------------------------- CREATE DIRECTORY STRUCTURE -------------------------
# First set up the root work directory.
#
# This directory is the default location where the corpus/, corpus_new/, and
# artifacts/ directories will be created.
#
# We also create symlinks here if any of those options are provided, so that
# the same directory structure is always available, and any external utilities
# can easily find the last fuzzing session's data.
WORK_ROOT="$WORK_BASE/$FUZZER"
symlink_or_create "$WORK_ROOT" "$OPT_WORKDIR"
WORK_ROOT="${OPT_WORKDIR:-$WORK_ROOT}"
# Change into the work root, in case any of the OPT_XXX paths are relative.
cd "$WORK_ROOT"
# Create the rest of the directory structure
ARTIFACTS="$WORK_ROOT/artifacts"
CORPUS="$WORK_ROOT/corpus"
CORPUS_NEW="$WORK_ROOT/corpus_new"
symlink_or_create "$ARTIFACTS" "$OPT_ARTIFACTS"
symlink_or_create "$CORPUS" "$OPT_CORPUS"
symlink_or_create "$CORPUS_NEW" "$OPT_CORPUS_NEW"
# Update the environment variables so that the "real" paths show up in
# the command invocation.
ARTIFACTS="${OPT_ARTIFACTS:-$ARTIFACTS}"
CORPUS="${OPT_CORPUS:-$CORPUS}"
CORPUS_NEW="${OPT_CORPUS_NEW:-$CORPUS_NEW}"
# Check the contents of the corpus aren't empty, this indicates a user error.
ensure_not_empty "$CORPUS"
# Ensure that there's room to grow the corpus / dump artifacts.
ensure_space "$CORPUS_NEW"
ensure_space "$ARTIFACTS"
#------------------------- UPDATE ENVIRONMENT OPTIONS -------------------------
# Set up the ASAN_OPTIONS for optimal everything
# Note that we are only appending options, so that we do not override the
# default-at-boot ASAN_OPTIONS (e.g include=/system/asan.options).
ASAN_OPTIONS+=:coverage=1
ASAN_OPTIONS+=:atexit=1
# ASAN_OPTIONS+=:verbosity=2
ASAN_OPTIONS+=:print_cmdline=1
ASAN_OPTIONS+=:print_stats=1
ASAN_OPTIONS+=:print_legend=1
ASAN_OPTIONS+=:print_scariness=1
ASAN_OPTIONS+=:log_path=/dev/null
export ASAN_OPTIONS="$ASAN_OPTIONS"
echo "ASAN_OPTIONS=$ASAN_OPTIONS"
#---------------------------- BUILD FUZZER COMMAND ----------------------------
# Based on the fuzzer engine selected, build up the command line.
case "${OPT_ENGINE}" in
libFuzzer)
# NOTE: We use '-jobs=-1' to get libFuzzer to fuzz forever.
set -A FUZZ_CMD -- \
"$FUZZER_BIN" \
-artifact_prefix="$ARTIFACTS" \
-print_coverage=1 \
-detect_leaks=0 \
-jobs=-1 \
"$CORPUS_NEW" \
"$CORPUS" \
"$@"
;;
honggfuzz)
set -A FUZZ_CMD -- \
honggfuzz \
--persistent \
--sanitizers \
--tmout_sigvtalrm \
--workspace "$ARTIFACTS" \
--input "$CORPUS_NEW" \
--covdir_new "$CORPUS_NEW" \
-- "$FUZZER_BIN" "$@"
;;
*)
die "Unknown fuzzer engine $FUZZER_TYPE"
;;
esac
#--------------------------------- RUN FUZZER ---------------------------------
# Set up a symlink for the "last fuzz session" so that we can easily find it.
ln -svf "$WORK_ROOT" "$LAST_FUZZ"
# Change into the artifacts directory, so that anything
# the fuzzer emits to $PWD will also be captured.
cd "$ARTIFACTS"
echo "Running fuzzer: ${FUZZ_CMD[@]}"
echo "------------------------------"
${FUZZ_CMD[@]} | tee "${OPT_LOG}"