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