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