Skip to content
Snippets Groups Projects
Commit d4efbf4c authored by Zach Riggle's avatar Zach Riggle
Browse files

Make the 'fuzz' script more robust

Adds support for easy location of the most recent fuzzing session.

Adds support for command-line options to override the default
locations of the various directories.

Also expose the script to AOSP by moving it to //tools/security

Change-Id: Iab104a69251548c6bbff3c7de77ba37bcfe087ab
parent fdda0f4c
No related branches found
No related tags found
No related merge requests found
cc_prebuilt_binary {
name: "fuzz",
srcs: ["fuzz"]
}
#!/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_BASE="/data/nativetest64/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}"
FUZZER_BIN="${FUZZER_BASE}/${OPT_ENGINE}/${FUZZER}_fuzzer"
if [ ! -e "$FUZZER_BIN" ]; then
die "Invalid fuzzer name ${FUZZER}: ($FUZZER_BIN does not exist)"
fi
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}"
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment