1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2012 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
28 #include "journal-def.h"
29 #include "journal-file.h"
30 #include "journal-authenticate.h"
31 #include "journal-verify.h"
38 * - write tag only if non-tag objects have been written
39 * - write bit mucking test
40 * - tag timestamps should be between entry timestamps
41 * - output validated time ranges
42 * - add missing fields to journal header dump
44 * - Allow building without libgcrypt
50 static int journal_file_object_verify(JournalFile *f, Object *o) {
54 /* This does various superficial tests about the length an
55 * possible field values. It does not follow any references to
58 if ((o->object.flags & OBJECT_COMPRESSED) &&
59 o->object.type != OBJECT_DATA)
62 switch (o->object.type) {
67 if (le64toh(o->data.entry_offset) <= 0 ||
68 le64toh(o->data.n_entries) <= 0)
71 if (le64toh(o->object.size) - offsetof(DataObject, payload) <= 0)
74 h1 = le64toh(o->data.hash);
76 if (o->object.flags & OBJECT_COMPRESSED) {
78 uint64_t alloc = 0, b_size;
80 if (!uncompress_blob(o->data.payload,
81 le64toh(o->object.size) - offsetof(Object, data.payload),
85 h2 = hash64(b, b_size);
88 h2 = hash64(o->data.payload, le64toh(o->object.size) - offsetof(Object, data.payload));
97 if (le64toh(o->object.size) - offsetof(FieldObject, payload) <= 0)
102 if ((le64toh(o->object.size) - offsetof(EntryObject, items)) % sizeof(EntryItem) != 0)
105 if ((le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem) <= 0)
108 if (le64toh(o->entry.seqnum) <= 0 ||
109 le64toh(o->entry.realtime) <= 0)
114 case OBJECT_DATA_HASH_TABLE:
115 case OBJECT_FIELD_HASH_TABLE:
116 if ((le64toh(o->object.size) - offsetof(HashTableObject, items)) % sizeof(HashItem) != 0)
119 if ((le64toh(o->object.size) - offsetof(HashTableObject, items)) / sizeof(HashItem) <= 0)
124 case OBJECT_ENTRY_ARRAY:
125 if ((le64toh(o->object.size) - offsetof(EntryArrayObject, items)) % sizeof(le64_t) != 0)
128 if ((le64toh(o->object.size) - offsetof(EntryArrayObject, items)) / sizeof(le64_t) <= 0)
134 if (le64toh(o->object.size) != sizeof(TagObject))
142 static void draw_progress(uint64_t p, usec_t *last_usec) {
146 if (!isatty(STDOUT_FILENO))
149 z = now(CLOCK_MONOTONIC);
152 if (x != 0 && x + 40 * USEC_PER_MSEC > z)
157 n = (3 * columns()) / 4;
158 j = (n * (unsigned) p) / 65535ULL;
161 fputs("\r\x1B[?25l" ANSI_HIGHLIGHT_GREEN_ON, stdout);
163 for (i = 0; i < j; i++)
164 fputs("\xe2\x96\x88", stdout);
166 fputs(ANSI_HIGHLIGHT_OFF, stdout);
168 for (i = 0; i < k; i++)
169 fputs("\xe2\x96\x91", stdout);
171 printf(" %3lu%%", 100LU * (unsigned long) p / 65535LU);
173 fputs("\r\x1B[?25h", stdout);
177 static void flush_progress(void) {
180 if (!isatty(STDOUT_FILENO))
183 n = (3 * columns()) / 4;
187 for (i = 0; i < n + 5; i++)
194 static int write_uint64(int fd, uint64_t p) {
197 k = write(fd, &p, sizeof(p));
206 static int contains_uint64(MMapCache *m, int fd, uint64_t n, uint64_t p) {
221 r = mmap_cache_get(m, fd, PROT_READ|PROT_WRITE, 0, c * sizeof(uint64_t), sizeof(uint64_t), (void **) &z);
237 static int entry_points_to_data(
250 assert(entry_fd >= 0);
252 if (!contains_uint64(f->mmap, entry_fd, n_entries, entry_p)) {
253 log_error("Data object references invalid entry at %llu", (unsigned long long) data_p);
257 r = journal_file_move_to_object(f, OBJECT_ENTRY, entry_p, &o);
261 n = journal_file_entry_n_items(o);
262 for (i = 0; i < n; i++)
263 if (le64toh(o->entry.items[i].object_offset) == data_p) {
269 log_error("Data object not referenced by linked entry at %llu", (unsigned long long) data_p);
273 /* Check if this entry is also in main entry array. Since the
274 * main entry array has already been verified we can rely on
277 n = le64toh(f->header->n_entries);
278 a = le64toh(f->header->entry_array_offset);
284 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
288 m = journal_file_entry_array_n_items(o);
289 for (j = 0; i < n && j < m; i++, j++)
290 if (le64toh(o->entry_array.items[j]) == entry_p)
293 a = le64toh(o->entry_array.next_entry_array_offset);;
299 static int verify_data(
301 Object *o, uint64_t p,
302 int entry_fd, uint64_t n_entries,
303 int entry_array_fd, uint64_t n_entry_arrays) {
305 uint64_t i, n, a, last, q;
310 assert(entry_fd >= 0);
311 assert(entry_array_fd >= 0);
313 n = le64toh(o->data.n_entries);
314 a = le64toh(o->data.entry_array_offset);
316 /* We already checked this earlier */
319 last = q = le64toh(o->data.entry_offset);
320 r = entry_points_to_data(f, entry_fd, n_entries, q, p);
329 log_error("Array chain too short at %llu.", (unsigned long long) p);
333 if (!contains_uint64(f->mmap, entry_array_fd, n_entry_arrays, a)) {
334 log_error("Invalid array at %llu.", (unsigned long long) p);
338 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
342 next = le64toh(o->entry_array.next_entry_array_offset);
343 if (next != 0 && next <= a) {
344 log_error("Array chain has cycle at %llu.", (unsigned long long) p);
348 m = journal_file_entry_array_n_items(o);
349 for (j = 0; i < n && j < m; i++, j++) {
351 q = le64toh(o->entry_array.items[j]);
353 log_error("Data object's entry array not sorted at %llu.", (unsigned long long) p);
358 r = entry_points_to_data(f, entry_fd, n_entries, q, p);
362 /* Pointer might have moved, reposition */
363 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
374 static int verify_hash_table(
376 int data_fd, uint64_t n_data,
377 int entry_fd, uint64_t n_entries,
378 int entry_array_fd, uint64_t n_entry_arrays,
385 assert(data_fd >= 0);
386 assert(entry_fd >= 0);
387 assert(entry_array_fd >= 0);
390 n = le64toh(f->header->data_hash_table_size) / sizeof(HashItem);
391 for (i = 0; i < n; i++) {
392 uint64_t last = 0, p;
394 draw_progress(0xC000 + (0x3FFF * i / n), last_usec);
396 p = le64toh(f->data_hash_table[i].head_hash_offset);
401 if (!contains_uint64(f->mmap, data_fd, n_data, p)) {
402 log_error("Invalid data object at hash entry %llu of %llu.",
403 (unsigned long long) i, (unsigned long long) n);
407 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
411 next = le64toh(o->data.next_hash_offset);
412 if (next != 0 && next <= p) {
413 log_error("Hash chain has a cycle in hash entry %llu of %llu.",
414 (unsigned long long) i, (unsigned long long) n);
418 if (le64toh(o->data.hash) % n != i) {
419 log_error("Hash value mismatch in hash entry %llu of %llu.",
420 (unsigned long long) i, (unsigned long long) n);
424 r = verify_data(f, o, p, entry_fd, n_entries, entry_array_fd, n_entry_arrays);
432 if (last != le64toh(f->data_hash_table[i].tail_hash_offset)) {
433 log_error("Tail hash pointer mismatch in hash table.");
441 static int data_object_in_hash_table(JournalFile *f, uint64_t hash, uint64_t p) {
446 n = le64toh(f->header->data_hash_table_size) / sizeof(HashItem);
449 q = le64toh(f->data_hash_table[h].head_hash_offset);
456 r = journal_file_move_to_object(f, OBJECT_DATA, q, &o);
460 q = le64toh(o->data.next_hash_offset);
466 static int verify_entry(
468 Object *o, uint64_t p,
469 int data_fd, uint64_t n_data) {
476 assert(data_fd >= 0);
478 n = journal_file_entry_n_items(o);
479 for (i = 0; i < n; i++) {
483 q = le64toh(o->entry.items[i].object_offset);
484 h = le64toh(o->entry.items[i].hash);
486 if (!contains_uint64(f->mmap, data_fd, n_data, q)) {
487 log_error("Invalid data object at entry %llu.",
488 (unsigned long long) o);
492 r = journal_file_move_to_object(f, OBJECT_DATA, q, &u);
496 if (le64toh(u->data.hash) != h) {
497 log_error("Hash mismatch for data object at entry %llu.",
498 (unsigned long long) p);
502 r = data_object_in_hash_table(f, h, q);
506 log_error("Data object missing from hash at entry %llu.",
507 (unsigned long long) p);
515 static int verify_entry_array(
517 int data_fd, uint64_t n_data,
518 int entry_fd, uint64_t n_entries,
519 int entry_array_fd, uint64_t n_entry_arrays,
522 uint64_t i = 0, a, n, last = 0;
526 assert(data_fd >= 0);
527 assert(entry_fd >= 0);
528 assert(entry_array_fd >= 0);
531 n = le64toh(f->header->n_entries);
532 a = le64toh(f->header->entry_array_offset);
537 draw_progress(0x8000 + (0x3FFF * i / n), last_usec);
540 log_error("Array chain too short at %llu of %llu.",
541 (unsigned long long) i, (unsigned long long) n);
545 if (!contains_uint64(f->mmap, entry_array_fd, n_entry_arrays, a)) {
546 log_error("Invalid array at %llu of %llu.",
547 (unsigned long long) i, (unsigned long long) n);
551 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
555 next = le64toh(o->entry_array.next_entry_array_offset);
556 if (next != 0 && next <= a) {
557 log_error("Array chain has cycle at %llu of %llu.",
558 (unsigned long long) i, (unsigned long long) n);
562 m = journal_file_entry_array_n_items(o);
563 for (j = 0; i < n && j < m; i++, j++) {
566 p = le64toh(o->entry_array.items[j]);
568 log_error("Entry array not sorted at %llu of %llu.",
569 (unsigned long long) i, (unsigned long long) n);
574 if (!contains_uint64(f->mmap, entry_fd, n_entries, p)) {
575 log_error("Invalid array entry at %llu of %llu.",
576 (unsigned long long) i, (unsigned long long) n);
580 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
584 r = verify_entry(f, o, p, data_fd, n_data);
588 /* Pointer might have moved, reposition */
589 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
600 static int journal_file_parse_verification_key(JournalFile *f, const char *key) {
605 unsigned long long start, interval;
607 seed_size = FSPRG_RECOMMENDED_SEEDLEN;
608 seed = malloc(seed_size);
613 for (c = 0; c < seed_size; c++) {
632 seed[c] = (uint8_t) (x * 16 + y);
641 r = sscanf(k, "%llx-%llx", &start, &interval);
647 f->fsprg_seed = seed;
648 f->fsprg_seed_size = seed_size;
650 f->fss_start_usec = start;
651 f->fss_interval_usec = interval;
656 int journal_file_verify(JournalFile *f, const char *key) {
659 uint64_t p = 0, last_tag = 0, last_epoch = 0;
660 uint64_t entry_seqnum = 0, entry_monotonic = 0, entry_realtime = 0;
661 sd_id128_t entry_boot_id;
662 bool entry_seqnum_set = false, entry_monotonic_set = false, entry_realtime_set = false, found_main_entry_array = false;
663 uint64_t n_weird = 0, n_objects = 0, n_entries = 0, n_data = 0, n_fields = 0, n_data_hash_tables = 0, n_field_hash_tables = 0, n_entry_arrays = 0, n_tags = 0;
664 usec_t last_usec = 0;
665 int data_fd = -1, entry_fd = -1, entry_array_fd = -1;
666 char data_path[] = "/var/tmp/journal-data-XXXXXX",
667 entry_path[] = "/var/tmp/journal-entry-XXXXXX",
668 entry_array_path[] = "/var/tmp/journal-entry-array-XXXXXX";
673 r = journal_file_parse_verification_key(f, key);
675 log_error("Failed to parse seed.");
680 data_fd = mkostemp(data_path, O_CLOEXEC);
682 log_error("Failed to create data file: %m");
688 entry_fd = mkostemp(entry_path, O_CLOEXEC);
690 log_error("Failed to create entry file: %m");
696 entry_array_fd = mkostemp(entry_array_path, O_CLOEXEC);
697 if (entry_array_fd < 0) {
698 log_error("Failed to create entry array file: %m");
702 unlink(entry_array_path);
704 /* First iteration: we go through all objects, verify the
705 * superficial structure, headers, hashes. */
707 p = le64toh(f->header->header_size);
709 draw_progress(0x7FFF * p / le64toh(f->header->tail_object_offset), &last_usec);
711 r = journal_file_move_to_object(f, -1, p, &o);
713 log_error("Invalid object at %llu", (unsigned long long) p);
717 if (le64toh(f->header->tail_object_offset) < p) {
718 log_error("Invalid tail object pointer.");
725 r = journal_file_object_verify(f, o);
727 log_error("Invalid object contents at %llu", (unsigned long long) p);
731 if (o->object.flags & OBJECT_COMPRESSED &&
732 !(le32toh(f->header->incompatible_flags) & HEADER_INCOMPATIBLE_COMPRESSED)) {
733 log_error("Compressed object without compression at %llu", (unsigned long long) p);
738 switch (o->object.type) {
741 r = write_uint64(data_fd, p);
753 r = write_uint64(entry_fd, p);
757 if (!entry_seqnum_set &&
758 le64toh(o->entry.seqnum) != le64toh(f->header->head_entry_seqnum)) {
759 log_error("Head entry sequence number incorrect");
764 if (entry_seqnum_set &&
765 entry_seqnum >= le64toh(o->entry.seqnum)) {
766 log_error("Entry sequence number out of synchronization at %llu", (unsigned long long) p);
771 entry_seqnum = le64toh(o->entry.seqnum);
772 entry_seqnum_set = true;
774 if (entry_monotonic_set &&
775 sd_id128_equal(entry_boot_id, o->entry.boot_id) &&
776 entry_monotonic > le64toh(o->entry.monotonic)) {
777 log_error("Entry timestamp out of synchronization at %llu", (unsigned long long) p);
782 entry_monotonic = le64toh(o->entry.monotonic);
783 entry_boot_id = o->entry.boot_id;
784 entry_monotonic_set = true;
786 if (!entry_realtime_set &&
787 le64toh(o->entry.realtime) != le64toh(f->header->head_entry_realtime)) {
788 log_error("Head entry realtime timestamp incorrect");
793 entry_realtime = le64toh(o->entry.realtime);
794 entry_realtime_set = true;
799 case OBJECT_DATA_HASH_TABLE:
800 if (n_data_hash_tables > 1) {
801 log_error("More than one data hash table at %llu", (unsigned long long) p);
806 if (le64toh(f->header->data_hash_table_offset) != p + offsetof(HashTableObject, items) ||
807 le64toh(f->header->data_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) {
808 log_error("Header fields for data hash table invalid.");
813 n_data_hash_tables++;
816 case OBJECT_FIELD_HASH_TABLE:
817 if (n_field_hash_tables > 1) {
818 log_error("More than one field hash table at %llu", (unsigned long long) p);
823 if (le64toh(f->header->field_hash_table_offset) != p + offsetof(HashTableObject, items) ||
824 le64toh(f->header->field_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) {
825 log_error("Header fields for field hash table invalid.");
830 n_field_hash_tables++;
833 case OBJECT_ENTRY_ARRAY:
834 r = write_uint64(entry_array_fd, p);
838 if (p == le64toh(f->header->entry_array_offset)) {
839 if (found_main_entry_array) {
840 log_error("More than one main entry array at %llu", (unsigned long long) p);
845 found_main_entry_array = true;
854 if (!(le32toh(f->header->compatible_flags) & HEADER_COMPATIBLE_SEALED)) {
855 log_error("Tag object without sealing at %llu", (unsigned long long) p);
860 if (le64toh(o->tag.seqnum) != n_tags + 1) {
861 log_error("Tag sequence number out of synchronization at %llu", (unsigned long long) p);
866 if (le64toh(o->tag.epoch) < last_epoch) {
867 log_error("Epoch sequence out of synchronization at %llu", (unsigned long long) p);
872 /* OK, now we know the epoch. So let's now set
873 * it, and calculate the HMAC for everything
874 * since the last tag. */
875 r = journal_file_fsprg_seek(f, le64toh(o->tag.epoch));
879 r = journal_file_hmac_start(f);
884 r = journal_file_hmac_put_header(f);
888 q = le64toh(f->header->header_size);
893 r = journal_file_move_to_object(f, -1, q, &o);
897 r = journal_file_hmac_put_object(f, -1, q);
901 q = q + ALIGN64(le64toh(o->object.size));
904 /* Position might have changed, let's reposition things */
905 r = journal_file_move_to_object(f, -1, p, &o);
909 if (memcmp(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH) != 0) {
910 log_error("Tag failed verification at %llu", (unsigned long long) p);
915 f->hmac_running = false;
917 last_tag = p + ALIGN64(le64toh(o->object.size));
926 if (p == le64toh(f->header->tail_object_offset))
929 p = p + ALIGN64(le64toh(o->object.size));
932 if (n_objects != le64toh(f->header->n_objects)) {
933 log_error("Object number mismatch");
938 if (n_entries != le64toh(f->header->n_entries)) {
939 log_error("Entry number mismatch");
944 if (JOURNAL_HEADER_CONTAINS(f->header, n_data) &&
945 n_data != le64toh(f->header->n_data)) {
946 log_error("Data number mismatch");
951 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields) &&
952 n_fields != le64toh(f->header->n_fields)) {
953 log_error("Field number mismatch");
958 if (JOURNAL_HEADER_CONTAINS(f->header, n_tags) &&
959 n_tags != le64toh(f->header->n_tags)) {
960 log_error("Tag number mismatch");
965 if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays) &&
966 n_entry_arrays != le64toh(f->header->n_entry_arrays)) {
967 log_error("Entry array number mismatch");
972 if (n_data_hash_tables != 1) {
973 log_error("Missing data hash table");
978 if (n_field_hash_tables != 1) {
979 log_error("Missing field hash table");
984 if (!found_main_entry_array) {
985 log_error("Missing entry array");
990 if (entry_seqnum_set &&
991 entry_seqnum != le64toh(f->header->tail_entry_seqnum)) {
992 log_error("Invalid tail seqnum");
997 if (entry_monotonic_set &&
998 (!sd_id128_equal(entry_boot_id, f->header->boot_id) ||
999 entry_monotonic != le64toh(f->header->tail_entry_monotonic))) {
1000 log_error("Invalid tail monotonic timestamp");
1005 if (entry_realtime_set && entry_realtime != le64toh(f->header->tail_entry_realtime)) {
1006 log_error("Invalid tail realtime timestamp");
1011 /* Second iteration: we follow all objects referenced from the
1012 * two entry points: the object hash table and the entry
1013 * array. We also check that everything referenced (directly
1014 * or indirectly) in the data hash table also exists in the
1015 * entry array, and vice versa. Note that we do not care for
1016 * unreferenced objects. We only care that everything that is
1017 * referenced is consistent. */
1019 r = verify_entry_array(f,
1021 entry_fd, n_entries,
1022 entry_array_fd, n_entry_arrays,
1027 r = verify_hash_table(f,
1029 entry_fd, n_entries,
1030 entry_array_fd, n_entry_arrays,
1037 mmap_cache_close_fd(f->mmap, data_fd);
1038 mmap_cache_close_fd(f->mmap, entry_fd);
1039 mmap_cache_close_fd(f->mmap, entry_array_fd);
1041 close_nointr_nofail(data_fd);
1042 close_nointr_nofail(entry_fd);
1043 close_nointr_nofail(entry_array_fd);
1050 log_error("File corruption detected at %s:%llu (of %llu, %llu%%).",
1052 (unsigned long long) p,
1053 (unsigned long long) f->last_stat.st_size,
1054 (unsigned long long) (100 * p / f->last_stat.st_size));
1057 mmap_cache_close_fd(f->mmap, data_fd);
1058 close_nointr_nofail(data_fd);
1061 if (entry_fd >= 0) {
1062 mmap_cache_close_fd(f->mmap, entry_fd);
1063 close_nointr_nofail(entry_fd);
1066 if (entry_array_fd >= 0) {
1067 mmap_cache_close_fd(f->mmap, entry_array_fd);
1068 close_nointr_nofail(entry_array_fd);