]> git.proxmox.com Git - systemd.git/blob - src/journal/journal-verify.c
Imported Upstream version 223
[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_ON, stdout);
57
58 for (i = 0; i < j; i++)
59 fputs("\xe2\x96\x88", stdout);
60
61 fputs(ANSI_HIGHLIGHT_OFF, 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 log_error_errno(errno, "Failed to create data file: %m");
843 r = -errno;
844 goto fail;
845 }
846
847 entry_fd = open_tmpfile("/var/tmp", O_RDWR | O_CLOEXEC);
848 if (entry_fd < 0) {
849 log_error_errno(errno, "Failed to create entry file: %m");
850 r = -errno;
851 goto fail;
852 }
853
854 entry_array_fd = open_tmpfile("/var/tmp", O_RDWR | O_CLOEXEC);
855 if (entry_array_fd < 0) {
856 log_error_errno(errno, "Failed to create entry array file: %m");
857 r = -errno;
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, "Envalid 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 }