chiark / gitweb /
1a9a73092e4e21404012c6812131af78bdcd1f30
[elogind.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 }