#!/bin/bash
# Check whether our preload library has any unexpected public symbol.
# Check that all our overridden methods actually exist in libc, libdl or libpthread.
# Check that for each overridden method we also override its fortified
# counterpart, if that exists at all.

# Copyright (c) 2022 Firebuild Inc.
# All rights reserved.
# Free for personal use and commercial trial.
# Non-trial commercial use requires licenses available from https://firebuild.com.
# Modification and redistribution are permitted, but commercial use of
# derivative works is subject to the same requirements of this license
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

# Figure out CMAKE_BINARY_DIR
if [ $# -lt 1 ]; then
  echo "Usage: test_symbols binary_dir" >&2
  exit 1
fi
binary_dir="$1"

status=0

# Additional allowed public symbols that are not auto-generated into gen_list.txt.
additional_allowed_symbols=""

# Extract and sort the public symbols of our library,
# filtering out the ones added by gcov (__gcov* and mangle_path)
nm -D "$binary_dir/src/interceptor/libfirebuild.so" | \
  grep -v ' [Uvw] ' | \
  cut -d' ' -f3- | \
  grep -v '^__gcov_' | \
  grep -v '^mangle_path$' | \
  LC_ALL=C sort | uniq > public-symbols.txt

# Gather and sort the allowed public symbols
{ cat "$binary_dir/src/interceptor/gen_list.txt"; echo "$additional_allowed_symbols"; } | \
  LC_ALL=C sort > public-symbols-allowed.txt

# Get the list of unexpected ones
unexpected=$(LC_ALL=C comm -23 public-symbols.txt public-symbols-allowed.txt)

# Report the list of unexpected ones
if [ -z "$unexpected" ]; then
  echo "No unexpected public symbols"
else
  echo "Unexpected public symbols:"
  echo "$unexpected" | sed 's/^\(.*\)$/  \1/'
  status=1
fi

# Get the list of libc, libdl and libpthread symbols.

# Some symbols have been removed from or introduced recently to glibc
known_extra="
_Fork
arc4random
arc4random_buf
arc4random_uniform
execveat
stime
ustat
stat
stat64
lstat
lstat64
fstat
fstat64
fstatat
fstatat64
mknod
mknodat
closefrom
close_range
pidfd_open
posix_spawn_file_actions_addclosefrom_np
shm_open
shm_unlink
"
# TODO(rbalint) provide properly versioned symbols in libfirebuild
libs=$(LD_PRELOAD=libpthread.so.0 ldd "$binary_dir/src/interceptor/libfirebuild.so" | grep -E 'lib(c|dl|pthread).so' | cut -d' ' -f3)
(nm -D $libs | \
     grep ' [TWi] ' | \
     cut -d' ' -f3- ; \
     echo "$known_extra") | \
  sed 's/\(.*\)@@\(.*\)/\1@@\2\n\1/' | \
  LC_ALL=C sort > libc-symbols.txt

# Get the list of extra ones
extra=$(LC_ALL=C comm -23 public-symbols.txt libc-symbols.txt)

# Report the list of extra ones
if [ -z "$extra" ]; then
  echo "No unexpected overridden methods"
else
  echo "Overridden methods that do not exist in libc, libdl or libpthread:"
  echo "$extra" | sed 's/^\(.*\)$/  \1/'
  status=1
fi

# Get the list of libc, libdl and libpthread fortified symbols
grep '_chk$' < libc-symbols.txt > libc-fortify-symbols.txt

# Get the list of fortified libc symbols that we don't override, but override the non-fortified counterpart
fortify_missing=$(for chk in $(< libc-fortify-symbols.txt); do
  base="${chk%_chk}"
  base="${base#__}"
  if grep -Fqx "$base" public-symbols.txt && ! grep -Fqx "$chk" public-symbols.txt; then
    echo "$chk"
  fi
done)

# Report the non-overridden fortified symbols
if [ -z "$fortify_missing" ]; then
  echo "All expected fortified methods overridden"
else
  echo "Fortified methods not overridden:"
  echo "$fortify_missing" | sed 's/^\(.*\)$/  \1/'
  status=1
fi

exit $status
