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