#!/bin/sh -ef
#
# verify-elf - verify ELF objects.
#
# Copyright (C) 2002-2011  Dmitry V. Levin <ldv@altlinux.org>
# Copyright (C) 2009  Alexey Tourbin <at@altlinux.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#

. /usr/lib/rpm/functions
ValidateBuildRoot

elf_ldd='/usr/lib/rpm/ldd'

lookup_path()
{
	local d dir path found=
	dir="$1" && shift
	path="$1" && shift
	for d in $(printf %s "$path" |tr : ' '); do
		[ "$d" = "$dir" ] || continue
		found="$d"
		break
	done
	[ -n "$found" ] && return 0 || return 1
}

rc=0

get_verify_policy()
{
	local name value
        name="VERIFY_ELF_$1" && shift
	eval "value=\"\$$name\""
	[ -n "$value" ] || value=normal
	printf %s "$value"
}

for m in ARCH FHS LFS LINT RPATH STACK TEXTREL UNRESOLVED; do
	[ "${VERIFY_ELF_ANY-}" != strict ] || break
	case "$(get_verify_policy "$m")" in
		strict) VERIFY_ELF_ANY=strict ;;
		normal|'') VERIFY_ELF_ANY=normal ;;
		relaxed) [ "${VERIFY_ELF_ANY-}" = normal ] || VERIFY_ELF_ANY=relaxed ;;
		*) [ -n "${VERIFY_ELF_ANY-}" ] || VERIFY_ELF_ANY=no ;;
	esac
done

error_strict()
{
	local method filename prefix
	method="$1"; shift
	filename="$1"; shift
	case "$(get_verify_policy "$method")" in
		strict) prefix=ERROR; rc=1 ;;
		*) prefix=WARNING ;;
	esac
	Info "$prefix: $filename: $*"
}

error_normal()
{
	local method filename prefix
	method="$1"; shift
	filename="$1"; shift
	case "$(get_verify_policy "$method")" in
		strict|normal) prefix=ERROR; rc=1 ;;
		*) prefix=WARNING ;;
	esac
	Info "$prefix: $filename: $*"
}

error_relaxed()
{
	local method filename prefix
	method="$1"; shift
	filename="$1"; shift
	case "$(get_verify_policy "$method")" in
		strict|normal|relaxed) prefix=ERROR; rc=1 ;;
		*) prefix=WARNING ;;
	esac
	Info "$prefix: $filename: $*"
}

verify_rpath()
{
	local f rpath
	f="$1"; shift
	rpath="$1"; shift

	[ -n "$rpath" ] || return 0

	local found=
	if [ -z "${rpath##:*}" ]; then
		error_relaxed RPATH "$f" "RPATH starts with \":\": $rpath"
		found=1
	elif [ -z "${rpath%%*:}" ]; then
		error_relaxed RPATH "$f" "RPATH ends with \":\": $rpath"
		found=1
	elif [ -z "${rpath##*::*}" ]; then
		error_relaxed RPATH "$f" "RPATH contains \"::\": $rpath"
		found=1
	elif [ -z "${rpath##*:*}" ]; then
		error_strict RPATH "$f" "RPATH contains several entries: $rpath"
		found=1
	fi

	if [ $(printf "%s" "$rpath" | LC_ALL=C tr -d '[ -~]' | wc -c) != 0 ]; then
		error_relaxed RPATH "$f" "RPATH contains a non-ascii entry: $rpath"
		found=1
	else
		for p in $(printf "%s" "$rpath" | tr : ' '); do
			if [ -z "${p#\$ORIGIN}" -o -z "${p##\$ORIGIN/*}" -o \
			     -z "${p#/lib}" -o -z "${p##/lib/*}" -o \
			     -z "${p#/lib64}" -o -z "${p##/lib64/*}" -o \
			     -z "${p#/usr/lib}" -o -z "${p##/usr/lib/*}" -o \
			     -z "${p#/usr/lib64}" -o -z "${p##/usr/lib64/*}" ]; then
				continue
			fi
			if [ -z "${p##/*}" ]; then
				error_normal RPATH "$f" "RPATH contains illegal absolute entry \"$p\": $rpath"
			else
				error_relaxed RPATH "$f" "RPATH contains illegal relative entry \"$p\": $rpath"
			fi
			found=1
		done
	fi

	for p in $RPM_BUILD_ROOT $RPM_BUILD_DIR $RPM_SOURCE_DIR /lib/../lib64; do
		if printf %s "$rpath" | grep -Fqs "$p"; then
			error_relaxed RPATH "$f" "RPATH contains illegal entry \"$p\": $rpath"
			found=1
		fi
	done

	for p in /lib /lib64 /usr/lib /usr/lib64; do
		if printf %s " $rpath " | tr : ' ' | grep -Fqs " $p "; then
			error_normal RPATH "$f" "RPATH contains standard library path \"$p\": $rpath"
			found=1
		fi
	done

	[ -n "$found" ] ||
		error_strict RPATH "$f" "RPATH entry found: $rpath"
}

verify_unresolved()
{
	local f fname rpath ldd_info ldd_rc
	f="$1"; shift
	fname="$1"; shift
	rpath="$1"; shift

	if [ -n "$rpath" ]; then
		rpath="$rpath $RPM_VERIFY_ELF_LDD_RPATH"
	else
		rpath="$RPM_VERIFY_ELF_LDD_RPATH"
	fi
	rpath="$(printf %s "$rpath" |
		tr -s '[:space:]' '\n' |
		grep -v '^$' |
		LANG=C uniq |
		sed -e "s|^|$RPM_BUILD_ROOT&|" |
		tr -s '[:space:]' : |
		sed -e 's/^:\+//; s/:\+$//')"

	if ! ldd_info="$("$elf_ldd" --undefined -- "$f" "$rpath" 2>&1)"; then
		printf >&2 '%s\n' "$ldd_info"
		error_relaxed UNRESOLVED "$f" 'ldd failed'
		return
	fi

	case "$VERIFY_ELF_UNRESOLVED" in
		no|relaxed)
			ldd_rc=0
			;;
		strict)
			ldd_rc=1
			;;
		*)
			if [ -z "${t##*ELF* executable*dynamically linked*}" ] ||
			   lookup_path "${fname%/*}" "$RPM_VERIFY_ELF_LDD_RPATH"; then
				ldd_rc=1
			else
				ldd_rc=0
			fi
			;;
	esac
	printf '%s\n' "$ldd_info" |
		awk -vrc="$ldd_rc" -vprog="$PROG" -vfname="$f" -- '
BEGIN {
        if (rc == "0")
                prefix="WARNING"
        else
                prefix="ERROR"
        errors=0
}
$2 == "=>" && $3 == "not" && $4 == "found" {
        lib=$1
        printf ("%s: %s: %s: not found: %s\n", prog, prefix, fname, lib)
        errors=1
}
$1 == "undefined" && $2 == "symbol:" {
        sym=$3
        lib=$4
        sub("^[(]", "", lib)
        sub("[)]$", "", lib)
        if (lib == fname) {
                printf ("%s: %s: %s: undefined symbol: %s\n", prog, prefix, fname, sym)
                errors=1
        }
}
END {
        if (rc != "0" && errors != 0)
                exit 1
}
		' >&2 && ldd_rc=0 || ldd_rc=1
	[ "$ldd_rc" = 0 ] || rc=1
}

verify_stack()
{
	local f objdump_info stack
	f="$1"; shift
	objdump_info="$1"; shift

	stack="$(printf %s "$objdump_info" |sed -ne 's/^[[:space:]]*STACK[[:space:]]\+\([^[:space:]]\+\).*/\1/p')"
	if [ -z "$stack" ]; then
		error_strict STACK "$f" 'STACK entry not found'
	elif [ "$stack" = on ]; then
		stack="$(printf %s "$objdump_info" |sed -ne 's/^[[:space:]]*STACK[[:space:]]\+\([^[:space:]]\+.*\)/\1/p')"
		error_strict STACK "$f" "found executable STACK entry: $stack"
	fi
}

find_elf_interp()
{
	local f
	f="$1"; shift

	readelf --wide --segments "$f" |
		sed -n 's,^[[:space:]]*\[Requesting program interpreter: \(/[^]]\+\)\]$,\1,p'
}

LFS_CFLAGS="$(getconf LFS_CFLAGS)"
non_lfs_funcs='/usr/lib/rpm/verify-elf-non-lfs-funcs.list'
dump_ld_config='/usr/lib/rpm/dump_ld_config'
default_elf_interp=

verify_lfs()
{
	[ -n "$LFS_CFLAGS" -a -s "$non_lfs_funcs" ] || return 0

	local f interp funcs
	f="$1"; shift

	[ -n "$default_elf_interp" ] ||
		default_elf_interp="$(find_elf_interp "$dump_ld_config")"

	if [ -z "$default_elf_interp" ]; then
		error_normal LFS "$dump_ld_config" 'program interpreter not found'
		default_elf_interp=/
	fi
	interp="$(find_elf_interp "$f")" || {
		error_normal LFS "$f" 'program interpreter not found'
		return
	}
	[ "$interp" = "$default_elf_interp" ] ||
		return 0

	funcs="$(readelf --wide --symbols "$f" |
		sed -n 's/^[[:space:]]*[0-9]\+:[[:space:]]\+[0-9a-f]\+[[:space:]]\+[0-9]\+[[:space:]]\+FUNC[[:space:]]\+[^[:space:]]\+[[:space:]]\+DEFAULT[[:space:]]\+UND[[:space:]]\+\([^@[:space:]]\+\)@.*/\1/p' |
		sort -u |
		comm -12 - "$non_lfs_funcs" |
		tr '\n' ' ')"
	funcs="${funcs%% }"
	[ -z "$funcs" ] ||
		error_normal LFS "$f" "uses non-LFS functions: $funcs"
}

run_eu()
{
	local prog="$1"; shift
	# Internally, eu-* use $ORIGIN to dlopen their backends.
	# Pass LD_ORIGIN_PATH to make them work without /proc.
	LD_ORIGIN_PATH=/usr/bin eu-$prog "$@"
}

VerifyELF()
{
	local f t objdump_info fname lint_info textrel
	f="$1"; shift

	if [ ! -f "$f" ]; then
		error_strict ANY "$f" 'file not available'
		return
	fi

	if ! t=$(file -b "$f"); then
		error_relaxed ANY "$f" 'file type not available'
		return
	fi

	if ! objdump_info=$(objdump -p "$f"); then
		error_normal ANY "$f" 'objdump failed'
		return
	fi

	fname="${f#$RPM_BUILD_ROOT}"
	fname="${fname#.}"

	if [ "$RPM_TARGET_ARCH" = noarch ]; then
		error_normal ARCH "$f" "ELF object for \"$RPM_TARGET_ARCH\" architecture"
	fi

	if [ -z "${fname##/usr/share/*}" -o -z "${fname##/etc/*}" ]; then
		error_normal FHS "$f" 'ELF object out of allowed directory tree'
	fi

	if ! lint_info=$(run_eu elflint --gnu-ld "$f" 2>&1); then
		printf '%s\n' "$lint_info" >&2
		error_normal LINT "$f" 'eu-elflint failed'
	fi

	verify_rpath "$f" "$(printf %s "$objdump_info" |awk '{if ($1=="RPATH") print $2}')"

	if [ -z "${t##*ELF* executable*}" -o -z "${t##*ELF* shared object*}" ]; then
		verify_stack "$f" "$objdump_info"
	fi

	textrel="$(printf %s "$objdump_info" |sed -ne 's/^[[:space:]]*TEXTREL[[:space:]]\+\([^[:space:]]\+\).*/\1/p')"
	if [ -n "$textrel" ]; then
		run_eu findtextrel "$f" 2>&1 |uniq >&2
		error_normal TEXTREL "$f" "TEXTREL entry found: $textrel"
	fi

	if [ -z "${t##*ELF* executable*dynamically linked*}" -o -z "${t##*ELF* shared object*}" ]; then
		verify_unresolved "$f" "$fname" \
			"$(printf %s "$objdump_info" |awk '{if ($1=="RPATH") print $2}' |tr -s : ' ' |sed -e "s|\$ORIGIN|${fname%/*}|g")"
		if [ -z "${t##*ELF 32-bit*}" ]; then
			verify_lfs "$f"
		fi
	fi
}

if [ $# -gt 0 ]; then
	for f; do
		VerifyELF "$f"
	done
else
	while read -r f; do
		VerifyELF "$f"
	done
fi

exit $rc
