]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/journal-verify.c
journalctl: add --verify-seed= switch to specify seed value
[thirdparty/systemd.git] / src / journal / journal-verify.c
CommitLineData
0284adc6
LP
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"
f59a5f6b
LP
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 * */
0284adc6
LP
42
43static 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
f59a5f6b
LP
51 if ((o->object.flags & OBJECT_COMPRESSED) &&
52 o->object.type != OBJECT_DATA)
53 return -EBADMSG;
54
0284adc6 55 switch (o->object.type) {
f59a5f6b 56
0284adc6
LP
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;
f59a5f6b
LP
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
0284adc6
LP
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
117static 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
150static 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
167static 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
179static 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
210int 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
0284adc6
LP
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
f59a5f6b
LP
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);
0284adc6
LP
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
552fail:
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}