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