From: Jennifer Herbert Date: Tue, 23 Aug 2022 16:08:10 +0000 (+0100) Subject: swtpm: Add a chroot option X-Git-Tag: v0.8.0~32 X-Git-Url: https://git.proxmox.com/?p=swtpm.git;a=commitdiff_plain;h=68e5428492365c50ec6e2dfcada6621b61c91666 swtpm: Add a chroot option Add an option to enter a chroot after starting swtpm. This is useful for sandboxing purposes. When this option is used, it is expected that swtpm is started as root and the --runas option is used to subsequently drop privileges (otherwise the chroot could be escaped). Signed-off-by: Jennifer Herbert Signed-off-by: Ross Lagerwall --- diff --git a/man/man8/swtpm.pod b/man/man8/swtpm.pod index f67a3e2..d656587 100644 --- a/man/man8/swtpm.pod +++ b/man/man8/swtpm.pod @@ -267,6 +267,11 @@ has been opened for writing. Switch to the given user. This option can only be used when swtpm is started as root. +=item B<-R|--chroot Epath> + +Chroot to the given directory at startup. This option can only be used when swtpm is +started as root. + =item B<--seccomp action=none|log|kill> (since v0.2) This option allows a user to select the action to take by the seccomp profile when diff --git a/src/swtpm/cuse_tpm.c b/src/swtpm/cuse_tpm.c index a011b44..71a0920 100644 --- a/src/swtpm/cuse_tpm.c +++ b/src/swtpm/cuse_tpm.c @@ -136,6 +136,7 @@ static GMutex *file_ops_lock; struct cuse_param { char *runas; + char *chroot; char *logging; char *keydata; char *migkeydata; @@ -240,6 +241,7 @@ static const char *usage = " TPM2_Shutdown before TPM 2 reset or swtpm termination;\n" "-r|--runas : after creating the CUSE device, change to the given\n" " user\n" +"-R|--chroot : chroot to the given directory at startup\n" "--tpm2 : choose TPM2 functionality\n" #ifdef WITH_SECCOMP # ifndef SCMP_ACT_LOG @@ -1494,6 +1496,7 @@ int swtpm_cuse_main(int argc, char **argv, const char *prgname, const char *ifac {"min" , required_argument, 0, 'm'}, {"name" , required_argument, 0, 'n'}, {"runas" , required_argument, 0, 'r'}, + {"chroot" , required_argument, 0, 'R'}, {"log" , required_argument, 0, 'l'}, {"locality" , required_argument, 0, 'L'}, {"key" , required_argument, 0, 'k'}, @@ -1537,7 +1540,7 @@ int swtpm_cuse_main(int argc, char **argv, const char *prgname, const char *ifac tpmversion = TPMLIB_TPM_VERSION_1_2; while (true) { - opt = getopt_long(argc, argv, "M:m:n:r:hv", longopts, &longindex); + opt = getopt_long(argc, argv, "M:m:n:r:R:hv", longopts, &longindex); if (opt == -1) break; @@ -1591,6 +1594,9 @@ int swtpm_cuse_main(int argc, char **argv, const char *prgname, const char *ifac case 'r': /* runas */ param.runas = optarg; break; + case 'R': + param.chroot = optarg; + break; case 'l': /* log */ param.logging = optarg; break; @@ -1651,6 +1657,13 @@ int swtpm_cuse_main(int argc, char **argv, const char *prgname, const char *ifac goto exit; } + if (param.chroot) { + if (do_chroot(param.chroot) < 0) { + ret = EXIT_FAILURE; + goto exit; + } + } + if (param.runas) { if (!(passwd = getpwnam(param.runas))) { logprintf(STDERR_FILENO, "User '%s' does not exist\n", diff --git a/src/swtpm/swtpm.c b/src/swtpm/swtpm.c index 94c8c09..223e26f 100644 --- a/src/swtpm/swtpm.c +++ b/src/swtpm/swtpm.c @@ -175,6 +175,8 @@ static void usage(FILE *file, const char *prgname, const char *iface) " disable-auto-shutdown disables automatic sending of\n" " TPM2_Shutdown before TPM 2 reset or swtpm termination;\n" "-r|--runas : change to the given user\n" + "-R|--chroot \n" + " : chroot to the given directory at startup\n" "--tpm2 : choose TPM2 functionality\n" #ifdef WITH_SECCOMP # ifndef SCMP_ACT_LOG @@ -235,6 +237,7 @@ int swtpm_main(int argc, char **argv, const char *prgname, const char *iface) char *flagsdata = NULL; char *seccompdata = NULL; char *runas = NULL; + char *chroot = NULL; bool need_init_cmd = true; #ifdef DEBUG time_t start_time; @@ -249,6 +252,7 @@ int swtpm_main(int argc, char **argv, const char *prgname, const char *iface) {"fd" , required_argument, 0, 'f'}, {"server" , required_argument, 0, 'c'}, {"runas" , required_argument, 0, 'r'}, + {"chroot" , required_argument, 0, 'R'}, {"terminate" , no_argument, 0, 't'}, {"locality" , required_argument, 0, 'L'}, {"log" , required_argument, 0, 'l'}, @@ -271,7 +275,7 @@ int swtpm_main(int argc, char **argv, const char *prgname, const char *iface) log_set_prefix("swtpm: "); while (TRUE) { - opt = getopt_long(argc, argv, "dhp:f:tr:", longopts, &longindex); + opt = getopt_long(argc, argv, "dhp:f:tr:R:", longopts, &longindex); if (opt == -1) break; @@ -399,6 +403,10 @@ int swtpm_main(int argc, char **argv, const char *prgname, const char *iface) runas = optarg; break; + case 'R': + chroot = optarg; + break; + case 'S': seccompdata = optarg; break; @@ -415,6 +423,11 @@ int swtpm_main(int argc, char **argv, const char *prgname, const char *iface) exit(EXIT_FAILURE); } + if (chroot) { + if (do_chroot(chroot) < 0) + exit(EXIT_FAILURE); + } + /* change process ownership before accessing files */ if (runas) { if (change_process_owner(runas) < 0) diff --git a/src/swtpm/swtpm_chardev.c b/src/swtpm/swtpm_chardev.c index 8767d90..7ea855d 100644 --- a/src/swtpm/swtpm_chardev.c +++ b/src/swtpm/swtpm_chardev.c @@ -182,6 +182,8 @@ static void usage(FILE *file, const char *prgname, const char *iface) " mode allows a user to set the file mode bits of the state files;\n" " the default mode is 0640;\n" "-r|--runas : change to the given user\n" + "-R|--chroot \n" + " : chroot to the given directory at startup\n" #ifdef WITH_VTPM_PROXY "--vtpm-proxy : spawn a Linux vTPM proxy driver device and read TPM\n" #endif @@ -291,6 +293,7 @@ int swtpm_chardev_main(int argc, char **argv, const char *prgname, const char *i char *flagsdata = NULL; char *seccompdata = NULL; char *runas = NULL; + char *chroot = NULL; #ifdef WITH_VTPM_PROXY bool use_vtpm_proxy = false; #endif @@ -307,6 +310,7 @@ int swtpm_chardev_main(int argc, char **argv, const char *prgname, const char *i {"chardev" , required_argument, 0, 'c'}, {"fd" , required_argument, 0, 'f'}, {"runas" , required_argument, 0, 'r'}, + {"chroot" , required_argument, 0, 'R'}, {"locality" , required_argument, 0, 'L'}, {"log" , required_argument, 0, 'l'}, {"key" , required_argument, 0, 'k'}, @@ -331,7 +335,7 @@ int swtpm_chardev_main(int argc, char **argv, const char *prgname, const char *i log_set_prefix("swtpm: "); while (TRUE) { - opt = getopt_long(argc, argv, "dhc:f:r:", longopts, &longindex); + opt = getopt_long(argc, argv, "dhc:f:r:R:", longopts, &longindex); if (opt == -1) break; @@ -444,6 +448,10 @@ int swtpm_chardev_main(int argc, char **argv, const char *prgname, const char *i runas = optarg; break; + case 'R': + chroot = optarg; + break; + #ifdef WITH_VTPM_PROXY case 'v': use_vtpm_proxy = true; @@ -466,6 +474,11 @@ int swtpm_chardev_main(int argc, char **argv, const char *prgname, const char *i exit(EXIT_FAILURE); } + if (chroot) { + if (do_chroot(chroot) < 0) + exit(EXIT_FAILURE); + } + /* change process ownership before accessing files */ if (runas) { if (change_process_owner(runas) < 0) diff --git a/src/swtpm/utils.c b/src/swtpm/utils.c index 8beb32d..14cc976 100644 --- a/src/swtpm/utils.c +++ b/src/swtpm/utils.c @@ -146,6 +146,23 @@ change_process_owner(const char *user) return 0; } +int +do_chroot(const char *path) +{ + if (chroot(path) < 0) { + logprintf(STDERR_FILENO, "chroot failed: %s\n", + strerror(errno)); + return -1; + } + + if (chdir("/") < 0) { + logprintf(STDERR_FILENO, "chdir failed: %s\n", + strerror(errno)); + return -1; + } + return 0; +} + void tpmlib_debug_libtpms_parameters(TPMLIB_TPMVersion tpmversion) { switch (tpmversion) { diff --git a/src/swtpm/utils.h b/src/swtpm/utils.h index 7502442..d3e6b30 100644 --- a/src/swtpm/utils.h +++ b/src/swtpm/utils.h @@ -61,6 +61,7 @@ typedef void (*sighandler_t)(int); int install_sighandlers(int pipefd[2], sighandler_t handler); void uninstall_sighandlers(void); int change_process_owner(const char *owner); +int do_chroot(const char *path); void tpmlib_debug_libtpms_parameters(TPMLIB_TPMVersion); diff --git a/tests/Makefile.am b/tests/Makefile.am index eb9dae3..0318fd0 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -47,6 +47,9 @@ TESTS += \ TESTS += \ test_tpm2_avoid_da_lockout \ + test_tpm2_chroot_socket \ + test_tpm2_chroot_chardev \ + test_tpm2_chroot_cuse \ test_tpm2_ctrlchannel2 \ test_tpm2_derived_keys \ test_tpm2_encrypted_state \ diff --git a/tests/test_tpm2_chroot_chardev b/tests/test_tpm2_chroot_chardev new file mode 100755 index 0000000..faab321 --- /dev/null +++ b/tests/test_tpm2_chroot_chardev @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +# For the license, see the LICENSE file in the root directory. + +if [ "$(id -u)" -ne 0 ]; then + echo "Need to be root to run this test." + exit 77 +fi + +if [ "$(uname -s)" != "Linux" ]; then + # Due to using /proc//root + echo "This test only runs only Linux." + exit 77 +fi + +ROOT=${abs_top_builddir:-$(dirname "$0")/..} +TESTDIR=${abs_top_testdir:-$(dirname "$0")} + +SWTPM=swtpm +SWTPM_EXE=${SWTPM_EXE:-$ROOT/src/swtpm/$SWTPM} +PID_FILE=/${SWTPM}.pid + +source ${TESTDIR}/common +source ${TESTDIR}/test_common +skip_test_no_chardev "${SWTPM_EXE}" +skip_test_no_tpm20 "${SWTPM_EXE}" + +trap "cleanup" SIGTERM EXIT + +function cleanup() +{ + rm -rf $TPMDIR + if [ -n "$PID" ]; then + kill_quiet -SIGTERM $PID 2>/dev/null + fi +} + +for OPTION in --chroot -R; do + TPMDIR="$(mktemp -d)" || exit 1 + mkdir $TPMDIR/dev + mknod -m 0666 $TPMDIR/dev/urandom c 1 9 + + # use a pseudo terminal + exec 100<>/dev/ptmx + $SWTPM_EXE chardev \ + --fd 100 \ + "$OPTION" $TPMDIR \ + --tpmstate dir=/ \ + --pid file=$PID_FILE \ + --tpm2 \ + --flags not-need-init \ + ${SWTPM_TEST_SECCOMP_OPT} & + PID=$! + + if wait_for_file $TPMDIR/$PID_FILE 3; then + echo "Error: Chardev TPM did not write pidfile." + exit 1 + fi + + validate_pidfile $PID $TPMDIR/$PID_FILE + + if [ "$(readlink /proc/$PID/root)" != $TPMDIR ]; then + echo "Test 1 failed: Unexpected chroot dir" + exit 1 + fi + + if [ ! -f ${TPMDIR}/tpm2-00.permall ]; then + echo "Missing state file" + exit 1 + fi + + echo "Test $OPTION passed" + cleanup +done diff --git a/tests/test_tpm2_chroot_cuse b/tests/test_tpm2_chroot_cuse new file mode 100755 index 0000000..33e9887 --- /dev/null +++ b/tests/test_tpm2_chroot_cuse @@ -0,0 +1,81 @@ +#!/usr/bin/env bash + +# For the license, see the LICENSE file in the root directory. + +if [ "$(id -u)" -ne 0 ]; then + echo "Need to be root to run this test." + exit 77 +fi + +if [ "$(uname -s)" != "Linux" ]; then + # Due to using /proc//root + echo "This test only runs only Linux." + exit 77 +fi + +ROOT=${abs_top_builddir:-$(dirname "$0")/..} +TESTDIR=${abs_top_testdir:-$(dirname "$0")} + +SWTPM=swtpm +SWTPM_EXE=${SWTPM_EXE:-$ROOT/src/swtpm/$SWTPM} +PID_FILE=/${SWTPM}.pid +VTPM_NAME="vtpm-test-chroot" +SWTPM_DEV_NAME="/dev/${VTPM_NAME}" + +source ${TESTDIR}/common +source ${TESTDIR}/test_common +source ${TESTDIR}/test_cuse + +skip_test_no_tpm20 "${SWTPM_EXE}" + +trap "cleanup" SIGTERM EXIT + +function cleanup() +{ + rm -rf $TPMDIR + if [ -n "$PID" ]; then + kill_quiet -SIGTERM $PID 2>/dev/null + fi +} + +for OPTION in --chroot -R; do + TPMDIR="$(mktemp -d)" || exit 1 + mkdir $TPMDIR/dev + mknod -m 0666 $TPMDIR/dev/urandom c 1 9 + mknod -m 0666 $TPMDIR/dev/cuse c 10 203 + + $SWTPM_EXE cuse \ + -n "$SWTPM_DEV_NAME" \ + "$OPTION" $TPMDIR \ + --tpmstate dir=/ \ + --pid file=$PID_FILE \ + --tpm2 \ + --flags not-need-init \ + ${SWTPM_TEST_SECCOMP_OPT} &>/dev/null & + + if wait_for_file $TPMDIR/$PID_FILE 3; then + echo "Error: CUSE TPM did not write pidfile." + exit 1 + fi + + PID=$(ps aux | + grep "cuse" | + grep " ${SWTPM_DEV_NAME}" | + grep -v grep | + gawk '{print $2}') + + validate_pidfile $PID $TPMDIR/$PID_FILE + + if [ "$(readlink /proc/$PID/root)" != $TPMDIR ]; then + echo "Test 1 failed: Unexpected chroot dir" + exit 1 + fi + + if [ ! -f ${TPMDIR}/tpm2-00.permall ]; then + echo "Missing state file" + exit 1 + fi + + echo "Test $OPTION passed" + cleanup +done diff --git a/tests/test_tpm2_chroot_socket b/tests/test_tpm2_chroot_socket new file mode 100755 index 0000000..3109b08 --- /dev/null +++ b/tests/test_tpm2_chroot_socket @@ -0,0 +1,78 @@ +#!/usr/bin/env bash + +# For the license, see the LICENSE file in the root directory. + +if [ "$(id -u)" -ne 0 ]; then + echo "Need to be root to run this test." + exit 77 +fi + +if [ "$(uname -s)" != "Linux" ]; then + # Due to using /proc//root + echo "This test only runs only Linux." + exit 77 +fi + +ROOT=${abs_top_builddir:-$(dirname "$0")/..} +TESTDIR=${abs_top_testdir:=$(dirname "$0")} + +SWTPM=swtpm +SWTPM_EXE=${SWTPM_EXE:-$ROOT/src/swtpm/$SWTPM} +PID_FILE=/${SWTPM}.pid + +source ${TESTDIR}/common +source ${TESTDIR}/test_common +skip_test_no_chardev "${SWTPM_EXE}" +skip_test_no_tpm20 "${SWTPM_EXE}" + +trap "cleanup" SIGTERM EXIT + +function cleanup() +{ + rm -rf $TPMDIR + if [ -n "$PID" ]; then + kill_quiet -SIGTERM $PID 2>/dev/null + fi +} + +PORT=65468 + +export TCSD_TCP_DEVICE_HOSTNAME=localhost +export TCSD_TCP_DEVICE_PORT=$PORT +export TCSD_USE_TCP_DEVICE=1 + +for OPTION in --chroot -R; do + TPMDIR="$(mktemp -d)" || exit 1 + mkdir $TPMDIR/dev + mknod -m 0666 $TPMDIR/dev/urandom c 1 9 + + $SWTPM_EXE socket \ + -p $PORT \ + "$OPTION" $TPMDIR \ + --tpmstate dir=/ \ + --pid file=$PID_FILE \ + --tpm2 \ + --flags not-need-init \ + ${SWTPM_TEST_SECCOMP_OPT} &>/dev/null & + PID=$! + + if wait_for_file $TPMDIR/$PID_FILE 3; then + echo "Error: socket TPM did not write pidfile." + exit 1 + fi + + validate_pidfile $PID $TPMDIR/$PID_FILE + + if [ "$(readlink /proc/$PID/root)" != $TPMDIR ]; then + echo "Test 1 failed: Unexpected chroot dir" + exit 1 + fi + + if [ ! -f ${TPMDIR}/tpm2-00.permall ]; then + echo "Missing state file" + exit 1 + fi + + echo "Test $OPTION passed" + cleanup +done