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