]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/journal-verify.c
journal: add color to verification progress bar
[thirdparty/systemd.git] / src / journal / journal-verify.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2012 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <unistd.h>
23 #include <sys/mman.h>
24 #include <fcntl.h>
25
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"
32 #include "lookup3.h"
33 #include "compress.h"
34
35 /* FIXME:
36 *
37 * - follow all chains
38 * - check for unreferenced objects
39 * - verify FSPRG
40 * - Allow building without libgcrypt
41 *
42 * */
43
44 static int journal_file_object_verify(JournalFile *f, Object *o) {
45 assert(f);
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
52 if ((o->object.flags & OBJECT_COMPRESSED) &&
53 o->object.type != OBJECT_DATA)
54 return -EBADMSG;
55
56 switch (o->object.type) {
57
58 case OBJECT_DATA: {
59 uint64_t h1, h2;
60
61 if (le64toh(o->data.entry_offset) <= 0 ||
62 le64toh(o->data.n_entries) <= 0)
63 return -EBADMSG;
64
65 if (le64toh(o->object.size) - offsetof(DataObject, payload) <= 0)
66 return -EBADMSG;
67
68 h1 = le64toh(o->data.hash);
69
70 if (o->object.flags & OBJECT_COMPRESSED) {
71 void *b = NULL;
72 uint64_t alloc = 0, b_size;
73
74 if (!uncompress_blob(o->data.payload,
75 le64toh(o->object.size) - offsetof(Object, data.payload),
76 &b, &alloc, &b_size))
77 return -EBADMSG;
78
79 h2 = hash64(b, b_size);
80 free(b);
81 } else
82 h2 = hash64(o->data.payload, le64toh(o->object.size) - offsetof(Object, data.payload));
83
84 if (h1 != h2)
85 return -EBADMSG;
86
87 break;
88 }
89
90 case OBJECT_FIELD:
91 if (le64toh(o->object.size) - offsetof(FieldObject, payload) <= 0)
92 return -EBADMSG;
93 break;
94
95 case OBJECT_ENTRY:
96 if ((le64toh(o->object.size) - offsetof(EntryObject, items)) % sizeof(EntryItem) != 0)
97 return -EBADMSG;
98
99 if ((le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem) <= 0)
100 return -EBADMSG;
101
102 if (le64toh(o->entry.seqnum) <= 0 ||
103 le64toh(o->entry.realtime) <= 0)
104 return -EBADMSG;
105
106 break;
107
108 case OBJECT_DATA_HASH_TABLE:
109 case OBJECT_FIELD_HASH_TABLE:
110 if ((le64toh(o->object.size) - offsetof(HashTableObject, items)) % sizeof(HashItem) != 0)
111 return -EBADMSG;
112
113 break;
114
115 case OBJECT_ENTRY_ARRAY:
116 if ((le64toh(o->object.size) - offsetof(EntryArrayObject, items)) % sizeof(le64_t) != 0)
117 return -EBADMSG;
118
119 break;
120
121 case OBJECT_TAG:
122 if (le64toh(o->object.size) != sizeof(TagObject))
123 return -EBADMSG;
124 break;
125 }
126
127 return 0;
128 }
129
130 static void draw_progress(uint64_t p, usec_t *last_usec) {
131 unsigned n, i, j, k;
132 usec_t z, x;
133
134 if (!isatty(STDOUT_FILENO))
135 return;
136
137 z = now(CLOCK_MONOTONIC);
138 x = *last_usec;
139
140 if (x != 0 && x + 40 * USEC_PER_MSEC > z)
141 return;
142
143 *last_usec = z;
144
145 n = (3 * columns()) / 4;
146 j = (n * (unsigned) p) / 65535ULL;
147 k = n - j;
148
149 fputs("\r\x1B[?25l" ANSI_HIGHLIGHT_GREEN_ON, stdout);
150
151 for (i = 0; i < j; i++)
152 fputs("\xe2\x96\x88", stdout);
153
154 fputs(ANSI_HIGHLIGHT_OFF, stdout);
155
156 for (i = 0; i < k; i++)
157 fputs("\xe2\x96\x91", stdout);
158
159 printf(" %3lu%%", 100LU * (unsigned long) p / 65535LU);
160
161 fputs("\r\x1B[?25h", stdout);
162 fflush(stdout);
163 }
164
165 static void flush_progress(void) {
166 unsigned n, i;
167
168 if (!isatty(STDOUT_FILENO))
169 return;
170
171 n = (3 * columns()) / 4;
172
173 putchar('\r');
174
175 for (i = 0; i < n + 5; i++)
176 putchar(' ');
177
178 putchar('\r');
179 fflush(stdout);
180 }
181
182 static int write_uint64(int fd, uint64_t p) {
183 ssize_t k;
184
185 k = write(fd, &p, sizeof(p));
186 if (k < 0)
187 return -errno;
188 if (k != sizeof(p))
189 return -EIO;
190
191 return 0;
192 }
193
194 static int contains_uint64(MMapCache *m, int fd, uint64_t n, uint64_t p) {
195 uint64_t a, b;
196 int r;
197
198 assert(m);
199 assert(fd >= 0);
200
201 /* Bisection ... */
202
203 a = 0; b = n;
204 while (a < b) {
205 uint64_t c, *z;
206
207 c = (a + b) / 2;
208
209 r = mmap_cache_get(m, fd, PROT_READ, 0, c * sizeof(uint64_t), sizeof(uint64_t), (void **) &z);
210 if (r < 0)
211 return r;
212
213 if (*z == p)
214 return 1;
215
216 if (p < *z)
217 b = c;
218 else
219 a = c;
220 }
221
222 return 0;
223 }
224
225 int journal_file_verify(JournalFile *f, const char *key) {
226 int r;
227 Object *o;
228 uint64_t p = 0;
229 uint64_t tag_seqnum = 0, entry_seqnum = 0, entry_monotonic = 0, entry_realtime = 0;
230 sd_id128_t entry_boot_id;
231 bool entry_seqnum_set = false, entry_monotonic_set = false, entry_realtime_set = false, found_main_entry_array = false;
232 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;
233 usec_t last_usec = 0;
234 int data_fd = -1, entry_fd = -1, entry_array_fd = -1;
235 char data_path[] = "/var/tmp/journal-data-XXXXXX",
236 entry_path[] = "/var/tmp/journal-entry-XXXXXX",
237 entry_array_path[] = "/var/tmp/journal-entry-array-XXXXXX";
238
239 assert(f);
240
241 data_fd = mkostemp(data_path, O_CLOEXEC);
242 if (data_fd < 0) {
243 log_error("Failed to create data file: %m");
244 goto fail;
245 }
246 unlink(data_path);
247
248 entry_fd = mkostemp(entry_path, O_CLOEXEC);
249 if (entry_fd < 0) {
250 log_error("Failed to create entry file: %m");
251 goto fail;
252 }
253 unlink(entry_path);
254
255 entry_array_fd = mkostemp(entry_array_path, O_CLOEXEC);
256 if (entry_array_fd < 0) {
257 log_error("Failed to create entry array file: %m");
258 goto fail;
259 }
260 unlink(entry_array_path);
261
262 /* First iteration: we go through all objects, verify the
263 * superficial structure, headers, hashes. */
264
265 r = journal_file_hmac_put_header(f);
266 if (r < 0) {
267 log_error("Failed to calculate HMAC of header.");
268 goto fail;
269 }
270
271 p = le64toh(f->header->header_size);
272 while (p != 0) {
273 draw_progress((0x7FFF * p) / le64toh(f->header->tail_object_offset), &last_usec);
274
275 r = journal_file_move_to_object(f, -1, p, &o);
276 if (r < 0) {
277 log_error("Invalid object at %llu", (unsigned long long) p);
278 goto fail;
279 }
280
281 if (le64toh(f->header->tail_object_offset) < p) {
282 log_error("Invalid tail object pointer.");
283 r = -EBADMSG;
284 goto fail;
285 }
286
287 n_objects ++;
288
289 r = journal_file_object_verify(f, o);
290 if (r < 0) {
291 log_error("Invalid object contents at %llu", (unsigned long long) p);
292 goto fail;
293 }
294
295 if (o->object.flags & OBJECT_COMPRESSED &&
296 !(le32toh(f->header->incompatible_flags) & HEADER_INCOMPATIBLE_COMPRESSED)) {
297 log_error("Compressed object without compression at %llu", (unsigned long long) p);
298 r = -EBADMSG;
299 goto fail;
300 }
301
302 r = journal_file_hmac_put_object(f, -1, p);
303 if (r < 0) {
304 log_error("Failed to calculate HMAC at %llu", (unsigned long long) p);
305 goto fail;
306 }
307
308 if (o->object.type == OBJECT_TAG) {
309
310 if (!(le32toh(f->header->compatible_flags) & HEADER_COMPATIBLE_AUTHENTICATED)) {
311 log_error("Tag object without authentication at %llu", (unsigned long long) p);
312 r = -EBADMSG;
313 goto fail;
314 }
315
316 if (le64toh(o->tag.seqnum) != tag_seqnum) {
317 log_error("Tag sequence number out of synchronization at %llu", (unsigned long long) p);
318 r = -EBADMSG;
319 goto fail;
320 }
321
322 } else if (o->object.type == OBJECT_ENTRY) {
323
324 r = write_uint64(entry_fd, p);
325 if (r < 0)
326 goto fail;
327
328 if (!entry_seqnum_set &&
329 le64toh(o->entry.seqnum) != le64toh(f->header->head_entry_seqnum)) {
330 log_error("Head entry sequence number incorrect");
331 r = -EBADMSG;
332 goto fail;
333 }
334
335 if (entry_seqnum_set &&
336 entry_seqnum >= le64toh(o->entry.seqnum)) {
337 log_error("Entry sequence number out of synchronization at %llu", (unsigned long long) p);
338 r = -EBADMSG;
339 goto fail;
340 }
341
342 entry_seqnum = le64toh(o->entry.seqnum);
343 entry_seqnum_set = true;
344
345 if (entry_monotonic_set &&
346 sd_id128_equal(entry_boot_id, o->entry.boot_id) &&
347 entry_monotonic > le64toh(o->entry.monotonic)) {
348 log_error("Entry timestamp out of synchronization at %llu", (unsigned long long) p);
349 r = -EBADMSG;
350 goto fail;
351 }
352
353 entry_monotonic = le64toh(o->entry.monotonic);
354 entry_boot_id = o->entry.boot_id;
355 entry_monotonic_set = true;
356
357 if (!entry_realtime_set &&
358 le64toh(o->entry.realtime) != le64toh(f->header->head_entry_realtime)) {
359 log_error("Head entry realtime timestamp incorrect");
360 r = -EBADMSG;
361 goto fail;
362 }
363
364 entry_realtime = le64toh(o->entry.realtime);
365 entry_realtime_set = true;
366
367 n_entries ++;
368 } else if (o->object.type == OBJECT_ENTRY_ARRAY) {
369
370 r = write_uint64(entry_array_fd, p);
371 if (r < 0)
372 goto fail;
373
374 if (p == le64toh(f->header->entry_array_offset)) {
375 if (found_main_entry_array) {
376 log_error("More than one main entry array at %llu", (unsigned long long) p);
377 r = -EBADMSG;
378 goto fail;
379 }
380
381 found_main_entry_array = true;
382 }
383
384 n_entry_arrays++;
385
386 } else if (o->object.type == OBJECT_DATA) {
387
388 r = write_uint64(data_fd, p);
389 if (r < 0)
390 goto fail;
391
392 n_data++;
393
394 } else if (o->object.type == OBJECT_FIELD)
395 n_fields++;
396 else if (o->object.type == OBJECT_DATA_HASH_TABLE) {
397 n_data_hash_tables++;
398
399 if (n_data_hash_tables > 1) {
400 log_error("More than one data hash table at %llu", (unsigned long long) p);
401 r = -EBADMSG;
402 goto fail;
403 }
404
405 if (le64toh(f->header->data_hash_table_offset) != p + offsetof(HashTableObject, items) ||
406 le64toh(f->header->data_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) {
407 log_error("Header fields for data hash table invalid.");
408 r = -EBADMSG;
409 goto fail;
410 }
411 } else if (o->object.type == OBJECT_FIELD_HASH_TABLE) {
412 n_field_hash_tables++;
413
414 if (n_field_hash_tables > 1) {
415 log_error("More than one field hash table at %llu", (unsigned long long) p);
416 r = -EBADMSG;
417 goto fail;
418 }
419
420 if (le64toh(f->header->field_hash_table_offset) != p + offsetof(HashTableObject, items) ||
421 le64toh(f->header->field_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) {
422 log_error("Header fields for field hash table invalid.");
423 r = -EBADMSG;
424 goto fail;
425 }
426 } else if (o->object.type >= _OBJECT_TYPE_MAX)
427 n_weird ++;
428
429 if (p == le64toh(f->header->tail_object_offset))
430 p = 0;
431 else
432 p = p + ALIGN64(le64toh(o->object.size));
433 }
434
435 if (n_objects != le64toh(f->header->n_objects)) {
436 log_error("Object number mismatch");
437 r = -EBADMSG;
438 goto fail;
439 }
440
441 if (n_entries != le64toh(f->header->n_entries)) {
442 log_error("Entry number mismatch");
443 r = -EBADMSG;
444 goto fail;
445 }
446
447 if (JOURNAL_HEADER_CONTAINS(f->header, n_data) &&
448 n_data != le64toh(f->header->n_data)) {
449 log_error("Data number mismatch");
450 r = -EBADMSG;
451 goto fail;
452 }
453
454 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields) &&
455 n_fields != le64toh(f->header->n_fields)) {
456 log_error("Field number mismatch");
457 r = -EBADMSG;
458 goto fail;
459 }
460
461 if (JOURNAL_HEADER_CONTAINS(f->header, n_tags) &&
462 tag_seqnum != le64toh(f->header->n_tags)) {
463 log_error("Tag number mismatch");
464 r = -EBADMSG;
465 goto fail;
466 }
467
468 if (n_data_hash_tables != 1) {
469 log_error("Missing data hash table");
470 r = -EBADMSG;
471 goto fail;
472 }
473
474 if (n_field_hash_tables != 1) {
475 log_error("Missing field hash table");
476 r = -EBADMSG;
477 goto fail;
478 }
479
480 if (!found_main_entry_array) {
481 log_error("Missing entry array");
482 r = -EBADMSG;
483 goto fail;
484 }
485
486 if (entry_seqnum_set &&
487 entry_seqnum != le64toh(f->header->tail_entry_seqnum)) {
488 log_error("Invalid tail seqnum");
489 r = -EBADMSG;
490 goto fail;
491 }
492
493 if (entry_monotonic_set &&
494 (!sd_id128_equal(entry_boot_id, f->header->boot_id) ||
495 entry_monotonic != le64toh(f->header->tail_entry_monotonic))) {
496 log_error("Invalid tail monotonic timestamp");
497 r = -EBADMSG;
498 goto fail;
499 }
500
501 if (entry_realtime_set && entry_realtime != le64toh(f->header->tail_entry_realtime)) {
502 log_error("Invalid tail realtime timestamp");
503 r = -EBADMSG;
504 goto fail;
505 }
506
507 /* Second iteration: we go through all objects again, this
508 * time verify all pointers. */
509
510 p = le64toh(f->header->header_size);
511 while (p != 0) {
512 draw_progress(0x8000 + (0x7FFF * p) / le64toh(f->header->tail_object_offset), &last_usec);
513
514 r = journal_file_move_to_object(f, -1, p, &o);
515 if (r < 0) {
516 log_error("Invalid object at %llu", (unsigned long long) p);
517 goto fail;
518 }
519
520 if (o->object.type == OBJECT_ENTRY_ARRAY) {
521 uint64_t i = 0, n;
522
523 if (le64toh(o->entry_array.next_entry_array_offset) != 0 &&
524 !contains_uint64(f->mmap, entry_array_fd, n_entry_arrays, le64toh(o->entry_array.next_entry_array_offset))) {
525 log_error("Entry array chains up to invalid next array at %llu", (unsigned long long) p);
526 r = -EBADMSG;
527 goto fail;
528 }
529
530 n = journal_file_entry_array_n_items(o);
531 for (i = 0; i < n; i++) {
532 if (le64toh(o->entry_array.items[i]) != 0 &&
533 !contains_uint64(f->mmap, entry_fd, n_entries, le64toh(o->entry_array.items[i]))) {
534
535 log_error("Entry array points to invalid next array at %llu", (unsigned long long) p);
536 r = -EBADMSG;
537 goto fail;
538 }
539 }
540
541 }
542
543 r = journal_file_move_to_object(f, -1, p, &o);
544 if (r < 0) {
545 log_error("Invalid object at %llu", (unsigned long long) p);
546 goto fail;
547 }
548
549 if (p == le64toh(f->header->tail_object_offset))
550 p = 0;
551 else
552 p = p + ALIGN64(le64toh(o->object.size));
553 }
554
555 flush_progress();
556
557 mmap_cache_close_fd(f->mmap, data_fd);
558 mmap_cache_close_fd(f->mmap, entry_fd);
559 mmap_cache_close_fd(f->mmap, entry_array_fd);
560
561 close_nointr_nofail(data_fd);
562 close_nointr_nofail(entry_fd);
563 close_nointr_nofail(entry_array_fd);
564
565 return 0;
566
567 fail:
568 flush_progress();
569
570 log_error("File corruption detected at %s:%llu (of %llu, %llu%%).",
571 f->path,
572 (unsigned long long) p,
573 (unsigned long long) f->last_stat.st_size,
574 (unsigned long long) (100 * p / f->last_stat.st_size));
575
576 if (data_fd >= 0) {
577 mmap_cache_close_fd(f->mmap, data_fd);
578 close_nointr_nofail(data_fd);
579 }
580
581 if (entry_fd >= 0) {
582 mmap_cache_close_fd(f->mmap, entry_fd);
583 close_nointr_nofail(entry_fd);
584 }
585
586 if (entry_array_fd >= 0) {
587 mmap_cache_close_fd(f->mmap, entry_array_fd);
588 close_nointr_nofail(entry_array_fd);
589 }
590
591 return r;
592 }