#!/bin/bash
# vim: sw=4:sts=4:et
. $(dirname "$0")/common-functions
usage()
{
echo "Check state of a kernel fix and eventually suggest needed actions"
echo
echo "Expect upstream kernel tree sha or CVE number as the parameter."
echo "The script checks whether the commit is already in the upstream"
echo "baseline or backported in kernel-source tree."
echo
echo "If backported, checks for CVE/bsc references and recommends adding these"
echo "if they are missing. (Requires VULNS_GIT pointing to"
echo "https://git.kernel.org/pub/scm/linux/security/vulns.git tree."
echo "This will also allow cve number instead of sha and it resolves proer"
echo "upstream commit automatically."
echo
echo "Also the script looks for \"Fixes:\" tag of the given \"sha\"."
echo "When defined, the script informs where the fix has to be backported."
echo
echo "The script also takes into account the hierarchy of branches."
echo "It checks all branches. But the action is proposed only for"
echo "the top level ones. The assumption is that the other branches"
echo "will get the fix via a merge."
echo
echo "If the patch has CVE number with CVSS score associated then limits"
echo "actions only to CVSS affected branches."
echo
echo "Usage: ${0##*/} [options] sha|CVE"
echo
echo "Parameters:"
echo " sha: sha of the upstream commit"
echo " cve: CVE-XXXX-YYYY of the upstream commit (requires VULNS_GIT)"
echo
echo "Options:"
echo " -h: help"
echo " -q: quiet mode (no progress)"
echo " -v: verbose mode: show state of each branch and even NOP actions"
echo " -r: refresh any cached data. (git pull VULNS_GIT, cve, bsc medata)"
}
branch=
bprefix=
sha=
references=
cve=
top_level=
tmpdir=$(mktemp -d /tmp/${0##*/}.XXXXXX)
trap 'rm -rf "$tmpdir"' EXIT
branch_state_file="$tmpdir/branch-state"
patch_file="$tmpdir/patches"
actions_file="$tmpdir/actions"
print_branch_state()
{
local msg="$@"
if [ -n "$verbose_mode" ] ; then
echo "$msg"
elif [ -z "$quiet_mode" ] ; then
# show progress
echo -n "."
fi
echo "$msg" >> "$branch_state_file"
}
# Check state of the given branch and store
# The states are stored $tmpdir/branch-state are are the following:
#
# + nope: branch not affected
# + ok: branch has the fix and all references
# + missing_references: all or some references were not found
# + missing_patch: patch has to be backported
# + maybe_missing_patch: patch is missing and it is not known which commit
# introduced the bug
#
# When found, the name of the patch is stored into "$patch_file".
check_branch_state()
{
local branch="$1"
local sha="$2"
shift 2
local references="$@"
[ -z "$branch" ] && fail "check_branch_state: No branch provided"
[ -z "$sha" ] && fail "check_branch_state: No sha provided"
local patch=
local base=
local ref=
local missing_references=
local msg_prefix="$branch:$sha"
base=$(branch_base_ver $branch)
# Already merged upstream?
if sha_merged_in_upstream_tag "$sha" "$base" ; then
print_branch_state "$msg_prefix:nope"
return
fi
# Does the patch exist?
patch=$(sha_to_patch_in_branch "$sha" "$branch")
if [ -n "$patch" ] ; then
echo "$branch:$patch" >> "$patch_file"
# Check references
for ref in $references ; do
if ! patch_has_reference_in_branch "$patch" "$ref" "$branch" ; then
[ -n "$missing_references" ] && missing_references="$missing_references "
missing_references="$missing_references$ref"
fi
done
if [ -z "$missing_references" ] ; then
print_branch_state "$msg_prefix:ok"
else
print_branch_state "$msg_prefix:missing_references:$missing_references"
fi
return
fi
# Sha is not backported
# Do we need to backport it because of the Fixes tag?
local sha_git_fixes=$(sha_get_upstream_git_fixes $sha)
if [ -n "$sha_git_fixes" ] ; then
local affected_by_git_fixes="$(affected_by_git_fixes "$branch" "$base" $sha_git_fixes)"
if [ -n "$affected_by_git_fixes" ] ; then
print_branch_state "$msg_prefix:missing_patch:$affected_by_git_fixes"
else
print_branch_state "$msg_prefix:nope"
fi
return
fi
# missing git fixes
print_branch_state "$msg_prefix:maybe_missing_patch:$ref"
}
print_action()
{
local branch="$1"
local sha="$2"
local state="$3"
shift 3
local references="$@"
[ -z "$branch" ] && fail "print_action: No branch provided"
[ -z "$sha" ] && fail "print action: No sha provided"
[ -z "$state" ] && fail "print action: No state provided"
# make sure tha the file exists
touch "$patch_file"
patch=
action=
case "$state" in
missing_patch)
action="$branch: MANUAL: backport $sha ($references)"
;;
maybe_missing_patch)
action="$branch: MANUAL: might need backport of $sha ($references)"
;;
missing_references)
patch=$(grep "^$branch:" "$patch_file" | cut -d : -f 2)
if [ -n "$patch" ] ; then
action="$branch: RUN: add-missing-reference $patch $references"
else
action="$branch: MANUAL: no patch has the references: $references"
fi
;;
nope)
[ -n "$verbose_mode" ] && action="$branch: NOPE: no problema for $sha $references"
;;
ok)
[ -n "$verbose_mode" ] && action="$branch: NOPE: up-to-date $sha $references"
;;
*)
echo "print_action: Unknown action: $action" >&2
echo "for $branch:$sha:$state:$references" >&2
exit 1
;;
esac
if [ -n "$action" ] ; then
if [ ! -e "$actions_file" ] ; then
# first action
echo "ACTION NEEDED!"
touch "$actions_file"
fi
echo "$action"
fi
}
cvss_affects_branch()
{
local branch="$1"
local cvss="$2"
local ret=1
if [[ "$branch" =~ .*-EB.* ]]
then
[ $cvss -ge 9 ] && ret=0
elif [[ "$branch" =~ .*-GA.* ]]
then
[ $cvss -ge 7 ] && ret=0
elif [[ "$branch" =~ .*-LTSS.* ]]
then
[ $cvss -ge 7 ] && ret=0
else
ret=0
fi
return $ret
}
find_and_print_toplevel_actions()
{
local branch="$1"
local cvss="${2%%.*}"
local action_parameters=
local merge_branch=
local mb_line=
local line=
local merge_found=
local state=
local mb_state=
[ -z "$branch" ] && fail "find_and_print_toplevel_actions: No branch provided"
grep "^$branch:" "$branch_state_file" | \
while read line ; do
state=$(echo $line | cut -d: -f3)
# We only want to print branches which really need CVE fix backported
# CVSS 9+ EB branches
# CVSS 7+ LTSS branches
# Any CVSS for regular branch
# If we just need to add a reference then print everything
if [ -n "$cvss" -a "$state" != "missing_references" ]
then
if ! cvss_affects_branch "$branch" "$cvss"
then
continue
fi
fi
for merge_branch in $(print_merge_branches $branches_conf $branch) ; do
# Make sure merge_branches are in the same cvss scope
if [ -n "$cvss" -a "$state" != "missing_references" ]
then
if ! cvss_affects_branch "$merge_branch" "$cvss"
then
continue
fi
fi
# branch name might include '/', e.g. cve/linux-4.12
mb_line=$(echo -n "$line" | sed -e "s|^$branch:|$merge_branch:|")
# ignore the state when the same change is needed in a merge branch
if grep -q "^$mb_line$" "$branch_state_file" ; then
merge_found=1
fi
mb_state=$(echo $mb_line | cut -d: -f3)
if [ "$state" == "missing_references" -o \
"$state" == "missing_patch" -o \
"$state" == "maybe_missing_patch" ] ; then
# No action is needed when the patch is backported
# and has all the references in the merge branch
if [ "$mb_state" == "ok" ] ; then
merge_found=1
fi
fi
done
if [ -z "$merge_found" ] ; then
# split line into parameters
print_action ${line//:/ }
fi
done
}
verbose_mode=
quiet_mode=
while getopts "hvrq" OPT
do
case $OPT in
h)
usage
exit
;;
v)
verbose_mode=1
;;
r)
refresh=1
;;
q)
quiet_mode=1
;;
esac
done
shift "$(($OPTIND-1))"
[ -n "$verbose_mode" ] && quiet_mode=
if [ -z "$1" ] ; then
echo "No references provided"
usage
exit 1
fi
sha=$1
if ! sha_in_upstream "$1" ; then
sha=$(cve2sha $1 $refresh)
if [ -z "$sha" ]
then
[ -z "$VULNS_GIT" ] && fail "VULNS_GIT not defined. It has to point https://git.kernel.org/pub/scm/linux/security/vulns.git tree clone."
fail "Can find't sha in upstream: $1."
fi
fi
cve=$(sha2cve $sha $refresh)
bsc=
if [ -n "$cve" ]
then
bsc=$(cve2bugzilla $cve $refresh)
cvss=$(cve2cvss $cve $refresh)
echo "Security fix for $cve $bsc with CVSS ${cvss:-unknown}"
fi
references="$cve $bsc"
branches_conf="$(fetch_branches $refresh)"
# Check state of each branch
for_each_build_branch "$branches_conf" check_branch_state $sha $references
# Newline after the dots showing progress
[ -z "$quiet_mode" ] && echo
for_each_build_branch "$branches_conf" find_and_print_toplevel_actions $cvss
if [ ! -e "$actions_file" ] ; then
echo "EVERYTHING IS OK!"
fi