tests: Add test case for state migration and storage locking

Add a test case that monitors the locking of the storage by swtpm using the
directory storage backend to ensure that the lock is taken at the right
time and released when required.

Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 0318fd0..a6deca5 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -73,6 +73,7 @@
 	test_tpm2_save_load_state_2_block \
 	test_tpm2_save_load_state_3 \
 	test_tpm2_save_load_state_da_timeout \
+	test_tpm2_save_load_state_locking \
 	test_tpm2_setbuffersize \
 	test_tpm2_volatilestate \
 	test_tpm2_wrongorder \
@@ -207,6 +208,7 @@
 	_test_tpm2_save_load_encrypted_state \
 	_test_tpm2_save_load_state \
 	_test_tpm2_save_load_state_da_timeout \
+	_test_tpm2_save_load_state_locking \
 	_test_tpm2_setbuffersize \
 	_test_tpm2_swtpm_bios \
 	_test_tpm2_volatilestate \
diff --git a/tests/_test_tpm2_save_load_state_locking b/tests/_test_tpm2_save_load_state_locking
new file mode 100755
index 0000000..d91ce02
--- /dev/null
+++ b/tests/_test_tpm2_save_load_state_locking
@@ -0,0 +1,266 @@
+#!/bin/bash
+
+# For the license, see the LICENSE file in the root directory.
+#set -x
+
+ROOT=${abs_top_builddir:-$(pwd)/..}
+TESTDIR=${abs_top_testdir:-$(dirname "$0")}
+
+VTPM_NAME="${VTPM_NAME:-vtpm-tpm2-test-save-load-state-locking}"
+SWTPM_DEV_NAME="/dev/${VTPM_NAME}"
+export TPM_PATH="$(mktemp -d)" || exit 1
+STATE_FILE="$TPM_PATH/tpm2-00.permall"
+VOLATILE_STATE_FILE="$TPM_PATH/tpm2-00.volatilestate"
+MY_VOLATILE_STATE_FILE="$TPM_PATH/my.volatilestate"
+MY_PERMANENT_STATE_FILE="$TPM_PATH/my.permanent"
+MY_SAVESTATE_STATE_FILE="$TPM_PATH/my.savestate"
+SWTPM_INTERFACE="${SWTPM_INTERFACE:-cuse}"
+SWTPM_CMD_UNIX_PATH="${TPM_PATH}/unix-cmd.sock"
+SWTPM_CTRL_UNIX_PATH="${TPM_PATH}/unix-ctrl.sock"
+
+function cleanup()
+{
+	if [ -n "${SWTPM_PID}" ]; then
+		kill_quiet -9 "${SWTPM_PID}"
+	fi
+	rm -rf "$TPM_PATH"
+}
+
+trap "cleanup" EXIT
+
+[ "${SWTPM_INTERFACE}" == "cuse" ] && source "${TESTDIR}/test_cuse"
+source "${TESTDIR}/common"
+
+run_swtpm "${SWTPM_INTERFACE}" \
+	--migration release-lock-outgoing \
+	--tpm2
+
+kill_quiet -0 "${SWTPM_PID}"
+if [ $? -ne 0 ]; then
+	echo "Error: ${SWTPM_INTERFACE} TPM did not start."
+	exit 1
+fi
+check_swtpm_no_storage_lock "${SWTPM_PID}"
+
+# Init the TPM
+run_swtpm_ioctl "${SWTPM_INTERFACE}" -i
+if [ $? -ne 0 ]; then
+	echo "Error: Could not initialize the ${SWTPM_INTERFACE} TPM."
+	exit 1
+fi
+check_swtpm_storage_locked "${SWTPM_PID}"
+swtpm_lock_storage "re-lock"
+
+kill_quiet -0 "${SWTPM_PID}" 2>/dev/null
+if [ $? -ne 0 ]; then
+	echo "Error: ${SWTPM_INTERFACE} TPM not running anymore after INIT."
+	exit 1
+fi
+
+# Startup the TPM
+RES=$(swtpm_cmd_tx "${SWTPM_INTERFACE}" '\x80\x01\x00\x00\x00\x0c\x00\x00\x01\x44\x00\x00')
+exp=' 80 01 00 00 00 0a 00 00 00 00'
+if [ "$RES" != "$exp" ]; then
+	echo "Error: Did not get expected result from TPM2_Startup(ST_Clear)"
+	echo "expected: $exp"
+	echo "received: $RES"
+	exit 1
+fi
+check_swtpm_storage_locked "${SWTPM_PID}"
+swtpm_lock_storage "re-lock"
+
+run_swtpm_ioctl "${SWTPM_INTERFACE}" -h 1234
+if [ $? -ne 0 ]; then
+	echo "Error: Hash command did not work."
+	exit 1
+fi
+
+# Read PCR 17
+#                                                  length         CC            count       hashalg         sz
+RES=$(swtpm_cmd_tx "${SWTPM_INTERFACE}" '\x80\x01\x00\x00\x00\x14\x00\x00\x01\x7e\x00\x00\x00\x01\x00\x0b\x03\x00\x00\x02')
+exp=' 80 01 00 00 00 3e 00 00 00 00 00 00 00 18 00 00 00 01 00 0b 03 00 00 02 00 00 00 01 00 20 fc a5 d6 49 bf b0 c9 22 fd 33 0f 79 b2 00 43 28 9d af d6 0d 01 a4 c4 37 3c f2 8a db 56 c9 b4 54'
+if [ "$RES" != "$exp" ]; then
+	echo "Error: (1) Did not get expected result from TPM_PCRRead(17)"
+	echo "expected: $exp"
+	echo "received: $RES"
+	exit 1
+fi
+
+run_swtpm_ioctl "${SWTPM_INTERFACE}" --save permanent "$MY_PERMANENT_STATE_FILE"
+if [ $? -ne 0 ]; then
+	echo "Error: Could not write permanent state file $MY_PERMANENT_STATE_FILE."
+	exit 1
+fi
+if [ ! -r "$MY_PERMANENT_STATE_FILE" ]; then
+	echo "Error: Permanent state file $MY_PERMANENT_STATE_FILE does not exist."
+	exit 1
+fi
+echo "Saved permanent state."
+check_swtpm_storage_locked "${SWTPM_PID}"
+swtpm_lock_storage "re-lock"
+
+run_swtpm_ioctl "${SWTPM_INTERFACE}" --save volatile "$MY_VOLATILE_STATE_FILE"
+if [ $? -ne 0 ]; then
+	echo "Error: Could not write volatile state file $MY_VOLATILE_STATE_FILE."
+	exit 1
+fi
+if [ ! -r "$MY_VOLATILE_STATE_FILE" ]; then
+	echo "Error: Volatile state file $MY_VOLATILE_STATE_FILE does not exist."
+	exit 1
+fi
+echo "Saved volatile state."
+check_swtpm_storage_locked "${SWTPM_PID}"
+swtpm_lock_storage "re-lock"
+
+run_swtpm_ioctl "${SWTPM_INTERFACE}" --save savestate "$MY_SAVESTATE_STATE_FILE"
+if [ $? -ne 0 ]; then
+	echo "Error: Could not write savestate state file $MY_SAVESTATE_STATE_FILE."
+	exit 1
+fi
+if [ ! -r "$MY_SAVESTATE_STATE_FILE" ]; then
+	echo "Error: Volatile state file $MY_SAVESTATE_STATE_FILE does not exist."
+	exit 1
+fi
+echo "Saved savestate state."
+check_swtpm_no_storage_lock "${SWTPM_PID}"
+
+swtpm_lock_storage "lock"
+swtpm_lock_storage "re-lock"
+
+check_swtpm_storage_locked "${SWTPM_PID}"
+swtpm_lock_storage "re-lock"
+
+# Upon the next command swtpm must again lock storage; this simulates TPM usage after migration fall-back
+RES=$(swtpm_cmd_tx "${SWTPM_INTERFACE}" '\x80\x01\x00\x00\x00\x0a\x00\x00\x01\x7a')
+exp=' 80 01 00 00 00 0a 00 00 01 da'
+if [ "$RES" != "$exp" ]; then
+	echo "Error: (1) Did not get expected result from TPM_PCRRead(17)"
+	echo "expected: $exp"
+	echo "received: $RES"
+	exit 1
+fi
+check_swtpm_storage_locked "${SWTPM_PID}"
+swtpm_lock_storage "re-lock"
+
+run_swtpm_ioctl "${SWTPM_INTERFACE}" -s
+if [ $? -ne 0 ]; then
+	echo "Error: Could not shut down the ${SWTPM_INTERFACE} TPM."
+	exit 1
+fi
+
+if wait_process_gone "${SWTPM_PID}" 4; then
+	echo "Error: ${SWTPM_INTERFACE} TPM should not be running anymore."
+	exit 1
+fi
+
+# Restart swtpm
+rm -f "$VOLATILE_STATE_FILE" "$STATE_FILE"
+run_swtpm "${SWTPM_INTERFACE}" \
+	--migration incoming,release-lock-outgoing \
+	--tpm2
+
+kill_quiet -0 "${SWTPM_PID}"
+if [ $? -ne 0 ]; then
+	echo "Error: ${SWTPM_INTERFACE} TPM did not start."
+	exit 1
+fi
+check_swtpm_no_storage_lock "${SWTPM_PID}"
+
+# load state into the TPM
+run_swtpm_ioctl "${SWTPM_INTERFACE}" --load permanent "$MY_PERMANENT_STATE_FILE"
+if [ $? -ne 0 ]; then
+	echo "Could not load permanent state into vTPM"
+	exit 1
+fi
+echo "Loaded permanent state."
+check_swtpm_storage_locked "${SWTPM_PID}"
+swtpm_lock_storage "re-lock"
+
+run_swtpm_ioctl "${SWTPM_INTERFACE}" --load volatile "$MY_VOLATILE_STATE_FILE"
+if [ $? -ne 0 ]; then
+	echo "Could not load volatile state into vTPM"
+	exit 1
+fi
+echo "Loaded volatile state."
+check_swtpm_storage_locked "${SWTPM_PID}"
+
+# Init the TPM
+run_swtpm_ioctl "${SWTPM_INTERFACE}" -i
+if [ $? -ne 0 ]; then
+	echo "TPM Init failed."
+	exit 1
+fi
+check_swtpm_storage_locked "${SWTPM_PID}"
+swtpm_lock_storage "re-lock"
+
+# Volatile state must have been removed by TPM now
+if [ -r "$VOLATILE_STATE_FILE" ]; then
+	echo "Error: Volatile state file $VOLATILE_STATE_FILE still exists."
+	exit 1
+fi
+
+# Read the PCR again ...
+RES=$(swtpm_cmd_tx "${SWTPM_INTERFACE}" '\x80\x01\x00\x00\x00\x14\x00\x00\x01\x7e\x00\x00\x00\x01\x00\x0b\x03\x00\x00\x02')
+exp=' 80 01 00 00 00 3e 00 00 00 00 00 00 00 18 00 00 00 01 00 0b 03 00 00 02 00 00 00 01 00 20 fc a5 d6 49 bf b0 c9 22 fd 33 0f 79 b2 00 43 28 9d af d6 0d 01 a4 c4 37 3c f2 8a db 56 c9 b4 54'
+if [ "$RES" != "$exp" ]; then
+	echo "Error: (2) Did not get expected result from TPM_PCRRead(17)"
+	echo "expected: $exp"
+	echo "received: $RES"
+	exit 1
+fi
+
+# Save the volatile state again
+run_swtpm_ioctl "${SWTPM_INTERFACE}" -v
+if [ $? -ne 0 ]; then
+	echo "Error: Could not have the ${SWTPM_INTERFACE} TPM store the volatile state to a file."
+	exit 1
+fi
+
+if [ ! -r "$VOLATILE_STATE_FILE" ]; then
+	echo "Error: Volatile state file $VOLATILE_STATE_FILE does not exist."
+	exit 1
+fi
+
+# Send a new TPM_Init
+run_swtpm_ioctl "${SWTPM_INTERFACE}" -i
+if [ $? -ne 0 ]; then
+	echo "Error: Could not initialize the ${SWTPM_INTERFACE} TPM."
+	exit 1
+fi
+
+# Volatile state must have been removed by TPM now
+if [ -r "$VOLATILE_STATE_FILE" ]; then
+	echo "Error: Volatile state file $VOLATILE_STATE_FILE still exists."
+	exit 1
+fi
+
+# Read the PCR again ...
+RES=$(swtpm_cmd_tx "${SWTPM_INTERFACE}" '\x80\x01\x00\x00\x00\x14\x00\x00\x01\x7e\x00\x00\x00\x01\x00\x0b\x03\x00\x00\x02')
+exp=' 80 01 00 00 00 3e 00 00 00 00 00 00 00 18 00 00 00 01 00 0b 03 00 00 02 00 00 00 01 00 20 fc a5 d6 49 bf b0 c9 22 fd 33 0f 79 b2 00 43 28 9d af d6 0d 01 a4 c4 37 3c f2 8a db 56 c9 b4 54'
+if [ "$RES" != "$exp" ]; then
+	echo "Error: (3) Did not get expected result from TPM_PCRRead(17)"
+	echo "expected: $exp"
+	echo "received: $RES"
+	exit 1
+fi
+
+# Final shut down
+run_swtpm_ioctl "${SWTPM_INTERFACE}" -s
+if [ $? -ne 0 ]; then
+	echo "Error: Could not shut down the ${SWTPM_INTERFACE} TPM."
+	exit 1
+fi
+
+if wait_process_gone "${SWTPM_PID}" 4; then
+	echo "Error: ${SWTPM_INTERFACE} TPM should not be running anymore."
+	exit 1
+fi
+
+if [ ! -e "$STATE_FILE" ]; then
+	echo "Error: TPM state file $STATE_FILE does not exist."
+	exit 1
+fi
+
+echo "OK"
+
+exit 0
diff --git a/tests/common b/tests/common
index fed1d96..e3caebc 100644
--- a/tests/common
+++ b/tests/common
@@ -915,3 +915,63 @@
 		fi
 	fi
 }
+
+# Have swtpm lock or re-lock the storage. Neither locking nor re-locking
+# may produce an error
+#
+# @param1: reason like 'lock' or 're-lock'
+function swtpm_lock_storage()
+{
+	local reason="$1"
+
+	if ! run_swtpm_ioctl  "${SWTPM_INTERFACE}" --lock-storage 0; then
+		echo "Error: 'swtpm_ioctl --lock-storage' to ${reason} storage failed"
+		exit 1
+	fi
+}
+
+# Check that swtpm has no lock on the directory backend storage
+function check_swtpm_no_storage_lock()
+{
+	local pid="$1"
+
+	if [ -d "/proc/${pid}/fd" ]; then
+		if [ -n "$(ls -l "/proc/${pid}/fd" | grep -E "\.lock\$")" ]; then
+			echo "Error: swtpm must not have storage locked"
+			ls -l /proc/${1}/fd
+			exit 1
+		fi
+	elif [ -n "$(type -P lsof)" ]; then
+		if [ -n "$(lsof -p "${pid}" | grep -e "\.lock\$")" ]; then
+			echo "Error: swtpm must not have storage locked"
+			lsof -p "${pid}"
+			exit 1
+		fi
+	else
+		echo "Missing procfs directory and lsof tool to determine open files."
+		exit 1
+	fi
+}
+
+# Check that swtpm has a lock on the directory backend storage
+function check_swtpm_storage_locked()
+{
+	local pid="$1"
+
+	if [ -d "/proc/${pid}/fd" ]; then
+		if [ -z "$(ls -l "/proc/${pid}/fd" | grep -E "\.lock\$")" ]; then
+			echo "Error: swtpm must have storage locked"
+			ls -l /proc/${1}/fd
+			exit 1
+		fi
+	elif [ -n "$(type -P lsof)" ]; then
+		if [ -z "$(lsof -p "${pid}" | grep -e "\.lock\$")" ]; then
+			echo "Error: swtpm must have storage locked"
+			ls -l /proc/${1}/fd
+			exit 1
+		fi
+	else
+		echo "Missing procfs directory and lsof tool to determine open files."
+		exit 1
+	fi
+}
diff --git a/tests/test_tpm2_save_load_state_locking b/tests/test_tpm2_save_load_state_locking
new file mode 100755
index 0000000..bab2336
--- /dev/null
+++ b/tests/test_tpm2_save_load_state_locking
@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+
+TESTDIR=${abs_top_testdir:-$(dirname "$0")}
+ROOT=${abs_top_builddir:-$(dirname "$0")/..}
+source ${TESTDIR}/common
+skip_test_no_tpm20 "${SWTPM_EXE}"
+
+if [ ! -d /proc/$$/fd ] && [ -z "$(type -P lsof)" ]; then
+	echo "This test needs procfs process file descriptor support or 'lsof'"
+	exit 77
+fi
+
+cd "$(dirname "$0")"
+
+export SWTPM_IOCTL_BUFFERSIZE=100
+export SWTPM_INTERFACE=cuse
+bash _test_tpm2_save_load_state_locking
+ret=$?
+[ $ret -ne 0  ] && [ $ret -ne 77 ] && exit $ret
+
+export SWTPM_IOCTL_BUFFERSIZE=4096
+export SWTPM_INTERFACE=cuse
+bash _test_tpm2_save_load_state_locking
+ret=$?
+[ $ret -ne 0  ] && [ $ret -ne 77 ] && exit $ret
+
+export SWTPM_INTERFACE=socket+socket
+export SWTPM_SERVER_NAME=localhost
+export SWTPM_SERVER_PORT=65466
+export SWTPM_CTRL_PORT=65467
+bash _test_tpm2_save_load_state_locking
+ret=$?
+[ $ret -ne 0  ] && [ $ret -ne 77 ] && exit $ret
+
+export SWTPM_INTERFACE=socket+unix
+export SWTPM_SERVER_NAME=localhost
+export SWTPM_SERVER_PORT=65466
+bash _test_tpm2_save_load_state_locking
+ret=$?
+[ $ret -ne 0  ] && [ $ret -ne 77 ] && exit $ret
+
+export SWTPM_INTERFACE=unix+unix
+bash _test_tpm2_save_load_state_locking
+ret=$?
+[ $ret -ne 0  ] && [ $ret -ne 77 ] && exit $ret
+
+exit 0