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