diff options
Diffstat (limited to '')
-rw-r--r-- | tools/testing/selftests/kexec/Makefile | 13 | ||||
-rw-r--r-- | tools/testing/selftests/kexec/config | 3 | ||||
-rwxr-xr-x | tools/testing/selftests/kexec/kexec_common_lib.sh | 220 | ||||
-rwxr-xr-x | tools/testing/selftests/kexec/test_kexec_file_load.sh | 238 | ||||
-rwxr-xr-x | tools/testing/selftests/kexec/test_kexec_load.sh | 47 |
5 files changed, 521 insertions, 0 deletions
diff --git a/tools/testing/selftests/kexec/Makefile b/tools/testing/selftests/kexec/Makefile new file mode 100644 index 000000000..aa91d2063 --- /dev/null +++ b/tools/testing/selftests/kexec/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Makefile for kexec tests + +uname_M := $(shell uname -m 2>/dev/null || echo not) +ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/x86/ -e s/x86_64/x86/) + +ifeq ($(ARCH),x86) +TEST_PROGS := test_kexec_load.sh test_kexec_file_load.sh +TEST_FILES := kexec_common_lib.sh + +include ../lib.mk + +endif diff --git a/tools/testing/selftests/kexec/config b/tools/testing/selftests/kexec/config new file mode 100644 index 000000000..8962e862b --- /dev/null +++ b/tools/testing/selftests/kexec/config @@ -0,0 +1,3 @@ +CONFIG_IMA_APPRAISE=y +CONFIG_IMA_ARCH_POLICY=y +CONFIG_SECURITYFS=y diff --git a/tools/testing/selftests/kexec/kexec_common_lib.sh b/tools/testing/selftests/kexec/kexec_common_lib.sh new file mode 100755 index 000000000..43017cfe8 --- /dev/null +++ b/tools/testing/selftests/kexec/kexec_common_lib.sh @@ -0,0 +1,220 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# +# Kselftest framework defines: ksft_pass=0, ksft_fail=1, ksft_skip=4 + +VERBOSE="${VERBOSE:-1}" +IKCONFIG="/tmp/config-`uname -r`" +KERNEL_IMAGE="/boot/vmlinuz-`uname -r`" +SECURITYFS=$(grep "securityfs" /proc/mounts | awk '{print $2}') + +log_info() +{ + [ $VERBOSE -ne 0 ] && echo "[INFO] $1" +} + +# The ksefltest framework requirement returns 0 for PASS. +log_pass() +{ + [ $VERBOSE -ne 0 ] && echo "$1 [PASS]" + exit 0 +} + +# The ksefltest framework requirement returns 1 for FAIL. +log_fail() +{ + [ $VERBOSE -ne 0 ] && echo "$1 [FAIL]" + exit 1 +} + +# The ksefltest framework requirement returns 4 for SKIP. +log_skip() +{ + [ $VERBOSE -ne 0 ] && echo "$1" + exit 4 +} + +# Check efivar SecureBoot-$(the UUID) and SetupMode-$(the UUID). +# (Based on kdump-lib.sh) +get_efivarfs_secureboot_mode() +{ + local efivarfs="/sys/firmware/efi/efivars" + local secure_boot_file="" + local setup_mode_file="" + local secureboot_mode=0 + local setup_mode=0 + + # Make sure that efivar_fs is mounted in the normal location + if ! grep -q "^\S\+ $efivarfs efivarfs" /proc/mounts; then + log_info "efivars is not mounted on $efivarfs" + return 0; + fi + secure_boot_file=$(find "$efivarfs" -name SecureBoot-* 2>/dev/null) + setup_mode_file=$(find "$efivarfs" -name SetupMode-* 2>/dev/null) + if [ -f "$secure_boot_file" ] && [ -f "$setup_mode_file" ]; then + secureboot_mode=$(hexdump -v -e '/1 "%d\ "' \ + "$secure_boot_file"|cut -d' ' -f 5) + setup_mode=$(hexdump -v -e '/1 "%d\ "' \ + "$setup_mode_file"|cut -d' ' -f 5) + + if [ $secureboot_mode -eq 1 ] && [ $setup_mode -eq 0 ]; then + log_info "secure boot mode enabled (CONFIG_EFIVAR_FS)" + return 1; + fi + fi + return 0; +} + +get_efi_var_secureboot_mode() +{ + local efi_vars + local secure_boot_file + local setup_mode_file + local secureboot_mode + local setup_mode + + if [ ! -d "$efi_vars" ]; then + log_skip "efi_vars is not enabled\n" + fi + secure_boot_file=$(find "$efi_vars" -name SecureBoot-* 2>/dev/null) + setup_mode_file=$(find "$efi_vars" -name SetupMode-* 2>/dev/null) + if [ -f "$secure_boot_file/data" ] && \ + [ -f "$setup_mode_file/data" ]; then + secureboot_mode=`od -An -t u1 "$secure_boot_file/data"` + setup_mode=`od -An -t u1 "$setup_mode_file/data"` + + if [ $secureboot_mode -eq 1 ] && [ $setup_mode -eq 0 ]; then + log_info "secure boot mode enabled (CONFIG_EFI_VARS)" + return 1; + fi + fi + return 0; +} + +# Check efivar SecureBoot-$(the UUID) and SetupMode-$(the UUID). +# The secure boot mode can be accessed either as the last integer +# of "od -An -t u1 /sys/firmware/efi/efivars/SecureBoot-*" or from +# "od -An -t u1 /sys/firmware/efi/vars/SecureBoot-*/data". The efi +# SetupMode can be similarly accessed. +# Return 1 for SecureBoot mode enabled and SetupMode mode disabled. +get_secureboot_mode() +{ + local secureboot_mode=0 + + get_efivarfs_secureboot_mode + secureboot_mode=$? + + # fallback to using the efi_var files + if [ $secureboot_mode -eq 0 ]; then + get_efi_var_secureboot_mode + secureboot_mode=$? + fi + + if [ $secureboot_mode -eq 0 ]; then + log_info "secure boot mode not enabled" + fi + return $secureboot_mode; +} + +require_root_privileges() +{ + if [ $(id -ru) -ne 0 ]; then + log_skip "requires root privileges" + fi +} + +# Look for config option in Kconfig file. +# Return 1 for found and 0 for not found. +kconfig_enabled() +{ + local config="$1" + local msg="$2" + + grep -E -q $config $IKCONFIG + if [ $? -eq 0 ]; then + log_info "$msg" + return 1 + fi + return 0 +} + +# Attempt to get the kernel config first via proc, and then by +# extracting it from the kernel image or the configs.ko using +# scripts/extract-ikconfig. +# Return 1 for found. +get_kconfig() +{ + local proc_config="/proc/config.gz" + local module_dir="/lib/modules/`uname -r`" + local configs_module="$module_dir/kernel/kernel/configs.ko" + + if [ ! -f $proc_config ]; then + modprobe configs > /dev/null 2>&1 + fi + if [ -f $proc_config ]; then + cat $proc_config | gunzip > $IKCONFIG 2>/dev/null + if [ $? -eq 0 ]; then + return 1 + fi + fi + + local extract_ikconfig="$module_dir/source/scripts/extract-ikconfig" + if [ ! -f $extract_ikconfig ]; then + log_skip "extract-ikconfig not found" + fi + + $extract_ikconfig $KERNEL_IMAGE > $IKCONFIG 2>/dev/null + if [ $? -eq 1 ]; then + if [ ! -f $configs_module ]; then + log_skip "CONFIG_IKCONFIG not enabled" + fi + $extract_ikconfig $configs_module > $IKCONFIG + if [ $? -eq 1 ]; then + log_skip "CONFIG_IKCONFIG not enabled" + fi + fi + return 1 +} + +# Make sure that securityfs is mounted +mount_securityfs() +{ + if [ -z $SECURITYFS ]; then + SECURITYFS=/sys/kernel/security + mount -t securityfs security $SECURITYFS + fi + + if [ ! -d "$SECURITYFS" ]; then + log_fail "$SECURITYFS :securityfs is not mounted" + fi +} + +# The policy rule format is an "action" followed by key-value pairs. This +# function supports up to two key-value pairs, in any order. +# For example: action func=<keyword> [appraise_type=<type>] +# Return 1 for found and 0 for not found. +check_ima_policy() +{ + local action="$1" + local keypair1="$2" + local keypair2="$3" + local ret=0 + + mount_securityfs + + local ima_policy=$SECURITYFS/ima/policy + if [ ! -e $ima_policy ]; then + log_fail "$ima_policy not found" + fi + + if [ -n $keypair2 ]; then + grep -e "^$action.*$keypair1" "$ima_policy" | \ + grep -q -e "$keypair2" + else + grep -q -e "^$action.*$keypair1" "$ima_policy" + fi + + # invert "grep -q" result, returning 1 for found. + [ $? -eq 0 ] && ret=1 + return $ret +} diff --git a/tools/testing/selftests/kexec/test_kexec_file_load.sh b/tools/testing/selftests/kexec/test_kexec_file_load.sh new file mode 100755 index 000000000..2ff600388 --- /dev/null +++ b/tools/testing/selftests/kexec/test_kexec_file_load.sh @@ -0,0 +1,238 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# +# Loading a kernel image via the kexec_file_load syscall can verify either +# the IMA signature stored in the security.ima xattr or the PE signature, +# both signatures depending on the IMA policy, or none. +# +# To determine whether the kernel image is signed, this test depends +# on pesign and getfattr. This test also requires the kernel to be +# built with CONFIG_IKCONFIG enabled and either CONFIG_IKCONFIG_PROC +# enabled or access to the extract-ikconfig script. + +TEST="KEXEC_FILE_LOAD" +. ./kexec_common_lib.sh + +trap "{ rm -f $IKCONFIG ; }" EXIT + +# Some of the IMA builtin policies may require the kexec kernel image to +# be signed, but these policy rules may be replaced with a custom +# policy. Only CONFIG_IMA_APPRAISE_REQUIRE_KEXEC_SIGS persists after +# loading a custom policy. Check if it is enabled, before reading the +# IMA runtime sysfs policy file. +# Return 1 for IMA signature required and 0 for not required. +is_ima_sig_required() +{ + local ret=0 + + kconfig_enabled "CONFIG_IMA_APPRAISE_REQUIRE_KEXEC_SIGS=y" \ + "IMA kernel image signature required" + if [ $? -eq 1 ]; then + log_info "IMA signature required" + return 1 + fi + + # The architecture specific or a custom policy may require the + # kexec kernel image be signed. Policy rules are walked + # sequentially. As a result, a policy rule may be defined, but + # might not necessarily be used. This test assumes if a policy + # rule is specified, that is the intent. + + # First check for appended signature (modsig), then xattr + if [ $ima_read_policy -eq 1 ]; then + check_ima_policy "appraise" "func=KEXEC_KERNEL_CHECK" \ + "appraise_type=imasig|modsig" + ret=$? + if [ $ret -eq 1 ]; then + log_info "IMA or appended(modsig) signature required" + else + check_ima_policy "appraise" "func=KEXEC_KERNEL_CHECK" \ + "appraise_type=imasig" + ret=$? + [ $ret -eq 1 ] && log_info "IMA signature required"; + fi + fi + return $ret +} + +# The kexec_file_load_test() is complicated enough, require pesign. +# Return 1 for PE signature found and 0 for not found. +check_for_pesig() +{ + which pesign > /dev/null 2>&1 || log_skip "pesign not found" + + pesign -i $KERNEL_IMAGE --show-signature | grep -q "No signatures" + local ret=$? + if [ $ret -eq 1 ]; then + log_info "kexec kernel image PE signed" + else + log_info "kexec kernel image not PE signed" + fi + return $ret +} + +# The kexec_file_load_test() is complicated enough, require getfattr. +# Return 1 for IMA signature found and 0 for not found. +check_for_imasig() +{ + local ret=0 + + which getfattr > /dev/null 2>&1 + if [ $? -eq 1 ]; then + log_skip "getfattr not found" + fi + + line=$(getfattr -n security.ima -e hex --absolute-names $KERNEL_IMAGE 2>&1) + echo $line | grep -q "security.ima=0x03" + if [ $? -eq 0 ]; then + ret=1 + log_info "kexec kernel image IMA signed" + else + log_info "kexec kernel image not IMA signed" + fi + return $ret +} + +# Return 1 for appended signature (modsig) found and 0 for not found. +check_for_modsig() +{ + local module_sig_string="~Module signature appended~" + local sig="$(tail --bytes $((${#module_sig_string} + 1)) $KERNEL_IMAGE)" + local ret=0 + + if [ "$sig" == "$module_sig_string" ]; then + ret=1 + log_info "kexec kernel image modsig signed" + else + log_info "kexec kernel image not modsig signed" + fi + return $ret +} + +kexec_file_load_test() +{ + local succeed_msg="kexec_file_load succeeded" + local failed_msg="kexec_file_load failed" + local key_msg="try enabling the CONFIG_INTEGRITY_PLATFORM_KEYRING" + + line=$(kexec --load --kexec-file-syscall $KERNEL_IMAGE 2>&1) + + if [ $? -eq 0 ]; then + kexec --unload --kexec-file-syscall + + # In secureboot mode with an architecture specific + # policy, make sure either an IMA or PE signature exists. + if [ $secureboot -eq 1 ] && [ $arch_policy -eq 1 ] && \ + [ $ima_signed -eq 0 ] && [ $pe_signed -eq 0 ] \ + && [ $ima_modsig -eq 0 ]; then + log_fail "$succeed_msg (missing sig)" + fi + + if [ $kexec_sig_required -eq 1 -o $pe_sig_required -eq 1 ] \ + && [ $pe_signed -eq 0 ]; then + log_fail "$succeed_msg (missing PE sig)" + fi + + if [ $ima_sig_required -eq 1 ] && [ $ima_signed -eq 0 ] \ + && [ $ima_modsig -eq 0 ]; then + log_fail "$succeed_msg (missing IMA sig)" + fi + + if [ $pe_sig_required -eq 0 ] && [ $ima_appraise -eq 1 ] \ + && [ $ima_sig_required -eq 0 ] && [ $ima_signed -eq 0 ] \ + && [ $ima_read_policy -eq 0 ]; then + log_fail "$succeed_msg (possibly missing IMA sig)" + fi + + if [ $pe_sig_required -eq 0 ] && [ $ima_appraise -eq 0 ]; then + log_info "No signature verification required" + elif [ $pe_sig_required -eq 0 ] && [ $ima_appraise -eq 1 ] \ + && [ $ima_sig_required -eq 0 ] && [ $ima_signed -eq 0 ] \ + && [ $ima_read_policy -eq 1 ]; then + log_info "No signature verification required" + fi + + log_pass "$succeed_msg" + fi + + # Check the reason for the kexec_file_load failure + echo $line | grep -q "Required key not available" + if [ $? -eq 0 ]; then + if [ $platform_keyring -eq 0 ]; then + log_pass "$failed_msg (-ENOKEY), $key_msg" + else + log_pass "$failed_msg (-ENOKEY)" + fi + fi + + if [ $kexec_sig_required -eq 1 -o $pe_sig_required -eq 1 ] \ + && [ $pe_signed -eq 0 ]; then + log_pass "$failed_msg (missing PE sig)" + fi + + if [ $ima_sig_required -eq 1 ] && [ $ima_signed -eq 0 ]; then + log_pass "$failed_msg (missing IMA sig)" + fi + + if [ $pe_sig_required -eq 0 ] && [ $ima_appraise -eq 1 ] \ + && [ $ima_sig_required -eq 0 ] && [ $ima_read_policy -eq 0 ] \ + && [ $ima_signed -eq 0 ]; then + log_pass "$failed_msg (possibly missing IMA sig)" + fi + + log_pass "$failed_msg" + return 0 +} + +# kexec requires root privileges +require_root_privileges + +# get the kernel config +get_kconfig + +kconfig_enabled "CONFIG_KEXEC_FILE=y" "kexec_file_load is enabled" +if [ $? -eq 0 ]; then + log_skip "kexec_file_load is not enabled" +fi + +# Determine which kernel config options are enabled +kconfig_enabled "CONFIG_IMA_APPRAISE=y" "IMA enabled" +ima_appraise=$? + +kconfig_enabled "CONFIG_IMA_ARCH_POLICY=y" \ + "architecture specific policy enabled" +arch_policy=$? + +kconfig_enabled "CONFIG_INTEGRITY_PLATFORM_KEYRING=y" \ + "platform keyring enabled" +platform_keyring=$? + +kconfig_enabled "CONFIG_IMA_READ_POLICY=y" "reading IMA policy permitted" +ima_read_policy=$? + +kconfig_enabled "CONFIG_KEXEC_SIG_FORCE=y" \ + "kexec signed kernel image required" +kexec_sig_required=$? + +kconfig_enabled "CONFIG_KEXEC_BZIMAGE_VERIFY_SIG=y" \ + "PE signed kernel image required" +pe_sig_required=$? + +is_ima_sig_required +ima_sig_required=$? + +get_secureboot_mode +secureboot=$? + +# Are there pe and ima signatures +check_for_pesig +pe_signed=$? + +check_for_imasig +ima_signed=$? + +check_for_modsig +ima_modsig=$? + +# Test loading the kernel image via kexec_file_load syscall +kexec_file_load_test diff --git a/tools/testing/selftests/kexec/test_kexec_load.sh b/tools/testing/selftests/kexec/test_kexec_load.sh new file mode 100755 index 000000000..49c6aa929 --- /dev/null +++ b/tools/testing/selftests/kexec/test_kexec_load.sh @@ -0,0 +1,47 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# +# Prevent loading a kernel image via the kexec_load syscall when +# signatures are required. (Dependent on CONFIG_IMA_ARCH_POLICY.) + +TEST="$0" +. ./kexec_common_lib.sh + +# kexec requires root privileges +require_root_privileges + +# get the kernel config +get_kconfig + +kconfig_enabled "CONFIG_KEXEC=y" "kexec_load is enabled" +if [ $? -eq 0 ]; then + log_skip "kexec_load is not enabled" +fi + +kconfig_enabled "CONFIG_IMA_APPRAISE=y" "IMA enabled" +ima_appraise=$? + +kconfig_enabled "CONFIG_IMA_ARCH_POLICY=y" \ + "IMA architecture specific policy enabled" +arch_policy=$? + +get_secureboot_mode +secureboot=$? + +# kexec_load should fail in secure boot mode and CONFIG_IMA_ARCH_POLICY enabled +kexec --load $KERNEL_IMAGE > /dev/null 2>&1 +if [ $? -eq 0 ]; then + kexec --unload + if [ $secureboot -eq 1 ] && [ $arch_policy -eq 1 ]; then + log_fail "kexec_load succeeded" + elif [ $ima_appraise -eq 0 -o $arch_policy -eq 0 ]; then + log_info "Either IMA or the IMA arch policy is not enabled" + fi + log_pass "kexec_load succeeded" +else + if [ $secureboot -eq 1 ] && [ $arch_policy -eq 1 ] ; then + log_pass "kexec_load failed" + else + log_fail "kexec_load failed" + fi +fi |