@@ -36,4 +36,7 @@ rmman:
doc: evmctl.1.html rmman evmctl.1
-.PHONY: $(tarname)
+evmtest:
+ $(MAKE) -C evmtest
+
+.PHONY: $(tarname) evmtest
@@ -52,6 +52,7 @@ EVMCTL_MANPAGE_DOCBOOK_XSL
AC_CONFIG_FILES([Makefile
src/Makefile
packaging/ima-evm-utils.spec
+ evmtest/Makefile
])
AC_OUTPUT
new file mode 100644
@@ -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.
new file mode 100644
@@ -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
new file mode 100644
@@ -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 - davidj@linux.ibm.com
new file mode 100755
@@ -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
new file mode 100644
@@ -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.
new file mode 100755
@@ -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
new file mode 100755
@@ -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
new file mode 100755
@@ -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
new file mode 100755
@@ -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 <davidj@linux.ibm.com>
+
+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