/src/cryptsetup/lib/utils_storage_wrappers.c
Line | Count | Source |
1 | | // SPDX-License-Identifier: LGPL-2.1-or-later |
2 | | /* |
3 | | * Generic wrapper for storage functions |
4 | | * (experimental only) |
5 | | * |
6 | | * Copyright (C) 2018-2026 Ondrej Kozina |
7 | | */ |
8 | | |
9 | | #include <errno.h> |
10 | | #include <stdio.h> |
11 | | #include <stddef.h> |
12 | | #include <stdint.h> |
13 | | #include <stdlib.h> |
14 | | #include <limits.h> |
15 | | #include <sys/stat.h> |
16 | | #include <sys/types.h> |
17 | | |
18 | | #include "utils_storage_wrappers.h" |
19 | | #include "internal.h" |
20 | | |
21 | | struct crypt_storage_wrapper { |
22 | | crypt_storage_wrapper_type type; |
23 | | int dev_fd; |
24 | | int block_size; |
25 | | size_t mem_alignment; |
26 | | uint64_t data_offset; |
27 | | union { |
28 | | struct { |
29 | | struct crypt_storage *s; |
30 | | uint64_t iv_start; |
31 | | } cb; |
32 | | struct { |
33 | | int dmcrypt_fd; |
34 | | char name[PATH_MAX]; |
35 | | } dm; |
36 | | } u; |
37 | | }; |
38 | | |
39 | | static int crypt_storage_backend_init(struct crypt_device *cd, |
40 | | struct crypt_storage_wrapper *w, |
41 | | uint64_t iv_start, |
42 | | int sector_size, |
43 | | const char *cipher, |
44 | | const char *cipher_mode, |
45 | | const struct volume_key *vk, |
46 | | uint32_t flags) |
47 | 0 | { |
48 | 0 | int r; |
49 | 0 | struct crypt_storage *s; |
50 | | |
51 | | /* iv_start, sector_size */ |
52 | 0 | r = crypt_storage_init(&s, sector_size, cipher, cipher_mode, |
53 | 0 | crypt_volume_key_get_key(vk), |
54 | 0 | crypt_volume_key_length(vk), flags & CSW_LARGE_IV); |
55 | 0 | if (r) |
56 | 0 | return r; |
57 | | |
58 | 0 | if ((flags & CSW_DISABLE_KCAPI) && crypt_storage_kernel_only(s)) { |
59 | 0 | log_dbg(cd, "Could not initialize userspace block cipher and kernel fallback is disabled."); |
60 | 0 | crypt_storage_destroy(s); |
61 | 0 | return -ENOTSUP; |
62 | 0 | } |
63 | | |
64 | 0 | w->type = USPACE; |
65 | 0 | w->u.cb.s = s; |
66 | 0 | w->u.cb.iv_start = iv_start; |
67 | |
|
68 | 0 | return 0; |
69 | 0 | } |
70 | | |
71 | | static int crypt_storage_dmcrypt_init( |
72 | | struct crypt_device *cd, |
73 | | struct crypt_storage_wrapper *cw, |
74 | | struct device *device, |
75 | | uint64_t device_offset, |
76 | | uint64_t iv_start, |
77 | | int sector_size, |
78 | | const char *cipher_spec, |
79 | | struct volume_key *vk, |
80 | | int open_flags) |
81 | 0 | { |
82 | 0 | static int counter = 0; |
83 | 0 | char path[PATH_MAX]; |
84 | 0 | struct crypt_dm_active_device dmd = { |
85 | 0 | .flags = CRYPT_ACTIVATE_PRIVATE, |
86 | 0 | }; |
87 | 0 | int mode, r, fd = -1; |
88 | |
|
89 | 0 | log_dbg(cd, "Using temporary dmcrypt to access data."); |
90 | |
|
91 | 0 | if (snprintf(cw->u.dm.name, sizeof(cw->u.dm.name), "temporary-cryptsetup-%d-%d", getpid(), counter++) < 0) |
92 | 0 | return -ENOMEM; |
93 | 0 | if (snprintf(path, sizeof(path), "%s/%s", dm_get_dir(), cw->u.dm.name) < 0) |
94 | 0 | return -ENOMEM; |
95 | | |
96 | 0 | r = device_block_adjust(cd, device, DEV_OK, |
97 | 0 | device_offset, &dmd.size, &dmd.flags); |
98 | 0 | if (r < 0) { |
99 | 0 | log_err(cd, _("Device %s does not exist or access denied."), |
100 | 0 | device_path(device)); |
101 | 0 | return -EIO; |
102 | 0 | } |
103 | | |
104 | 0 | mode = open_flags | O_DIRECT; |
105 | 0 | if (dmd.flags & CRYPT_ACTIVATE_READONLY) |
106 | 0 | mode = (open_flags & ~O_ACCMODE) | O_RDONLY; |
107 | |
|
108 | 0 | if (crypt_volume_key_description(vk)) |
109 | 0 | dmd.flags |= CRYPT_ACTIVATE_KEYRING_KEY; |
110 | |
|
111 | 0 | r = dm_crypt_target_set(&dmd.segment, 0, dmd.size, device, vk, cipher_spec, iv_start, |
112 | 0 | device_offset, NULL, 0, 0, sector_size); |
113 | 0 | if (r) |
114 | 0 | return r; |
115 | | |
116 | 0 | r = dm_create_device(cd, cw->u.dm.name, "TEMP", &dmd); |
117 | 0 | if (r < 0) { |
118 | 0 | if (r != -EACCES && r != -ENOTSUP) |
119 | 0 | log_dbg(cd, "error hint would be nice"); |
120 | 0 | r = -EIO; |
121 | 0 | } |
122 | |
|
123 | 0 | dm_targets_free(cd, &dmd); |
124 | |
|
125 | 0 | if (r) |
126 | 0 | return r; |
127 | | |
128 | 0 | fd = open(path, mode); |
129 | 0 | if (fd < 0) { |
130 | 0 | log_dbg(cd, "Failed to open %s", path); |
131 | 0 | dm_remove_device(cd, cw->u.dm.name, CRYPT_DEACTIVATE_FORCE); |
132 | 0 | return -EINVAL; |
133 | 0 | } |
134 | | |
135 | 0 | cw->type = DMCRYPT; |
136 | 0 | cw->u.dm.dmcrypt_fd = fd; |
137 | |
|
138 | 0 | return 0; |
139 | 0 | } |
140 | | |
141 | | int crypt_storage_wrapper_init(struct crypt_device *cd, |
142 | | struct crypt_storage_wrapper **cw, |
143 | | struct device *device, |
144 | | uint64_t data_offset, |
145 | | uint64_t iv_start, |
146 | | int sector_size, |
147 | | const char *cipher, |
148 | | struct volume_key *vk, |
149 | | uint32_t flags) |
150 | 0 | { |
151 | 0 | int open_flags, r; |
152 | 0 | char _cipher[MAX_CIPHER_LEN], mode[MAX_CIPHER_LEN]; |
153 | 0 | struct crypt_storage_wrapper *w; |
154 | |
|
155 | 0 | if ((flags & CSW_DISABLE_DMCRYPT) && (flags & CSW_DMCRYPT_ONLY)) |
156 | 0 | return -EINVAL; |
157 | | |
158 | | /* device-mapper restrictions */ |
159 | 0 | if (data_offset & ((1 << SECTOR_SHIFT) - 1)) |
160 | 0 | return -EINVAL; |
161 | | |
162 | 0 | if (crypt_parse_name_and_mode(cipher, _cipher, NULL, mode)) |
163 | 0 | return -EINVAL; |
164 | | |
165 | 0 | open_flags = O_CLOEXEC | ((flags & CSW_OPEN_READONLY) ? O_RDONLY : O_RDWR); |
166 | |
|
167 | 0 | w = malloc(sizeof(*w)); |
168 | 0 | if (!w) |
169 | 0 | return -ENOMEM; |
170 | | |
171 | 0 | memset(w, 0, sizeof(*w)); |
172 | 0 | w->data_offset = data_offset; |
173 | 0 | w->mem_alignment = device_alignment(device); |
174 | 0 | w->block_size = device_block_size(cd, device); |
175 | 0 | if (!w->block_size || !w->mem_alignment) { |
176 | 0 | log_dbg(cd, "block size or alignment error."); |
177 | 0 | r = -EINVAL; |
178 | 0 | goto err; |
179 | 0 | } |
180 | | |
181 | 0 | if (flags & CSW_OPEN_LOCKED) |
182 | 0 | w->dev_fd = device_open_locked(cd, device, open_flags); |
183 | 0 | else |
184 | 0 | w->dev_fd = device_open(cd, device, open_flags); |
185 | 0 | if (w->dev_fd < 0) { |
186 | 0 | r = -EINVAL; |
187 | 0 | goto err; |
188 | 0 | } |
189 | | |
190 | 0 | if (crypt_is_cipher_null(_cipher)) { |
191 | 0 | log_dbg(cd, "Requested cipher_null, switching to noop wrapper."); |
192 | 0 | w->type = NONE; |
193 | 0 | *cw = w; |
194 | 0 | return 0; |
195 | 0 | } |
196 | | |
197 | 0 | if (!vk) { |
198 | 0 | log_dbg(cd, "no key passed."); |
199 | 0 | r = -EINVAL; |
200 | 0 | goto err; |
201 | 0 | } |
202 | | |
203 | 0 | if (!(flags & CSW_DMCRYPT_ONLY)) { |
204 | 0 | r = crypt_storage_backend_init(cd, w, iv_start, sector_size, _cipher, mode, vk, flags); |
205 | 0 | if (!r) { |
206 | 0 | *cw = w; |
207 | 0 | return 0; |
208 | 0 | } |
209 | | |
210 | 0 | log_dbg(cd, "Failed to initialize userspace block cipher."); |
211 | |
|
212 | 0 | if ((r != -ENOTSUP && r != -ENOENT) || (flags & CSW_DISABLE_DMCRYPT)) |
213 | 0 | goto err; |
214 | 0 | } |
215 | | |
216 | 0 | r = crypt_storage_dmcrypt_init(cd, w, device, data_offset >> SECTOR_SHIFT, iv_start, |
217 | 0 | sector_size, cipher, vk, open_flags); |
218 | 0 | if (r) { |
219 | 0 | log_dbg(cd, "Dm-crypt backend failed to initialize."); |
220 | 0 | goto err; |
221 | 0 | } |
222 | 0 | *cw = w; |
223 | 0 | return 0; |
224 | 0 | err: |
225 | 0 | crypt_storage_wrapper_destroy(w); |
226 | | /* wrapper destroy */ |
227 | 0 | return r; |
228 | 0 | } |
229 | | |
230 | | /* offset is relative to sector_start */ |
231 | | ssize_t crypt_storage_wrapper_read(struct crypt_storage_wrapper *cw, |
232 | | off_t offset, void *buffer, size_t buffer_length) |
233 | 0 | { |
234 | 0 | return read_lseek_blockwise(cw->dev_fd, |
235 | 0 | cw->block_size, |
236 | 0 | cw->mem_alignment, |
237 | 0 | buffer, |
238 | 0 | buffer_length, |
239 | 0 | cw->data_offset + offset); |
240 | 0 | } |
241 | | |
242 | | ssize_t crypt_storage_wrapper_read_decrypt(struct crypt_storage_wrapper *cw, |
243 | | off_t offset, void *buffer, size_t buffer_length) |
244 | 0 | { |
245 | 0 | int r; |
246 | 0 | ssize_t read; |
247 | |
|
248 | 0 | if (cw->type == DMCRYPT) |
249 | 0 | return read_lseek_blockwise(cw->u.dm.dmcrypt_fd, |
250 | 0 | cw->block_size, |
251 | 0 | cw->mem_alignment, |
252 | 0 | buffer, |
253 | 0 | buffer_length, |
254 | 0 | offset); |
255 | | |
256 | 0 | read = read_lseek_blockwise(cw->dev_fd, |
257 | 0 | cw->block_size, |
258 | 0 | cw->mem_alignment, |
259 | 0 | buffer, |
260 | 0 | buffer_length, |
261 | 0 | cw->data_offset + offset); |
262 | 0 | if (cw->type == NONE || read < 0) |
263 | 0 | return read; |
264 | | |
265 | 0 | r = crypt_storage_decrypt(cw->u.cb.s, |
266 | 0 | cw->u.cb.iv_start + (offset >> SECTOR_SHIFT), |
267 | 0 | read, |
268 | 0 | buffer); |
269 | 0 | if (r) |
270 | 0 | return -EINVAL; |
271 | | |
272 | 0 | return read; |
273 | 0 | } |
274 | | |
275 | | ssize_t crypt_storage_wrapper_decrypt(struct crypt_storage_wrapper *cw, |
276 | | off_t offset, void *buffer, size_t buffer_length) |
277 | 0 | { |
278 | 0 | int r; |
279 | 0 | ssize_t read; |
280 | |
|
281 | 0 | if (cw->type == NONE) |
282 | 0 | return 0; |
283 | | |
284 | 0 | if (cw->type == DMCRYPT) { |
285 | | /* there's nothing we can do, just read/decrypt via dm-crypt */ |
286 | 0 | read = crypt_storage_wrapper_read_decrypt(cw, offset, buffer, buffer_length); |
287 | 0 | if (read < 0 || (size_t)read != buffer_length) |
288 | 0 | return -EINVAL; |
289 | 0 | return 0; |
290 | 0 | } |
291 | | |
292 | 0 | r = crypt_storage_decrypt(cw->u.cb.s, |
293 | 0 | cw->u.cb.iv_start + (offset >> SECTOR_SHIFT), |
294 | 0 | buffer_length, |
295 | 0 | buffer); |
296 | 0 | if (r) |
297 | 0 | return r; |
298 | | |
299 | 0 | return 0; |
300 | 0 | } |
301 | | |
302 | | ssize_t crypt_storage_wrapper_write(struct crypt_storage_wrapper *cw, |
303 | | off_t offset, void *buffer, size_t buffer_length) |
304 | 0 | { |
305 | 0 | return write_lseek_blockwise(cw->dev_fd, |
306 | 0 | cw->block_size, |
307 | 0 | cw->mem_alignment, |
308 | 0 | buffer, |
309 | 0 | buffer_length, |
310 | 0 | cw->data_offset + offset); |
311 | 0 | } |
312 | | |
313 | | ssize_t crypt_storage_wrapper_encrypt_write(struct crypt_storage_wrapper *cw, |
314 | | off_t offset, void *buffer, size_t buffer_length) |
315 | 0 | { |
316 | 0 | if (cw->type == DMCRYPT) |
317 | 0 | return write_lseek_blockwise(cw->u.dm.dmcrypt_fd, |
318 | 0 | cw->block_size, |
319 | 0 | cw->mem_alignment, |
320 | 0 | buffer, |
321 | 0 | buffer_length, |
322 | 0 | offset); |
323 | | |
324 | 0 | if (cw->type == USPACE && |
325 | 0 | crypt_storage_encrypt(cw->u.cb.s, |
326 | 0 | cw->u.cb.iv_start + (offset >> SECTOR_SHIFT), |
327 | 0 | buffer_length, buffer)) |
328 | 0 | return -EINVAL; |
329 | | |
330 | 0 | return write_lseek_blockwise(cw->dev_fd, |
331 | 0 | cw->block_size, |
332 | 0 | cw->mem_alignment, |
333 | 0 | buffer, |
334 | 0 | buffer_length, |
335 | 0 | cw->data_offset + offset); |
336 | 0 | } |
337 | | |
338 | | void crypt_storage_wrapper_destroy(struct crypt_storage_wrapper *cw) |
339 | 0 | { |
340 | 0 | if (!cw) |
341 | 0 | return; |
342 | | |
343 | 0 | if (cw->type == USPACE) |
344 | 0 | crypt_storage_destroy(cw->u.cb.s); |
345 | 0 | if (cw->type == DMCRYPT) { |
346 | 0 | close(cw->u.dm.dmcrypt_fd); |
347 | 0 | dm_remove_device(NULL, cw->u.dm.name, CRYPT_DEACTIVATE_FORCE); |
348 | 0 | } |
349 | |
|
350 | 0 | free(cw); |
351 | 0 | } |
352 | | |
353 | | int crypt_storage_wrapper_datasync(const struct crypt_storage_wrapper *cw) |
354 | 0 | { |
355 | 0 | if (!cw) |
356 | 0 | return -EINVAL; |
357 | 0 | if (cw->type == DMCRYPT) |
358 | 0 | return fdatasync(cw->u.dm.dmcrypt_fd); |
359 | 0 | else |
360 | 0 | return fdatasync(cw->dev_fd); |
361 | 0 | } |
362 | | |
363 | | crypt_storage_wrapper_type crypt_storage_wrapper_get_type(const struct crypt_storage_wrapper *cw) |
364 | 0 | { |
365 | 0 | return cw ? cw->type : NONE; |
366 | 0 | } |