#!/bin/bash
# test02_pkgsecurity -- autospec test (rpm security checks)
# Copyright (C) 2008,2012,2014 Davide Madrisan <davide.madrisan@gmail.com>

[ -z "$BASH" ] || [ ${BASH_VERSION:0:1} -lt 2 ] &&
   echo $"this script requires bash version 2 or better" >&2 && exit 1

me=("test02_pkgsecurity" "1.45" "Thu Apr 23 2026")

[ -r /usr/share/autospec/lib/libmsgmng.lib ] ||
 { echo "$me: "$"library not found"": /usr/share/autospec/lib/libmsgmng.lib" 1>&2
   exit 1; }

. /usr/share/autospec/lib/libmsgmng.lib

[ -r /usr/share/autospec/lib/libtranslate.lib ] ||
 { echo "$me: "$"library not found"": /usr/share/autospec/lib/libtranslate.lib" 1>&2
   exit 1; }
. /usr/share/autospec/lib/libtranslate.lib

notify.debug $"loading"": \`test02_pkgsecurity'..."

# check if all the needed tools are available
for tool in file find getopt grep objdump sed; do
   [ "$(type -p $tool)" ] ||
      notify.error $"utility not found"": \`$tool'"
done

function alltests() {
   # FIXME: add to 'po' file
   notify.note " ${NOTE}"$"performing security checks""${NORM}""..."

   TEMP=`LC_ALL=C getopt \
      -o i:t: --long infofile:,tmpdir: \
      -n "$FUNCNAME" -- "$@"`
   [ $? = 0 ] || return 1
   eval set -- "$TEMP"

   while :; do
      case "$1" in
      -i|--infofile)
         rpminfofile="$2"
         shift
      ;;
      -t|--tmpdir)
         tmpextractdir="$2"
         shift
      ;;
      --) shift; break ;;
      *) notify.error $"\
(bug)"" -- $FUNCNAME: "$"\`getopt' error" ;;
      esac
      shift
   done

   [ "$rpminfofile" ] || notify.error $"\
(bug)"" -- $FUNCNAME: "$"missing mandatory arg"" (--infofile)"
   [ -r "$rpminfofile" ] || notify.error $"\
(bug)"" -- $FUNCNAME: "$"cannot read"" \`$rpminfofile'"

   . $rpminfofile

   [ "$tmpextractdir" ] || notify.error $"\
(bug)"" -- $FUNCNAME: "$"missing mandatory arg"" (--tmpdir)"
   [ -d "$tmpextractdir" ] || notify.error $"\
(bug)"" -- $FUNCNAME: "$"no such file or directory"" \`$tmpextractdir'"


   function security.filecheckrpath() {
      # $1: file to check
      # RPATH /usr/lib/.:/usr/lib/qt3/lib:/usr/lib:/usr/X11R6/lib
      objdump -p $1 2>/dev/null | \
         sed -n '/RPATH/{s/.* \(.*\)/\1:/p}' | \
         while read -d: path; do
            # try to discard false positive warnings
            [[ "${allowed_libdirs}:" =~ ${path}: ]] || echo -n "$path "
         done
   }

   local total_issues=0

   test.skip $test_number || {
   notify.note "$(test.num2str). ${NOTE}"\
$"checking for RPATH vulnerabilities""${NORM}..."

   let "i = 0"
   for pck in ${rpmpkg_name[@]}; do
      pushd $tmpextractdir/$i >/dev/null

      # find ELF binaries (ELF 32-bit LSB executable)
      # and libs (ELF 32-bit LSB shared object)
      for f in $(find -mindepth 2 -perm /111 -type f); do
         if [[ "$(file $f | grep " ELF ")" ]]; then
            rpath="$(security.filecheckrpath $f)"
            if [ "$rpath" ]; then
               notify.warning "${NORM}${pck##*/} --> ${f/./}"
               notify.note "RPATH:   $rpath"
               let "total_issues += 1"
            fi
         fi
      done
      popd >/dev/null
      let "i += 1"
   done; }
   test_number=$(($test_number + 1))

   test.skip $test_number || {
   notify.note "$(test.num2str). ${NOTE}"\
$"checking for setuid binaries""${NORM}..."

   let "i = 0"
   for pck in ${rpmpkg_name[@]}; do
      pushd $tmpextractdir/$i >/dev/null
      for f in $(find -mindepth 2 -perm /111 -type f 2>/dev/null); do
         if [[ "$(LC_ALL=C file $f | grep " setuid ")" ]]; then
            notify.warning "${NORM}${pck##*/} --> ${f/./}"
            let "total_issues += 1"
         fi
      done
      popd >/dev/null
      let "i += 1"
   done; }
   test_number=$(($test_number + 1))

   test.skip $test_number || {
   notify.note "$(test.num2str). ${NOTE}"\
$"checking for setgid directories""${NORM}..."

   let "i = 0"
   for pck in ${rpmpkg_name[@]}; do
      pushd $tmpextractdir/$i >/dev/null
      for d in $(find -mindepth 2 -perm -2000 -type d 2>/dev/null); do
         notify.warning "${NORM}${pck##*/} --> ${d/./}"
         let "total_issues += 1"
      done
      popd >/dev/null
      let "i += 1"
   done; }
   test_number=$(($test_number + 1))

   # checking for unsecure use of $$ as random source in shell scripts
   test.skip $test_number || {
   notify.note "$(test.num2str). ${NOTE}"\
$"checking for unsecure use of \`\$\$' in shell and perl scripts""${NORM}..."

   vulnerable=0
   let "i = 0"
   for pck in ${rpmpkg_name[@]}; do
      pushd $tmpextractdir/$i >/dev/null
      for f in $(find -mindepth 1 -perm /111 -type f); do
         # we are interesting only in shell scripts
         [[ "$(file $f | grep "shell script\|perl script")" ]] ||
            continue
         # check for string like:
         # - tmp=/tmp/zfoo.$$
         # - gzip -cdfq "$2" > /tmp/"$F".$$
         [[ \
-n "$(grep $f -m1 -s -rl -e"[^[:space:]]*=.*\$\$")" ||
-n "$(grep $f -m1 -s -rl -e">[[:space:]]*.*[[:space:]]*[^[:space:]]*\$\$")" ]] &&
            let "vulnerable = 1" &&
             { notify.warning "${NOTE}${pck##*/}${NORM}"
               notify.note $"\
seems to be affected"": \`${NOTE}${f/./}${NORM}'"
               let "total_issues += 1"; }
      done
      popd >/dev/null
      let "i += 1"
   done
   [[ $vulnerable -eq 1 ]] && notify.note "\
-----------------------------
${NOTE}"$"Hint for bash scripts (\`mktemp' required)"":${NORM}
   tmpdir=\`mktemp -d /tmp/<script_name>.XXXXXX\` ||
    { echo \"Cannot create directory \\\`\$tmpdir'. Aborting.\" >&2; exit 1; }
   trap 'ret=\$?; rm -rf \$tmpdir && exit \$ret' 0
   trap '(exit $?); exit' 1 2 13 15
   tmpfile=\`env TMPDIR=\"\" mktemp -p \$tmpdir tmpfile.XXXXXX\` ||
    { echo \"Cannot create temporary file \\\`\$tmpfile'. Aborting.\" >&2; exit 1; }
${NOTE}"$"Hint for perl scripts"":${NORM}
   use File::Temp qw/ tempfile /;
   (\$fh,\$file) = tempfile ('<script_name>.XXXXXX');
-----------------------------"; }
   test_number=$(($test_number + 1))

   notify.note "
 --> "$"${NOTE}Security checks: ${#rpmpkg_name[@]} \
package(s) checked: ${NORM}${WARN}$total_issues${NORM}${NOTE} warning(s).${NORM}""
"
}
