]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
0284adc6 | 2 | |
0284adc6 | 3 | #include <fcntl.h> |
feb12d3e | 4 | #include <stddef.h> |
cf0fbc49 TA |
5 | #include <sys/mman.h> |
6 | #include <unistd.h> | |
0284adc6 | 7 | |
b5efdb8a | 8 | #include "alloc-util.h" |
3ffd4af2 LP |
9 | #include "compress.h" |
10 | #include "fd-util.h" | |
0d39fa9c | 11 | #include "fileio.h" |
34a8f081 | 12 | #include "fs-util.h" |
3ffd4af2 | 13 | #include "journal-authenticate.h" |
0284adc6 LP |
14 | #include "journal-def.h" |
15 | #include "journal-file.h" | |
0284adc6 | 16 | #include "journal-verify.h" |
f59a5f6b | 17 | #include "lookup3.h" |
3ffd4af2 | 18 | #include "macro.h" |
288a74cc | 19 | #include "terminal-util.h" |
e4de7287 | 20 | #include "tmpfile-util.h" |
f59a5f6b | 21 | |
54f3ff07 ZJS |
22 | static void draw_progress(uint64_t p, usec_t *last_usec) { |
23 | unsigned n, i, j, k; | |
24 | usec_t z, x; | |
25 | ||
26 | if (!on_tty()) | |
27 | return; | |
28 | ||
29 | z = now(CLOCK_MONOTONIC); | |
30 | x = *last_usec; | |
31 | ||
32 | if (x != 0 && x + 40 * USEC_PER_MSEC > z) | |
33 | return; | |
34 | ||
35 | *last_usec = z; | |
36 | ||
37 | n = (3 * columns()) / 4; | |
38 | j = (n * (unsigned) p) / 65535ULL; | |
39 | k = n - j; | |
40 | ||
7565bb98 LP |
41 | fputs("\r", stdout); |
42 | if (colors_enabled()) | |
25e4608b MGR |
43 | fputs("\x1B[?25l", stdout); |
44 | ||
45 | fputs(ansi_highlight_green(), stdout); | |
54f3ff07 ZJS |
46 | |
47 | for (i = 0; i < j; i++) | |
48 | fputs("\xe2\x96\x88", stdout); | |
49 | ||
af1060e1 | 50 | fputs(ansi_normal(), stdout); |
54f3ff07 ZJS |
51 | |
52 | for (i = 0; i < k; i++) | |
53 | fputs("\xe2\x96\x91", stdout); | |
54 | ||
55 | printf(" %3"PRIu64"%%", 100U * p / 65535U); | |
56 | ||
7565bb98 LP |
57 | fputs("\r", stdout); |
58 | if (colors_enabled()) | |
59 | fputs("\x1B[?25h", stdout); | |
60 | ||
54f3ff07 ZJS |
61 | fflush(stdout); |
62 | } | |
63 | ||
45c047b2 | 64 | static uint64_t scale_progress(uint64_t scale, uint64_t p, uint64_t m) { |
0a587335 ZJS |
65 | /* Calculates scale * p / m, but handles m == 0 safely, and saturates. |
66 | * Currently all callers use m >= 1, but we keep the check to be defensive. | |
67 | */ | |
45c047b2 | 68 | |
f37508d5 | 69 | if (p >= m || m == 0) |
45c047b2 LP |
70 | return scale; |
71 | ||
72 | return scale * p / m; | |
73 | } | |
74 | ||
54f3ff07 ZJS |
75 | static void flush_progress(void) { |
76 | unsigned n, i; | |
77 | ||
78 | if (!on_tty()) | |
79 | return; | |
80 | ||
81 | n = (3 * columns()) / 4; | |
82 | ||
83 | putchar('\r'); | |
84 | ||
85 | for (i = 0; i < n + 5; i++) | |
86 | putchar(' '); | |
87 | ||
88 | putchar('\r'); | |
89 | fflush(stdout); | |
90 | } | |
91 | ||
9ed794a3 | 92 | #define debug(_offset, _fmt, ...) do { \ |
54f3ff07 ZJS |
93 | flush_progress(); \ |
94 | log_debug(OFSfmt": " _fmt, _offset, ##__VA_ARGS__); \ | |
9ed794a3 | 95 | } while (0) |
54f3ff07 | 96 | |
9ed794a3 | 97 | #define warning(_offset, _fmt, ...) do { \ |
54f3ff07 ZJS |
98 | flush_progress(); \ |
99 | log_warning(OFSfmt": " _fmt, _offset, ##__VA_ARGS__); \ | |
9ed794a3 | 100 | } while (0) |
54f3ff07 | 101 | |
9ed794a3 | 102 | #define error(_offset, _fmt, ...) do { \ |
54f3ff07 ZJS |
103 | flush_progress(); \ |
104 | log_error(OFSfmt": " _fmt, (uint64_t)_offset, ##__VA_ARGS__); \ | |
9ed794a3 | 105 | } while (0) |
54f3ff07 | 106 | |
581fc868 ZJS |
107 | #define error_errno(_offset, error, _fmt, ...) do { \ |
108 | flush_progress(); \ | |
109 | log_error_errno(error, OFSfmt": " _fmt, (uint64_t)_offset, ##__VA_ARGS__); \ | |
110 | } while (0) | |
111 | ||
d18e868e | 112 | static int hash_payload(JournalFile *f, Object *o, uint64_t offset, const uint8_t *src, uint64_t size, uint64_t *res_hash) { |
acc50c92 LP |
113 | Compression c; |
114 | int r; | |
d18e868e VC |
115 | |
116 | assert(o); | |
117 | assert(src); | |
118 | assert(res_hash); | |
119 | ||
acc50c92 | 120 | c = COMPRESSION_FROM_OBJECT(o); |
4d698d12 LP |
121 | if (c < 0) |
122 | return -EBADMSG; | |
acc50c92 | 123 | if (c != COMPRESSION_NONE) { |
d18e868e VC |
124 | _cleanup_free_ void *b = NULL; |
125 | size_t b_size; | |
126 | ||
acc50c92 | 127 | r = decompress_blob(c, src, size, &b, &b_size, 0); |
d18e868e VC |
128 | if (r < 0) { |
129 | error_errno(offset, r, "%s decompression failed: %m", | |
acc50c92 | 130 | compression_to_string(c)); |
d18e868e VC |
131 | return r; |
132 | } | |
133 | ||
134 | *res_hash = journal_file_hash_data(f, b, b_size); | |
135 | } else | |
136 | *res_hash = journal_file_hash_data(f, src, size); | |
137 | ||
138 | return 0; | |
139 | } | |
140 | ||
92fba83e | 141 | static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o) { |
0284adc6 | 142 | assert(f); |
92fba83e | 143 | assert(offset); |
0284adc6 LP |
144 | assert(o); |
145 | ||
146 | /* This does various superficial tests about the length an | |
147 | * possible field values. It does not follow any references to | |
148 | * other objects. */ | |
149 | ||
d4cc5c80 | 150 | if ((o->object.flags & _OBJECT_COMPRESSED_MASK) != 0 && |
bca9e39d | 151 | o->object.type != OBJECT_DATA) { |
9df247da DDM |
152 | error(offset, |
153 | "Found compressed object of type %s that isn't of type data, which is not allowed.", | |
154 | journal_object_type_to_string(o->object.type)); | |
f59a5f6b | 155 | return -EBADMSG; |
bca9e39d | 156 | } |
f59a5f6b | 157 | |
0284adc6 | 158 | switch (o->object.type) { |
f59a5f6b | 159 | |
fd5dc320 LP |
160 | case OBJECT_DATA: { |
161 | uint64_t h1, h2; | |
d18e868e | 162 | int r; |
fd5dc320 | 163 | |
92fba83e | 164 | if (le64toh(o->data.entry_offset) == 0) |
e80acc51 | 165 | warning(offset, "Unused data (entry_offset==0)"); |
92fba83e ZJS |
166 | |
167 | if ((le64toh(o->data.entry_offset) == 0) ^ (le64toh(o->data.n_entries) == 0)) { | |
f39c13e0 | 168 | error(offset, "Bad n_entries: %"PRIu64, le64toh(o->data.n_entries)); |
0284adc6 | 169 | return -EBADMSG; |
92fba83e | 170 | } |
0284adc6 | 171 | |
e81710d3 | 172 | if (le64toh(o->object.size) - journal_file_data_payload_offset(f) <= 0) { |
e80acc51 | 173 | error(offset, "Bad object size (<= %zu): %"PRIu64, |
e81710d3 | 174 | journal_file_data_payload_offset(f), |
54f3ff07 | 175 | le64toh(o->object.size)); |
0284adc6 | 176 | return -EBADMSG; |
92fba83e | 177 | } |
f59a5f6b | 178 | |
fd5dc320 | 179 | h1 = le64toh(o->data.hash); |
e81710d3 DDM |
180 | r = hash_payload(f, o, offset, journal_file_data_payload_field(f, o), |
181 | le64toh(o->object.size) - journal_file_data_payload_offset(f), | |
d18e868e VC |
182 | &h2); |
183 | if (r < 0) | |
184 | return r; | |
fd5dc320 | 185 | |
92fba83e | 186 | if (h1 != h2) { |
6abd991c | 187 | error(offset, "Invalid hash (%08" PRIx64 " vs. %08" PRIx64 ")", h1, h2); |
fd5dc320 | 188 | return -EBADMSG; |
92fba83e | 189 | } |
f59a5f6b | 190 | |
f39c13e0 LP |
191 | if (!VALID64(le64toh(o->data.next_hash_offset)) || |
192 | !VALID64(le64toh(o->data.next_field_offset)) || | |
193 | !VALID64(le64toh(o->data.entry_offset)) || | |
194 | !VALID64(le64toh(o->data.entry_array_offset))) { | |
e80acc51 | 195 | error(offset, "Invalid offset (next_hash_offset="OFSfmt", next_field_offset="OFSfmt", entry_offset="OFSfmt", entry_array_offset="OFSfmt, |
f39c13e0 LP |
196 | le64toh(o->data.next_hash_offset), |
197 | le64toh(o->data.next_field_offset), | |
198 | le64toh(o->data.entry_offset), | |
199 | le64toh(o->data.entry_array_offset)); | |
db11ac1a | 200 | return -EBADMSG; |
92fba83e | 201 | } |
db11ac1a | 202 | |
0284adc6 | 203 | break; |
fd5dc320 | 204 | } |
0284adc6 | 205 | |
921fcd5d VC |
206 | case OBJECT_FIELD: { |
207 | uint64_t h1, h2; | |
208 | int r; | |
209 | ||
3a8099a8 | 210 | if (le64toh(o->object.size) - offsetof(Object, field.payload) <= 0) { |
54f3ff07 | 211 | error(offset, |
e80acc51 | 212 | "Bad field size (<= %zu): %"PRIu64, |
3a8099a8 | 213 | offsetof(Object, field.payload), |
54f3ff07 | 214 | le64toh(o->object.size)); |
0284adc6 | 215 | return -EBADMSG; |
92fba83e | 216 | } |
db11ac1a | 217 | |
921fcd5d VC |
218 | h1 = le64toh(o->field.hash); |
219 | r = hash_payload(f, o, offset, o->field.payload, | |
220 | le64toh(o->object.size) - offsetof(Object, field.payload), | |
221 | &h2); | |
222 | if (r < 0) | |
223 | return r; | |
224 | ||
225 | if (h1 != h2) { | |
226 | error(offset, "Invalid hash (%08" PRIx64 " vs. %08" PRIx64 ")", h1, h2); | |
227 | return -EBADMSG; | |
228 | } | |
229 | ||
f39c13e0 LP |
230 | if (!VALID64(le64toh(o->field.next_hash_offset)) || |
231 | !VALID64(le64toh(o->field.head_data_offset))) { | |
54f3ff07 | 232 | error(offset, |
e80acc51 | 233 | "Invalid offset (next_hash_offset="OFSfmt", head_data_offset="OFSfmt, |
f39c13e0 LP |
234 | le64toh(o->field.next_hash_offset), |
235 | le64toh(o->field.head_data_offset)); | |
db11ac1a | 236 | return -EBADMSG; |
92fba83e | 237 | } |
0284adc6 | 238 | break; |
921fcd5d | 239 | } |
0284adc6 LP |
240 | |
241 | case OBJECT_ENTRY: | |
a9089a66 | 242 | if ((le64toh(o->object.size) - offsetof(Object, entry.items)) % journal_file_entry_item_size(f) != 0) { |
54f3ff07 | 243 | error(offset, |
e80acc51 | 244 | "Bad entry size (<= %zu): %"PRIu64, |
3a8099a8 | 245 | offsetof(Object, entry.items), |
54f3ff07 | 246 | le64toh(o->object.size)); |
0284adc6 | 247 | return -EBADMSG; |
92fba83e | 248 | } |
0284adc6 | 249 | |
a9089a66 | 250 | if ((le64toh(o->object.size) - offsetof(Object, entry.items)) / journal_file_entry_item_size(f) <= 0) { |
54f3ff07 | 251 | error(offset, |
e80acc51 | 252 | "Invalid number items in entry: %"PRIu64, |
a9089a66 | 253 | (le64toh(o->object.size) - offsetof(Object, entry.items)) / journal_file_entry_item_size(f)); |
0284adc6 | 254 | return -EBADMSG; |
92fba83e ZJS |
255 | } |
256 | ||
257 | if (le64toh(o->entry.seqnum) <= 0) { | |
54f3ff07 | 258 | error(offset, |
e80acc51 | 259 | "Invalid entry seqnum: %"PRIx64, |
54f3ff07 | 260 | le64toh(o->entry.seqnum)); |
92fba83e ZJS |
261 | return -EBADMSG; |
262 | } | |
0284adc6 | 263 | |
92fba83e | 264 | if (!VALID_REALTIME(le64toh(o->entry.realtime))) { |
54f3ff07 | 265 | error(offset, |
e80acc51 | 266 | "Invalid entry realtime timestamp: %"PRIu64, |
54f3ff07 | 267 | le64toh(o->entry.realtime)); |
0284adc6 | 268 | return -EBADMSG; |
92fba83e ZJS |
269 | } |
270 | ||
271 | if (!VALID_MONOTONIC(le64toh(o->entry.monotonic))) { | |
54f3ff07 | 272 | error(offset, |
e80acc51 | 273 | "Invalid entry monotonic timestamp: %"PRIu64, |
54f3ff07 | 274 | le64toh(o->entry.monotonic)); |
92fba83e ZJS |
275 | return -EBADMSG; |
276 | } | |
0284adc6 | 277 | |
a9089a66 DDM |
278 | for (uint64_t i = 0; i < journal_file_entry_n_items(f, o); i++) { |
279 | if (journal_file_entry_item_object_offset(f, o, i) == 0 || | |
280 | !VALID64(journal_file_entry_item_object_offset(f, o, i))) { | |
54f3ff07 | 281 | error(offset, |
f63d1b0e | 282 | "Invalid entry item (%"PRIu64"/%"PRIu64") offset: "OFSfmt, |
a9089a66 DDM |
283 | i, journal_file_entry_n_items(f, o), |
284 | journal_file_entry_item_object_offset(f, o, i)); | |
db11ac1a | 285 | return -EBADMSG; |
92fba83e | 286 | } |
db11ac1a LP |
287 | } |
288 | ||
0284adc6 LP |
289 | break; |
290 | ||
291 | case OBJECT_DATA_HASH_TABLE: | |
292 | case OBJECT_FIELD_HASH_TABLE: | |
3a8099a8 DDM |
293 | if ((le64toh(o->object.size) - offsetof(Object, hash_table.items)) % sizeof(HashItem) != 0 || |
294 | (le64toh(o->object.size) - offsetof(Object, hash_table.items)) / sizeof(HashItem) <= 0) { | |
54f3ff07 | 295 | error(offset, |
9df247da DDM |
296 | "Invalid %s size: %"PRIu64, |
297 | journal_object_type_to_string(o->object.type), | |
54f3ff07 | 298 | le64toh(o->object.size)); |
86adf873 | 299 | return -EBADMSG; |
92fba83e | 300 | } |
86adf873 | 301 | |
c710944c | 302 | for (uint64_t i = 0; i < journal_file_hash_table_n_items(o); i++) { |
fb9a24b6 | 303 | if (o->hash_table.items[i].head_hash_offset != 0 && |
92fba83e | 304 | !VALID64(le64toh(o->hash_table.items[i].head_hash_offset))) { |
54f3ff07 | 305 | error(offset, |
e80acc51 | 306 | "Invalid %s hash table item (%"PRIu64"/%"PRIu64") head_hash_offset: "OFSfmt, |
9df247da | 307 | journal_object_type_to_string(o->object.type), |
54f3ff07 ZJS |
308 | i, journal_file_hash_table_n_items(o), |
309 | le64toh(o->hash_table.items[i].head_hash_offset)); | |
fb9a24b6 | 310 | return -EBADMSG; |
92fba83e | 311 | } |
fb9a24b6 | 312 | if (o->hash_table.items[i].tail_hash_offset != 0 && |
92fba83e | 313 | !VALID64(le64toh(o->hash_table.items[i].tail_hash_offset))) { |
54f3ff07 | 314 | error(offset, |
e80acc51 | 315 | "Invalid %s hash table item (%"PRIu64"/%"PRIu64") tail_hash_offset: "OFSfmt, |
9df247da | 316 | journal_object_type_to_string(o->object.type), |
54f3ff07 ZJS |
317 | i, journal_file_hash_table_n_items(o), |
318 | le64toh(o->hash_table.items[i].tail_hash_offset)); | |
fb9a24b6 | 319 | return -EBADMSG; |
92fba83e | 320 | } |
fb9a24b6 LP |
321 | |
322 | if ((o->hash_table.items[i].head_hash_offset != 0) != | |
92fba83e | 323 | (o->hash_table.items[i].tail_hash_offset != 0)) { |
54f3ff07 | 324 | error(offset, |
e80acc51 | 325 | "Invalid %s hash table item (%"PRIu64"/%"PRIu64"): head_hash_offset="OFSfmt" tail_hash_offset="OFSfmt, |
9df247da | 326 | journal_object_type_to_string(o->object.type), |
54f3ff07 ZJS |
327 | i, journal_file_hash_table_n_items(o), |
328 | le64toh(o->hash_table.items[i].head_hash_offset), | |
329 | le64toh(o->hash_table.items[i].tail_hash_offset)); | |
fb9a24b6 | 330 | return -EBADMSG; |
92fba83e | 331 | } |
fb9a24b6 LP |
332 | } |
333 | ||
0284adc6 LP |
334 | break; |
335 | ||
336 | case OBJECT_ENTRY_ARRAY: | |
99daf3ce DDM |
337 | if ((le64toh(o->object.size) - offsetof(Object, entry_array.items)) % journal_file_entry_array_item_size(f) != 0 || |
338 | (le64toh(o->object.size) - offsetof(Object, entry_array.items)) / journal_file_entry_array_item_size(f) <= 0) { | |
54f3ff07 | 339 | error(offset, |
e80acc51 | 340 | "Invalid object entry array size: %"PRIu64, |
54f3ff07 | 341 | le64toh(o->object.size)); |
86adf873 | 342 | return -EBADMSG; |
92fba83e | 343 | } |
86adf873 | 344 | |
f39c13e0 | 345 | if (!VALID64(le64toh(o->entry_array.next_entry_array_offset))) { |
54f3ff07 | 346 | error(offset, |
e80acc51 | 347 | "Invalid object entry array next_entry_array_offset: "OFSfmt, |
f39c13e0 | 348 | le64toh(o->entry_array.next_entry_array_offset)); |
db11ac1a | 349 | return -EBADMSG; |
92fba83e | 350 | } |
db11ac1a | 351 | |
99daf3ce DDM |
352 | for (uint64_t i = 0; i < journal_file_entry_array_n_items(f, o); i++) { |
353 | uint64_t q = journal_file_entry_array_item(f, o, i); | |
354 | if (q != 0 && !VALID64(q)) { | |
54f3ff07 | 355 | error(offset, |
e80acc51 | 356 | "Invalid object entry array item (%"PRIu64"/%"PRIu64"): "OFSfmt, |
99daf3ce | 357 | i, journal_file_entry_array_n_items(f, o), q); |
fb9a24b6 | 358 | return -EBADMSG; |
92fba83e | 359 | } |
99daf3ce | 360 | } |
fb9a24b6 | 361 | |
0284adc6 LP |
362 | break; |
363 | ||
364 | case OBJECT_TAG: | |
92fba83e | 365 | if (le64toh(o->object.size) != sizeof(TagObject)) { |
54f3ff07 | 366 | error(offset, |
e80acc51 | 367 | "Invalid object tag size: %"PRIu64, |
54f3ff07 | 368 | le64toh(o->object.size)); |
0284adc6 | 369 | return -EBADMSG; |
92fba83e | 370 | } |
fc89a139 | 371 | |
f39c13e0 | 372 | if (!VALID_EPOCH(le64toh(o->tag.epoch))) { |
54f3ff07 | 373 | error(offset, |
e80acc51 | 374 | "Invalid object tag epoch: %"PRIu64, |
f39c13e0 | 375 | le64toh(o->tag.epoch)); |
fc89a139 | 376 | return -EBADMSG; |
92fba83e | 377 | } |
fc89a139 | 378 | |
0284adc6 LP |
379 | break; |
380 | } | |
381 | ||
382 | return 0; | |
383 | } | |
384 | ||
a4121e96 VC |
385 | static int write_uint64(FILE *fp, uint64_t p) { |
386 | if (fwrite(&p, sizeof(p), 1, fp) != 1) | |
0284adc6 | 387 | return -errno; |
0284adc6 LP |
388 | |
389 | return 0; | |
390 | } | |
391 | ||
1da2c4ce | 392 | static int contains_uint64(MMapFileDescriptor *f, uint64_t n, uint64_t p) { |
0284adc6 LP |
393 | uint64_t a, b; |
394 | int r; | |
395 | ||
be7cdd8e | 396 | assert(f); |
0284adc6 LP |
397 | |
398 | /* Bisection ... */ | |
399 | ||
400 | a = 0; b = n; | |
401 | while (a < b) { | |
402 | uint64_t c, *z; | |
403 | ||
404 | c = (a + b) / 2; | |
405 | ||
c3bd54bf | 406 | r = mmap_cache_fd_get(f, 0, false, c * sizeof(uint64_t), sizeof(uint64_t), NULL, (void **) &z); |
0284adc6 LP |
407 | if (r < 0) |
408 | return r; | |
409 | ||
410 | if (*z == p) | |
411 | return 1; | |
412 | ||
a2e99cdf LP |
413 | if (a + 1 >= b) |
414 | return 0; | |
415 | ||
0284adc6 LP |
416 | if (p < *z) |
417 | b = c; | |
fcde2389 | 418 | else |
0284adc6 LP |
419 | a = c; |
420 | } | |
421 | ||
422 | return 0; | |
423 | } | |
424 | ||
86adf873 LP |
425 | static int verify_data( |
426 | JournalFile *f, | |
427 | Object *o, uint64_t p, | |
be7cdd8e VC |
428 | MMapFileDescriptor *cache_entry_fd, uint64_t n_entries, |
429 | MMapFileDescriptor *cache_entry_array_fd, uint64_t n_entry_arrays) { | |
86adf873 LP |
430 | |
431 | uint64_t i, n, a, last, q; | |
432 | int r; | |
433 | ||
434 | assert(f); | |
435 | assert(o); | |
be7cdd8e VC |
436 | assert(cache_entry_fd); |
437 | assert(cache_entry_array_fd); | |
86adf873 LP |
438 | |
439 | n = le64toh(o->data.n_entries); | |
440 | a = le64toh(o->data.entry_array_offset); | |
441 | ||
92fba83e ZJS |
442 | /* Entry array means at least two objects */ |
443 | if (a && n < 2) { | |
e80acc51 | 444 | error(p, "Entry array present (entry_array_offset="OFSfmt", but n_entries=%"PRIu64")", a, n); |
92fba83e ZJS |
445 | return -EBADMSG; |
446 | } | |
447 | ||
448 | if (n == 0) | |
449 | return 0; | |
450 | ||
451 | /* We already checked that earlier */ | |
452 | assert(o->data.entry_offset); | |
86adf873 LP |
453 | |
454 | last = q = le64toh(o->data.entry_offset); | |
578cd185 DDM |
455 | if (!contains_uint64(cache_entry_fd, n_entries, q)) { |
456 | error(p, "Data object references invalid entry at "OFSfmt, q); | |
457 | return -EBADMSG; | |
458 | } | |
459 | ||
460 | r = journal_file_move_to_entry_by_offset(f, q, DIRECTION_DOWN, NULL, NULL); | |
86adf873 LP |
461 | if (r < 0) |
462 | return r; | |
578cd185 DDM |
463 | if (r == 0) { |
464 | error(q, "Entry object doesn't exist in the main entry array"); | |
465 | return -EBADMSG; | |
466 | } | |
86adf873 | 467 | |
2a7273ef | 468 | i = 1; |
86adf873 LP |
469 | while (i < n) { |
470 | uint64_t next, m, j; | |
471 | ||
472 | if (a == 0) { | |
e80acc51 | 473 | error(p, "Array chain too short"); |
86adf873 LP |
474 | return -EBADMSG; |
475 | } | |
476 | ||
1da2c4ce | 477 | if (!contains_uint64(cache_entry_array_fd, n_entry_arrays, a)) { |
e80acc51 | 478 | error(p, "Invalid array offset "OFSfmt, a); |
86adf873 LP |
479 | return -EBADMSG; |
480 | } | |
481 | ||
482 | r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o); | |
483 | if (r < 0) | |
484 | return r; | |
485 | ||
486 | next = le64toh(o->entry_array.next_entry_array_offset); | |
487 | if (next != 0 && next <= a) { | |
e80acc51 | 488 | error(p, "Array chain has cycle (jumps back from "OFSfmt" to "OFSfmt")", a, next); |
86adf873 LP |
489 | return -EBADMSG; |
490 | } | |
491 | ||
99daf3ce | 492 | m = journal_file_entry_array_n_items(f, o); |
86adf873 LP |
493 | for (j = 0; i < n && j < m; i++, j++) { |
494 | ||
99daf3ce | 495 | q = journal_file_entry_array_item(f, o, j); |
86adf873 | 496 | if (q <= last) { |
9df247da | 497 | error(p, "Data object's entry array not sorted (%"PRIu64" <= %"PRIu64")", q, last); |
86adf873 LP |
498 | return -EBADMSG; |
499 | } | |
500 | last = q; | |
501 | ||
578cd185 DDM |
502 | if (!contains_uint64(cache_entry_fd, n_entries, q)) { |
503 | error(p, "Data object references invalid entry at "OFSfmt, q); | |
504 | return -EBADMSG; | |
505 | } | |
506 | ||
507 | r = journal_file_move_to_entry_by_offset(f, q, DIRECTION_DOWN, NULL, NULL); | |
86adf873 LP |
508 | if (r < 0) |
509 | return r; | |
578cd185 DDM |
510 | if (r == 0) { |
511 | error(q, "Entry object doesn't exist in the main entry array"); | |
512 | return -EBADMSG; | |
513 | } | |
86adf873 LP |
514 | |
515 | /* Pointer might have moved, reposition */ | |
516 | r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o); | |
517 | if (r < 0) | |
518 | return r; | |
519 | } | |
520 | ||
521 | a = next; | |
522 | } | |
523 | ||
524 | return 0; | |
525 | } | |
526 | ||
368a14b6 | 527 | static int verify_data_hash_table( |
86adf873 | 528 | JournalFile *f, |
be7cdd8e VC |
529 | MMapFileDescriptor *cache_data_fd, uint64_t n_data, |
530 | MMapFileDescriptor *cache_entry_fd, uint64_t n_entries, | |
531 | MMapFileDescriptor *cache_entry_array_fd, uint64_t n_entry_arrays, | |
b72631e5 LP |
532 | usec_t *last_usec, |
533 | bool show_progress) { | |
86adf873 LP |
534 | |
535 | uint64_t i, n; | |
536 | int r; | |
537 | ||
538 | assert(f); | |
be7cdd8e VC |
539 | assert(cache_data_fd); |
540 | assert(cache_entry_fd); | |
541 | assert(cache_entry_array_fd); | |
86adf873 LP |
542 | assert(last_usec); |
543 | ||
544 | n = le64toh(f->header->data_hash_table_size) / sizeof(HashItem); | |
dade37d4 LP |
545 | if (n <= 0) |
546 | return 0; | |
547 | ||
548 | r = journal_file_map_data_hash_table(f); | |
549 | if (r < 0) | |
550 | return log_error_errno(r, "Failed to map data hash table: %m"); | |
551 | ||
86adf873 LP |
552 | for (i = 0; i < n; i++) { |
553 | uint64_t last = 0, p; | |
554 | ||
b72631e5 | 555 | if (show_progress) |
45c047b2 | 556 | draw_progress(0xC000 + scale_progress(0x3FFF, i, n), last_usec); |
86adf873 LP |
557 | |
558 | p = le64toh(f->data_hash_table[i].head_hash_offset); | |
559 | while (p != 0) { | |
560 | Object *o; | |
561 | uint64_t next; | |
562 | ||
1da2c4ce | 563 | if (!contains_uint64(cache_data_fd, n_data, p)) { |
e80acc51 | 564 | error(p, "Invalid data object at hash entry %"PRIu64" of %"PRIu64, i, n); |
86adf873 LP |
565 | return -EBADMSG; |
566 | } | |
567 | ||
568 | r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); | |
569 | if (r < 0) | |
570 | return r; | |
571 | ||
572 | next = le64toh(o->data.next_hash_offset); | |
573 | if (next != 0 && next <= p) { | |
e80acc51 | 574 | error(p, "Hash chain has a cycle in hash entry %"PRIu64" of %"PRIu64, i, n); |
86adf873 LP |
575 | return -EBADMSG; |
576 | } | |
577 | ||
578 | if (le64toh(o->data.hash) % n != i) { | |
e80acc51 | 579 | error(p, "Hash value mismatch in hash entry %"PRIu64" of %"PRIu64, i, n); |
86adf873 LP |
580 | return -EBADMSG; |
581 | } | |
582 | ||
be7cdd8e | 583 | r = verify_data(f, o, p, cache_entry_fd, n_entries, cache_entry_array_fd, n_entry_arrays); |
86adf873 LP |
584 | if (r < 0) |
585 | return r; | |
586 | ||
587 | last = p; | |
588 | p = next; | |
589 | } | |
590 | ||
591 | if (last != le64toh(f->data_hash_table[i].tail_hash_offset)) { | |
9df247da DDM |
592 | error(p, |
593 | "Tail hash pointer mismatch in hash table (%"PRIu64" != %"PRIu64")", | |
594 | last, | |
595 | le64toh(f->data_hash_table[i].tail_hash_offset)); | |
86adf873 LP |
596 | return -EBADMSG; |
597 | } | |
598 | } | |
599 | ||
600 | return 0; | |
601 | } | |
602 | ||
603 | static int data_object_in_hash_table(JournalFile *f, uint64_t hash, uint64_t p) { | |
604 | uint64_t n, h, q; | |
605 | int r; | |
606 | assert(f); | |
607 | ||
608 | n = le64toh(f->header->data_hash_table_size) / sizeof(HashItem); | |
dade37d4 LP |
609 | if (n <= 0) |
610 | return 0; | |
611 | ||
612 | r = journal_file_map_data_hash_table(f); | |
613 | if (r < 0) | |
614 | return log_error_errno(r, "Failed to map data hash table: %m"); | |
615 | ||
86adf873 LP |
616 | h = hash % n; |
617 | ||
618 | q = le64toh(f->data_hash_table[h].head_hash_offset); | |
619 | while (q != 0) { | |
620 | Object *o; | |
621 | ||
622 | if (p == q) | |
623 | return 1; | |
624 | ||
625 | r = journal_file_move_to_object(f, OBJECT_DATA, q, &o); | |
626 | if (r < 0) | |
627 | return r; | |
628 | ||
629 | q = le64toh(o->data.next_hash_offset); | |
630 | } | |
631 | ||
632 | return 0; | |
633 | } | |
634 | ||
635 | static int verify_entry( | |
636 | JournalFile *f, | |
637 | Object *o, uint64_t p, | |
578cd185 DDM |
638 | MMapFileDescriptor *cache_data_fd, uint64_t n_data, |
639 | bool last) { | |
86adf873 LP |
640 | |
641 | uint64_t i, n; | |
642 | int r; | |
643 | ||
644 | assert(f); | |
645 | assert(o); | |
be7cdd8e | 646 | assert(cache_data_fd); |
86adf873 | 647 | |
a9089a66 | 648 | n = journal_file_entry_n_items(f, o); |
86adf873 | 649 | for (i = 0; i < n; i++) { |
8bad5453 | 650 | uint64_t q; |
86adf873 LP |
651 | Object *u; |
652 | ||
a9089a66 | 653 | q = journal_file_entry_item_object_offset(f, o, i); |
86adf873 | 654 | |
1da2c4ce | 655 | if (!contains_uint64(cache_data_fd, n_data, q)) { |
e80acc51 LP |
656 | error(p, "Invalid data object of entry"); |
657 | return -EBADMSG; | |
658 | } | |
86adf873 LP |
659 | |
660 | r = journal_file_move_to_object(f, OBJECT_DATA, q, &u); | |
661 | if (r < 0) | |
662 | return r; | |
663 | ||
8bad5453 | 664 | r = data_object_in_hash_table(f, le64toh(u->data.hash), q); |
86adf873 LP |
665 | if (r < 0) |
666 | return r; | |
667 | if (r == 0) { | |
e80acc51 | 668 | error(p, "Data object missing from hash table"); |
86adf873 LP |
669 | return -EBADMSG; |
670 | } | |
578cd185 | 671 | |
b8478c14 YW |
672 | /* Pointer might have moved, reposition */ |
673 | r = journal_file_move_to_object(f, OBJECT_DATA, q, &u); | |
674 | if (r < 0) | |
675 | return r; | |
676 | ||
578cd185 DDM |
677 | r = journal_file_move_to_entry_by_offset_for_data(f, u, p, DIRECTION_DOWN, NULL, NULL); |
678 | if (r < 0) | |
679 | return r; | |
680 | ||
681 | /* The last entry object has a very high chance of not being referenced as journal files | |
682 | * almost always run out of space during linking of entry items when trying to add a new | |
683 | * entry array so let's not error in that scenario. */ | |
684 | if (r == 0 && !last) { | |
685 | error(p, "Entry object not referenced by linked data object at "OFSfmt, q); | |
686 | return -EBADMSG; | |
687 | } | |
86adf873 LP |
688 | } |
689 | ||
690 | return 0; | |
691 | } | |
692 | ||
693 | static int verify_entry_array( | |
694 | JournalFile *f, | |
be7cdd8e VC |
695 | MMapFileDescriptor *cache_data_fd, uint64_t n_data, |
696 | MMapFileDescriptor *cache_entry_fd, uint64_t n_entries, | |
697 | MMapFileDescriptor *cache_entry_array_fd, uint64_t n_entry_arrays, | |
b72631e5 LP |
698 | usec_t *last_usec, |
699 | bool show_progress) { | |
86adf873 LP |
700 | |
701 | uint64_t i = 0, a, n, last = 0; | |
702 | int r; | |
703 | ||
704 | assert(f); | |
be7cdd8e VC |
705 | assert(cache_data_fd); |
706 | assert(cache_entry_fd); | |
707 | assert(cache_entry_array_fd); | |
86adf873 LP |
708 | assert(last_usec); |
709 | ||
710 | n = le64toh(f->header->n_entries); | |
711 | a = le64toh(f->header->entry_array_offset); | |
712 | while (i < n) { | |
713 | uint64_t next, m, j; | |
714 | Object *o; | |
715 | ||
b72631e5 | 716 | if (show_progress) |
45c047b2 | 717 | draw_progress(0x8000 + scale_progress(0x3FFF, i, n), last_usec); |
86adf873 LP |
718 | |
719 | if (a == 0) { | |
e80acc51 | 720 | error(a, "Array chain too short at %"PRIu64" of %"PRIu64, i, n); |
86adf873 LP |
721 | return -EBADMSG; |
722 | } | |
723 | ||
1da2c4ce | 724 | if (!contains_uint64(cache_entry_array_fd, n_entry_arrays, a)) { |
e80acc51 | 725 | error(a, "Invalid array %"PRIu64" of %"PRIu64, i, n); |
86adf873 LP |
726 | return -EBADMSG; |
727 | } | |
728 | ||
729 | r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o); | |
730 | if (r < 0) | |
731 | return r; | |
732 | ||
733 | next = le64toh(o->entry_array.next_entry_array_offset); | |
734 | if (next != 0 && next <= a) { | |
e80acc51 | 735 | error(a, "Array chain has cycle at %"PRIu64" of %"PRIu64" (jumps back from to "OFSfmt")", i, n, next); |
86adf873 LP |
736 | return -EBADMSG; |
737 | } | |
738 | ||
99daf3ce | 739 | m = journal_file_entry_array_n_items(f, o); |
86adf873 LP |
740 | for (j = 0; i < n && j < m; i++, j++) { |
741 | uint64_t p; | |
742 | ||
99daf3ce | 743 | p = journal_file_entry_array_item(f, o, j); |
86adf873 | 744 | if (p <= last) { |
e80acc51 | 745 | error(a, "Entry array not sorted at %"PRIu64" of %"PRIu64, i, n); |
86adf873 LP |
746 | return -EBADMSG; |
747 | } | |
748 | last = p; | |
749 | ||
1da2c4ce | 750 | if (!contains_uint64(cache_entry_fd, n_entries, p)) { |
e80acc51 | 751 | error(a, "Invalid array entry at %"PRIu64" of %"PRIu64, i, n); |
86adf873 LP |
752 | return -EBADMSG; |
753 | } | |
754 | ||
755 | r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o); | |
756 | if (r < 0) | |
757 | return r; | |
758 | ||
578cd185 | 759 | r = verify_entry(f, o, p, cache_data_fd, n_data, /*last=*/ i + 1 == n); |
86adf873 LP |
760 | if (r < 0) |
761 | return r; | |
762 | ||
763 | /* Pointer might have moved, reposition */ | |
764 | r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o); | |
765 | if (r < 0) | |
766 | return r; | |
767 | } | |
768 | ||
769 | a = next; | |
770 | } | |
771 | ||
772 | return 0; | |
773 | } | |
774 | ||
368a14b6 DDM |
775 | static int verify_hash_table( |
776 | Object *o, uint64_t p, uint64_t *n_hash_tables, uint64_t header_offset, uint64_t header_size) { | |
777 | ||
778 | assert(o); | |
779 | assert(n_hash_tables); | |
780 | ||
781 | if (*n_hash_tables > 1) { | |
782 | error(p, | |
783 | "More than one %s: %" PRIu64, | |
784 | journal_object_type_to_string(o->object.type), | |
785 | *n_hash_tables); | |
786 | return -EBADMSG; | |
787 | } | |
788 | ||
3a8099a8 | 789 | if (header_offset != p + offsetof(Object, hash_table.items)) { |
368a14b6 DDM |
790 | error(p, |
791 | "Header offset for %s invalid (%" PRIu64 " != %" PRIu64 ")", | |
792 | journal_object_type_to_string(o->object.type), | |
793 | header_offset, | |
3a8099a8 | 794 | p + offsetof(Object, hash_table.items)); |
368a14b6 DDM |
795 | return -EBADMSG; |
796 | } | |
797 | ||
3a8099a8 | 798 | if (header_size != le64toh(o->object.size) - offsetof(Object, hash_table.items)) { |
368a14b6 DDM |
799 | error(p, |
800 | "Header size for %s invalid (%" PRIu64 " != %" PRIu64 ")", | |
801 | journal_object_type_to_string(o->object.type), | |
802 | header_size, | |
3a8099a8 | 803 | le64toh(o->object.size) - offsetof(Object, hash_table.items)); |
368a14b6 DDM |
804 | return -EBADMSG; |
805 | } | |
806 | ||
807 | (*n_hash_tables)++; | |
808 | ||
809 | return 0; | |
810 | } | |
811 | ||
6c7be122 LP |
812 | int journal_file_verify( |
813 | JournalFile *f, | |
814 | const char *key, | |
2a7b539a | 815 | usec_t *first_contained, usec_t *last_validated, usec_t *last_contained, |
b72631e5 | 816 | bool show_progress) { |
0284adc6 LP |
817 | int r; |
818 | Object *o; | |
0ab5c3ed ZJS |
819 | uint64_t p = 0, last_epoch = 0, last_tag_realtime = 0, last_sealed_realtime = 0; |
820 | ||
14d10188 | 821 | uint64_t entry_seqnum = 0, entry_monotonic = 0, entry_realtime = 0; |
55ce4e1b | 822 | sd_id128_t entry_boot_id = {}; /* Unnecessary initialization to appease gcc */ |
0284adc6 | 823 | bool entry_seqnum_set = false, entry_monotonic_set = false, entry_realtime_set = false, found_main_entry_array = false; |
94510784 | 824 | uint64_t 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, n_tags = 0; |
0284adc6 | 825 | usec_t last_usec = 0; |
254d1313 | 826 | _cleanup_close_ int data_fd = -EBADF, entry_fd = -EBADF, entry_array_fd = -EBADF; |
a4121e96 | 827 | _cleanup_fclose_ FILE *data_fp = NULL, *entry_fp = NULL, *entry_array_fp = NULL; |
be7cdd8e | 828 | MMapFileDescriptor *cache_data_fd = NULL, *cache_entry_fd = NULL, *cache_entry_array_fd = NULL; |
97147f8c | 829 | unsigned i; |
e8c108ca | 830 | bool found_last = false; |
992e8f22 | 831 | const char *tmp_dir = NULL; |
8b4fbbb0 | 832 | MMapCache *m; |
34a8f081 | 833 | |
349cc4a5 | 834 | #if HAVE_GCRYPT |
0ab5c3ed ZJS |
835 | uint64_t last_tag = 0; |
836 | #endif | |
0284adc6 LP |
837 | assert(f); |
838 | ||
baed47c3 | 839 | if (key) { |
349cc4a5 | 840 | #if HAVE_GCRYPT |
baed47c3 | 841 | r = journal_file_parse_verification_key(f, key); |
b7c9ae91 LP |
842 | if (r < 0) { |
843 | log_error("Failed to parse seed."); | |
56e81f7c | 844 | return r; |
b7c9ae91 | 845 | } |
feb12d3e | 846 | #else |
15411c0c | 847 | return -EOPNOTSUPP; |
feb12d3e | 848 | #endif |
4374d7ea | 849 | } else if (JOURNAL_HEADER_SEALED(f->header)) |
c586dbf1 | 850 | return -ENOKEY; |
b7c9ae91 | 851 | |
992e8f22 | 852 | r = var_tmp_dir(&tmp_dir); |
34a8f081 OW |
853 | if (r < 0) { |
854 | log_error_errno(r, "Failed to determine temporary directory: %m"); | |
855 | goto fail; | |
856 | } | |
857 | ||
858 | data_fd = open_tmpfile_unlinkable(tmp_dir, O_RDWR | O_CLOEXEC); | |
0284adc6 | 859 | if (data_fd < 0) { |
709f6e46 | 860 | r = log_error_errno(data_fd, "Failed to create data file: %m"); |
0284adc6 LP |
861 | goto fail; |
862 | } | |
0284adc6 | 863 | |
34a8f081 | 864 | entry_fd = open_tmpfile_unlinkable(tmp_dir, O_RDWR | O_CLOEXEC); |
0284adc6 | 865 | if (entry_fd < 0) { |
709f6e46 | 866 | r = log_error_errno(entry_fd, "Failed to create entry file: %m"); |
0284adc6 LP |
867 | goto fail; |
868 | } | |
0284adc6 | 869 | |
34a8f081 | 870 | entry_array_fd = open_tmpfile_unlinkable(tmp_dir, O_RDWR | O_CLOEXEC); |
0284adc6 | 871 | if (entry_array_fd < 0) { |
709f6e46 | 872 | r = log_error_errno(entry_array_fd, |
76ef789d | 873 | "Failed to create entry array file: %m"); |
0284adc6 LP |
874 | goto fail; |
875 | } | |
0284adc6 | 876 | |
8b4fbbb0 | 877 | m = mmap_cache_fd_cache(f->cache_fd); |
8926a6a4 YW |
878 | r = mmap_cache_add_fd(m, data_fd, PROT_READ|PROT_WRITE, &cache_data_fd); |
879 | if (r < 0) { | |
880 | log_error_errno(r, "Failed to cache data file: %m"); | |
be7cdd8e VC |
881 | goto fail; |
882 | } | |
883 | ||
8926a6a4 YW |
884 | r = mmap_cache_add_fd(m, entry_fd, PROT_READ|PROT_WRITE, &cache_entry_fd); |
885 | if (r < 0) { | |
886 | log_error_errno(r, "Failed to cache entry file: %m"); | |
be7cdd8e VC |
887 | goto fail; |
888 | } | |
889 | ||
8926a6a4 YW |
890 | r = mmap_cache_add_fd(m, entry_array_fd, PROT_READ|PROT_WRITE, &cache_entry_array_fd); |
891 | if (r < 0) { | |
892 | log_error_errno(r, "Failed to cache entry array file: %m"); | |
be7cdd8e VC |
893 | goto fail; |
894 | } | |
895 | ||
a4121e96 VC |
896 | r = take_fdopen_unlocked(&data_fd, "w+", &data_fp); |
897 | if (r < 0) { | |
898 | log_error_errno(r, "Failed to open data file stream: %m"); | |
899 | goto fail; | |
900 | } | |
901 | ||
902 | r = take_fdopen_unlocked(&entry_fd, "w+", &entry_fp); | |
903 | if (r < 0) { | |
904 | log_error_errno(r, "Failed to open entry file stream: %m"); | |
905 | goto fail; | |
906 | } | |
907 | ||
908 | r = take_fdopen_unlocked(&entry_array_fd, "w+", &entry_array_fp); | |
909 | if (r < 0) { | |
910 | log_error_errno(r, "Failed to open entry array file stream: %m"); | |
911 | goto fail; | |
912 | } | |
913 | ||
54f3ff07 | 914 | if (le32toh(f->header->compatible_flags) & ~HEADER_COMPATIBLE_SUPPORTED) { |
97147f8c | 915 | log_error("Cannot verify file with unknown extensions."); |
15411c0c | 916 | r = -EOPNOTSUPP; |
97147f8c LP |
917 | goto fail; |
918 | } | |
919 | ||
920 | for (i = 0; i < sizeof(f->header->reserved); i++) | |
921 | if (f->header->reserved[i] != 0) { | |
e80acc51 | 922 | error(offsetof(Header, reserved[i]), "Reserved field is non-zero"); |
97147f8c LP |
923 | r = -EBADMSG; |
924 | goto fail; | |
925 | } | |
926 | ||
0284adc6 LP |
927 | /* First iteration: we go through all objects, verify the |
928 | * superficial structure, headers, hashes. */ | |
929 | ||
0284adc6 | 930 | p = le64toh(f->header->header_size); |
8dc37a85 LP |
931 | for (;;) { |
932 | /* Early exit if there are no objects in the file, at all */ | |
933 | if (le64toh(f->header->tail_object_offset) == 0) | |
934 | break; | |
935 | ||
b72631e5 | 936 | if (show_progress) |
45c047b2 | 937 | draw_progress(scale_progress(0x7FFF, p, le64toh(f->header->tail_object_offset)), &last_usec); |
0284adc6 | 938 | |
d05089d8 | 939 | r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &o); |
0284adc6 | 940 | if (r < 0) { |
9df247da | 941 | error_errno(p, r, "Invalid object: %m"); |
0284adc6 LP |
942 | goto fail; |
943 | } | |
944 | ||
97147f8c | 945 | if (p > le64toh(f->header->tail_object_offset)) { |
9df247da DDM |
946 | error(offsetof(Header, tail_object_offset), |
947 | "Invalid tail object pointer (%"PRIu64" > %"PRIu64")", | |
948 | p, | |
949 | le64toh(f->header->tail_object_offset)); | |
0284adc6 LP |
950 | r = -EBADMSG; |
951 | goto fail; | |
952 | } | |
953 | ||
313cefa1 | 954 | n_objects++; |
0284adc6 | 955 | |
92fba83e | 956 | r = journal_file_object_verify(f, p, o); |
0284adc6 | 957 | if (r < 0) { |
581fc868 | 958 | error_errno(p, r, "Invalid object contents: %m"); |
0284adc6 LP |
959 | goto fail; |
960 | } | |
961 | ||
8653185a LP |
962 | if (!!(o->object.flags & OBJECT_COMPRESSED_XZ) + |
963 | !!(o->object.flags & OBJECT_COMPRESSED_LZ4) + | |
964 | !!(o->object.flags & OBJECT_COMPRESSED_ZSTD) > 1) { | |
9df247da | 965 | error(p, "Object has multiple compression flags set (flags: 0x%x)", o->object.flags); |
54f3ff07 | 966 | r = -EINVAL; |
d89c8fdf ZJS |
967 | goto fail; |
968 | } | |
969 | ||
970 | if ((o->object.flags & OBJECT_COMPRESSED_XZ) && !JOURNAL_HEADER_COMPRESSED_XZ(f->header)) { | |
54f3ff07 | 971 | error(p, "XZ compressed object in file without XZ compression"); |
d89c8fdf ZJS |
972 | r = -EBADMSG; |
973 | goto fail; | |
974 | } | |
975 | ||
976 | if ((o->object.flags & OBJECT_COMPRESSED_LZ4) && !JOURNAL_HEADER_COMPRESSED_LZ4(f->header)) { | |
54f3ff07 | 977 | error(p, "LZ4 compressed object in file without LZ4 compression"); |
0284adc6 LP |
978 | r = -EBADMSG; |
979 | goto fail; | |
980 | } | |
981 | ||
8653185a LP |
982 | if ((o->object.flags & OBJECT_COMPRESSED_ZSTD) && !JOURNAL_HEADER_COMPRESSED_ZSTD(f->header)) { |
983 | error(p, "ZSTD compressed object in file without ZSTD compression"); | |
984 | r = -EBADMSG; | |
985 | goto fail; | |
986 | } | |
987 | ||
a8e5f514 | 988 | switch (o->object.type) { |
0284adc6 | 989 | |
a8e5f514 | 990 | case OBJECT_DATA: |
a4121e96 | 991 | r = write_uint64(data_fp, p); |
a8e5f514 | 992 | if (r < 0) |
0284adc6 | 993 | goto fail; |
0284adc6 | 994 | |
a8e5f514 LP |
995 | n_data++; |
996 | break; | |
0284adc6 | 997 | |
a8e5f514 LP |
998 | case OBJECT_FIELD: |
999 | n_fields++; | |
1000 | break; | |
0284adc6 | 1001 | |
a8e5f514 | 1002 | case OBJECT_ENTRY: |
8088cbd3 | 1003 | if (JOURNAL_HEADER_SEALED(f->header) && n_tags <= 0) { |
e80acc51 | 1004 | error(p, "First entry before first tag"); |
6c7be122 LP |
1005 | r = -EBADMSG; |
1006 | goto fail; | |
1007 | } | |
1008 | ||
a4121e96 | 1009 | r = write_uint64(entry_fp, p); |
0284adc6 LP |
1010 | if (r < 0) |
1011 | goto fail; | |
1012 | ||
f7fab8a5 | 1013 | if (le64toh(o->entry.realtime) < last_tag_realtime) { |
9df247da DDM |
1014 | error(p, |
1015 | "Older entry after newer tag (%"PRIu64" < %"PRIu64")", | |
1016 | le64toh(o->entry.realtime), | |
1017 | last_tag_realtime); | |
7b5fd91c LP |
1018 | r = -EBADMSG; |
1019 | goto fail; | |
1020 | } | |
1021 | ||
0284adc6 LP |
1022 | if (!entry_seqnum_set && |
1023 | le64toh(o->entry.seqnum) != le64toh(f->header->head_entry_seqnum)) { | |
9df247da DDM |
1024 | error(p, |
1025 | "Head entry sequence number incorrect (%"PRIu64" != %"PRIu64")", | |
1026 | le64toh(o->entry.seqnum), | |
1027 | le64toh(f->header->head_entry_seqnum)); | |
0284adc6 LP |
1028 | r = -EBADMSG; |
1029 | goto fail; | |
1030 | } | |
1031 | ||
1032 | if (entry_seqnum_set && | |
1033 | entry_seqnum >= le64toh(o->entry.seqnum)) { | |
9df247da DDM |
1034 | error(p, |
1035 | "Entry sequence number out of synchronization (%"PRIu64" >= %"PRIu64")", | |
1036 | entry_seqnum, | |
1037 | le64toh(o->entry.seqnum)); | |
0284adc6 LP |
1038 | r = -EBADMSG; |
1039 | goto fail; | |
1040 | } | |
1041 | ||
1042 | entry_seqnum = le64toh(o->entry.seqnum); | |
1043 | entry_seqnum_set = true; | |
1044 | ||
1045 | if (entry_monotonic_set && | |
1046 | sd_id128_equal(entry_boot_id, o->entry.boot_id) && | |
1047 | entry_monotonic > le64toh(o->entry.monotonic)) { | |
9df247da DDM |
1048 | error(p, |
1049 | "Entry timestamp out of synchronization (%"PRIu64" > %"PRIu64")", | |
1050 | entry_monotonic, | |
1051 | le64toh(o->entry.monotonic)); | |
0284adc6 LP |
1052 | r = -EBADMSG; |
1053 | goto fail; | |
1054 | } | |
1055 | ||
1056 | entry_monotonic = le64toh(o->entry.monotonic); | |
1057 | entry_boot_id = o->entry.boot_id; | |
1058 | entry_monotonic_set = true; | |
1059 | ||
1060 | if (!entry_realtime_set && | |
1061 | le64toh(o->entry.realtime) != le64toh(f->header->head_entry_realtime)) { | |
9df247da DDM |
1062 | error(p, |
1063 | "Head entry realtime timestamp incorrect (%"PRIu64" != %"PRIu64")", | |
1064 | le64toh(o->entry.realtime), | |
1065 | le64toh(f->header->head_entry_realtime)); | |
0284adc6 LP |
1066 | r = -EBADMSG; |
1067 | goto fail; | |
1068 | } | |
1069 | ||
1070 | entry_realtime = le64toh(o->entry.realtime); | |
1071 | entry_realtime_set = true; | |
1072 | ||
313cefa1 | 1073 | n_entries++; |
a8e5f514 | 1074 | break; |
0284adc6 | 1075 | |
a8e5f514 | 1076 | case OBJECT_DATA_HASH_TABLE: |
368a14b6 DDM |
1077 | r = verify_hash_table(o, p, &n_data_hash_tables, |
1078 | le64toh(f->header->data_hash_table_offset), | |
1079 | le64toh(f->header->data_hash_table_size)); | |
1080 | if (r < 0) | |
0284adc6 | 1081 | goto fail; |
a8e5f514 LP |
1082 | break; |
1083 | ||
1084 | case OBJECT_FIELD_HASH_TABLE: | |
368a14b6 DDM |
1085 | r = verify_hash_table(o, p, &n_field_hash_tables, |
1086 | le64toh(f->header->field_hash_table_offset), | |
1087 | le64toh(f->header->field_hash_table_size)); | |
1088 | if (r < 0) | |
0284adc6 | 1089 | goto fail; |
a8e5f514 | 1090 | |
a8e5f514 LP |
1091 | break; |
1092 | ||
1093 | case OBJECT_ENTRY_ARRAY: | |
a4121e96 | 1094 | r = write_uint64(entry_array_fp, p); |
a8e5f514 LP |
1095 | if (r < 0) |
1096 | goto fail; | |
1097 | ||
1098 | if (p == le64toh(f->header->entry_array_offset)) { | |
1099 | if (found_main_entry_array) { | |
e80acc51 | 1100 | error(p, "More than one main entry array"); |
a8e5f514 LP |
1101 | r = -EBADMSG; |
1102 | goto fail; | |
1103 | } | |
1104 | ||
1105 | found_main_entry_array = true; | |
1106 | } | |
1107 | ||
1108 | n_entry_arrays++; | |
1109 | break; | |
1110 | ||
feb12d3e | 1111 | case OBJECT_TAG: |
8088cbd3 | 1112 | if (!JOURNAL_HEADER_SEALED(f->header)) { |
e80acc51 | 1113 | error(p, "Tag object in file without sealing"); |
a8e5f514 LP |
1114 | r = -EBADMSG; |
1115 | goto fail; | |
1116 | } | |
1117 | ||
1118 | if (le64toh(o->tag.seqnum) != n_tags + 1) { | |
9df247da DDM |
1119 | error(p, |
1120 | "Tag sequence number out of synchronization (%"PRIu64" != %"PRIu64")", | |
1121 | le64toh(o->tag.seqnum), | |
1122 | n_tags + 1); | |
a8e5f514 LP |
1123 | r = -EBADMSG; |
1124 | goto fail; | |
1125 | } | |
1126 | ||
14d10188 | 1127 | if (le64toh(o->tag.epoch) < last_epoch) { |
9df247da DDM |
1128 | error(p, |
1129 | "Epoch sequence out of synchronization (%"PRIu64" < %"PRIu64")", | |
1130 | le64toh(o->tag.epoch), | |
1131 | last_epoch); | |
7b5fd91c LP |
1132 | r = -EBADMSG; |
1133 | goto fail; | |
1134 | } | |
1135 | ||
349cc4a5 | 1136 | #if HAVE_GCRYPT |
4374d7ea | 1137 | if (JOURNAL_HEADER_SEALED(f->header)) { |
feb12d3e LP |
1138 | uint64_t q, rt; |
1139 | ||
e80acc51 | 1140 | debug(p, "Checking tag %"PRIu64"...", le64toh(o->tag.seqnum)); |
14d10188 | 1141 | |
f39c13e0 | 1142 | rt = f->fss_start_usec + le64toh(o->tag.epoch) * f->fss_interval_usec; |
f7fab8a5 | 1143 | if (entry_realtime_set && entry_realtime >= rt + f->fss_interval_usec) { |
9df247da DDM |
1144 | error(p, |
1145 | "tag/entry realtime timestamp out of synchronization (%"PRIu64" >= %"PRIu64")", | |
1146 | entry_realtime, | |
1147 | rt + f->fss_interval_usec); | |
c586dbf1 LP |
1148 | r = -EBADMSG; |
1149 | goto fail; | |
1150 | } | |
14d10188 | 1151 | |
c586dbf1 LP |
1152 | /* OK, now we know the epoch. So let's now set |
1153 | * it, and calculate the HMAC for everything | |
1154 | * since the last tag. */ | |
1155 | r = journal_file_fsprg_seek(f, le64toh(o->tag.epoch)); | |
14d10188 LP |
1156 | if (r < 0) |
1157 | goto fail; | |
1158 | ||
c586dbf1 | 1159 | r = journal_file_hmac_start(f); |
14d10188 LP |
1160 | if (r < 0) |
1161 | goto fail; | |
1162 | ||
c586dbf1 LP |
1163 | if (last_tag == 0) { |
1164 | r = journal_file_hmac_put_header(f); | |
1165 | if (r < 0) | |
1166 | goto fail; | |
1167 | ||
1168 | q = le64toh(f->header->header_size); | |
1169 | } else | |
1170 | q = last_tag; | |
1171 | ||
1172 | while (q <= p) { | |
d05089d8 | 1173 | r = journal_file_move_to_object(f, OBJECT_UNUSED, q, &o); |
c586dbf1 LP |
1174 | if (r < 0) |
1175 | goto fail; | |
1176 | ||
d05089d8 | 1177 | r = journal_file_hmac_put_object(f, OBJECT_UNUSED, o, q); |
c586dbf1 LP |
1178 | if (r < 0) |
1179 | goto fail; | |
1180 | ||
1181 | q = q + ALIGN64(le64toh(o->object.size)); | |
1182 | } | |
1183 | ||
1184 | /* Position might have changed, let's reposition things */ | |
d05089d8 | 1185 | r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &o); |
14d10188 LP |
1186 | if (r < 0) |
1187 | goto fail; | |
1188 | ||
c586dbf1 | 1189 | if (memcmp(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH) != 0) { |
e80acc51 | 1190 | error(p, "Tag failed verification"); |
c586dbf1 LP |
1191 | r = -EBADMSG; |
1192 | goto fail; | |
1193 | } | |
14d10188 | 1194 | |
c586dbf1 LP |
1195 | f->hmac_running = false; |
1196 | last_tag_realtime = rt; | |
f7fab8a5 | 1197 | last_sealed_realtime = entry_realtime; |
14d10188 LP |
1198 | } |
1199 | ||
14d10188 | 1200 | last_tag = p + ALIGN64(le64toh(o->object.size)); |
0ab5c3ed ZJS |
1201 | #endif |
1202 | ||
c586dbf1 | 1203 | last_epoch = le64toh(o->tag.epoch); |
6c7be122 | 1204 | |
313cefa1 | 1205 | n_tags++; |
a8e5f514 | 1206 | break; |
a8e5f514 | 1207 | } |
0284adc6 | 1208 | |
8dc37a85 LP |
1209 | if (p == le64toh(f->header->tail_object_offset)) { |
1210 | found_last = true; | |
1211 | break; | |
1212 | } | |
0284adc6 | 1213 | |
8dc37a85 LP |
1214 | p = p + ALIGN64(le64toh(o->object.size)); |
1215 | }; | |
1216 | ||
1217 | if (!found_last && le64toh(f->header->tail_object_offset) != 0) { | |
9df247da DDM |
1218 | error(le64toh(f->header->tail_object_offset), |
1219 | "Tail object pointer dead (%"PRIu64" != 0)", | |
1220 | le64toh(f->header->tail_object_offset)); | |
97147f8c LP |
1221 | r = -EBADMSG; |
1222 | goto fail; | |
1223 | } | |
1224 | ||
0284adc6 | 1225 | if (n_objects != le64toh(f->header->n_objects)) { |
9df247da DDM |
1226 | error(offsetof(Header, n_objects), |
1227 | "Object number mismatch (%"PRIu64" != %"PRIu64")", | |
1228 | n_objects, | |
1229 | le64toh(f->header->n_objects)); | |
0284adc6 LP |
1230 | r = -EBADMSG; |
1231 | goto fail; | |
1232 | } | |
1233 | ||
1234 | if (n_entries != le64toh(f->header->n_entries)) { | |
9df247da DDM |
1235 | error(offsetof(Header, n_entries), |
1236 | "Entry number mismatch (%"PRIu64" != %"PRIu64")", | |
1237 | n_entries, | |
1238 | le64toh(f->header->n_entries)); | |
0284adc6 LP |
1239 | r = -EBADMSG; |
1240 | goto fail; | |
1241 | } | |
1242 | ||
1243 | if (JOURNAL_HEADER_CONTAINS(f->header, n_data) && | |
1244 | n_data != le64toh(f->header->n_data)) { | |
9df247da DDM |
1245 | error(offsetof(Header, n_data), |
1246 | "Data number mismatch (%"PRIu64" != %"PRIu64")", | |
1247 | n_data, | |
1248 | le64toh(f->header->n_data)); | |
0284adc6 LP |
1249 | r = -EBADMSG; |
1250 | goto fail; | |
1251 | } | |
1252 | ||
1253 | if (JOURNAL_HEADER_CONTAINS(f->header, n_fields) && | |
1254 | n_fields != le64toh(f->header->n_fields)) { | |
9df247da DDM |
1255 | error(offsetof(Header, n_fields), |
1256 | "Field number mismatch (%"PRIu64" != %"PRIu64")", | |
1257 | n_fields, | |
1258 | le64toh(f->header->n_fields)); | |
0284adc6 LP |
1259 | r = -EBADMSG; |
1260 | goto fail; | |
1261 | } | |
1262 | ||
1263 | if (JOURNAL_HEADER_CONTAINS(f->header, n_tags) && | |
a8e5f514 | 1264 | n_tags != le64toh(f->header->n_tags)) { |
9df247da DDM |
1265 | error(offsetof(Header, n_tags), |
1266 | "Tag number mismatch (%"PRIu64" != %"PRIu64")", | |
1267 | n_tags, | |
1268 | le64toh(f->header->n_tags)); | |
0284adc6 LP |
1269 | r = -EBADMSG; |
1270 | goto fail; | |
1271 | } | |
1272 | ||
2dee23eb LP |
1273 | if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays) && |
1274 | n_entry_arrays != le64toh(f->header->n_entry_arrays)) { | |
9df247da DDM |
1275 | error(offsetof(Header, n_entry_arrays), |
1276 | "Entry array number mismatch (%"PRIu64" != %"PRIu64")", | |
1277 | n_entry_arrays, | |
1278 | le64toh(f->header->n_entry_arrays)); | |
2dee23eb LP |
1279 | r = -EBADMSG; |
1280 | goto fail; | |
1281 | } | |
1282 | ||
8dc37a85 | 1283 | if (!found_main_entry_array && le64toh(f->header->entry_array_offset) != 0) { |
9df247da | 1284 | error(0, "Missing main entry array"); |
0284adc6 LP |
1285 | r = -EBADMSG; |
1286 | goto fail; | |
1287 | } | |
1288 | ||
1289 | if (entry_seqnum_set && | |
1290 | entry_seqnum != le64toh(f->header->tail_entry_seqnum)) { | |
9df247da DDM |
1291 | error(offsetof(Header, tail_entry_seqnum), |
1292 | "Tail entry sequence number incorrect (%"PRIu64" != %"PRIu64")", | |
1293 | entry_seqnum, | |
1294 | le64toh(f->header->tail_entry_seqnum)); | |
0284adc6 LP |
1295 | r = -EBADMSG; |
1296 | goto fail; | |
1297 | } | |
1298 | ||
1299 | if (entry_monotonic_set && | |
9204fc64 LP |
1300 | (sd_id128_equal(entry_boot_id, f->header->tail_entry_boot_id) && |
1301 | JOURNAL_HEADER_TAIL_ENTRY_BOOT_ID(f->header) && | |
0284adc6 | 1302 | entry_monotonic != le64toh(f->header->tail_entry_monotonic))) { |
9df247da DDM |
1303 | error(0, |
1304 | "Invalid tail monotonic timestamp (%"PRIu64" != %"PRIu64")", | |
1305 | entry_monotonic, | |
1306 | le64toh(f->header->tail_entry_monotonic)); | |
0284adc6 LP |
1307 | r = -EBADMSG; |
1308 | goto fail; | |
1309 | } | |
1310 | ||
1311 | if (entry_realtime_set && entry_realtime != le64toh(f->header->tail_entry_realtime)) { | |
9df247da DDM |
1312 | error(0, |
1313 | "Invalid tail realtime timestamp (%"PRIu64" != %"PRIu64")", | |
1314 | entry_realtime, | |
1315 | le64toh(f->header->tail_entry_realtime)); | |
0284adc6 LP |
1316 | r = -EBADMSG; |
1317 | goto fail; | |
1318 | } | |
1319 | ||
a4121e96 VC |
1320 | if (fflush(data_fp) != 0) { |
1321 | r = log_error_errno(errno, "Failed to flush data file stream: %m"); | |
1322 | goto fail; | |
1323 | } | |
1324 | ||
1325 | if (fflush(entry_fp) != 0) { | |
1326 | r = log_error_errno(errno, "Failed to flush entry file stream: %m"); | |
1327 | goto fail; | |
1328 | } | |
1329 | ||
1330 | if (fflush(entry_array_fp) != 0) { | |
1331 | r = log_error_errno(errno, "Failed to flush entry array file stream: %m"); | |
1332 | goto fail; | |
1333 | } | |
1334 | ||
86adf873 LP |
1335 | /* Second iteration: we follow all objects referenced from the |
1336 | * two entry points: the object hash table and the entry | |
1337 | * array. We also check that everything referenced (directly | |
1338 | * or indirectly) in the data hash table also exists in the | |
1339 | * entry array, and vice versa. Note that we do not care for | |
1340 | * unreferenced objects. We only care that everything that is | |
1341 | * referenced is consistent. */ | |
1342 | ||
1343 | r = verify_entry_array(f, | |
be7cdd8e VC |
1344 | cache_data_fd, n_data, |
1345 | cache_entry_fd, n_entries, | |
1346 | cache_entry_array_fd, n_entry_arrays, | |
b72631e5 LP |
1347 | &last_usec, |
1348 | show_progress); | |
86adf873 LP |
1349 | if (r < 0) |
1350 | goto fail; | |
0284adc6 | 1351 | |
368a14b6 DDM |
1352 | r = verify_data_hash_table(f, |
1353 | cache_data_fd, n_data, | |
1354 | cache_entry_fd, n_entries, | |
1355 | cache_entry_array_fd, n_entry_arrays, | |
1356 | &last_usec, | |
1357 | show_progress); | |
86adf873 LP |
1358 | if (r < 0) |
1359 | goto fail; | |
0284adc6 | 1360 | |
b72631e5 LP |
1361 | if (show_progress) |
1362 | flush_progress(); | |
0284adc6 | 1363 | |
c3bd54bf VC |
1364 | mmap_cache_fd_free(cache_data_fd); |
1365 | mmap_cache_fd_free(cache_entry_fd); | |
1366 | mmap_cache_fd_free(cache_entry_array_fd); | |
0284adc6 | 1367 | |
2a7b539a LP |
1368 | if (first_contained) |
1369 | *first_contained = le64toh(f->header->head_entry_realtime); | |
6c7be122 | 1370 | if (last_validated) |
f7fab8a5 | 1371 | *last_validated = last_sealed_realtime; |
6c7be122 LP |
1372 | if (last_contained) |
1373 | *last_contained = le64toh(f->header->tail_entry_realtime); | |
1374 | ||
0284adc6 LP |
1375 | return 0; |
1376 | ||
1377 | fail: | |
b72631e5 LP |
1378 | if (show_progress) |
1379 | flush_progress(); | |
0284adc6 | 1380 | |
8c29ac2f | 1381 | log_error("File corruption detected at %s:%"PRIu64" (of %"PRIu64" bytes, %"PRIu64"%%).", |
0284adc6 | 1382 | f->path, |
507f22bd | 1383 | p, |
8c29ac2f LP |
1384 | (uint64_t) f->last_stat.st_size, |
1385 | 100U * p / (uint64_t) f->last_stat.st_size); | |
0284adc6 | 1386 | |
be7cdd8e | 1387 | if (cache_data_fd) |
c3bd54bf | 1388 | mmap_cache_fd_free(cache_data_fd); |
be7cdd8e VC |
1389 | |
1390 | if (cache_entry_fd) | |
c3bd54bf | 1391 | mmap_cache_fd_free(cache_entry_fd); |
be7cdd8e VC |
1392 | |
1393 | if (cache_entry_array_fd) | |
c3bd54bf | 1394 | mmap_cache_fd_free(cache_entry_array_fd); |
0284adc6 LP |
1395 | |
1396 | return r; | |
1397 | } |