1
0
mirror of https://github.com/systemd/systemd.git synced 2025-02-07 05:57:46 +03:00

Merge pull request #29004 from poettering/measure-log

tpm2: whenever we measure, also write a tpm log record
This commit is contained in:
Lennart Poettering 2023-08-31 09:47:35 +02:00 committed by GitHub
commit 359b5e7539
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 200 additions and 4 deletions

View File

@ -241,6 +241,7 @@ static int get_file_system_word(
static int run(int argc, char *argv[]) {
_cleanup_free_ char *joined = NULL, *word = NULL;
Tpm2UserspaceEventType event;
unsigned target_pcr_nr;
size_t length;
int r;
@ -291,6 +292,7 @@ static int run(int argc, char *argv[]) {
}
target_pcr_nr = TPM2_PCR_SYSTEM_IDENTITY; /* → PCR 15 */
event = TPM2_EVENT_FILESYSTEM;
} else if (arg_machine_id) {
sd_id128_t mid;
@ -307,6 +309,7 @@ static int run(int argc, char *argv[]) {
return log_oom();
target_pcr_nr = TPM2_PCR_SYSTEM_IDENTITY; /* → PCR 15 */
event = TPM2_EVENT_MACHINE_ID;
} else {
if (optind+1 != argc)
@ -323,6 +326,7 @@ static int run(int argc, char *argv[]) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "String to measure cannot be empty, refusing.");
target_pcr_nr = TPM2_PCR_KERNEL_BOOT; /* → PCR 11 */
event = TPM2_EVENT_PHASE;
}
if (arg_graceful && tpm2_support() != TPM2_SUPPORT_FULL) {
@ -358,7 +362,7 @@ static int run(int argc, char *argv[]) {
log_debug("Measuring '%s' into PCR index %u, banks %s.", word, target_pcr_nr, joined);
r = tpm2_extend_bytes(c, arg_banks, target_pcr_nr, word, length, NULL, 0);
r = tpm2_extend_bytes(c, arg_banks, target_pcr_nr, word, length, NULL, 0, event, word);
if (r < 0)
return r;

View File

@ -862,7 +862,7 @@ static int measure_volume_key(
if (!s)
return log_oom();
r = tpm2_extend_bytes(c, l ?: arg_tpm2_measure_banks, arg_tpm2_measure_pcr, s, SIZE_MAX, volume_key, volume_key_size);
r = tpm2_extend_bytes(c, l ?: arg_tpm2_measure_banks, arg_tpm2_measure_pcr, s, SIZE_MAX, volume_key, volume_key_size, TPM2_EVENT_VOLUME_KEY, s);
if (r < 0)
return r;

View File

@ -1,5 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/file.h>
#include "alloc-util.h"
#include "constants.h"
#include "cryptsetup-util.h"
@ -14,10 +16,12 @@
#include "hexdecoct.h"
#include "hmac.h"
#include "initrd-util.h"
#include "io-util.h"
#include "lock-util.h"
#include "log.h"
#include "logarithm.h"
#include "memory-util.h"
#include "mkdir.h"
#include "nulstr-util.h"
#include "parse-util.h"
#include "random-util.h"
@ -25,6 +29,7 @@
#include "sort-util.h"
#include "stat-util.h"
#include "string-table.h"
#include "sync-util.h"
#include "time-util.h"
#include "tpm2-util.h"
#include "virt.h"
@ -4293,6 +4298,159 @@ int tpm2_find_device_auto(
}
#if HAVE_TPM2
static const char* tpm2_userspace_event_type_table[_TPM2_USERSPACE_EVENT_TYPE_MAX] = {
[TPM2_EVENT_PHASE] = "phase",
[TPM2_EVENT_FILESYSTEM] = "filesystem",
[TPM2_EVENT_VOLUME_KEY] = "volume-key",
[TPM2_EVENT_MACHINE_ID] = "machine-id",
};
DEFINE_STRING_TABLE_LOOKUP(tpm2_userspace_event_type, Tpm2UserspaceEventType);
const char *tpm2_userspace_log_path(void) {
return secure_getenv("SYSTEMD_MEASURE_LOG_USERSPACE") ?: "/var/log/systemd/tpm2-measure.log";
}
static int tpm2_userspace_log_open(void) {
_cleanup_close_ int fd = -EBADF;
struct stat st;
const char *e;
int r;
e = tpm2_userspace_log_path();
(void) mkdir_parents(e, 0755);
/* We use access mode 0600 here (even though the measurements should not strictly be confidential),
* because we use BSD file locking on it, and if anyone but root can access the file they can also
* lock it, which we want to avoid. */
fd = open(e, O_CREAT|O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
if (fd < 0)
return log_warning_errno(errno, "Failed to open TPM log file '%s' for writing, ignoring: %m", e);
if (flock(fd, LOCK_EX) < 0)
return log_warning_errno(errno, "Failed to lock TPM log file '%s', ignoring: %m", e);
if (fstat(fd, &st) < 0)
return log_warning_errno(errno, "Failed to fstat TPM log file '%s', ignoring: %m", e);
r = stat_verify_regular(&st);
if (r < 0)
return log_warning_errno(r, "TPM log file '%s' is not regular, ignoring: %m", e);
/* We set the sticky bit when we are about to append to the log file. We'll unset it afterwards
* again. If we manage to take a lock on a file that has it set we know we didn't write it fully and
* it is corrupted. Ideally we'd like to use user xattrs for this, but unfortunately tmpfs (which is
* our assumed backend fs) doesn't know user xattrs. */
if (st.st_mode & S_ISVTX)
return log_warning_errno(SYNTHETIC_ERRNO(ESTALE), "TPM log file '%s' aborted, ignoring.", e);
if (fchmod(fd, 0600 | S_ISVTX) < 0)
return log_warning_errno(errno, "Failed to chmod() TPM log file '%s', ignoring: %m", e);
return TAKE_FD(fd);
}
static int tpm2_userspace_log(
int fd,
unsigned pcr_index,
const TPML_DIGEST_VALUES *values,
Tpm2UserspaceEventType event_type,
const char *description) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *array = NULL;
_cleanup_free_ char *f = NULL;
sd_id128_t boot_id;
int r;
assert(values);
assert(values->count > 0);
/* We maintain a local PCR measurement log. This implements a subset of the TCG Canonical Event Log
* Format the JSON flavour
* (https://trustedcomputinggroup.org/resource/canonical-event-log-format/), but departs in certain
* ways from it, specifically:
*
* - We don't write out a recnum. It's a bit too vaguely defined which means we'd have to read
* through the whole logs (include firmware logs) before knowing what the next value is we should
* use. Hence we simply don't write this out as append-time, and instead expect a consumer to add
* it in when it uses the data.
*
* - We write this out in RFC 7464 application/json-seq rather than as a JSON array. Writing this as
* JSON array would mean that for each appending we'd have to read the whole log file fully into
* memory before writing it out again. We prefer a strictly append-only write pattern however. (RFC
* 7464 is what jq --seq eats.) Conversion into a proper JSON array is trivial.
*
* It should be possible to convert this format in a relatively straight-forward way into the
* official TCG Canonical Event Log Format on read, by simply adding in a few more fields that can be
* determined from the full dataset.
*
* We set the 'content_type' field to "systemd" to make clear this data is generated by us, and
* include various interesting fields in the 'content' subobject, including a CLOCK_BOOTTIME
* timestamp which can be used to order this measurement against possibly other measurements
* independently done by other subsystems on the system.
*/
if (fd < 0) /* Apparently tpm2_local_log_open() failed earlier, let's not complain again */
return 0;
for (size_t i = 0; i < values->count; i++) {
const EVP_MD *implementation;
const char *a;
assert_se(a = tpm2_hash_alg_to_string(values->digests[i].hashAlg));
assert_se(implementation = EVP_get_digestbyname(a));
r = json_variant_append_arrayb(
&array, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_STRING("hashAlg", a),
JSON_BUILD_PAIR("digest", JSON_BUILD_HEX(&values->digests[i].digest, EVP_MD_size(implementation)))));
if (r < 0)
return log_error_errno(r, "Failed to append digest object to JSON array: %m");
}
assert(array);
r = sd_id128_get_boot(&boot_id);
if (r < 0)
return log_error_errno(r, "Failed to acquire boot ID: %m");
r = json_build(&v, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("pcr", JSON_BUILD_UNSIGNED(pcr_index)),
JSON_BUILD_PAIR("digests", JSON_BUILD_VARIANT(array)),
JSON_BUILD_PAIR("content_type", JSON_BUILD_STRING("systemd")),
JSON_BUILD_PAIR("content", JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_CONDITION(description, "string", JSON_BUILD_STRING(description)),
JSON_BUILD_PAIR("bootId", JSON_BUILD_ID128(boot_id)),
JSON_BUILD_PAIR("timestamp", JSON_BUILD_UNSIGNED(now(CLOCK_BOOTTIME))),
JSON_BUILD_PAIR_CONDITION(event_type >= 0, "eventType", JSON_BUILD_STRING(tpm2_userspace_event_type_to_string(event_type)))))));
if (r < 0)
return log_error_errno(r, "Failed to build log record JSON: %m");
r = json_variant_format(v, JSON_FORMAT_SEQ, &f);
if (r < 0)
return log_error_errno(r, "Failed to format JSON: %m");
if (lseek(fd, 0, SEEK_END) == (off_t) -1)
return log_error_errno(errno, "Failed to seek to end of JSON log: %m");
r = loop_write(fd, f, SIZE_MAX, /* do_poll= */ false);
if (r < 0)
return log_error_errno(r, "Failed to write JSON data to log: %m");
if (fsync(fd) < 0)
return log_error_errno(errno, "Failed to sync JSON data: %m");
/* Unset S_ISVTX again */
if (fchmod(fd, 0600) < 0)
return log_warning_errno(errno, "Failed to chmod() TPM log file, ignoring: %m");
r = fsync_full(fd);
if (r < 0)
return log_error_errno(r, "Failed to sync JSON log: %m");
return 1;
}
int tpm2_extend_bytes(
Tpm2Context *c,
char **banks,
@ -4300,9 +4458,12 @@ int tpm2_extend_bytes(
const void *data,
size_t data_size,
const void *secret,
size_t secret_size) {
size_t secret_size,
Tpm2UserspaceEventType event_type,
const char *description) {
#if HAVE_OPENSSL
_cleanup_close_ int log_fd = -EBADF;
TPML_DIGEST_VALUES values = {};
TSS2_RC rc;
@ -4354,6 +4515,10 @@ int tpm2_extend_bytes(
values.count++;
}
/* Open + lock the log file *before* we start measuring, so that noone else can come between our log
* and our measurement and change either */
log_fd = tpm2_userspace_log_open();
rc = sym_Esys_PCR_Extend(
c->esys_context,
ESYS_TR_PCR0 + pcr_index,
@ -4368,6 +4533,9 @@ int tpm2_extend_bytes(
pcr_index,
sym_Tss2_RC_Decode(rc));
/* Now, write what we just extended to the log, too. */
(void) tpm2_userspace_log(log_fd, pcr_index, &values, event_type, description);
return 0;
#else /* HAVE_OPENSSL */
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled.");

View File

@ -112,7 +112,21 @@ int tpm2_get_good_pcr_banks(Tpm2Context *c, uint32_t pcr_mask, TPMI_ALG_HASH **r
int tpm2_get_good_pcr_banks_strv(Tpm2Context *c, uint32_t pcr_mask, char ***ret);
int tpm2_get_best_pcr_bank(Tpm2Context *c, uint32_t pcr_mask, TPMI_ALG_HASH *ret);
int tpm2_extend_bytes(Tpm2Context *c, char **banks, unsigned pcr_index, const void *data, size_t data_size, const void *secret, size_t secret_size);
const char *tpm2_userspace_log_path(void);
typedef enum Tpm2UserspaceEventType {
TPM2_EVENT_PHASE,
TPM2_EVENT_FILESYSTEM,
TPM2_EVENT_VOLUME_KEY,
TPM2_EVENT_MACHINE_ID,
_TPM2_USERSPACE_EVENT_TYPE_MAX,
_TPM2_USERSPACE_EVENT_TYPE_INVALID = -EINVAL,
} Tpm2UserspaceEventType;
const char* tpm2_userspace_event_type_to_string(Tpm2UserspaceEventType type) _const_;
Tpm2UserspaceEventType tpm2_userspace_event_type_from_string(const char *s) _pure_;
int tpm2_extend_bytes(Tpm2Context *c, char **banks, unsigned pcr_index, const void *data, size_t data_size, const void *secret, size_t secret_size, Tpm2UserspaceEventType event, const char *description);
uint32_t tpm2_tpms_pcr_selection_to_mask(const TPMS_PCR_SELECTION *s);
void tpm2_tpms_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash, TPMS_PCR_SELECTION *ret);

View File

@ -264,6 +264,10 @@ if [[ -x "$SD_PCRPHASE" ]] && tpm_has_pcr sha256 11 && tpm_has_pcr sha256 15; th
rm -f /tmp/oldpcr15 /tmp/newpcr15
# Check that the event log record was properly written:
test "$(jq --seq --slurp '.[0].pcr' < /var/log/systemd/tpm2-measure.log)" == "$(printf '\x1e15')"
test "$(jq --seq --slurp --raw-output '.[0].digests[1].digest' < /var/log/systemd/tpm2-measure.log) *stdin" == "$(echo -n "machine-id:994013bf23864ee7992eab39a96dd3bb" | openssl dgst -hex -sha256 -r)"
# And similar for the boot phase measurement into PCR 11
tpm2_pcrread sha256:11 -Q -o /tmp/oldpcr11
SYSTEMD_FORCE_MEASURE=1 "$SD_PCRPHASE" foobar
@ -272,6 +276,12 @@ if [[ -x "$SD_PCRPHASE" ]] && tpm_has_pcr sha256 11 && tpm_has_pcr sha256 15; th
diff /tmp/newpcr11 \
<(cat /tmp/oldpcr11 <(echo -n "foobar" | openssl dgst -binary -sha256) | openssl dgst -binary -sha256)
# Check the event log for the 2nd record
jq --seq --slurp < /var/log/systemd/tpm2-measure.log
test "$(jq --seq --slurp .[1].pcr < /var/log/systemd/tpm2-measure.log)" == "$(printf '\x1e11')"
test "$(jq --seq --slurp --raw-output .[1].digests[0].digest < /var/log/systemd/tpm2-measure.log) *stdin" == "$(echo -n "foobar" | openssl dgst -hex -sha256 -r)"
rm -f /tmp/oldpcr11 /tmp/newpcr11
else
echo "$SD_PCRPHASE or PCR sysfs files not found, skipping PCR extension test case"