]> git.proxmox.com Git - systemd.git/blame - src/journal/journal-verify.c
Imported Upstream version 223
[systemd.git] / src / journal / journal-verify.c
CommitLineData
663996b3
MS
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"
e3bff60a 35#include "terminal-util.h"
663996b3 36
5eef597e
MP
37static 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
7035cd9e
MP
72static 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
5eef597e
MP
82static 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
14228c0d 114static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o) {
663996b3
MS
115 uint64_t i;
116
117 assert(f);
14228c0d 118 assert(offset);
663996b3
MS
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
5eef597e 125 if ((o->object.flags & OBJECT_COMPRESSED_XZ) &&
7035cd9e
MP
126 o->object.type != OBJECT_DATA) {
127 error(offset, "Found compressed object that isn't of type DATA, which is not allowed.");
663996b3 128 return -EBADMSG;
7035cd9e 129 }
663996b3
MS
130
131 switch (o->object.type) {
132
133 case OBJECT_DATA: {
134 uint64_t h1, h2;
5eef597e 135 int compression, r;
663996b3 136
14228c0d 137 if (le64toh(o->data.entry_offset) == 0)
7035cd9e 138 warning(offset, "Unused data (entry_offset==0)");
14228c0d
MB
139
140 if ((le64toh(o->data.entry_offset) == 0) ^ (le64toh(o->data.n_entries) == 0)) {
7035cd9e 141 error(offset, "Bad n_entries: %"PRIu64, o->data.n_entries);
663996b3 142 return -EBADMSG;
14228c0d 143 }
663996b3 144
14228c0d 145 if (le64toh(o->object.size) - offsetof(DataObject, payload) <= 0) {
7035cd9e 146 error(offset, "Bad object size (<= %zu): %"PRIu64,
5eef597e
MP
147 offsetof(DataObject, payload),
148 le64toh(o->object.size));
663996b3 149 return -EBADMSG;
14228c0d 150 }
663996b3
MS
151
152 h1 = le64toh(o->data.hash);
153
5eef597e
MP
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;
14228c0d 167 }
663996b3
MS
168
169 h2 = hash64(b, b_size);
663996b3
MS
170 } else
171 h2 = hash64(o->data.payload, le64toh(o->object.size) - offsetof(Object, data.payload));
172
14228c0d 173 if (h1 != h2) {
7035cd9e 174 error(offset, "Invalid hash (%08"PRIx64" vs. %08"PRIx64, h1, h2);
663996b3 175 return -EBADMSG;
14228c0d 176 }
663996b3
MS
177
178 if (!VALID64(o->data.next_hash_offset) ||
179 !VALID64(o->data.next_field_offset) ||
180 !VALID64(o->data.entry_offset) ||
14228c0d 181 !VALID64(o->data.entry_array_offset)) {
7035cd9e 182 error(offset, "Invalid offset (next_hash_offset="OFSfmt", next_field_offset="OFSfmt", entry_offset="OFSfmt", entry_array_offset="OFSfmt,
5eef597e
MP
183 o->data.next_hash_offset,
184 o->data.next_field_offset,
185 o->data.entry_offset,
186 o->data.entry_array_offset);
663996b3 187 return -EBADMSG;
14228c0d 188 }
663996b3
MS
189
190 break;
191 }
192
193 case OBJECT_FIELD:
14228c0d 194 if (le64toh(o->object.size) - offsetof(FieldObject, payload) <= 0) {
5eef597e 195 error(offset,
7035cd9e 196 "Bad field size (<= %zu): %"PRIu64,
5eef597e
MP
197 offsetof(FieldObject, payload),
198 le64toh(o->object.size));
663996b3 199 return -EBADMSG;
14228c0d 200 }
663996b3
MS
201
202 if (!VALID64(o->field.next_hash_offset) ||
14228c0d 203 !VALID64(o->field.head_data_offset)) {
5eef597e 204 error(offset,
7035cd9e 205 "Invalid offset (next_hash_offset="OFSfmt", head_data_offset="OFSfmt,
5eef597e
MP
206 o->field.next_hash_offset,
207 o->field.head_data_offset);
663996b3 208 return -EBADMSG;
14228c0d 209 }
663996b3
MS
210 break;
211
212 case OBJECT_ENTRY:
14228c0d 213 if ((le64toh(o->object.size) - offsetof(EntryObject, items)) % sizeof(EntryItem) != 0) {
5eef597e 214 error(offset,
7035cd9e 215 "Bad entry size (<= %zu): %"PRIu64,
5eef597e
MP
216 offsetof(EntryObject, items),
217 le64toh(o->object.size));
663996b3 218 return -EBADMSG;
14228c0d 219 }
663996b3 220
14228c0d 221 if ((le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem) <= 0) {
5eef597e 222 error(offset,
7035cd9e 223 "Invalid number items in entry: %"PRIu64,
5eef597e 224 (le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem));
663996b3 225 return -EBADMSG;
14228c0d
MB
226 }
227
228 if (le64toh(o->entry.seqnum) <= 0) {
5eef597e 229 error(offset,
7035cd9e 230 "Invalid entry seqnum: %"PRIx64,
5eef597e 231 le64toh(o->entry.seqnum));
14228c0d
MB
232 return -EBADMSG;
233 }
663996b3 234
14228c0d 235 if (!VALID_REALTIME(le64toh(o->entry.realtime))) {
5eef597e 236 error(offset,
7035cd9e 237 "Invalid entry realtime timestamp: %"PRIu64,
5eef597e 238 le64toh(o->entry.realtime));
663996b3 239 return -EBADMSG;
14228c0d
MB
240 }
241
242 if (!VALID_MONOTONIC(le64toh(o->entry.monotonic))) {
5eef597e 243 error(offset,
7035cd9e 244 "Invalid entry monotonic timestamp: %"PRIu64,
5eef597e 245 le64toh(o->entry.monotonic));
14228c0d
MB
246 return -EBADMSG;
247 }
663996b3
MS
248
249 for (i = 0; i < journal_file_entry_n_items(o); i++) {
250 if (o->entry.items[i].object_offset == 0 ||
14228c0d 251 !VALID64(o->entry.items[i].object_offset)) {
5eef597e 252 error(offset,
7035cd9e 253 "Invalid entry item (%"PRIu64"/%"PRIu64" offset: "OFSfmt,
5eef597e
MP
254 i, journal_file_entry_n_items(o),
255 o->entry.items[i].object_offset);
663996b3 256 return -EBADMSG;
14228c0d 257 }
663996b3
MS
258 }
259
260 break;
261
262 case OBJECT_DATA_HASH_TABLE:
263 case OBJECT_FIELD_HASH_TABLE:
14228c0d
MB
264 if ((le64toh(o->object.size) - offsetof(HashTableObject, items)) % sizeof(HashItem) != 0 ||
265 (le64toh(o->object.size) - offsetof(HashTableObject, items)) / sizeof(HashItem) <= 0) {
5eef597e 266 error(offset,
7035cd9e 267 "Invalid %s hash table size: %"PRIu64,
5eef597e
MP
268 o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field",
269 le64toh(o->object.size));
663996b3 270 return -EBADMSG;
14228c0d 271 }
663996b3
MS
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 &&
14228c0d 275 !VALID64(le64toh(o->hash_table.items[i].head_hash_offset))) {
5eef597e 276 error(offset,
7035cd9e 277 "Invalid %s hash table item (%"PRIu64"/%"PRIu64") head_hash_offset: "OFSfmt,
5eef597e
MP
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));
663996b3 281 return -EBADMSG;
14228c0d 282 }
663996b3 283 if (o->hash_table.items[i].tail_hash_offset != 0 &&
14228c0d 284 !VALID64(le64toh(o->hash_table.items[i].tail_hash_offset))) {
5eef597e 285 error(offset,
7035cd9e 286 "Invalid %s hash table item (%"PRIu64"/%"PRIu64") tail_hash_offset: "OFSfmt,
5eef597e
MP
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));
663996b3 290 return -EBADMSG;
14228c0d 291 }
663996b3
MS
292
293 if ((o->hash_table.items[i].head_hash_offset != 0) !=
14228c0d 294 (o->hash_table.items[i].tail_hash_offset != 0)) {
5eef597e 295 error(offset,
7035cd9e 296 "Invalid %s hash table item (%"PRIu64"/%"PRIu64"): head_hash_offset="OFSfmt" tail_hash_offset="OFSfmt,
5eef597e
MP
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));
663996b3 301 return -EBADMSG;
14228c0d 302 }
663996b3
MS
303 }
304
305 break;
306
307 case OBJECT_ENTRY_ARRAY:
14228c0d
MB
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) {
5eef597e 310 error(offset,
7035cd9e 311 "Invalid object entry array size: %"PRIu64,
5eef597e 312 le64toh(o->object.size));
663996b3 313 return -EBADMSG;
14228c0d 314 }
663996b3 315
14228c0d 316 if (!VALID64(o->entry_array.next_entry_array_offset)) {
5eef597e 317 error(offset,
7035cd9e 318 "Invalid object entry array next_entry_array_offset: "OFSfmt,
5eef597e 319 o->entry_array.next_entry_array_offset);
663996b3 320 return -EBADMSG;
14228c0d 321 }
663996b3
MS
322
323 for (i = 0; i < journal_file_entry_array_n_items(o); i++)
60f067b4
JS
324 if (le64toh(o->entry_array.items[i]) != 0 &&
325 !VALID64(le64toh(o->entry_array.items[i]))) {
5eef597e 326 error(offset,
7035cd9e 327 "Invalid object entry array item (%"PRIu64"/%"PRIu64"): "OFSfmt,
5eef597e
MP
328 i, journal_file_entry_array_n_items(o),
329 le64toh(o->entry_array.items[i]));
663996b3 330 return -EBADMSG;
14228c0d 331 }
663996b3
MS
332
333 break;
334
335 case OBJECT_TAG:
14228c0d 336 if (le64toh(o->object.size) != sizeof(TagObject)) {
5eef597e 337 error(offset,
7035cd9e 338 "Invalid object tag size: %"PRIu64,
5eef597e 339 le64toh(o->object.size));
663996b3 340 return -EBADMSG;
14228c0d 341 }
663996b3 342
14228c0d 343 if (!VALID_EPOCH(o->tag.epoch)) {
5eef597e 344 error(offset,
7035cd9e 345 "Invalid object tag epoch: %"PRIu64,
5eef597e 346 o->tag.epoch);
663996b3 347 return -EBADMSG;
14228c0d 348 }
663996b3
MS
349
350 break;
351 }
352
353 return 0;
354}
355
663996b3
MS
356static 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
368static 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
e735f4d4 383 r = mmap_cache_get(m, fd, PROT_READ|PROT_WRITE, 0, false, c * sizeof(uint64_t), sizeof(uint64_t), NULL, (void **) &z);
663996b3
MS
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
402static 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)) {
7035cd9e 418 error(data_p, "Data object references invalid entry at "OFSfmt, entry_p);
663996b3
MS
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) {
7035cd9e 434 error(entry_p, "Data object at "OFSfmt" not referenced by linked entry", data_p);
663996b3
MS
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
e735f4d4 440 * its consistency. */
663996b3
MS
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
7035cd9e 477 error(entry_p, "Entry object doesn't exist in main entry array");
663996b3
MS
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
488static 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
14228c0d
MB
505 /* Entry array means at least two objects */
506 if (a && n < 2) {
7035cd9e 507 error(p, "Entry array present (entry_array_offset="OFSfmt", but n_entries=%"PRIu64")", a, n);
14228c0d
MB
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);
663996b3
MS
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) {
7035cd9e 527 error(p, "Array chain too short");
663996b3
MS
528 return -EBADMSG;
529 }
530
531 if (!contains_uint64(f->mmap, entry_array_fd, n_entry_arrays, a)) {
7035cd9e 532 error(p, "Invalid array offset "OFSfmt, a);
663996b3
MS
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) {
7035cd9e 542 error(p, "Array chain has cycle (jumps back from "OFSfmt" to "OFSfmt")", a, next);
663996b3
MS
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) {
7035cd9e 551 error(p, "Data object's entry array not sorted");
663996b3
MS
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
572static 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);
7035cd9e
MP
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
663996b3
MS
597 for (i = 0; i < n; i++) {
598 uint64_t last = 0, p;
599
600 if (show_progress)
7035cd9e 601 draw_progress(0xC000 + scale_progress(0x3FFF, i, n), last_usec);
663996b3
MS
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)) {
7035cd9e 609 error(p, "Invalid data object at hash entry %"PRIu64" of %"PRIu64, i, n);
663996b3
MS
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) {
7035cd9e 619 error(p, "Hash chain has a cycle in hash entry %"PRIu64" of %"PRIu64, i, n);
663996b3
MS
620 return -EBADMSG;
621 }
622
623 if (le64toh(o->data.hash) % n != i) {
7035cd9e 624 error(p, "Hash value mismatch in hash entry %"PRIu64" of %"PRIu64, i, n);
663996b3
MS
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)) {
7035cd9e 637 error(p, "Tail hash pointer mismatch in hash table");
663996b3
MS
638 return -EBADMSG;
639 }
640 }
641
642 return 0;
643}
644
645static 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);
7035cd9e
MP
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
663996b3
MS
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
677static 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)) {
7035cd9e
MP
698 error(p, "Invalid data object of entry");
699 return -EBADMSG;
700 }
663996b3
MS
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) {
7035cd9e 707 error(p, "Hash mismatch for data object of entry");
663996b3
MS
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) {
7035cd9e 715 error(p, "Data object missing from hash table");
663996b3
MS
716 return -EBADMSG;
717 }
718 }
719
720 return 0;
721}
722
723static 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)
7035cd9e 747 draw_progress(0x8000 + scale_progress(0x3FFF, i, n), last_usec);
663996b3
MS
748
749 if (a == 0) {
7035cd9e 750 error(a, "Array chain too short at %"PRIu64" of %"PRIu64, i, n);
663996b3
MS
751 return -EBADMSG;
752 }
753
754 if (!contains_uint64(f->mmap, entry_array_fd, n_entry_arrays, a)) {
7035cd9e 755 error(a, "Invalid array %"PRIu64" of %"PRIu64, i, n);
663996b3
MS
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) {
7035cd9e 765 error(a, "Array chain has cycle at %"PRIu64" of %"PRIu64" (jumps back from to "OFSfmt")", i, n, next);
663996b3
MS
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) {
7035cd9e 775 error(a, "Entry array not sorted at %"PRIu64" of %"PRIu64, i, n);
663996b3
MS
776 return -EBADMSG;
777 }
778 last = p;
779
780 if (!contains_uint64(f->mmap, entry_fd, n_entries, p)) {
7035cd9e 781 error(a, "Invalid array entry at %"PRIu64" of %"PRIu64, i, n);
663996b3
MS
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
805int 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;
663996b3 820 unsigned i;
5eef597e 821 bool found_last = false;
663996b3
MS
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
e3bff60a 835 return -EOPNOTSUPP;
663996b3
MS
836#endif
837 } else if (f->seal)
838 return -ENOKEY;
839
60f067b4 840 data_fd = open_tmpfile("/var/tmp", O_RDWR | O_CLOEXEC);
663996b3 841 if (data_fd < 0) {
f47781d8 842 log_error_errno(errno, "Failed to create data file: %m");
663996b3
MS
843 r = -errno;
844 goto fail;
845 }
663996b3 846
60f067b4 847 entry_fd = open_tmpfile("/var/tmp", O_RDWR | O_CLOEXEC);
663996b3 848 if (entry_fd < 0) {
f47781d8 849 log_error_errno(errno, "Failed to create entry file: %m");
663996b3
MS
850 r = -errno;
851 goto fail;
852 }
663996b3 853
60f067b4 854 entry_array_fd = open_tmpfile("/var/tmp", O_RDWR | O_CLOEXEC);
663996b3 855 if (entry_array_fd < 0) {
f47781d8 856 log_error_errno(errno, "Failed to create entry array file: %m");
663996b3
MS
857 r = -errno;
858 goto fail;
859 }
663996b3 860
5eef597e 861 if (le32toh(f->header->compatible_flags) & ~HEADER_COMPATIBLE_SUPPORTED) {
663996b3 862 log_error("Cannot verify file with unknown extensions.");
e3bff60a 863 r = -EOPNOTSUPP;
663996b3
MS
864 goto fail;
865 }
866
867 for (i = 0; i < sizeof(f->header->reserved); i++)
868 if (f->header->reserved[i] != 0) {
7035cd9e 869 error(offsetof(Header, reserved[i]), "Reserved field is non-zero");
663996b3
MS
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);
7035cd9e
MP
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
663996b3 883 if (show_progress)
7035cd9e 884 draw_progress(scale_progress(0x7FFF, p, le64toh(f->header->tail_object_offset)), &last_usec);
663996b3 885
e735f4d4 886 r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &o);
663996b3 887 if (r < 0) {
7035cd9e 888 error(p, "Invalid object");
663996b3
MS
889 goto fail;
890 }
891
892 if (p > le64toh(f->header->tail_object_offset)) {
7035cd9e 893 error(offsetof(Header, tail_object_offset), "Invalid tail object pointer");
663996b3
MS
894 r = -EBADMSG;
895 goto fail;
896 }
897
663996b3
MS
898 n_objects ++;
899
14228c0d 900 r = journal_file_object_verify(f, p, o);
663996b3 901 if (r < 0) {
7035cd9e 902 error(p, "Envalid object contents: %s", strerror(-r));
5eef597e
MP
903 goto fail;
904 }
905
906 if ((o->object.flags & OBJECT_COMPRESSED_XZ) &&
907 (o->object.flags & OBJECT_COMPRESSED_LZ4)) {
7035cd9e 908 error(p, "Objected with double compression");
5eef597e
MP
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;
663996b3
MS
916 goto fail;
917 }
918
5eef597e
MP
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");
663996b3
MS
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) {
7035cd9e 941 error(p, "First entry before first tag");
663996b3
MS
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) {
7035cd9e 951 error(p, "Older entry after newer tag");
663996b3
MS
952 r = -EBADMSG;
953 goto fail;
954 }
955
956 if (!entry_seqnum_set &&
957 le64toh(o->entry.seqnum) != le64toh(f->header->head_entry_seqnum)) {
7035cd9e 958 error(p, "Head entry sequence number incorrect");
663996b3
MS
959 r = -EBADMSG;
960 goto fail;
961 }
962
963 if (entry_seqnum_set &&
964 entry_seqnum >= le64toh(o->entry.seqnum)) {
7035cd9e 965 error(p, "Entry sequence number out of synchronization");
663996b3
MS
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)) {
7035cd9e 976 error(p, "Entry timestamp out of synchronization");
663996b3
MS
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)) {
7035cd9e 987 error(p, "Head entry realtime timestamp incorrect");
663996b3
MS
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) {
7035cd9e 1000 error(p, "More than one data hash table");
663996b3
MS
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)) {
5eef597e 1007 error(p, "header fields for data hash table invalid");
663996b3
MS
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) {
7035cd9e 1017 error(p, "More than one field hash table");
663996b3
MS
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)) {
7035cd9e 1024 error(p, "Header fields for field hash table invalid");
663996b3
MS
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) {
7035cd9e 1039 error(p, "More than one main entry array");
663996b3
MS
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)) {
7035cd9e 1052 error(p, "Tag object in file without sealing");
663996b3
MS
1053 r = -EBADMSG;
1054 goto fail;
1055 }
1056
1057 if (le64toh(o->tag.seqnum) != n_tags + 1) {
7035cd9e 1058 error(p, "Tag sequence number out of synchronization");
663996b3
MS
1059 r = -EBADMSG;
1060 goto fail;
1061 }
1062
1063 if (le64toh(o->tag.epoch) < last_epoch) {
7035cd9e 1064 error(p, "Epoch sequence out of synchronization");
663996b3
MS
1065 r = -EBADMSG;
1066 goto fail;
1067 }
1068
1069#ifdef HAVE_GCRYPT
1070 if (f->seal) {
1071 uint64_t q, rt;
1072
7035cd9e 1073 debug(p, "Checking tag %"PRIu64"...", le64toh(o->tag.seqnum));
663996b3
MS
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) {
5eef597e 1077 error(p, "tag/entry realtime timestamp out of synchronization");
663996b3
MS
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) {
e735f4d4 1103 r = journal_file_move_to_object(f, OBJECT_UNUSED, q, &o);
663996b3
MS
1104 if (r < 0)
1105 goto fail;
1106
e735f4d4 1107 r = journal_file_hmac_put_object(f, OBJECT_UNUSED, o, q);
663996b3
MS
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 */
e735f4d4 1115 r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &o);
663996b3
MS
1116 if (r < 0)
1117 goto fail;
1118
1119 if (memcmp(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH) != 0) {
7035cd9e 1120 error(p, "Tag failed verification");
663996b3
MS
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
7035cd9e
MP
1142 if (p == le64toh(f->header->tail_object_offset)) {
1143 found_last = true;
1144 break;
1145 }
663996b3 1146
7035cd9e
MP
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");
663996b3
MS
1152 r = -EBADMSG;
1153 goto fail;
1154 }
1155
1156 if (n_objects != le64toh(f->header->n_objects)) {
7035cd9e 1157 error(offsetof(Header, n_objects), "Object number mismatch");
663996b3
MS
1158 r = -EBADMSG;
1159 goto fail;
1160 }
1161
1162 if (n_entries != le64toh(f->header->n_entries)) {
7035cd9e 1163 error(offsetof(Header, n_entries), "Entry number mismatch");
663996b3
MS
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)) {
7035cd9e 1170 error(offsetof(Header, n_data), "Data number mismatch");
663996b3
MS
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)) {
7035cd9e 1177 error(offsetof(Header, n_fields), "Field number mismatch");
663996b3
MS
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)) {
7035cd9e 1184 error(offsetof(Header, n_tags), "Tag number mismatch");
663996b3
MS
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)) {
7035cd9e 1191 error(offsetof(Header, n_entry_arrays), "Entry array number mismatch");
663996b3
MS
1192 r = -EBADMSG;
1193 goto fail;
1194 }
1195
7035cd9e
MP
1196 if (!found_main_entry_array && le64toh(f->header->entry_array_offset) != 0) {
1197 error(0, "Missing entry array");
663996b3
MS
1198 r = -EBADMSG;
1199 goto fail;
1200 }
1201
1202 if (entry_seqnum_set &&
1203 entry_seqnum != le64toh(f->header->tail_entry_seqnum)) {
7035cd9e 1204 error(offsetof(Header, tail_entry_seqnum), "Invalid tail seqnum");
663996b3
MS
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))) {
7035cd9e 1212 error(0, "Invalid tail monotonic timestamp");
663996b3
MS
1213 r = -EBADMSG;
1214 goto fail;
1215 }
1216
1217 if (entry_realtime_set && entry_realtime != le64toh(f->header->tail_entry_realtime)) {
7035cd9e 1218 error(0, "Invalid tail realtime timestamp");
663996b3
MS
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
60f067b4
JS
1256 safe_close(data_fd);
1257 safe_close(entry_fd);
1258 safe_close(entry_array_fd);
663996b3
MS
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
1269fail:
1270 if (show_progress)
1271 flush_progress();
1272
14228c0d 1273 log_error("File corruption detected at %s:"OFSfmt" (of %llu bytes, %"PRIu64"%%).",
663996b3 1274 f->path,
14228c0d 1275 p,
663996b3 1276 (unsigned long long) f->last_stat.st_size,
14228c0d 1277 100 * p / f->last_stat.st_size);
663996b3
MS
1278
1279 if (data_fd >= 0) {
1280 mmap_cache_close_fd(f->mmap, data_fd);
60f067b4 1281 safe_close(data_fd);
663996b3
MS
1282 }
1283
1284 if (entry_fd >= 0) {
1285 mmap_cache_close_fd(f->mmap, entry_fd);
60f067b4 1286 safe_close(entry_fd);
663996b3
MS
1287 }
1288
1289 if (entry_array_fd >= 0) {
1290 mmap_cache_close_fd(f->mmap, entry_array_fd);
60f067b4 1291 safe_close(entry_array_fd);
663996b3
MS
1292 }
1293
1294 return r;
1295}