| #!/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}" |
| } |
| |
| ############################# |
| # get_kernel_version_type() |
| # Determine the type of a kernel version (mainline, rc, queue, or stable) |
| # arguments: |
| # "kernel version" |
| # returns: |
| # Prints one of: "mainline", "rc", "queue", or "stable" |
| ############################# |
| get_kernel_version_type() { |
| local VERSION=$1 |
| |
| # First check if it's a queue version since that's a simple string match |
| if [[ "${VERSION}" =~ .*"queue" ]]; then |
| echo "queue" |
| return |
| fi |
| |
| # Parse version number into components |
| # shellcheck disable=SC2206 |
| local VERSION_PARTS=(${VERSION//./ }) |
| local MAJOR=${VERSION_PARTS[0]} |
| |
| # Check for -rc versions |
| if [[ "${VERSION}" =~ .*"rc" ]]; then |
| echo "rc" |
| return |
| fi |
| |
| # 2.6.X is just one more level "deep" |
| if [[ "${MAJOR}" == "2" ]]; then |
| if [[ "${#VERSION_PARTS[@]}" == "3" ]]; then |
| echo "mainline" |
| return |
| fi |
| fi |
| |
| # If version only has X.Y format (no .Z), it's mainline |
| if [[ "${#VERSION_PARTS[@]}" == "2" ]]; then |
| echo "mainline" |
| return |
| fi |
| |
| # Otherwise it's a stable release |
| echo "stable" |
| } |
| |
| ############################# |
| # kernel_version_is_rc() |
| # Check if a kernel version is a release candidate |
| # |
| # Arguments: |
| # $1: Kernel version string to check |
| # |
| # Returns: |
| # 1 if version is a release candidate, 0 otherwise |
| # |
| # Example: |
| # kernel_version_is_rc "6.7-rc1" # Returns: 1 |
| # kernel_version_is_rc "6.7" # Returns: 0 |
| ############################# |
| kernel_version_is_rc() { |
| local TYPE |
| TYPE=$(get_kernel_version_type "$1") |
| [[ "${TYPE}" == "rc" ]] && return 1 || return 0 |
| } |
| |
| ############################# |
| # kernel_version_is_queue() |
| # Check if a kernel version is a queue version |
| # |
| # Arguments: |
| # $1: Kernel version string to check |
| # |
| # Returns: |
| # 1 if version is a queue version, 0 otherwise |
| # |
| # Example: |
| # kernel_version_is_queue "6.7-queue" # Returns: 1 |
| # kernel_version_is_queue "6.7" # Returns: 0 |
| ############################# |
| kernel_version_is_queue() { |
| local TYPE |
| TYPE=$(get_kernel_version_type "$1") |
| [[ "${TYPE}" == "queue" ]] && return 1 || return 0 |
| } |
| |
| ############################# |
| # kernel_version_is_mainline() |
| # Check if a kernel version is mainline |
| # Note: Both pure mainline and rc versions are considered mainline |
| # |
| # Arguments: |
| # $1: Kernel version string to check |
| # |
| # Returns: |
| # 1 if version is mainline or rc, 0 otherwise |
| # |
| # Example: |
| # kernel_version_is_mainline "6.7" # Returns: 1 |
| # kernel_version_is_mainline "6.7-rc1" # Returns: 1 |
| # kernel_version_is_mainline "6.7.1" # Returns: 0 |
| ############################# |
| kernel_version_is_mainline() { |
| local TYPE |
| TYPE=$(get_kernel_version_type "$1") |
| [[ "${TYPE}" == "mainline" || "${TYPE}" == "rc" ]] && return 1 || return 0 |
| } |
| |
| ############################# |
| # kernel_version_match() |
| # Compare two kernel version strings to see if they share the same major.minor |
| # version numbers (X.Y), ignoring any patch version (Z). For example, |
| # "4.9.1" and "4.9.2" match, while "4.9.1" and "4.10.1" don't. |
| # |
| # Arguments: |
| # $1: First kernel version string (format: X.Y or X.Y.Z) |
| # $2: Second kernel version string (format: X.Y or X.Y.Z) |
| # |
| # Returns: |
| # 1 if major.minor versions match |
| # 0 if they don't match |
| # |
| # Example: |
| # kernel_version_match "4.9.1" "4.9.2" # returns 1 (match) |
| # kernel_version_match "4.9" "4.9.1" # returns 1 (match) |
| # kernel_version_match "4.9.1" "4.10.1" # returns 0 (no match) |
| ############################# |
| kernel_version_match() |
| { |
| local v1=(${1//./ }) |
| local v2=(${2//./ }) |
| [[ "${v1[0]}" == "${v2[0]}" && "${v1[1]}" == "${v2[1]}" ]] && return 1 || 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 |
| if [[ "${fixed_major}" == "2" ]]; then |
| # Ugh, 2.6.x.y, let's move everything left by one |
| fixed_major="2.6" |
| fixed_minor=${fixed_array[2]} |
| else |
| fixed_minor=${fixed_array[1]} |
| fi |
| |
| local stable_git |
| stable_git=$($git_cmd 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=$($git_cmd 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 ("subject") |
| # returns |
| # "full git id" if found, "" if not found |
| # assumes: |
| # KERNEL_TREE points to a valid Linux stable git tree |
| ############################# |
| git_full_id() |
| { |
| local arg="$@" |
| local short_id=${arg%% *} |
| local subject=${arg#* } |
| local long_id |
| local id="" |
| |
| long_id=($($git_cmd rev-parse --disambiguate="${short_id}" 2> /dev/null)) |
| if [ ${#long_id[@]} -eq 1 ]; then |
| echo "${long_id[0]}" |
| return |
| fi |
| if [ ${#long_id[@]} -eq 0 ]; then |
| echo "" |
| return |
| fi |
| |
| # Otherwise, disambiguate the short id. |
| |
| # Strip the ("...") around the Subject. |
| subject=$(echo "${subject}" | sed -e 's/("\(.*\)")[ \t]*$/\1/') |
| for id in "${long_id[@]}"; do |
| local id_subject=$($git_cmd log -1 --format="%s" "${id}") |
| if [ "${id_subject}" == "${subject}" ]; then |
| # Found the matching subject; stop here. |
| echo "${id}" |
| return |
| fi |
| done |
| } |
| |
| ############################# |
| # 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=$($git_cmd 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 |
| local type |
| type=$(get_kernel_version_type "${kernel}") |
| |
| local dbg_string="${kernel} is " |
| case "${type}" in |
| "queue") dbg_string+="queue ";; |
| "rc") dbg_string+="-rc and mainline";; |
| "mainline") dbg_string+="mainline";; |
| "stable") dbg_string+="not mainline";; |
| esac |
| |
| 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 |
| git_cmd="git --git-dir=${KERNEL_TREE}/.git" |
| |
| 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}ERROR:${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 as the |
| # commit that will be deemed the "vulnerable" version (and might have been |
| # backported). |
| # |
| # 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. |
| v=() |
| 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=$($git_cmd 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}" |
| v+=("${full_id}") |
| else |
| # Grab the full commit text, we need that to figure out what ids this commit |
| # actually fixes (if any) |
| commit_text=$($git_cmd 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]*//') |
| temp="${vuln_lines}" |
| dbg "vuln_lines=${temp}" |
| if [[ "${vuln_lines}" != "" ]] ; then |
| # get a list of vulnerable kernels, sort them by the order in |
| # which they are in the tree, oldest first. |
| while read line; do |
| full_id=$(git_full_id "${line}") |
| if [[ "${full_id}" == "" ]]; then |
| dbg "invalid fix entry of '${line}', skipping" |
| continue |
| fi |
| v+=("${full_id}") |
| done <<<"${vuln_lines}" |
| fi |
| fi |
| |
| # We now have a list of "vulnerable" kernels in v(), so walk them and create a |
| # bunch of matching "vulnerable : fixed" kernel pairs |
| 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 -t "${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=$($git_cmd 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_queue "${kernel}" |
| kernel_is_queue=$? |
| if [[ "${kernel_is_queue}" == 1 ]] ; then |
| continue |
| fi |
| |
| 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 |
| |
| 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 |
| if [[ "${vuln_mainline_pair}" == "" ]]; then |
| dbg " no mainline pair vulnerable at this point in time (fix in the future?), so skipping ${fixed_version} for now" |
| else |
| dbg " nothing found for ${fixed_version}, using default of ${vuln_mainline_pair}" |
| create_fixed_pair "${vuln_mainline_pair}" "${fixed_entry}" |
| fi |
| 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 |