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