#!/usr/bin/env sh
#
#   honggfuzz stackhash blocklist file create script
#   -----------------------------------------
#
#   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.

set -e # fail on unhandled error
set -u # fail on undefined variable
#set -x # debug

readonly tmpFile=$(pwd)/.hf.bl.txt
declare -a sysTools=("perl" "cut" "sort" "paste" "wc" "tr" "cat")

usage() {
cat <<_EOF

  Usage: $(basename $0) [options]
    OPTIONS:
      -i|--input   : input crash(es) directory / file
      -B|--bl-file : output file to save found hashes (merge if exists)
      -e|--ext     : file extension of fuzzer files (e.g. fuzz)
      -a|--arch    : arch fuzzer have run against ('MAC' or 'LINUX')

    INFO:
      * Blacklist file sort mode only requires [-B/--bl-file] argument
      * Hashes gather mode requires all argument to be set
_EOF
  exit 1
}

command_exists () {
    type "$1" &> /dev/null ;
}

# Check that system tools exist
for i in "${sysTools[@]}"
do
  if ! command_exists $i; then
    echo "[-] '$i' command not found"
    exit 1
  fi
done

INPUT_DIR=""
BL_FILE=""
FILE_EXT=""
ARCH=""

nArgs=$#
while [[ $# > 1 ]]
do
  arg="$1"
  case $arg in
    -i|--input)
      INPUT_DIR="$2"
      shift
      ;;
    -B|--bl-file)
      BL_FILE="$2"
      shift
      ;;
    -e|--ext)
      FILE_EXT="$2"
      shift
      ;;
    -a|--arch)
      ARCH="$2"
      shift
      ;;
    *)
      echo "[-] Invalid argument '$1'"
      usage
      ;;
  esac
  shift
done

gatherMode=false

# Sort only mode
if [[ "$BL_FILE" == "" ]]; then
  echo "[-] Missing blocklist file"
  usage
fi

# Hashes gather mode
if [ $nArgs -gt 2 ]; then
  if [[ "$INPUT_DIR" == "" || ! -e "$INPUT_DIR" ]]; then
    echo "[-] Missing or invalid input directory"
    usage
  fi

  if [[ "$FILE_EXT" == "" ]]; then
    echo "[-] Missing file extension"
    usage
  fi

  if [[ "$ARCH" != "MAC" && "$ARCH" != "LINUX" ]]; then
    echo "[-] Invalid architecture, expecting 'MAC' or 'LINUX'"
    usage
  fi

  if [[ "$ARCH" == "LINUX" ]]; then
    STACKHASH_FIELD=5
  elif [[ "$ARCH" == "MAC" ]]; then
    STACKHASH_FIELD=6
  else
    echo "[-] Unsupported architecture"
    exit 1
  fi
  gatherMode=true
fi

# save old data
if [ -f $BL_FILE ]; then
  cat $BL_FILE > $tmpFile
  oldCount=$(cat $BL_FILE | wc -l | tr -d " ")
else
  oldCount=0
fi

if $gatherMode; then
  echo "[*] Processing files from '$INPUT_DIR' ..."
  find $INPUT_DIR -type f -iname "*.$FILE_EXT" | while read -r FILE
  do
    fileName=$(basename $FILE)
    if ! echo $fileName | grep -qF ".STACK."; then
      echo "[!] Skipping '$FILE'"
      continue
    fi
    stackHash=$(echo $fileName | cut -d '.' -f$STACKHASH_FIELD)

    # We don't want to lose crashes where unwinder failed
    if [[ "$stackHash" != "0" && ! "$stackHash" =~ ^badbad.* ]]; then
      echo $stackHash >> $tmpFile
    fi
  done
fi

# sort hex values
echo "[*] Sorting blocklist file entries"
perl -lpe '$_=hex' $tmpFile | \
paste -d" " - $tmpFile  | sort -nu | cut -d" " -f 2- \
> $BL_FILE

entries=$(cat $BL_FILE | wc -l | tr -d " ")
echo "[*] $BL_FILE contains $entries blocklisted stack hashes"

rm $tmpFile
