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