From 36cac53661f2e47505affa34b323a73fc0677828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Old=C5=99ich=20Jedli=C4=8Dka?= Date: Fri, 8 Nov 2024 22:40:53 +0100 Subject: [PATCH] initramfs: fix running on root-only readable initrd filesystem MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oldřich Jedlička --- meson.build | 2 + src/initramfs-tools/hooks/clevis.in | 14 ++++-- .../scripts/local-top/clevis.in | 4 ++ ...unctions => clevis-luks-tpm1-functions.in} | 4 +- .../dracut/clevis-pin-tpm1/module-setup.sh.in | 10 ++-- .../dracut/clevis/clevis-password-unlocker.in | 2 +- src/luks/dracut/clevis/meson.build | 12 +++-- src/luks/meson.build | 23 ++++++++-- src/luks/systemd/meson.build | 26 ++++------- src/pins/tpm1/clevis-tpm1-tcsd-preload.c | 46 +++++++++++++++++++ src/pins/tpm1/meson.build | 7 +++ src/pins/tpm1/tests/meson.build | 1 + src/pins/tpm1/tests/tcsd-patch.c.in | 15 ------ .../tpm1/tests/tpm1-common-test-functions.in | 6 ++- 14 files changed, 116 insertions(+), 56 deletions(-) rename src/luks/{clevis-luks-tpm1-functions => clevis-luks-tpm1-functions.in} (93%) create mode 100644 src/pins/tpm1/clevis-tpm1-tcsd-preload.c diff --git a/meson.build b/meson.build index 7bf0bff4..b64653f3 100644 --- a/meson.build +++ b/meson.build @@ -6,11 +6,13 @@ project('clevis', 'c', license: 'GPL3+', libexecdir = join_paths(get_option('prefix'), get_option('libexecdir')) sysconfdir = join_paths(get_option('prefix'), get_option('sysconfdir')) bindir = join_paths(get_option('prefix'), get_option('bindir')) +libdir = join_paths(get_option('prefix'), get_option('libdir')) data = configuration_data() data.set('libexecdir', libexecdir) data.set('sysconfdir', sysconfdir) data.set('bindir', bindir) +data.set('libdir', libdir) add_project_arguments( '-Wall', diff --git a/src/initramfs-tools/hooks/clevis.in b/src/initramfs-tools/hooks/clevis.in index e4aef627..44e2c794 100755 --- a/src/initramfs-tools/hooks/clevis.in +++ b/src/initramfs-tools/hooks/clevis.in @@ -99,12 +99,11 @@ fi if [ -x @bindir@/clevis-decrypt-tpm1 ]; then copy_exec @bindir@/clevis-decrypt-tpm1 || die 1 "@bindir@/clevis-decrypt-tpm1 not found" copy_exec @libexecdir@/clevis-luks-tpm1-functions || die 1 "@libexecdir@/clevis-luks-tpm1-functions not found" + copy_exec @libdir@/libclevis-tpm1-tcsd-preload.so || die 1 "@libdir@/libclevis-tpm1-tcsd-preload.so not found" tcsd_bin=$(find_binary "tcsd") tpm_version_bin=$(find_binary "tpm_version") tpm_unsealdata_bin=$(find_binary "tpm_unsealdata") - stdbuf_bin=$(find_binary "stdbuf") - libstdbuf_bin=$(find_library "coreutils/libstdbuf.so*") copy_exec "${tpm_version_bin}" || die 1 "Unable to copy ${tpm_version_bin}" copy_exec "${tpm_unsealdata_bin}" || die 1 "Unable to copy ${tpm_unsealdata_bin}" @@ -112,12 +111,17 @@ if [ -x @bindir@/clevis-decrypt-tpm1 ]; then copy_exec "${tcsd_bin}" || die 1 "Unable to copy ${tcsd_bin}" copy_file config /etc/tcsd.conf || dia 2 "Unable to copy /etc/tcsd.conf" - copy_exec "${stdbuf_bin}" || die 1 "Unable to copy ${stdbuf_bin}" - copy_exec "${libstdbuf_bin}" || die 1 "Unable to copy ${libstdbuf_bin}" - mkdir -p "${DESTDIR}/var/lib/tpm" || die 2 "Unable to create /var/lib/tpm" cp /var/lib/tpm/* "${DESTDIR}/var/lib/tpm/" || die 2 "Unable to copy /var/lib/tpm" + if [ -n "$UMASK" ] && (( UMASK & 0004 )); then + # Root-only readable initrd filesystem, we need to run as root + # shellcheck disable=SC2154 # $verbose is a dracut variable + [ "${verbose}" = "y" ] && echo "Forcing tcsd to run as root" + sed -i 's/^\([ ]*remote_ops\)/#\1/' "${DESTDIR}/etc/tcsd.conf" + echo "TCSD_NO_PRIVILEGE_DROP=1" >> "${DESTDIR}/conf/conf.d/clevis" + fi + mkdir -p "${DESTDIR}/lib/udev/rules.d" || die 2 "Unable to create /lib/udev/rules.d" # shellcheck disable=SC2043 for rule in 60-tpm-udev.rules; do diff --git a/src/initramfs-tools/scripts/local-top/clevis.in b/src/initramfs-tools/scripts/local-top/clevis.in index 8f877ea7..e8431ec5 100755 --- a/src/initramfs-tools/scripts/local-top/clevis.in +++ b/src/initramfs-tools/scripts/local-top/clevis.in @@ -282,6 +282,10 @@ do_configure_tpm1() { wait_for_udev 10 + # shellcheck disable=SC2034 # setting default value + TCSD_NO_PRIVILEGE_DROP=0 + [ -f /conf/conf.d/clevis ] && . /conf/conf.d/clevis + if ! tcsd_output=$(start_tcsd 2>&1); then if [ -n "$tcsd_output" ]; then log_failure_msg "failed to start TCSD: $tcsd_output" diff --git a/src/luks/clevis-luks-tpm1-functions b/src/luks/clevis-luks-tpm1-functions.in similarity index 93% rename from src/luks/clevis-luks-tpm1-functions rename to src/luks/clevis-luks-tpm1-functions.in index 6656f7a0..969b7645 100755 --- a/src/luks/clevis-luks-tpm1-functions +++ b/src/luks/clevis-luks-tpm1-functions.in @@ -63,8 +63,8 @@ start_tcsd() { # available for debugging, so start TCSD in foreground mode, but as a # background job. Unfortunatelly the redirected output to pipe is # block-buffered (see `man 3 setbuf`), so in order to see any output we - # need to set it to line-buffered with stdbuf tool - stdbuf -oL tcsd -f >$fifo_file 2>&1 & + # need to set it to line-buffered with LD_PRELOAD library + TCSD_NO_PRIVILEGE_DROP=${TCSD_NO_PRIVILEGE_DROP:-0} LD_PRELOAD="@libdir@/libclevis-tpm1-tcsd-preload.so" tcsd -f >$fifo_file 2>&1 & tcsd_pid=$! wait $sleep_pid 2>/dev/null diff --git a/src/luks/dracut/clevis-pin-tpm1/module-setup.sh.in b/src/luks/dracut/clevis-pin-tpm1/module-setup.sh.in index 5eb7a1f4..f0deab0c 100755 --- a/src/luks/dracut/clevis-pin-tpm1/module-setup.sh.in +++ b/src/luks/dracut/clevis-pin-tpm1/module-setup.sh.in @@ -64,7 +64,7 @@ require_nonempty_dir() { check() { local _module_name="${moddir##*/[0-9][0-9]}" - require_binaries clevis-decrypt-tpm1 tpm_version tpm_unsealdata tcsd stdbuf || return 1 + require_binaries clevis-decrypt-tpm1 tpm_version tpm_unsealdata tcsd || return 1 if [[ $hostonly ]]; then require_nonempty_dir /var/lib/tpm || return 1 else @@ -96,13 +96,9 @@ install() { systemctl -q --root "$initdir" add-wants cryptsetup.target tcsd.service else inst_multiple \ - awk chmod chown mkfifo mktemp ip ps stdbuf \ + awk chmod chown mkfifo mktemp ip ps \ + @libdir@/libclevis-tpm1-tcsd-preload.so \ @libexecdir@/clevis-luks-tpm1-functions - if [ -f /usr/libexec/coreutils/libstdbuf.so ]; then - inst_multiple /usr/libexec/coreutils/libstdbuf.so* - else - inst_libdir_file 'coreutils/libstdbuf.so*' - fi fi inst_multiple \ diff --git a/src/luks/dracut/clevis/clevis-password-unlocker.in b/src/luks/dracut/clevis/clevis-password-unlocker.in index 9190edaa..b755c1eb 100755 --- a/src/luks/dracut/clevis/clevis-password-unlocker.in +++ b/src/luks/dracut/clevis/clevis-password-unlocker.in @@ -168,7 +168,7 @@ do_configure_tpm1() { info "Starting TCSD daemon" - if ! tcsd_output=$(start_tcsd 2>&1); then + if ! tcsd_output=$(TCSD_NO_PRIVILEGE_DROP=0 start_tcsd 2>&1); then if [ -n "$tcsd_output" ]; then echo "Unable to start TCSD: $tcsd_output" | vwarn else diff --git a/src/luks/dracut/clevis/meson.build b/src/luks/dracut/clevis/meson.build index 4fd4486b..eed325f6 100644 --- a/src/luks/dracut/clevis/meson.build +++ b/src/luks/dracut/clevis/meson.build @@ -3,32 +3,36 @@ dracut = dependency('dracut', required: false) if dracut.found() dracutdir = dracut.get_pkgconfig_variable('dracutmodulesdir') + '/60' + meson.project_name() + dracut_data = configuration_data() + dracut_data.merge_from(data) + dracut_data.set('SYSTEMD_REPLY_PASS', sd_reply_pass.path()) + configure_file( input: 'module-setup.sh.in', output: 'module-setup.sh', install_dir: dracutdir, - configuration: data, + configuration: dracut_data, ) configure_file( input: 'clevis-cleanup.in', output: 'clevis-cleanup', install_dir: dracutdir, - configuration: data, + configuration: dracut_data, ) configure_file( input: 'clevis-password-unlocker.in', output: 'clevis-password-unlocker', install_dir: dracutdir, - configuration: data, + configuration: dracut_data, ) configure_file( input: 'clevis-password-unlocker-prepare.in', output: 'clevis-password-unlocker-prepare', install_dir: dracutdir, - configuration: data, + configuration: dracut_data, ) install_data('clevis-cleanup-hook.sh', install_dir: dracutdir) diff --git a/src/luks/meson.build b/src/luks/meson.build index 818ffc0d..2a9bb7b0 100644 --- a/src/luks/meson.build +++ b/src/luks/meson.build @@ -28,14 +28,31 @@ clevis_luks_common_functions = configure_file( configuration: luksmeta_data ) +clevis_luks_tpm1_functions = configure_file( + input: 'clevis-luks-tpm1-functions.in', + output: 'clevis-luks-tpm1-functions', + configuration: data +) + clevis_luks_unbind = configure_file(input: 'clevis-luks-unbind.in', output: 'clevis-luks-unbind', configuration: luksmeta_data) +# SystemD dependencies checked here, used both in systemd and dracut subdirs +systemd = dependency('systemd', required: false) +systemdutildir = systemd.found() ? systemd.get_pkgconfig_variable('systemdutildir', default: '') : '' + +sd_reply_pass = find_program( + (systemdutildir != '') ? join_paths(systemdutildir, 'systemd-reply-password') : '', + join_paths(get_option('prefix'), get_option('libdir'), 'systemd', 'systemd-reply-password'), + join_paths(get_option('prefix'), 'lib', 'systemd', 'systemd-reply-password'), + join_paths('/', 'usr', get_option('libdir'), 'systemd', 'systemd-reply-password'), + join_paths('/', 'usr', 'lib', 'systemd', 'systemd-reply-password'), + required: false +) + if libcryptsetup.found() and luksmeta.found() subdir('systemd') - # systemd should come before dracut in order to set up - # variables like SYSTEMD_REPLY_PASS. subdir('dracut') subdir('udisks2') @@ -67,7 +84,7 @@ if libcryptsetup.found() and luksmeta.found() bins += join_paths(meson.current_source_dir(), 'clevis-luks-pass') mans += join_paths(meson.current_source_dir(), 'clevis-luks-pass.1') - install_data('clevis-luks-tpm1-functions', install_dir: libexecdir) + install_data(clevis_luks_tpm1_functions, install_dir: libexecdir) else warning('Will not install LUKS support due to missing dependencies!') endif diff --git a/src/luks/systemd/meson.build b/src/luks/systemd/meson.build index e1f9180e..64e678cd 100644 --- a/src/luks/systemd/meson.build +++ b/src/luks/systemd/meson.build @@ -1,17 +1,7 @@ -systemd = dependency('systemd', required: false) -systemdutildir = systemd.found() ? systemd.get_pkgconfig_variable('systemdutildir', default: '') : '' - -sd_reply_pass = find_program( - (systemdutildir != '') ? join_paths(systemdutildir, 'systemd-reply-password') : '', - join_paths(get_option('prefix'), get_option('libdir'), 'systemd', 'systemd-reply-password'), - join_paths(get_option('prefix'), 'lib', 'systemd', 'systemd-reply-password'), - join_paths('/', 'usr', get_option('libdir'), 'systemd', 'systemd-reply-password'), - join_paths('/', 'usr', 'lib', 'systemd', 'systemd-reply-password'), - required: false -) - if systemd.found() and sd_reply_pass.found() - data.set('SYSTEMD_REPLY_PASS', sd_reply_pass.path()) + systemd_data = configuration_data() + systemd_data.merge_from(data) + systemd_data.set('SYSTEMD_REPLY_PASS', sd_reply_pass.path()) unitdir = systemd.get_pkgconfig_variable('systemdsystemunitdir') tcsdoverridedir = join_paths(unitdir, 'tcsd.service.d') @@ -20,31 +10,31 @@ if systemd.found() and sd_reply_pass.found() input: 'clevis-luks-askpass.service.in', output: 'clevis-luks-askpass.service', install_dir: unitdir, - configuration: data, + configuration: systemd_data, ) configure_file( input: 'clevis-luks-pkcs11-askpass.service.in', output: 'clevis-luks-pkcs11-askpass.service', install_dir: unitdir, - configuration: data, + configuration: systemd_data, ) configure_file( input: 'clevis-luks-askpass.in', output: 'clevis-luks-askpass', install_dir: libexecdir, - configuration: data + configuration: systemd_data ) configure_file( input: 'clevis-luks-pkcs11-askpass.in', output: 'clevis-luks-pkcs11-askpass', install_dir: libexecdir, - configuration: data + configuration: systemd_data ) configure_file( input: 'clevis-luks-pkcs11-askpin.in', output: 'clevis-luks-pkcs11-askpin', install_dir: libexecdir, - configuration: data + configuration: systemd_data ) install_data('clevis-luks-askpass.path', install_dir: unitdir) diff --git a/src/pins/tpm1/clevis-tpm1-tcsd-preload.c b/src/pins/tpm1/clevis-tpm1-tcsd-preload.c new file mode 100644 index 00000000..3f2b4da5 --- /dev/null +++ b/src/pins/tpm1/clevis-tpm1-tcsd-preload.c @@ -0,0 +1,46 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include + +#define TCSD_NO_PRIVILEGE_DROP_ENV "TCSD_NO_PRIVILEGE_DROP" + +static int no_privilege_drop(void) { + char *no_privilege_drop_env = getenv(TCSD_NO_PRIVILEGE_DROP_ENV); + return (no_privilege_drop_env != NULL + && no_privilege_drop_env[0] != '\0' + && no_privilege_drop_env[0] != '0'); +} + +int setuid(uid_t uid) { + static int (*real_setuid)(uid_t) = NULL; + if (no_privilege_drop()) { + return 0; + } else { + if (!real_setuid) { + real_setuid = dlsym(RTLD_NEXT, "setuid"); + } + return real_setuid(uid); + } +} + +int setgid(gid_t gid) { + static int (*real_setgid)(uid_t) = NULL; + if (no_privilege_drop()) { + return 0; + } else { + if (!real_setgid) { + real_setgid = dlsym(RTLD_NEXT, "setgid"); + } + return real_setgid(gid); + } + return 0; +} + +static void __attribute ((constructor)) +set_line_buffering (void) +{ + setvbuf(stdout, NULL, _IOLBF, 0); +} diff --git a/src/pins/tpm1/meson.build b/src/pins/tpm1/meson.build index 1c07bbb4..6f7cca0b 100644 --- a/src/pins/tpm1/meson.build +++ b/src/pins/tpm1/meson.build @@ -9,6 +9,13 @@ if all bins += join_paths(meson.current_source_dir(), 'clevis-decrypt-tpm1') bins += join_paths(meson.current_source_dir(), 'clevis-encrypt-tpm1') mans += join_paths(meson.current_source_dir(), 'clevis-encrypt-tpm1.1') + + libdl_dep = dependency('dl', required: true) + libclevis_tpm1_tcsd_preload = shared_library('clevis-tpm1-tcsd-preload', 'clevis-tpm1-tcsd-preload.c', + dependencies: libdl_dep, + install_dir: libdir, + install: true) + subdir('tests') else warning('Will not install tpm1 pin due to missing dependencies!') diff --git a/src/pins/tpm1/tests/meson.build b/src/pins/tpm1/tests/meson.build index fd999aa7..cf832188 100644 --- a/src/pins/tpm1/tests/meson.build +++ b/src/pins/tpm1/tests/meson.build @@ -32,6 +32,7 @@ tpm1_data.set('TCSD_BIN', tcsd.found() ? tcsd.path() : '') tpm1_data.set('SWTPM_BIN', swtpm.found() ? swtpm.path() : '') tpm1_data.set('SWTPM_SETUP_BIN', swtpm_setup.found() ? swtpm_setup.path() : '') tpm1_data.set('SWTPM_BIOS_BIN', swtpm_bios.found() ? swtpm_bios.path() : '') +tpm1_data.set('LIBCLEVIS_TPM1_TCSD_PRELOAD', libclevis_tpm1_tcsd_preload.path()) configure_file( input: 'tpm1-common-test-functions.in', diff --git a/src/pins/tpm1/tests/tcsd-patch.c.in b/src/pins/tpm1/tests/tcsd-patch.c.in index 99e85523..7921fbc2 100644 --- a/src/pins/tpm1/tests/tcsd-patch.c.in +++ b/src/pins/tpm1/tests/tcsd-patch.c.in @@ -74,18 +74,3 @@ int __xstat(int ver, const char *pathname, struct stat *statbuf) { return result; } #endif - -int setuid(uid_t uid) { - return 0; -} - -int setgid(gid_t gid) { - return 0; -} - -static void __attribute ((constructor)) -set_line_buffering (void) -{ - setvbuf(stdout, NULL, _IOLBF, 0); - fprintf(stderr, "set_line_buffering : done\n"); -} diff --git a/src/pins/tpm1/tests/tpm1-common-test-functions.in b/src/pins/tpm1/tests/tpm1-common-test-functions.in index 59b225cf..219e48e4 100644 --- a/src/pins/tpm1/tests/tpm1-common-test-functions.in +++ b/src/pins/tpm1/tests/tpm1-common-test-functions.in @@ -29,6 +29,7 @@ TCSD_BIN="@TCSD_BIN@" SWTPM_BIN="@SWTPM_BIN@" SWTPM_SETUP_BIN="@SWTPM_SETUP_BIN@" SWTPM_BIOS_BIN="@SWTPM_BIOS_BIN@" +LIBCLEVIS_TPM1_TCSD_PRELOAD="@LIBCLEVIS_TPM1_TCSD_PRELOAD@" SWTPM_SOCKET_PID= TCSD_PID= @@ -136,7 +137,10 @@ EOM chmod 0640 "$TESTDIR"/tcsd.conf fi - LD_PRELOAD="$TCSD_PATCH_LIB" TCSD_UN_SOCKET_DEVICE_PATH="$TESTDIR"/swtpm.sock "${TCSD_BIN}" -f -e -c "$TESTDIR"/tcsd.conf >&2 &2