]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/journal-verify.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / journal / journal-verify.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2012 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <fcntl.h>
22 #include <stddef.h>
23 #include <sys/mman.h>
24 #include <unistd.h>
25
26 #include "alloc-util.h"
27 #include "compress.h"
28 #include "fd-util.h"
29 #include "fileio.h"
30 #include "fs-util.h"
31 #include "journal-authenticate.h"
32 #include "journal-def.h"
33 #include "journal-file.h"
34 #include "journal-verify.h"
35 #include "lookup3.h"
36 #include "macro.h"
37 #include "terminal-util.h"
38 #include "util.h"
39
40 static void draw_progress(uint64_t p, usec_t *last_usec) {
41 unsigned n, i, j, k;
42 usec_t z, x;
43
44 if (!on_tty())
45 return;
46
47 z = now(CLOCK_MONOTONIC);
48 x = *last_usec;
49
50 if (x != 0 && x + 40 * USEC_PER_MSEC > z)
51 return;
52
53 *last_usec = z;
54
55 n = (3 * columns()) / 4;
56 j = (n * (unsigned) p) / 65535ULL;
57 k = n - j;
58
59 fputs("\r", stdout);
60 if (colors_enabled())
61 fputs("\x1B[?25l" ANSI_HIGHLIGHT_GREEN, stdout);
62
63 for (i = 0; i < j; i++)
64 fputs("\xe2\x96\x88", stdout);
65
66 fputs(ANSI_NORMAL, stdout);
67
68 for (i = 0; i < k; i++)
69 fputs("\xe2\x96\x91", stdout);
70
71 printf(" %3"PRIu64"%%", 100U * p / 65535U);
72
73 fputs("\r", stdout);
74 if (colors_enabled())
75 fputs("\x1B[?25h", stdout);
76
77 fflush(stdout);
78 }
79
80 static uint64_t scale_progress(uint64_t scale, uint64_t p, uint64_t m) {
81
82 /* Calculates scale * p / m, but handles m == 0 safely, and saturates */
83
84 if (p >= m || m == 0)
85 return scale;
86
87 return scale * p / m;
88 }
89
90 static void flush_progress(void) {
91 unsigned n, i;
92
93 if (!on_tty())
94 return;
95
96 n = (3 * columns()) / 4;
97
98 putchar('\r');
99
100 for (i = 0; i < n + 5; i++)
101 putchar(' ');
102
103 putchar('\r');
104 fflush(stdout);
105 }
106
107 #define debug(_offset, _fmt, ...) do { \
108 flush_progress(); \
109 log_debug(OFSfmt": " _fmt, _offset, ##__VA_ARGS__); \
110 } while (0)
111
112 #define warning(_offset, _fmt, ...) do { \
113 flush_progress(); \
114 log_warning(OFSfmt": " _fmt, _offset, ##__VA_ARGS__); \
115 } while (0)
116
117 #define error(_offset, _fmt, ...) do { \
118 flush_progress(); \
119 log_error(OFSfmt": " _fmt, (uint64_t)_offset, ##__VA_ARGS__); \
120 } while (0)
121
122 #define error_errno(_offset, error, _fmt, ...) do { \
123 flush_progress(); \
124 log_error_errno(error, OFSfmt": " _fmt, (uint64_t)_offset, ##__VA_ARGS__); \
125 } while (0)
126
127 static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o) {
128 uint64_t i;
129
130 assert(f);
131 assert(offset);
132 assert(o);
133
134 /* This does various superficial tests about the length an
135 * possible field values. It does not follow any references to
136 * other objects. */
137
138 if ((o->object.flags & OBJECT_COMPRESSED_XZ) &&
139 o->object.type != OBJECT_DATA) {
140 error(offset, "Found compressed object that isn't of type DATA, which is not allowed.");
141 return -EBADMSG;
142 }
143
144 switch (o->object.type) {
145
146 case OBJECT_DATA: {
147 uint64_t h1, h2;
148 int compression, r;
149
150 if (le64toh(o->data.entry_offset) == 0)
151 warning(offset, "Unused data (entry_offset==0)");
152
153 if ((le64toh(o->data.entry_offset) == 0) ^ (le64toh(o->data.n_entries) == 0)) {
154 error(offset, "Bad n_entries: %"PRIu64, le64toh(o->data.n_entries));
155 return -EBADMSG;
156 }
157
158 if (le64toh(o->object.size) - offsetof(DataObject, payload) <= 0) {
159 error(offset, "Bad object size (<= %zu): %"PRIu64,
160 offsetof(DataObject, payload),
161 le64toh(o->object.size));
162 return -EBADMSG;
163 }
164
165 h1 = le64toh(o->data.hash);
166
167 compression = o->object.flags & OBJECT_COMPRESSION_MASK;
168 if (compression) {
169 _cleanup_free_ void *b = NULL;
170 size_t alloc = 0, b_size;
171
172 r = decompress_blob(compression,
173 o->data.payload,
174 le64toh(o->object.size) - offsetof(Object, data.payload),
175 &b, &alloc, &b_size, 0);
176 if (r < 0) {
177 error_errno(offset, r, "%s decompression failed: %m",
178 object_compressed_to_string(compression));
179 return r;
180 }
181
182 h2 = hash64(b, b_size);
183 } else
184 h2 = hash64(o->data.payload, le64toh(o->object.size) - offsetof(Object, data.payload));
185
186 if (h1 != h2) {
187 error(offset, "Invalid hash (%08"PRIx64" vs. %08"PRIx64, h1, h2);
188 return -EBADMSG;
189 }
190
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));
200 return -EBADMSG;
201 }
202
203 break;
204 }
205
206 case OBJECT_FIELD:
207 if (le64toh(o->object.size) - offsetof(FieldObject, payload) <= 0) {
208 error(offset,
209 "Bad field size (<= %zu): %"PRIu64,
210 offsetof(FieldObject, payload),
211 le64toh(o->object.size));
212 return -EBADMSG;
213 }
214
215 if (!VALID64(le64toh(o->field.next_hash_offset)) ||
216 !VALID64(le64toh(o->field.head_data_offset))) {
217 error(offset,
218 "Invalid offset (next_hash_offset="OFSfmt", head_data_offset="OFSfmt,
219 le64toh(o->field.next_hash_offset),
220 le64toh(o->field.head_data_offset));
221 return -EBADMSG;
222 }
223 break;
224
225 case OBJECT_ENTRY:
226 if ((le64toh(o->object.size) - offsetof(EntryObject, items)) % sizeof(EntryItem) != 0) {
227 error(offset,
228 "Bad entry size (<= %zu): %"PRIu64,
229 offsetof(EntryObject, items),
230 le64toh(o->object.size));
231 return -EBADMSG;
232 }
233
234 if ((le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem) <= 0) {
235 error(offset,
236 "Invalid number items in entry: %"PRIu64,
237 (le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem));
238 return -EBADMSG;
239 }
240
241 if (le64toh(o->entry.seqnum) <= 0) {
242 error(offset,
243 "Invalid entry seqnum: %"PRIx64,
244 le64toh(o->entry.seqnum));
245 return -EBADMSG;
246 }
247
248 if (!VALID_REALTIME(le64toh(o->entry.realtime))) {
249 error(offset,
250 "Invalid entry realtime timestamp: %"PRIu64,
251 le64toh(o->entry.realtime));
252 return -EBADMSG;
253 }
254
255 if (!VALID_MONOTONIC(le64toh(o->entry.monotonic))) {
256 error(offset,
257 "Invalid entry monotonic timestamp: %"PRIu64,
258 le64toh(o->entry.monotonic));
259 return -EBADMSG;
260 }
261
262 for (i = 0; i < journal_file_entry_n_items(o); i++) {
263 if (le64toh(o->entry.items[i].object_offset) == 0 ||
264 !VALID64(le64toh(o->entry.items[i].object_offset))) {
265 error(offset,
266 "Invalid entry item (%"PRIu64"/%"PRIu64" offset: "OFSfmt,
267 i, journal_file_entry_n_items(o),
268 le64toh(o->entry.items[i].object_offset));
269 return -EBADMSG;
270 }
271 }
272
273 break;
274
275 case OBJECT_DATA_HASH_TABLE:
276 case OBJECT_FIELD_HASH_TABLE:
277 if ((le64toh(o->object.size) - offsetof(HashTableObject, items)) % sizeof(HashItem) != 0 ||
278 (le64toh(o->object.size) - offsetof(HashTableObject, items)) / sizeof(HashItem) <= 0) {
279 error(offset,
280 "Invalid %s hash table size: %"PRIu64,
281 o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field",
282 le64toh(o->object.size));
283 return -EBADMSG;
284 }
285
286 for (i = 0; i < journal_file_hash_table_n_items(o); i++) {
287 if (o->hash_table.items[i].head_hash_offset != 0 &&
288 !VALID64(le64toh(o->hash_table.items[i].head_hash_offset))) {
289 error(offset,
290 "Invalid %s hash table item (%"PRIu64"/%"PRIu64") head_hash_offset: "OFSfmt,
291 o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field",
292 i, journal_file_hash_table_n_items(o),
293 le64toh(o->hash_table.items[i].head_hash_offset));
294 return -EBADMSG;
295 }
296 if (o->hash_table.items[i].tail_hash_offset != 0 &&
297 !VALID64(le64toh(o->hash_table.items[i].tail_hash_offset))) {
298 error(offset,
299 "Invalid %s hash table item (%"PRIu64"/%"PRIu64") tail_hash_offset: "OFSfmt,
300 o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field",
301 i, journal_file_hash_table_n_items(o),
302 le64toh(o->hash_table.items[i].tail_hash_offset));
303 return -EBADMSG;
304 }
305
306 if ((o->hash_table.items[i].head_hash_offset != 0) !=
307 (o->hash_table.items[i].tail_hash_offset != 0)) {
308 error(offset,
309 "Invalid %s hash table item (%"PRIu64"/%"PRIu64"): head_hash_offset="OFSfmt" tail_hash_offset="OFSfmt,
310 o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field",
311 i, journal_file_hash_table_n_items(o),
312 le64toh(o->hash_table.items[i].head_hash_offset),
313 le64toh(o->hash_table.items[i].tail_hash_offset));
314 return -EBADMSG;
315 }
316 }
317
318 break;
319
320 case OBJECT_ENTRY_ARRAY:
321 if ((le64toh(o->object.size) - offsetof(EntryArrayObject, items)) % sizeof(le64_t) != 0 ||
322 (le64toh(o->object.size) - offsetof(EntryArrayObject, items)) / sizeof(le64_t) <= 0) {
323 error(offset,
324 "Invalid object entry array size: %"PRIu64,
325 le64toh(o->object.size));
326 return -EBADMSG;
327 }
328
329 if (!VALID64(le64toh(o->entry_array.next_entry_array_offset))) {
330 error(offset,
331 "Invalid object entry array next_entry_array_offset: "OFSfmt,
332 le64toh(o->entry_array.next_entry_array_offset));
333 return -EBADMSG;
334 }
335
336 for (i = 0; i < journal_file_entry_array_n_items(o); i++)
337 if (le64toh(o->entry_array.items[i]) != 0 &&
338 !VALID64(le64toh(o->entry_array.items[i]))) {
339 error(offset,
340 "Invalid object entry array item (%"PRIu64"/%"PRIu64"): "OFSfmt,
341 i, journal_file_entry_array_n_items(o),
342 le64toh(o->entry_array.items[i]));
343 return -EBADMSG;
344 }
345
346 break;
347
348 case OBJECT_TAG:
349 if (le64toh(o->object.size) != sizeof(TagObject)) {
350 error(offset,
351 "Invalid object tag size: %"PRIu64,
352 le64toh(o->object.size));
353 return -EBADMSG;
354 }
355
356 if (!VALID_EPOCH(le64toh(o->tag.epoch))) {
357 error(offset,
358 "Invalid object tag epoch: %"PRIu64,
359 le64toh(o->tag.epoch));
360 return -EBADMSG;
361 }
362
363 break;
364 }
365
366 return 0;
367 }
368
369 static int write_uint64(int fd, uint64_t p) {
370 ssize_t k;
371
372 k = write(fd, &p, sizeof(p));
373 if (k < 0)
374 return -errno;
375 if (k != sizeof(p))
376 return -EIO;
377
378 return 0;
379 }
380
381 static int contains_uint64(MMapCache *m, MMapFileDescriptor *f, uint64_t n, uint64_t p) {
382 uint64_t a, b;
383 int r;
384
385 assert(m);
386 assert(f);
387
388 /* Bisection ... */
389
390 a = 0; b = n;
391 while (a < b) {
392 uint64_t c, *z;
393
394 c = (a + b) / 2;
395
396 r = mmap_cache_get(m, f, PROT_READ|PROT_WRITE, 0, false, c * sizeof(uint64_t), sizeof(uint64_t), NULL, (void **) &z, NULL);
397 if (r < 0)
398 return r;
399
400 if (*z == p)
401 return 1;
402
403 if (a + 1 >= b)
404 return 0;
405
406 if (p < *z)
407 b = c;
408 else
409 a = c;
410 }
411
412 return 0;
413 }
414
415 static int entry_points_to_data(
416 JournalFile *f,
417 MMapFileDescriptor *cache_entry_fd,
418 uint64_t n_entries,
419 uint64_t entry_p,
420 uint64_t data_p) {
421
422 int r;
423 uint64_t i, n, a;
424 Object *o;
425 bool found = false;
426
427 assert(f);
428 assert(cache_entry_fd);
429
430 if (!contains_uint64(f->mmap, cache_entry_fd, n_entries, entry_p)) {
431 error(data_p, "Data object references invalid entry at "OFSfmt, entry_p);
432 return -EBADMSG;
433 }
434
435 r = journal_file_move_to_object(f, OBJECT_ENTRY, entry_p, &o);
436 if (r < 0)
437 return r;
438
439 n = journal_file_entry_n_items(o);
440 for (i = 0; i < n; i++)
441 if (le64toh(o->entry.items[i].object_offset) == data_p) {
442 found = true;
443 break;
444 }
445
446 if (!found) {
447 error(entry_p, "Data object at "OFSfmt" not referenced by linked entry", data_p);
448 return -EBADMSG;
449 }
450
451 /* Check if this entry is also in main entry array. Since the
452 * main entry array has already been verified we can rely on
453 * its consistency. */
454
455 i = 0;
456 n = le64toh(f->header->n_entries);
457 a = le64toh(f->header->entry_array_offset);
458
459 while (i < n) {
460 uint64_t m, u;
461
462 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
463 if (r < 0)
464 return r;
465
466 m = journal_file_entry_array_n_items(o);
467 u = MIN(n - i, m);
468
469 if (entry_p <= le64toh(o->entry_array.items[u-1])) {
470 uint64_t x, y, z;
471
472 x = 0;
473 y = u;
474
475 while (x < y) {
476 z = (x + y) / 2;
477
478 if (le64toh(o->entry_array.items[z]) == entry_p)
479 return 0;
480
481 if (x + 1 >= y)
482 break;
483
484 if (entry_p < le64toh(o->entry_array.items[z]))
485 y = z;
486 else
487 x = z;
488 }
489
490 error(entry_p, "Entry object doesn't exist in main entry array");
491 return -EBADMSG;
492 }
493
494 i += u;
495 a = le64toh(o->entry_array.next_entry_array_offset);
496 }
497
498 return 0;
499 }
500
501 static int verify_data(
502 JournalFile *f,
503 Object *o, uint64_t p,
504 MMapFileDescriptor *cache_entry_fd, uint64_t n_entries,
505 MMapFileDescriptor *cache_entry_array_fd, uint64_t n_entry_arrays) {
506
507 uint64_t i, n, a, last, q;
508 int r;
509
510 assert(f);
511 assert(o);
512 assert(cache_entry_fd);
513 assert(cache_entry_array_fd);
514
515 n = le64toh(o->data.n_entries);
516 a = le64toh(o->data.entry_array_offset);
517
518 /* Entry array means at least two objects */
519 if (a && n < 2) {
520 error(p, "Entry array present (entry_array_offset="OFSfmt", but n_entries=%"PRIu64")", a, n);
521 return -EBADMSG;
522 }
523
524 if (n == 0)
525 return 0;
526
527 /* We already checked that earlier */
528 assert(o->data.entry_offset);
529
530 last = q = le64toh(o->data.entry_offset);
531 r = entry_points_to_data(f, cache_entry_fd, n_entries, q, p);
532 if (r < 0)
533 return r;
534
535 i = 1;
536 while (i < n) {
537 uint64_t next, m, j;
538
539 if (a == 0) {
540 error(p, "Array chain too short");
541 return -EBADMSG;
542 }
543
544 if (!contains_uint64(f->mmap, cache_entry_array_fd, n_entry_arrays, a)) {
545 error(p, "Invalid array offset "OFSfmt, a);
546 return -EBADMSG;
547 }
548
549 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
550 if (r < 0)
551 return r;
552
553 next = le64toh(o->entry_array.next_entry_array_offset);
554 if (next != 0 && next <= a) {
555 error(p, "Array chain has cycle (jumps back from "OFSfmt" to "OFSfmt")", a, next);
556 return -EBADMSG;
557 }
558
559 m = journal_file_entry_array_n_items(o);
560 for (j = 0; i < n && j < m; i++, j++) {
561
562 q = le64toh(o->entry_array.items[j]);
563 if (q <= last) {
564 error(p, "Data object's entry array not sorted");
565 return -EBADMSG;
566 }
567 last = q;
568
569 r = entry_points_to_data(f, cache_entry_fd, n_entries, q, p);
570 if (r < 0)
571 return r;
572
573 /* Pointer might have moved, reposition */
574 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
575 if (r < 0)
576 return r;
577 }
578
579 a = next;
580 }
581
582 return 0;
583 }
584
585 static int verify_hash_table(
586 JournalFile *f,
587 MMapFileDescriptor *cache_data_fd, uint64_t n_data,
588 MMapFileDescriptor *cache_entry_fd, uint64_t n_entries,
589 MMapFileDescriptor *cache_entry_array_fd, uint64_t n_entry_arrays,
590 usec_t *last_usec,
591 bool show_progress) {
592
593 uint64_t i, n;
594 int r;
595
596 assert(f);
597 assert(cache_data_fd);
598 assert(cache_entry_fd);
599 assert(cache_entry_array_fd);
600 assert(last_usec);
601
602 n = le64toh(f->header->data_hash_table_size) / sizeof(HashItem);
603 if (n <= 0)
604 return 0;
605
606 r = journal_file_map_data_hash_table(f);
607 if (r < 0)
608 return log_error_errno(r, "Failed to map data hash table: %m");
609
610 for (i = 0; i < n; i++) {
611 uint64_t last = 0, p;
612
613 if (show_progress)
614 draw_progress(0xC000 + scale_progress(0x3FFF, i, n), last_usec);
615
616 p = le64toh(f->data_hash_table[i].head_hash_offset);
617 while (p != 0) {
618 Object *o;
619 uint64_t next;
620
621 if (!contains_uint64(f->mmap, cache_data_fd, n_data, p)) {
622 error(p, "Invalid data object at hash entry %"PRIu64" of %"PRIu64, i, n);
623 return -EBADMSG;
624 }
625
626 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
627 if (r < 0)
628 return r;
629
630 next = le64toh(o->data.next_hash_offset);
631 if (next != 0 && next <= p) {
632 error(p, "Hash chain has a cycle in hash entry %"PRIu64" of %"PRIu64, i, n);
633 return -EBADMSG;
634 }
635
636 if (le64toh(o->data.hash) % n != i) {
637 error(p, "Hash value mismatch in hash entry %"PRIu64" of %"PRIu64, i, n);
638 return -EBADMSG;
639 }
640
641 r = verify_data(f, o, p, cache_entry_fd, n_entries, cache_entry_array_fd, n_entry_arrays);
642 if (r < 0)
643 return r;
644
645 last = p;
646 p = next;
647 }
648
649 if (last != le64toh(f->data_hash_table[i].tail_hash_offset)) {
650 error(p, "Tail hash pointer mismatch in hash table");
651 return -EBADMSG;
652 }
653 }
654
655 return 0;
656 }
657
658 static int data_object_in_hash_table(JournalFile *f, uint64_t hash, uint64_t p) {
659 uint64_t n, h, q;
660 int r;
661 assert(f);
662
663 n = le64toh(f->header->data_hash_table_size) / sizeof(HashItem);
664 if (n <= 0)
665 return 0;
666
667 r = journal_file_map_data_hash_table(f);
668 if (r < 0)
669 return log_error_errno(r, "Failed to map data hash table: %m");
670
671 h = hash % n;
672
673 q = le64toh(f->data_hash_table[h].head_hash_offset);
674 while (q != 0) {
675 Object *o;
676
677 if (p == q)
678 return 1;
679
680 r = journal_file_move_to_object(f, OBJECT_DATA, q, &o);
681 if (r < 0)
682 return r;
683
684 q = le64toh(o->data.next_hash_offset);
685 }
686
687 return 0;
688 }
689
690 static int verify_entry(
691 JournalFile *f,
692 Object *o, uint64_t p,
693 MMapFileDescriptor *cache_data_fd, uint64_t n_data) {
694
695 uint64_t i, n;
696 int r;
697
698 assert(f);
699 assert(o);
700 assert(cache_data_fd);
701
702 n = journal_file_entry_n_items(o);
703 for (i = 0; i < n; i++) {
704 uint64_t q, h;
705 Object *u;
706
707 q = le64toh(o->entry.items[i].object_offset);
708 h = le64toh(o->entry.items[i].hash);
709
710 if (!contains_uint64(f->mmap, cache_data_fd, n_data, q)) {
711 error(p, "Invalid data object of entry");
712 return -EBADMSG;
713 }
714
715 r = journal_file_move_to_object(f, OBJECT_DATA, q, &u);
716 if (r < 0)
717 return r;
718
719 if (le64toh(u->data.hash) != h) {
720 error(p, "Hash mismatch for data object of entry");
721 return -EBADMSG;
722 }
723
724 r = data_object_in_hash_table(f, h, q);
725 if (r < 0)
726 return r;
727 if (r == 0) {
728 error(p, "Data object missing from hash table");
729 return -EBADMSG;
730 }
731 }
732
733 return 0;
734 }
735
736 static int verify_entry_array(
737 JournalFile *f,
738 MMapFileDescriptor *cache_data_fd, uint64_t n_data,
739 MMapFileDescriptor *cache_entry_fd, uint64_t n_entries,
740 MMapFileDescriptor *cache_entry_array_fd, uint64_t n_entry_arrays,
741 usec_t *last_usec,
742 bool show_progress) {
743
744 uint64_t i = 0, a, n, last = 0;
745 int r;
746
747 assert(f);
748 assert(cache_data_fd);
749 assert(cache_entry_fd);
750 assert(cache_entry_array_fd);
751 assert(last_usec);
752
753 n = le64toh(f->header->n_entries);
754 a = le64toh(f->header->entry_array_offset);
755 while (i < n) {
756 uint64_t next, m, j;
757 Object *o;
758
759 if (show_progress)
760 draw_progress(0x8000 + scale_progress(0x3FFF, i, n), last_usec);
761
762 if (a == 0) {
763 error(a, "Array chain too short at %"PRIu64" of %"PRIu64, i, n);
764 return -EBADMSG;
765 }
766
767 if (!contains_uint64(f->mmap, cache_entry_array_fd, n_entry_arrays, a)) {
768 error(a, "Invalid array %"PRIu64" of %"PRIu64, i, n);
769 return -EBADMSG;
770 }
771
772 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
773 if (r < 0)
774 return r;
775
776 next = le64toh(o->entry_array.next_entry_array_offset);
777 if (next != 0 && next <= a) {
778 error(a, "Array chain has cycle at %"PRIu64" of %"PRIu64" (jumps back from to "OFSfmt")", i, n, next);
779 return -EBADMSG;
780 }
781
782 m = journal_file_entry_array_n_items(o);
783 for (j = 0; i < n && j < m; i++, j++) {
784 uint64_t p;
785
786 p = le64toh(o->entry_array.items[j]);
787 if (p <= last) {
788 error(a, "Entry array not sorted at %"PRIu64" of %"PRIu64, i, n);
789 return -EBADMSG;
790 }
791 last = p;
792
793 if (!contains_uint64(f->mmap, cache_entry_fd, n_entries, p)) {
794 error(a, "Invalid array entry at %"PRIu64" of %"PRIu64, i, n);
795 return -EBADMSG;
796 }
797
798 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
799 if (r < 0)
800 return r;
801
802 r = verify_entry(f, o, p, cache_data_fd, n_data);
803 if (r < 0)
804 return r;
805
806 /* Pointer might have moved, reposition */
807 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
808 if (r < 0)
809 return r;
810 }
811
812 a = next;
813 }
814
815 return 0;
816 }
817
818 int journal_file_verify(
819 JournalFile *f,
820 const char *key,
821 usec_t *first_contained, usec_t *last_validated, usec_t *last_contained,
822 bool show_progress) {
823 int r;
824 Object *o;
825 uint64_t p = 0, last_epoch = 0, last_tag_realtime = 0, last_sealed_realtime = 0;
826
827 uint64_t entry_seqnum = 0, entry_monotonic = 0, entry_realtime = 0;
828 sd_id128_t entry_boot_id;
829 bool entry_seqnum_set = false, entry_monotonic_set = false, entry_realtime_set = false, found_main_entry_array = false;
830 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;
831 usec_t last_usec = 0;
832 int data_fd = -1, entry_fd = -1, entry_array_fd = -1;
833 MMapFileDescriptor *cache_data_fd = NULL, *cache_entry_fd = NULL, *cache_entry_array_fd = NULL;
834 unsigned i;
835 bool found_last = false;
836 const char *tmp_dir = NULL;
837
838 #if HAVE_GCRYPT
839 uint64_t last_tag = 0;
840 #endif
841 assert(f);
842
843 if (key) {
844 #if HAVE_GCRYPT
845 r = journal_file_parse_verification_key(f, key);
846 if (r < 0) {
847 log_error("Failed to parse seed.");
848 return r;
849 }
850 #else
851 return -EOPNOTSUPP;
852 #endif
853 } else if (f->seal)
854 return -ENOKEY;
855
856 r = var_tmp_dir(&tmp_dir);
857 if (r < 0) {
858 log_error_errno(r, "Failed to determine temporary directory: %m");
859 goto fail;
860 }
861
862 data_fd = open_tmpfile_unlinkable(tmp_dir, O_RDWR | O_CLOEXEC);
863 if (data_fd < 0) {
864 r = log_error_errno(data_fd, "Failed to create data file: %m");
865 goto fail;
866 }
867
868 entry_fd = open_tmpfile_unlinkable(tmp_dir, O_RDWR | O_CLOEXEC);
869 if (entry_fd < 0) {
870 r = log_error_errno(entry_fd, "Failed to create entry file: %m");
871 goto fail;
872 }
873
874 entry_array_fd = open_tmpfile_unlinkable(tmp_dir, O_RDWR | O_CLOEXEC);
875 if (entry_array_fd < 0) {
876 r = log_error_errno(entry_array_fd,
877 "Failed to create entry array file: %m");
878 goto fail;
879 }
880
881 cache_data_fd = mmap_cache_add_fd(f->mmap, data_fd);
882 if (!cache_data_fd) {
883 r = log_oom();
884 goto fail;
885 }
886
887 cache_entry_fd = mmap_cache_add_fd(f->mmap, entry_fd);
888 if (!cache_entry_fd) {
889 r = log_oom();
890 goto fail;
891 }
892
893 cache_entry_array_fd = mmap_cache_add_fd(f->mmap, entry_array_fd);
894 if (!cache_entry_array_fd) {
895 r = log_oom();
896 goto fail;
897 }
898
899 if (le32toh(f->header->compatible_flags) & ~HEADER_COMPATIBLE_SUPPORTED) {
900 log_error("Cannot verify file with unknown extensions.");
901 r = -EOPNOTSUPP;
902 goto fail;
903 }
904
905 for (i = 0; i < sizeof(f->header->reserved); i++)
906 if (f->header->reserved[i] != 0) {
907 error(offsetof(Header, reserved[i]), "Reserved field is non-zero");
908 r = -EBADMSG;
909 goto fail;
910 }
911
912 /* First iteration: we go through all objects, verify the
913 * superficial structure, headers, hashes. */
914
915 p = le64toh(f->header->header_size);
916 for (;;) {
917 /* Early exit if there are no objects in the file, at all */
918 if (le64toh(f->header->tail_object_offset) == 0)
919 break;
920
921 if (show_progress)
922 draw_progress(scale_progress(0x7FFF, p, le64toh(f->header->tail_object_offset)), &last_usec);
923
924 r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &o);
925 if (r < 0) {
926 error(p, "Invalid object");
927 goto fail;
928 }
929
930 if (p > le64toh(f->header->tail_object_offset)) {
931 error(offsetof(Header, tail_object_offset), "Invalid tail object pointer");
932 r = -EBADMSG;
933 goto fail;
934 }
935
936 n_objects++;
937
938 r = journal_file_object_verify(f, p, o);
939 if (r < 0) {
940 error_errno(p, r, "Invalid object contents: %m");
941 goto fail;
942 }
943
944 if ((o->object.flags & OBJECT_COMPRESSED_XZ) &&
945 (o->object.flags & OBJECT_COMPRESSED_LZ4)) {
946 error(p, "Objected with double compression");
947 r = -EINVAL;
948 goto fail;
949 }
950
951 if ((o->object.flags & OBJECT_COMPRESSED_XZ) && !JOURNAL_HEADER_COMPRESSED_XZ(f->header)) {
952 error(p, "XZ compressed object in file without XZ compression");
953 r = -EBADMSG;
954 goto fail;
955 }
956
957 if ((o->object.flags & OBJECT_COMPRESSED_LZ4) && !JOURNAL_HEADER_COMPRESSED_LZ4(f->header)) {
958 error(p, "LZ4 compressed object in file without LZ4 compression");
959 r = -EBADMSG;
960 goto fail;
961 }
962
963 switch (o->object.type) {
964
965 case OBJECT_DATA:
966 r = write_uint64(data_fd, p);
967 if (r < 0)
968 goto fail;
969
970 n_data++;
971 break;
972
973 case OBJECT_FIELD:
974 n_fields++;
975 break;
976
977 case OBJECT_ENTRY:
978 if (JOURNAL_HEADER_SEALED(f->header) && n_tags <= 0) {
979 error(p, "First entry before first tag");
980 r = -EBADMSG;
981 goto fail;
982 }
983
984 r = write_uint64(entry_fd, p);
985 if (r < 0)
986 goto fail;
987
988 if (le64toh(o->entry.realtime) < last_tag_realtime) {
989 error(p, "Older entry after newer tag");
990 r = -EBADMSG;
991 goto fail;
992 }
993
994 if (!entry_seqnum_set &&
995 le64toh(o->entry.seqnum) != le64toh(f->header->head_entry_seqnum)) {
996 error(p, "Head entry sequence number incorrect");
997 r = -EBADMSG;
998 goto fail;
999 }
1000
1001 if (entry_seqnum_set &&
1002 entry_seqnum >= le64toh(o->entry.seqnum)) {
1003 error(p, "Entry sequence number out of synchronization");
1004 r = -EBADMSG;
1005 goto fail;
1006 }
1007
1008 entry_seqnum = le64toh(o->entry.seqnum);
1009 entry_seqnum_set = true;
1010
1011 if (entry_monotonic_set &&
1012 sd_id128_equal(entry_boot_id, o->entry.boot_id) &&
1013 entry_monotonic > le64toh(o->entry.monotonic)) {
1014 error(p, "Entry timestamp out of synchronization");
1015 r = -EBADMSG;
1016 goto fail;
1017 }
1018
1019 entry_monotonic = le64toh(o->entry.monotonic);
1020 entry_boot_id = o->entry.boot_id;
1021 entry_monotonic_set = true;
1022
1023 if (!entry_realtime_set &&
1024 le64toh(o->entry.realtime) != le64toh(f->header->head_entry_realtime)) {
1025 error(p, "Head entry realtime timestamp incorrect");
1026 r = -EBADMSG;
1027 goto fail;
1028 }
1029
1030 entry_realtime = le64toh(o->entry.realtime);
1031 entry_realtime_set = true;
1032
1033 n_entries++;
1034 break;
1035
1036 case OBJECT_DATA_HASH_TABLE:
1037 if (n_data_hash_tables > 1) {
1038 error(p, "More than one data hash table");
1039 r = -EBADMSG;
1040 goto fail;
1041 }
1042
1043 if (le64toh(f->header->data_hash_table_offset) != p + offsetof(HashTableObject, items) ||
1044 le64toh(f->header->data_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) {
1045 error(p, "header fields for data hash table invalid");
1046 r = -EBADMSG;
1047 goto fail;
1048 }
1049
1050 n_data_hash_tables++;
1051 break;
1052
1053 case OBJECT_FIELD_HASH_TABLE:
1054 if (n_field_hash_tables > 1) {
1055 error(p, "More than one field hash table");
1056 r = -EBADMSG;
1057 goto fail;
1058 }
1059
1060 if (le64toh(f->header->field_hash_table_offset) != p + offsetof(HashTableObject, items) ||
1061 le64toh(f->header->field_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) {
1062 error(p, "Header fields for field hash table invalid");
1063 r = -EBADMSG;
1064 goto fail;
1065 }
1066
1067 n_field_hash_tables++;
1068 break;
1069
1070 case OBJECT_ENTRY_ARRAY:
1071 r = write_uint64(entry_array_fd, p);
1072 if (r < 0)
1073 goto fail;
1074
1075 if (p == le64toh(f->header->entry_array_offset)) {
1076 if (found_main_entry_array) {
1077 error(p, "More than one main entry array");
1078 r = -EBADMSG;
1079 goto fail;
1080 }
1081
1082 found_main_entry_array = true;
1083 }
1084
1085 n_entry_arrays++;
1086 break;
1087
1088 case OBJECT_TAG:
1089 if (!JOURNAL_HEADER_SEALED(f->header)) {
1090 error(p, "Tag object in file without sealing");
1091 r = -EBADMSG;
1092 goto fail;
1093 }
1094
1095 if (le64toh(o->tag.seqnum) != n_tags + 1) {
1096 error(p, "Tag sequence number out of synchronization");
1097 r = -EBADMSG;
1098 goto fail;
1099 }
1100
1101 if (le64toh(o->tag.epoch) < last_epoch) {
1102 error(p, "Epoch sequence out of synchronization");
1103 r = -EBADMSG;
1104 goto fail;
1105 }
1106
1107 #if HAVE_GCRYPT
1108 if (f->seal) {
1109 uint64_t q, rt;
1110
1111 debug(p, "Checking tag %"PRIu64"...", le64toh(o->tag.seqnum));
1112
1113 rt = f->fss_start_usec + le64toh(o->tag.epoch) * f->fss_interval_usec;
1114 if (entry_realtime_set && entry_realtime >= rt + f->fss_interval_usec) {
1115 error(p, "tag/entry realtime timestamp out of synchronization");
1116 r = -EBADMSG;
1117 goto fail;
1118 }
1119
1120 /* OK, now we know the epoch. So let's now set
1121 * it, and calculate the HMAC for everything
1122 * since the last tag. */
1123 r = journal_file_fsprg_seek(f, le64toh(o->tag.epoch));
1124 if (r < 0)
1125 goto fail;
1126
1127 r = journal_file_hmac_start(f);
1128 if (r < 0)
1129 goto fail;
1130
1131 if (last_tag == 0) {
1132 r = journal_file_hmac_put_header(f);
1133 if (r < 0)
1134 goto fail;
1135
1136 q = le64toh(f->header->header_size);
1137 } else
1138 q = last_tag;
1139
1140 while (q <= p) {
1141 r = journal_file_move_to_object(f, OBJECT_UNUSED, q, &o);
1142 if (r < 0)
1143 goto fail;
1144
1145 r = journal_file_hmac_put_object(f, OBJECT_UNUSED, o, q);
1146 if (r < 0)
1147 goto fail;
1148
1149 q = q + ALIGN64(le64toh(o->object.size));
1150 }
1151
1152 /* Position might have changed, let's reposition things */
1153 r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &o);
1154 if (r < 0)
1155 goto fail;
1156
1157 if (memcmp(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH) != 0) {
1158 error(p, "Tag failed verification");
1159 r = -EBADMSG;
1160 goto fail;
1161 }
1162
1163 f->hmac_running = false;
1164 last_tag_realtime = rt;
1165 last_sealed_realtime = entry_realtime;
1166 }
1167
1168 last_tag = p + ALIGN64(le64toh(o->object.size));
1169 #endif
1170
1171 last_epoch = le64toh(o->tag.epoch);
1172
1173 n_tags++;
1174 break;
1175
1176 default:
1177 n_weird++;
1178 }
1179
1180 if (p == le64toh(f->header->tail_object_offset)) {
1181 found_last = true;
1182 break;
1183 }
1184
1185 p = p + ALIGN64(le64toh(o->object.size));
1186 };
1187
1188 if (!found_last && le64toh(f->header->tail_object_offset) != 0) {
1189 error(le64toh(f->header->tail_object_offset), "Tail object pointer dead");
1190 r = -EBADMSG;
1191 goto fail;
1192 }
1193
1194 if (n_objects != le64toh(f->header->n_objects)) {
1195 error(offsetof(Header, n_objects), "Object number mismatch");
1196 r = -EBADMSG;
1197 goto fail;
1198 }
1199
1200 if (n_entries != le64toh(f->header->n_entries)) {
1201 error(offsetof(Header, n_entries), "Entry number mismatch");
1202 r = -EBADMSG;
1203 goto fail;
1204 }
1205
1206 if (JOURNAL_HEADER_CONTAINS(f->header, n_data) &&
1207 n_data != le64toh(f->header->n_data)) {
1208 error(offsetof(Header, n_data), "Data number mismatch");
1209 r = -EBADMSG;
1210 goto fail;
1211 }
1212
1213 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields) &&
1214 n_fields != le64toh(f->header->n_fields)) {
1215 error(offsetof(Header, n_fields), "Field number mismatch");
1216 r = -EBADMSG;
1217 goto fail;
1218 }
1219
1220 if (JOURNAL_HEADER_CONTAINS(f->header, n_tags) &&
1221 n_tags != le64toh(f->header->n_tags)) {
1222 error(offsetof(Header, n_tags), "Tag number mismatch");
1223 r = -EBADMSG;
1224 goto fail;
1225 }
1226
1227 if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays) &&
1228 n_entry_arrays != le64toh(f->header->n_entry_arrays)) {
1229 error(offsetof(Header, n_entry_arrays), "Entry array number mismatch");
1230 r = -EBADMSG;
1231 goto fail;
1232 }
1233
1234 if (!found_main_entry_array && le64toh(f->header->entry_array_offset) != 0) {
1235 error(0, "Missing entry array");
1236 r = -EBADMSG;
1237 goto fail;
1238 }
1239
1240 if (entry_seqnum_set &&
1241 entry_seqnum != le64toh(f->header->tail_entry_seqnum)) {
1242 error(offsetof(Header, tail_entry_seqnum), "Invalid tail seqnum");
1243 r = -EBADMSG;
1244 goto fail;
1245 }
1246
1247 if (entry_monotonic_set &&
1248 (!sd_id128_equal(entry_boot_id, f->header->boot_id) ||
1249 entry_monotonic != le64toh(f->header->tail_entry_monotonic))) {
1250 error(0, "Invalid tail monotonic timestamp");
1251 r = -EBADMSG;
1252 goto fail;
1253 }
1254
1255 if (entry_realtime_set && entry_realtime != le64toh(f->header->tail_entry_realtime)) {
1256 error(0, "Invalid tail realtime timestamp");
1257 r = -EBADMSG;
1258 goto fail;
1259 }
1260
1261 /* Second iteration: we follow all objects referenced from the
1262 * two entry points: the object hash table and the entry
1263 * array. We also check that everything referenced (directly
1264 * or indirectly) in the data hash table also exists in the
1265 * entry array, and vice versa. Note that we do not care for
1266 * unreferenced objects. We only care that everything that is
1267 * referenced is consistent. */
1268
1269 r = verify_entry_array(f,
1270 cache_data_fd, n_data,
1271 cache_entry_fd, n_entries,
1272 cache_entry_array_fd, n_entry_arrays,
1273 &last_usec,
1274 show_progress);
1275 if (r < 0)
1276 goto fail;
1277
1278 r = verify_hash_table(f,
1279 cache_data_fd, n_data,
1280 cache_entry_fd, n_entries,
1281 cache_entry_array_fd, n_entry_arrays,
1282 &last_usec,
1283 show_progress);
1284 if (r < 0)
1285 goto fail;
1286
1287 if (show_progress)
1288 flush_progress();
1289
1290 mmap_cache_free_fd(f->mmap, cache_data_fd);
1291 mmap_cache_free_fd(f->mmap, cache_entry_fd);
1292 mmap_cache_free_fd(f->mmap, cache_entry_array_fd);
1293
1294 safe_close(data_fd);
1295 safe_close(entry_fd);
1296 safe_close(entry_array_fd);
1297
1298 if (first_contained)
1299 *first_contained = le64toh(f->header->head_entry_realtime);
1300 if (last_validated)
1301 *last_validated = last_sealed_realtime;
1302 if (last_contained)
1303 *last_contained = le64toh(f->header->tail_entry_realtime);
1304
1305 return 0;
1306
1307 fail:
1308 if (show_progress)
1309 flush_progress();
1310
1311 log_error("File corruption detected at %s:"OFSfmt" (of %llu bytes, %"PRIu64"%%).",
1312 f->path,
1313 p,
1314 (unsigned long long) f->last_stat.st_size,
1315 100 * p / f->last_stat.st_size);
1316
1317 if (data_fd >= 0)
1318 safe_close(data_fd);
1319
1320 if (entry_fd >= 0)
1321 safe_close(entry_fd);
1322
1323 if (entry_array_fd >= 0)
1324 safe_close(entry_array_fd);
1325
1326 if (cache_data_fd)
1327 mmap_cache_free_fd(f->mmap, cache_data_fd);
1328
1329 if (cache_entry_fd)
1330 mmap_cache_free_fd(f->mmap, cache_entry_fd);
1331
1332 if (cache_entry_array_fd)
1333 mmap_cache_free_fd(f->mmap, cache_entry_array_fd);
1334
1335 return r;
1336 }