tpm2: re-manufacture software TPM when state dir is incomplete
setup_swtpm() decided whether a software TPM had already been manufactured by checking whether the state directory was empty. But manufacture_swtpm() writes swtpm's config files before forking swtpm_setup, so an interrupted manufacture leaves the directory non-empty yet without a usable TPM. The next boot then mistook it for a complete TPM and started swtpm against a broken state directory. Keying off a swtpm state file like tpm2-00.permall is no better, as swtpm_setup gives no guarantee any single one is written atomically or last. Instead, have manufacture_swtpm() write a marker (.manufactured) as its very last step, once swtpm_setup has exited successfully, and gate on it: re-manufacture when it is missing in the initrd, and refuse rather than start a broken TPM outside it. Signed-off-by: Paul Meyer <katexochen0@gmail.com>
This commit is contained in:
+15
-2
@@ -1,5 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sd-json.h"
|
||||
@@ -8,6 +9,7 @@
|
||||
#include "escape.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "fs-util.h"
|
||||
#include "json-util.h"
|
||||
#include "log.h"
|
||||
#include "memfd-util.h"
|
||||
@@ -17,6 +19,7 @@
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "swtpm-util.h"
|
||||
#include "sync-util.h"
|
||||
|
||||
static int swtpm_find_best_profile(const char *swtpm_setup, char **ret) {
|
||||
int r;
|
||||
@@ -135,9 +138,9 @@ int manufacture_swtpm(const char *state_dir, const char *secret) {
|
||||
|
||||
assert(state_dir);
|
||||
|
||||
_cleanup_close_ int state_dir_fd = xopenat(AT_FDCWD, state_dir, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
|
||||
_cleanup_close_ int state_dir_fd = open(state_dir, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
|
||||
if (state_dir_fd < 0)
|
||||
return log_error_errno(state_dir_fd, "Failed to open TPM state directory '%s': %m", state_dir);
|
||||
return log_error_errno(errno, "Failed to open TPM state directory '%s': %m", state_dir);
|
||||
|
||||
_cleanup_free_ char *swtpm_setup = NULL;
|
||||
r = find_executable("swtpm_setup", &swtpm_setup);
|
||||
@@ -238,5 +241,15 @@ int manufacture_swtpm(const char *state_dir, const char *secret) {
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Persist swtpm_setup's freshly created TPM state before writing the completion marker. */
|
||||
r = syncfs_path(state_dir_fd, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to sync TPM state directory: %m");
|
||||
|
||||
/* Marker, written last, signals that manufacturing completed successfully. */
|
||||
_cleanup_close_ int marker_fd = xopenat(state_dir_fd, SWTPM_MANUFACTURED_MARKER, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOFOLLOW);
|
||||
if (marker_fd < 0)
|
||||
return log_error_errno(marker_fd, "Failed to write '%s' marker: %m", SWTPM_MANUFACTURED_MARKER);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
/* Marker file written into the TPM state directory once swtpm_setup has successfully created the TPM state.
|
||||
* It is written last, so its presence reliably means a complete TPM was manufactured, rather than a manufacture
|
||||
* that was interrupted halfway through. */
|
||||
#define SWTPM_MANUFACTURED_MARKER ".manufactured"
|
||||
|
||||
int manufacture_swtpm(const char *state_dir, const char *secret);
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
#include "main-func.h"
|
||||
#include "path-lookup.h"
|
||||
#include "path-util.h"
|
||||
#include "rm-rf.h"
|
||||
#include "sha256.h"
|
||||
#include "stat-util.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "swtpm-util.h"
|
||||
@@ -116,18 +116,28 @@ static int setup_swtpm(const char *state_dir, int state_fd, const char *secret)
|
||||
return log_error_errno(r, "Failed to remove 'tpm2-00.volatilestate': %m");
|
||||
}
|
||||
|
||||
r = dir_is_empty_at(state_fd, /* path= */ NULL, /* ignore_hidden_or_backup= */ false);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to check if TPM state directory is empty: %m");
|
||||
if (r == 0) {
|
||||
log_debug("TPM state directory is already populated, not manufacturing a TPM.");
|
||||
/* manufacture_swtpm() writes its marker only after swtpm_setup has fully created the TPM state.
|
||||
* If the marker file is missing, the existing state is incomplete and recreation is needed. */
|
||||
r = RET_NERRNO(faccessat(state_fd, SWTPM_MANUFACTURED_MARKER, F_OK, AT_SYMLINK_NOFOLLOW));
|
||||
if (r >= 0) {
|
||||
log_debug("TPM state directory holds a fully manufactured TPM, not manufacturing a TPM.");
|
||||
return 0;
|
||||
}
|
||||
if (r != -ENOENT)
|
||||
return log_error_errno(r, "Failed to check for TPM manufacture marker: %m");
|
||||
|
||||
if (!in_initrd())
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ESTALE), "swtpm TPM state directory has not been initialized in the initrd, refusing.");
|
||||
|
||||
log_debug("TPM state directory is unpopulated, manufacturing a TPM.");
|
||||
/* Cleanup incomplete state before recreating. */
|
||||
_cleanup_close_ int wipe_fd = fd_reopen(state_fd, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
|
||||
if (wipe_fd < 0)
|
||||
return log_error_errno(wipe_fd, "Failed to reopen swtpm state directory: %m");
|
||||
r = rm_rf_children(TAKE_FD(wipe_fd), REMOVE_PHYSICAL, /* root_dev= */ NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to clear incomplete swtpm state directory: %m");
|
||||
|
||||
log_debug("TPM state directory holds no fully manufactured TPM, manufacturing a TPM.");
|
||||
|
||||
return manufacture_swtpm(state_dir, secret);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user