/src/cryptsetup/lib/verity/verity.c
Line | Count | Source |
1 | | // SPDX-License-Identifier: LGPL-2.1-or-later |
2 | | /* |
3 | | * dm-verity volume handling |
4 | | * |
5 | | * Copyright (C) 2012-2025 Red Hat, Inc. All rights reserved. |
6 | | */ |
7 | | |
8 | | #include <errno.h> |
9 | | #include <stdio.h> |
10 | | #include <stdlib.h> |
11 | | #include <string.h> |
12 | | #include <stdint.h> |
13 | | #include <ctype.h> |
14 | | #include <sys/types.h> |
15 | | #include <sys/stat.h> |
16 | | #include <uuid/uuid.h> |
17 | | |
18 | | #include "libcryptsetup.h" |
19 | | #include "verity.h" |
20 | | #include "internal.h" |
21 | | |
22 | 0 | #define VERITY_SIGNATURE "verity\0\0" |
23 | | |
24 | | /* https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity#verity-superblock-format */ |
25 | | struct verity_sb { |
26 | | uint8_t signature[8]; /* "verity\0\0" */ |
27 | | uint32_t version; /* superblock version */ |
28 | | uint32_t hash_type; /* 0 - Chrome OS, 1 - normal */ |
29 | | uint8_t uuid[16]; /* UUID of hash device */ |
30 | | uint8_t algorithm[32];/* hash algorithm name */ |
31 | | uint32_t data_block_size; /* data block in bytes */ |
32 | | uint32_t hash_block_size; /* hash block in bytes */ |
33 | | uint64_t data_blocks; /* number of data blocks */ |
34 | | uint16_t salt_size; /* salt size */ |
35 | | uint8_t _pad1[6]; |
36 | | uint8_t salt[256]; /* salt */ |
37 | | uint8_t _pad2[168]; |
38 | | } __attribute__((packed)); |
39 | | |
40 | | /* Read verity superblock from disk */ |
41 | | int VERITY_read_sb(struct crypt_device *cd, |
42 | | uint64_t sb_offset, |
43 | | char **uuid_string, |
44 | | struct crypt_params_verity *params) |
45 | 0 | { |
46 | 0 | struct device *device = crypt_metadata_device(cd); |
47 | 0 | struct verity_sb sb = {}; |
48 | 0 | ssize_t hdr_size = sizeof(struct verity_sb); |
49 | 0 | int devfd, sb_version; |
50 | |
|
51 | 0 | log_dbg(cd, "Reading VERITY header of size %zu on device %s, offset %" PRIu64 ".", |
52 | 0 | sizeof(struct verity_sb), device_path(device), sb_offset); |
53 | |
|
54 | 0 | if (params->flags & CRYPT_VERITY_NO_HEADER) { |
55 | 0 | log_err(cd, _("Verity device %s does not use on-disk header."), |
56 | 0 | device_path(device)); |
57 | 0 | return -EINVAL; |
58 | 0 | } |
59 | | |
60 | 0 | if (MISALIGNED_512(sb_offset)) { |
61 | 0 | log_err(cd, _("Unsupported VERITY hash offset.")); |
62 | 0 | return -EINVAL; |
63 | 0 | } |
64 | | |
65 | 0 | devfd = device_open(cd, device, O_RDONLY); |
66 | 0 | if (devfd < 0) { |
67 | 0 | log_err(cd, _("Cannot open device %s."), device_path(device)); |
68 | 0 | return -EINVAL; |
69 | 0 | } |
70 | | |
71 | 0 | if (read_lseek_blockwise(devfd, device_block_size(cd, device), |
72 | 0 | device_alignment(device), &sb, hdr_size, |
73 | 0 | sb_offset) < hdr_size) |
74 | 0 | return -EIO; |
75 | | |
76 | 0 | if (memcmp(sb.signature, VERITY_SIGNATURE, sizeof(sb.signature))) { |
77 | 0 | log_dbg(cd, "No VERITY signature detected."); |
78 | 0 | return -EINVAL; |
79 | 0 | } |
80 | | |
81 | 0 | sb_version = le32_to_cpu(sb.version); |
82 | 0 | if (sb_version != 1) { |
83 | 0 | log_err(cd, _("Unsupported VERITY version %d."), sb_version); |
84 | 0 | return -EINVAL; |
85 | 0 | } |
86 | 0 | params->hash_type = le32_to_cpu(sb.hash_type); |
87 | 0 | if (params->hash_type > VERITY_MAX_HASH_TYPE) { |
88 | 0 | log_err(cd, _("Unsupported VERITY hash type %d."), params->hash_type); |
89 | 0 | return -EINVAL; |
90 | 0 | } |
91 | | |
92 | 0 | params->data_block_size = le32_to_cpu(sb.data_block_size); |
93 | 0 | params->hash_block_size = le32_to_cpu(sb.hash_block_size); |
94 | 0 | if (VERITY_BLOCK_SIZE_OK(params->data_block_size) || |
95 | 0 | VERITY_BLOCK_SIZE_OK(params->hash_block_size)) { |
96 | 0 | log_err(cd, _("Unsupported VERITY block size.")); |
97 | 0 | return -EINVAL; |
98 | 0 | } |
99 | 0 | params->data_size = le64_to_cpu(sb.data_blocks); |
100 | | |
101 | | /* Update block size to be used for loop devices */ |
102 | 0 | device_set_block_size(crypt_metadata_device(cd), params->hash_block_size); |
103 | 0 | device_set_block_size(crypt_data_device(cd), params->data_block_size); |
104 | |
|
105 | 0 | params->hash_name = strndup((const char*)sb.algorithm, sizeof(sb.algorithm)); |
106 | 0 | if (!params->hash_name) |
107 | 0 | return -ENOMEM; |
108 | 0 | if (crypt_hash_size(params->hash_name) <= 0) { |
109 | 0 | log_err(cd, _("Hash algorithm %s not supported."), |
110 | 0 | params->hash_name); |
111 | 0 | free(CONST_CAST(char*)params->hash_name); |
112 | 0 | params->hash_name = NULL; |
113 | 0 | return -EINVAL; |
114 | 0 | } |
115 | | |
116 | 0 | params->salt_size = le16_to_cpu(sb.salt_size); |
117 | 0 | if (params->salt_size > sizeof(sb.salt)) { |
118 | 0 | log_err(cd, _("VERITY header corrupted.")); |
119 | 0 | free(CONST_CAST(char*)params->hash_name); |
120 | 0 | params->hash_name = NULL; |
121 | 0 | return -EINVAL; |
122 | 0 | } |
123 | 0 | params->salt = malloc(params->salt_size); |
124 | 0 | if (!params->salt) { |
125 | 0 | free(CONST_CAST(char*)params->hash_name); |
126 | 0 | params->hash_name = NULL; |
127 | 0 | return -ENOMEM; |
128 | 0 | } |
129 | 0 | memcpy(CONST_CAST(char*)params->salt, sb.salt, params->salt_size); |
130 | |
|
131 | 0 | if ((*uuid_string = malloc(40))) |
132 | 0 | uuid_unparse(sb.uuid, *uuid_string); |
133 | |
|
134 | 0 | params->hash_area_offset = sb_offset; |
135 | 0 | return 0; |
136 | 0 | } |
137 | | |
138 | | static void _to_lower(char *str) |
139 | 0 | { |
140 | 0 | for(; *str; str++) |
141 | 0 | if (isupper(*str)) |
142 | 0 | *str = tolower(*str); |
143 | 0 | } |
144 | | |
145 | | /* Write verity superblock to disk */ |
146 | | int VERITY_write_sb(struct crypt_device *cd, |
147 | | uint64_t sb_offset, |
148 | | const char *uuid_string, |
149 | | struct crypt_params_verity *params) |
150 | 0 | { |
151 | 0 | struct device *device = crypt_metadata_device(cd); |
152 | 0 | struct verity_sb sb = {}; |
153 | 0 | ssize_t hdr_size = sizeof(struct verity_sb); |
154 | 0 | size_t block_size; |
155 | 0 | char *algorithm; |
156 | 0 | uuid_t uuid; |
157 | 0 | int r, devfd; |
158 | |
|
159 | 0 | log_dbg(cd, "Updating VERITY header of size %zu on device %s, offset %" PRIu64 ".", |
160 | 0 | sizeof(struct verity_sb), device_path(device), sb_offset); |
161 | |
|
162 | 0 | if (!uuid_string || uuid_parse(uuid_string, uuid) == -1) { |
163 | 0 | log_err(cd, _("Wrong VERITY UUID format provided on device %s."), |
164 | 0 | device_path(device)); |
165 | 0 | return -EINVAL; |
166 | 0 | } |
167 | | |
168 | 0 | if (params->flags & CRYPT_VERITY_NO_HEADER) { |
169 | 0 | log_err(cd, _("Verity device %s does not use on-disk header."), |
170 | 0 | device_path(device)); |
171 | 0 | return -EINVAL; |
172 | 0 | } |
173 | | |
174 | | /* Avoid possible increasing of image size - FEC could fail later because of it */ |
175 | 0 | block_size = device_block_size(cd, device); |
176 | 0 | if (block_size > params->hash_block_size) { |
177 | 0 | device_disable_direct_io(device); |
178 | 0 | block_size = params->hash_block_size; |
179 | 0 | } |
180 | |
|
181 | 0 | devfd = device_open(cd, device, O_RDWR); |
182 | 0 | if (devfd < 0) { |
183 | 0 | log_err(cd, _("Cannot open device %s."), device_path(device)); |
184 | 0 | return -EINVAL; |
185 | 0 | } |
186 | | |
187 | 0 | memcpy(&sb.signature, VERITY_SIGNATURE, sizeof(sb.signature)); |
188 | 0 | sb.version = cpu_to_le32(1); |
189 | 0 | sb.hash_type = cpu_to_le32(params->hash_type); |
190 | 0 | sb.data_block_size = cpu_to_le32(params->data_block_size); |
191 | 0 | sb.hash_block_size = cpu_to_le32(params->hash_block_size); |
192 | 0 | sb.salt_size = cpu_to_le16(params->salt_size); |
193 | 0 | sb.data_blocks = cpu_to_le64(params->data_size); |
194 | | |
195 | | /* Kernel always use lower-case */ |
196 | 0 | algorithm = (char *)sb.algorithm; |
197 | 0 | strncpy(algorithm, params->hash_name, sizeof(sb.algorithm)-1); |
198 | 0 | algorithm[sizeof(sb.algorithm)-1] = '\0'; |
199 | 0 | _to_lower(algorithm); |
200 | |
|
201 | 0 | memcpy(sb.salt, params->salt, params->salt_size); |
202 | 0 | memcpy(sb.uuid, uuid, sizeof(sb.uuid)); |
203 | |
|
204 | 0 | r = write_lseek_blockwise(devfd, block_size, device_alignment(device), |
205 | 0 | (char*)&sb, hdr_size, sb_offset) < hdr_size ? -EIO : 0; |
206 | 0 | if (r) |
207 | 0 | log_err(cd, _("Error during update of verity header on device %s."), |
208 | 0 | device_path(device)); |
209 | |
|
210 | 0 | device_sync(cd, device); |
211 | |
|
212 | 0 | return r; |
213 | 0 | } |
214 | | |
215 | | /* Calculate hash offset in hash blocks */ |
216 | | uint64_t VERITY_hash_offset_block(struct crypt_params_verity *params) |
217 | 0 | { |
218 | 0 | uint64_t hash_offset = params->hash_area_offset; |
219 | |
|
220 | 0 | if (params->flags & CRYPT_VERITY_NO_HEADER) |
221 | 0 | return hash_offset / params->hash_block_size; |
222 | | |
223 | 0 | hash_offset += sizeof(struct verity_sb); |
224 | 0 | hash_offset += params->hash_block_size - 1; |
225 | |
|
226 | 0 | return hash_offset / params->hash_block_size; |
227 | 0 | } |
228 | | |
229 | | int VERITY_UUID_generate(char **uuid_string) |
230 | 0 | { |
231 | 0 | uuid_t uuid; |
232 | |
|
233 | 0 | *uuid_string = malloc(40); |
234 | 0 | if (!*uuid_string) |
235 | 0 | return -ENOMEM; |
236 | 0 | uuid_generate(uuid); |
237 | 0 | uuid_unparse(uuid, *uuid_string); |
238 | 0 | return 0; |
239 | 0 | } |
240 | | |
241 | | int VERITY_verify_params(struct crypt_device *cd, |
242 | | struct crypt_params_verity *hdr, |
243 | | bool signed_root_hash, |
244 | | struct device *fec_device, |
245 | | struct volume_key *root_hash) |
246 | 0 | { |
247 | 0 | bool userspace_verification; |
248 | 0 | int v, r; |
249 | 0 | unsigned int fec_errors = 0; |
250 | |
|
251 | 0 | assert(cd); |
252 | 0 | assert(hdr); |
253 | 0 | assert(root_hash); |
254 | |
|
255 | 0 | log_dbg(cd, "Verifying VERITY device using hash %s.", |
256 | 0 | hdr->hash_name); |
257 | |
|
258 | 0 | userspace_verification = hdr->flags & CRYPT_VERITY_CHECK_HASH; |
259 | |
|
260 | 0 | if (userspace_verification && signed_root_hash) { |
261 | 0 | log_err(cd, _("Root hash signature verification is not supported.")); |
262 | 0 | return -EINVAL; |
263 | 0 | } |
264 | | |
265 | 0 | if ((hdr->flags & CRYPT_VERITY_ROOT_HASH_SIGNATURE) && !signed_root_hash) { |
266 | 0 | log_err(cd, _("Root hash signature required.")); |
267 | 0 | return -EINVAL; |
268 | 0 | } |
269 | | |
270 | 0 | if (!userspace_verification) |
271 | 0 | return 0; |
272 | | |
273 | 0 | log_dbg(cd, "Verification of VERITY data in userspace required."); |
274 | 0 | r = VERITY_verify(cd, hdr, crypt_volume_key_get_key(root_hash), |
275 | 0 | crypt_volume_key_length(root_hash)); |
276 | |
|
277 | 0 | if ((r == -EPERM || r == -EFAULT) && fec_device) { |
278 | 0 | v = r; |
279 | 0 | log_dbg(cd, "Verification failed, trying to repair with FEC device."); |
280 | 0 | r = VERITY_FEC_process(cd, hdr, fec_device, 1, &fec_errors); |
281 | 0 | if (r < 0) |
282 | 0 | log_err(cd, _("Errors cannot be repaired with FEC device.")); |
283 | 0 | else if (fec_errors) { |
284 | 0 | log_err(cd, _("Found %u repairable errors with FEC device."), |
285 | 0 | fec_errors); |
286 | | /* If root hash failed, we cannot be sure it was properly repaired */ |
287 | 0 | } |
288 | 0 | if (v == -EFAULT) |
289 | 0 | r = -EPERM; |
290 | 0 | } |
291 | |
|
292 | 0 | return r; |
293 | 0 | } |
294 | | |
295 | | /* Activate verity device in kernel device-mapper */ |
296 | | int VERITY_activate(struct crypt_device *cd, |
297 | | const char *name, |
298 | | struct volume_key *root_hash, |
299 | | struct volume_key *signature, |
300 | | struct device *fec_device, |
301 | | struct crypt_params_verity *verity_hdr, |
302 | | uint32_t activation_flags) |
303 | 0 | { |
304 | 0 | uint64_t dmv_flags; |
305 | 0 | int r; |
306 | 0 | key_serial_t kid = 0; |
307 | 0 | char *description = NULL; |
308 | 0 | struct crypt_dm_active_device dmd = { 0 }; |
309 | |
|
310 | 0 | assert(name); |
311 | 0 | assert(root_hash); |
312 | 0 | assert(verity_hdr); |
313 | |
|
314 | 0 | dmd.size = verity_hdr->data_size * verity_hdr->data_block_size / 512; |
315 | 0 | dmd.flags = activation_flags; |
316 | 0 | dmd.uuid = crypt_get_uuid(cd); |
317 | |
|
318 | 0 | log_dbg(cd, "Activating VERITY device %s using hash %s.", |
319 | 0 | name, verity_hdr->hash_name); |
320 | |
|
321 | 0 | if (signature) { |
322 | 0 | r = asprintf(&description, "cryptsetup:%s%s%s", |
323 | 0 | crypt_get_uuid(cd) ?: "", crypt_get_uuid(cd) ? "-" : "", name); |
324 | 0 | if (r < 0) |
325 | 0 | return -EINVAL; |
326 | | |
327 | 0 | log_dbg(cd, "Adding signature %s (type user) into thread keyring.", description); |
328 | 0 | kid = keyring_add_key_in_thread_keyring(USER_KEY, description, |
329 | 0 | crypt_volume_key_get_key(signature), |
330 | 0 | crypt_volume_key_length(signature)); |
331 | 0 | if (kid < 0) { |
332 | 0 | log_dbg(cd, "keyring_add_key_in_thread_keyring failed with errno %d.", errno); |
333 | 0 | log_err(cd, _("Failed to load key in kernel keyring.")); |
334 | 0 | free(description); |
335 | 0 | return -EINVAL; |
336 | 0 | } |
337 | 0 | } |
338 | | |
339 | 0 | r = device_block_adjust(cd, crypt_metadata_device(cd), DEV_OK, |
340 | 0 | 0, NULL, NULL); |
341 | 0 | if (r) |
342 | 0 | goto out; |
343 | | |
344 | 0 | r = device_block_adjust(cd, crypt_data_device(cd), |
345 | 0 | activation_flags & CRYPT_ACTIVATE_SHARED ? DEV_OK : DEV_EXCL, |
346 | 0 | 0, &dmd.size, &dmd.flags); |
347 | 0 | if (r) |
348 | 0 | goto out; |
349 | | |
350 | 0 | if (fec_device) { |
351 | 0 | r = device_block_adjust(cd, fec_device, DEV_OK, |
352 | 0 | 0, NULL, NULL); |
353 | 0 | if (r) |
354 | 0 | goto out; |
355 | 0 | } |
356 | | |
357 | 0 | r = dm_verity_target_set(&dmd.segment, 0, dmd.size, crypt_data_device(cd), |
358 | 0 | crypt_metadata_device(cd), fec_device, crypt_volume_key_get_key(root_hash), |
359 | 0 | crypt_volume_key_length(root_hash), description, |
360 | 0 | VERITY_hash_offset_block(verity_hdr), |
361 | 0 | VERITY_FEC_blocks(cd, fec_device, verity_hdr), verity_hdr); |
362 | |
|
363 | 0 | if (r) |
364 | 0 | goto out; |
365 | | |
366 | 0 | r = dm_create_device(cd, name, CRYPT_VERITY, &dmd); |
367 | 0 | if (r < 0 && (dm_flags(cd, DM_VERITY, &dmv_flags) || !(dmv_flags & DM_VERITY_SUPPORTED))) { |
368 | 0 | log_err(cd, _("Kernel does not support dm-verity mapping.")); |
369 | 0 | r = -ENOTSUP; |
370 | 0 | } |
371 | 0 | if (r < 0 && signature && !(dmv_flags & DM_VERITY_SIGNATURE_SUPPORTED)) { |
372 | 0 | log_err(cd, _("Kernel does not support dm-verity signature option.")); |
373 | 0 | r = -ENOTSUP; |
374 | 0 | } |
375 | 0 | if (r < 0) |
376 | 0 | goto out; |
377 | | |
378 | 0 | r = dm_status_verity_ok(cd, name); |
379 | 0 | if (r < 0) |
380 | 0 | goto out; |
381 | | |
382 | 0 | if (!r) |
383 | 0 | log_err(cd, _("Verity device detected corruption after activation.")); |
384 | |
|
385 | 0 | r = 0; |
386 | 0 | out: |
387 | 0 | if (signature) { |
388 | 0 | log_dbg(cd, "Unlinking signature (id: %" PRIi32 ") from thread keyring.", kid); |
389 | |
|
390 | 0 | if (keyring_unlink_key_from_thread_keyring(kid)) |
391 | 0 | log_dbg(cd, "keyring_unlink_key_from_thread_keyring failed with errno %d.", errno); |
392 | 0 | } |
393 | 0 | free(description); |
394 | 0 | dm_targets_free(cd, &dmd); |
395 | 0 | return r; |
396 | 0 | } |
397 | | |
398 | | int VERITY_dump(struct crypt_device *cd, |
399 | | struct crypt_params_verity *verity_hdr, |
400 | | const char *root_hash, |
401 | | unsigned int root_hash_size, |
402 | | struct device *fec_device) |
403 | 0 | { |
404 | 0 | uint64_t hash_blocks, verity_blocks, fec_blocks = 0, rs_blocks = 0; |
405 | 0 | bool fec_on_hash_device = false; |
406 | |
|
407 | 0 | hash_blocks = VERITY_hash_blocks(cd, verity_hdr); |
408 | 0 | verity_blocks = VERITY_hash_offset_block(verity_hdr) + hash_blocks; |
409 | |
|
410 | 0 | if (fec_device && verity_hdr->fec_roots) { |
411 | 0 | fec_blocks = VERITY_FEC_blocks(cd, fec_device, verity_hdr); |
412 | 0 | rs_blocks = VERITY_FEC_RS_blocks(fec_blocks, verity_hdr->fec_roots); |
413 | 0 | fec_on_hash_device = device_is_identical(crypt_metadata_device(cd), fec_device) > 0; |
414 | | /* |
415 | | * No way to access fec_area_offset directly. |
416 | | * Assume FEC area starts directly after hash blocks. |
417 | | */ |
418 | 0 | if (fec_on_hash_device) |
419 | 0 | verity_blocks += rs_blocks; |
420 | 0 | } |
421 | |
|
422 | 0 | log_std(cd, "VERITY header information for %s.\n", device_path(crypt_metadata_device(cd))); |
423 | 0 | log_std(cd, "UUID: \t%s\n", crypt_get_uuid(cd) ?: ""); |
424 | 0 | log_std(cd, "Hash type: \t%u\n", verity_hdr->hash_type); |
425 | 0 | log_std(cd, "Data blocks: \t%" PRIu64 "\n", verity_hdr->data_size); |
426 | 0 | log_std(cd, "Data block size: \t%u [bytes]\n", verity_hdr->data_block_size); |
427 | 0 | log_std(cd, "Hash blocks: \t%" PRIu64 "\n", hash_blocks); |
428 | 0 | log_std(cd, "Hash block size: \t%u [bytes]\n", verity_hdr->hash_block_size); |
429 | 0 | log_std(cd, "Hash algorithm: \t%s\n", verity_hdr->hash_name); |
430 | 0 | if (fec_device && fec_blocks) { |
431 | 0 | log_std(cd, "FEC RS roots: \t%" PRIu32 "\n", verity_hdr->fec_roots); |
432 | 0 | log_std(cd, "FEC blocks: \t%" PRIu64 "\n", rs_blocks); |
433 | 0 | } |
434 | |
|
435 | 0 | log_std(cd, "Salt: \t"); |
436 | 0 | if (verity_hdr->salt_size) |
437 | 0 | crypt_log_hex(cd, verity_hdr->salt, verity_hdr->salt_size, "", 0, NULL); |
438 | 0 | else |
439 | 0 | log_std(cd, "-"); |
440 | 0 | log_std(cd, "\n"); |
441 | |
|
442 | 0 | if (root_hash) { |
443 | 0 | log_std(cd, "Root hash: \t"); |
444 | 0 | crypt_log_hex(cd, root_hash, root_hash_size, "", 0, NULL); |
445 | 0 | log_std(cd, "\n"); |
446 | 0 | } |
447 | | |
448 | | /* As dump can take only hash device, we have no idea about offsets here. */ |
449 | 0 | if (verity_hdr->hash_area_offset == 0) |
450 | 0 | log_std(cd, "Hash device size: \t%" PRIu64 " [bytes]\n", verity_blocks * verity_hdr->hash_block_size); |
451 | |
|
452 | 0 | if (fec_device && verity_hdr->fec_area_offset == 0 && fec_blocks && !fec_on_hash_device) |
453 | 0 | log_std(cd, "FEC device size: \t%" PRIu64 " [bytes]\n", rs_blocks * verity_hdr->data_block_size); |
454 | |
|
455 | 0 | return 0; |
456 | 0 | } |