#!/usr/bin/env bash
# AddressSanitizer regression for the BPSec security-context value display path.
#
# bpsec_scvm_byIdIdxFind() returns -1 for a value whose (id, type) is not in the
# security context's value map.  bpsec_scv_smListPrint() used that -1 directly
# as an array index -- reading scvm[-1] out of bounds and calling its function
# pointer -- the same defect fixed on the deserialize and serialize paths
# (GHSA-74r8-6qvf-vj4f).  The fix skips a value whose lookup fails.
#
# The harness builds a one-element value list whose id is not in the map and
# calls bpsec_scv_smListPrint().  On the vulnerable code ASan reports the
# out-of-bounds read; on the fixed code the value is skipped and the call
# returns cleanly.  It uses the private bpsec headers, so it compiles against
# the ION source tree ($IONDIR), and it allocates from ION working memory, so a
# minimal node is started for it to attach to.
#
# Meaningful only on an --enable-asan build; skips otherwise.
set -u
cd "$(dirname "$0")"

trap 'killm f' EXIT

if [ -z "${CC:-}" ]; then
    command -v gcc >/dev/null 2>&1 && CC=gcc || CC=cc
fi

echo "########################################"
echo "NAME: asan-bpsec-scvalue-print"
echo "PURPOSE: Verify the BPSec SC-value display path does not use a failed map"
echo "         lookup as an array index, under AddressSanitizer."
echo "########################################"

# Need the ION source tree for the private bpsec headers.
SRC="${IONDIR:-}"
[ -z "$SRC" ] && SRC=$(cd ../.. && pwd)
if [ ! -f "$SRC/bpv7/bpsec/sci/sc_value.h" ]; then
    echo "SKIP: ION source tree not found (need private bpsec headers)."
    exit 2
fi

# Locate libbp (installed or build tree) and require an ASan build.
export PKG_CONFIG_PATH="$SRC:${PKG_CONFIG_PATH:-}"
LIBDIR=$(pkg-config --variable=libdir ion 2>/dev/null)
LIBBP=""
for d in "$LIBDIR" /usr/local/lib "$SRC/.libs"; do
    [ -z "$d" ] && continue
    LIBBP=$(find "$d"/* -prune -name "libbp.so*" 2>/dev/null | head -n1)
    [ -n "$LIBBP" ] && { FOUNDLIBDIR="$d"; break; }
done
if [ -z "$LIBBP" ] || ! nm -D "$LIBBP" 2>/dev/null | grep -q "__asan"; then
    echo "SKIP: ION not built with --enable-asan (libbp is not instrumented)."
    exit 2
fi

ION_CFLAGS=$(pkg-config --cflags ion 2>/dev/null)
ION_LIBS="-L$FOUNDLIBDIR -lbp -lici -pthread"

INC="-I$SRC/ici/include -I$SRC/bpv7/include -I$SRC/bpv7/library"
INC="$INC -I$SRC/bpv7/bpsec/sci -I$SRC/bpv7/bpsec/utils"
INC="$INC -I$SRC/bpv7/bpsec/policy -I$SRC/bpv7/bpsec/instr"

BIN=$(mktemp /tmp/scvalue_print_test.XXXXXX)
# shellcheck disable=SC2086
if ! $CC -fsanitize=address -g -O1 $INC $ION_CFLAGS scvalue_print_oob_test.c \
        $ION_LIBS -o "$BIN" 2>compile.log; then
    echo "FAIL: could not compile the harness."
    cat compile.log
    rm -f "$BIN"
    exit 1
fi

export ION_NODE_LIST_DIR=$PWD
rm -f ion_nodes asanrep.*
killm f >/dev/null 2>&1
if ! ionadmin node.ionrc >start.log 2>&1; then
    echo "FAIL: could not start the ION node."; cat start.log; rm -f "$BIN"; exit 1
fi

LD_LIBRARY_PATH="$FOUNDLIBDIR:/usr/local/lib:${LD_LIBRARY_PATH:-}" \
    ASAN_OPTIONS="log_path=$PWD/asanrep:detect_leaks=0:abort_on_error=1:halt_on_error=1" \
    "$BIN"
RETVAL=$?
rm -f "$BIN"
killm f >/dev/null 2>&1

REPORTS=$(ls asanrep.* 2>/dev/null | wc -l)
if [ "$REPORTS" -ne 0 ]; then
    echo "FAIL: AddressSanitizer reported a memory error:"
    grep -hE "ERROR: AddressSanitizer|SUMMARY|smListPrint|sc_value.c" asanrep.* 2>/dev/null | head
    exit 1
fi
if [ "$RETVAL" -eq 2 ]; then
    echo "SKIP: harness could not attach to ION."
    exit 2
fi
if [ "$RETVAL" -ne 0 ]; then
    echo "FAIL: harness exited $RETVAL."
    exit 1
fi

echo "SUCCESS: the unrecognized SC value was skipped without an out-of-bounds access."
exit 0
