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