From: Felix Dörre Date: Fri, 18 Aug 2023 08:00:40 +0000 (+0200) Subject: journalctl: verify sealed log epochs are continuous X-Git-Tag: v255-rc2~72 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e375bc5fcc080815ce849261eb505d084c2fde3e;p=thirdparty%2Fsystemd.git journalctl: verify sealed log epochs are continuous Currently empty epochs are not sealed. This allows an attacker to truncate a sealed log and continue it without any problems showing when verifying the log. This partially addresses CVE-2023-31438. One way to extend this change to address CVE-2023-31438 completely, would be to verify that there is exactly one seal per epoch (and not sealing when the epoch has not ended yet). the change also adds a journal-file flag: HEADER_COMPATIBLE_SEALED_CONTINUOUS this flag indicates that a journal file is sealed continuously and decides whether any missing crypto epochs should trigger a warning or an error. --- diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index 58e23f7f76e..85def6c56ba 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -677,6 +677,7 @@ static int server_archive_offline_user_journals(Server *s) { TAKE_FD(fd); /* Donated to journal_file_open() */ + journal_file_write_final_tag(f); r = journal_file_archive(f, NULL); if (r < 0) log_debug_errno(r, "Failed to archive journal file '%s', ignoring: %m", full); diff --git a/src/libsystemd/sd-journal/journal-authenticate.c b/src/libsystemd/sd-journal/journal-authenticate.c index 10e5eafbfcf..8e7533eee3d 100644 --- a/src/libsystemd/sd-journal/journal-authenticate.c +++ b/src/libsystemd/sd-journal/journal-authenticate.c @@ -45,8 +45,11 @@ int journal_file_append_tag(JournalFile *f) { if (!JOURNAL_HEADER_SEALED(f->header)) return 0; - if (!f->hmac_running) - return 0; + if (!f->hmac_running) { + r = journal_file_hmac_start(f); + if (r < 0) + return r; + } assert(f->hmac); @@ -166,6 +169,11 @@ int journal_file_fsprg_evolve(JournalFile *f, uint64_t realtime) { FSPRG_Evolve(f->fsprg_state); epoch = FSPRG_GetEpoch(f->fsprg_state); + if (epoch < goal) { + r = journal_file_append_tag(f); + if (r < 0) + return r; + } } } diff --git a/src/libsystemd/sd-journal/journal-def.h b/src/libsystemd/sd-journal/journal-def.h index b773f26719e..1b10f24aa9c 100644 --- a/src/libsystemd/sd-journal/journal-def.h +++ b/src/libsystemd/sd-journal/journal-def.h @@ -192,10 +192,12 @@ enum { enum { HEADER_COMPATIBLE_SEALED = 1 << 0, HEADER_COMPATIBLE_TAIL_ENTRY_BOOT_ID = 1 << 1, /* if set, the last_entry_boot_id field in the header is exclusively refreshed when an entry is appended */ - HEADER_COMPATIBLE_ANY = HEADER_COMPATIBLE_SEALED| - HEADER_COMPATIBLE_TAIL_ENTRY_BOOT_ID, + HEADER_COMPATIBLE_SEALED_CONTINUOUS = 1 << 2, + HEADER_COMPATIBLE_ANY = HEADER_COMPATIBLE_SEALED | + HEADER_COMPATIBLE_TAIL_ENTRY_BOOT_ID | + HEADER_COMPATIBLE_SEALED_CONTINUOUS, - HEADER_COMPATIBLE_SUPPORTED = (HAVE_GCRYPT ? HEADER_COMPATIBLE_SEALED : 0) | + HEADER_COMPATIBLE_SUPPORTED = (HAVE_GCRYPT ? HEADER_COMPATIBLE_SEALED | HEADER_COMPATIBLE_SEALED_CONTINUOUS : 0) | HEADER_COMPATIBLE_TAIL_ENTRY_BOOT_ID, }; diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index 9658afafa6b..4b74d06997a 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -414,7 +414,7 @@ static int journal_file_init_header( keyed_hash_requested() * HEADER_INCOMPATIBLE_KEYED_HASH | compact_mode_requested() * HEADER_INCOMPATIBLE_COMPACT), .compatible_flags = htole32( - (seal * HEADER_COMPATIBLE_SEALED) | + (seal * (HEADER_COMPATIBLE_SEALED | HEADER_COMPATIBLE_SEALED_CONTINUOUS) ) | HEADER_COMPATIBLE_TAIL_ENTRY_BOOT_ID), }; @@ -487,6 +487,8 @@ static bool warn_wrong_flags(const JournalFile *f, bool compatible) { if (compatible) { if (flags & HEADER_COMPATIBLE_SEALED) strv[n++] = "sealed"; + if (flags & HEADER_COMPATIBLE_SEALED_CONTINUOUS) + strv[n++] = "sealed-continuous"; } else { if (flags & HEADER_INCOMPATIBLE_COMPRESSED_XZ) strv[n++] = "xz-compressed"; @@ -3827,7 +3829,7 @@ void journal_file_print_header(JournalFile *f) { "Boot ID: %s\n" "Sequential number ID: %s\n" "State: %s\n" - "Compatible flags:%s%s%s\n" + "Compatible flags:%s%s%s%s\n" "Incompatible flags:%s%s%s%s%s%s\n" "Header size: %"PRIu64"\n" "Arena size: %"PRIu64"\n" @@ -3850,6 +3852,7 @@ void journal_file_print_header(JournalFile *f) { f->header->state == STATE_ONLINE ? "ONLINE" : f->header->state == STATE_ARCHIVED ? "ARCHIVED" : "UNKNOWN", JOURNAL_HEADER_SEALED(f->header) ? " SEALED" : "", + JOURNAL_HEADER_SEALED_CONTINUOUS(f->header) ? " SEALED_CONTINUOUS" : "", JOURNAL_HEADER_TAIL_ENTRY_BOOT_ID(f->header) ? " TAIL_ENTRY_BOOT_ID" : "", (le32toh(f->header->compatible_flags) & ~HEADER_COMPATIBLE_ANY) ? " ???" : "", JOURNAL_HEADER_COMPRESSED_XZ(f->header) ? " COMPRESSED-XZ" : "", diff --git a/src/libsystemd/sd-journal/journal-file.h b/src/libsystemd/sd-journal/journal-file.h index 183a5e43bb4..81fafb9becd 100644 --- a/src/libsystemd/sd-journal/journal-file.h +++ b/src/libsystemd/sd-journal/journal-file.h @@ -189,6 +189,9 @@ static inline bool VALID_EPOCH(uint64_t u) { #define JOURNAL_HEADER_SEALED(h) \ FLAGS_SET(le32toh((h)->compatible_flags), HEADER_COMPATIBLE_SEALED) +#define JOURNAL_HEADER_SEALED_CONTINUOUS(h) \ + FLAGS_SET(le32toh((h)->compatible_flags), HEADER_COMPATIBLE_SEALED_CONTINUOUS) + #define JOURNAL_HEADER_TAIL_ENTRY_BOOT_ID(h) \ FLAGS_SET(le32toh((h)->compatible_flags), HEADER_COMPATIBLE_TAIL_ENTRY_BOOT_ID) diff --git a/src/libsystemd/sd-journal/journal-verify.c b/src/libsystemd/sd-journal/journal-verify.c index aa086c651e2..654993ac81f 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -816,7 +816,7 @@ int journal_file_verify( bool show_progress) { int r; Object *o; - uint64_t p = 0, last_epoch = 0, last_tag_realtime = 0, last_sealed_realtime = 0; + uint64_t p = 0, last_epoch = 0, last_tag_realtime = 0; uint64_t entry_seqnum = 0, entry_monotonic = 0, entry_realtime = 0; usec_t min_entry_realtime = USEC_INFINITY, max_entry_realtime = 0; @@ -925,6 +925,10 @@ int journal_file_verify( goto fail; } + if (!JOURNAL_HEADER_SEALED_CONTINUOUS(f->header)) + warning(p, + "This log file was sealed with an old journald version where the sequence of seals might not be continuous. We cannot guarantee completeness."); + /* First iteration: we go through all objects, verify the * superficial structure, headers, hashes. */ @@ -1128,13 +1132,25 @@ int journal_file_verify( goto fail; } - if (le64toh(o->tag.epoch) < last_epoch) { - error(p, - "Epoch sequence out of synchronization (%"PRIu64" < %"PRIu64")", - le64toh(o->tag.epoch), - last_epoch); - r = -EBADMSG; - goto fail; + if (JOURNAL_HEADER_SEALED_CONTINUOUS(f->header)) { + if (!(n_tags == 0 || (n_tags == 1 && le64toh(o->tag.epoch) == last_epoch) + || le64toh(o->tag.epoch) == last_epoch + 1)) { + error(p, + "Epoch sequence not continuous (%"PRIu64" vs %"PRIu64")", + le64toh(o->tag.epoch), + last_epoch); + r = -EBADMSG; + goto fail; + } + } else { + if (le64toh(o->tag.epoch) < last_epoch) { + error(p, + "Epoch sequence out of synchronization (%"PRIu64" < %"PRIu64")", + le64toh(o->tag.epoch), + last_epoch); + r = -EBADMSG; + goto fail; + } } #if HAVE_GCRYPT @@ -1216,7 +1232,6 @@ int journal_file_verify( f->hmac_running = false; last_tag_realtime = rt; - last_sealed_realtime = entry_realtime; } last_tag = p + ALIGN64(le64toh(o->object.size)); @@ -1389,8 +1404,10 @@ int journal_file_verify( if (first_contained) *first_contained = le64toh(f->header->head_entry_realtime); +#if HAVE_GCRYPT if (last_validated) - *last_validated = last_sealed_realtime; + *last_validated = last_tag_realtime + f->fss_interval_usec; +#endif if (last_contained) *last_contained = le64toh(f->header->tail_entry_realtime); diff --git a/src/shared/journal-file-util.c b/src/shared/journal-file-util.c index 44bf292a69e..e444a2bdd85 100644 --- a/src/shared/journal-file-util.c +++ b/src/shared/journal-file-util.c @@ -379,20 +379,23 @@ bool journal_file_is_offlining(JournalFile *f) { return true; } +void journal_file_write_final_tag(JournalFile *f) { + assert(f); +#if HAVE_GCRYPT + if (!JOURNAL_HEADER_SEALED(f->header) || !journal_file_writable(f)) + return; + + int r = journal_file_append_tag(f); + if (r < 0) + log_debug_errno(r, "Failed to append tag when closing journal: %m"); +#endif +} + JournalFile* journal_file_offline_close(JournalFile *f) { if (!f) return NULL; -#if HAVE_GCRYPT - /* Write the final tag */ - if (JOURNAL_HEADER_SEALED(f->header) && journal_file_writable(f)) { - int r; - - r = journal_file_append_tag(f); - if (r < 0) - log_error_errno(r, "Failed to append tag when closing journal: %m"); - } -#endif + journal_file_write_final_tag(f); if (sd_event_source_get_enabled(f->post_change_timer, NULL) > 0) journal_file_post_change(f); @@ -435,6 +438,7 @@ int journal_file_rotate( assert(f); assert(*f); + journal_file_write_final_tag(*f); r = journal_file_archive(*f, &path); if (r < 0) return r; diff --git a/src/shared/journal-file-util.h b/src/shared/journal-file-util.h index cbe29c762eb..f9426c47d74 100644 --- a/src/shared/journal-file-util.h +++ b/src/shared/journal-file-util.h @@ -5,6 +5,7 @@ int journal_file_set_offline(JournalFile *f, bool wait); bool journal_file_is_offlining(JournalFile *f); +void journal_file_write_final_tag(JournalFile *f); JournalFile* journal_file_offline_close(JournalFile *f); DEFINE_TRIVIAL_CLEANUP_FUNC(JournalFile*, journal_file_offline_close);