#!/bin/bash
# Copyright (C) 2018 Red Hat, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
VERSION="1.2"
# Warning! Be sure to download the latest version of this script from its primary source:
#
https://access.redhat.com/security/vulnerabilities/l1tf
# DO NOT blindly trust any internet sources and NEVER do `curl something | bash`!
# This script is meant for simple detection of the vulnerability. Feel free to modify it for your
# environment or needs. For more advanced detection, consider Red Hat Insights:
#
https://access.redhat.com/products/red-hat-insights#getstarted
ARTICLE="
https://access.redhat.com/security/vulnerabilities/l1tf"
ARTICLE_PERF="
https://access.redhat.com/security/vulnerabilities/l1tf-perf"
basic_args() {
# Parses basic commandline arguments and sets basic environment.
#
# Args:
# parameters - an array of commandline arguments
#
# Side effects:
# Exits if --help parameters is used
# Sets COLOR constants and debug variable
local parameters=( "$@" )
RED="\\033[1;31m"
GREEN="\\033[1;32m"
BOLD="\\033[1m"
RESET="\\033[0m"
for parameter in "${parameters[@]}"; do
if [[ "$parameter" == "-h" || "$parameter" == "--help" ]]; then
echo "Usage: $( basename "$0" ) [-n | --no-colors] [-d | --debug]"
exit 1
elif [[ "$parameter" == "-n" || "$parameter" == "--no-colors" ]]; then
RED=""
GREEN=""
BOLD=""
RESET=""
elif [[ "$parameter" == "-d" || "$parameter" == "--debug" ]]; then
debug=true
fi
done
}
basic_reqs() {
# Prints common disclaimer and checks basic requirements.
#
# Args:
# CVE - string printed in the disclaimer
#
# Side effects:
# Exits when 'rpm' command is not available
local CVE="$1"
# Disclaimer
echo
echo -e "$CVE Detection Script Ver. $VERSION"
echo -e "${BOLD}This script is primarily designed to detect $CVE on supported"
echo -e "Red Hat Enterprise Linux systems and kernel packages."
echo -e "Result may be inaccurate for other RPM based systems.${RESET}"
echo
# RPM is required
if ! command -v rpm &> /dev/null; then
echo "'rpm' command is required, but not installed. Exiting."
exit 1
fi
}
read_array() {
# Reads lines from stdin and saves them in a global array referenced by a name.
# It is a poor man's readarray compatible with Bash 3.1.
#
# Args:
# array_name - name of the global array
#
# Side effects:
# Overwrites content of the array 'array_name' with lines from stdin
local array_name="$1"
local i=0
while IFS= read -r line; do
read -r "$array_name[$(( i++ ))]" <<< "$line"
done
}
check_supported_kernel() {
# Checks if running kernel is supported.
#
# Args:
# running_kernel - kernel string as returned by 'uname -r'
#
# Side effects:
# Exits when running kernel is obviously not supported
local running_kernel="$1"
# Check supported platform
if [[ "$running_kernel" != *".el"[5-7]* ]]; then
echo -e "This script is meant to be used only on Red Hat Enterprise Linux 5, 6 and 7."
exit 1
fi
}
get_rhel() {
# Gets RHEL number.
#
# Args:
# running_kernel - kernel string as returned by 'uname -r'
#
# Prints:
# RHEL number, e.g. '5', '6', or '7'
local running_kernel="$1"
local rhel
rhel=$( sed -r -n 's/^.*el([[:digit:]]).*$/\1/p' <<< "$running_kernel" )
echo "$rhel"
}
check_affected_cpu() {
# Checks for affected CPU vendor/architecture.
#
# Prints:
# CPU vendor, e.g. 'Intel', 'AMD', 'POWER', or 'Other'
#
# Returns:
# 1 if affected CPU found, otherwise 0
#
# Notes:
# MOCK_CPU_INFO_PATH can be used to mock /proc/cpuinfo file
local cpuinfo=${MOCK_CPU_INFO_PATH:-/proc/cpuinfo}
if grep --quiet "GenuineIntel" "$cpuinfo"; then
echo "Intel"
return 1
fi
if grep --quiet "AuthenticAMD" "$cpuinfo"; then
echo "AMD"
return 0
fi
if grep --quiet "POWER" "$cpuinfo"; then
echo "POWER"
return 0
fi
echo "Other"
return 0
}
check_updated_kernel() {
# Checks the kernel was updated to fixed one.
#
# Returns:
# 1 if using the updated kernel, otherwise 0
#
# Notes:
# MOCK_VULN_FILE_PATH can be used to mock /sys/devices/system/cpu/vulnerabilities/l1tf file
# MOCK_SMT_IFACE_PATH can be used to mock /sys/devices/system/cpu/smt directory
local vuln_file=${MOCK_VULN_FILE_PATH:-/sys/devices/system/cpu/vulnerabilities/l1tf}
local smt_iface=${MOCK_SMT_IFACE_PATH:-/sys/devices/system/cpu/smt}
# Updated kernel introduces new vulnerability file in sysfs interface:
# /sys/devices/system/cpu/vulnerabilities/l1tf
# And interface for disabling SMT:
# /sys/devices/system/cpu/smt
# /sys/devices/system/cpu/smt/active
# /sys/devices/system/cpu/smt/control
if [[ -f "${vuln_file}" && -d "${smt_iface}" ]]; then
# Both available - vulnerability file and interface for disabling SMT.
return 1
fi
return 0 # Kernel not updated.
}
check_updated_microcode() {
# Checks if microcode was updated and properly loaded. Updated kernel
# with updated microcode introduces cpu flag 'flush_l1d'.
#
# Returns:
# 1 if microcode is updated, otherwise 0
#
# Notes:
# MOCK_CPU_INFO_PATH can be used to mock /proc/cpuinfo file
local cpuinfo=${MOCK_CPU_INFO_PATH:-/proc/cpuinfo}
local cpuflags
local flags
cpuflags=()
read_array cpuflags <<< "$( sed -rn 's/flags\s+: (.*)/\1/p' "$cpuinfo" )"
for flags in "${cpuflags[@]}"; do
# Any CPU that does not have the flag present.
if grep --quiet -v 'flush_l1d' <<< "$flags" ; then
return 0 # Microcode not updated.
fi
done
# The flag 'flush_l1d' is present for all CPUs. Microcode was updated.
return 1
}
check_mitigation_active() {
# Check the mitigation type in the vulnerability file.
#
# Prints:
# Value from vulnerability file, e.g. 'Mitigation: PTE Inversion', 'Vulnerable',
# 'Mitigation: PTE Inversion; VMX: SMT vulnerable, L1D conditional cache flushes',
# or 'Not affected'
#
# Returns:
# 1 if mitigation active, otherwise 0
#
# Notes:
# MOCK_VULN_FILE_PATH can be used to mock /sys/devices/system/cpu/vulnerabilities/l1tf file
local vuln_file=${MOCK_VULN_FILE_PATH:-/sys/devices/system/cpu/vulnerabilities/l1tf}
local status
# Is cpu/vulnerability files present?
if [[ -r "${vuln_file}" ]]; then
# Read status from vulnerability file.
status=$( <"$vuln_file" )
# With command 'l1tf=off' file contains this value on rhel-7 and
# the mitigation is not active.
if grep --quiet 'L1D vulnerable' <<< "$status"; then
echo "$status"
return 0 # Mitigation not active.
fi
# The status should not be 'Vulnerable'.
if grep --quiet -v 'Vulnerable' <<< "$status"; then
echo "$status"
return 1 # Mitigation active.
fi
fi
return 0 # Mitigation not active.
}
check_smt_enabled() {
# Check if hyper-threading (SMT) is enadled.
# 'lscpu' is not available on rhel-5 and cannot be used for detection.
#
# Returns:
# 1 if smt enabled, otherwise 0
#
# Notes:
# MOCK_CPU_DIRS_PATH can be used to mock files in /sys/devices/system/cpu.
local sysfs_cpu_dirs=${MOCK_CPU_DIRS_PATH:-/sys/devices/system/cpu}
local cpu_number
local main_thread_number
local siblings_list_file
# At least one SMT CPU thread is enabled.
for cpu_directory in "${sysfs_cpu_dirs}"/cpu[0-9]*; do
cpu_number="${cpu_directory##*cpu}"
siblings_list_file="$cpu_directory/topology/thread_siblings_list"
# Example output with disabled SMT: '0'
# Example output with enabled SMT: '0-1'
if [[ -r "${siblings_list_file}" ]]; then
main_thread_number=$( < "$siblings_list_file" )
if (( cpu_number != main_thread_number )); then
return 1 # SMT enabled
fi
fi
done
return 0 # SMT disabled
}
require_root() {
# Checks if user is root.
#
# Side effects:
# Exits when user is not root.
#
# Notes:
# MOCK_EUID can be used to mock EUID variable
local euid=${MOCK_EUID:-$EUID}
# Am I root?
if (( euid != 0 )); then
echo "This script must run with elevated privileges (e.g. as root)"
exit 1
fi
}
get_virtualization() {
# Gets virtualization type.
#
# Prints:
# Virtualization type, "None", or "virt-what not available"
local virt
if command -v virt-what &> /dev/null; then
virt=$( virt-what 2>&1 | tr '\n' ' ' )
if [[ "$virt" ]]; then
echo "$virt"
else
echo "None"
fi
else
echo "virt-what not available"
fi
}
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
require_root # Needed for virt-what and reading debugfs
basic_args "$@"
basic_reqs "CVE-2018-3620"
running_kernel=$( uname -r )
check_supported_kernel "$running_kernel"
rhel=$( get_rhel "$running_kernel" )
if [[ "$rhel" == "5" ]]; then
export PATH='/sbin':$PATH
fi
# Checks
vendor=$( check_affected_cpu )
# shellcheck disable=SC2181
affected_cpu=$(( $? )) # store 1 as True, 0 as False
check_updated_kernel
# shellcheck disable=SC2181
kernel_updated=$(( $? )) # store 1 as True, 0 as False
check_updated_microcode
# shellcheck disable=SC2181
microcode_updated=$(( $? )) # store 1 as True, 0 as False
mitigation=$( check_mitigation_active )
# shellcheck disable=SC2181
mitigation_active=$(( $? )) # store 1 as True, 0 as False
check_smt_enabled
# shellcheck disable=SC2181
smt_enabled=$(( $? )) # store 1 as True, 0 as False
virtualization=$( get_virtualization )
# System is vulnerable when it has affected CPU and at least one
# of required mitigation steps is not applied.
vulnerable=$(( affected_cpu && (!kernel_updated || !mitigation_active) ))
# Debug prints
if [[ "$debug" ]]; then
variables=( running_kernel rhel vendor affected_cpu kernel_updated
microcode_updated mitigation_active mitigation smt_enabled
virtualization vulnerable
)
for variable in "${variables[@]}"; do
echo "$variable = *${!variable}*"
done
echo
fi
# Output
echo -e "CPU vendor: ${BOLD}$vendor${RESET}"
echo -e "Running kernel: ${BOLD}$running_kernel${RESET}"
echo -e "Virtualization: ${BOLD}$virtualization${RESET}"
if (( smt_enabled )); then
echo -e "SMT status: ${BOLD}On${RESET}"
else
echo -e "SMT status: ${BOLD}Off${RESET}"
fi
if (( mitigation_active )); then
echo -e "Mitigation: ${BOLD}$mitigation${RESET}"
fi
echo
# Results
if (( vulnerable )); then
echo -e "This system is ${RED}vulnerable${RESET} for the following reasons:"
if (( ! kernel_updated )); then
echo -e "* Kernel is not updated"
fi
if (( ! mitigation_active )); then
echo -e "* Mitigation is not active"
fi
echo
echo "Red Hat recommends that you:"
if (( ! kernel_updated )); then
echo -e "* Update your kernel and reboot the system."
echo -e "* Optionally ask your HW vendor for CPU microcode update if available."
fi
if (( kernel_updated && ! mitigation_active )); then
echo -e "* Activate the mitigation provided by the kernel"
fi
echo
result=1
else
if (( ! affected_cpu )); then
echo -e "This system is ${GREEN}not vulnerable${RESET}, because your CPU vendor is not affected."
echo
else
echo -e "This system is ${GREEN}not vulnerable${RESET}, because it has correct mitigation applied."
echo
fi
result=0
fi
# Final notes
if (( affected_cpu && ! microcode_updated )); then
echo -e "${BOLD}Note about microcode update${RESET}"
echo -e "Customers desiring performance improvement will need to ask their HW vendor for CPU microcode update"
echo -e "in order to use the mitigation implemented in the microcode."
echo
fi
if (( affected_cpu && smt_enabled )); then
echo -e "${BOLD}Note about Hyper-Threading (SMT)${RESET}"
echo -e "Customers desiring to completely mitigate this issue will need to consider disabling SMT."
echo -e "For details how to disable SMT see:"
echo -e "
https://access.redhat.com/solutions/352663"
echo
fi
if [[ "$virtualization" != "None" ]]; then
echo -e "${BOLD}Note about virtualization${RESET}"
echo -e "${BOLD}CVE-2018-3646${RESET} is the CVE identifier assigned to the virtualization escape aspect of the"
echo -e "flaw. In virtualized environment, there are more steps to mitigate the issue, including:"
echo -e "* Host needs to have updated kernel and CPU microcode"
echo -e "* Host needs to have updated virtualization software"
echo -e "* Guest needs to have updated kernel"
echo -e "* Hypervisor needs to propagate new CPU features correctly"
echo -e "For more details about mitigations in virtualized environment see the vulnerability article."
echo
fi
echo -e "For more information about the vulnerability see:"
echo -e "$ARTICLE"
echo
echo -e "For more information about possible performance impact see:"
echo -e "$ARTICLE_PERF"
exit "$result"
fi