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