chiark / gitweb /
journal: verify compressed objects
[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", stdout);
150
151         for (i = 0; i < j; i++)
152                 fputs("\xe2\x96\x88", stdout);
153
154         for (i = 0; i < k; i++)
155                 fputs("\xe2\x96\x91", stdout);
156
157         printf(" %3lu%%", 100LU * (unsigned long) p / 65535LU);
158
159         fputs("\r\x1B[?25h", stdout);
160         fflush(stdout);
161 }
162
163 static void flush_progress(void) {
164         unsigned n, i;
165
166         if (!isatty(STDOUT_FILENO))
167                 return;
168
169         n = (3 * columns()) / 4;
170
171         putchar('\r');
172
173         for (i = 0; i < n + 5; i++)
174                 putchar(' ');
175
176         putchar('\r');
177         fflush(stdout);
178 }
179
180 static int write_uint64(int fd, uint64_t p) {
181         ssize_t k;
182
183         k = write(fd, &p, sizeof(p));
184         if (k < 0)
185                 return -errno;
186         if (k != sizeof(p))
187                 return -EIO;
188
189         return 0;
190 }
191
192 static int contains_uint64(MMapCache *m, int fd, uint64_t n, uint64_t p) {
193         uint64_t a, b;
194         int r;
195
196         assert(m);
197         assert(fd >= 0);
198
199         /* Bisection ... */
200
201         a = 0; b = n;
202         while (a < b) {
203                 uint64_t c, *z;
204
205                 c = (a + b) / 2;
206
207                 r = mmap_cache_get(m, fd, PROT_READ, 0, c * sizeof(uint64_t), sizeof(uint64_t), (void **) &z);
208                 if (r < 0)
209                         return r;
210
211                 if (*z == p)
212                         return 1;
213
214                 if (p < *z)
215                         b = c;
216                 else
217                         a = c;
218         }
219
220         return 0;
221 }
222
223 int journal_file_verify(JournalFile *f, const char *key) {
224         int r;
225         Object *o;
226         uint64_t p = 0;
227         uint64_t tag_seqnum = 0, entry_seqnum = 0, entry_monotonic = 0, entry_realtime = 0;
228         sd_id128_t entry_boot_id;
229         bool entry_seqnum_set = false, entry_monotonic_set = false, entry_realtime_set = false, found_main_entry_array = false;
230         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;
231         usec_t last_usec = 0;
232         int data_fd = -1, entry_fd = -1, entry_array_fd = -1;
233         char data_path[] = "/var/tmp/journal-data-XXXXXX",
234                 entry_path[] = "/var/tmp/journal-entry-XXXXXX",
235                 entry_array_path[] = "/var/tmp/journal-entry-array-XXXXXX";
236
237         assert(f);
238
239         data_fd = mkostemp(data_path, O_CLOEXEC);
240         if (data_fd < 0) {
241                 log_error("Failed to create data file: %m");
242                 goto fail;
243         }
244         unlink(data_path);
245
246         entry_fd = mkostemp(entry_path, O_CLOEXEC);
247         if (entry_fd < 0) {
248                 log_error("Failed to create entry file: %m");
249                 goto fail;
250         }
251         unlink(entry_path);
252
253         entry_array_fd = mkostemp(entry_array_path, O_CLOEXEC);
254         if (entry_array_fd < 0) {
255                 log_error("Failed to create entry array file: %m");
256                 goto fail;
257         }
258         unlink(entry_array_path);
259
260         /* First iteration: we go through all objects, verify the
261          * superficial structure, headers, hashes. */
262
263         r = journal_file_hmac_put_header(f);
264         if (r < 0) {
265                 log_error("Failed to calculate HMAC of header.");
266                 goto fail;
267         }
268
269         p = le64toh(f->header->header_size);
270         while (p != 0) {
271                 draw_progress((0x7FFF * p) / le64toh(f->header->tail_object_offset), &last_usec);
272
273                 r = journal_file_move_to_object(f, -1, p, &o);
274                 if (r < 0) {
275                         log_error("Invalid object at %llu", (unsigned long long) p);
276                         goto fail;
277                 }
278
279                 if (le64toh(f->header->tail_object_offset) < p) {
280                         log_error("Invalid tail object pointer.");
281                         r = -EBADMSG;
282                         goto fail;
283                 }
284
285                 n_objects ++;
286
287                 r = journal_file_object_verify(f, o);
288                 if (r < 0) {
289                         log_error("Invalid object contents at %llu", (unsigned long long) p);
290                         goto fail;
291                 }
292
293                 if (o->object.flags & OBJECT_COMPRESSED &&
294                     !(le32toh(f->header->incompatible_flags) & HEADER_INCOMPATIBLE_COMPRESSED)) {
295                         log_error("Compressed object without compression at %llu", (unsigned long long) p);
296                         r = -EBADMSG;
297                         goto fail;
298                 }
299
300                 r = journal_file_hmac_put_object(f, -1, p);
301                 if (r < 0) {
302                         log_error("Failed to calculate HMAC at %llu", (unsigned long long) p);
303                         goto fail;
304                 }
305
306                 if (o->object.type == OBJECT_TAG) {
307
308                         if (!(le32toh(f->header->compatible_flags) & HEADER_COMPATIBLE_AUTHENTICATED)) {
309                                 log_error("Tag object without authentication at %llu", (unsigned long long) p);
310                                 r = -EBADMSG;
311                                 goto fail;
312                         }
313
314                         if (le64toh(o->tag.seqnum) != tag_seqnum) {
315                                 log_error("Tag sequence number out of synchronization at %llu", (unsigned long long) p);
316                                 r = -EBADMSG;
317                                 goto fail;
318                         }
319
320                 } else if (o->object.type == OBJECT_ENTRY) {
321
322                         r = write_uint64(entry_fd, p);
323                         if (r < 0)
324                                 goto fail;
325
326                         if (!entry_seqnum_set &&
327                             le64toh(o->entry.seqnum) != le64toh(f->header->head_entry_seqnum)) {
328                                 log_error("Head entry sequence number incorrect");
329                                 r = -EBADMSG;
330                                 goto fail;
331                         }
332
333                         if (entry_seqnum_set &&
334                             entry_seqnum >= le64toh(o->entry.seqnum)) {
335                                 log_error("Entry sequence number out of synchronization at %llu", (unsigned long long) p);
336                                 r = -EBADMSG;
337                                 goto fail;
338                         }
339
340                         entry_seqnum = le64toh(o->entry.seqnum);
341                         entry_seqnum_set = true;
342
343                         if (entry_monotonic_set &&
344                             sd_id128_equal(entry_boot_id, o->entry.boot_id) &&
345                             entry_monotonic > le64toh(o->entry.monotonic)) {
346                                 log_error("Entry timestamp out of synchronization at %llu", (unsigned long long) p);
347                                 r = -EBADMSG;
348                                 goto fail;
349                         }
350
351                         entry_monotonic = le64toh(o->entry.monotonic);
352                         entry_boot_id = o->entry.boot_id;
353                         entry_monotonic_set = true;
354
355                         if (!entry_realtime_set &&
356                             le64toh(o->entry.realtime) != le64toh(f->header->head_entry_realtime)) {
357                                 log_error("Head entry realtime timestamp incorrect");
358                                 r = -EBADMSG;
359                                 goto fail;
360                         }
361
362                         entry_realtime = le64toh(o->entry.realtime);
363                         entry_realtime_set = true;
364
365                         n_entries ++;
366                 } else if (o->object.type == OBJECT_ENTRY_ARRAY) {
367
368                         r = write_uint64(entry_array_fd, p);
369                         if (r < 0)
370                                 goto fail;
371
372                         if (p == le64toh(f->header->entry_array_offset)) {
373                                 if (found_main_entry_array) {
374                                         log_error("More than one main entry array at %llu", (unsigned long long) p);
375                                         r = -EBADMSG;
376                                         goto fail;
377                                 }
378
379                                 found_main_entry_array = true;
380                         }
381
382                         n_entry_arrays++;
383
384                 } else if (o->object.type == OBJECT_DATA) {
385
386                         r = write_uint64(data_fd, p);
387                         if (r < 0)
388                                 goto fail;
389
390                         n_data++;
391
392                 } else if (o->object.type == OBJECT_FIELD)
393                         n_fields++;
394                 else if (o->object.type == OBJECT_DATA_HASH_TABLE) {
395                         n_data_hash_tables++;
396
397                         if (n_data_hash_tables > 1) {
398                                 log_error("More than one data hash table at %llu", (unsigned long long) p);
399                                 r = -EBADMSG;
400                                 goto fail;
401                         }
402
403                         if (le64toh(f->header->data_hash_table_offset) != p + offsetof(HashTableObject, items) ||
404                             le64toh(f->header->data_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) {
405                                 log_error("Header fields for data hash table invalid.");
406                                 r = -EBADMSG;
407                                 goto fail;
408                         }
409                 } else if (o->object.type == OBJECT_FIELD_HASH_TABLE) {
410                         n_field_hash_tables++;
411
412                         if (n_field_hash_tables > 1) {
413                                 log_error("More than one field hash table at %llu", (unsigned long long) p);
414                                 r = -EBADMSG;
415                                 goto fail;
416                         }
417
418                         if (le64toh(f->header->field_hash_table_offset) != p + offsetof(HashTableObject, items) ||
419                             le64toh(f->header->field_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) {
420                                 log_error("Header fields for field hash table invalid.");
421                                 r = -EBADMSG;
422                                 goto fail;
423                         }
424                 } else if (o->object.type >= _OBJECT_TYPE_MAX)
425                         n_weird ++;
426
427                 if (p == le64toh(f->header->tail_object_offset))
428                         p = 0;
429                 else
430                         p = p + ALIGN64(le64toh(o->object.size));
431         }
432
433         if (n_objects != le64toh(f->header->n_objects)) {
434                 log_error("Object number mismatch");
435                 r = -EBADMSG;
436                 goto fail;
437         }
438
439         if (n_entries != le64toh(f->header->n_entries)) {
440                 log_error("Entry number mismatch");
441                 r = -EBADMSG;
442                 goto fail;
443         }
444
445         if (JOURNAL_HEADER_CONTAINS(f->header, n_data) &&
446             n_data != le64toh(f->header->n_data)) {
447                 log_error("Data number mismatch");
448                 r = -EBADMSG;
449                 goto fail;
450         }
451
452         if (JOURNAL_HEADER_CONTAINS(f->header, n_fields) &&
453             n_fields != le64toh(f->header->n_fields)) {
454                 log_error("Field number mismatch");
455                 r = -EBADMSG;
456                 goto fail;
457         }
458
459         if (JOURNAL_HEADER_CONTAINS(f->header, n_tags) &&
460             tag_seqnum != le64toh(f->header->n_tags)) {
461                 log_error("Tag number mismatch");
462                 r = -EBADMSG;
463                 goto fail;
464         }
465
466         if (n_data_hash_tables != 1) {
467                 log_error("Missing data hash table");
468                 r = -EBADMSG;
469                 goto fail;
470         }
471
472         if (n_field_hash_tables != 1) {
473                 log_error("Missing field hash table");
474                 r = -EBADMSG;
475                 goto fail;
476         }
477
478         if (!found_main_entry_array) {
479                 log_error("Missing entry array");
480                 r = -EBADMSG;
481                 goto fail;
482         }
483
484         if (entry_seqnum_set &&
485             entry_seqnum != le64toh(f->header->tail_entry_seqnum)) {
486                 log_error("Invalid tail seqnum");
487                 r = -EBADMSG;
488                 goto fail;
489         }
490
491         if (entry_monotonic_set &&
492             (!sd_id128_equal(entry_boot_id, f->header->boot_id) ||
493              entry_monotonic != le64toh(f->header->tail_entry_monotonic))) {
494                 log_error("Invalid tail monotonic timestamp");
495                 r = -EBADMSG;
496                 goto fail;
497         }
498
499         if (entry_realtime_set && entry_realtime != le64toh(f->header->tail_entry_realtime)) {
500                 log_error("Invalid tail realtime timestamp");
501                 r = -EBADMSG;
502                 goto fail;
503         }
504
505         /* Second iteration: we go through all objects again, this
506          * time verify all pointers. */
507
508         p = le64toh(f->header->header_size);
509         while (p != 0) {
510                 draw_progress(0x8000 + (0x7FFF * p) / le64toh(f->header->tail_object_offset), &last_usec);
511
512                 r = journal_file_move_to_object(f, -1, p, &o);
513                 if (r < 0) {
514                         log_error("Invalid object at %llu", (unsigned long long) p);
515                         goto fail;
516                 }
517
518                 if (o->object.type == OBJECT_ENTRY_ARRAY) {
519                         uint64_t i = 0, n;
520
521                         if (le64toh(o->entry_array.next_entry_array_offset) != 0 &&
522                             !contains_uint64(f->mmap, entry_array_fd, n_entry_arrays, le64toh(o->entry_array.next_entry_array_offset))) {
523                                 log_error("Entry array chains up to invalid next array at %llu", (unsigned long long) p);
524                                 r = -EBADMSG;
525                                 goto fail;
526                         }
527
528                         n = journal_file_entry_array_n_items(o);
529                         for (i = 0; i < n; i++) {
530                                 if (le64toh(o->entry_array.items[i]) != 0 &&
531                                     !contains_uint64(f->mmap, entry_fd, n_entries, le64toh(o->entry_array.items[i]))) {
532
533                                         log_error("Entry array points to invalid next array at %llu", (unsigned long long) p);
534                                         r = -EBADMSG;
535                                         goto fail;
536                                 }
537                         }
538
539                 }
540
541                 r = journal_file_move_to_object(f, -1, p, &o);
542                 if (r < 0) {
543                         log_error("Invalid object at %llu", (unsigned long long) p);
544                         goto fail;
545                 }
546
547                 if (p == le64toh(f->header->tail_object_offset))
548                         p = 0;
549                 else
550                         p = p + ALIGN64(le64toh(o->object.size));
551         }
552
553         flush_progress();
554
555         mmap_cache_close_fd(f->mmap, data_fd);
556         mmap_cache_close_fd(f->mmap, entry_fd);
557         mmap_cache_close_fd(f->mmap, entry_array_fd);
558
559         close_nointr_nofail(data_fd);
560         close_nointr_nofail(entry_fd);
561         close_nointr_nofail(entry_array_fd);
562
563         return 0;
564
565 fail:
566         flush_progress();
567
568         log_error("File corruption detected at %s:%llu (of %llu, %llu%%).",
569                   f->path,
570                   (unsigned long long) p,
571                   (unsigned long long) f->last_stat.st_size,
572                   (unsigned long long) (100 * p / f->last_stat.st_size));
573
574         if (data_fd >= 0) {
575                 mmap_cache_close_fd(f->mmap, data_fd);
576                 close_nointr_nofail(data_fd);
577         }
578
579         if (entry_fd >= 0) {
580                 mmap_cache_close_fd(f->mmap, entry_fd);
581                 close_nointr_nofail(entry_fd);
582         }
583
584         if (entry_array_fd >= 0) {
585                 mmap_cache_close_fd(f->mmap, entry_array_fd);
586                 close_nointr_nofail(entry_array_fd);
587         }
588
589         return r;
590 }