]>
Commit | Line | Data |
---|---|---|
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 | |
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 | ||
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 | ||
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 | ||
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 | ||
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 | } |