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