]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
journalctl: verify sealed log epochs are continuous
authorFelix Dörre <felix.doerre@kit.edu>
Fri, 18 Aug 2023 08:00:40 +0000 (10:00 +0200)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 8 Nov 2023 20:13:27 +0000 (05:13 +0900)
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.

src/journal/journald-server.c
src/libsystemd/sd-journal/journal-authenticate.c
src/libsystemd/sd-journal/journal-def.h
src/libsystemd/sd-journal/journal-file.c
src/libsystemd/sd-journal/journal-file.h
src/libsystemd/sd-journal/journal-verify.c
src/shared/journal-file-util.c
src/shared/journal-file-util.h

index 58e23f7f76edd715b4ed068c286b794a9520b999..85def6c56baeb8e1aa4b22914060640e7020418b 100644 (file)
@@ -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);
index 10e5eafbfcf5afb4f25eba4a345c9985138f621f..8e7533eee3d852207a8632305fa03bf89371d1d9 100644 (file)
@@ -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;
+                }
         }
 }
 
index b773f26719eb791c0768e89497203402137b52c9..1b10f24aa9c5969d762e743cccd82893f34a8d79 100644 (file)
@@ -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,
 };
 
index 9658afafa6bc7dd14a80b581c401a875bb8a6650..4b74d06997a1610c5234890f2047eecee466e30b 100644 (file)
@@ -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" : "",
index 183a5e43bb434bc838abfdf46c326b7dc524c94a..81fafb9becdb4e06a30683a483670ea0a3e78535 100644 (file)
@@ -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)
 
index aa086c651e2c655cd38d10c09e8e43d96cc480a0..654993ac81f390f60d9c576189519e5cc62657c3 100644 (file)
@@ -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);
 
index 44bf292a69ec936ca7f409f663acef29122c4a75..e444a2bdd853365540c9a0b47779793dc03bdd1e 100644 (file)
@@ -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;
index cbe29c762eb0912f7b60f5f5e42b56f643ff4cbd..f9426c47d7447e2b1721a1afbf8b9908a54e3952 100644 (file)
@@ -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);