/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/file.h>
+#include <sys/stat.h>
#include <unistd.h>
#include "sd-device.h"
#include "chase.h"
#include "color-util.h"
#include "conf-files.h"
+#include "copy.h"
#include "creds-util.h"
#include "crypto-util.h"
#include "efi-api.h"
#include "string-util.h"
#include "strv.h"
#include "time-util.h"
+#include "tmpfile-util.h"
#include "tpm2-pcr.h"
#include "tpm2-util.h"
#include "unaligned.h"
#define PCRLOCK_MACHINE_ID_PATH "/var/lib/pcrlock.d/820-machine-id.pcrlock"
#define PCRLOCK_ROOT_FILE_SYSTEM_PATH "/var/lib/pcrlock.d/830-root-file-system.pcrlock"
#define PCRLOCK_FILE_SYSTEM_PATH_PREFIX "/var/lib/pcrlock.d/840-file-system-"
+#define PCRLOCK_PE_INPUT_MAX (2U * U64_GB)
/* The default set of PCRs to lock to */
#define DEFAULT_PCR_MASK \
return unlink_pcrlock(PCRLOCK_GPT_PATH);
}
+static int pe_stdin_too_large(void) {
+ return log_error_errno(SYNTHETIC_ERRNO(EFBIG),
+ "PE/UKI binary from stdin is larger than the maximum supported size of %s.",
+ FORMAT_BYTES(PCRLOCK_PE_INPUT_MAX));
+}
+
+static int acquire_stdin_pe_fd(void) {
+ _cleanup_close_ int fd = -EBADF;
+ const char *td;
+ struct stat st;
+ int r;
+
+ if (fstat(STDIN_FILENO, &st) < 0)
+ return log_error_errno(errno, "Failed to stat stdin: %m");
+
+ if (S_ISREG(st.st_mode)) {
+ if ((uint64_t) st.st_size > PCRLOCK_PE_INPUT_MAX)
+ return pe_stdin_too_large();
+
+ /* Regular stdin is already seekable. Keep it directly to avoid copying large redirected
+ * PE/UKI files before hashing them. The size check above is a fast-path guard; a file that
+ * grows concurrently is still hashed stream-wise with bounded memory use. */
+ fd = fcntl(STDIN_FILENO, F_DUPFD_CLOEXEC, 3);
+ if (fd < 0)
+ return log_error_errno(errno, "Failed to duplicate stdin: %m");
+
+ return TAKE_FD(fd);
+ }
+
+ r = var_tmp_dir(&td);
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine temporary directory: %m");
+
+ fd = open_tmpfile_unlinkable(td, O_RDWR|O_CLOEXEC);
+ if (fd < 0)
+ return log_error_errno(fd, "Failed to create temporary file for PE binary: %m");
+
+ r = copy_bytes(STDIN_FILENO, fd, PCRLOCK_PE_INPUT_MAX + 1, /* copy_flags= */ 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to copy PE binary from stdin to temporary file: %m");
+ if (r > 0)
+ return pe_stdin_too_large();
+
+ return TAKE_FD(fd);
+}
+
+static int acquire_pe_fd(const char *path) {
+ if (path) {
+ int fd = open(path, O_RDONLY|O_CLOEXEC);
+ if (fd < 0)
+ return log_error_errno(errno, "Failed to open '%s': %m", path);
+
+ return fd;
+ }
+
+ return acquire_stdin_pe_fd();
+}
+
VERB(verb_lock_pe, "lock-pe", "[BINARY]", VERB_ANY, 2, 0,
"Generate a .pcrlock file from PE binary");
static int verb_lock_pe(int argc, char *argv[], uintptr_t _data, void *userdata) {
// FIXME: Maybe also generate a matching EV_EFI_VARIABLE_AUTHORITY records here for each signature that
// covers this PE plus its hash, as alternatives under the same component name
- if (argc >= 2) {
- fd = open(argv[1], O_RDONLY|O_CLOEXEC);
- if (fd < 0)
- return log_error_errno(errno, "Failed to open '%s': %m", argv[1]);
- }
+ fd = acquire_pe_fd(argc >= 2 ? argv[1] : NULL);
+ if (fd < 0)
+ return fd;
if (arg_pcr_mask == 0)
arg_pcr_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE;
assert_se(a = tpm2_hash_alg_to_string(*pa));
assert_se(md = sym_EVP_get_digestbyname(a));
- r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &hash, &hash_size);
+ r = pe_hash(fd, md, &hash, &hash_size);
if (r < 0)
return log_error_errno(r, "Failed to hash PE binary: %m");
if (arg_pcr_mask != 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "PCR not configurable for UKI lock down.");
- if (argc >= 2) {
- fd = open(argv[1], O_RDONLY|O_CLOEXEC);
- if (fd < 0)
- return log_error_errno(errno, "Failed to open '%s': %m", argv[1]);
- }
+ fd = acquire_pe_fd(argc >= 2 ? argv[1] : NULL);
+ if (fd < 0)
+ return fd;
for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) {
_cleanup_free_ void *peh = NULL;
assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i]));
assert_se(md = sym_EVP_get_digestbyname(a));
- r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &peh, hash_sizes + i);
+ r = pe_hash(fd, md, &peh, hash_sizes + i);
if (r < 0)
return log_error_errno(r, "Failed to hash PE binary: %m");
if (r < 0)
return log_error_errno(r, "Failed to build JSON digest object: %m");
- r = uki_hash(fd < 0 ? STDIN_FILENO : fd, md, section_hashes + (i * _UNIFIED_SECTION_MAX), hash_sizes + i);
+ r = uki_hash(fd, md, section_hashes + (i * _UNIFIED_SECTION_MAX), hash_sizes + i);
if (r < 0)
return log_error_errno(r, "Failed to UKI hash PE binary: %m");
}