blob: 1d0b2ea67d2db15902878b1c7e89a58153a68163 [file] [log] [blame]
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
#
# Copyright (c) 2024 - Greg Kroah-Hartman <gregkh@linuxfoundation.org>
#
# bippy - creates a json and/or mbox file on standard output in the proper
# format to submit a CVE based on a specific git SHA.
#
# Usage:
# bippy [loads of options, see the help text below]
#
# Right now only works with CVEs, will handle other identifiers as needed.
#
# Name comes from the phrase "you bet your bippy!" as said by David L. Morse.
#
# Requires:
# A kernel git tree with the SHA to be used in it
# jo - the json output tool, found at: https://github.com/jpmens/jo
# 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
# TODO - make these options that are not hard-coded
KERNEL_TREE="/home/gregkh/linux/stable/linux-stable"
FOUND_IN="/home/gregkh/linux/stable/commit_tree/id_found_in"
# Might be dropped if we don't need the full "container" output, see at the
# bottom for more details
USER="gregkh@linuxfoundation.org"
# don't use unset variables
set -o nounset
# set where the tool was run from,
# the name of our script,
# and the git version of it
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
SCRIPT=${0##*/}
SCRIPT_VERSION=$(cd "${DIR}" && git ls-tree --abbrev=12 HEAD | grep "${SCRIPT}" | awk '{print $3}')
# global variables
vuln_kernels=()
fixed_pairs=()
help() {
echo "Usage: $0 [OPTIONS]"
echo "Create a JSON or MBOX file to report a CVE based on a specific Linux kernel"
echo "git sha value."
echo ""
echo "Arguments:"
echo " -c, --cve=CVE_NUMBER The full CVE number to assign"
echo " -s, --sha=GIT_SHA The kernel git sha1 to assign the CVE to"
echo " -j, --json=JSON_FILENAME Output a JSON report to submit to CVE to the"
echo " specified filename"
echo " -m, --mbox=MBOX_FILENAME Output a mbox file to submit to the CVE"
echo " announce mailing list"
echo " -h, --help This information"
echo " -v, --verbose Show debugging information to stdout"
echo ""
echo "Note, CVE_NUMBER and GIT_SHA are required, as well as at least one of"
echo "JSON_FILENAME and/or MBOX_FILENAME."
exit 1
}
dbg() {
if [[ ${DEBUG} -ge 1 ]] ; then
echo "$1"
fi
}
# Parse the command line
short_opts="j:m:c:s:hv"
long_opts="json:,mbox:,cve:,sha:,help,verbose"
JSON_FILE=""
MBOX_FILE=""
CVE_NUMBER=""
GIT_SHA=""
TMP=$(getopt -o "${short_opts}" --long "${long_opts}" --name="${SCRIPT}" -- "$@")
eval set -- "${TMP}"
while :; do
dbg "arg=${1}"
case "${1}" in
-j | --json ) JSON_FILE="${2}"; shift 2 ;;
-m | --mbox ) MBOX_FILE="${2}"; shift 2 ;;
-c | --cve ) CVE_NUMBER="${2}"; shift 2 ;;
-s | --sha ) GIT_SHA="${2}"; shift 2 ;;
-h | --help ) help ;;
-v | --verbose ) DEBUG=1; shift ;;
-- ) shift; break ;;
* ) help ;;
esac
done
dbg "CVE_NUMBER=${CVE_NUMBER}"
dbg "GIT_SHA=${GIT_SHA}"
dbg "JSON_FILE=${JSON_FILE}"
dbg "MBOX_FILE=${MBOX_FILE}"
if [[ "${CVE_NUMBER}" == "" || "${GIT_SHA}" == "" ]] ; then
help
fi
if [[ "${JSON_FILE}" == "" && "${MBOX_FILE}" == "" ]] ; then
help
fi
# Functions for us to use, main flow starts below at ======= point
# Given a short SHA value in $1 (we hope), turn it into an "expanded" sha and
# then look up where that commit came from.
# Might be multiple kernels, so parse accordingly
find_fix() {
FIX=$1
id=""
if [[ ${FIX} =~ [[:xdigit:]]{12} ]] ; then
id=${BASH_REMATCH[0]}
else
# Let's try it again in a cruder way
id=$(echo "${FIX}" | sed -e 's/^[ \t]*//' | cut -f 2 -d ':' | sed -e 's/^[ \t]*//' | cut -f 1 -d ' ')
fi
if [ "${id}" == "" ] ; then
# can't find a valid sha or something resembing it, so just return
return
fi
long_id=$(cd ${KERNEL_TREE} && git log -1 --format="%H" "${id}")
if [ "${long_id}" == "" ] ; then
# git id is not a valid one, so just return
return
fi
release=$("${FOUND_IN}" "${long_id}")
echo "${release} "
return
}
# determine if a kernel version is a "-rc" release or not.
# Will return 1 if -rc, 0 if not
function version_is_rc
{
#dbg "version_is_rc($1)"
local VERSION=$1
if [[ "${VERSION}" =~ .*"rc" ]] ; then
#dbg "version_is_rc: ${VERSION} is -rc"
return 1
else
#dbg "version_is_rc: ${VERSION} is NOT -rc"
return 0
fi
}
function version_is_queue
{
local VERSION=$1
if [[ "${VERSION}" =~ .*"queue" ]] ; then
return 1
else
return 0
fi
}
# Determine if the "major.minor" versions of 2 kernels are matching
# will return 1 if true, 0 if not
function version_match
{
local VERSION_1=$1
local VERSION_2=$2
local REL_ARRAY_1=(${VERSION_1//./ })
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 "version_match: ${1} ${2}: succeeded"
return 1
fi
fi
# dbg "version_match: ${1} ${2}: failed"
return 0
}
# Determine if a kernel version is a "mainline" one, or if it is a stable
# kernel release. Will return 1 if mainline, 0 if not
function version_is_mainline
{
#dbg "version_is_mainline($1)"
local VERSION=$1
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
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
version_is_queue "${VERSION}"
local queue=$?
if [[ "${queue}" == "1" ]] ; then
return 0
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
}
# =======
# Main logic starts here
# Get the UUID we are going to use from the linux.uuid file in the directory
# where the script is. This allows us to change this if needed in the future
# (and it's easier to move between testing and production databases this way,
# as those require different uuids.)
orig_id=$(cat "${DIR}"/linux.uuid)
if [[ "${orig_id}" == "" ]]; then
echo "No UUID found to use at ${DIR}/linux.uuid, aborting"
exit 1
fi
dbg "orig_id=${orig_id}"
# go into the kernel tree, we need this to be a valid one
#cd ${KERNEL_TREE} || exit 1
# 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=$(cd ${KERNEL_TREE} && git log -1 --format="%H" "${GIT_SHA}")
if [[ "${GIT_SHA_FULL}" == "" ]] ; then
echo "error: git id ${GIT_SHA} is not found in the tree at ${KERNEL_TREE}"
exit 1
fi
# Grab a "real" 12 character short sha to use as well, we "know" this will not fail.
GIT_SHA_SHORT=$(cd ${KERNEL_TREE} && git log -1 --abbrev=12 --format="%h" "${GIT_SHA_FULL}")
# Get the subject line of our sha
subject=$(cd ${KERNEL_TREE} && git show --no-patch --pretty=format:"%s" "${GIT_SHA_FULL}" 2> /dev/null)
if [[ "${subject}" == "" ]] ; then
echo "error: git id ${GIT_SHA_FULL} is not found in the tree at ${KERNEL_TREE}"
exit 1
fi
dbg "subject=${subject}"
# Get the list of files affected in the change
files=$(cd ${KERNEL_TREE} && git diff --name-only "${GIT_SHA_FULL}"^.."${GIT_SHA_FULL}" 2> /dev/null)
dbg "${GIT_SHA_FULL} touched the following files:"
while IFS= read -r entry; do
dbg " ${entry}"
done <<< "${files}"
# Grab the full commit text, we will use that for many things
# We strip off the signed-off-by stuff AFTER we are done with parsing
# this text
commit_text=$(cd ${KERNEL_TREE} && git show --no-patch --pretty=format:"%B" "${GIT_SHA_FULL}")
#echo "commit_text=${commit_text}"
# Fixes matching, this is the "meat" of the logic for what people are really
# looking for as no one ever seemed to do it in the past, and is one of the
# primary reasons we are tracking these ourselves now.
#
# The logic here is a bit complex, as we have a number of different situations
# that a commit could have. We track all of this in different variables:
# og_vuln:
# "Original vulnerable kernel version"
# Version where in mainline the problem first showed up. If this can not
# be determined, "0" is used here.
# vuln_kernels:
# "Vulnerable kernels"
# An array of kernel versions that describes where the problem first
# occured. This list is not always present and can not always be
# determined, so it might be empty. If the vulnerable kernel(s) can be
# determined, this variable will be an array of a it will be a mix of
# stable releases and hopefully a release in Linus's tree (but NOT a -rc
# release, as we don't track them this way because we do not consider
# them to be valid versions to care about.) Note, the list is sorted in
# "sort -V" order, and Linus's releases CAN be in the middle of the list,
# so beware of this when trying to match versions up (see below for
# matching logic.)
# fixed_kernels:
# "Fixed kernels"
# An array of kernel versions where the fix showed up in. As we always
# have at least one valid commit id (by virtue of this being required to
# run this script), this array will always have at least one version
# number in it.
# Note, this list will contain numbers that either describe a stable
# release (X.Y.Z from linux-stable) or a released version (X.Y from
# Linus's tree, we do not track -rc releases because they are development
# kernels.)
#
# Here's the different states, with example commit
# ids so that they can be used for testing:
#
# - Commit is only in a stable kernel tree, with no Fixes: tag
# For this the og_vuln will be set to "0" and fixed_kernels will have just
# one stable kernel release version in the array.
# Example: 10d75984495f ("Revert "NFSD: Fix possible sleep during nfsd4_release_lockowner()"")
#
# - Commit is only in a stable kernel tree, with a Fixes: tag
# For this the og_vuln will be set to the released version and fixed_kernels
# will have just a one stable kernel release in the array.
# Example: 2a8664583d4d ("fs: sysfs_emit_at: Remove PAGE_SIZE alignment check")
#
# - Commit is only in a stable kernel tree, with multiple Fixes: tags
# For this the og_vuln will be set to the released version and fixed_kernels
# will have multiple stable kernel releases in the array.
# Example: rare, I can't find one...
#
# - Commit is in Linus's tree, no stable trees, with no Fixes: tag
# For this the og_vuln will be set to 0 and fixed_kernels will have just the
# single released version in the array.
# This is rare, the example here is not really a bugfix, but you can use it
# for testing:
# Example: 94959c0e796e ("i2c: make i2c_bus_type const")
#
# - Commit is in Linus and stable trees, with no Fixes: tag
# For this the og_vuln will be set to 0 and fixed_kernels will be an array of
# versions where the commit is present.
# Example: c481016bb4f8 ("ASoC: qcom: sc8280xp: limit speaker volumes")
#
# - Commit is in Linus and stable trees, with Fixes: tag
# For this the og_vuln will be set to the "oldest" released kernel (by using
# 'sort -V' which should catch the correct version), and fixed_kernels will
# be an array of versions where the commit is present.
# Example: d9407ff11809 ("pds_core: Prevent health thread from running during reset/remove")
#
#
# Matching versions
# Ideally we want to match up when a vulnerability showed up, and when it was
# fixed, for each supported kernel branch we have at the time (list of
# supported kernel branches can be found on www.kernel.org) This logic isn't
# the most obvious, so here's our current solution in pseudo-code:
# For every fixed_entry in fixed_kernels do:
# if fixed_entry is a -rc kernel
# continue
# if og_vuln == 0
# create fix_pair(og_vuln, fixed_entry)
# continue
# for every vuln_entry in vuln_kernels do:
# if fixed_entry == vuln_entry
# continue, this is something added in this tree, so no need for an entry here.
# if fixed_entry == X.Y-type release (i.e. Linus release) && vuln_entry == X.Y-type release
# create fix_pair(vuln_entry, fixed_entry)
# continue
# if fixed_entry X.Y == vuln_entry X.Y
# create fix_pair(vuln_entry, fixed_entry)
# continue
# create fix_pair(og_vuln, fixed_entry)
#
# This matching allows us to catch places where a fix was done in Linus's tree,
# for a specific commit in Linus's tree, as well as catch when fixes are made
# in stable branches for issues created in that same stable branch.
# Here are some good commits to use for testing this all out:
# d6938c1c76c6 ("ipvs: avoid stat macros calls from preemptible context")
# c95f919567d6 ("nfc: llcp_core: Hold a ref to llcp_local->dev when holding a ref to llcp_local")
# 38d75297745f ("watchdog: set cdev owner before adding")
#
# 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
fixes_lines=$(echo "${commit_text}" | grep -i "fixes:" | sed -e 's/^[ \t]*//' | cut -f 2 -d ':' | sed -e 's/^[ \t]*//' | cut -f 1 -d ' ')
dbg "fixes_lines=${fixes_lines}"
og_vuln="0"
og_git="1da177e4c3f4" # "Linux-2.6.12-rc2"
if [[ "${fixes_lines}" != "" ]] ; then
# figure out what kernels this commit fixes, (i.e. which are
# vulnerable) and turn them into an array
v=()
for fix_line in ${fixes_lines}; do
x=$(find_fix ${fix_line})
v+=${x}
og_git=${fix_line} # fixme, what about multiples?
done
#dbg "v=${v[@]} size=${#v[@]}"
# now sort and uniq the list of versions
vuln_kernels=($(echo "${v[@]}" | sed 's/ /\n/g' | sort -V | uniq))
if [[ "${#vuln_kernels[@]}" == "0" ]] ; then
dbg "no vuln_kernels_found!"
else
dbg "${#vuln_kernels[@]} vuln_kernels found:"
for x in ${vuln_kernels[@]}; do
dbg " $x"
done
# Figure out the "original" oldest commit where this showed up,
# we need this as a default value in case we can't match up
# anything else to it, this must be a Linus release
for x in ${vuln_kernels[@]}; do
version_is_mainline "${x}"
mainline=$?
if [[ "${mainline}" == "1" ]] ; then
og_vuln="${x}"
break
fi
done
# Ick, no fixes version was found to be a mainline release, so
# we must have a stable-tree-only regression, so pick the
# "first" version as it must be the one.
if [[ "${og_vuln}" == "0" ]] ; then
og_vuln=${vuln_kernels[0]}
# og_git will be already set to the fixes
# commit, based on the loop up above, so no need
# to set it again.
fi
fi
dbg "og_vuln=${og_vuln}"
dbg "og_git=${og_git}"
fi
# Find the fixed kernels where this release was done
fixed_kernels=$("${FOUND_IN}" "${GIT_SHA_FULL}")
dbg "fixed_kernels=${fixed_kernels}"
create_fix_pair()
{
local v=$1
local f=$2
local v_git=$3
local f_git=$4
fixed_pairs+=("${v}:${f}:${v_git}:${f_git}")
dbg "pair=${v}:${f}:${v_git}:${f_git}"
}
find_stable_git_id()
{
#>&2 echo "find_stable_git_id: \"${1}\" \"${2}\""
local og_git=${1}
local fixed_version=${2}
local fixed_array=(${fixed_version//./ })
local fixed_major=${fixed_array[0]}
local fixed_minor=${fixed_array[1]}
local stable_git=$(cd ${KERNEL_TREE} && git log -1 --abbrev=12 --oneline --grep=${og_git} "v${fixed_major}.${fixed_minor}".."v${fixed_version}" | awk '{print $1}')
#>&2 echo "fine_stable_git_id: stable_git=${stable_git}"
echo "${stable_git}"
}
find_mainline_git_id()
{
local fixed_version=${1}
local stable_git=$(cd ${KERNEL_TREE} && git log -1 --abbrev=12 --oneline "v${fixed_version}" | awk '{print $1}')
echo "${stable_git}"
}
git_full_id()
{
local short_id=${1}
local long_id=$(cd ${KERNEL_TREE} && git log -1 --format="%H" "${short_id}")
echo "${long_id}"
}
# Do the crazy matching mess listed up above
fe=""
ve=""
# the default state is unaffected, unless a mainline kernel is touched,
# and then we will switch it below to affected.
default_status="unaffected"
for fixed_entry in ${fixed_kernels[@]}; do
create=0
# figure out if this is a -rc kernel or not
version_is_mainline "${fixed_entry}"
fixed_entry_mainline=$?
version_is_rc "${fixed_entry}"
fixed_entry_rc=$?
version_is_queue "${fixed_entry}"
fixed_entry_queue=$?
# Print out some debugging to see if kernel version detection is working:
dbg_string="${fixed_entry} is "
if [[ "${fixed_entry_rc}" == "1" ]]; then
dbg_string+="-rc and "
fi
if [[ "${fixed_entry_mainline}" == "1" ]]; then
dbg_string+="mainline"
else
dbg_string+="not mainline"
fi
dbg "${dbg_string}"
# if a commit is in a newer "stable" release, but not in an older one
# and only in the queue there, we get "queue-X.Y as a fixed kernel,
# which we don't want to have here, so filter that out.
if [[ "${fixed_entry_queue}" == "1" ]] ; then
dbg "${fixed_entry} is in queue"
continue
fi
# We do care about fixes in -rc kernels, as that is "mainline" and the
# logic of "affected/unaffected" depends on having a mainline commit,
# so that is why the following check is commented out on purpose.
# if [[ "${fixed_entry_rc}" == "1" ]] ; then
# dbg "${fixed_entry} is -rc"
# continue
# fi
# If we do not know what the root release is, then just create the pair
if [[ "${og_vuln}" == "0" ]] ; then
if [[ "${fixed_entry_mainline}" == "1" ]]; then
create_fix_pair ${og_vuln} ${fixed_entry} ${og_git} ${GIT_SHA_SHORT}
default_status="affected"
else
fe=$(find_stable_git_id ${GIT_SHA_FULL} ${fixed_entry})
create_fix_pair ${og_vuln} ${fixed_entry} ${og_git} ${fe}
fi
create=1
continue
fi
for vuln_entry in ${vuln_kernels[@]}; do
if [[ "${fixed_entry}" == "${vuln_entry}" ]] ; then
# this entry was broken and fixed in the same version,
# so skip it
create=1 # fake that an entry was created.
break
fi
version_is_mainline "${vuln_entry}"
vuln_entry_mainline=$?
if [[ "${vuln_entry_mainline}" == "1" ]] ; then
if [[ "${fixed_entry_mainline}" == "1" ]]; then
create_fix_pair ${vuln_entry} ${fixed_entry} ${og_git} ${GIT_SHA_SHORT}
default_status="affected"
else
fe=$(find_stable_git_id ${GIT_SHA_FULL} ${fixed_entry})
create_fix_pair ${vuln_entry} ${fixed_entry} ${og_git} ${fe}
fi
create=1
break
fi
version_match ${vuln_entry} ${fixed_entry}
match=$?
if [[ "${match}" == "1" ]] ; then
if [[ "${fixed_entry_mainline}" == "1" ]]; then
create_fix_pair ${vuln_entry} ${fixed_entry} ${og_git} ${GIT_SHA_SHORT}
default_status="affected"
else
ve=$(find_stable_git_id ${og_git} ${vuln_entry})
if [[ "${ve}" == "" ]] ; then
ve=${og_git}
fi
fe=$(find_stable_git_id ${GIT_SHA_FULL} ${fixed_entry})
if [[ "${fe}" == "" ]] ; then
fe=${GIT_SHA_SHORT}
fi
create_fix_pair ${vuln_entry} ${fixed_entry} "${ve}" "${fe}"
fi
create=1
break
fi
done
# If we haven't created anything yet, this must be it
if [[ ${create} == 0 ]] ; then
create_fix_pair ${og_vuln} ${fixed_entry} ${og_git} "${GIT_SHA_SHORT}"
default_status="affected"
fi
done
dbg "We have found ${#fixed_pairs[@]} vulnerable:fixed kernel pairs"
if [[ "${#fixed_pairs[@]}" == "0" ]] ; then
echo "No vulnerable and then fixed pairs of kernels were found for commit ${GIT_SHA_SHORT}"
exit 1
fi
for entry in "${fixed_pairs[@]}"; do
dbg " ${entry}"
done
dbg "default_status=${default_status}"
# Generate the "vulnerable" kernel json and mbox information
vuln_array_json=""
vuln_array_mbox=()
url_array=()
url_string_json=""
git_array_json=()
if [[ "${default_status}" == "affected" ]]; then
# we need to now say what the "unaffected" range is, so loop
# through and find the "mainline" part to figure that out.
for entry in "${fixed_pairs[@]}"; do
x=(${entry//:/ })
vuln=${x[0]}
fix=${x[1]}
vuln_git=${x[2]}
fix_git=${x[3]}
if [[ "${vuln}" == "0" ]]; then
#FIXME this needs to handle this case
dbg "FIXME, vuln=${0}"
else
version_is_mainline "${vuln}"
is_mainline=$?
if [[ "${is_mainline}" == "1" ]]; then
dbg "adding ${vuln} as where everything was affected"
vuln_array_json+="versions[]=$(jo -- \
-s version="${vuln}" \
-s status="affected" \
) "
vuln_array_json+="versions[]=$(jo -- \
-s version="0" \
-s lessThan="${vuln}" \
-s status="unaffected" \
-s versionType="custom" \
) "
fi
break
fi
done
fi
for entry in "${fixed_pairs[@]}"; do
x=(${entry//:/ })
vuln=${x[0]}
fix=${x[1]}
vuln_git=${x[2]}
fix_git=${x[3]}
# create the json array for the version numbers
if [[ "${default_status}" == "unaffected" ]]; then
# this is easy, our pairs are the versions that are
# affected, no tricky matching needs to happen here
vuln_array_json+="versions[]=$(jo -- \
-s version="${vuln}" \
-s lessThan="${fix}" \
-s status="affected" \
-s versionType="custom" \
) "
else
# much more tricky, we now need to say what ranges are
# both affected, AND unaffected. We handled the
# "affected" range above, so now our pairs show where
# things are "unaffected".
#
# By default, everything is affected from the "root" to
# the commit in mainline, so we have described that
# already above the loop, so this is just going to be
# the affected list...
#
# Note, the "mainline" fix shows where things "stop",
# so that gets a "short" record.
version_is_mainline "${fix}"
is_mainline=$?
if [[ "${is_mainline}" == "1" ]]; then
vuln_array_json+="versions[]=$(jo -- \
-s version="${fix}" \
-s lessThanOrEqual="*" \
-s status="unaffected" \
-s versionType="original_commit_for_fix" \
) "
else
# This is a stable range, so make an unaffected
# range with a wildcard
number_array=${fix}
REL_ARRAY=(${fix//./ })
MAJOR=${REL_ARRAY[0]}
MINOR=${REL_ARRAY[1]}
vuln_array_json+="versions[]=$(jo -- \
-s version="${fix}" \
-s lessThanOrEqual="${MAJOR}.${MINOR}.*" \
-s status="unaffected" \
-s versionType="custom" \
) "
fi
# FIXME, I think we need more logic here, but I can't
# remember why...
fi
# create the json array for the git ids
git_array_json+="versions[]=$(jo -- \
-s version="${vuln_git}" \
-s lessThan="${fix_git}" \
-s status="affected" \
-s versionType="git" \
) "
# If this issue has always been there, just say when it was
# fixed, otherwise try to give a hint when it was introduced.
# The json file just wants 0 for "always been there", so no need
# to check it for the array.
if [[ "${vuln}" == "0" ]] ; then
vuln_array_mbox+=("Fixed in ${fix} with commit ${fix_git}")
else
vuln_array_mbox+=("Issue introduced in ${vuln} with commit ${vuln_git} and fixed in ${fix} with commit ${fix_git}")
fi
# Add the git sha of the fix to the url array so we can print
# them later
long_id=$(git_full_id "${fix_git}")
url_array+=("https://git.kernel.org/stable/c/${long_id}")
url_string_json+="references[]=$(jo -- -s url="https://git.kernel.org/stable/c/${long_id}") "
done
dbg "vuln_array_json=${vuln_array_json}"
dbg "git_array_json=${git_array_json}"
for entry in "${vuln_array_mbox[@]}"; do
dbg "vuln_array_mbox=${entry}"
done
for entry in "${url_string_json[@]}"; do
dbg "url_string_json=${entry}"
done
# Strip off all of the signed-off-by stuff out of the commit text.
# We have a long list of "tags" to drop in the file, "tags", so compose
# the sed regex from the file and run the changelog through sed to strip
# things off.
# tags consist of one-line-per-tag, and we search the beginning of the
# line and a ':' character. This saves us from doing a whole bunch of:
# commit_text=$(echo "${commit_text}" | sed -e '/^cc:/Id;/^signed-off-by:/Id')
# calls.
sed_script=""
for tag in $(cat "${DIR}"/tags); do
sed_script+="/^${tag}:/Id;"
done
#dbg "sed_script=${sed_script}"
sed_file=$(mktemp "${TMPDIR}"/bippy.XXXX || exit 1)
echo "${sed_script}" > "${sed_file}"
commit_text=$(echo "${commit_text}" | sed -f "${sed_file}")
rm "${sed_file}"
# Add a prefix of what this is for, as per the CVE requirements as documented:
# https://www.cve.org/ResourcesSupport/AllResources/CNARules#section_8-2_cve_record_prose_description_requirements
commit_text=$(printf "In the Linux kernel, the following vulnerability has been resolved:\n\n%s" "${commit_text}")
dbg "commit_text length is ${#commit_text}"
# The json record description can only be 4096 bytes big (because bytes are
# expensive) So trim it at 4079, and add "---truncated---" text which brings it
# out to 4095, with 1 byte to spare incase people are off-by-one in their
# parsing logic
#json_commit_text=$(printf "%.4079s" "${commit_text}")
json_commit_text=$(printf "%.3982s" "${commit_text}") # really 3982 for now due to CVE backend issues
if [[ "${#commit_text}" != "${#json_commit_text}" ]]; then
# we truncated the text, so say so
json_commit_text=$(printf "%s\n---truncated---\n" "${json_commit_text}")
fi
dbg "json_commit_text length is ${#json_commit_text}"
#########################
# Compose the json knowing what we now know, using the 'jo' tool
#########################
if [[ "${JSON_FILE}" != "" ]] ; then
# NOTE, be VERY careful about the quoting around the bash
# variables when using 'jo', it isn't obvious, for some places
# we need the variables to be expanded without the "", and
# the shellcheck tool will complain, and test the heck out of
# any changes you make here, it seems to work as-is, so watch
# out, here lies many dragons. Comments have been added where
# needed and able to be used.
x_generator=$(jo -- engine="${SCRIPT}-${SCRIPT_VERSION}")
cveMetadata=$(jo -- assignerOrgId="${orig_id}" \
cveID="${CVE_NUMBER}" \
requesterUserId="${USER}" \
-s serial="1" \
state="PUBLISHED")
d=$(jo -- \
lang="en" \
-s value="${json_commit_text}" \
)
descriptions=$(jo -a -- "${d}")
providerMetadata=$(jo -- \
orgId="${orig_id}" \
)
f=""
while IFS= read -r entry; do
f+="${entry} "
done <<< "${files}"
# We want f to be expanded without quotes
# shellcheck disable=SC2086
program_files=$(jo -a -- ${f})
# FIXME: add program_files down below to a and ag in the proper place,
# but for now, the json is not validating on the CVE server side, so
# don't add it until that is figured out...
# We want vuln_array_json to be expanded without quotes
# shellcheck disable=SC2086
a=$(jo -- \
product="Linux" \
vendor="Linux" \
defaultStatus="${default_status}" \
repo="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git" \
${vuln_array_json} \
)
# We want git_array_json to be expanded without quotes
# shellcheck disable=SC2086
ag=$(jo -- \
product="Linux" \
vendor="Linux" \
defaultStatus="unaffected" \
repo="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git" \
${git_array_json} \
)
affected=$(jo -a -- "${ag}" "${a}")
# We want url_string_json to be expanded without quotes
# shellcheck disable=SC2086
cna=$(jo -- \
providerMetadata="${providerMetadata}" \
descriptions="${descriptions}" \
affected="${affected}" \
${url_string_json} \
title="${subject}" \
x_generator="${x_generator}" \
)
# We might just need the "cna" output, and not the "containers" output below.
# Test with the 'cve' tool a bit, I think this might be able to be dropped as
# the tool might provide it for us. If not, then just output the above cna
# record instead.
containers=$(jo -- cna="${cna}")
# output the final combination
jo -p -- \
containers="${containers}" \
cveMetadata="${cveMetadata}" \
-s dataType="CVE_RECORD" \
-s dataVersion="5.0" > "${JSON_FILE}"
dbg "json file written to ${JSON_FILE}"
fi # end json creation
#########################
# Compose the mbox file
#########################
if [[ "${MBOX_FILE}" != "" ]] ; then
cat << EOF > "${MBOX_FILE}"
From ${SCRIPT}-${SCRIPT_VERSION} Mon Sep 17 00:00:00 2001
From: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
To: <linux-cve-announce@vger.kernel.org>
Reply-to: <cve@kernel.org>
Subject: ${CVE_NUMBER}: ${subject}
Description
===========
${commit_text}
The Linux kernel CVE team has assigned ${CVE_NUMBER} to this issue.
Affected and fixed versions
===========================
EOF
for line in "${vuln_array_mbox[@]}"; do
echo " ${line}" >> "${MBOX_FILE}"
done
cat << EOF >> "${MBOX_FILE}"
Please see https://www.kernel.org or a full list of currently supported
kernel versions by the kernel community.
Unaffected versions might change over time as fixes are backported to
older supported kernel versions. The official CVE entry at
https://cve.org/CVERecord/?id=${CVE_NUMBER}
will be updated if fixes are backported, please check that for the most
up to date information about this issue.
Affected files
==============
The file(s) affected by this issue are:
EOF
while IFS= read -r entry; do
echo " ${entry}" >> "${MBOX_FILE}"
done <<< "${files}"
cat << EOF >> "${MBOX_FILE}"
Mitigation
==========
The Linux kernel CVE team recommends that you update to the latest
stable kernel version for this, and many other bugfixes. Individual
changes are never tested alone, but rather are part of a larger kernel
release. Cherry-picking individual commits is not recommended or
supported by the Linux kernel community at all. If however, updating to
the latest release is impossible, the individual changes to resolve this
issue can be found at these commits:
EOF
for url in "${url_array[@]}"; do
echo " ${url}" >> "${MBOX_FILE}"
done
dbg "mbox file written to ${MBOX_FILE}"
fi # end mbox creation
# all done!
exit 0