#!/bin/sh
#
# american fuzzy lop++ - QEMU build script
# --------------------------------------
#
# Originally written by Andrew Griffiths <agriffiths@google.com> and
#                       Michal Zalewski
#
# TCG instrumentation and block chaining support by Andrea Biondo
#                                    <andrea.biondo965@gmail.com>
#
# QEMU 5+ port, TCG thread-safety, CompareCoverage and NeverZero
# counters by Andrea Fioraldi <andreafioraldi@gmail.com>
#
# Copyright 2015, 2016, 2017 Google Inc. All rights reserved.
# Copyright 2019-2023 AFLplusplus Project. All rights reserved.
#
# 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:
#
#   https://www.apache.org/licenses/LICENSE-2.0
#
# This script downloads, patches, and builds a version of QEMU with
# minor tweaks to allow non-instrumented binaries to be run under
# afl-fuzz. 
#
# The modifications reside in patches/*. The standalone QEMU binary
# will be written to ../afl-qemu-trace.
#

QEMUAFL_VERSION="$(cat ./QEMUAFL_VERSION)"

echo "================================================="
echo "           QemuAFL build script"
echo "================================================="
echo

echo "[*] Performing basic sanity checks..."

if [ ! "`uname -s`" = "Linux" ]; then

  echo "[-] Error: QEMU instrumentation is supported only on Linux."
  exit 0

fi

if [ ! -f "../config.h" ]; then

  echo "[-] Error: key files not found - wrong working directory?"
  exit 1

fi

if [ ! -f "../afl-showmap" ]; then

  echo "[-] Error: ../afl-showmap not found - compile AFL first!"
  exit 1

fi

if echo "$CC" | grep -qF /afl-; then

  echo "[-] Error: do not use afl-gcc or afl-clang to compile this tool."
  exit 1

fi

echo "[+] All checks passed!"

echo "[*] Making sure qemuafl is checked out"

git status 1>/dev/null 2>/dev/null
if [ $? -eq 0 ]; then
  echo "[*] initializing qemuafl submodule"
  git submodule init || exit 1
  git submodule update ./qemuafl 2>/dev/null # ignore errors
else
  echo "[*] cloning qemuafl"
  test -d qemuafl/.git || {
    CNT=1
    while [ '!' -d qemuafl/.git -a "$CNT" -lt 4 ]; do
      echo "Trying to clone qemuafl (attempt $CNT/3)"
      git clone --depth 1 https://github.com/AFLplusplus/qemuafl
      CNT=`expr "$CNT" + 1`
    done
  }
fi

test -e qemuafl/.git || { echo "[-] Not checked out, please install git or check your internet connection." ; exit 1 ; }
echo "[+] Got qemuafl."

cd "qemuafl" || exit 1
if [ -n "$NO_CHECKOUT" ]; then
  echo "[*] Skipping checkout to $QEMUAFL_VERSION"
else
  echo "[*] Checking out $QEMUAFL_VERSION"
  sh -c 'git stash' 1>/dev/null 2>/dev/null
  git pull
  git checkout "$QEMUAFL_VERSION" || echo Warning: could not check out to commit $QEMUAFL_VERSION
fi

echo "[*] Making sure imported headers matches"
cp "../../include/config.h" "./qemuafl/imported/" || exit 1
cp "../../include/cmplog.h" "./qemuafl/imported/" || exit 1
cp "../../include/snapshot-inl.h" "./qemuafl/imported/" || exit 1
cp "../../include/types.h" "./qemuafl/imported/" || exit 1

if [ -n "$HOST" ]; then
  echo "[+] Configuring host architecture to $HOST..."
  CROSS_PREFIX=$HOST-
else
  CROSS_PREFIX=
fi

echo "[*] Configuring QEMU for $CPU_TARGET..."

ORIG_CPU_TARGET="$CPU_TARGET"

if [ "$ORIG_CPU_TARGET" = "" ]; then
  CPU_TARGET="`uname -m`"
  test "$CPU_TARGET" = "i686" && CPU_TARGET="i386"
  test "$CPU_TARGET" = "arm64v8" && CPU_TARGET="aarch64"
  case "$CPU_TARGET" in 
    *arm*)
      CPU_TARGET="arm"
      ;;
  esac
fi

echo "Building for CPU target $CPU_TARGET"

# --enable-pie seems to give a couple of exec's a second performance
# improvement, much to my surprise. Not sure how universal this is..
QEMU_CONF_FLAGS=" \
  --audio-drv-list= \
  --disable-blobs \
  --disable-bochs \
  --disable-brlapi \
  --disable-bsd-user \
  --disable-bzip2 \
  --disable-cap-ng \
  --disable-cloop \
  --disable-curl \
  --disable-curses \
  --disable-dmg \
  --disable-fdt \
  --disable-gcrypt \
  --disable-glusterfs \
  --disable-gnutls \
  --disable-gtk \
  --disable-guest-agent \
  --disable-iconv \
  --disable-libiscsi \
  --disable-libnfs \
  --disable-libssh \
  --disable-libusb \
  --disable-linux-aio \
  --disable-live-block-migration \
  --disable-lzo \
  --disable-nettle \
  --disable-numa \
  --disable-opengl \
  --disable-parallels \
  --disable-plugins \
  --disable-qcow1 \
  --disable-qed \
  --disable-rbd \
  --disable-rdma \
  --disable-replication \
  --disable-sdl \
  --disable-seccomp \
  --disable-sheepdog \
  --disable-smartcard \
  --disable-snappy \
  --disable-spice \
  --disable-system \
  --disable-tools \
  --disable-tpm \
  --disable-usb-redir \
  --disable-vde \
  --disable-vdi \
  --disable-vhost-crypto \
  --disable-vhost-kernel \
  --disable-vhost-net \
  --disable-vhost-scsi \
  --disable-vhost-user \
  --disable-vhost-vdpa \
  --disable-vhost-vsock \
  --disable-virglrenderer \
  --disable-virtfs \
  --disable-vnc \
  --disable-vnc-jpeg \
  --disable-vnc-png \
  --disable-vnc-sasl \
  --disable-vte \
  --disable-vvfat \
  --disable-xen \
  --disable-xen-pci-passthrough \
  --disable-xfsctl \
  --target-list="${CPU_TARGET}-linux-user" \
  --without-default-devices \
  "

if [ -n "${CROSS_PREFIX}" ]; then

  QEMU_CONF_FLAGS="$QEMU_CONF_FLAGS --cross-prefix=$CROSS_PREFIX"

fi

if [ "$STATIC" = "1" ]; then

  echo Building STATIC binary

  # static PIE causes https://github.com/AFLplusplus/AFLplusplus/issues/892
  QEMU_CONF_FLAGS="$QEMU_CONF_FLAGS \
    --static --disable-pie \
    --extra-cflags=-DAFL_QEMU_STATIC_BUILD=1 \
    "

else

  QEMU_CONF_FLAGS="${QEMU_CONF_FLAGS} --enable-pie "

fi

if [ "$DEBUG" = "1" ]; then

  echo Building DEBUG binary

  # --enable-gcov might go here but incurs a mesonbuild error on meson
  # versions prior to 0.56:
  # https://github.com/qemu/meson/commit/903d5dd8a7dc1d6f8bef79e66d6ebc07c
  QEMU_CONF_FLAGS="$QEMU_CONF_FLAGS \
    --disable-strip \
    --enable-debug \
    --enable-debug-info \
    --enable-debug-mutex \
    --enable-debug-stack-usage \
    --enable-debug-tcg \
    --enable-qom-cast-debug \
    --enable-werror \
    "

else

  QEMU_CONF_FLAGS="$QEMU_CONF_FLAGS \
    --disable-debug-info \
    --disable-debug-mutex \
    --disable-debug-tcg \
    --disable-qom-cast-debug \
    --disable-stack-protector \
    --disable-werror \
    --disable-docs \
    "

fi

if [ "$PROFILING" = "1" ]; then

  echo Building PROFILED binary

  QEMU_CONF_FLAGS="$QEMU_CONF_FLAGS \
    --enable-gprof \
    --enable-profiler \
    "

fi

# shellcheck disable=SC2086
./configure $QEMU_CONF_FLAGS || exit 1

echo "[+] Configuration complete."

echo "[*] Attempting to build QEMU (fingers crossed!)..."

make -j$(nproc) || exit 1

echo "[+] Build process successful!"

echo "[*] Copying binary..."

cp -f "build/${CPU_TARGET}-linux-user/qemu-${CPU_TARGET}" "../../afl-qemu-trace" || exit 1

cd ..
ls -l ../afl-qemu-trace || exit 1

echo "[+] Successfully created '../afl-qemu-trace'."

if [ "$ORIG_CPU_TARGET" = "" ]; then

  echo "[*] Testing the build..."

  cd ..

  make >/dev/null || exit 1

  cc test-instr.c -o test-instr || exit 1

  unset AFL_INST_RATIO
  export ASAN_OPTIONS=detect_leaks=0

  echo "[*] Comparing two afl-showmap -Q outputs..."
  echo 0 | ./afl-showmap -m none -Q -q -o .test-instr0 ./test-instr || exit 1
  echo 1 | ./afl-showmap -m none -Q -q -o .test-instr1 ./test-instr || exit 1

  rm -f test-instr

  cmp -s .test-instr0 .test-instr1
  DR="$?"

  rm -f .test-instr0 .test-instr1

  if [ "$DR" = "0" ]; then

    echo "[-] Error: afl-qemu-trace instrumentation doesn't seem to work!"
    exit 1

  fi

  echo "[+] Instrumentation tests passed. "
  echo "[+] All set, you can now use the -Q mode in afl-fuzz!"

  cd qemu_mode || exit 1

else

  echo "[!] Note: can't test instrumentation when CPU_TARGET set."
  echo "[+] All set, you can now (hopefully) use the -Q mode in afl-fuzz!"

fi

ORIG_CROSS="$CROSS"

if [ "$ORIG_CROSS" = "" ]; then
  CROSS=$CPU_TARGET-linux-gnu-gcc
  if ! command -v "$CROSS" > /dev/null
  then # works on Arch Linux
    CROSS=$CPU_TARGET-pc-linux-gnu-gcc
  fi
  if ! command -v "$CROSS" > /dev/null && [ "$CPU_TARGET" = "i386" ]
  then
    CROSS=i686-linux-gnu-gcc
    if ! command -v "$CROSS" > /dev/null
    then # works on Arch Linux
      CROSS=i686-pc-linux-gnu-gcc
    fi
    if ! command -v "$CROSS" > /dev/null && [ "`uname -m`" = "x86_64" ]
    then # set -m32
      test "$CC" = "" && CC="gcc"
      CROSS="$CC"
      CROSS_FLAGS=-m32
    fi
  fi
fi

if ! command -v "$CROSS" > /dev/null ; then
  if [ "$CPU_TARGET" = "$(uname -m)" ] ; then
    echo "[+] Building AFL++ qemu support libraries with CC=$CC"
    echo "[+] Building libcompcov ..."
    make -C libcompcov && echo "[+] libcompcov ready"
    echo "[+] Building unsigaction ..."
    make -C unsigaction && echo "[+] unsigaction ready"
    echo "[+] Building fastexit ..."
    make -C fastexit && echo "[+] fastexit ready"
    echo "[+] Building libqasan ..."
    make -C libqasan && echo "[+] libqasan ready"
    echo "[+] Building qemu libfuzzer helpers ..."
    make -C ../utils/aflpp_driver
  else
    echo "[!] Cross compiler $CROSS could not be found, cannot compile libcompcov libqasan and unsigaction"
  fi
else
  echo "[+] Building AFL++ qemu support libraries with CC=\"$CROSS $CROSS_FLAGS\""
  echo "[+] Building libcompcov ..."
  make -C libcompcov CC="$CROSS $CROSS_FLAGS" && echo "[+] libcompcov ready"
  echo "[+] Building unsigaction ..."
  make -C unsigaction CC="$CROSS $CROSS_FLAGS" && echo "[+] unsigaction ready"
  echo "[+] Building fastexit ..."
  make -C fastexit CC="$CROSS $CROSS_FLAGS" && echo "[+] fastexit ready"
  echo "[+] Building libqasan ..."
  make -C libqasan CC="$CROSS $CROSS_FLAGS" && echo "[+] libqasan ready"
fi

echo "[+] All done for qemu_mode, enjoy!"

exit 0
