2019-03-22 08:37:20

by djacobs7

[permalink] [raw]
Subject: [PATCH v2 1/8] evmtest: Regression testing integrity subsystem

From: David Jacobson <[email protected]>

As the Linux integrity subsystem matures and new features are added,
the number of kernel configuration options (Kconfig) and methods for
loading policies have increased. Regression testing of new and existing
features is needed to ensure correct behavior.

The Linux Test Project (LTP) is a test suite that aims to validate the
"reliability, robustness, and stability" of Linux.

Currently, the Linux Test Project (LTP) only includes the original
IMA-measurement regression tests, not the newer IMA-appraisal or IMA-
audit features.

This patchset introduces "evmtest" — a standalone Linux integrity
regression tool. evmtest can be used to validate individual behaviors
by exercising execve, kexec, module load, and other LSM hooks.
The initial evmtest regression tests verify the IMA-appraisal behavior
based on appending IMA-appraisal policy rules to the custom policy.
evmtest uses a combination of unsigned and validly signed files to
verify the running system.

The custom policy assumes the kernel is properly configured. The
first evmtest named "env_validate" validates the kernel is properly
configured. For example, CONFIG_IMA_WRITE_POLICY is required for
appending to the IMA custom policy. A local-CA certificate needs to
either be builtinto the kernel or memory reserved for embedding the
certificate post-build.

"evmtest" output is consistent, allowing "evmtest" to be plugged into a
testing framework/harness. Testing frameworks, such as xfstests, require
deterministic output. xfstests runs a test and compares its output to a
predefined value, created by running the test script under conditions
where it passes. evmtest provides output that can easily be integrated
with xfstests. All tests have a verbose mode (-v) that outputs more
information for debugging purposes.

New tests can be defined by placing them in the functions/ directory.
An "example_test.sh" script is provided for reference.

Example 1: Successful example test output
$ evmtest runtest example_test -h

example_test -e <example_file> [-vh]

This is an example of how to structure an evmtest

-e <example_file>
-h Displays this help message
-v Verbose logging

$ evmtest runtest example_test -e /bin/bash
[*] Starting test: example_test
[*] TEST: PASSED

Example 1a: successful verbose example test output
$ evmtest runtest example_test -e /bin/bash -v
[*] Starting test: example_test
[*] Example file exists
[*] TEST: PASSED

Example 1b: failed verbose example test output
$ evmtest runtest example_test -e /bin/foo -v
[*] Starting test: example_test
[!] Example file does not exist
[!] TEST: FAILED

SYNOPSIS:
evmtest runtest <test name> [OPTIONS]
options:
-h Displays a help message
-v Verbose logging

Signed-off-by: David Jacobson <[email protected]>

Changelog:
* Various clean-up to env_validate
* Redid loading of running config
* Changed comment order in example test
* Cleaned up README
* Removed notes about VM in env_validate and README
* kernel_build_dir -> build_dir
* Removed listing of functions/ directory
* Added individual name of each test
* Rewritten and expanded README
* Rewrite validate and validate_defined
* shellcheck compliant
* example test, fewer comments + shellcheck
* clean ups suggested by Mimi
* renamed functions -> tests
* checkbashishms compliant
* removed begin funcion usage
* removed long opt names
* Notes file has changes in proper commit
* switched to using functions for structure
* added policy_readable to common.sh
---
Makefile.am | 5 +-
configure.ac | 1 +
evmtest/INSTALL | 11 ++
evmtest/Makefile.am | 23 ++++
evmtest/README | 240 ++++++++++++++++++++++++++++++++++
evmtest/evmtest | 67 ++++++++++
evmtest/files/Notes | 5 +
evmtest/files/common.sh | 59 +++++++++
evmtest/files/load_policy.sh | 37 ++++++
evmtest/tests/env_validate.sh | 196 +++++++++++++++++++++++++++
evmtest/tests/example_test.sh | 63 +++++++++
11 files changed, 706 insertions(+), 1 deletion(-)
create mode 100644 evmtest/INSTALL
create mode 100644 evmtest/Makefile.am
create mode 100644 evmtest/README
create mode 100755 evmtest/evmtest
create mode 100644 evmtest/files/Notes
create mode 100755 evmtest/files/common.sh
create mode 100755 evmtest/files/load_policy.sh
create mode 100755 evmtest/tests/env_validate.sh
create mode 100755 evmtest/tests/example_test.sh

diff --git a/Makefile.am b/Makefile.am
index dba408d..0cb4111 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -36,4 +36,7 @@ rmman:

doc: evmctl.1.html rmman evmctl.1

-.PHONY: $(tarname)
+evmtest:
+ $(MAKE) -C evmtest
+
+.PHONY: $(tarname) evmtest
diff --git a/configure.ac b/configure.ac
index a5b4288..59ec1d1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -52,6 +52,7 @@ EVMCTL_MANPAGE_DOCBOOK_XSL
AC_CONFIG_FILES([Makefile
src/Makefile
packaging/ima-evm-utils.spec
+ evmtest/Makefile
])
AC_OUTPUT

diff --git a/evmtest/INSTALL b/evmtest/INSTALL
new file mode 100644
index 0000000..699853e
--- /dev/null
+++ b/evmtest/INSTALL
@@ -0,0 +1,11 @@
+Installation Instructions
+-------------------------
+
+Basic Installation
+------------------
+
+From the root directory of ima-evm-utils, run the commands: `./autogen.sh`
+followed by `./configure`. `cd` to evmtest directory, execute `make`, and
+`sudo make install`.
+For details on how to use `evmtest` See the installed manpage or the README.
+There is an evmtest.html provided as well.
diff --git a/evmtest/Makefile.am b/evmtest/Makefile.am
new file mode 100644
index 0000000..e74feaf
--- /dev/null
+++ b/evmtest/Makefile.am
@@ -0,0 +1,23 @@
+prefix=@prefix@
+datarootdir=@datarootdir@
+exec_prefix=@exec_prefix@
+bindir=@bindir@
+
+all: evmtest.1
+
+evmtest.1:
+ asciidoc -d manpage -b docbook -o evmtest.1.xsl README
+ asciidoc INSTALL
+ xsltproc --nonet -o $@ $(MANPAGE_DOCBOOK_XSL) evmtest.1.xsl
+ asciidoc -o evmtest.html README
+ rm -f evmtest.1.xsl
+install:
+ install -m 755 evmtest $(bindir)
+ install -d $(datarootdir)/evmtest/files/
+ install -d $(datarootdir)/evmtest/tests/
+ install -D $$(find ./files/ -not -type d) $(datarootdir)/evmtest/files/
+ install -D ./tests/* $(datarootdir)/evmtest/tests/
+ cp evmtest.1 $(datarootdir)/man/man1
+ mandb -q
+
+.PHONY: install evmtest.1
diff --git a/evmtest/README b/evmtest/README
new file mode 100644
index 0000000..5a44070
--- /dev/null
+++ b/evmtest/README
@@ -0,0 +1,240 @@
+evmtest(1)
+==========
+
+
+NAME
+----
+
+evmtest - Linux integrity subsystem regression testing utility
+
+
+SYNOPSIS
+--------
+
+evmtest runtest <test name> [OPTIONS]
+
+
+DESCRIPTION
+-----------
+
+evmtest is a regression testing framework for testing different aspects of the
+Linux integrity subsystem.
+
+
+OPTIONS
+-------
+
+ -b <kernel build directory pathname>
+ -c <kernel config file pathname>
+ -e <example file pathname>
+ -h Help
+ -i <kernel image pathname>
+ -k <private key pathname>
+ -v Verbose logging
+
+
+TEST NAMES
+----------
+
+ env_validate - verify kernel build
+ example_test - example test
+
+
+Introduction
+------------
+
+IMA-appraisal verifies a file's integrity based on the information
+stored in the "security.ima" extended attribute (xattr), before the file
+is made accessible to userspace. The "security.ima" xattr may contain
+either a file hash or a file signature. However, only immutable files
+may be signed.
+
+The file signatures are verified based on keys loaded onto the `.ima`
+keyring. To prevent untrusted or unknown keys from being loaded onto
+the `.ima` keyring, only certificates signed by a kernel "builtin" key
+may be loaded onto the `.ima` keyring. This establishes a signature
+chain of trust from a signed kernel image up to the running system.
+
+
+Confirming the kernel is properly configured
+--------------------------------------------
+
+Several kernel configuration (Kconfig) options need to be enabled in order to
+execute the regression test suite. To verify these options are enabled,
+either a configuration file may be validated directly or the running
+kernel's configuration may be validated.
+
+To directly validate a kernel's configuration file, execute:
+
+ evmtest runtest r_env_validate -c <Kconfig pathname> [-v]
+
+To validate the running kernel's configuration is properly configured
+requires root privileges. As root, execute:
+
+ evmtest runtest r_env_validate -r [-v]
+
+
+Creating a local-CA certificate
+-------------------------------
+
+The local-CA's private key is used to sign certificates that are loaded
+onto the `.ima` keyring. The evmctl manpage provides directions for
+generating the local-CA keypair and for creating the certificate. Refer
+to the evmctl manpage section named "GENERATE TRUSTED KEYS".
+
+The examples directory contains two sample scripts named
+`ima-gen-local-ca.sh` and `ima-genkey.sh`, for generating these files.
+
+* `ima-local-ca.x509` - Inserted into the Linux kernel, to be loaded onto
+ the "builtin" keyring.
+* `ima-local-ca.priv` - Used for signing the IMA certificate
+* `x509_ima.der` - Loaded onto the IMA trusted keyring
+
+
+Adding keys to the builtin kernel keyring
+-------------------------------------------
+
+The Linux kernel's `scripts/insert-sys-cert`, included in the kernel
+headers package (eg. kernel-headers, linux-headers), inserts a DER
+encoded certificate into the Linux kernel post build. This requires
+the kernel to be configured with `CONFIG_SYSTEM_EXTRA_CERTIFICATE`
+enabled to reserve memory for the additional certificate.
+
+Some prebuilt kernel images are configured with this reserved memory.
+For these kernels, after inserting the local-CA public key, the kernel
+only needs to be resigned. For kernels which do not reserve memory for
+a certificate, the kernel needs to be recompiled and resigned.
+
+
+=== Inserting a local-CA certificate post build into the kernel
+
+upstreamed: insert-sys-cert -s System.map -b vmlinux -c <local-CA cert>
+posted*: insert-sys-cert -s System.map -z vmlinuz -c <local-CA cert>
+
+After inserting the certificate with either of these methods, the kernel
+needs to be resigned.
+
+* https://lwn.net/Articles/753487/
+
+
+=== Kernel build method for including a local-CA certificate
+
+The second method for adding a key to the .builtin_trusted_keys keyring is
+to either directly include the certificate during the build, by
+specifying the certificate pathname or by reserving memory for the
+certificate.
+
+==== Kconfig options: including the certificate during build
+ CONFIG_SYSTEM_TRUSTED_KEYRING=y
+ CONFIG_SYSTEM_TRUSTED_KEYS="<path to ima-local-ca.pem>"
+
+==== Kconfig options: reserving memory for the certificate
+ CONFIG_SYSTEM_EXTRA_CERTIFICATE=y
+ CONFIG_SYSTEM_EXTRA_CERTIFICATE_SIZE=4096
+
+
+The latter method allows a generic kernel to be built, initially
+containing a "test" certificate, but released with the "real"
+certificate.
+
+In this kernel build environment, the certificate may be inserted into
+the vmlinux post build, followed by "make" and "sudo make install". The kernel
+image needs to be resigned as usual.
+
+
+Signing the kernel image containing the local-CA certificate
+------------------------------------------------------------
+
+Once the kernel image is built and contains the local-CA certificate,
+sign the kernel image as normal. A couple of tools exist for signing
+kernel images (eg. pesign and sbsign). Refer to the distro’s
+documentation.
+
+
+Loading signed certificates onto the IMA trusted keyring
+--------------------------------------------------------
+
+The integrity dracut module 98integrity/ima-keys-load.sh loads keys
+stored in /etc/keys/ima directory onto the IMA keyring. Assuming the
+integrity dracut module is enabled, properly signed keys will be loaded
+onto the IMA keyring on boot.
+
+To view the keys loaded onto the `.ima` keyring, as root execute:
+
+ keyctl show %keyring:.ima
+
+
+Boot command line options
+-------------------------
+
+IMA's behavior is dependent on its policy. The policy defines which
+files are measured, appraised, and audited. Without a policy, IMA does
+not do anything.
+
+
+=== Methods for defining policy rules
+
+* Builtin policies: are specified on the boot command line (eg.
+ ima_policy="tcb|appraise_tcb")
+* Custom policy: is specified by echo'ing the custom policy pathname to
+ <securityfs>/ima/policy
+* Build time policy rules: Kconfig options
+* Architecture specific policy rules: Kconfig option
+
+The "builtin" policies, specified on the boot command line, are enabled
+from boot. Once the LSMs are initialized, IMA policy rules can be
+defined in terms of LSM labels, allowing for more fine grained policy
+rules to be defined. The "builtin" policies can be replaced with a
+"custom" policy.
+
+After loading a custom policy, additional rules may extend the
+custom policy, if the kernel is configured with CONFIG_IMA_WRITE_POLICY
+enabled.
+
+Unlike the "builtin" policies, the "build" time policy rules are
+automatically enabled at runtime and continue to persist after loading a
+custom policy.
+
+The "architecture" specific policy rules are derived during kernel boot,
+based on runtime secure boot flags. These are similar to the "build" time
+policies in that they persist after loading a custom policy.
+
+Initially each of the regression tests, first executes without an appraisal
+policy rule and then extends the IMA policy with the specific test rule.
+For this reason, the kernel must be configured with:
+
+ CONFIG_IMA_WRITE_POLICY=y
+ CONFIG_IMA_READ_POLICY=y
+
+and booted without any appraisal policy rules.
+
+As the regression tests mature and additional tests are defined, the
+regression tests will not make policy assumptions.
+
+
+FAQ
+---
+=== 1. How can an IMA key be loaded without rebuilding dracut?
+
+Unlike thread (@t), process (@p), session (@s), user (@u), or group (@g)
+keyrings, loading keys onto a trusted keyring requires searching for the
+keyring id. The shell command, below, finds and saves the keyring id.
+The subsequent shell command loads a DER encoded key on to the keyring.
+
+ keyring_id=`sudo keyctl describe %keyring:.ima | awk -F ':' '{print $1}';`
+ evmctl import <path to ima key> ${keyring_id}
+
+This process can be scripted and added to startup/login
+
+=== 2. Should verbose mode be used when integrating with a test platform?
+
+When using evmtest inside of a test platform, output should be kept minimal.
+This is accomplished by not using the --verbose option.
+
+== Reference
+
+1. https://sourceforge.net/p/linux-ima/wiki/Home/
+
+Author
+------
+David Jacobson - [email protected]
diff --git a/evmtest/evmtest b/evmtest/evmtest
new file mode 100755
index 0000000..d579d03
--- /dev/null
+++ b/evmtest/evmtest
@@ -0,0 +1,67 @@
+#!/bin/bash
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+# Check to see if we're installed or not
+evmtest=$(type -p evmtest) # Find in path
+if [ -e "${evmtest}" ]; then
+ _evmdir=$(dirname "${evmtest}")
+ prefix=${_evmdir%%/bin}
+ EVMDIR=${prefix}/share/evmtest
+else
+ EVMDIR=$DIR
+fi
+
+source "$EVMDIR"/files/common.sh
+usage (){
+ echo "Usage:"
+ echo " evmtest runtest <test name> [OPTIONS]"
+ echo ""
+ echo "Options:"
+ echo " -h Displays this help message"
+ echo " -v Verbose logging"
+ echo "List of tests: [R] = Must be run as root"
+ echo ""
+
+ # Any test should be added here manually
+ # The reason this is manual is to prevent the accidental / malicious
+ # placement of a script in tests/
+ echo "[R] env_validate"
+ echo "[ ] examples_test"
+
+ echo ""
+ echo "Note: Tests may be run directly from the \"tests\" directory"
+}
+
+
+runtest (){
+ local test_name=$1
+ shift
+
+ if [ ! -e "$EVMDIR"/tests/"$test_name".sh ]; then
+ echo "[!] Test: \"$test_name\" not found"
+ usage
+ exit 1
+ else
+ ("$EVMDIR"/tests/"$test_name".sh "$@")
+ fi
+}
+
+if [ "$#" == 0 ]; then
+ usage
+ exit 1
+elif [ "$1" == "-h" ]; then
+ usage
+ exit 0
+elif [ "$1" == "runtest" ]; then
+ if [ -z "$2" ]; then
+ echo "[!] Provide a test"
+ exit
+ else
+ shift # Drop runtest
+ runtest "$@"
+ exit $?
+ fi
+else
+ usage
+fi
diff --git a/evmtest/files/Notes b/evmtest/files/Notes
new file mode 100644
index 0000000..f20a272
--- /dev/null
+++ b/evmtest/files/Notes
@@ -0,0 +1,5 @@
+This file contains a description of the contents of this directory.
+
+1. common.sh
+
+This file contains useful functions and variables for evmtest scripts.
diff --git a/evmtest/files/common.sh b/evmtest/files/common.sh
new file mode 100755
index 0000000..e48733f
--- /dev/null
+++ b/evmtest/files/common.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+
+# This is the EVMTest common.sh file
+# This is sourced at the top of a test file to provide common variables,
+# paths, and functions.
+
+EVMTEST_forbid_root () {
+ if [ "$UID" == 0 ]; then
+ echo "[!] This test should not be run as root"
+ exit 1
+ fi
+}
+
+EVMTEST_require_root () {
+ if [ "$UID" != 0 ]; then
+ echo "[!] This test must be run as root"
+ exit 1
+ fi
+}
+
+# verbose_output function - will only echo output if verbose is true
+# otherwise, output is muted
+v_out () {
+ [ "$VERBOSE" != "0" ] && { echo "[*]" "$@" ; return ; }
+}
+
+# Function to fail a test
+fail () {
+ if [ "$VERBOSE" != 0 ]; then
+ if [ -n "$*" ]; then
+ echo "[!]" "$@"
+ fi
+ fi
+ echo "[*] TEST: FAILED"
+ exit 1
+}
+
+passed () {
+ echo "[*] TEST: PASSED"
+ exit 0
+}
+
+EVMTEST_check_policy_readable () {
+ v_out "Attempting to read current policy..."
+ if ! cat "$EVMTEST_SECFS"/ima/policy &>> /dev/null; then
+ fail "Could not read running policy. Kernel must be"\
+ "configured with Kconfig option CONFIG_IMA_READ_POLICY=y"
+ fi
+ v_out "Policy is readable"
+}
+
+# Everything exported should be prefixed with EVMTEST_
+EVMTEST_SECFS_EXISTS=$(findmnt securityfs)
+EVMTEST_SECFS=$(findmnt -f -n securityfs -o TARGET)
+EVMTEST_BOOT_OPTS=$(cat /proc/cmdline)
+
+export EVMTEST_SECFS_EXISTS
+export EVMTEST_SECFS
+export EVMTEST_BOOT_OPTS
diff --git a/evmtest/files/load_policy.sh b/evmtest/files/load_policy.sh
new file mode 100755
index 0000000..0430830
--- /dev/null
+++ b/evmtest/files/load_policy.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )/.."
+source "$ROOT"/files/common.sh
+EVMTEST_require_root
+
+#This script loads the IMA policy either by replacing the builtin
+#policies specified on the boot command line or by appending the policy
+#rules to the existing custom policy.
+
+# This program assumes that the running kernel has been compiled with
+# CONFIG_IMA_WRITE_POLICY=y
+# To validate this, run env_validate <path_to_kernel_build>
+# Otherwise, this will fail
+
+if [ "$#" != 1 ] || [ "$1" == "-h" ]; then
+ echo "Usage: load_policy <policy pathname>"
+ exit
+fi
+
+IMA_POLICY="$EVMTEST_SECFS"/ima/policy
+EVMTESTPOLICY_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" \
+ >/dev/null && pwd )/policies"
+
+if [ ! -e "$EVMTESTPOLICY_DIR"/"$1" ]; then
+ echo "[!] Policy: $1 not found, ensure it is in files/policies"
+ exit 1
+fi
+
+if ! echo "$EVMTESTPOLICY_DIR/$1" > "$IMA_POLICY"; then
+ echo "[!] Load failed - see dmesg"
+ exit 1
+else
+ echo "[*] Policy update completed"
+fi
+
+exit 0
diff --git a/evmtest/tests/env_validate.sh b/evmtest/tests/env_validate.sh
new file mode 100755
index 0000000..c630a23
--- /dev/null
+++ b/evmtest/tests/env_validate.sh
@@ -0,0 +1,196 @@
+#!/bin/bash
+# This test serves to validate a kernel build for running with EVMTEST
+
+TEST="env_validate"
+ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )/.."
+source "$ROOT"/files/common.sh
+VERBOSE=0
+CONFIG_FILE=""
+
+usage () {
+ echo ""
+ echo "env_validate [-c <config>]|-r] [-vh]"
+ echo ""
+ echo " This test validates that a kernel is properly configured, "
+ echo " based on either the provided config file or the builtin"
+ echo " kernel image config file of the running system"
+ echo ""
+ echo " -c Kernel config file"
+ echo " -r Will attempt to pull running config"
+ echo " -v Verbose testing"
+ echo " -h Displays this help message"
+ echo ""
+}
+
+parse_args () {
+ TEMP=$(getopt -o 'hc:rv' -n 'env_validate' -- "$@")
+ eval set -- "$TEMP"
+
+ while true ; do
+ case "$1" in
+ -h) usage; exit 0 ;;
+ -c) CONFIG="$2"; shift 2;;
+ -r) RUNNING=1; shift;;
+ -v) VERBOSE=1; shift;;
+ --) shift; break;;
+ *) echo "[*] Unrecognized option $1"; exit 1 ;;
+ esac
+ done
+
+ # One must be defined
+ if [ -z "$CONFIG" ] && [ -z "$RUNNING" ]; then
+ usage
+ exit 1
+ # But not both
+ elif [ -n "$CONFIG" ] && [ -n "$RUNNING" ]; then
+ usage
+ exit 1
+ fi
+}
+
+# Validate that a variable has been set to a value
+validate () {
+ search="$1=$2"
+ for line in "${lines[@]}"
+ do
+ :
+ if test "${line}" == "${search}"; then
+ return
+ fi
+ done
+ INVALID_DEFINITION+=( "$search" )
+}
+
+# Validate that a variable is defined
+validate_defined () {
+ search="$1"
+ for line in "${lines[@]}"
+ do
+ :
+ if test "${line#*$search}" != "$line"; then
+ if test "${line#*"#"}" == "$line"; then
+ return
+ fi
+ fi
+ done
+ NOT_DEFINED+=( "$1" )
+}
+
+# Attempt to find the config on /proc. If not on proc, try extracting from
+# the image, and then the configs.ko module using extract-ikconfig.
+locate_config () {
+ if [ -n "$RUNNING" ]; then
+ CONFIG_FILE=$(mktemp)
+ if ! gunzip -c /proc/config.gz &>> "$CONFIG_FILE"; then
+ # Clear errors
+ rm "$CONFIG_FILE"
+
+ v_out "$WARN_PROC"
+
+ build=$(uname -r)
+ scripts=/lib/modules/"$build"/build/scripts
+ extract="$scripts"/extract-ikconfig
+ image=/boot/vmlinuz-"$build"
+ mod=/lib/modules/"$build"/kernel/kernel/configs.ko
+
+ if ! "$extract" "$image" &>> "$CONFIG_FILE"; then
+ rm "$CONFIG_FILE"
+ v_out "$WARN_IMAGE"
+
+ if ! "$extract" "$mod" &>> "$CONFIG_FILE"; then
+ fail "$NO_CONF"
+ fi
+ fi
+ fi
+ v_out "Extracted config to $CONFIG_FILE"
+ fi
+
+ if [ -n "$CONFIG" ]; then
+ CONFIG_FILE="$CONFIG"
+ fi
+
+ if [ ! -f "$CONFIG_FILE" ]; then
+ fail "Could not find config file"
+ fi
+}
+
+check_config () {
+ v_out "Parsing .config file..."
+
+ IFS=$'\n' read -d '' -r -a lines < "$CONFIG_FILE"
+
+ v_out "Validating keyring configuration..."
+ # Keyring configuration
+ validate "CONFIG_SYSTEM_EXTRA_CERTIFICATE" "y"
+ validate_defined "CONFIG_SYSTEM_EXTRA_CERTIFICATE_SIZE"
+ validate "CONFIG_SYSTEM_TRUSTED_KEYRING" "y"
+ validate_defined "CONFIG_SYSTEM_TRUSTED_KEYS"
+
+ v_out "Validating integrity configuration..."
+ # Integrity configuration
+ validate "CONFIG_INTEGRITY" "y"
+ validate "CONFIG_INTEGRITY_SIGNATURE" "y"
+ validate "CONFIG_INTEGRITY_ASYMMETRIC_KEYS" "y"
+ validate "CONFIG_INTEGRITY_TRUSTED_KEYRING" "y"
+ validate "CONFIG_INTEGRITY_AUDIT" "y"
+
+ v_out "Validating IMA configuration..."
+ # IMA configuration
+ validate "CONFIG_IMA" "y"
+ validate "CONFIG_IMA_MEASURE_PCR_IDX" "10"
+ validate "CONFIG_IMA_LSM_RULES" "y"
+ validate "CONFIG_IMA_SIG_TEMPLATE" "y"
+ validate_defined "CONFIG_IMA_DEFAULT_TEMPLATE"
+ validate_defined "CONFIG_IMA_DEFAULT_HASH_SHA256"
+ validate_defined "CONFIG_IMA_DEFAULT_HASH"
+ validate "CONFIG_IMA_WRITE_POLICY" "y"
+ validate "CONFIG_IMA_READ_POLICY" "y"
+ validate "CONFIG_IMA_APPRAISE" "y"
+ validate "CONFIG_IMA_TRUSTED_KEYRING" "y"
+ validate "CONFIG_IMA_LOAD_X509" "y"
+ validate_defined "CONFIG_IMA_X509_PATH"
+ v_out "Validating module signing configuration..."
+ # Module signing configuration
+ validate_defined "CONFIG_MODULE_SIG_KEY"
+ validate "CONFIG_MODULE_SIG" "y"
+
+ if [ ${#INVALID_DEFINITION[@]} != 0 ]; then
+ v_out "The following Kconfig variables are incorrectly defined:"
+ for var in "${INVALID_DEFINITION[@]}"; do
+ v_out "$var"
+ done
+ fi
+
+ if [ ${#NOT_DEFINED[@]} != 0 ]; then
+ v_out "The following Kconfig variables need to be defined:"
+ for var in "${NOT_DEFINED[@]}"; do
+ v_out "$var"
+ done
+
+ fi
+
+ [ "${#NOT_DEFINED[@]}" -eq 0 ] && [ "${#INVALID_DEFINITION[@]}" -eq 0 ]
+ code=$?
+
+ if [ -n "$RUNNING" ]; then
+ rm "$CONFIG_FILE"
+ fi
+
+ if [ "$code" != 0 ]; then
+ fail
+ fi
+}
+
+WARN_PROC="Configuration not on /proc, will attempt to extract from image"
+WARN_IMAGE="Unable to extract from image, will attempt to extract from module"
+NO_CONF="Unable to extract from module. Extracting kernel configuration
+ requires CONFIG_IKCONFIG to be enabled. Support for reading from /proc
+ is enabled with CONFIG_IKCONFIG_PROC"
+INVALID_DEFINITION=()
+NOT_DEFINED=()
+
+echo "[*] Starting test: $TEST"
+parse_args "$@"
+locate_config
+check_config
+passed
diff --git a/evmtest/tests/example_test.sh b/evmtest/tests/example_test.sh
new file mode 100755
index 0000000..c6035c9
--- /dev/null
+++ b/evmtest/tests/example_test.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+
+# This is an example test for documentation purposes.
+# This test describes the outline of evmtest test files.
+
+# Author: David Jacobson <[email protected]>
+
+TEST="example_test"
+ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )/.."
+source "$ROOT"/files/common.sh
+VERBOSE=0
+
+
+
+usage () {
+ echo ""
+ echo "example_test -e <example_file> [-vh]"
+ echo ""
+ echo " This is an example of how to structure an evmtest"
+ echo ""
+ echo " -e <example_file>"
+ echo " -h Display this help message"
+ echo " -v Verbose logging"
+ echo ""
+}
+
+
+
+parse_args () {
+ TEMP=$(getopt -o 'e:hv' -n 'example_test' -- "$@")
+ eval set -- "$TEMP"
+ while true ; do
+ case "$1" in
+ -h) usage; exit 0 ; shift;;
+ -e) EXAMPLE_FILE=$2; shift 2;;
+ -v) VERBOSE=1; shift;;
+ --) shift; break;;
+ *) echo "[*] Unrecognized option $1"; exit 1 ;;
+ esac
+ done
+
+ if [ -z "$EXAMPLE_FILE" ]; then
+ usage
+ exit 1
+ fi
+}
+
+# Define what needs to be tested as a function
+check_file_exists () {
+ if [ -e "$EXAMPLE_FILE" ]; then
+ v_out "Example file exists"
+ else
+ fail "Example file not found"
+ fi
+}
+
+# The two options are: EVMTEST_forbid_root and EVMTEST_require_root
+EVMTEST_forbid_root
+
+echo "[*] Starting test: $TEST"
+parse_args "$@"
+check_file_exists
+passed
--
2.20.1



2019-03-22 08:36:12

by djacobs7

[permalink] [raw]
Subject: [PATCH v2 7/8] emvtest: Add ability to run all tests

From: David Jacobson <[email protected]>

evmtest tests functionality of different IMA-Appraisal policies.

To simplify testing, this patch defines an evmtest config file. This
allows for running all tests at once, rather than invoking each test
individually. Variables can be set once rather than specifying
parameters at runtime on the command line.

Signed-off-by: David Jacobson <[email protected]>

changelog:
* removed [OPTIONS] for runall
* added CONFIGURATION PATHNAME -> configuration file
* shellcheck compliant
---
evmtest/README | 31 +++++++++++++++++++++++++-
evmtest/evmtest | 52 ++++++++++++++++++++++++++++++++++++++++++++
evmtest/example.conf | 14 ++++++++++++
3 files changed, 96 insertions(+), 1 deletion(-)
create mode 100644 evmtest/example.conf

diff --git a/evmtest/README b/evmtest/README
index 4dddbc0..d202559 100644
--- a/evmtest/README
+++ b/evmtest/README
@@ -13,6 +13,7 @@ SYNOPSIS

evmtest runtest <test name> [OPTIONS]

+evmtest runall <configuration pathname>

DESCRIPTION
-----------
@@ -34,7 +35,7 @@ OPTIONS


TEST NAMES
-----------
+---------

boot_aggregate - verify the IMA boot-aggregate
env_validate - verify kernel build
@@ -45,6 +46,34 @@ TEST NAMES
xattr_preserve - test metadata preservation on file move


+
+CONFIGURATION PATHNAME
+----------------------
+
+The configuration pathname should point to the runall configuration file.
+
+
+=== Configuration File
+
+The evmtest configuration file allows all tests to be run by executing a single
+command. The configuration file contains all the options that needed for
+various tests and allows tests to be run non-interactively, so they can be
+integrated in a larger testing suite.
+
+The `example.conf` file provides a skeleton configuration file, where the only
+variable that *must* be defined is `IMA_KEY`. Defaults are described below.
+
+* `IMA_KEY` - The private key for the certificate on the IMA Trusted Keyring
+
+* `KBUILD_DIR` - Should point to a kernel build tree. If not provided, the test
+will use `/lib/modules/$(uname -r)/build`.
+
+* `KERN_IMAGE` - Should point towards an unsigned kernel image. If not provided,
+the test will attempt to use the running kernel.
+
+* `VERBOSE` - If set to 1, will add -v to all tests run
+
+
Introduction
------------

diff --git a/evmtest/evmtest b/evmtest/evmtest
index 18cb98d..d6f46f5 100755
--- a/evmtest/evmtest
+++ b/evmtest/evmtest
@@ -16,6 +16,7 @@ source "$EVMDIR"/files/common.sh
usage (){
echo "Usage:"
echo " evmtest runtest <test name> [OPTIONS]"
+ echo " evmtest runall <configuration file>"
echo ""
echo "Options:"
echo " -h Displays this help message"
@@ -67,6 +68,57 @@ elif [ "$1" == "runtest" ]; then
runtest "$@"
exit $?
fi
+elif [ "$1" == "runall" ]; then
+ if [ -z "$2" ] || [ ! -e "$2" ]; then
+ echo "evmtest runall <config file>"
+ echo "[!] Please provide a config file"
+ exit 1
+ fi
+ source "$2" # Load in config
+ if [ "$VERBOSE" -eq 1 ]; then
+ V="-v"
+ fi
+
+ # Key is not optional
+ if [ -z "$IMA_KEY" ]; then
+ echo "[*] Please correct your config file"
+ exit 1
+ fi
+
+ EVMTEST_require_root
+ FAIL=0
+ echo "[*] Running tests..."
+ # 1
+ "$EVMDIR"/tests/env_validate.sh -r "$V"
+ FAIL=$((FAIL+$?))
+ # 2
+ if [ -z "$KERN_IMAGE" ]; then
+ "$EVMDIR"/tests/kexec_sig.sh -k "$IMA_KEY" "$V"
+ else
+ "$EVMDIR"/tests/kexec_sig.sh -k "$IMA_KEY" -i \
+ "$KERN_IMAGE" "$V"
+ fi
+ FAIL=$((FAIL+$?))
+ # 3
+ if [ -z "$KBUILD_DIR" ]; then
+ "$EVMDIR"/tests/kmod_sig.sh -k "$IMA_KEY" "$V"
+ else
+ "$EVMDIR"/tests/kmod_sig.sh -b "$KBUILD_DIR" \
+ -k "$IMA_KEY" "$V"
+ fi
+ FAIL=$((FAIL+$?))
+ # 4
+ "$EVMDIR"/tests/policy_sig.sh -k "$IMA_KEY" "$V"
+ FAIL=$((FAIL+$?))
+ # 5
+ "$EVMDIR"/tests/boot_aggregate.sh "$V"
+ FAIL=$((FAIL+$?))
+ # 6
+ "$EVMDIR"/tests/xattr_preserve.sh "$V"
+ FAIL=$((FAIL+$?))
+ echo "..."
+ echo "[*] TESTS PASSED: $((6-FAIL))"
+ echo "[*] TESTS FAILED: $FAIL"
else
usage
fi
diff --git a/evmtest/example.conf b/evmtest/example.conf
new file mode 100644
index 0000000..fd1c8fe
--- /dev/null
+++ b/evmtest/example.conf
@@ -0,0 +1,14 @@
+# This is an example config file
+# There are three variables that can be set when using evmtest runall
+
+#Set this to 1 for verbose output
+VERBOSE=0
+# Path to the private key for the IMA Trusted Keyring
+# This is required
+IMA_KEY=/path/to/your/ima_key
+
+# If this is not provided, tests will run but attempt to copy the running kernel
+KERN_IMAGE=/path/to/unsigned/kernel_image
+
+# If this is not defined, tests will try to find build tree
+KBUILD_DIR=/path/to/kernel/build/tree
--
2.20.1


2019-03-22 08:36:17

by djacobs7

[permalink] [raw]
Subject: [PATCH v2 8/8] evmtest: virtual machine compatibility

From: David Jacobson <[email protected]>

Regression testing kernels is a task that is often virtualized. This
patch adds functionality to evmtest that enables a developer to
determine if their kernel build is suitable for running in a virtual
machine.

Signed-off-by: David Jacobson <[email protected]>

changelog:
* shellcheck compliant
* updated patch to work with function restructure
---
evmtest/tests/env_validate.sh | 23 +++++++++++++++++++++--
1 file changed, 21 insertions(+), 2 deletions(-)

diff --git a/evmtest/tests/env_validate.sh b/evmtest/tests/env_validate.sh
index c630a23..09b1a87 100755
--- a/evmtest/tests/env_validate.sh
+++ b/evmtest/tests/env_validate.sh
@@ -4,12 +4,13 @@
TEST="env_validate"
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )/.."
source "$ROOT"/files/common.sh
+VM_VALIDATE=0
VERBOSE=0
CONFIG_FILE=""

usage () {
echo ""
- echo "env_validate [-c <config>]|-r] [-vh]"
+ echo "env_validate [-c <config>]|-r] [--vm] [-vh]"
echo ""
echo " This test validates that a kernel is properly configured, "
echo " based on either the provided config file or the builtin"
@@ -18,12 +19,13 @@ usage () {
echo " -c Kernel config file"
echo " -r Will attempt to pull running config"
echo " -v Verbose testing"
+ echo " --vm Will validate that the build is VM compatible"
echo " -h Displays this help message"
echo ""
}

parse_args () {
- TEMP=$(getopt -o 'hc:rv' -n 'env_validate' -- "$@")
+ TEMP=$(getopt -o 'hc:rv' -l "vm" -n 'env_validate' -- "$@")
eval set -- "$TEMP"

while true ; do
@@ -32,6 +34,7 @@ parse_args () {
-c) CONFIG="$2"; shift 2;;
-r) RUNNING=1; shift;;
-v) VERBOSE=1; shift;;
+ --vm) VM_VALIDATE=1; shift;;
--) shift; break;;
*) echo "[*] Unrecognized option $1"; exit 1 ;;
esac
@@ -154,6 +157,22 @@ check_config () {
validate_defined "CONFIG_MODULE_SIG_KEY"
validate "CONFIG_MODULE_SIG" "y"

+
+ if [ $VM_VALIDATE == 1 ]; then
+ v_out "Validating VM configuration"
+
+ validate "CONFIG_BLK_MQ_VIRTIO" "y"
+ validate "CONFIG_MEMORY_BALLOON" "y"
+ validate "CONFIG_VIRTIO_BLK" "y"
+ validate "CONFIG_SCSI_VIRTIO" "y"
+ validate "CONFIG_HW_RANDOM_VIRTIO" "y"
+ validate "CONFIG_VIRTIO" "y"
+ validate "CONFIG_VIRTIO_MENU" "y"
+ validate "CONFIG_VIRTIO_PCI" "y"
+ validate "CONFIG_VIRTIO_PCI_LEGACY" "y"
+ validate "CONFIG_VIRTIO_BALLOON" "y"
+ fi
+
if [ ${#INVALID_DEFINITION[@]} != 0 ]; then
v_out "The following Kconfig variables are incorrectly defined:"
for var in "${INVALID_DEFINITION[@]}"; do
--
2.20.1


2019-03-22 08:36:23

by djacobs7

[permalink] [raw]
Subject: [PATCH v2 4/8] evmtest: test kexec signature policy

From: David Jacobson <[email protected]>

With secure boot enabled, the bootloader verifies the kernel image's
signature before transferring control to it. With Linux as the
bootloader running with secure boot enabled, kexec needs to verify the
kernel image's signature.

This patch defined a new test named "kexec_sig", which first attempts to
kexec an unsigned kernel image with an IMA policy that requires
signatures on any kernel image. Then, the test attempts to kexec the
signed kernel image, which should succeed.

Signed-off-by: David Jacobson <[email protected]>

Changelog:
* Added policy_sig to test list
* shellcheck compliant
* move from functions to tests
* suggestions from Mimi
* checkbashisms complaint
* removed begin
* removed long opts
* restructed to use functions
---
evmtest/README | 3 +-
evmtest/evmtest | 1 +
evmtest/files/policies/kexec_policy | 3 +
evmtest/tests/kexec_sig.sh | 167 ++++++++++++++++++++++++++++
4 files changed, 173 insertions(+), 1 deletion(-)
create mode 100644 evmtest/files/policies/kexec_policy
create mode 100755 evmtest/tests/kexec_sig.sh

diff --git a/evmtest/README b/evmtest/README
index 8c63630..91c8cda 100644
--- a/evmtest/README
+++ b/evmtest/README
@@ -39,7 +39,8 @@ TEST NAMES
env_validate - verify kernel build
example_test - example test
policy_sig - verify loading IMA policies
- policy_sig - test IMA-appraise on policies
+ kexec_sig - test IMA-appraise on kexec image loading
+ kmod_sig - test IMA-appraise on kernel module loading


Introduction
diff --git a/evmtest/evmtest b/evmtest/evmtest
index 49b162d..cd5e238 100755
--- a/evmtest/evmtest
+++ b/evmtest/evmtest
@@ -28,6 +28,7 @@ usage (){
# placement of a script in tests/
echo "[R] env_validate"
echo "[ ] examples_test"
+ echo "[R] kexec_sig"
echo "[R] kmod_sig"
echo "[R] policy_sig"

diff --git a/evmtest/files/policies/kexec_policy b/evmtest/files/policies/kexec_policy
new file mode 100644
index 0000000..dc00fa7
--- /dev/null
+++ b/evmtest/files/policies/kexec_policy
@@ -0,0 +1,3 @@
+appraise func=KEXEC_KERNEL_CHECK appraise_type=imasig
+measure func=KEXEC_KERNEL_CHECK
+audit func=KEXEC_KERNEL_CHECK
diff --git a/evmtest/tests/kexec_sig.sh b/evmtest/tests/kexec_sig.sh
new file mode 100755
index 0000000..3a9459d
--- /dev/null
+++ b/evmtest/tests/kexec_sig.sh
@@ -0,0 +1,167 @@
+#!/bin/bash
+# Author: David Jacobson <[email protected]>
+TEST="kexec_sig"
+ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )/.."
+source "$ROOT"/files/common.sh
+VERBOSE=0
+POLICY_LOAD="$ROOT"/files/load_policy.sh
+
+# This test validates that IMA measures and appraises signatures on kernel
+# images when trying to kexec, if the current policy requires that.
+usage() {
+ echo ""
+ echo "kexec_sig -k <key> [-i <kernel_image]"
+ echo " [-vh]"
+ echo ""
+ echo " This test must be run as root"
+ echo ""
+ echo " This test validates that IMA prevents kexec-ing to an"
+ echo " unsigned kernel image."
+ echo ""
+ echo ""
+ echo " -k The key for the certificate on the IMA keyring"
+ echo " -i An unsigned kernel image"
+ echo " -h Display this help message"
+ echo " -v Verbose logging"
+ echo ""
+ echo " Note: kexec may require PECOFF signature"
+}
+
+parse_args () {
+ TEMP=$(getopt -o 'k:i:hv' -n 'kexec_sig' -- "$@")
+ eval set -- "$TEMP"
+
+ while true ; do
+ case "$1" in
+ -h) usage; exit 0 ; shift;;
+ -i) KERNEL_IMAGE=$2; shift 2;;
+ -k) IMA_KEY=$2; shift 2;;
+ -v) VERBOSE=1; shift;;
+ --) shift; break;;
+ *) echo "[*] Unrecognized option $1"; exit 1;;
+ esac
+ done
+
+ if [ -z "$IMA_KEY" ]; then
+ usage
+ exit 1
+ else
+ if [ ! -e "$IMA_KEY" ]; then
+ fail "Please provide valid keys"
+ fi
+ fi
+}
+
+
+
+# If the user doesn't provide a kernel image for kexec, get the current
+get_image () {
+ if [ -z "$KERNEL_IMAGE" ]; then
+ v_out "No kernel provided, looking for running kernel"
+ RUNNING_KERNEL=$(uname -r)
+ if [ -e /boot/vmlinuz-"$RUNNING_KERNEL" ]; then
+ KERNEL_IMAGE=/boot/vmlinuz-"$RUNNING_KERNEL"
+ TEMP_LOCATION=$(mktemp)
+ v_out "Copying kernel ($KERNEL_IMAGE) to $TEMP_LOCATION"
+ cp "$KERNEL_IMAGE" "$TEMP_LOCATION"
+ KERNEL_IMAGE="$TEMP_LOCATION"
+ fi
+ else
+ if [ ! -e "$KERNEL_IMAGE" ]; then
+ fail "Kernel image not found..."
+ else
+ v_out "Valid Kernel provided, continuing"
+ fi
+ fi
+}
+
+write_hash () {
+ v_out "Writing file hash on kernel image"
+ evmctl ima_hash -a sha256 -f "$KERNEL_IMAGE"
+}
+
+load_policy () {
+ v_out "Attempting to sign policy..."
+ evmctl ima_sign -f "$ROOT"/files/policies/kexec_policy -k "$IMA_KEY"
+
+ v_out "Loading kexec policy..."
+ if ! "$POLICY_LOAD" kexec_policy &>> /dev/null; then
+ fail "Could not update policy - verify keys"
+ fi
+}
+
+check_unsigned_KEXEC_FILE_LOAD () {
+ v_out "Testing loading an unsigned kernel image using KEXEC_FILE_LOAD"\
+ "syscall"
+ # -s uses the kexec_file_load syscall
+ if ! kexec -s -l "$KERNEL_IMAGE" &>> /dev/null; then
+ v_out "Correctly prevented kexec of an unsigned image"
+ else
+ kexec -s -u
+ fail "kexec loaded instead of rejecting. Unloading and exiting."
+ fi
+}
+
+check_unsigned_KEXEC_LOAD () {
+ v_out "Testing loading an unsigned kernel image using KEXEC_LOAD"\
+ "syscall"
+ if kexec -l "$KERNEL_IMAGE" &>> /dev/null; then
+ kexec -u
+ fail "Kexec loaded unsigned image - unloading"
+ else
+ v_out "Correctly prevented kexec of an unsigned image"
+ fi
+}
+
+sign_image () {
+ v_out "Signing kernel image with provided key..."
+ evmctl ima_sign -f "$KERNEL_IMAGE" -k "$IMA_KEY"
+}
+
+check_signed_KEXEC_FILE_LOAD () {
+ v_out "Testing loading a signed kernel image using KEXEC_FILE_LOAD"\
+ "syscall"
+ if ! kexec -s -l "$KERNEL_IMAGE" &>> /dev/null; then
+ fail "kexec rejected a signed image - possibly due to PECOFF"\
+ "signature"
+ else
+ v_out "kexec correctly loaded signed image...unloading"
+ fi
+
+ kexec -s -u
+}
+
+check_signed_KEXEC_LOAD () {
+ v_out "Testing loading a signed kernel image \
+ (without file descriptor) using KEXEC_LOAD syscall"
+
+ if kexec -l "$KERNEL_IMAGE" &>> /dev/null; then
+ kexec -u
+ fail "Signed image was allowed to load without file descriptor"\
+ "for appraisal. Unloading."
+ fi
+
+ v_out "Correctly prevented loading"
+}
+
+cleanup () {
+v_out "Cleaning up..."
+if [ -n "$TEMP_LOCATION" ]; then
+ rm "$TEMP_LOCATION"
+fi
+}
+
+
+EVMTEST_require_root
+echo "[*] Starting test: $TEST"
+parse_args "$@"
+get_image
+write_hash
+load_policy
+check_unsigned_KEXEC_FILE_LOAD
+check_unsigned_KEXEC_LOAD
+sign_image
+check_signed_KEXEC_FILE_LOAD
+check_signed_KEXEC_LOAD
+cleanup
+passed
--
2.20.1


2019-03-22 08:36:28

by djacobs7

[permalink] [raw]
Subject: [PATCH v2 6/8] evmtest: test the preservation of extended attributes

From: David Jacobson <[email protected]>

IMA supports file signatures by storing information in a security.ima
extended file attribute. This test ensures that the attribute is
preserved when a file is copied. This test requires root because only
root can write "security." xattrs to files.

Signed-off-by: David Jacobson <[email protected]>

Changelog:
* Clean ups suggested via mailing list
* getfattr used correctly
* more information about which file is created
* added xattr_preserve to test list
* shellcheck compliant
* move from functions to tests
* checkbashisms complaint
* remove begin
* removed long opts
* restructured using functions
---
evmtest/README | 1 +
evmtest/evmtest | 1 +
evmtest/tests/xattr_preserve.sh | 81 +++++++++++++++++++++++++++++++++
3 files changed, 83 insertions(+)
create mode 100755 evmtest/tests/xattr_preserve.sh

diff --git a/evmtest/README b/evmtest/README
index b2d37e2..4dddbc0 100644
--- a/evmtest/README
+++ b/evmtest/README
@@ -42,6 +42,7 @@ TEST NAMES
policy_sig - verify loading IMA policies
kexec_sig - test IMA-appraise on kexec image loading
kmod_sig - test IMA-appraise on kernel module loading
+ xattr_preserve - test metadata preservation on file move


Introduction
diff --git a/evmtest/evmtest b/evmtest/evmtest
index 3c967f9..18cb98d 100755
--- a/evmtest/evmtest
+++ b/evmtest/evmtest
@@ -32,6 +32,7 @@ usage (){
echo "[R] kexec_sig"
echo "[R] kmod_sig"
echo "[R] policy_sig"
+ echo "[R] xattr_preserve"

echo ""
echo "Note: Tests may be run directly from the \"tests\" directory"
diff --git a/evmtest/tests/xattr_preserve.sh b/evmtest/tests/xattr_preserve.sh
new file mode 100755
index 0000000..61f6ded
--- /dev/null
+++ b/evmtest/tests/xattr_preserve.sh
@@ -0,0 +1,81 @@
+#!/bin/bash
+# Author: David Jacobson <[email protected]>
+TEST="xattr_preserve"
+ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )/.."
+source "$ROOT"/files/common.sh
+
+VERBOSE=0
+# This test ensures that extended file attributes are preserved when a file is
+# moved with the correct flag
+
+usage (){
+ echo ""
+ echo "xattr_preserve [-hv]"
+ echo ""
+ echo "This test requires root privileges to write security xattrs"
+ echo ""
+ echo " This test ensures that extended file attributes (specifically"
+ echo " security.ima labels) are preserved when copying"
+ echo "Options"
+ echo " -h Display this help message"
+ echo " -v Verbose logging"
+}
+
+parse_args () {
+ TEMP=$(getopt -o 'hv' -n 'xattr_preserve' -- "$@")
+ eval set -- "$TEMP"
+
+ while true ; do
+ case "$1" in
+ -h) usage; exit; shift;;
+ -v) VERBOSE=1; shift;;
+ --) shift; break;;
+ *) echo "[*] Unrecognized option $1"; exit 1;;
+ esac
+ done
+}
+
+check_xattr_preserve () {
+ LOCATION_1=$(mktemp)
+ LOCATION_2=$(mktemp -u) # Doesn't create the file
+
+ v_out "Creating and labeling file $LOCATION_1..."
+
+ evmctl ima_hash "$LOCATION_1"
+
+ initial_ima_label=$(getfattr --absolute-names -n security.ima \
+ "$LOCATION_1")
+ initial_hash=$(echo "$initial_ima_label" | awk -F '=' '{print $2}')
+ if printf '%s' "$initial_ima_label" | grep -E -q "security.ima"; then
+ v_out "Found hash on initial file... "
+ else
+ fail "Hash not found on initial file"
+ fi
+
+ initial_hash=$(echo "$initial_ima_label" | awk -F '=' '{print $2}')
+
+ v_out "Copying file to $LOCATION_2..."
+ cp --preserve=xattr "$LOCATION_1" "$LOCATION_2"
+ v_out "Checking if extended attribute has been preserved..."
+
+
+ second_ima_label=$(getfattr --absolute-names -n security.ima \
+ "$LOCATION_2")
+ second_hash=$(echo "$second_ima_label" | awk -F '=' '{print $2}')
+ if [ "$initial_hash" != "$second_hash" ]; then
+ fail "security.ima xattr was not preserved!"
+ else
+ v_out "Extended attribute was preserved during copy"
+ fi
+}
+
+cleanup () {
+ v_out "Cleaning up..."
+ rm "$LOCATION_1" "$LOCATION_2"
+}
+
+EVMTEST_require_root
+echo "[*] Starting test: $TEST"
+check_xattr_preserve
+cleanup
+passed
--
2.20.1


2019-03-22 08:36:37

by djacobs7

[permalink] [raw]
Subject: [PATCH v2 3/8] evmtest: test kernel module loading

From: David Jacobson <[email protected]>

The Linux kernel supports two methods of loading kernel modules -
init_module and finit_module syscalls. This test verifies loading kernel
modules with both syscalls, first without an IMA policy, and
subsequently with an IMA policy (that restricts module loading to signed
modules).

This test requires the kernel to be configured with the
"CONFIG_MODULE_SIG" option, but not with "CONFIG_MODULE_SIG_FORCE". For
this reason, the test requires that "module.sig_enforce=1" is supplied
as a boot option to the kernel.

Signed-off-by: David Jacobson <[email protected]>

Changelog:
* kernel_build_directory -> build_directory
* Added kmod_sig to test list
* shellcheck compliant
* move from functions to tests
* clean up for readability
* redid order of loading and pushing policy
* added policy check
* checkbashisms complaint
* removed begin
* removed long opts
* Notes file updated in appropriate patch
* Restructured with functions
* Renamed to simple_modload, can now be used in other areas
---
evmtest/Makefile.am | 11 +-
evmtest/README | 11 +
evmtest/evmtest | 1 +
evmtest/files/Notes | 5 +
evmtest/files/policies/kernel_module_policy | 2 +
evmtest/src/Makefile | 5 +
evmtest/src/basic_mod.c | 36 +++
evmtest/src/simple_modload.c | 151 ++++++++++
evmtest/tests/kmod_sig.sh | 288 ++++++++++++++++++++
evmtest/tests/policy_sig.sh | 1 -
10 files changed, 507 insertions(+), 4 deletions(-)
create mode 100644 evmtest/files/policies/kernel_module_policy
create mode 100644 evmtest/src/Makefile
create mode 100644 evmtest/src/basic_mod.c
create mode 100644 evmtest/src/simple_modload.c
create mode 100755 evmtest/tests/kmod_sig.sh

diff --git a/evmtest/Makefile.am b/evmtest/Makefile.am
index 496a5de..74a8199 100644
--- a/evmtest/Makefile.am
+++ b/evmtest/Makefile.am
@@ -3,7 +3,7 @@ datarootdir=@datarootdir@
exec_prefix=@exec_prefix@
bindir=@bindir@

-all: evmtest.1
+all: src evmtest.1

evmtest.1:
asciidoc -d manpage -b docbook -o evmtest.1.xsl README
@@ -11,7 +11,10 @@ evmtest.1:
xsltproc --nonet -o $@ $(MANPAGE_DOCBOOK_XSL) evmtest.1.xsl
asciidoc -o evmtest.html README
rm -f evmtest.1.xsl
-install:
+src:
+ cd src && make
+
+install: src
install -m 755 evmtest $(bindir)
install -d $(datarootdir)/evmtest/files/
install -d $(datarootdir)/evmtest/files/policies
@@ -21,7 +24,9 @@ install:
$(datarootdir)/evmtest/files/
install -D ./tests/* $(datarootdir)/evmtest/tests/
install -D ./files/policies/* $(datarootdir)/evmtest/files/policies/
+ cp ./src/basic_mod.ko $(datarootdir)/evmtest/files/
+ cp ./src/basic_modload $(datarootdir)/evmtest/files
cp evmtest.1 $(datarootdir)/man/man1
mandb -q

-.PHONY: install evmtest.1
+.PHONY: src install evmtest.1
diff --git a/evmtest/README b/evmtest/README
index 480f426..8c63630 100644
--- a/evmtest/README
+++ b/evmtest/README
@@ -39,6 +39,7 @@ TEST NAMES
env_validate - verify kernel build
example_test - example test
policy_sig - verify loading IMA policies
+ policy_sig - test IMA-appraise on policies


Introduction
@@ -172,6 +173,9 @@ IMA's behavior is dependent on its policy. The policy defines which
files are measured, appraised, and audited. Without a policy, IMA does
not do anything.

+When running evmtest, boot with: module.sig_enforce=1. This tells the kernel to
+prevent the loading of any unsigned modules.
+

=== Methods for defining policy rules

@@ -213,6 +217,13 @@ As the regression tests mature and additional tests are defined, the
regression tests will not make policy assumptions.


+=== Require kernel module appended signatures
+
+Most kernels are configured with CONFIG_MODULE_SIG enabled but without
+CONFIG_MODULE_SIG_FORCE. For testing purposes, require kernel module appended
+signatures by specifying `module.sig_enforce=1` on the boot command line.
+
+
FAQ
---
=== 1. How can an IMA key be loaded without rebuilding dracut?
diff --git a/evmtest/evmtest b/evmtest/evmtest
index 9902e61..49b162d 100755
--- a/evmtest/evmtest
+++ b/evmtest/evmtest
@@ -28,6 +28,7 @@ usage (){
# placement of a script in tests/
echo "[R] env_validate"
echo "[ ] examples_test"
+ echo "[R] kmod_sig"
echo "[R] policy_sig"

echo ""
diff --git a/evmtest/files/Notes b/evmtest/files/Notes
index 8aa2670..574f5d8 100644
--- a/evmtest/files/Notes
+++ b/evmtest/files/Notes
@@ -19,3 +19,8 @@ This is a directory that contains IMA policies with self explanatory names.
This file was generated such that its corresponding public key could be placed
on the IMA Trusted Keyring, however, it has not. Therefore, any policy (or file)
signed by this key cannot be verified, and is untrusted.
+
+5. basic_mod.ko
+
+This is a kernel module that logs (to dmesg) the syscall that was used to load
+it.
diff --git a/evmtest/files/policies/kernel_module_policy b/evmtest/files/policies/kernel_module_policy
new file mode 100644
index 0000000..8096e18
--- /dev/null
+++ b/evmtest/files/policies/kernel_module_policy
@@ -0,0 +1,2 @@
+measure func=MODULE_CHECK
+appraise func=MODULE_CHECK appraise_type=imasig
diff --git a/evmtest/src/Makefile b/evmtest/src/Makefile
new file mode 100644
index 0000000..2d66ece
--- /dev/null
+++ b/evmtest/src/Makefile
@@ -0,0 +1,5 @@
+obj-m += basic_mod.o
+
+all:
+ make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
+ $(CC) simple_modload.c -o simple_modload
diff --git a/evmtest/src/basic_mod.c b/evmtest/src/basic_mod.c
new file mode 100644
index 0000000..7c49c74
--- /dev/null
+++ b/evmtest/src/basic_mod.c
@@ -0,0 +1,36 @@
+/*
+ * Basic kernel module
+ *
+ * Copyright (C) 2018 IBM
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+/*
+ * evmtest_load_type is a flag passed when loading the module, it indicates
+ * which syscall is being used. It should be either init_module or finit_module
+ * When loaded, evmtest_load_type is outputted to the kernel's message buffer
+ */
+static char *evmtest_load_type;
+
+module_param(evmtest_load_type, charp, 000);
+MODULE_PARM_DESC(evmtest_load_type, "Which syscall is loading this module.");
+
+static int __init basic_module_init(void)
+{
+ printk(KERN_INFO "EVMTEST: LOADED MODULE (%s)\n", evmtest_load_type);
+ return 0;
+}
+
+static void __exit basic_module_cleanup(void)
+{
+ printk(KERN_INFO "EVMTEST: UNLOADED MODULE (%s)\n", evmtest_load_type);
+}
+
+module_init(basic_module_init);
+module_exit(basic_module_cleanup);
+
+MODULE_AUTHOR("David Jacobson");
+MODULE_DESCRIPTION("Kernel module for testing IMA signatures");
+MODULE_LICENSE("GPL");
diff --git a/evmtest/src/simple_modload.c b/evmtest/src/simple_modload.c
new file mode 100644
index 0000000..42510f0
--- /dev/null
+++ b/evmtest/src/simple_modload.c
@@ -0,0 +1,151 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <getopt.h>
+
+/*
+ * finit_module - load a kernel module using the finit_module syscall
+ * @fd: File Descriptor of the kernel module to be loaded
+ */
+int finit_module(int fd)
+{
+ return syscall(__NR_finit_module, fd,
+ "evmtest_load_type=finit_module", 0);
+}
+
+/*
+ * init_module - load a kernel module using the init_module syscall
+ * @fd: File Descriptor of the kernel module to be loaded
+ *
+ * Adapted explanation from: https://github.com/cirosantilli/
+ * linux-kernel-module-cheat/blob/
+ * 91583552ba2c2d547c8577ac888ab9f851642b25/kernel_module/user/
+ * myinsmod.c
+ */
+int init_module(int fd)
+{
+
+ struct stat st;
+
+ int mod = fstat(fd, &st);
+
+ if (mod != 0) {
+ printf("[!] Failed to load module\n");
+ return -1;
+ }
+
+ size_t im_size = st.st_size;
+ void *im = malloc(im_size);
+
+ if (im == NULL) {
+ printf("[!] Failed to load module - MALLOC NULL\n");
+ return -1;
+ }
+ read(fd, im, im_size);
+ close(fd);
+
+ int loaded = syscall(__NR_init_module, im, im_size,
+ "evmtest_load_type=init_module");
+ free(im);
+
+ return loaded;
+}
+
+/*
+ * usage - print out a help message to the user
+ */
+void usage(void)
+{
+ printf("Usage: simple_modload <-p pathname> <-o | -n>\n");
+ printf(" -p,--path pathname of kernel module\n");
+ printf(" -o,--old old syscall (INIT_MODULE)\n");
+ printf(" -n,--new new syscall (FINIT_MODULE)\n");
+}
+
+int main(int argc, char **argv)
+{
+
+ int ret;
+ int uid = getuid();
+ char * path;
+ char old = 0;
+ char new = 0;
+
+ // For getopt
+ char * opt_path = 0;
+ int next;
+
+ const char * const short_opts = "p:on";
+ const struct option long_opts[] =
+ {
+ { "path", 1, NULL, 'p' },
+ { "old", 0, NULL, 'o' },
+ { "new", 0, NULL, 'n' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ while (1) {
+ next = getopt_long(argc, argv, short_opts, long_opts, NULL);
+
+ if (next == -1) {
+ break;
+ }
+
+ switch (next) {
+ case 'p' :
+ opt_path=optarg;
+ int size = strlen(opt_path) + 1;
+ path=(char *)malloc(sizeof(char) * size);
+ strcpy(path,opt_path);
+ break;
+
+ case 'o' :
+ old = 1;
+ break;
+
+ case 'n' :
+ new = 1;
+ break;
+
+ case '?' :
+ case -1 :
+ break;
+
+ default :
+ return -1;
+ }
+ }
+
+ if ( (old && new) || !(old || new) || path == NULL) {
+ usage();
+ return -1;
+ }
+
+ /* Root is required to try and load kernel modules */
+ if (uid != 0) {
+ printf("[!] simple_modload must be run as root\n");
+ return -1;
+ }
+
+ int fd = open(path, O_RDONLY);
+ if (fd == -1) {
+ printf("[!] Could not open file for read.\n");
+ return -1;
+ }
+
+ if (old == 1) {
+ ret = init_module(fd);
+ } else {
+ ret = finit_module(fd);
+ }
+
+ return ret;
+}
diff --git a/evmtest/tests/kmod_sig.sh b/evmtest/tests/kmod_sig.sh
new file mode 100755
index 0000000..0ebbaf3
--- /dev/null
+++ b/evmtest/tests/kmod_sig.sh
@@ -0,0 +1,288 @@
+#!/bin/bash
+# Author: David Jacobson <[email protected]>
+TEST="kmod_sig"
+BUILD_DIR=""
+ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )/.."
+source "$ROOT"/files/common.sh
+
+VERBOSE=0
+# This test validates that IMA prevents the loading of unsigned
+# kernel modules
+
+# The boot command line option module.sig_enforce=1 is equivalent to
+# compiling with CONFIG_MODULE_SIG_FORCE enabled.
+
+usage(){
+ echo ""
+ echo "kmod_sig [-b build_directory] -k <ima_key> [-v]"
+ echo " This test verifies that IMA prevents the loading of an"
+ echo " unsigned kernel module with a policy appraising MODULE_CHECK"
+ echo ""
+ echo " This test must be run as root"
+ echo ""
+ echo " -b The path to a kernel build dir"
+ echo " -k IMA key"
+ echo " -v Verbose logging"
+ echo " -h Display this help message"
+}
+
+parse_args () {
+ TEMP=$(getopt -o 'b:k:hv' -n 'kmod_sig' -- "$@")
+ eval set -- "$TEMP"
+
+ while true ; do
+ case "$1" in
+ -h) usage; exit 0 ;;
+ -b) BUILD_DIR=$2; shift 2;;
+ -k) IMA_KEY=$2; shift 2;;
+ -v) VERBOSE=1; shift;;
+ --) shift; break;;
+ *) echo "[*] Unrecognized option $1"; exit 1 ;;
+ esac
+ done
+
+ if [ -z "$IMA_KEY" ]; then
+ echo "[!] Please provide an IMA key."
+ usage
+ exit 1
+ fi
+}
+
+
+set_build_tree_location () {
+ if [ -z "$BUILD_DIR" ]; then
+ BUILD_DIR="/lib/modules/$(uname -r)/build"
+ if [ ! -e "$BUILD_DIR" ]; then
+ echo "[!] Could not find build tree. Specify with -b"
+ exit 1
+ else
+ v_out "No build tree provided."\
+ "Found - using: $(readlink -f "$BUILD_DIR")"
+ fi
+ fi
+
+
+ if [ ! -d "$BUILD_DIR" ]; then
+ fail "Could not find kernel build path"
+ fi
+}
+
+check_key () {
+ if [ ! -e "$IMA_KEY" ]; then
+ fail "Could not find IMA key"
+ fi
+}
+
+check_policy () {
+ already_run="IMA policy already contains MODULE_CHECK"
+ if [ -e "$EVMTEST_SECFS"/ima/policy ]; then
+ POLICY=$(mktemp -u)
+ cp "$EVMTEST_SECFS"/ima/policy "$POLICY"
+ if grep -q "MODULE_CHECK" "$POLICY"; then
+ rm "$POLICY"
+ fail "$already_run"
+ fi
+ rm "$POLICY"
+ fi
+}
+
+unload_module () {
+ v_out "Unloading test module if loaded..."
+ rmmod basic_mod &>> /dev/null
+}
+
+
+
+check_boot_opts () {
+ if ! printf '%s' "$EVMTEST_BOOT_OPTS" | grep -E -q "$SIG_ENFORCE_CMD";
+ then
+ v_out "Run with kernel command: $SIG_ENFORCE_CMD"
+ fail "Booted with options: $EVMTEST_BOOT_OPTS"
+ else
+ v_out "Booted with correct configuration..."
+ fi
+}
+
+# This test may have been run before - remove the security attribute so we can
+# test again
+remove_xattr_appended_sig () {
+ v_out "Removing security attribute and appended signature if present"
+ setfattr -x security.ima "$ROOT"/files/basic_mod.ko &>> /dev/null
+ strip --strip-debug "$ROOT"/files/basic_mod.ko
+}
+
+check_hash_algo () {
+ # First attempt to find hash algo
+ hash_alg=$(grep CONFIG_MODULE_SIG_HASH "$BUILD_DIR"/.config|awk -F "=" \
+ '{print $2}'| tr -d "\"")
+ # Need to read the config more to determine how to sign module...
+ if [ -z "$hash_alg" ]; then
+ v_out "Could not determine hash algorithm used on module"\
+ "signing. Checking for other Kconfig variables..."
+ hash_opts=$(grep CONFIG_MODULE_SIG "$BUILD_DIR"/.config)
+
+ # All possible hashes from:
+ # https://www.kernel.org/doc/html/v4.17/admin-guide/
+ # module-signing.html
+ case $hash_opts in
+ *"CONFIG_MODULE_SIG_SHA1=y"*)
+ hash_alg="sha1"
+ ;;
+ *"CONFIG_MODULE_SIG_SHA224"*)
+ hash_alg="sha224"
+ ;;
+ *"CONFIG_MODULE_SIG_SHA256"*)
+ hash_alg="sha256"
+ ;;
+ *"CONFIG_MODULE_SIG_SHA384"*)
+ hash_alg="sha384"
+ ;;
+ *"CONFIG_MODULE_SIG_SHA512"*)
+ hash_alg="sha512"
+ ;;
+ *)
+ fail "Could not determine hash"
+ ;;
+ esac
+ fi
+
+ v_out "Found hash algo: $hash_alg"
+}
+
+check_signing_key () {
+ v_out "Looking for signing key..."
+ if [ ! -e "$BUILD_DIR"/certs/signing_key.pem ]; then
+ v_out "signing_key.pem not in certs/ finding via Kconfig";
+ key_location=$(grep MODULE_SIG_KEY "$BUILD_DIR"/.config)
+ if [ -z "$key_location" ]; then
+ fail "Could not determine key location"
+ fi
+ # Parse from .config
+ key_location=${key_location/CONFIG_MODULE_SIG_KEY=/}
+ # Drop quotes
+ key_location=${key_location//\"}
+ # Drop .pem
+ key_location=${key_location/.pem}
+ sig_key="$key_location"
+
+ else
+ sig_key="$BUILD_DIR"/certs/signing_key
+ fi
+
+ v_out "Found key: $sig_key"
+}
+
+sign_appended_signature () {
+ v_out "Signing module [appended signature]..."
+
+ if ! "$BUILD_DIR"/scripts/sign-file "$hash_alg" "$sig_key".pem \
+ "$sig_key".x509 "$ROOT"/files/basic_mod.ko; then
+ fail "Signing failed - please ensure sign-file is in scripts/"
+ fi
+}
+
+check_appended_signature_init_mod () {
+ v_out "Attempting to load signed (appended) module with INIT_MODULE"\
+ " syscall [should pass]"
+ if ! "$mod_load" -p "$ROOT"/files/basic_mod.ko -o &>> /dev/null;
+ then
+ fail "Failed to load using init_module - check key"
+ fi
+
+ v_out "Module loaded - unloading"
+ rmmod basic_mod &>> /dev/null
+}
+
+check_appended_signature_finit_mod () {
+ v_out "Attempting to load signed (appended) module with FINIT_MODULE"\
+ " syscall [should pass]"
+ if ! "$mod_load" -p "$ROOT"/files/basic_mod.ko -n &>> /dev/null;
+ then
+ fail "Failed to load module"
+ fi
+
+ v_out "Module loaded - unloading"
+ rmmod basic_mod &>> /dev/null
+}
+
+update_policy () {
+ if ! evmctl ima_sign -f "$POLICY_PATH" -k "$IMA_KEY"; then
+ fail "Failed to sign policy - check key"
+ fi
+
+ v_out "Signing and loading policy to prevent loading unsigned kernel"\
+ " modules..."
+ if ! "$POLICY_LOAD" kernel_module_policy &>> /dev/null; then
+ fail "Could not write policy - is the supplied key correct?"
+ fi
+}
+
+check_appended_signature_init_mod_IMA () {
+ v_out "Attempting to load signed (appended) module with FINIT_MODULE "\
+ "syscall [should fail]"
+ if "$mod_load" -p "$ROOT"/files/basic_mod.ko -n &>> /dev/null;
+ then
+ rmmod_basic_mod &>> /dev/null
+ fail "FINIT_MODULE loaded module without xattr. Unloading"
+ fi
+ v_out "Prevented module without file attribute from loading"
+}
+
+sign_xattr () {
+ v_out "Signing file [extended file attribute]..."
+ if ! evmctl ima_sign -k "$IMA_KEY" -f "$ROOT"/files/basic_mod.ko; then
+ fail "Error signing module - check keys"
+ fi
+}
+
+check_xattr_finit_mod () {
+ v_out "Attempting to load module with FINIT_MODULE syscall"\
+ " [should pass]"
+ "$mod_load" -p "$ROOT"/files/basic_mod.ko -n &>> /dev/null
+}
+
+check_unknown_key () {
+ v_out "Signing with unknown key..."
+ evmctl ima_sign -f "$ROOT"/files/basic_mod.ko &>> /dev/null
+ if "$mod_load" -p "$ROOT"/files/basic_mod.ko -n &>> /dev/null;
+ then
+ fail "Allowed module to load with wrong signature"
+ fi
+
+ v_out "Prevented loading module signed by unknown key using"\
+ " FINIT_MODULE syscall"
+
+ if "$mod_load" -p "$ROOT"/files/basic_mod.ko -o &>> /dev/null;
+ then
+ fail "Allowed module to load with wrong signature"
+ fi
+
+ v_out "Prevented loading module signed by unknown key using"\
+ " INIT_MODULE syscall"
+}
+
+mod_load="$ROOT"/files/simple_modload
+SIG_ENFORCE_CMD="module.sig_enforce=1"
+POLICY_LOAD="$ROOT"/files/load_policy.sh
+POLICY_PATH="$ROOT"/files/policies/kernel_module_policy
+
+EVMTEST_require_root
+echo "[*] Starting test: $TEST"
+parse_args "$@"
+set_build_tree_location
+check_key
+check_policy
+unload_module
+check_boot_opts
+remove_xattr_appended_sig
+check_hash_algo
+check_signing_key
+sign_appended_signature
+check_appended_signature_init_mod
+check_appended_signature_finit_mod
+update_policy
+check_appended_signature_init_mod_IMA
+sign_xattr
+check_xattr_finit_mod
+remove_xattr_appended_sig
+passed
diff --git a/evmtest/tests/policy_sig.sh b/evmtest/tests/policy_sig.sh
index 174d111..ac56d0a 100755
--- a/evmtest/tests/policy_sig.sh
+++ b/evmtest/tests/policy_sig.sh
@@ -3,7 +3,6 @@ TEST="policy_sig"
# Author: David Jacobson <[email protected]>

ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )/.."
-#shellcheck source=/usr/local/share/evmtest/files/common.sh
source "$ROOT"/files/common.sh

VERBOSE=0
--
2.20.1


2019-03-22 08:36:54

by djacobs7

[permalink] [raw]
Subject: [PATCH v2 5/8] evmtest: validate boot record

From: David Jacobson <[email protected]>

The first record in the IMA runtime measurement list is the boot
aggregate - a hash of PCRs 0-7. This test calculates the boot aggregate
based off the PCRs and compares it to IMA's boot aggregate.

Dependencies: a TPM, IBMTSS2.

Signed-off-by: David Jacobson <[email protected]>

Changelog:
* Added boot_aggregate to test list
* shellcheck compliant
* minor fixes
* move from functions to tests
* redid tss parsing
* checkbashisms complaint
* remove begin
* removed long opts
* restructured to use functions
* added changes from Mimi to work with new TSS
* removed searching for TSS locations
---
evmtest/README | 1 +
evmtest/evmtest | 1 +
evmtest/tests/boot_aggregate.sh | 140 ++++++++++++++++++++++++++++++++
3 files changed, 142 insertions(+)
create mode 100755 evmtest/tests/boot_aggregate.sh

diff --git a/evmtest/README b/evmtest/README
index 91c8cda..b2d37e2 100644
--- a/evmtest/README
+++ b/evmtest/README
@@ -36,6 +36,7 @@ OPTIONS
TEST NAMES
----------

+ boot_aggregate - verify the IMA boot-aggregate
env_validate - verify kernel build
example_test - example test
policy_sig - verify loading IMA policies
diff --git a/evmtest/evmtest b/evmtest/evmtest
index cd5e238..3c967f9 100755
--- a/evmtest/evmtest
+++ b/evmtest/evmtest
@@ -26,6 +26,7 @@ usage (){
# Any test should be added here manually
# The reason this is manual is to prevent the accidental / malicious
# placement of a script in tests/
+ echo "[R] boot_aggregate"
echo "[R] env_validate"
echo "[ ] examples_test"
echo "[R] kexec_sig"
diff --git a/evmtest/tests/boot_aggregate.sh b/evmtest/tests/boot_aggregate.sh
new file mode 100755
index 0000000..adecfeb
--- /dev/null
+++ b/evmtest/tests/boot_aggregate.sh
@@ -0,0 +1,140 @@
+#!/bin/bash
+# Author: David Jacobson <[email protected]>
+TEST="boot_aggregate"
+
+ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )/.."
+source "$ROOT"/files/common.sh
+
+VERBOSE=0
+TPM_VERSION="2.0"
+# This test validates the eventlog against the hardware PCRs in the TPM, and
+# the boot aggregate against IMA.
+
+usage (){
+ echo "boot_aggregate [-hv]"
+ echo ""
+ echo " This test must be run as root"
+ echo ""
+ echo " This test validates PCRs 0-7 in the TPM"
+ echo " It also validates the boot_aggregate based those PCRs"
+ echo " against what IMA has recorded"
+ echo ""
+ echo " -h Display this help message"
+ echo " -v Verbose logging"
+}
+
+parse_args () {
+ TEMP=$(getopt -o 'hv' -n 'boot_aggregate' -- "$@")
+ eval set -- "$TEMP"
+
+ while true ; do
+ case "$1" in
+ -h) usage; exit; shift;;
+ -v) VERBOSE=1; shift;;
+ --) shift; break;;
+ *) echo "[*] Unrecognized option $1"; exit 1 ;;
+ esac
+ done
+}
+
+check_requirements () {
+ v_out "Checking if securityfs is mounted..."
+ if [ -z "$EVMTEST_SECFS_EXISTS" ]; then
+ fail "securityfs not found..."
+ fi
+
+ v_out "Verifying TPM is present..."
+ if [ ! -d "$EVMTEST_SECFS/tpm0" ]; then
+ fail "Could not locate TPM in $EVMTEST_SECFS"
+ fi
+
+ v_out "TPM found..."
+
+ v_out "Checking if system supports reading event log..."
+
+ if [ ! -f "$EVMTEST_SECFS"/tpm0/binary_bios_measurements ]; then
+ fail "Kernel does not support reading BIOS measurements,
+ please update to at least 4.16.0"
+ fi
+
+ v_out "Verifying TPM Version"
+ if [ -e /sys/class/tpm/tpm0/device/caps ]; then
+ TPM_VERSION="1.2"
+ fi
+}
+
+check_pcrs () {
+ v_out "Grabbing PCR values..."
+ local pcrs=() # array to store the Hardware PCR values
+ local sim_pcrs=() # What PCRs should be according to the event log
+ local eventextend=tsseventextend
+ local pcrread="tsspcrread -halg sha1"
+ local eventlog=/sys/kernel/security/tpm0/binary_bios_measurements
+
+ if [ "$TPM_VERSION" == "1.2" ]; then
+ eventextend=tss1eventextend
+ pcrread=tss1pcrread
+ fi
+
+ for ((i=0; i<=7; i++)); do
+ pcrs[i]=$(TPM_INTERFACE_TYPE=dev $pcrread -ha "$i" -ns)
+ done
+
+ local output=$(mktemp -u)
+ "$eventextend" -if "$eventlog" -sim -ns > "$output"
+
+ # Some PTT's are using TPM 1.2 event log format. Retry on failure.
+ if [ $? -ne 0 ]; then
+ eventextend=tss1eventextend
+ "$eventextend" -if "$eventlog" -sim -ns > "$output"
+ fi
+
+ IFS=$'\n' read -d '' -r -a lines < "$output"
+ rm "$output"
+
+ for line in "${lines[@]}"
+ do
+ :
+ sim_pcrs+=( "$(echo "$line" | cut -d ':' -f2 | \
+ tr -d '[:space:]')" )
+ if printf '%s' "$line" | grep -E -q "boot aggregate"; then
+ tss_agg=$(echo "$line" | cut -d ':' -f2 | \
+ tr -d '[:space:]')
+ fi
+ done
+
+ v_out "Validating PCRs.."
+ for ((i=0; i<=7; i++)); do
+ v_out "SIM PCR [$i]: ${sim_pcrs[$i]}"
+ v_out "TPM PCR [$i]: ${pcrs[$i]}"
+ if [ "${pcrs[$i]}" != "${sim_pcrs[$i]}" ]; then
+ v_out "PCRs are incorrect..."
+ fail "Mismatch at PCR $i "
+ else
+ v_out "PCR $i validated..."
+ fi
+ done
+}
+
+check_boot_aggregate () {
+ v_out "Validating Boot Aggregate..."
+ ima_agg=$(grep boot_aggregate \
+ "$EVMTEST_SECFS"/ima/ascii_runtime_measurements| head -1 | cut \
+ -d ":" -f2|cut -d " " -f1)
+ v_out "TSS BOOT AGG: $tss_agg"
+ v_out "IMA BOOT AGG: $ima_agg"
+
+ if [ "$tss_agg" != "$ima_agg" ]; then
+ fail "Boot Aggregate is inconsistent"
+ else
+ v_out "Boot Aggregate validated"
+ fi
+}
+
+EVMTEST_require_root
+echo "[*] Starting test: $TEST"
+parse_args "$@"
+check_requirements
+check_pcrs
+check_boot_aggregate
+passed
--
2.20.1


2019-03-22 08:37:25

by djacobs7

[permalink] [raw]
Subject: [PATCH v2 2/8] evmtest: test loading IMA policies

From: David Jacobson <[email protected]>

IMA can be configured to require signatures on policies before loading
them. This test verifies that IMA correctly validates signatures, and
rejects policies that lack signatures or have been signed by an
unauthorized party (i.e. certificate is not on the appropriate keyring).

This test requires root privileges in order to write to securityfs
files.

Signed-off-by: David Jacobson <[email protected]>

Changelog:
* Placed policy_sig on list of tests
* make sure key exists
* shellcheck compliant
* Update makefiles with tests instead of functions
* removed begin
* removed long opts
* Notes file is updated in correct patch
* restructure to use functions
* reword usage
* reworded patch title
* Fixed changes in Notes
* renamed key
* Cut down on functions
---
evmtest/Makefile.am | 6 +-
evmtest/README | 1 +
evmtest/evmtest | 1 +
evmtest/files/Notes | 16 +++
evmtest/files/policies/signed_policy | 2 +
evmtest/files/policies/unknown_signed_policy | 1 +
evmtest/files/policies/unsigned_policy | 1 +
evmtest/tests/policy_sig.sh | 110 +++++++++++++++++++
evmtest/unknown_privkey_ima.pem | 16 +++
9 files changed, 153 insertions(+), 1 deletion(-)
create mode 100644 evmtest/files/policies/signed_policy
create mode 100644 evmtest/files/policies/unknown_signed_policy
create mode 100644 evmtest/files/policies/unsigned_policy
create mode 100755 evmtest/tests/policy_sig.sh
create mode 100644 evmtest/unknown_privkey_ima.pem

diff --git a/evmtest/Makefile.am b/evmtest/Makefile.am
index e74feaf..496a5de 100644
--- a/evmtest/Makefile.am
+++ b/evmtest/Makefile.am
@@ -14,9 +14,13 @@ evmtest.1:
install:
install -m 755 evmtest $(bindir)
install -d $(datarootdir)/evmtest/files/
+ install -d $(datarootdir)/evmtest/files/policies
install -d $(datarootdir)/evmtest/tests/
- install -D $$(find ./files/ -not -type d) $(datarootdir)/evmtest/files/
+ install -D \
+ $$(find ./files/ -not -type d -not -path "./files/policies/*") \
+ $(datarootdir)/evmtest/files/
install -D ./tests/* $(datarootdir)/evmtest/tests/
+ install -D ./files/policies/* $(datarootdir)/evmtest/files/policies/
cp evmtest.1 $(datarootdir)/man/man1
mandb -q

diff --git a/evmtest/README b/evmtest/README
index 5a44070..480f426 100644
--- a/evmtest/README
+++ b/evmtest/README
@@ -38,6 +38,7 @@ TEST NAMES

env_validate - verify kernel build
example_test - example test
+ policy_sig - verify loading IMA policies


Introduction
diff --git a/evmtest/evmtest b/evmtest/evmtest
index d579d03..9902e61 100755
--- a/evmtest/evmtest
+++ b/evmtest/evmtest
@@ -28,6 +28,7 @@ usage (){
# placement of a script in tests/
echo "[R] env_validate"
echo "[ ] examples_test"
+ echo "[R] policy_sig"

echo ""
echo "Note: Tests may be run directly from the \"tests\" directory"
diff --git a/evmtest/files/Notes b/evmtest/files/Notes
index f20a272..8aa2670 100644
--- a/evmtest/files/Notes
+++ b/evmtest/files/Notes
@@ -3,3 +3,19 @@ This file contains a description of the contents of this directory.
1. common.sh

This file contains useful functions and variables for evmtest scripts.
+
+2. load_policy.sh
+
+This is a script to load policies. The first time this is called, it will
+replace the existing policy. Subsequent calls will append additional rules to
+the existing policy.
+
+3. policies/
+
+This is a directory that contains IMA policies with self explanatory names.
+
+4. unknown_privkey_ima.pem
+
+This file was generated such that its corresponding public key could be placed
+on the IMA Trusted Keyring, however, it has not. Therefore, any policy (or file)
+signed by this key cannot be verified, and is untrusted.
diff --git a/evmtest/files/policies/signed_policy b/evmtest/files/policies/signed_policy
new file mode 100644
index 0000000..87828f0
--- /dev/null
+++ b/evmtest/files/policies/signed_policy
@@ -0,0 +1,2 @@
+measure func=POLICY_CHECK
+appraise func=POLICY_CHECK appraise_type=imasig
diff --git a/evmtest/files/policies/unknown_signed_policy b/evmtest/files/policies/unknown_signed_policy
new file mode 100644
index 0000000..1f8f8f4
--- /dev/null
+++ b/evmtest/files/policies/unknown_signed_policy
@@ -0,0 +1 @@
+audit func=POLICY_CHECK
diff --git a/evmtest/files/policies/unsigned_policy b/evmtest/files/policies/unsigned_policy
new file mode 100644
index 0000000..1f8f8f4
--- /dev/null
+++ b/evmtest/files/policies/unsigned_policy
@@ -0,0 +1 @@
+audit func=POLICY_CHECK
diff --git a/evmtest/tests/policy_sig.sh b/evmtest/tests/policy_sig.sh
new file mode 100755
index 0000000..174d111
--- /dev/null
+++ b/evmtest/tests/policy_sig.sh
@@ -0,0 +1,110 @@
+#!/bin/bash
+TEST="policy_sig"
+# Author: David Jacobson <[email protected]>
+
+ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )/.."
+#shellcheck source=/usr/local/share/evmtest/files/common.sh
+source "$ROOT"/files/common.sh
+
+VERBOSE=0
+POLICY_LOAD="$ROOT"/files/load_policy.sh
+# This test validates that IMA measures and appraises policies.
+usage() {
+ echo ""
+ echo "policy_sig -k <key> [-vh]"
+ echo ""
+ echo " This test first loads a policy requiring all subsequent"
+ echo " policies to be signed, and verifies that only signed policies"
+ echo " may then be loaded."
+ echo ""
+ echo " Loading policy rules requires root privilege. This test must be"
+ echo " executed as root."
+ echo ""
+ echo " -k The key for the certificate on the IMA keyring"
+ echo " -h Display this help message"
+ echo " -v Verbose logging"
+}
+
+parse_args () {
+ TEMP=$(getopt -o 'k:hv' -n 'policy_sig' -- "$@")
+ eval set -- "$TEMP"
+
+ while true ; do
+ case "$1" in
+ -h) usage; exit 0; shift;;
+ -k) IMA_KEY=$2; shift 2;;
+ -v) VERBOSE=1; shift;;
+ --) shift; break;;
+ *) echo "[*] Unrecognized option $1"; exit 1 ;;
+ esac
+ done
+
+ if [ -z "$IMA_KEY" ]; then
+ usage
+ exit 1
+ fi
+
+ if [ ! -f "$IMA_KEY" ]; then
+ cleanup
+ fail "Missing key"
+ fi
+}
+
+load_signed_policy () {
+ v_out "Signing policy with provided key..."
+ if ! evmctl ima_sign -f "$POLICY_PATH" -k "$IMA_KEY" &>> /dev/null; then
+ cleanup
+ fail "Failed to sign policy - check key file"
+ fi
+
+ v_out "Loading policy..."
+ if ! "$POLICY_LOAD" signed_policy &>> /dev/null; then
+ cleanup
+ fail "Failed to write policy. "
+ fi
+ v_out "Loaded"
+}
+
+load_unsigned_policy () {
+ v_out "Attempting to load unsigned policy..."
+ if "$POLICY_LOAD" unsigned_policy &>> /dev/null; then
+ cleanup
+ fail "Failed to reject unsigned policy"
+ fi
+
+ v_out "IMA Blocked unsigned policy"
+}
+
+load_unknown_key_policy () {
+ v_out "Signing policy with invalid key..."
+ evmctl ima_sign -f "$ROOT"/files/policies/unknown_signed_policy \
+ -k "$ROOT"/files/unknown_privkey_ima.pem &>> /dev/null
+
+ v_out "Attempting to load policy signed by invalid key..."
+ if "$POLICY_LOAD" unknown_signed_policy &>> /dev/null; then
+ cleanup
+ fail "Failed to reject policy signed by unknown key"
+ fi
+
+ v_out "IMA blocked policy signed by unknown key"
+}
+
+cleanup () {
+ v_out "Removing security.ima attribute from policies..."
+ setfattr -x security.ima "$ROOT"/files/policies/unsigned_policy &>> \
+ /dev/null
+ setfattr -x security.ima "$ROOT"/files/policies/unknown_signed_policy \
+ &>> /dev/null
+ v_out "Done"
+}
+
+POLICY_PATH="$ROOT"/files/policies/signed_policy
+
+EVMTEST_require_root
+echo "[*] Starting test: $TEST"
+parse_args "$@"
+load_signed_policy
+load_unsigned_policy
+load_unknown_key_policy
+cleanup
+passed
diff --git a/evmtest/unknown_privkey_ima.pem b/evmtest/unknown_privkey_ima.pem
new file mode 100644
index 0000000..dcc0e24
--- /dev/null
+++ b/evmtest/unknown_privkey_ima.pem
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMOnki6OKMHExpH1
+IWgUlPWWSbsDpW1lpqXMj0/ZWo9xU5W2xZC53TVArUGOImQ5PcMNkw1VcHhKbFKO
+jYT0gEE0Sv+VbePiEnhUheFOWUxNNFE3DVQaOpBN0OzsUCSGX9RKIIwkIAwJkvWA
+MHzR4ZPQGGM9hMJKhEvlTG4PP96LAgMBAAECgYBKVKVCrptpUhqmZNx2MCuPSbNl
+KzNz5kRzhM2FZmvzRvicTj2siBA0JQgteZQzQ1PlgIi3bhg2ev/ANYwqUMFQWZv9
+zm5d4P7Zsdyle15MDTSrQIaroeb1nbfNvaB0L4D4Inv0p6ksyIFp7TR5MLVenC5k
+bxfESVWVPDseiAFKUQJBAPQ/x3LmnT0RiMeX6quCGAON7DGpV5KFwL97luWO6vH+
+qZ2W1/J0UxTbruv7rA+tj3ZXpdNOxfmq+JStY0jrJV0CQQDNEUqomnA183rX0dv8
+MWyOPmX0Z9SMSTRvflNRW85Bzbosq68uLTq3qOBj+td9zUlopsLpJlfF0Vc+moff
+uq0HAkEAi/Sz47oTZXfTqZL6TBZ6jibXrck8PeBYhyBZYebX55ymMn/J88sGBFCx
+VdVbTYyFRSmKAqADv0FhuUf1OUZMnQJAOayjUsgcxw+zfP+I32UHIvppslOBc/Mi
+zDi7Niab2+YAdo/StSoDWaQld/kUok0aWFSOfQRLq1c1MmZD0KiwAQJANY0LopqG
+pxACc4/QawxtBoV1a8j5Zui8LZPRtKwjkA30Nq8fOufzMuBeJIlLap45uD1xC7St
+bsPWG5+uz18e5w==
+-----END PRIVATE KEY-----
--
2.20.1


2019-03-22 12:20:50

by Petr Vorel

[permalink] [raw]
Subject: Re: [PATCH v2 1/8] evmtest: Regression testing integrity subsystem

Hi David,

> $ evmtest runtest example_test -e /bin/bash
> [*] Starting test: example_test
> [*] TEST: PASSED

> Example 1a: successful verbose example test output
> $ evmtest runtest example_test -e /bin/bash -v
...

> Changelog:
...
> * checkbashishms compliant
Not yet :). I noticed using
source (should be .)
== (should be =)
${BASH_SOURCE[0]} (should be $0)
$UID (should be $(id -u)
bash redirection &>> (should be >/dev/null 2>&1)
+= (should be VAR="${VAR}foo")
and bash shebang.

Could you please fix that?

...
> +++ b/evmtest/evmtest
> @@ -0,0 +1,67 @@
> +#!/bin/bash

Can we, please, use /bin/sh and not-expect bash?
Portability matters.

> +source "$EVMDIR"/files/common.sh
> +usage (){
> + echo "Usage:"
> + echo " evmtest runtest <test name> [OPTIONS]"
> + echo ""
Can be just echo. Maybe using cat might be better than many echos:

cat <<EOF
Usage:
\tevmtest runtest <test name> [OPTIONS]
...
EOF


Kind regards,
Petr