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"
39 * - Allow building without libgcrypt
45 static int journal_file_object_verify(JournalFile
*f
, Object
*o
) {
49 /* This does various superficial tests about the length an
50 * possible field values. It does not follow any references to
53 if ((o
->object
.flags
& OBJECT_COMPRESSED
) &&
54 o
->object
.type
!= OBJECT_DATA
)
57 switch (o
->object
.type
) {
62 if (le64toh(o
->data
.entry_offset
) <= 0 ||
63 le64toh(o
->data
.n_entries
) <= 0)
66 if (le64toh(o
->object
.size
) - offsetof(DataObject
, payload
) <= 0)
69 h1
= le64toh(o
->data
.hash
);
71 if (o
->object
.flags
& OBJECT_COMPRESSED
) {
73 uint64_t alloc
= 0, b_size
;
75 if (!uncompress_blob(o
->data
.payload
,
76 le64toh(o
->object
.size
) - offsetof(Object
, data
.payload
),
80 h2
= hash64(b
, b_size
);
83 h2
= hash64(o
->data
.payload
, le64toh(o
->object
.size
) - offsetof(Object
, data
.payload
));
92 if (le64toh(o
->object
.size
) - offsetof(FieldObject
, payload
) <= 0)
97 if ((le64toh(o
->object
.size
) - offsetof(EntryObject
, items
)) % sizeof(EntryItem
) != 0)
100 if ((le64toh(o
->object
.size
) - offsetof(EntryObject
, items
)) / sizeof(EntryItem
) <= 0)
103 if (le64toh(o
->entry
.seqnum
) <= 0 ||
104 le64toh(o
->entry
.realtime
) <= 0)
109 case OBJECT_DATA_HASH_TABLE
:
110 case OBJECT_FIELD_HASH_TABLE
:
111 if ((le64toh(o
->object
.size
) - offsetof(HashTableObject
, items
)) % sizeof(HashItem
) != 0)
114 if ((le64toh(o
->object
.size
) - offsetof(HashTableObject
, items
)) / sizeof(HashItem
) <= 0)
119 case OBJECT_ENTRY_ARRAY
:
120 if ((le64toh(o
->object
.size
) - offsetof(EntryArrayObject
, items
)) % sizeof(le64_t
) != 0)
123 if ((le64toh(o
->object
.size
) - offsetof(EntryArrayObject
, items
)) / sizeof(le64_t
) <= 0)
129 if (le64toh(o
->object
.size
) != sizeof(TagObject
))
137 static void draw_progress(uint64_t p
, usec_t
*last_usec
) {
141 if (!isatty(STDOUT_FILENO
))
144 z
= now(CLOCK_MONOTONIC
);
147 if (x
!= 0 && x
+ 40 * USEC_PER_MSEC
> z
)
152 n
= (3 * columns()) / 4;
153 j
= (n
* (unsigned) p
) / 65535ULL;
156 fputs("\r\x1B[?25l" ANSI_HIGHLIGHT_GREEN_ON
, stdout
);
158 for (i
= 0; i
< j
; i
++)
159 fputs("\xe2\x96\x88", stdout
);
161 fputs(ANSI_HIGHLIGHT_OFF
, stdout
);
163 for (i
= 0; i
< k
; i
++)
164 fputs("\xe2\x96\x91", stdout
);
166 printf(" %3lu%%", 100LU * (unsigned long) p
/ 65535LU);
168 fputs("\r\x1B[?25h", stdout
);
172 static void flush_progress(void) {
175 if (!isatty(STDOUT_FILENO
))
178 n
= (3 * columns()) / 4;
182 for (i
= 0; i
< n
+ 5; i
++)
189 static int write_uint64(int fd
, uint64_t p
) {
192 k
= write(fd
, &p
, sizeof(p
));
201 static int contains_uint64(MMapCache
*m
, int fd
, uint64_t n
, uint64_t p
) {
216 r
= mmap_cache_get(m
, fd
, PROT_READ
|PROT_WRITE
, 0, c
* sizeof(uint64_t), sizeof(uint64_t), (void **) &z
);
232 static int entry_points_to_data(
245 assert(entry_fd
>= 0);
247 if (!contains_uint64(f
->mmap
, entry_fd
, n_entries
, entry_p
)) {
248 log_error("Data object references invalid entry at %llu", (unsigned long long) data_p
);
252 r
= journal_file_move_to_object(f
, OBJECT_ENTRY
, entry_p
, &o
);
256 n
= journal_file_entry_n_items(o
);
257 for (i
= 0; i
< n
; i
++)
258 if (le64toh(o
->entry
.items
[i
].object_offset
) == data_p
) {
264 log_error("Data object not referenced by linked entry at %llu", (unsigned long long) data_p
);
268 /* Check if this entry is also in main entry array. Since the
269 * main entry array has already been verified we can rely on
272 n
= le64toh(f
->header
->n_entries
);
273 a
= le64toh(f
->header
->entry_array_offset
);
279 r
= journal_file_move_to_object(f
, OBJECT_ENTRY_ARRAY
, a
, &o
);
283 m
= journal_file_entry_array_n_items(o
);
284 for (j
= 0; i
< n
&& j
< m
; i
++, j
++)
285 if (le64toh(o
->entry_array
.items
[j
]) == entry_p
)
288 a
= le64toh(o
->entry_array
.next_entry_array_offset
);;
294 static int verify_data(
296 Object
*o
, uint64_t p
,
297 int entry_fd
, uint64_t n_entries
,
298 int entry_array_fd
, uint64_t n_entry_arrays
) {
300 uint64_t i
, n
, a
, last
, q
;
305 assert(entry_fd
>= 0);
306 assert(entry_array_fd
>= 0);
308 n
= le64toh(o
->data
.n_entries
);
309 a
= le64toh(o
->data
.entry_array_offset
);
311 /* We already checked this earlier */
314 last
= q
= le64toh(o
->data
.entry_offset
);
315 r
= entry_points_to_data(f
, entry_fd
, n_entries
, q
, p
);
324 log_error("Array chain too short at %llu.", (unsigned long long) p
);
328 if (!contains_uint64(f
->mmap
, entry_array_fd
, n_entry_arrays
, a
)) {
329 log_error("Invalid array at %llu.", (unsigned long long) p
);
333 r
= journal_file_move_to_object(f
, OBJECT_ENTRY_ARRAY
, a
, &o
);
337 next
= le64toh(o
->entry_array
.next_entry_array_offset
);
338 if (next
!= 0 && next
<= a
) {
339 log_error("Array chain has cycle at %llu.", (unsigned long long) p
);
343 m
= journal_file_entry_array_n_items(o
);
344 for (j
= 0; i
< n
&& j
< m
; i
++, j
++) {
346 q
= le64toh(o
->entry_array
.items
[j
]);
348 log_error("Data object's entry array not sorted at %llu.", (unsigned long long) p
);
353 r
= entry_points_to_data(f
, entry_fd
, n_entries
, q
, p
);
357 /* Pointer might have moved, reposition */
358 r
= journal_file_move_to_object(f
, OBJECT_ENTRY_ARRAY
, a
, &o
);
369 static int verify_hash_table(
371 int data_fd
, uint64_t n_data
,
372 int entry_fd
, uint64_t n_entries
,
373 int entry_array_fd
, uint64_t n_entry_arrays
,
380 assert(data_fd
>= 0);
381 assert(entry_fd
>= 0);
382 assert(entry_array_fd
>= 0);
385 n
= le64toh(f
->header
->data_hash_table_size
) / sizeof(HashItem
);
386 for (i
= 0; i
< n
; i
++) {
387 uint64_t last
= 0, p
;
389 draw_progress(0xC000 + (0x3FFF * i
/ n
), last_usec
);
391 p
= le64toh(f
->data_hash_table
[i
].head_hash_offset
);
396 if (!contains_uint64(f
->mmap
, data_fd
, n_data
, p
)) {
397 log_error("Invalid data object at hash entry %llu of %llu.",
398 (unsigned long long) i
, (unsigned long long) n
);
402 r
= journal_file_move_to_object(f
, OBJECT_DATA
, p
, &o
);
406 next
= le64toh(o
->data
.next_hash_offset
);
407 if (next
!= 0 && next
<= p
) {
408 log_error("Hash chain has a cycle in hash entry %llu of %llu.",
409 (unsigned long long) i
, (unsigned long long) n
);
413 if (le64toh(o
->data
.hash
) % n
!= i
) {
414 log_error("Hash value mismatch in hash entry %llu of %llu.",
415 (unsigned long long) i
, (unsigned long long) n
);
419 r
= verify_data(f
, o
, p
, entry_fd
, n_entries
, entry_array_fd
, n_entry_arrays
);
427 if (last
!= le64toh(f
->data_hash_table
[i
].tail_hash_offset
)) {
428 log_error("Tail hash pointer mismatch in hash table.");
436 static int data_object_in_hash_table(JournalFile
*f
, uint64_t hash
, uint64_t p
) {
441 n
= le64toh(f
->header
->data_hash_table_size
) / sizeof(HashItem
);
444 q
= le64toh(f
->data_hash_table
[h
].head_hash_offset
);
451 r
= journal_file_move_to_object(f
, OBJECT_DATA
, q
, &o
);
455 q
= le64toh(o
->data
.next_hash_offset
);
461 static int verify_entry(
463 Object
*o
, uint64_t p
,
464 int data_fd
, uint64_t n_data
) {
471 assert(data_fd
>= 0);
473 n
= journal_file_entry_n_items(o
);
474 for (i
= 0; i
< n
; i
++) {
478 q
= le64toh(o
->entry
.items
[i
].object_offset
);
479 h
= le64toh(o
->entry
.items
[i
].hash
);
481 if (!contains_uint64(f
->mmap
, data_fd
, n_data
, q
)) {
482 log_error("Invalid data object at entry %llu.",
483 (unsigned long long) o
);
487 r
= journal_file_move_to_object(f
, OBJECT_DATA
, q
, &u
);
491 if (le64toh(u
->data
.hash
) != h
) {
492 log_error("Hash mismatch for data object at entry %llu.",
493 (unsigned long long) p
);
497 r
= data_object_in_hash_table(f
, h
, q
);
501 log_error("Data object missing from hash at entry %llu.",
502 (unsigned long long) p
);
510 static int verify_entry_array(
512 int data_fd
, uint64_t n_data
,
513 int entry_fd
, uint64_t n_entries
,
514 int entry_array_fd
, uint64_t n_entry_arrays
,
517 uint64_t i
= 0, a
, n
, last
= 0;
521 assert(data_fd
>= 0);
522 assert(entry_fd
>= 0);
523 assert(entry_array_fd
>= 0);
526 n
= le64toh(f
->header
->n_entries
);
527 a
= le64toh(f
->header
->entry_array_offset
);
532 draw_progress(0x8000 + (0x3FFF * i
/ n
), last_usec
);
535 log_error("Array chain too short at %llu of %llu.",
536 (unsigned long long) i
, (unsigned long long) n
);
540 if (!contains_uint64(f
->mmap
, entry_array_fd
, n_entry_arrays
, a
)) {
541 log_error("Invalid array at %llu of %llu.",
542 (unsigned long long) i
, (unsigned long long) n
);
546 r
= journal_file_move_to_object(f
, OBJECT_ENTRY_ARRAY
, a
, &o
);
550 next
= le64toh(o
->entry_array
.next_entry_array_offset
);
551 if (next
!= 0 && next
<= a
) {
552 log_error("Array chain has cycle at %llu of %llu.",
553 (unsigned long long) i
, (unsigned long long) n
);
557 m
= journal_file_entry_array_n_items(o
);
558 for (j
= 0; i
< n
&& j
< m
; i
++, j
++) {
561 p
= le64toh(o
->entry_array
.items
[j
]);
563 log_error("Entry array not sorted at %llu of %llu.",
564 (unsigned long long) i
, (unsigned long long) n
);
569 if (!contains_uint64(f
->mmap
, entry_fd
, n_entries
, p
)) {
570 log_error("Invalid array entry at %llu of %llu.",
571 (unsigned long long) i
, (unsigned long long) n
);
575 r
= journal_file_move_to_object(f
, OBJECT_ENTRY
, p
, &o
);
579 r
= verify_entry(f
, o
, p
, data_fd
, n_data
);
583 /* Pointer might have moved, reposition */
584 r
= journal_file_move_to_object(f
, OBJECT_ENTRY_ARRAY
, a
, &o
);
595 static int journal_file_parse_seed(JournalFile
*f
, const char *s
) {
600 unsigned long long start
, interval
;
602 seed_size
= FSPRG_RECOMMENDED_SEEDLEN
;
603 seed
= malloc(seed_size
);
608 for (c
= 0; c
< seed_size
; c
++) {
627 seed
[c
] = (uint8_t) (x
* 16 + y
);
636 r
= sscanf(k
, "%llx-%llx", &start
, &interval
);
642 f
->fsprg_seed
= seed
;
643 f
->fsprg_seed_size
= seed_size
;
644 f
->fsprg_start_usec
= start
;
645 f
->fsprg_interval_usec
= interval
;
650 int journal_file_verify(JournalFile
*f
, const char *seed
) {
654 uint64_t tag_seqnum
= 0, entry_seqnum
= 0, entry_monotonic
= 0, entry_realtime
= 0;
655 sd_id128_t entry_boot_id
;
656 bool entry_seqnum_set
= false, entry_monotonic_set
= false, entry_realtime_set
= false, found_main_entry_array
= false;
657 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;
658 usec_t last_usec
= 0;
659 int data_fd
= -1, entry_fd
= -1, entry_array_fd
= -1;
660 char data_path
[] = "/var/tmp/journal-data-XXXXXX",
661 entry_path
[] = "/var/tmp/journal-entry-XXXXXX",
662 entry_array_path
[] = "/var/tmp/journal-entry-array-XXXXXX";
667 r
= journal_file_parse_seed(f
, seed
);
669 log_error("Failed to parse seed.");
674 data_fd
= mkostemp(data_path
, O_CLOEXEC
);
676 log_error("Failed to create data file: %m");
682 entry_fd
= mkostemp(entry_path
, O_CLOEXEC
);
684 log_error("Failed to create entry file: %m");
690 entry_array_fd
= mkostemp(entry_array_path
, O_CLOEXEC
);
691 if (entry_array_fd
< 0) {
692 log_error("Failed to create entry array file: %m");
696 unlink(entry_array_path
);
698 /* First iteration: we go through all objects, verify the
699 * superficial structure, headers, hashes. */
701 r
= journal_file_hmac_put_header(f
);
703 log_error("Failed to calculate HMAC of header.");
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 r
= journal_file_hmac_put_object(f
, -1, p
);
740 log_error("Failed to calculate HMAC at %llu", (unsigned long long) p
);
744 if (o
->object
.type
== OBJECT_TAG
) {
746 if (!(le32toh(f
->header
->compatible_flags
) & HEADER_COMPATIBLE_AUTHENTICATED
)) {
747 log_error("Tag object without authentication at %llu", (unsigned long long) p
);
752 if (le64toh(o
->tag
.seqnum
) != tag_seqnum
) {
753 log_error("Tag sequence number out of synchronization at %llu", (unsigned long long) p
);
758 } else if (o
->object
.type
== OBJECT_ENTRY
) {
760 r
= write_uint64(entry_fd
, p
);
764 if (!entry_seqnum_set
&&
765 le64toh(o
->entry
.seqnum
) != le64toh(f
->header
->head_entry_seqnum
)) {
766 log_error("Head entry sequence number incorrect");
771 if (entry_seqnum_set
&&
772 entry_seqnum
>= le64toh(o
->entry
.seqnum
)) {
773 log_error("Entry sequence number out of synchronization at %llu", (unsigned long long) p
);
778 entry_seqnum
= le64toh(o
->entry
.seqnum
);
779 entry_seqnum_set
= true;
781 if (entry_monotonic_set
&&
782 sd_id128_equal(entry_boot_id
, o
->entry
.boot_id
) &&
783 entry_monotonic
> le64toh(o
->entry
.monotonic
)) {
784 log_error("Entry timestamp out of synchronization at %llu", (unsigned long long) p
);
789 entry_monotonic
= le64toh(o
->entry
.monotonic
);
790 entry_boot_id
= o
->entry
.boot_id
;
791 entry_monotonic_set
= true;
793 if (!entry_realtime_set
&&
794 le64toh(o
->entry
.realtime
) != le64toh(f
->header
->head_entry_realtime
)) {
795 log_error("Head entry realtime timestamp incorrect");
800 entry_realtime
= le64toh(o
->entry
.realtime
);
801 entry_realtime_set
= true;
804 } else if (o
->object
.type
== OBJECT_ENTRY_ARRAY
) {
806 r
= write_uint64(entry_array_fd
, p
);
810 if (p
== le64toh(f
->header
->entry_array_offset
)) {
811 if (found_main_entry_array
) {
812 log_error("More than one main entry array at %llu", (unsigned long long) p
);
817 found_main_entry_array
= true;
822 } else if (o
->object
.type
== OBJECT_DATA
) {
824 r
= write_uint64(data_fd
, p
);
830 } else if (o
->object
.type
== OBJECT_FIELD
)
832 else if (o
->object
.type
== OBJECT_DATA_HASH_TABLE
) {
833 n_data_hash_tables
++;
835 if (n_data_hash_tables
> 1) {
836 log_error("More than one data hash table at %llu", (unsigned long long) p
);
841 if (le64toh(f
->header
->data_hash_table_offset
) != p
+ offsetof(HashTableObject
, items
) ||
842 le64toh(f
->header
->data_hash_table_size
) != le64toh(o
->object
.size
) - offsetof(HashTableObject
, items
)) {
843 log_error("Header fields for data hash table invalid.");
847 } else if (o
->object
.type
== OBJECT_FIELD_HASH_TABLE
) {
848 n_field_hash_tables
++;
850 if (n_field_hash_tables
> 1) {
851 log_error("More than one field hash table at %llu", (unsigned long long) p
);
856 if (le64toh(f
->header
->field_hash_table_offset
) != p
+ offsetof(HashTableObject
, items
) ||
857 le64toh(f
->header
->field_hash_table_size
) != le64toh(o
->object
.size
) - offsetof(HashTableObject
, items
)) {
858 log_error("Header fields for field hash table invalid.");
862 } else if (o
->object
.type
>= _OBJECT_TYPE_MAX
)
865 if (p
== le64toh(f
->header
->tail_object_offset
))
868 p
= p
+ ALIGN64(le64toh(o
->object
.size
));
871 if (n_objects
!= le64toh(f
->header
->n_objects
)) {
872 log_error("Object number mismatch");
877 if (n_entries
!= le64toh(f
->header
->n_entries
)) {
878 log_error("Entry number mismatch");
883 if (JOURNAL_HEADER_CONTAINS(f
->header
, n_data
) &&
884 n_data
!= le64toh(f
->header
->n_data
)) {
885 log_error("Data number mismatch");
890 if (JOURNAL_HEADER_CONTAINS(f
->header
, n_fields
) &&
891 n_fields
!= le64toh(f
->header
->n_fields
)) {
892 log_error("Field number mismatch");
897 if (JOURNAL_HEADER_CONTAINS(f
->header
, n_tags
) &&
898 tag_seqnum
!= le64toh(f
->header
->n_tags
)) {
899 log_error("Tag number mismatch");
904 if (JOURNAL_HEADER_CONTAINS(f
->header
, n_entry_arrays
) &&
905 n_entry_arrays
!= le64toh(f
->header
->n_entry_arrays
)) {
906 log_error("Entry array number mismatch");
911 if (n_data_hash_tables
!= 1) {
912 log_error("Missing data hash table");
917 if (n_field_hash_tables
!= 1) {
918 log_error("Missing field hash table");
923 if (!found_main_entry_array
) {
924 log_error("Missing entry array");
929 if (entry_seqnum_set
&&
930 entry_seqnum
!= le64toh(f
->header
->tail_entry_seqnum
)) {
931 log_error("Invalid tail seqnum");
936 if (entry_monotonic_set
&&
937 (!sd_id128_equal(entry_boot_id
, f
->header
->boot_id
) ||
938 entry_monotonic
!= le64toh(f
->header
->tail_entry_monotonic
))) {
939 log_error("Invalid tail monotonic timestamp");
944 if (entry_realtime_set
&& entry_realtime
!= le64toh(f
->header
->tail_entry_realtime
)) {
945 log_error("Invalid tail realtime timestamp");
950 /* Second iteration: we follow all objects referenced from the
951 * two entry points: the object hash table and the entry
952 * array. We also check that everything referenced (directly
953 * or indirectly) in the data hash table also exists in the
954 * entry array, and vice versa. Note that we do not care for
955 * unreferenced objects. We only care that everything that is
956 * referenced is consistent. */
958 r
= verify_entry_array(f
,
961 entry_array_fd
, n_entry_arrays
,
966 r
= verify_hash_table(f
,
969 entry_array_fd
, n_entry_arrays
,
976 mmap_cache_close_fd(f
->mmap
, data_fd
);
977 mmap_cache_close_fd(f
->mmap
, entry_fd
);
978 mmap_cache_close_fd(f
->mmap
, entry_array_fd
);
980 close_nointr_nofail(data_fd
);
981 close_nointr_nofail(entry_fd
);
982 close_nointr_nofail(entry_array_fd
);
989 log_error("File corruption detected at %s:%llu (of %llu, %llu%%).",
991 (unsigned long long) p
,
992 (unsigned long long) f
->last_stat
.st_size
,
993 (unsigned long long) (100 * p
/ f
->last_stat
.st_size
));
996 mmap_cache_close_fd(f
->mmap
, data_fd
);
997 close_nointr_nofail(data_fd
);
1000 if (entry_fd
>= 0) {
1001 mmap_cache_close_fd(f
->mmap
, entry_fd
);
1002 close_nointr_nofail(entry_fd
);
1005 if (entry_array_fd
>= 0) {
1006 mmap_cache_close_fd(f
->mmap
, entry_array_fd
);
1007 close_nointr_nofail(entry_array_fd
);