#!/bin/bash
# SPDX-License-Identifier: GPL-2.0-only
#
# Copyright (c) 2024 - Greg Kroah-Hartman <gregkh@linuxfoundation.org>
#
# dyad - create a listing of "pairs" of vulnerable:fixed kernels based on a
#	 specific git SHA that purports to fix an issue.  Used in combination
#	 with 'bippy' to create CVE entries for the Linux kernel.  Is VERY
#	 specific to how the Linux kernel has its stable branches and how it
#	 labels things.
#
# Usage:
#	dyad [options] GIT_SHA
#	For full options, see the help text below.
#
# Requires:
#  A kernel git tree with the SHA to be used in it
#  id_found_in - tool to find what kernel a specific SHA is in

# set to 1 to get some debugging logging messages (or use -v/--verbose option)
DEBUG=0

# Initialize our color variables if we are a normal terminal
if [[ -t 1 ]]; then
	txtred=$(tput setaf 1)          # Red
	txtgrn=$(tput setaf 2)          # Green
	txtblu=$(tput setaf 4)          # Blue
	txtcyn=$(tput setaf 6)          # Cyan
	txtrst=$(tput sgr0)             # Text reset
else
	txtred=""
	txtgrn=""
	txtblu=""
	txtcyn=""
	txtrst=""
fi

# set where the tool was run from,
# the name of our script,
# and the git version of it
DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
SCRIPT=${0##*/}
SCRIPT_VERSION=$(cd "${DIR}" && git ls-tree --abbrev=12 HEAD | grep -w "${SCRIPT}" | awk '{print $3}')

# Initialize some global variable arrays
fixed_set=()
vulnerable_set=()
fixed_pairs=()


#############################
#############################
# Functions for us to use, main flow starts below
#############################
#############################

#############################
# help()
#	Print out help options and exit
#############################
help()
{
	echo "Usage: $0 [OPTIONS] GIT_SHA"
	echo "Create a list of pairs of VULNERABLE:FIXED kernel versions and git ids based on a specific git sha value."
	echo ""
	echo "Arguments:"
	echo " --vulnerable=GIT_SHA		The kernel git sha1 that this issue became vulnerable at (optional)"
	echo " -h, --help			This information"
	echo " -v, --verbose			Show debugging information to stdout"
	echo ""
	exit 1
}

#############################
# dbg()
#	if DEBUG is enabled, print out a message.
#	Can also be set with -v or --verbose command line option
#	Does so with an initial "#" so it can be easily filtered out
# arguments:
# 	"message to print"
# assumes:
#	DEBUG is defined to something
#############################
dbg()
{
	if [[ ${DEBUG} -ge 1 ]] ; then
		echo "${txtcyn}# ${1}${txtrst}"
	fi
}

#############################
# info()
#	Print out a string as "information"
#	Does so with an initial "#" so it can be easily filtered out
# arguments:
#	"message to print"
#############################
info()
{
	echo "${txtgrn}# ${1}${txtrst}"
}

#############################
# kernel_version_is_rc()
#	Determine if a kernel version is a "-rc" release or not.
# arguments:
#	"kernel version"
# returns:
#	1 if -rc, 0 if not
#############################
kernel_version_is_rc()
{
	#dbg "kernel_version_is_rc($1)"
	local VERSION=$1
	if [[ "${VERSION}" =~ .*"rc" ]] ; then
		#dbg "kernel_version_is_rc: ${VERSION} is -rc"
		return 1
	else
		#dbg "kernel_version_is_rc: ${VERSION} is NOT -rc"
		return 0
	fi
}

#############################
# kernel_version_is_queue()
#	Determine if a kernel version is in a stable queue or not.
# arguments:
#	"kernel version"
# returns:
#	1 if in a queue, 0 if not
#############################
kernel_version_is_queue()
{
	local VERSION=$1

	if [[ "${VERSION}" =~ .*"queue" ]] ; then
		return 1
	else
		return 0
	fi
}

#############################
# kernel_version_is_mainline()
#	Determine if a kernel version is a mainline kernel or not (i.e. a
#	stable one).
# arguments:
#	"kernel version"
# returns:
#	1 if mainline, 0 if not
#############################
kernel_version_is_mainline()
{
	#dbg "kernel_version_is_mainline($1)"
	local VERSION=$1

	# shellcheck disable=SC2206
	local REL_ARRAY=(${VERSION//./ })
	local MAJOR=${REL_ARRAY[0]}
	#local BASE=${REL_ARRAY[0]}.${REL_ARRAY[1]}.${REL_ARRAY[2]}
	#local REL=${REL_ARRAY[3]}
	#local MINOR=${REL_ARRAY[2]}

	# If this is a 2.6.X release, just return now, we don't care about them
	# anymore
	if [[ "${MAJOR}" == "2" ]] ; then
		return 1
	fi

	kernel_version_is_rc "${VERSION}"
	local rc=$?
	#echo "rc=$rc"
	# If this is a -rc release, it's a mainline release
	#if [[ "${VERSION}" =~ *"-rc"* ]] ; then
	if [[ "${rc}" == "1" ]] ; then
		return 1
	fi

	# If this is in a queue, it's not a mainline release
	kernel_version_is_queue "${VERSION}"
	local queue=$?
	if [[ "${queue}" == "1" ]] ; then
		return 1
	fi

	# if the REL_ARRAY only has 2 elements in it, it's a mainline release
	# (X.Y, not X.Y.Z)
	if [[ "${#REL_ARRAY[@]}" == "2" ]] ; then
		return 1
	fi

	# Must be a stable release
	return 0
}

#############################
# kernel_version_match()
#	Determine if the "major.minor" versions of 2 kernel numbers passed in
#	are matching or not.
# arguments:
#	"kernel version 1 of format X.Y.Z  .Z is optional"
#	"kernel version 2 of format X.Y.Z  .Z is optional"
# returns:
#	1 if X.Y matches, 0 if not
#############################
kernel_version_match()
{
	local VERSION_1=$1
	local VERSION_2=$2

	# shellcheck disable=SC2206
	local REL_ARRAY_1=(${VERSION_1//./ })
	# shellcheck disable=SC2206
	local REL_ARRAY_2=(${VERSION_2//./ })
	local MAJOR_1=${REL_ARRAY_1[0]}
	local MAJOR_2=${REL_ARRAY_2[0]}

	if [[ "${MAJOR_1}" == "${MAJOR_2}" ]] ; then
		local MINOR_1=${REL_ARRAY_1[1]}
		local MINOR_2=${REL_ARRAY_2[1]}
		if [[ "${MINOR_1}" == "${MINOR_2}" ]] ; then
			# dbg "kernel_version_match: ${1} ${2}: succeeded"
			return 1
		fi
	fi

	# dbg "kernel_version_match: ${1} ${2}: failed"
	return 0
}

#############################
# create_fix_set()
#	Adds a new "fixed set" of kernel_version:git_id that fixes the problem
#	we are tracking to the global list of fixed sets
# arguments:
#	"kernel version"
#	"git id"
#############################
create_fix_set()
{
	local f=$1
	local f_git=$2
	fixed_set+=("${f}:${f_git}")

	#dbg "fixed pair='${f}:${f_git}'"
}

#############################
# create_vulnerable_set()
#	Adds a new "vulnerable set" of kernel_version:git_id to the global list of
#	vulnerable sets we want to track
# arguments:
#	"kernel version"
#	"git id"
#############################
create_vulnerable_set()
{
	local v=$1
	local v_git=$2
	vulnerable_set+=("${v}:${v_git}")

	#dbg "vulnerable pair='${v}:${v_git}'"
}

#############################
# create_fixed_set()
#	Adds a new "fixed pair" of vulnerable:fixed kernel information to the
#	global list of fixes that we want to output.
# arguments:
#	"vulnerable_kernel:vulnerable_git"
#	"fixed_kernel:fixed_git"
#############################
create_fixed_pair()
{
	local v=$1
	local f=$2
	fixed_pairs+=("${v}:${f}")
	#dbg "v='${v}'	f='${f}'"
	#dbg "fixed pair = ${v}:${f}"
}

#############################
# find_stable_git_id()
#	Given a git id and a stable kernel version, find the git id in that
#	stable kernel branch that corresponds with the commit that added the
#	specific git id to that stable branch.
# arguments:
#	"git id"
#	"stable kernel version"
# returns:
#	"git id of commit in the stable branch" or "" if not found
# assumes:
#	KERNEL_TREE points to a valid Linux stable git tree
#############################
find_stable_git_id()
{
	#>&2 echo "find_stable_git_id: \"${1}\" \"${2}\""
	local og_git=${1}
	local fixed_version=${2}

	# shellcheck disable=SC2206
	local fixed_array=(${fixed_version//./ })
	local fixed_major=${fixed_array[0]}
	local fixed_minor=${fixed_array[1]}

	local stable_git
	stable_git=$(cd "${KERNEL_TREE}" && git log -1 --abbrev=40 --oneline --grep="${og_git}" "v${fixed_major}.${fixed_minor}..v${fixed_version}" | awk '{print $1}')
	#>&2 echo "find_stable_git_id: first try: stable_git=${stable_git}"

	if [[ "${stable_git}" == "" ]]; then
		# oh no, not found as part of a commit in this version!
		# One case could be where the commit itself is in this
		# branch/version, and if so, just return it, as it is a valid
		# commit that we need to track.  This happens when commits are
		# "only" in stable branches for whatever reason (i.e. no
		# corresponding commit in a mainline branch.)
		# Verify this by seeing if the commit is actually in this
		# version by asking git for the information
		local local_version
		local_version=$(cd "${KERNEL_TREE}" && git describe --contains "${og_git}" | grep "${fixed_version}")
		if [[ "${local_version}" != "" ]]; then
			stable_git=${og_git}
		fi
		#>&2 echo "find_stable_git_id: second try: stable_git=${stable_git}"
	fi

	echo "${stable_git}"
}

#############################
# find_git_id()
#	Given a git id, and a kernel version, find the git id for that specific
#	branch that corrisponds with it.  This is used to find where a commit
#	is backported to, and find the actual commit.  It uses
#	find_stable_git_id() to find it in a stable branch, but if this is a
#	mainline branch, just return the kernel version passed to us.
# arguments:
#	"git id"
#	"kernel version"
# returns:
#	"git id where the commit is in the kernel version"
# assumes:
#	KERNEL_TREE points to a valid Linux stable git tree
#############################
find_git_id()
{
	local id=${1}
	local fixed_version=${2}

	kernel_version_is_mainline "${fixed_version}"
	local is_mainline=$?
	local git_id=${id}

	if [[ "${is_mainline}" == "0" ]]; then
		git_id=$(find_stable_git_id "${id}" "${fixed_version}")
	fi
	echo "${git_id}"
}

#############################
# git_full_id()
#	returns the "FULL" sha1 of the short git id passed in
# arguments
#	"git id"
# returns
#	"full git id" if found, "" if not found
# assumes:
#	KERNEL_TREE points to a valid Linux stable git tree
#############################
git_full_id()
{
	local short_id=${1}
	local long_id
	long_id=$(cd "${KERNEL_TREE}" && git log -1 --format="%H" "${short_id}" 2> /dev/null)
	echo "${long_id}"
}

#############################
# git_short_id()
#	returns the "SHORT" sha1 of the short git id passed in
# arguments
#	"git id"
# returns
#	"short git id" if found, "" if not found
# assumes:
#	KERNEL_TREE points to a valid Linux stable git tree
#############################
git_short_id()
{
	local id=${1}
	local short_id
	short_id=$(cd "${KERNEL_TREE}" && git log -1 --abbrev=12 --format="%h" "${id}" 2> /dev/null)
	echo "${short_id}"
}

#############################
# kernel_id_info()
#	prints a bunch of debugging information about the kernel version passed in.
# arguments
#	"kernel version"
#############################
kernel_id_info()
{
	local kernel=${1}

	# figure out if this is a -rc kernel or not
	kernel_version_is_mainline "${kernel}"
	local kernel_mainline=$?
	kernel_version_is_rc "${kernel}"
	local kernel_rc=$?
	kernel_version_is_queue "${kernel}"
	local kernel_queue=$?

	# Print out some debugging to see if kernel version detection is working:
	local dbg_string="${kernel} is "
	if [[ "${kernel_queue}" == "1" ]]; then
		dbg_string+="queue "
	fi
	if [[ "${kernel_rc}" == "1" ]]; then
		dbg_string+="-rc and "
	fi
	if [[ "${kernel_mainline}" == "1" ]]; then
		dbg_string+="mainline"
	else
		dbg_string+="not mainline"
	fi
	dbg "${dbg_string}"
}

#############################
# kernel_greater_than()
#	if kernel 1 is greater than kernel 2, then 1 is returned, otherwise 0
# arguments
#	"kernel 1"
#	"kernel 2"
#############################
kernel_greater_than()
{
	local k1=$1
	local k2=$2
	local temp
	local small

	temp=$(echo -e "${k1} \n")
	temp+=$(echo -e "${k2} \n")

	# ${temp} is not escaped on purpose, otherwise we end up with a
	# trailing space
	# shellcheck disable=SC2086
	small=$(printf "%s\n" ${temp} | sort -V | head -n 1)
	if [[ "${small}" == "${k1}" ]]; then
		return 0
	else
		return 1
	fi
}

#############################
#############################
# "main" logic starts here
#############################
#############################

# Verify that some basic environment variables are set up
KERNEL_TREE=${CVEKERNELTREE}
COMMIT_TREE=${CVECOMMITTREE}
FOUND_IN=${COMMIT_TREE}/id_found_in

if [[ ! -d "${KERNEL_TREE}" ]] || [[ ! -d "${COMMIT_TREE}" ]]; then
	echo "${txtred}ERROR:${txtrst}"
	echo "	${txtblu}CVEKERNELTREE${txtrst} needs setting to the stable repo directory"
	echo "	${txtblu}CVECOMMITTREE${txtrst} needs setting to the Stable commit tree"
	echo -e "\nEither manually export them or add them to your .bashrc/.zshrc et al."
	echo -e "\nSee HOWTO in the root of this repo"
	exit 1
fi

# Parse the command line
short_opts="hv"
long_opts="vulnerable:,help,verbose"
GIT_SHA=""
GIT_VULNERABLE=""

TMP=$(getopt -o "${short_opts}" --long "${long_opts}" --name="${SCRIPT}" -- "$@")
eval set -- "${TMP}"
while :; do
	dbg "arg=${1}"
	case "${1}" in
		-h | --help	) help;;
		-v | --verbose	) DEBUG=1;			shift ;;
		--vulnerable	) GIT_VULNERABLE="${2}";	shift 2 ;;
		-- )		  shift; break ;;
	esac
done

# Rest of the command line argument is the git sha we are looking at
GIT_SHA=$1

# Now we can test for unset variables, before we would have failed if someone
# forgot the git sha on the command line.
set -o nounset

# Verify we have a git sha on the command line
if [[ "${GIT_SHA}" == "" ]]; then
	help
fi

# Header boiler plate info to show what is happening
info "${SCRIPT} version: ${SCRIPT_VERSION}"

#
# See if the SHA given to us is a valid SHA in the git repo.
# This tests if we have a valid kernel tree, AND we need a full/long SHA1 for
# many of the searches we do later on.  If we stuck with a short one, some of
# the searches would give us false-positives as people use short shas in commit
# messages.
GIT_SHA_FULL=$(git_full_id "${GIT_SHA}")
if [[ "${GIT_SHA_FULL}" == "" ]] ; then
	echo "${txtred}ERROR:${txtrst} git id ${txtcyn}${GIT_SHA}${txtrst} is not found!"
	exit 1
fi

info "	getting vulnerable:fixed pairs for git id ${txtcyn}${GIT_SHA_FULL}${txtrst}"

# Grab a "real" 12 character short sha to use as well, we "know" this will not
# fail as the original id was valid.
GIT_SHA_SHORT=$(git_short_id "${GIT_SHA_FULL}")
dbg "GIT_SHA=${GIT_SHA}	GIT_SHA_FULL=${GIT_SHA_FULL}	GIT_SHA_SHORT=${GIT_SHA_SHORT}"

#
# Find all of the places (git id and release number) where this commit has been
# applied to.
#
# To do so we call ${FOUND_IN} to get the versions, and then we iterate over
# the branches to get the git ids.
#
fixed_kernels=$("${FOUND_IN}" "${GIT_SHA_FULL}")
dbg "fixed_kernels=${fixed_kernels}"

# no "" for variable on purpose, we want to split the line up
for kernel in ${fixed_kernels}; do
	#kernel_id_info "${kernel}"

	kernel_version_is_queue "${kernel}"
	kernel_queue=$?
	if [[ "${kernel_queue}" == "1" ]]; then
		continue
	fi

	fix_id=$(find_git_id "${GIT_SHA_FULL}" "${kernel}")
	#dbg "fix_id=${fix_id}"

	create_fix_set "${kernel}" "${fix_id}"

done

dbg "We have found ${#fixed_set[@]} sets of fixed kernels"
if [[ "${#fixed_set[@]}" == "0" ]] ; then
	echo "${txtred}ERROT:${txtrst} No vulnerable and then fixed pairs of kernels were found for commit ${txtcyn}${GIT_SHA_SHORT}${txtrst}"
	exit 1
fi
for fixed_entry in "${fixed_set[@]}"; do
	dbg "	${fixed_entry}"
done

#
# We have a set of where everything was fixed up, based on the original git id,
# so now let's try to determine where the problem first showed up (i.e. became
# vulnerable)
#
# If this is passed to us on the command line, it's easy, use that!
# Otherwise, try to dig in the changelog text and find any "Fixes:" lines and
# parse them to try to figure out where the issue first showed up at (i.e. what
# kernel version and git id caused the problem.)
#
# Kernel ids in a "Fixes:" line are almost always the the id in Linus's tree,
# so we need to dig through the stable branches to get the real git id for
# where the commit happened.  But note that sometimes they are the stable id.
# Rely on the regression tests to get this all correct.
if [[ "${GIT_VULNERABLE}" != "" ]]; then
	# We are asked to set the original vulnerable kernel to be a specific
	# one, so no need to look it up.
	full_id=$(git_full_id "${GIT_VULNERABLE}")
	if [[ "${full_id}" == "" ]]; then
		echo "${txtred}ERROR:${txtrst} Vulnerable git id ${txtcyn}${GIT_VULNERABLE}${txtrst} is not found!"
		exit 2
	fi

	kernel=$(cd "${KERNEL_TREE}" && git describe --contains "${full_id}" | cut -f 1 -d '-' | cut -f 1 -d '~' | sed -e 's/v//g')
	if [[ "${kernel}" == "" ]]; then
		echo "ERROR: Vulnerable git id ${txtcyn}${full_id}${txtrst} is not found in any version!"
		exit 2
	fi
	info "	Setting original vulnerable kernel to be kernel ${txtcyn}${kernel}${txtrst} and git id ${txtcyn}${full_id}${txtrst}"
	create_vulnerable_set "${kernel}" "${full_id}"
else
	# Grab the full commit text, we need that to figure out what ids this commit
	# actually fixes (if any)
	commit_text=$(cd "${KERNEL_TREE}" && git show --no-patch --pretty=format:"%B" "${GIT_SHA_FULL}")

	#dbg "commit_text=${commit_text}"

	# Look in the commit text to see if there is any "Fixes:" lines
	# if so, look them up to see what kernels they were released in.  Need to do
	# this with the "expanded" SHA value, the short one will give us too many
	# false-positives when it shows up in other Fixes: tags
	vuln_lines=$(echo "${commit_text}" | grep -i "fixes:" | sed -e 's/^[ \t]*//' | sed -e 's/.*[F|f]ixes:/:/' | cut -f 2 -d ':' | sed -e 's/^[ \t]*//' | cut -f 1 -d ' ')
	temp="${vuln_lines}"
	dbg "vuln_lines=${temp}"
	v=()
	if [[ "${vuln_lines}" != "" ]] ; then
		# get a list of vulnerable kernels, sort them by the order in
		# which they are in the tree, oldest first.
		for id in ${vuln_lines}; do
			full_id=$(git_full_id "${id}")
			if [[ "${full_id}" == "" ]]; then
				dbg "invalid fix entry of '${id}', skipping"
				continue
			fi
			v+=("${full_id}")
		done
	fi
	dbg "	number in v: ${#v[@]}"
	if [[ "${#v[@]}" == "0" ]]; then
		dbg "	nothing in v, skipping vuln lines check"
	else
		# We have some vulnerable kernels, let's figure out where they are
		v_file=$(mktemp "${TMPDIR}"/"${SCRIPT}".XXXX || exit 1)
		for id in "${v[@]}"; do
			echo "${id}" >> "${v_file}"
		done
		# use 'tac' as it's faster than having git do --reverse for 'git rev-list'
		sort_order=$(cd "${KERNEL_TREE}" && git rev-list --topo-order $(cat "${v_file}") | grep --file "${v_file}" --max-count ${#v[@]} | tac)
		rm "${v_file}"
		# git rev-list --topo-order $(cat SET_OF_SHA1S) | grep --file SET_OF_SHA1S --max-count $(wc -l SET_OF_SHA1S)
		dbg "sort_order=${sort_order}"

		# figure out what kernels this commit fixes, (i.e. which are
		# vulnerable) and add them to a list of vulnerable sets.
		for id in ${sort_order}; do
			full_id=$(git_full_id "${id}")
			if [[ "${full_id}" == "" ]]; then
				dbg "invalid fix: ${id}"
				continue
			fi
			x=$("${FOUND_IN}" "${full_id}")
			for kernel in ${x}; do
				kernel_id_info "${kernel}"

				kernel_version_is_mainline "${kernel}"
				kernel_is_mainline=$?
				if [[ "${kernel_is_mainline}" == "0" ]]; then
					stable_id=$(find_stable_git_id "${full_id}" "${kernel}")
					create_vulnerable_set "${kernel}" "${stable_id}"
				else
					create_vulnerable_set "${kernel}" "${full_id}"
				fi
			done
		done
	fi
fi

dbg "Before winnowing we have found ${#vulnerable_set[@]} sets of vulnerable kernels"
for vuln_entry in "${vulnerable_set[@]}"; do
	dbg "	${vuln_entry}"
done

#
# Now that we have a list of vulnerable kernels, we need to find the "root"
# mainline version that had the oldest issue in it.  We might have many
# mainline kernels listed in here, but we only care about the "oldest" one, so
# throw away all the rest.
#
# To do this, we create 2 lists, one for mainline kernels, and one for stable
# kernels.  The stable kernel list we will keep "as is", but for the mainline
# kernel list, we will sort it and then throw away everything EXCEPT the oldest
# kernel.  After that, we will re-create the vulnerable set with the new
# information.
vulnerable_stable_set=()
vulnerable_mainline_set=()
for vuln_entry in "${vulnerable_set[@]}"; do
	# shellcheck disable=SC2206
	y=(${vuln_entry//:/ })
	vuln_version=${y[0]}
	vuln_git=${y[1]}

	kernel_version_is_mainline "${vuln_version}"
	kernel_is_mainline=$?
	if [[ "${kernel_is_mainline}" == "1" ]]; then
		vulnerable_mainline_set+=("${vuln_entry}")
	else
		vulnerable_stable_set+=("${vuln_entry}")
	fi
done

# Reset the list
vulnerable_set=()

dbg "	vuln_stable_set: ${#vulnerable_stable_set[@]}"
for vuln_entry in "${vulnerable_stable_set[@]}"; do
	dbg "		${vuln_entry}"
done
temp=""

#
# The "default" vulnerable point in mainline where this issue first showed up.
# We need this for any fix that happened in a stable branch that happened AFTER
# this point in time (i.e. fixed in 6.6.3 for an issue that showed up in 5.4).
vuln_mainline_pair=""
dbg "	vuln_mainline_set: ${#vulnerable_mainline_set[@]}"
if [[ "${#vulnerable_mainline_set[@]}" != "0" ]]; then
	for vuln_entry in "${vulnerable_mainline_set[@]}"; do
		# trailing space is important
		temp+=$(echo -e "${vuln_entry} \n")
		dbg "		${vuln_entry}"
	done
	# ${temp} is not escaped on purpose, otherwise we end up with a
	# trailing space
	# shellcheck disable=SC2086
	vuln_mainline_pair=$(printf "%s\n" ${temp} | sort -V | head -n 1)
	dbg "	vuln_mainline_pair=${vuln_mainline_pair}"
	vulnerable_set+=("${vuln_mainline_pair}")

	vm=("${vuln_mainline_pair}")
	vuln_mainline_version=${vm[0]}

	# iterate over all of the stable entries, and only add the ones that
	# are "older" than the mainline release.
	if [[ "${#vulnerable_stable_set[@]}" != "0" ]]; then
		for vuln_stable_entry in "${vulnerable_stable_set[@]}"; do
			# shellcheck disable=SC2206
			vs=(${vuln_stable_entry//:/ })
			vuln_stable_version=${vs[0]}

			kernel_greater_than "${vuln_mainline_version}" "${vuln_stable_version}"
			fixed_version_greater=$?
			if [[ "${fixed_version_greater}" == "1" ]] ; then
				vulnerable_set+=("${vuln_stable_entry}")
			fi
		done
	fi
else
	# No mainline vulnerable kernels, so just take all of the stable ones
	if [[ "${#vulnerable_stable_set[@]}" != "0" ]]; then
		for vuln_entry in "${vulnerable_stable_set[@]}"; do
			vulnerable_set+=("${vuln_entry}")
		done
	fi
fi

dbg "We have found ${#vulnerable_set[@]} sets of vulnerable kernels"
for vuln_entry in "${vulnerable_set[@]}"; do
	dbg "	${vuln_entry}"
done

#
# Now we have two lists, one where the kernel became vulnerable (could not be
# known, so we assume 0), and where it was fixed (the id originally passed to
# us and where it has been backported to.)  Take those two lists and start
# matching them up based on kernel versions in order to get a set of
# vulnerable:fixed pairs
#
# Iterate over all of the "fixed" kernel versions/ids and try to match them up
# with any vulnerable kernel entries (if any)
for fixed_entry in "${fixed_set[@]}" ; do
	create=0
	# shellcheck disable=SC2206
	x=(${fixed_entry//:/ })
	fixed_version=${x[0]}
	fixed_git=${x[1]}
	dbg "fixed_entry:	'${fixed_version}'	'${fixed_git}'"

	kernel_version_is_mainline "${fixed_version}"
	fixed_version_mainline=$?

	# See if we have ANY kernels where the vulnerability showed up.  If not, assume
	# that it "has always been there", so create our final set of vulnerable/fixed
	# pairs straight from the fixed list
	if [[ "${#vulnerable_set[@]}" == "0" ]] ; then
		create_fixed_pair "0:0" "${fixed_entry}"
		create=1
		continue
	fi

	# We have some vulnerable kernels set, so let's try to match them up
	for vuln_entry in "${vulnerable_set[@]}" ; do
		# shellcheck disable=SC2206
		y=(${vuln_entry//:/ })
		vuln_version=${y[0]}
		vuln_git=${y[1]}
		dbg "	vuln_entry:	'${vuln_version}'	'${vuln_git}'"

		# vulnerable and fixed in the same version.  Save this off as
		# it is needed for the git vulnerable information (small window
		# of where things went wrong).
		if [[ "${fixed_version}" == "${vuln_version}" ]]; then
			dbg "${fixed_version} == ${vuln_version} save it"
			create_fixed_pair "${vuln_entry}" "${fixed_entry}"
			create=1
			break
		fi

		kernel_version_is_mainline "${vuln_version}"
		vuln_version_mainline=$?

		# If these are both mainline commits then create a matching pair
		if [[ "${vuln_version_mainline}" == "1" ]] ; then
			if [[ "${fixed_version_mainline}" == "1" ]]; then
				create_fixed_pair "${vuln_entry}" "${fixed_entry}"
				create=1
				break
			fi
		fi

		# if this is the same X.Y version, make a pair
		kernel_version_match "${vuln_version}" "${fixed_version}"
		match=$?
		if [[ "${match}" == "1" ]] ; then
			create_fixed_pair "${vuln_entry}" "${fixed_entry}"
			create=1
			break
		fi
	done

	# We did not create any entry at all above, so we need to set the
	# "default" vulnerable point to the original vulnerable mainline pair
	# found way above as that's where the issue showed up (i.e before this
	# stable kernel branch was forked from mainline.)
	if [[ "${create}" == "0" ]]; then
		dbg "	nothing found for ${fixed_version}, using default of ${vuln_mainline_pair}"
		create_fixed_pair "${vuln_mainline_pair}" "${fixed_entry}"
	fi
done

#
# Now the fun starts, which justified all of the hard work we did above.  We
# need to track the places where we are vulnerable, but NOT fixed.  So walk the
# vulnerable list, see if anything in the fixed_pair matches up, and if NOT,
# then add it to the list as an "unfixed" pair
for vuln_entry in "${vulnerable_set[@]}"; do
	found=0
	# shellcheck disable=SC2206
	y=(${vuln_entry//:/ })
	vuln_version=${y[0]}
	vuln_git=${y[1]}
	for fixed_entry in "${fixed_pairs[@]}"; do
		# shellcheck disable=SC2206
		x=(${fixed_entry//:/ })
		a=${x[0]}
		b=${x[1]}
		c=${x[2]}
		#d=${x[3]}
		# if the vulnerable version and git matches what is in the
		# list, mark this as found
		if [[ "${a}" == "${vuln_version}" ]]; then
			if [[ "${b}" == "${vuln_git}" ]]; then
				found=1
				break
			fi
		fi

		# If the fixed version matches the vuln version, mark this as
		# found
		if [[ "${c}" == "${vuln_version}" ]]; then
			found=1
			break
		fi
	done
	if [[ "${found}" == "0" ]]; then
		dbg "not found: ${vuln_entry}"
		create_fixed_pair "${vuln_entry}" "0:0"
	fi
done

#
# We are done!
# Print out the pairs we found so that bippy can do something with them.
dbg "Number of vulnerable / fixed kernel pairs: ${#fixed_pairs[@]}"
for entry in "${fixed_pairs[@]}" ; do
	echo "${txtgrn}${entry}${txtrst}"
done

exit 0
