]> git.ipfire.org Git - thirdparty/util-linux.git/blob - libmount/src/context_veritydev.c
libmount: add support for verity devices via libcryptsetup
[thirdparty/util-linux.git] / libmount / src / context_veritydev.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 /*
3 * This file is part of libmount from util-linux project.
4 *
5 * Copyright (C) 2019 Microsoft Corporation
6 *
7 * libmount is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation; either version 2.1 of the License, or
10 * (at your option) any later version.
11 */
12
13 #include "mountP.h"
14
15 #if defined(HAVE_CRYPTSETUP)
16
17 #include <libcryptsetup.h>
18
19 /* Taken from https://gitlab.com/cryptsetup/cryptsetup/blob/master/lib/utils_crypt.c#L225 */
20 static size_t crypt_hex_to_bytes(const char *hex, char **result)
21 {
22 char buf[3] = "xx\0", *endp, *bytes;
23 size_t i, len;
24
25 len = strlen(hex);
26 if (len % 2)
27 return -EINVAL;
28 len /= 2;
29
30 bytes = malloc(len);
31 if (!bytes)
32 return -ENOMEM;
33
34 for (i = 0; i < len; i++) {
35 memcpy(buf, &hex[i * 2], 2);
36 bytes[i] = strtoul(buf, &endp, 16);
37 if (endp != &buf[2]) {
38 free(bytes);
39 return -EINVAL;
40 }
41 }
42 *result = bytes;
43 return i;
44 }
45
46
47 int mnt_context_setup_veritydev(struct libmnt_context *cxt)
48 {
49 const char *backing_file, *optstr;
50 char *val = NULL, *key = NULL, *root_hash_binary = NULL, *mapper_device = NULL,
51 *mapper_device_full = NULL, *backing_file_basename = NULL, *root_hash = NULL,
52 *hash_device = NULL;
53 size_t len, hash_size, keysize = 0;
54 struct crypt_params_verity crypt_params = {};
55 struct crypt_device *crypt_dev = NULL;
56 int rc = 0;
57 uint64_t offset = 0;
58
59 assert(cxt);
60 assert(cxt->fs);
61 assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
62
63 /* dm-verity volumes are read-only, and mount will fail if not set */
64 mnt_context_set_mflags(cxt, (cxt->mountflags | MS_RDONLY));
65
66 backing_file = mnt_fs_get_srcpath(cxt->fs);
67 if (!backing_file)
68 return -EINVAL;
69
70 /* To avoid clashes, prefix libmnt_ to all mapper devices */
71 backing_file_basename = basename(backing_file);
72 mapper_device = calloc(strlen(backing_file_basename) + strlen("libmnt_") + 1, sizeof(char));
73 if (!mapper_device)
74 return -ENOMEM;
75 strcat(mapper_device, "libmnt_");
76 strcat(mapper_device, backing_file_basename);
77
78 DBG(VERITY, ul_debugobj(cxt, "trying to setup verity device for %s", backing_file));
79
80 optstr = mnt_fs_get_user_options(cxt->fs);
81
82 /*
83 * verity.hashdevice=
84 */
85 if (rc == 0 && (cxt->user_mountflags & MNT_MS_HASH_DEVICE) &&
86 mnt_optstr_get_option(optstr, "verity.hashdevice", &val, &len) == 0 && val) {
87 hash_device = strndup(val, len);
88 rc = hash_device ? 0 : -ENOMEM;
89 }
90
91 /*
92 * verity.roothash=
93 */
94 if (rc == 0 && (cxt->user_mountflags & MNT_MS_ROOT_HASH) &&
95 mnt_optstr_get_option(optstr, "verity.roothash", &val, &len) == 0 && val) {
96 root_hash = strndup(val, len);
97 rc = root_hash ? 0 : -ENOMEM;
98 }
99
100 /*
101 * verity.hashoffset=
102 */
103 if (rc == 0 && (cxt->user_mountflags & MNT_MS_HASH_OFFSET) &&
104 mnt_optstr_get_option(optstr, "verity.hashoffset", &val, &len) == 0) {
105 rc = mnt_parse_offset(val, len, &offset);
106 if (rc) {
107 DBG(VERITY, ul_debugobj(cxt, "failed to parse verity.hashoffset="));
108 rc = -MNT_ERR_MOUNTOPT;
109 }
110 }
111
112 if (rc)
113 goto done;
114
115 rc = crypt_init_data_device(&crypt_dev, hash_device, backing_file);
116 if (rc)
117 goto done;
118
119 memset(&crypt_params, 0, sizeof(struct crypt_params_verity));
120 crypt_params.hash_area_offset = offset;
121 crypt_params.fec_area_offset = 0;
122 crypt_params.fec_roots = 0;
123 crypt_params.fec_device = NULL;
124 crypt_params.flags = 0;
125 rc = crypt_load(crypt_dev, CRYPT_VERITY, &crypt_params);
126 if (rc < 0)
127 goto done;
128
129 hash_size = crypt_get_volume_key_size(crypt_dev);
130 if (crypt_hex_to_bytes(root_hash, &root_hash_binary) != hash_size) {
131 DBG(VERITY, ul_debugobj(cxt, "root hash %s is not of length %zu", root_hash, hash_size));
132 rc = -EINVAL;
133 goto done;
134 }
135 rc = crypt_activate_by_volume_key(crypt_dev, mapper_device, root_hash_binary, hash_size,
136 CRYPT_ACTIVATE_READONLY);
137 /*
138 * If the mapper device already exists, and if libcryptsetup supports it, get the root
139 * hash associated with the existing one and compare it with the parameter passed by
140 * the user. If they match, then we can be sure the user intended to mount the exact
141 * same device, and simply reuse it and return success.
142 * The kernel does the refcounting for us.
143 * If libcryptsetup does not support getting the root hash out of an existing device,
144 * then return an error and tell the user that the device is already in use.
145 * Pass through only OOM errors or mismatching root hash errors.
146 */
147 if (rc == -EEXIST) {
148 DBG(VERITY, ul_debugobj(cxt, "%s already in use as /dev/mapper/%s", backing_file, mapper_device));
149 crypt_free(crypt_dev);
150 rc = crypt_init_by_name(&crypt_dev, mapper_device);
151 if (!rc) {
152 rc = crypt_get_verity_info(crypt_dev, &crypt_params);
153 if (!rc) {
154 key = calloc(hash_size, 1);
155 if (!key) {
156 rc = -ENOMEM;
157 goto done;
158 }
159 }
160 if (!rc) {
161 keysize = hash_size;
162 rc = crypt_volume_key_get(crypt_dev, CRYPT_ANY_SLOT, key, &keysize, NULL, 0);
163 }
164 if (!rc) {
165 DBG(VERITY, ul_debugobj(cxt, "comparing root hash of existing device with %s", root_hash));
166 if (memcmp(key, root_hash_binary, hash_size)) {
167 DBG(VERITY, ul_debugobj(cxt, "existing device's hash does not match with %s", root_hash));
168 rc = -EINVAL;
169 goto done;
170 }
171 } else {
172 DBG(VERITY, ul_debugobj(cxt, "libcryptsetup does not support extracting root hash of existing device"));
173 }
174 }
175 if (rc) {
176 rc = -EEXIST;
177 } else {
178 DBG(VERITY, ul_debugobj(cxt, "root hash of %s matches %s, reusing device", mapper_device, root_hash));
179 }
180 }
181
182 if (!rc) {
183 cxt->flags |= MNT_FL_VERITYDEV_READY;
184 mapper_device_full = calloc(strlen(mapper_device) + strlen("/dev/mapper/") + 1, sizeof(char));
185 if (!mapper_device_full)
186 rc = -ENOMEM;
187 else {
188 strcat(mapper_device_full, "/dev/mapper/");
189 strcat(mapper_device_full, mapper_device);
190 rc = mnt_fs_set_source(cxt->fs, mapper_device_full);
191 }
192 }
193
194 done:
195 crypt_free(crypt_dev);
196 free(root_hash_binary);
197 free(mapper_device_full);
198 free(mapper_device);
199 free(hash_device);
200 free(root_hash);
201 free(key);
202 return rc;
203 }
204
205 int mnt_context_deferred_delete_veritydev(struct libmnt_context *cxt)
206 {
207 const char *src;
208 struct crypt_device *crypt_dev = NULL;
209 /* If mounting failed delete immediately, otherwise setup auto cleanup for user umount */
210 uint32_t flags = mnt_context_get_status(cxt) ? CRYPT_DEACTIVATE_DEFERRED : 0;
211 int rc;
212
213 assert(cxt);
214 assert(cxt->fs);
215
216 if (!(cxt->flags & MNT_FL_VERITYDEV_READY))
217 return 0;
218
219 src = mnt_fs_get_srcpath(cxt->fs);
220 if (!src)
221 return -EINVAL;
222
223 rc = crypt_init_by_name(&crypt_dev, src);
224 if (!rc) {
225 rc = crypt_deactivate_by_name(crypt_dev, src, flags);
226 if (!rc)
227 cxt->flags &= ~MNT_FL_VERITYDEV_READY;
228 }
229
230 crypt_free(crypt_dev);
231
232 DBG(VERITY, ul_debugobj(cxt, "deleted [rc=%d]", rc));
233
234 return rc;
235 }
236
237 #else
238
239 int mnt_context_setup_veritydev(struct libmnt_context *cxt __attribute__ ((__unused__)))
240 {
241 return 0;
242 }
243
244 int mnt_context_deferred_delete_veritydev(struct libmnt_context *cxt __attribute__ ((__unused__)))
245 {
246 return 0;
247 }
248 #endif
249
250 int mnt_context_is_veritydev(struct libmnt_context *cxt)
251 {
252 const char *src;
253
254 assert(cxt);
255
256 /* The mount flags have to be merged, otherwise we have to use
257 * expensive mnt_context_get_user_mflags() instead of cxt->user_mountflags. */
258 assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
259
260 if (!cxt->fs)
261 return 0;
262 src = mnt_fs_get_srcpath(cxt->fs);
263 if (!src)
264 return 0; /* backing file not set */
265
266 if (cxt->user_mountflags & (MNT_MS_HASH_DEVICE |
267 MNT_MS_ROOT_HASH |
268 MNT_MS_HASH_OFFSET)) {
269 #ifndef HAVE_CRYPTSETUP
270 DBG(VERITY, ul_debugobj(cxt, "veritydev specific options detected but libmount built without libcryptsetup"));
271 return -ENOTSUP;
272 #else
273 DBG(VERITY, ul_debugobj(cxt, "veritydev specific options detected"));
274 return 1;
275 #endif
276 }
277
278 if (!strncmp(src, "/dev/mapper/libmnt_", strlen("/dev/mapper/libmnt_"))) {
279 #ifndef HAVE_CRYPTSETUP
280 DBG(VERITY, ul_debugobj(cxt, "veritydev prefix detected in source device but libmount built without libcryptsetup"));
281 return -ENOTSUP;
282 #else
283 DBG(VERITY, ul_debugobj(cxt, "veritydev prefix detected in source device"));
284 return 1;
285 #endif
286 }
287
288 return 0;
289 }