mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
The cpictl utility sends control-program identification data from protected virtualization guests to hosts by default. This behaviour leaks the below potentially sensitive information to untrusted hosts. - system_type - system_level - sysplex_name - system_name To prevent this behaviour, enhance the cpictl utility to stop setting CPI information on protected virtualization guests by default. If the user chooses to set the CPI information, it could be set by one of the below options - use the command line option --permit-cpi - set the environment variable CPI_PERMIT_ON_PVGUEST to 1 to control the CPI service behaviour during boot Signed-off-by: Hendrik Brueckner <brueckner@linux.ibm.com> Signed-off-by: Shalini Chellathurai Saroja <shalini@linux.ibm.com> Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com> Reviewed-by: Peter Oberparleiter <oberpar@linux.ibm.com> Reviewed-by: Hendrik Brueckner <brueckner@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
552 lines
13 KiB
Bash
Executable File
552 lines
13 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# cpictl - Configure the Control-Program-Information (CPI) settings
|
|
#
|
|
# This is an internal helper script that is used by the "cpi.service"
|
|
# systemd unit and the "90-cpi.rules" udev rule.
|
|
#
|
|
# The bash shell is really needed. Other shells have different ideas of how
|
|
# bitwise operators work.
|
|
#
|
|
# Copyright 2017 IBM Corp.
|
|
#
|
|
# s390-tools is free software; you can redistribute it and/or modify
|
|
# it under the terms of the MIT license. See LICENSE for details.
|
|
#
|
|
|
|
readonly CPI_LOCK="/var/lock/cpictl.lock"
|
|
|
|
readonly PRG="${0##*/}"
|
|
|
|
readonly SYSTEM_LEVEL_PATH="/sys/firmware/cpi/system_level"
|
|
readonly SYSTEM_TYPE_PATH="/sys/firmware/cpi/system_type"
|
|
readonly SYSTEM_NAME_PATH="/sys/firmware/cpi/system_name"
|
|
readonly SYSPLEX_NAME_PATH="/sys/firmware/cpi/sysplex_name"
|
|
readonly CPI_SET="/sys/firmware/cpi/set"
|
|
|
|
# Location of os-release file - can be specified externally for testing purpose
|
|
readonly OS_RELEASE=${CPI_OS_RELEASE:-"/etc/os-release"}
|
|
|
|
declare LEVEL
|
|
declare TYPE
|
|
declare NAME
|
|
declare SYSPLEX
|
|
|
|
declare PV_GUEST
|
|
declare -i CPI_PERMIT="$CPI_PERMIT_ON_PVGUEST"
|
|
|
|
declare -i DRYRUN=0
|
|
|
|
# Exit codes
|
|
readonly EXIT_SUCCESS=0
|
|
readonly EXIT_FAILURE=1
|
|
readonly EXIT_ARG_TOO_LONG=3
|
|
readonly EXIT_INVALID_CHARS=4
|
|
readonly EXIT_INVALID_ARGS=5
|
|
readonly EXIT_NO_PERMIT_CPI=6
|
|
|
|
# Distro-IDs as supported by SE/HMC firmware
|
|
readonly DISTRO_GENERIC=0
|
|
readonly DISTRO_RHEL=1
|
|
readonly DISTRO_SLES=2
|
|
readonly DISTRO_UBUNTU=3
|
|
readonly DISTRO_FEDORA=4
|
|
readonly DISTRO_OPENSUSE=5
|
|
readonly DISTRO_DEBIAN=6
|
|
readonly DISTRO_RHCOS=7
|
|
|
|
print_help_and_exit()
|
|
{
|
|
cat <<EndHelp
|
|
Usage: $PRG [OPTIONS]
|
|
|
|
Configure the Control-Program-Information (CPI) settings.
|
|
|
|
-b, --set-bit BIT Set and commit the bit BIT in the flags
|
|
-e, --environment Set and commit the system type, level, name and
|
|
sysplex name with values taken from environment
|
|
variables
|
|
-h, --help Print this help, then exit
|
|
-L, --level LEVEL Set and commit OS level to LEVEL. Format: 0x<level> or
|
|
[[[flags:]distro_id:distro_version:]kernel_version]
|
|
-N, --name SYSTEM Set and commit the system name to SYSTEM
|
|
-S, --sysplex SYSPLEX Set and commit the sysplex name to SYSPLEX
|
|
-T, --type TYPE Set and commit OS type to TYPE
|
|
-v, --version Print version information, then exit
|
|
--permit-cpi Permit to send Control-Program Identification data of
|
|
protected virtualization guest to the host (must be
|
|
specified before any commit option). See also the
|
|
important note.
|
|
--commit Ignore all other options and commit any uncommitted
|
|
values
|
|
--dry-run Do not actually set or commit anything, but show what
|
|
would be done
|
|
--show Ignore all other options and show current (possibly
|
|
uncommitted) values
|
|
|
|
Environment variables used for the --defaults option:
|
|
CPI_SYSTEM_TYPE, CPI_SYSTEM_LEVEL, CPI_SYSTEM_NAME, CPI_SYSPLEX_NAME,
|
|
CPI_PERMIT_ON_PVGUEST (See also the important note.)
|
|
|
|
Important: Set CPI_PERMIT_ON_PVGUEST=1 or use --permit_cpi option only if you
|
|
trust the host system. Enabling these options allows the host to receive
|
|
potentially sensitive Control-Program Identification (CPI) data from the
|
|
protected virtualization guest, including:
|
|
- system_type
|
|
- system_level
|
|
- sysplex_name
|
|
- system_name
|
|
|
|
Available bits for the --set-bit option:
|
|
kvm: Indicate that system is a KVM host
|
|
|
|
The maximum length for system type, system name, and sysplex name is 8
|
|
characters. The allowed characters are: [A-Z0-9 @#$]
|
|
|
|
This tool should be called as root.
|
|
EndHelp
|
|
exit $EXIT_SUCCESS
|
|
}
|
|
|
|
print_version_and_exit()
|
|
{
|
|
cat <<EndVersion
|
|
$PRG: Configure the CPI settings version %S390_TOOLS_VERSION%
|
|
Copyright IBM Corp. 2017
|
|
EndVersion
|
|
exit $EXIT_SUCCESS
|
|
}
|
|
|
|
cpi_show()
|
|
{
|
|
cat <<-EndShow
|
|
System type: $(cat "$SYSTEM_TYPE_PATH")
|
|
System level: $(cat "$SYSTEM_LEVEL_PATH")
|
|
System name: $(cat "$SYSTEM_NAME_PATH")
|
|
Sysplex name: $(cat "$SYSPLEX_NAME_PATH")
|
|
EndShow
|
|
}
|
|
|
|
print_parse_error_and_exit()
|
|
{
|
|
echo "Try '$PRG --help' for more information." >&2
|
|
exit $EXIT_FAILURE
|
|
}
|
|
|
|
fail_with()
|
|
{
|
|
echo "$1" >&2
|
|
echo "Try '$PRG --help' for more information." >&2
|
|
exit ${2:-$EXIT_FAILURE}
|
|
}
|
|
|
|
cpi_commit()
|
|
{
|
|
# Commit Control-Program Identification changes on protected
|
|
# virtualization guests only if it is permitted by the guest. This
|
|
# prevents leakage of potentially sensitive information to untrusted
|
|
# hosts.
|
|
if [[ -f "/sys/firmware/uv/prot_virt_guest" ]]; then
|
|
read -r PV_GUEST < "/sys/firmware/uv/prot_virt_guest"
|
|
if [[ "$PV_GUEST" -eq 1 ]]; then
|
|
if [[ -z "$CPI_PERMIT" ]] || [[ "$CPI_PERMIT" -ne 1 ]]; then
|
|
echo "Sending CPI data from secure execution Linux guests is disabled. Use --permit-cpi to enable CPI data." >&2
|
|
exit "$EXIT_NO_PERMIT_CPI"
|
|
fi
|
|
fi
|
|
fi
|
|
echo 1 > "$CPI_SET" 2> /dev/null
|
|
}
|
|
|
|
do_length_check()
|
|
{
|
|
[ ${#1} -gt 8 ] &&
|
|
fail_with "$PRG: Specified $2 too long. The maximum length is 8 characters." $EXIT_ARG_TOO_LONG
|
|
}
|
|
|
|
do_character_check()
|
|
{
|
|
echo "$1" | grep -q -E '^[a-zA-Z0-9@#$ ]*$' ||
|
|
fail_with "$PRG: Invalid characters in $2. Valid characters are: A-Z0-9 @#$" $EXIT_INVALID_CHARS
|
|
}
|
|
|
|
cpi_set_bit()
|
|
{
|
|
LEVEL=$(printf '0x%x' $((LEVEL | (1 << (63 - $1)) )) )
|
|
}
|
|
|
|
#
|
|
# split_version - Split generic version string into array of sub-versions
|
|
#
|
|
# @version: Version string
|
|
# @delim: Characters that delimit sub-versions in version string
|
|
# @num: Number of sub-versions
|
|
#
|
|
# Print @num sub-versions of @version where each sub-version is delimited by
|
|
# any of the characters in @delim. Print 0 in place of non-decimal or missing
|
|
# sub-versions.
|
|
#
|
|
# Examples:
|
|
# version=10 delim=. num=3 => 10 0 0
|
|
# version=4.8 delim=. num=2 => 4 8
|
|
#
|
|
split_version()
|
|
{
|
|
local version="$1" delim="$2" num="$3"
|
|
local list i subver
|
|
|
|
IFS="$delim" read -r -a list <<< "$version"
|
|
|
|
for (( i=0; i<num; i++ )) ; do
|
|
subver="${list[i]:-0}"
|
|
# Handle non-number sub-versions
|
|
[[ "$subver" =~ ^[0-9]+$ ]] || subver=0
|
|
# Force decimal interpretation in case of leading zeroes
|
|
subver=$(( 10#$subver ))
|
|
printf "%s " "$subver"
|
|
done
|
|
}
|
|
|
|
#
|
|
# split_kver - Split Linux kernel version string into array of sub-versions
|
|
#
|
|
# @version: Linux kernel version string
|
|
# @num: Number of sub-versions
|
|
#
|
|
# Print @num sub-versions of the specified kernel @version. Print 0 in place
|
|
# of non-decimal or missing sub-versions.
|
|
#
|
|
# Examples:
|
|
# version=2.4-13 num=4 => 2 4 0 13
|
|
# version=3.0.93_3.0.101-0.8.2_0.8.1 num=6 => 3 0 93 3 0 101
|
|
# version=4.12.14-lp150.11.4 num=5 => 4 12 14 0 11
|
|
#
|
|
split_kver()
|
|
{
|
|
local version="$1" num="$2"
|
|
local main extra
|
|
|
|
# Separate extra version to handle short main version (e.g. 2.4-13)
|
|
IFS="-_" read -r main extra <<< "$version"
|
|
|
|
split_version "$main" "." $(( num > 3 ? 3 : num ))
|
|
[[ "$num" -gt 3 ]] && split_version "$extra" ".-_" $(( num - 3 ))
|
|
}
|
|
|
|
#
|
|
# bytes_to_word - Convert byte array to hexadecimal word
|
|
#
|
|
# @bytes: List of numbers representing byte values
|
|
#
|
|
# Print a big-endian hexadecimal representation of the word that results from
|
|
# combining the specified byte values.
|
|
#
|
|
bytes_to_word()
|
|
{
|
|
printf "0x"
|
|
printf "%02x" "$@"
|
|
}
|
|
|
|
#
|
|
# get_system_level - Print system level word for specified distribution version
|
|
#
|
|
# @distro: Distro ID (ID value from /etc/os-release)
|
|
# @ver_str: Distro version string (VERSION_ID from /etc/os-release)
|
|
# @kver_str: Kernel version string (output of 'uname -r')
|
|
# @flags: Optional statistics flags
|
|
#
|
|
# Print a 64 bit hexadecimal system level in a format as understood by firmware.
|
|
#
|
|
# The format is 0xabccddeeeeffgghh, where
|
|
# - a=statistics flags
|
|
# - b=distro id
|
|
# - c=distro major version
|
|
# - d=distro minor version(s)
|
|
# - e=kernel sublevel 2
|
|
# - f=kernel version
|
|
# - g=kernel patchlevel
|
|
# - h=kernel sublevel 1
|
|
#
|
|
get_system_level()
|
|
{
|
|
local distro="$1" ver_str="$2" kver_str="$3" flags="${4:-0}"
|
|
local distro_id d_major d_minor d_minor2
|
|
local k_ver k_patchlvl k_sublvl k_sublvl2 bytes=()
|
|
|
|
# Extract list of sub-version numbers from version strings
|
|
read -r d_major d_minor d_minor2 <<< "$(split_version "$ver_str" "._-" 3)"
|
|
read -r k_ver k_patchlvl k_sublvl k_sublvl2 <<< "$(split_kver "$kver_str" 4)"
|
|
|
|
# Handle excessive sublevel numbers consistently
|
|
if [[ "$k_sublvl" -gt 255 ]] ; then
|
|
k_sublvl=0
|
|
fi
|
|
if [[ "$k_sublvl2" -gt 65535 ]] ; then
|
|
k_sublvl2=0
|
|
fi
|
|
|
|
# Apply distro-specific logic
|
|
case "$distro" in
|
|
"rhel")
|
|
distro_id=$DISTRO_RHEL
|
|
;;
|
|
|
|
"sles")
|
|
distro_id=$DISTRO_SLES
|
|
;;
|
|
|
|
"ubuntu")
|
|
distro_id=$DISTRO_UBUNTU
|
|
|
|
# Encode minor and update version numbers in minor field
|
|
(( d_minor=((d_minor & 0xf) * 0x10) + (d_minor2 & 0xf) ))
|
|
;;
|
|
|
|
"fedora")
|
|
distro_id=$DISTRO_FEDORA
|
|
;;
|
|
|
|
"opensuse-leap")
|
|
distro_id=$DISTRO_OPENSUSE
|
|
;;
|
|
|
|
"debian")
|
|
distro_id=$DISTRO_DEBIAN
|
|
;;
|
|
|
|
"rhcos")
|
|
distro_id=$DISTRO_RHCOS
|
|
;;
|
|
|
|
*)
|
|
distro_id=$DISTRO_GENERIC
|
|
|
|
# Reset unsupported fields
|
|
d_major=0
|
|
d_minor=0
|
|
k_sublvl2=0
|
|
;;
|
|
esac
|
|
|
|
# Assemble byte data
|
|
(( bytes[0] = (flags & 0xf) * 0x10 + distro_id ))
|
|
(( bytes[1] = d_major ))
|
|
(( bytes[2] = d_minor ))
|
|
(( bytes[3] = (k_sublvl2 / 256) & 0xff ))
|
|
(( bytes[4] = k_sublvl2 & 0xff ))
|
|
(( bytes[5] = k_ver ))
|
|
(( bytes[6] = k_patchlvl ))
|
|
(( bytes[7] = k_sublvl ))
|
|
|
|
# Print as single hex word
|
|
bytes_to_word "${bytes[@]}"
|
|
}
|
|
|
|
get_distro()
|
|
{
|
|
local line ID="linux" VERSION_ID="0" VERSION="" update
|
|
|
|
[[ ! -e "$OS_RELEASE" ]] && return
|
|
|
|
# Only import required variables
|
|
while read -r line ; do
|
|
if [[ "$line" =~ ^ID= ]] || [[ "$line" =~ ^VERSION_ID= ]] ||
|
|
[[ "$line" =~ ^VERSION= ]] ; then
|
|
eval "$line"
|
|
fi
|
|
done <"$OS_RELEASE"
|
|
|
|
if [[ "$ID" == "ubuntu" ]] ; then
|
|
# Extract update version number only found in VERSION, e.g.
|
|
# VERSION_ID="18.04" VERSION="18.04.5 LTS"
|
|
update="${VERSION/*$VERSION_ID/}"
|
|
update="${update%% *}"
|
|
VERSION_ID="$VERSION_ID$update"
|
|
fi
|
|
|
|
echo "$ID:$VERSION_ID"
|
|
}
|
|
|
|
cpi_set_oslevel()
|
|
{
|
|
local level="${1:-}"
|
|
local flags list distro_id distro_ver kver id ver
|
|
|
|
if [[ "$level" =~ ^0x ]] && ! [[ "$level" =~ : ]] ; then
|
|
# Format: level=0x<hex>
|
|
printf -v LEVEL "0x%016x" "$level" 2>/dev/null ||
|
|
fail_with "$PRG: Invalid hexadecimal number in $level" \
|
|
$EXIT_INVALID_CHARS
|
|
return
|
|
fi
|
|
|
|
# Format: level=[[[flags:]distro_id:distro_ver:]kver]
|
|
IFS=":" read -r -a list <<< "$level:"
|
|
kver="${list[*]: -1: 1}"
|
|
distro_ver="${list[*]: -2: 1}"
|
|
distro_id="${list[*]: -3: 1}"
|
|
flags="${list[*]: -4: 1}"
|
|
|
|
if [[ -z "$kver" ]] ; then
|
|
# Use version of currently running kernel
|
|
kver="$(uname -r)"
|
|
fi
|
|
|
|
if [[ -z "$distro_ver" ]] || [[ -z "$distro_id" ]] ; then
|
|
# Use distro ID and version from os-release file
|
|
IFS=":" read -r id ver <<< "$(get_distro)"
|
|
distro_id=${distro_id:-$id}
|
|
distro_ver=${distro_ver:-$ver}
|
|
fi
|
|
|
|
if [[ -z "$flags" ]] ; then
|
|
# Keep statistics flags from current system level
|
|
flags=$(( (LEVEL >> 60) & 0xf ))
|
|
fi
|
|
|
|
LEVEL=$(get_system_level "$distro_id" "$distro_ver" "$kver" "$flags")
|
|
}
|
|
|
|
cpi_set_type()
|
|
{
|
|
TYPE="$1"
|
|
do_length_check "$TYPE" "system type"
|
|
do_character_check "$TYPE" "system type"
|
|
}
|
|
|
|
cpi_set_sysplex()
|
|
{
|
|
SYSPLEX="$1"
|
|
do_length_check "$SYSPLEX" "sysplex name"
|
|
do_character_check "$SYSPLEX" "sysplex name"
|
|
}
|
|
|
|
cpi_set_name()
|
|
{
|
|
NAME="$1"
|
|
do_length_check "$NAME" "system name"
|
|
do_character_check "$NAME" "system name"
|
|
}
|
|
|
|
# cpictl starts here
|
|
|
|
if [ $# -le 0 ]; then
|
|
echo "$PRG: No parameters specified"
|
|
print_parse_error_and_exit
|
|
fi
|
|
|
|
opts=$(getopt -o b:ehL:N:S:T:v -l set-bit:,environment,help,level:,name:,sysplex:,type:,commit,dry-run,permit-cpi,show,version -n "$PRG" -- "$@")
|
|
if [ $? -ne 0 ]; then
|
|
print_parse_error_and_exit
|
|
fi
|
|
|
|
# This guarantees that only one instance will be running, and will serialize
|
|
# the execution of multiple instances
|
|
[ -e "$CPI_LOCK" -a ! -w "$CPI_LOCK" ] &&
|
|
fail_with "$PRG: Cannot access lock file: $CPI_LOCK"
|
|
[ ! -w "${CPI_LOCK%/*}" ] &&
|
|
fail_with "$PRG: Cannot access lock file: $CPI_LOCK"
|
|
|
|
exec 9<> "$CPI_LOCK"
|
|
flock -x 9
|
|
|
|
# Get current values from sys/firmware
|
|
read LEVEL < "$SYSTEM_LEVEL_PATH"
|
|
read TYPE < "$SYSTEM_TYPE_PATH"
|
|
read NAME < "$SYSTEM_NAME_PATH"
|
|
read SYSPLEX < "$SYSPLEX_NAME_PATH"
|
|
|
|
# Parse command line options: Use eval to remove getopt quotes
|
|
eval set -- $opts
|
|
while [ -n $1 ]; do
|
|
case "$1" in
|
|
--help|-h)
|
|
print_help_and_exit
|
|
;;
|
|
--version|-v)
|
|
print_version_and_exit
|
|
;;
|
|
-b|--set-bit)
|
|
case "$2" in
|
|
kvm)
|
|
cpi_set_bit 0
|
|
;;
|
|
*)
|
|
fail_with "$PRG: Unknown bit \"$2\" for the $1 option"
|
|
;;
|
|
esac
|
|
shift 2
|
|
;;
|
|
-L|--level)
|
|
cpi_set_oslevel "$2"
|
|
shift 2
|
|
;;
|
|
-e|--environment)
|
|
cpi_set_type "$CPI_SYSTEM_TYPE"
|
|
cpi_set_name "$CPI_SYSTEM_NAME"
|
|
cpi_set_oslevel "$CPI_SYSTEM_LEVEL"
|
|
cpi_set_sysplex "$CPI_SYSPLEX_NAME"
|
|
shift
|
|
;;
|
|
-T|--type)
|
|
cpi_set_type "$2"
|
|
shift 2
|
|
;;
|
|
-S|--sysplex)
|
|
cpi_set_sysplex "$2"
|
|
shift 2
|
|
;;
|
|
-N|--name)
|
|
cpi_set_name "$2"
|
|
shift 2
|
|
;;
|
|
--show)
|
|
cpi_show
|
|
exit $EXIT_SUCCESS
|
|
;;
|
|
--permit-cpi)
|
|
CPI_PERMIT=1
|
|
shift
|
|
;;
|
|
--commit)
|
|
cpi_commit
|
|
exit $EXIT_SUCCESS
|
|
;;
|
|
--dry-run)
|
|
DRYRUN=1
|
|
shift
|
|
;;
|
|
--)
|
|
shift
|
|
break
|
|
;;
|
|
*)
|
|
break;
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Unparsed options are not supported
|
|
if [ $# -ne 0 ]; then
|
|
fail_with "$PRG: Invalid command-line option: $*" $EXIT_INVALID_ARGS
|
|
fi
|
|
|
|
# Print settings for --dry-run or commit them to sysfs otherwise
|
|
|
|
if [ $DRYRUN -eq 1 ]; then
|
|
cat <<-EndDryrun
|
|
System type: $TYPE
|
|
System level: $LEVEL
|
|
System name: $NAME
|
|
Sysplex name: $SYSPLEX
|
|
EndDryrun
|
|
else
|
|
echo "$LEVEL" > "$SYSTEM_LEVEL_PATH"
|
|
echo "$TYPE" > "$SYSTEM_TYPE_PATH"
|
|
echo "$NAME" > "$SYSTEM_NAME_PATH"
|
|
echo "$SYSPLEX" > "$SYSPLEX_NAME_PATH"
|
|
cpi_commit
|
|
fi
|
|
|
|
exit $EXIT_SUCCESS
|