1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
8 #include "alloc-util.h"
13 #include "journal-authenticate.h"
14 #include "journal-def.h"
15 #include "journal-file.h"
16 #include "journal-verify.h"
19 #include "terminal-util.h"
20 #include "tmpfile-util.h"
22 static void draw_progress(uint64_t p
, usec_t
*last_usec
) {
29 z
= now(CLOCK_MONOTONIC
);
32 if (x
!= 0 && x
+ 40 * USEC_PER_MSEC
> z
)
37 n
= (3 * columns()) / 4;
38 j
= (n
* (unsigned) p
) / 65535ULL;
43 fputs("\x1B[?25l", stdout
);
45 fputs(ansi_highlight_green(), stdout
);
47 for (i
= 0; i
< j
; i
++)
48 fputs("\xe2\x96\x88", stdout
);
50 fputs(ansi_normal(), stdout
);
52 for (i
= 0; i
< k
; i
++)
53 fputs("\xe2\x96\x91", stdout
);
55 printf(" %3"PRIu64
"%%", 100U * p
/ 65535U);
59 fputs("\x1B[?25h", stdout
);
64 static uint64_t scale_progress(uint64_t scale
, uint64_t p
, uint64_t m
) {
65 /* Calculates scale * p / m, but handles m == 0 safely, and saturates.
66 * Currently all callers use m >= 1, but we keep the check to be defensive.
75 static void flush_progress(void) {
81 n
= (3 * columns()) / 4;
85 for (i
= 0; i
< n
+ 5; i
++)
92 #define debug(_offset, _fmt, ...) do { \
94 log_debug(OFSfmt": " _fmt, _offset, ##__VA_ARGS__); \
97 #define warning(_offset, _fmt, ...) do { \
99 log_warning(OFSfmt": " _fmt, _offset, ##__VA_ARGS__); \
102 #define error(_offset, _fmt, ...) do { \
104 log_error(OFSfmt": " _fmt, (uint64_t)_offset, ##__VA_ARGS__); \
107 #define error_errno(_offset, error, _fmt, ...) do { \
109 log_error_errno(error, OFSfmt": " _fmt, (uint64_t)_offset, ##__VA_ARGS__); \
112 static int hash_payload(JournalFile
*f
, Object
*o
, uint64_t offset
, const uint8_t *src
, uint64_t size
, uint64_t *res_hash
) {
120 c
= COMPRESSION_FROM_OBJECT(o
);
123 if (c
!= COMPRESSION_NONE
) {
124 _cleanup_free_
void *b
= NULL
;
127 r
= decompress_blob(c
, src
, size
, &b
, &b_size
, 0);
129 error_errno(offset
, r
, "%s decompression failed: %m",
130 compression_to_string(c
));
134 *res_hash
= journal_file_hash_data(f
, b
, b_size
);
136 *res_hash
= journal_file_hash_data(f
, src
, size
);
141 static int journal_file_object_verify(JournalFile
*f
, uint64_t offset
, Object
*o
) {
146 /* This does various superficial tests about the length an
147 * possible field values. It does not follow any references to
150 if ((o
->object
.flags
& _OBJECT_COMPRESSED_MASK
) != 0 &&
151 o
->object
.type
!= OBJECT_DATA
) {
153 "Found compressed object of type %s that isn't of type data, which is not allowed.",
154 journal_object_type_to_string(o
->object
.type
));
158 switch (o
->object
.type
) {
164 if (le64toh(o
->data
.entry_offset
) == 0)
165 warning(offset
, "Unused data (entry_offset==0)");
167 if ((le64toh(o
->data
.entry_offset
) == 0) ^ (le64toh(o
->data
.n_entries
) == 0)) {
168 error(offset
, "Bad n_entries: %"PRIu64
, le64toh(o
->data
.n_entries
));
172 if (le64toh(o
->object
.size
) - journal_file_data_payload_offset(f
) <= 0) {
173 error(offset
, "Bad object size (<= %zu): %"PRIu64
,
174 journal_file_data_payload_offset(f
),
175 le64toh(o
->object
.size
));
179 h1
= le64toh(o
->data
.hash
);
180 r
= hash_payload(f
, o
, offset
, journal_file_data_payload_field(f
, o
),
181 le64toh(o
->object
.size
) - journal_file_data_payload_offset(f
),
187 error(offset
, "Invalid hash (%08" PRIx64
" vs. %08" PRIx64
")", h1
, h2
);
191 if (!VALID64(le64toh(o
->data
.next_hash_offset
)) ||
192 !VALID64(le64toh(o
->data
.next_field_offset
)) ||
193 !VALID64(le64toh(o
->data
.entry_offset
)) ||
194 !VALID64(le64toh(o
->data
.entry_array_offset
))) {
195 error(offset
, "Invalid offset (next_hash_offset="OFSfmt
", next_field_offset="OFSfmt
", entry_offset="OFSfmt
", entry_array_offset="OFSfmt
,
196 le64toh(o
->data
.next_hash_offset
),
197 le64toh(o
->data
.next_field_offset
),
198 le64toh(o
->data
.entry_offset
),
199 le64toh(o
->data
.entry_array_offset
));
210 if (le64toh(o
->object
.size
) - offsetof(Object
, field
.payload
) <= 0) {
212 "Bad field size (<= %zu): %"PRIu64
,
213 offsetof(Object
, field
.payload
),
214 le64toh(o
->object
.size
));
218 h1
= le64toh(o
->field
.hash
);
219 r
= hash_payload(f
, o
, offset
, o
->field
.payload
,
220 le64toh(o
->object
.size
) - offsetof(Object
, field
.payload
),
226 error(offset
, "Invalid hash (%08" PRIx64
" vs. %08" PRIx64
")", h1
, h2
);
230 if (!VALID64(le64toh(o
->field
.next_hash_offset
)) ||
231 !VALID64(le64toh(o
->field
.head_data_offset
))) {
233 "Invalid offset (next_hash_offset="OFSfmt
", head_data_offset="OFSfmt
,
234 le64toh(o
->field
.next_hash_offset
),
235 le64toh(o
->field
.head_data_offset
));
242 if ((le64toh(o
->object
.size
) - offsetof(Object
, entry
.items
)) % journal_file_entry_item_size(f
) != 0) {
244 "Bad entry size (<= %zu): %"PRIu64
,
245 offsetof(Object
, entry
.items
),
246 le64toh(o
->object
.size
));
250 if ((le64toh(o
->object
.size
) - offsetof(Object
, entry
.items
)) / journal_file_entry_item_size(f
) <= 0) {
252 "Invalid number items in entry: %"PRIu64
,
253 (le64toh(o
->object
.size
) - offsetof(Object
, entry
.items
)) / journal_file_entry_item_size(f
));
257 if (le64toh(o
->entry
.seqnum
) <= 0) {
259 "Invalid entry seqnum: %"PRIx64
,
260 le64toh(o
->entry
.seqnum
));
264 if (!VALID_REALTIME(le64toh(o
->entry
.realtime
))) {
266 "Invalid entry realtime timestamp: %"PRIu64
,
267 le64toh(o
->entry
.realtime
));
271 if (!VALID_MONOTONIC(le64toh(o
->entry
.monotonic
))) {
273 "Invalid entry monotonic timestamp: %"PRIu64
,
274 le64toh(o
->entry
.monotonic
));
278 for (uint64_t i
= 0; i
< journal_file_entry_n_items(f
, o
); i
++) {
279 if (journal_file_entry_item_object_offset(f
, o
, i
) == 0 ||
280 !VALID64(journal_file_entry_item_object_offset(f
, o
, i
))) {
282 "Invalid entry item (%"PRIu64
"/%"PRIu64
") offset: "OFSfmt
,
283 i
, journal_file_entry_n_items(f
, o
),
284 journal_file_entry_item_object_offset(f
, o
, i
));
291 case OBJECT_DATA_HASH_TABLE
:
292 case OBJECT_FIELD_HASH_TABLE
:
293 if ((le64toh(o
->object
.size
) - offsetof(Object
, hash_table
.items
)) % sizeof(HashItem
) != 0 ||
294 (le64toh(o
->object
.size
) - offsetof(Object
, hash_table
.items
)) / sizeof(HashItem
) <= 0) {
296 "Invalid %s size: %"PRIu64
,
297 journal_object_type_to_string(o
->object
.type
),
298 le64toh(o
->object
.size
));
302 for (uint64_t i
= 0; i
< journal_file_hash_table_n_items(o
); i
++) {
303 if (o
->hash_table
.items
[i
].head_hash_offset
!= 0 &&
304 !VALID64(le64toh(o
->hash_table
.items
[i
].head_hash_offset
))) {
306 "Invalid %s hash table item (%"PRIu64
"/%"PRIu64
") head_hash_offset: "OFSfmt
,
307 journal_object_type_to_string(o
->object
.type
),
308 i
, journal_file_hash_table_n_items(o
),
309 le64toh(o
->hash_table
.items
[i
].head_hash_offset
));
312 if (o
->hash_table
.items
[i
].tail_hash_offset
!= 0 &&
313 !VALID64(le64toh(o
->hash_table
.items
[i
].tail_hash_offset
))) {
315 "Invalid %s hash table item (%"PRIu64
"/%"PRIu64
") tail_hash_offset: "OFSfmt
,
316 journal_object_type_to_string(o
->object
.type
),
317 i
, journal_file_hash_table_n_items(o
),
318 le64toh(o
->hash_table
.items
[i
].tail_hash_offset
));
322 if ((o
->hash_table
.items
[i
].head_hash_offset
!= 0) !=
323 (o
->hash_table
.items
[i
].tail_hash_offset
!= 0)) {
325 "Invalid %s hash table item (%"PRIu64
"/%"PRIu64
"): head_hash_offset="OFSfmt
" tail_hash_offset="OFSfmt
,
326 journal_object_type_to_string(o
->object
.type
),
327 i
, journal_file_hash_table_n_items(o
),
328 le64toh(o
->hash_table
.items
[i
].head_hash_offset
),
329 le64toh(o
->hash_table
.items
[i
].tail_hash_offset
));
336 case OBJECT_ENTRY_ARRAY
:
337 if ((le64toh(o
->object
.size
) - offsetof(Object
, entry_array
.items
)) % journal_file_entry_array_item_size(f
) != 0 ||
338 (le64toh(o
->object
.size
) - offsetof(Object
, entry_array
.items
)) / journal_file_entry_array_item_size(f
) <= 0) {
340 "Invalid object entry array size: %"PRIu64
,
341 le64toh(o
->object
.size
));
345 if (!VALID64(le64toh(o
->entry_array
.next_entry_array_offset
))) {
347 "Invalid object entry array next_entry_array_offset: "OFSfmt
,
348 le64toh(o
->entry_array
.next_entry_array_offset
));
352 for (uint64_t i
= 0; i
< journal_file_entry_array_n_items(f
, o
); i
++) {
353 uint64_t q
= journal_file_entry_array_item(f
, o
, i
);
354 if (q
!= 0 && !VALID64(q
)) {
356 "Invalid object entry array item (%"PRIu64
"/%"PRIu64
"): "OFSfmt
,
357 i
, journal_file_entry_array_n_items(f
, o
), q
);
365 if (le64toh(o
->object
.size
) != sizeof(TagObject
)) {
367 "Invalid object tag size: %"PRIu64
,
368 le64toh(o
->object
.size
));
372 if (!VALID_EPOCH(le64toh(o
->tag
.epoch
))) {
374 "Invalid object tag epoch: %"PRIu64
,
375 le64toh(o
->tag
.epoch
));
385 static int write_uint64(FILE *fp
, uint64_t p
) {
386 if (fwrite(&p
, sizeof(p
), 1, fp
) != 1)
392 static int contains_uint64(MMapFileDescriptor
*f
, uint64_t n
, uint64_t p
) {
406 r
= mmap_cache_fd_get(f
, 0, false, c
* sizeof(uint64_t), sizeof(uint64_t), NULL
, (void **) &z
);
425 static int verify_data(
427 Object
*o
, uint64_t p
,
428 MMapFileDescriptor
*cache_entry_fd
, uint64_t n_entries
,
429 MMapFileDescriptor
*cache_entry_array_fd
, uint64_t n_entry_arrays
) {
431 uint64_t i
, n
, a
, last
, q
;
436 assert(cache_entry_fd
);
437 assert(cache_entry_array_fd
);
439 n
= le64toh(o
->data
.n_entries
);
440 a
= le64toh(o
->data
.entry_array_offset
);
442 /* Entry array means at least two objects */
444 error(p
, "Entry array present (entry_array_offset="OFSfmt
", but n_entries=%"PRIu64
")", a
, n
);
451 /* We already checked that earlier */
452 assert(o
->data
.entry_offset
);
454 last
= q
= le64toh(o
->data
.entry_offset
);
455 if (!contains_uint64(cache_entry_fd
, n_entries
, q
)) {
456 error(p
, "Data object references invalid entry at "OFSfmt
, q
);
460 r
= journal_file_move_to_entry_by_offset(f
, q
, DIRECTION_DOWN
, NULL
, NULL
);
464 error(q
, "Entry object doesn't exist in the main entry array");
473 error(p
, "Array chain too short");
477 if (!contains_uint64(cache_entry_array_fd
, n_entry_arrays
, a
)) {
478 error(p
, "Invalid array offset "OFSfmt
, a
);
482 r
= journal_file_move_to_object(f
, OBJECT_ENTRY_ARRAY
, a
, &o
);
486 next
= le64toh(o
->entry_array
.next_entry_array_offset
);
487 if (next
!= 0 && next
<= a
) {
488 error(p
, "Array chain has cycle (jumps back from "OFSfmt
" to "OFSfmt
")", a
, next
);
492 m
= journal_file_entry_array_n_items(f
, o
);
493 for (j
= 0; i
< n
&& j
< m
; i
++, j
++) {
495 q
= journal_file_entry_array_item(f
, o
, j
);
497 error(p
, "Data object's entry array not sorted (%"PRIu64
" <= %"PRIu64
")", q
, last
);
502 if (!contains_uint64(cache_entry_fd
, n_entries
, q
)) {
503 error(p
, "Data object references invalid entry at "OFSfmt
, q
);
507 r
= journal_file_move_to_entry_by_offset(f
, q
, DIRECTION_DOWN
, NULL
, NULL
);
511 error(q
, "Entry object doesn't exist in the main entry array");
515 /* Pointer might have moved, reposition */
516 r
= journal_file_move_to_object(f
, OBJECT_ENTRY_ARRAY
, a
, &o
);
527 static int verify_data_hash_table(
529 MMapFileDescriptor
*cache_data_fd
, uint64_t n_data
,
530 MMapFileDescriptor
*cache_entry_fd
, uint64_t n_entries
,
531 MMapFileDescriptor
*cache_entry_array_fd
, uint64_t n_entry_arrays
,
533 bool show_progress
) {
539 assert(cache_data_fd
);
540 assert(cache_entry_fd
);
541 assert(cache_entry_array_fd
);
544 n
= le64toh(f
->header
->data_hash_table_size
) / sizeof(HashItem
);
548 r
= journal_file_map_data_hash_table(f
);
550 return log_error_errno(r
, "Failed to map data hash table: %m");
552 for (i
= 0; i
< n
; i
++) {
553 uint64_t last
= 0, p
;
556 draw_progress(0xC000 + scale_progress(0x3FFF, i
, n
), last_usec
);
558 p
= le64toh(f
->data_hash_table
[i
].head_hash_offset
);
563 if (!contains_uint64(cache_data_fd
, n_data
, p
)) {
564 error(p
, "Invalid data object at hash entry %"PRIu64
" of %"PRIu64
, i
, n
);
568 r
= journal_file_move_to_object(f
, OBJECT_DATA
, p
, &o
);
572 next
= le64toh(o
->data
.next_hash_offset
);
573 if (next
!= 0 && next
<= p
) {
574 error(p
, "Hash chain has a cycle in hash entry %"PRIu64
" of %"PRIu64
, i
, n
);
578 if (le64toh(o
->data
.hash
) % n
!= i
) {
579 error(p
, "Hash value mismatch in hash entry %"PRIu64
" of %"PRIu64
, i
, n
);
583 r
= verify_data(f
, o
, p
, cache_entry_fd
, n_entries
, cache_entry_array_fd
, n_entry_arrays
);
591 if (last
!= le64toh(f
->data_hash_table
[i
].tail_hash_offset
)) {
593 "Tail hash pointer mismatch in hash table (%"PRIu64
" != %"PRIu64
")",
595 le64toh(f
->data_hash_table
[i
].tail_hash_offset
));
603 static int data_object_in_hash_table(JournalFile
*f
, uint64_t hash
, uint64_t p
) {
608 n
= le64toh(f
->header
->data_hash_table_size
) / sizeof(HashItem
);
612 r
= journal_file_map_data_hash_table(f
);
614 return log_error_errno(r
, "Failed to map data hash table: %m");
618 q
= le64toh(f
->data_hash_table
[h
].head_hash_offset
);
625 r
= journal_file_move_to_object(f
, OBJECT_DATA
, q
, &o
);
629 q
= le64toh(o
->data
.next_hash_offset
);
635 static int verify_entry(
637 Object
*o
, uint64_t p
,
638 MMapFileDescriptor
*cache_data_fd
, uint64_t n_data
,
646 assert(cache_data_fd
);
648 n
= journal_file_entry_n_items(f
, o
);
649 for (i
= 0; i
< n
; i
++) {
653 q
= journal_file_entry_item_object_offset(f
, o
, i
);
655 if (!contains_uint64(cache_data_fd
, n_data
, q
)) {
656 error(p
, "Invalid data object of entry");
660 r
= journal_file_move_to_object(f
, OBJECT_DATA
, q
, &u
);
664 r
= data_object_in_hash_table(f
, le64toh(u
->data
.hash
), q
);
668 error(p
, "Data object missing from hash table");
672 /* Pointer might have moved, reposition */
673 r
= journal_file_move_to_object(f
, OBJECT_DATA
, q
, &u
);
677 r
= journal_file_move_to_entry_by_offset_for_data(f
, u
, p
, DIRECTION_DOWN
, NULL
, NULL
);
681 /* The last entry object has a very high chance of not being referenced as journal files
682 * almost always run out of space during linking of entry items when trying to add a new
683 * entry array so let's not error in that scenario. */
684 if (r
== 0 && !last
) {
685 error(p
, "Entry object not referenced by linked data object at "OFSfmt
, q
);
693 static int verify_entry_array(
695 MMapFileDescriptor
*cache_data_fd
, uint64_t n_data
,
696 MMapFileDescriptor
*cache_entry_fd
, uint64_t n_entries
,
697 MMapFileDescriptor
*cache_entry_array_fd
, uint64_t n_entry_arrays
,
699 bool show_progress
) {
701 uint64_t i
= 0, a
, n
, last
= 0;
705 assert(cache_data_fd
);
706 assert(cache_entry_fd
);
707 assert(cache_entry_array_fd
);
710 n
= le64toh(f
->header
->n_entries
);
711 a
= le64toh(f
->header
->entry_array_offset
);
717 draw_progress(0x8000 + scale_progress(0x3FFF, i
, n
), last_usec
);
720 error(a
, "Array chain too short at %"PRIu64
" of %"PRIu64
, i
, n
);
724 if (!contains_uint64(cache_entry_array_fd
, n_entry_arrays
, a
)) {
725 error(a
, "Invalid array %"PRIu64
" of %"PRIu64
, i
, n
);
729 r
= journal_file_move_to_object(f
, OBJECT_ENTRY_ARRAY
, a
, &o
);
733 next
= le64toh(o
->entry_array
.next_entry_array_offset
);
734 if (next
!= 0 && next
<= a
) {
735 error(a
, "Array chain has cycle at %"PRIu64
" of %"PRIu64
" (jumps back from to "OFSfmt
")", i
, n
, next
);
739 m
= journal_file_entry_array_n_items(f
, o
);
740 for (j
= 0; i
< n
&& j
< m
; i
++, j
++) {
743 p
= journal_file_entry_array_item(f
, o
, j
);
745 error(a
, "Entry array not sorted at %"PRIu64
" of %"PRIu64
, i
, n
);
750 if (!contains_uint64(cache_entry_fd
, n_entries
, p
)) {
751 error(a
, "Invalid array entry at %"PRIu64
" of %"PRIu64
, i
, n
);
755 r
= journal_file_move_to_object(f
, OBJECT_ENTRY
, p
, &o
);
759 r
= verify_entry(f
, o
, p
, cache_data_fd
, n_data
, /*last=*/ i
+ 1 == n
);
763 /* Pointer might have moved, reposition */
764 r
= journal_file_move_to_object(f
, OBJECT_ENTRY_ARRAY
, a
, &o
);
775 static int verify_hash_table(
776 Object
*o
, uint64_t p
, uint64_t *n_hash_tables
, uint64_t header_offset
, uint64_t header_size
) {
779 assert(n_hash_tables
);
781 if (*n_hash_tables
> 1) {
783 "More than one %s: %" PRIu64
,
784 journal_object_type_to_string(o
->object
.type
),
789 if (header_offset
!= p
+ offsetof(Object
, hash_table
.items
)) {
791 "Header offset for %s invalid (%" PRIu64
" != %" PRIu64
")",
792 journal_object_type_to_string(o
->object
.type
),
794 p
+ offsetof(Object
, hash_table
.items
));
798 if (header_size
!= le64toh(o
->object
.size
) - offsetof(Object
, hash_table
.items
)) {
800 "Header size for %s invalid (%" PRIu64
" != %" PRIu64
")",
801 journal_object_type_to_string(o
->object
.type
),
803 le64toh(o
->object
.size
) - offsetof(Object
, hash_table
.items
));
812 int journal_file_verify(
815 usec_t
*first_contained
, usec_t
*last_validated
, usec_t
*last_contained
,
816 bool show_progress
) {
819 uint64_t p
= 0, last_epoch
= 0, last_tag_realtime
= 0;
821 uint64_t entry_seqnum
= 0, entry_monotonic
= 0, entry_realtime
= 0;
822 usec_t min_entry_realtime
= USEC_INFINITY
, max_entry_realtime
= 0;
823 sd_id128_t entry_boot_id
= {}; /* Unnecessary initialization to appease gcc */
824 bool entry_seqnum_set
= false, entry_monotonic_set
= false, entry_realtime_set
= false, found_main_entry_array
= false;
825 uint64_t 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;
826 usec_t last_usec
= 0;
827 _cleanup_close_
int data_fd
= -EBADF
, entry_fd
= -EBADF
, entry_array_fd
= -EBADF
;
828 _cleanup_fclose_
FILE *data_fp
= NULL
, *entry_fp
= NULL
, *entry_array_fp
= NULL
;
829 MMapFileDescriptor
*cache_data_fd
= NULL
, *cache_entry_fd
= NULL
, *cache_entry_array_fd
= NULL
;
831 bool found_last
= false;
832 const char *tmp_dir
= NULL
;
836 uint64_t last_tag
= 0;
842 r
= journal_file_parse_verification_key(f
, key
);
844 log_error("Failed to parse seed.");
850 } else if (JOURNAL_HEADER_SEALED(f
->header
))
853 r
= var_tmp_dir(&tmp_dir
);
855 log_error_errno(r
, "Failed to determine temporary directory: %m");
859 data_fd
= open_tmpfile_unlinkable(tmp_dir
, O_RDWR
| O_CLOEXEC
);
861 r
= log_error_errno(data_fd
, "Failed to create data file: %m");
865 entry_fd
= open_tmpfile_unlinkable(tmp_dir
, O_RDWR
| O_CLOEXEC
);
867 r
= log_error_errno(entry_fd
, "Failed to create entry file: %m");
871 entry_array_fd
= open_tmpfile_unlinkable(tmp_dir
, O_RDWR
| O_CLOEXEC
);
872 if (entry_array_fd
< 0) {
873 r
= log_error_errno(entry_array_fd
,
874 "Failed to create entry array file: %m");
878 m
= mmap_cache_fd_cache(f
->cache_fd
);
879 r
= mmap_cache_add_fd(m
, data_fd
, PROT_READ
|PROT_WRITE
, &cache_data_fd
);
881 log_error_errno(r
, "Failed to cache data file: %m");
885 r
= mmap_cache_add_fd(m
, entry_fd
, PROT_READ
|PROT_WRITE
, &cache_entry_fd
);
887 log_error_errno(r
, "Failed to cache entry file: %m");
891 r
= mmap_cache_add_fd(m
, entry_array_fd
, PROT_READ
|PROT_WRITE
, &cache_entry_array_fd
);
893 log_error_errno(r
, "Failed to cache entry array file: %m");
897 r
= take_fdopen_unlocked(&data_fd
, "w+", &data_fp
);
899 log_error_errno(r
, "Failed to open data file stream: %m");
903 r
= take_fdopen_unlocked(&entry_fd
, "w+", &entry_fp
);
905 log_error_errno(r
, "Failed to open entry file stream: %m");
909 r
= take_fdopen_unlocked(&entry_array_fd
, "w+", &entry_array_fp
);
911 log_error_errno(r
, "Failed to open entry array file stream: %m");
915 if (le32toh(f
->header
->compatible_flags
) & ~HEADER_COMPATIBLE_SUPPORTED
) {
916 log_error("Cannot verify file with unknown extensions.");
921 for (i
= 0; i
< sizeof(f
->header
->reserved
); i
++)
922 if (f
->header
->reserved
[i
] != 0) {
923 error(offsetof(Header
, reserved
[i
]), "Reserved field is non-zero");
928 if (JOURNAL_HEADER_SEALED(f
->header
) && !JOURNAL_HEADER_SEALED_CONTINUOUS(f
->header
))
930 "This log file was sealed with an old journald version where the sequence of seals might not be continuous. We cannot guarantee completeness.");
932 /* First iteration: we go through all objects, verify the
933 * superficial structure, headers, hashes. */
935 p
= le64toh(f
->header
->header_size
);
937 /* Early exit if there are no objects in the file, at all */
938 if (le64toh(f
->header
->tail_object_offset
) == 0)
942 draw_progress(scale_progress(0x7FFF, p
, le64toh(f
->header
->tail_object_offset
)), &last_usec
);
944 r
= journal_file_move_to_object(f
, OBJECT_UNUSED
, p
, &o
);
946 error_errno(p
, r
, "Invalid object: %m");
950 if (p
> le64toh(f
->header
->tail_object_offset
)) {
951 error(offsetof(Header
, tail_object_offset
),
952 "Invalid tail object pointer (%"PRIu64
" > %"PRIu64
")",
954 le64toh(f
->header
->tail_object_offset
));
961 r
= journal_file_object_verify(f
, p
, o
);
963 error_errno(p
, r
, "Invalid object contents: %m");
967 if (!!(o
->object
.flags
& OBJECT_COMPRESSED_XZ
) +
968 !!(o
->object
.flags
& OBJECT_COMPRESSED_LZ4
) +
969 !!(o
->object
.flags
& OBJECT_COMPRESSED_ZSTD
) > 1) {
970 error(p
, "Object has multiple compression flags set (flags: 0x%x)", o
->object
.flags
);
975 if ((o
->object
.flags
& OBJECT_COMPRESSED_XZ
) && !JOURNAL_HEADER_COMPRESSED_XZ(f
->header
)) {
976 error(p
, "XZ compressed object in file without XZ compression");
981 if ((o
->object
.flags
& OBJECT_COMPRESSED_LZ4
) && !JOURNAL_HEADER_COMPRESSED_LZ4(f
->header
)) {
982 error(p
, "LZ4 compressed object in file without LZ4 compression");
987 if ((o
->object
.flags
& OBJECT_COMPRESSED_ZSTD
) && !JOURNAL_HEADER_COMPRESSED_ZSTD(f
->header
)) {
988 error(p
, "ZSTD compressed object in file without ZSTD compression");
993 switch (o
->object
.type
) {
996 r
= write_uint64(data_fp
, p
);
1008 if (JOURNAL_HEADER_SEALED(f
->header
) && n_tags
<= 0) {
1009 error(p
, "First entry before first tag");
1014 r
= write_uint64(entry_fp
, p
);
1018 if (le64toh(o
->entry
.realtime
) < last_tag_realtime
) {
1020 "Older entry after newer tag (%"PRIu64
" < %"PRIu64
")",
1021 le64toh(o
->entry
.realtime
),
1027 if (!entry_seqnum_set
&&
1028 le64toh(o
->entry
.seqnum
) != le64toh(f
->header
->head_entry_seqnum
)) {
1030 "Head entry sequence number incorrect (%"PRIu64
" != %"PRIu64
")",
1031 le64toh(o
->entry
.seqnum
),
1032 le64toh(f
->header
->head_entry_seqnum
));
1037 if (entry_seqnum_set
&&
1038 entry_seqnum
>= le64toh(o
->entry
.seqnum
)) {
1040 "Entry sequence number out of synchronization (%"PRIu64
" >= %"PRIu64
")",
1042 le64toh(o
->entry
.seqnum
));
1047 entry_seqnum
= le64toh(o
->entry
.seqnum
);
1048 entry_seqnum_set
= true;
1050 if (entry_monotonic_set
&&
1051 sd_id128_equal(entry_boot_id
, o
->entry
.boot_id
) &&
1052 entry_monotonic
> le64toh(o
->entry
.monotonic
)) {
1054 "Entry timestamp out of synchronization (%"PRIu64
" > %"PRIu64
")",
1056 le64toh(o
->entry
.monotonic
));
1061 entry_monotonic
= le64toh(o
->entry
.monotonic
);
1062 entry_boot_id
= o
->entry
.boot_id
;
1063 entry_monotonic_set
= true;
1065 if (!entry_realtime_set
&&
1066 le64toh(o
->entry
.realtime
) != le64toh(f
->header
->head_entry_realtime
)) {
1068 "Head entry realtime timestamp incorrect (%"PRIu64
" != %"PRIu64
")",
1069 le64toh(o
->entry
.realtime
),
1070 le64toh(f
->header
->head_entry_realtime
));
1075 entry_realtime
= le64toh(o
->entry
.realtime
);
1076 entry_realtime_set
= true;
1078 max_entry_realtime
= MAX(max_entry_realtime
, le64toh(o
->entry
.realtime
));
1079 min_entry_realtime
= MIN(min_entry_realtime
, le64toh(o
->entry
.realtime
));
1084 case OBJECT_DATA_HASH_TABLE
:
1085 r
= verify_hash_table(o
, p
, &n_data_hash_tables
,
1086 le64toh(f
->header
->data_hash_table_offset
),
1087 le64toh(f
->header
->data_hash_table_size
));
1092 case OBJECT_FIELD_HASH_TABLE
:
1093 r
= verify_hash_table(o
, p
, &n_field_hash_tables
,
1094 le64toh(f
->header
->field_hash_table_offset
),
1095 le64toh(f
->header
->field_hash_table_size
));
1101 case OBJECT_ENTRY_ARRAY
:
1102 r
= write_uint64(entry_array_fp
, p
);
1106 if (p
== le64toh(f
->header
->entry_array_offset
)) {
1107 if (found_main_entry_array
) {
1108 error(p
, "More than one main entry array");
1113 found_main_entry_array
= true;
1120 if (!JOURNAL_HEADER_SEALED(f
->header
)) {
1121 error(p
, "Tag object in file without sealing");
1126 if (le64toh(o
->tag
.seqnum
) != n_tags
+ 1) {
1128 "Tag sequence number out of synchronization (%"PRIu64
" != %"PRIu64
")",
1129 le64toh(o
->tag
.seqnum
),
1135 if (JOURNAL_HEADER_SEALED_CONTINUOUS(f
->header
)) {
1136 if (!(n_tags
== 0 || (n_tags
== 1 && le64toh(o
->tag
.epoch
) == last_epoch
)
1137 || le64toh(o
->tag
.epoch
) == last_epoch
+ 1)) {
1139 "Epoch sequence not continuous (%"PRIu64
" vs %"PRIu64
")",
1140 le64toh(o
->tag
.epoch
),
1146 if (le64toh(o
->tag
.epoch
) < last_epoch
) {
1148 "Epoch sequence out of synchronization (%"PRIu64
" < %"PRIu64
")",
1149 le64toh(o
->tag
.epoch
),
1157 if (JOURNAL_HEADER_SEALED(f
->header
)) {
1158 uint64_t q
, rt
, rt_end
;
1160 debug(p
, "Checking tag %"PRIu64
"...", le64toh(o
->tag
.seqnum
));
1162 rt
= f
->fss_start_usec
+ le64toh(o
->tag
.epoch
) * f
->fss_interval_usec
;
1163 rt_end
= usec_add(rt
, f
->fss_interval_usec
);
1164 if (entry_realtime_set
&& entry_realtime
>= rt_end
) {
1166 "tag/entry realtime timestamp out of synchronization (%"PRIu64
" >= %"PRIu64
")",
1168 rt
+ f
->fss_interval_usec
);
1172 if (max_entry_realtime
>= rt_end
) {
1174 "Entry realtime (%"PRIu64
", %s) is too late with respect to tag (%"PRIu64
", %s)",
1175 max_entry_realtime
, FORMAT_TIMESTAMP(max_entry_realtime
),
1176 rt_end
, FORMAT_TIMESTAMP(rt_end
));
1180 if (min_entry_realtime
< rt
) {
1182 "Entry realtime (%"PRIu64
", %s) is too early with respect to tag (%"PRIu64
", %s)",
1183 min_entry_realtime
, FORMAT_TIMESTAMP(min_entry_realtime
),
1184 rt
, FORMAT_TIMESTAMP(rt
));
1188 min_entry_realtime
= USEC_INFINITY
;
1190 /* OK, now we know the epoch. So let's now set
1191 * it, and calculate the HMAC for everything
1192 * since the last tag. */
1193 r
= journal_file_fsprg_seek(f
, le64toh(o
->tag
.epoch
));
1197 r
= journal_file_hmac_start(f
);
1201 if (last_tag
== 0) {
1202 r
= journal_file_hmac_put_header(f
);
1206 q
= le64toh(f
->header
->header_size
);
1211 r
= journal_file_move_to_object(f
, OBJECT_UNUSED
, q
, &o
);
1215 r
= journal_file_hmac_put_object(f
, OBJECT_UNUSED
, o
, q
);
1219 q
= q
+ ALIGN64(le64toh(o
->object
.size
));
1222 /* Position might have changed, let's reposition things */
1223 r
= journal_file_move_to_object(f
, OBJECT_UNUSED
, p
, &o
);
1227 if (memcmp(o
->tag
.tag
, gcry_md_read(f
->hmac
, 0), TAG_LENGTH
) != 0) {
1228 error(p
, "Tag failed verification");
1233 f
->hmac_running
= false;
1234 last_tag_realtime
= rt
;
1237 last_tag
= p
+ ALIGN64(le64toh(o
->object
.size
));
1240 last_epoch
= le64toh(o
->tag
.epoch
);
1246 if (p
== le64toh(f
->header
->tail_object_offset
)) {
1251 p
= p
+ ALIGN64(le64toh(o
->object
.size
));
1254 if (!found_last
&& le64toh(f
->header
->tail_object_offset
) != 0) {
1255 error(le64toh(f
->header
->tail_object_offset
),
1256 "Tail object pointer dead (%"PRIu64
" != 0)",
1257 le64toh(f
->header
->tail_object_offset
));
1262 if (n_objects
!= le64toh(f
->header
->n_objects
)) {
1263 error(offsetof(Header
, n_objects
),
1264 "Object number mismatch (%"PRIu64
" != %"PRIu64
")",
1266 le64toh(f
->header
->n_objects
));
1271 if (n_entries
!= le64toh(f
->header
->n_entries
)) {
1272 error(offsetof(Header
, n_entries
),
1273 "Entry number mismatch (%"PRIu64
" != %"PRIu64
")",
1275 le64toh(f
->header
->n_entries
));
1280 if (JOURNAL_HEADER_CONTAINS(f
->header
, n_data
) &&
1281 n_data
!= le64toh(f
->header
->n_data
)) {
1282 error(offsetof(Header
, n_data
),
1283 "Data number mismatch (%"PRIu64
" != %"PRIu64
")",
1285 le64toh(f
->header
->n_data
));
1290 if (JOURNAL_HEADER_CONTAINS(f
->header
, n_fields
) &&
1291 n_fields
!= le64toh(f
->header
->n_fields
)) {
1292 error(offsetof(Header
, n_fields
),
1293 "Field number mismatch (%"PRIu64
" != %"PRIu64
")",
1295 le64toh(f
->header
->n_fields
));
1300 if (JOURNAL_HEADER_CONTAINS(f
->header
, n_tags
) &&
1301 n_tags
!= le64toh(f
->header
->n_tags
)) {
1302 error(offsetof(Header
, n_tags
),
1303 "Tag number mismatch (%"PRIu64
" != %"PRIu64
")",
1305 le64toh(f
->header
->n_tags
));
1310 if (JOURNAL_HEADER_CONTAINS(f
->header
, n_entry_arrays
) &&
1311 n_entry_arrays
!= le64toh(f
->header
->n_entry_arrays
)) {
1312 error(offsetof(Header
, n_entry_arrays
),
1313 "Entry array number mismatch (%"PRIu64
" != %"PRIu64
")",
1315 le64toh(f
->header
->n_entry_arrays
));
1320 if (!found_main_entry_array
&& le64toh(f
->header
->entry_array_offset
) != 0) {
1321 error(0, "Missing main entry array");
1326 if (entry_seqnum_set
&&
1327 entry_seqnum
!= le64toh(f
->header
->tail_entry_seqnum
)) {
1328 error(offsetof(Header
, tail_entry_seqnum
),
1329 "Tail entry sequence number incorrect (%"PRIu64
" != %"PRIu64
")",
1331 le64toh(f
->header
->tail_entry_seqnum
));
1336 if (entry_monotonic_set
&&
1337 (sd_id128_equal(entry_boot_id
, f
->header
->tail_entry_boot_id
) &&
1338 JOURNAL_HEADER_TAIL_ENTRY_BOOT_ID(f
->header
) &&
1339 entry_monotonic
!= le64toh(f
->header
->tail_entry_monotonic
))) {
1341 "Invalid tail monotonic timestamp (%"PRIu64
" != %"PRIu64
")",
1343 le64toh(f
->header
->tail_entry_monotonic
));
1348 if (entry_realtime_set
&& entry_realtime
!= le64toh(f
->header
->tail_entry_realtime
)) {
1350 "Invalid tail realtime timestamp (%"PRIu64
" != %"PRIu64
")",
1352 le64toh(f
->header
->tail_entry_realtime
));
1357 if (fflush(data_fp
) != 0) {
1358 r
= log_error_errno(errno
, "Failed to flush data file stream: %m");
1362 if (fflush(entry_fp
) != 0) {
1363 r
= log_error_errno(errno
, "Failed to flush entry file stream: %m");
1367 if (fflush(entry_array_fp
) != 0) {
1368 r
= log_error_errno(errno
, "Failed to flush entry array file stream: %m");
1372 /* Second iteration: we follow all objects referenced from the
1373 * two entry points: the object hash table and the entry
1374 * array. We also check that everything referenced (directly
1375 * or indirectly) in the data hash table also exists in the
1376 * entry array, and vice versa. Note that we do not care for
1377 * unreferenced objects. We only care that everything that is
1378 * referenced is consistent. */
1380 r
= verify_entry_array(f
,
1381 cache_data_fd
, n_data
,
1382 cache_entry_fd
, n_entries
,
1383 cache_entry_array_fd
, n_entry_arrays
,
1389 r
= verify_data_hash_table(f
,
1390 cache_data_fd
, n_data
,
1391 cache_entry_fd
, n_entries
,
1392 cache_entry_array_fd
, n_entry_arrays
,
1401 mmap_cache_fd_free(cache_data_fd
);
1402 mmap_cache_fd_free(cache_entry_fd
);
1403 mmap_cache_fd_free(cache_entry_array_fd
);
1405 if (first_contained
)
1406 *first_contained
= le64toh(f
->header
->head_entry_realtime
);
1409 *last_validated
= last_tag_realtime
+ f
->fss_interval_usec
;
1412 *last_contained
= le64toh(f
->header
->tail_entry_realtime
);
1420 log_error("File corruption detected at %s:%"PRIu64
" (of %"PRIu64
" bytes, %"PRIu64
"%%).",
1423 (uint64_t) f
->last_stat
.st_size
,
1424 100U * p
/ (uint64_t) f
->last_stat
.st_size
);
1427 mmap_cache_fd_free(cache_data_fd
);
1430 mmap_cache_fd_free(cache_entry_fd
);
1432 if (cache_entry_array_fd
)
1433 mmap_cache_fd_free(cache_entry_array_fd
);