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