#!/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=""

function check_function_class () {
    local fix=$1
    local class=$2
    local kind=$3
    local base
    # Get the list of libc, libdl and libpthread symbols of this class
                        case $kind in
                            "prefix")
                                grep ^${fix} < libc-symbols.txt > libc-${class}-symbols.txt
                                ;;
                            "postfix")
                                grep ${fix}'$' < libc-symbols.txt > libc-${class}-symbols.txt
                        esac

    # Get the list of libc symbols that we don't override, but override the
    # counterpart with the prefix/postfix
    local missing=$(for chk in $(< libc-${class}-symbols.txt); do
                        case $kind in
                            "prefix")
                                base="${chk#__}"
                                if grep -Fqx "$base" public-symbols.txt && ! grep -Fqx "$chk" public-symbols.txt; then
                                    echo "$chk"
                                fi
                                ;;
                            "postfix")
                                base="${chk%${fix}}"
                                base="${base#__}"
                                if grep -Fqx "$base" public-symbols.txt && ! grep -Fqx "$chk" public-symbols.txt; then
                                    echo "$chk"
                                fi
                                ;;
                        esac
                    done)

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

# Extract and sort the public symbols of our library,
# filtering out the ones added by gcov (__gcov* and mangle_path)
# and ones appearing with old toolchains(__bss_start, _edata, etc.)
nm -D "$binary_dir/src/interceptor/libfirebuild.so" | \
  grep -v ' [Uvw] ' | \
  cut -d' ' -f3- | \
  grep -v '^__gcov_' | \
  grep -v '^mangle_path$' | \
  egrep -v '^_(_bss_start|edata|end|fini|init)$' | \
  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="
__bss_start
_edata
_end
_fini
_init
_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

# Check the list of libc, libdl and libpthread fortified symbols
check_function_class "_chk" "fortified" "postfix"

# Check the *64 variants
check_function_class "64" "64bit" "postfix"

# Check the *_time64 variants
check_function_class "_time64" "time64" "postfix"

# Check the __* variants
check_function_class "__" "underscore" "prefix"

exit $status
