/src/cryptsetup/lib/utils_device_locking.c
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | /* |
3 | | * Metadata on-disk locking for processes serialization |
4 | | * |
5 | | * Copyright (C) 2016-2025 Red Hat, Inc. All rights reserved. |
6 | | * Copyright (C) 2016-2025 Ondrej Kozina |
7 | | */ |
8 | | |
9 | | #include <errno.h> |
10 | | #include <linux/limits.h> |
11 | | #include <stdio.h> |
12 | | #include <stdlib.h> |
13 | | #include <string.h> |
14 | | #include <sys/file.h> |
15 | | #include <sys/stat.h> |
16 | | #include <sys/types.h> |
17 | | #include <unistd.h> |
18 | | #if HAVE_SYS_SYSMACROS_H |
19 | | # include <sys/sysmacros.h> /* for major, minor */ |
20 | | #endif |
21 | | #include <libgen.h> |
22 | | |
23 | | #include "internal.h" |
24 | | #include "utils_device_locking.h" |
25 | | |
26 | | #define same_inode(buf1, buf2) \ |
27 | 10.7k | ((buf1).st_ino == (buf2).st_ino && \ |
28 | 10.7k | (buf1).st_dev == (buf2).st_dev) |
29 | | |
30 | | enum lock_type { |
31 | | DEV_LOCK_READ = 0, |
32 | | DEV_LOCK_WRITE |
33 | | }; |
34 | | |
35 | | enum lock_mode { |
36 | | DEV_LOCK_FILE = 0, |
37 | | DEV_LOCK_BDEV, |
38 | | DEV_LOCK_NAME |
39 | | }; |
40 | | |
41 | | struct crypt_lock_handle { |
42 | | unsigned refcnt; |
43 | | int flock_fd; |
44 | | enum lock_type type; |
45 | | enum lock_mode mode; |
46 | | union { |
47 | | struct { |
48 | | dev_t devno; |
49 | | } bdev; |
50 | | struct { |
51 | | char *name; |
52 | | } name; |
53 | | } u; |
54 | | }; |
55 | | |
56 | | static int resource_by_name(char *res, size_t res_size, const char *name, bool fullpath) |
57 | 0 | { |
58 | 0 | int r; |
59 | |
|
60 | 0 | if (fullpath) |
61 | 0 | r = snprintf(res, res_size, "%s/LN_%s", DEFAULT_LUKS2_LOCK_PATH, name); |
62 | 0 | else |
63 | 0 | r = snprintf(res, res_size, "LN_%s", name); |
64 | |
|
65 | 0 | return (r < 0 || (size_t)r >= res_size) ? -EINVAL : 0; |
66 | 0 | } |
67 | | |
68 | | static int resource_by_devno(char *res, size_t res_size, dev_t devno, unsigned fullpath) |
69 | 0 | { |
70 | 0 | int r; |
71 | |
|
72 | 0 | if (fullpath) |
73 | 0 | r = snprintf(res, res_size, "%s/L_%d:%d", DEFAULT_LUKS2_LOCK_PATH, major(devno), minor(devno)); |
74 | 0 | else |
75 | 0 | r = snprintf(res, res_size, "L_%d:%d", major(devno), minor(devno)); |
76 | |
|
77 | 0 | return (r < 0 || (size_t)r >= res_size) ? -EINVAL : 0; |
78 | 0 | } |
79 | | |
80 | | static int open_lock_dir(struct crypt_device *cd, const char *dir, const char *base) |
81 | 0 | { |
82 | 0 | int dirfd, lockdfd; |
83 | |
|
84 | 0 | dirfd = open(dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC); |
85 | 0 | if (dirfd < 0) { |
86 | 0 | log_dbg(cd, "Failed to open directory %s: (%d: %s).", dir, errno, strerror(errno)); |
87 | 0 | if (errno == ENOTDIR || errno == ENOENT) |
88 | 0 | log_err(cd, _("Locking aborted. The locking path %s/%s is unusable (not a directory or missing)."), dir, base); |
89 | 0 | return -EINVAL; |
90 | 0 | } |
91 | | |
92 | 0 | lockdfd = openat(dirfd, base, O_RDONLY | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC); |
93 | 0 | if (lockdfd < 0) { |
94 | 0 | if (errno == ENOENT) { |
95 | 0 | log_dbg(cd, "Locking directory %s/%s will be created with default compiled-in permissions.", dir, base); |
96 | | |
97 | | /* success or failure w/ errno == EEXIST either way just try to open the 'base' directory again */ |
98 | 0 | if (mkdirat(dirfd, base, DEFAULT_LUKS2_LOCK_DIR_PERMS) && errno != EEXIST) |
99 | 0 | log_dbg(cd, "Failed to create directory %s in %s (%d: %s).", base, dir, errno, strerror(errno)); |
100 | 0 | else |
101 | 0 | lockdfd = openat(dirfd, base, O_RDONLY | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC); |
102 | 0 | } else { |
103 | 0 | log_dbg(cd, "Failed to open directory %s/%s: (%d: %s)", dir, base, errno, strerror(errno)); |
104 | 0 | if (errno == ENOTDIR || errno == ELOOP) |
105 | 0 | log_err(cd, _("Locking aborted. The locking path %s/%s is unusable (%s is not a directory)."), dir, base, base); |
106 | 0 | } |
107 | 0 | } |
108 | |
|
109 | 0 | close(dirfd); |
110 | 0 | return lockdfd >= 0 ? lockdfd : -EINVAL; |
111 | 0 | } |
112 | | |
113 | | static int open_resource(struct crypt_device *cd, const char *res) |
114 | 0 | { |
115 | 0 | int err, lockdir_fd, r; |
116 | 0 | char dir[] = DEFAULT_LUKS2_LOCK_PATH, |
117 | 0 | base[] = DEFAULT_LUKS2_LOCK_PATH; |
118 | |
|
119 | 0 | lockdir_fd = open_lock_dir(cd, dirname(dir), basename(base)); |
120 | 0 | if (lockdir_fd < 0) |
121 | 0 | return -EINVAL; |
122 | | |
123 | 0 | log_dbg(cd, "Opening lock resource file %s/%s", DEFAULT_LUKS2_LOCK_PATH, res); |
124 | 0 | r = openat(lockdir_fd, res, O_CREAT|O_NOFOLLOW|O_RDWR|O_CLOEXEC, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); |
125 | 0 | err = errno; |
126 | |
|
127 | 0 | close(lockdir_fd); |
128 | |
|
129 | 0 | return r < 0 ? -err : r; |
130 | 0 | } |
131 | | |
132 | | static int acquire_lock_handle(struct crypt_device *cd, struct device *device, struct crypt_lock_handle *h) |
133 | 10.7k | { |
134 | 10.7k | char res[PATH_MAX]; |
135 | 10.7k | int dev_fd, fd; |
136 | 10.7k | struct stat st; |
137 | | |
138 | 10.7k | dev_fd = open(device_path(device), O_RDONLY | O_NONBLOCK | O_CLOEXEC); |
139 | 10.7k | if (dev_fd < 0) |
140 | 0 | return -EINVAL; |
141 | | |
142 | 10.7k | if (fstat(dev_fd, &st)) { |
143 | 0 | close(dev_fd); |
144 | 0 | return -EINVAL; |
145 | 0 | } |
146 | | |
147 | 10.7k | if (S_ISBLK(st.st_mode)) { |
148 | 0 | if (resource_by_devno(res, sizeof(res), st.st_rdev, 0)) { |
149 | 0 | close(dev_fd); |
150 | 0 | return -EINVAL; |
151 | 0 | } |
152 | | |
153 | 0 | fd = open_resource(cd, res); |
154 | 0 | close(dev_fd); |
155 | 0 | if (fd < 0) |
156 | 0 | return fd; |
157 | | |
158 | 0 | h->flock_fd = fd; |
159 | 0 | h->u.bdev.devno = st.st_rdev; |
160 | 0 | h->mode = DEV_LOCK_BDEV; |
161 | 10.7k | } else if (S_ISREG(st.st_mode)) { |
162 | | /* workaround for nfsv4 */ |
163 | 10.7k | fd = open(device_path(device), O_RDWR | O_NONBLOCK | O_CLOEXEC); |
164 | 10.7k | if (fd < 0) |
165 | 0 | h->flock_fd = dev_fd; |
166 | 10.7k | else { |
167 | 10.7k | h->flock_fd = fd; |
168 | 10.7k | close(dev_fd); |
169 | 10.7k | } |
170 | 10.7k | h->mode = DEV_LOCK_FILE; |
171 | 10.7k | } else { |
172 | | /* Wrong device type */ |
173 | 0 | close(dev_fd); |
174 | 0 | return -EINVAL; |
175 | 0 | } |
176 | | |
177 | 10.7k | return 0; |
178 | 10.7k | } |
179 | | |
180 | | static int acquire_lock_handle_by_name(struct crypt_device *cd, const char *name, struct crypt_lock_handle *h) |
181 | 0 | { |
182 | 0 | char res[PATH_MAX]; |
183 | 0 | int fd; |
184 | |
|
185 | 0 | h->u.name.name = strdup(name); |
186 | 0 | if (!h->u.name.name) |
187 | 0 | return -ENOMEM; |
188 | | |
189 | 0 | if (resource_by_name(res, sizeof(res), name, false)) { |
190 | 0 | free(h->u.name.name); |
191 | 0 | return -EINVAL; |
192 | 0 | } |
193 | | |
194 | 0 | fd = open_resource(cd, res); |
195 | 0 | if (fd < 0) { |
196 | 0 | free(h->u.name.name); |
197 | 0 | return fd; |
198 | 0 | } |
199 | | |
200 | 0 | h->flock_fd = fd; |
201 | 0 | h->mode = DEV_LOCK_NAME; |
202 | |
|
203 | 0 | return 0; |
204 | 0 | } |
205 | | |
206 | | static void release_lock_handle(struct crypt_device *cd, struct crypt_lock_handle *h) |
207 | 10.7k | { |
208 | 10.7k | char res[PATH_MAX]; |
209 | 10.7k | struct stat buf_a, buf_b; |
210 | | |
211 | 10.7k | if ((h->mode == DEV_LOCK_NAME) && /* was it name lock */ |
212 | 0 | !flock(h->flock_fd, LOCK_EX | LOCK_NB) && /* lock to drop the file */ |
213 | 0 | !resource_by_name(res, sizeof(res), h->u.name.name, true) && /* acquire lock resource name */ |
214 | 0 | !fstat(h->flock_fd, &buf_a) && /* read inode id referred by fd */ |
215 | 0 | !stat(res, &buf_b) && /* does path file still exist? */ |
216 | 0 | same_inode(buf_a, buf_b)) { /* is it same id as the one referenced by fd? */ |
217 | | /* coverity[toctou] */ |
218 | 0 | if (unlink(res)) /* yes? unlink the file. lgtm[cpp/toctou-race-condition] */ |
219 | 0 | log_dbg(cd, "Failed to unlink resource file: %s", res); |
220 | 0 | } |
221 | | |
222 | 10.7k | if ((h->mode == DEV_LOCK_BDEV) && /* was it block device */ |
223 | 0 | !flock(h->flock_fd, LOCK_EX | LOCK_NB) && /* lock to drop the file */ |
224 | 0 | !resource_by_devno(res, sizeof(res), h->u.bdev.devno, 1) && /* acquire lock resource name */ |
225 | 0 | !fstat(h->flock_fd, &buf_a) && /* read inode id referred by fd */ |
226 | 0 | !stat(res, &buf_b) && /* does path file still exist? */ |
227 | 0 | same_inode(buf_a, buf_b)) { /* is it same id as the one referenced by fd? */ |
228 | | /* coverity[toctou] */ |
229 | 0 | if (unlink(res)) /* yes? unlink the file. lgtm[cpp/toctou-race-condition] */ |
230 | 0 | log_dbg(cd, "Failed to unlink resource file: %s", res); |
231 | 0 | } |
232 | | |
233 | 10.7k | if (h->mode == DEV_LOCK_NAME) |
234 | 0 | free(h->u.name.name); |
235 | | |
236 | 10.7k | if (close(h->flock_fd)) |
237 | 0 | log_dbg(cd, "Failed to close lock resource fd (%d).", h->flock_fd); |
238 | 10.7k | } |
239 | | |
240 | | int device_locked(struct crypt_lock_handle *h) |
241 | 100k | { |
242 | 100k | return (h && (h->type == DEV_LOCK_READ || h->type == DEV_LOCK_WRITE)); |
243 | 100k | } |
244 | | |
245 | | int device_locked_readonly(struct crypt_lock_handle *h) |
246 | 17.3k | { |
247 | 17.3k | return (h && h->type == DEV_LOCK_READ); |
248 | 17.3k | } |
249 | | |
250 | | static int verify_lock_handle(struct crypt_lock_handle *h) |
251 | 10.7k | { |
252 | 10.7k | char res[PATH_MAX]; |
253 | 10.7k | struct stat lck_st, res_st; |
254 | | |
255 | | /* we locked a regular file, check during device_open() instead. No reason to check now */ |
256 | 10.7k | if (h->mode == DEV_LOCK_FILE) |
257 | 10.7k | return 0; |
258 | | |
259 | 0 | if (h->mode == DEV_LOCK_NAME) { |
260 | 0 | if (resource_by_name(res, sizeof(res), h->u.name.name, true)) |
261 | 0 | return -EINVAL; |
262 | 0 | } else if (h->mode == DEV_LOCK_BDEV) { |
263 | 0 | if (resource_by_devno(res, sizeof(res), h->u.bdev.devno, true)) |
264 | 0 | return -EINVAL; |
265 | 0 | } else |
266 | 0 | return -EINVAL; |
267 | | |
268 | 0 | if (fstat(h->flock_fd, &lck_st)) |
269 | 0 | return -EINVAL; |
270 | | |
271 | 0 | return (stat(res, &res_st) || !same_inode(lck_st, res_st)) ? -EAGAIN : 0; |
272 | 0 | } |
273 | | |
274 | | static unsigned device_lock_inc(struct crypt_lock_handle *h) |
275 | 0 | { |
276 | 0 | return ++h->refcnt; |
277 | 0 | } |
278 | | |
279 | | static unsigned device_lock_dec(struct crypt_lock_handle *h) |
280 | 10.7k | { |
281 | 10.7k | assert(h->refcnt); |
282 | | |
283 | 10.7k | return --h->refcnt; |
284 | 10.7k | } |
285 | | |
286 | | static int acquire_and_verify(struct crypt_device *cd, struct device *device, const char *resource, int flock_op, struct crypt_lock_handle **lock) |
287 | 10.7k | { |
288 | 10.7k | int r; |
289 | 10.7k | struct crypt_lock_handle *h; |
290 | | |
291 | 10.7k | if (device && resource) |
292 | 0 | return -EINVAL; |
293 | | |
294 | 10.7k | if (!(h = malloc(sizeof(*h)))) |
295 | 0 | return -ENOMEM; |
296 | | |
297 | 10.7k | do { |
298 | 10.7k | r = device ? acquire_lock_handle(cd, device, h) : acquire_lock_handle_by_name(cd, resource, h); |
299 | 10.7k | if (r < 0) |
300 | 0 | break; |
301 | | |
302 | 10.7k | if (flock(h->flock_fd, flock_op)) { |
303 | 0 | log_dbg(cd, "Flock on fd %d failed with errno %d.", h->flock_fd, errno); |
304 | 0 | r = (errno == EWOULDBLOCK) ? -EBUSY : -EINVAL; |
305 | 0 | release_lock_handle(cd, h); |
306 | 0 | break; |
307 | 0 | } |
308 | | |
309 | 10.7k | log_dbg(cd, "Verifying lock handle for %s.", device ? device_path(device) : resource); |
310 | | |
311 | | /* |
312 | | * check whether another libcryptsetup process removed resource file before this |
313 | | * one managed to flock() it. See release_lock_handle() for details |
314 | | */ |
315 | 10.7k | r = verify_lock_handle(h); |
316 | 10.7k | if (r < 0) { |
317 | 0 | if (flock(h->flock_fd, LOCK_UN)) |
318 | 0 | log_dbg(cd, "flock on fd %d failed.", h->flock_fd); |
319 | 0 | release_lock_handle(cd, h); |
320 | 0 | log_dbg(cd, "Lock handle verification failed."); |
321 | 0 | } |
322 | 10.7k | } while (r == -EAGAIN); |
323 | | |
324 | 10.7k | if (r < 0) { |
325 | 0 | free(h); |
326 | 0 | return r; |
327 | 0 | } |
328 | | |
329 | 10.7k | *lock = h; |
330 | | |
331 | 10.7k | return 0; |
332 | 10.7k | } |
333 | | |
334 | | int device_read_lock_internal(struct crypt_device *cd, struct device *device) |
335 | 8.57k | { |
336 | 8.57k | int r; |
337 | 8.57k | struct crypt_lock_handle *h; |
338 | | |
339 | 8.57k | if (!device) |
340 | 0 | return -EINVAL; |
341 | | |
342 | 8.57k | h = device_get_lock_handle(device); |
343 | | |
344 | 8.57k | if (device_locked(h)) { |
345 | 0 | device_lock_inc(h); |
346 | 0 | log_dbg(cd, "Device %s READ lock (or higher) already held.", device_path(device)); |
347 | 0 | return 0; |
348 | 0 | } |
349 | | |
350 | 8.57k | log_dbg(cd, "Acquiring read lock for device %s.", device_path(device)); |
351 | | |
352 | 8.57k | r = acquire_and_verify(cd, device, NULL, LOCK_SH, &h); |
353 | 8.57k | if (r < 0) |
354 | 0 | return r; |
355 | | |
356 | 8.57k | h->type = DEV_LOCK_READ; |
357 | 8.57k | h->refcnt = 1; |
358 | 8.57k | device_set_lock_handle(device, h); |
359 | | |
360 | 8.57k | log_dbg(cd, "Device %s READ lock taken.", device_path(device)); |
361 | | |
362 | 8.57k | return 0; |
363 | 8.57k | } |
364 | | |
365 | | int device_write_lock_internal(struct crypt_device *cd, struct device *device) |
366 | 2.19k | { |
367 | 2.19k | int r; |
368 | 2.19k | struct crypt_lock_handle *h; |
369 | | |
370 | 2.19k | if (!device) |
371 | 0 | return -EINVAL; |
372 | | |
373 | 2.19k | h = device_get_lock_handle(device); |
374 | | |
375 | 2.19k | if (device_locked(h)) { |
376 | 0 | log_dbg(cd, "Device %s WRITE lock already held.", device_path(device)); |
377 | 0 | return device_lock_inc(h); |
378 | 0 | } |
379 | | |
380 | 2.19k | log_dbg(cd, "Acquiring write lock for device %s.", device_path(device)); |
381 | | |
382 | 2.19k | r = acquire_and_verify(cd, device, NULL, LOCK_EX, &h); |
383 | 2.19k | if (r < 0) |
384 | 0 | return r; |
385 | | |
386 | 2.19k | h->type = DEV_LOCK_WRITE; |
387 | 2.19k | h->refcnt = 1; |
388 | 2.19k | device_set_lock_handle(device, h); |
389 | | |
390 | 2.19k | log_dbg(cd, "Device %s WRITE lock taken.", device_path(device)); |
391 | | |
392 | 2.19k | return 1; |
393 | 2.19k | } |
394 | | |
395 | | int crypt_write_lock(struct crypt_device *cd, const char *resource, bool blocking, struct crypt_lock_handle **lock) |
396 | 0 | { |
397 | 0 | int r; |
398 | 0 | struct crypt_lock_handle *h; |
399 | |
|
400 | 0 | if (!resource) |
401 | 0 | return -EINVAL; |
402 | | |
403 | 0 | log_dbg(cd, "Acquiring %sblocking write lock for resource %s.", blocking ? "" : "non", resource); |
404 | |
|
405 | 0 | r = acquire_and_verify(cd, NULL, resource, LOCK_EX | (blocking ? 0 : LOCK_NB), &h); |
406 | 0 | if (r < 0) |
407 | 0 | return r; |
408 | | |
409 | 0 | h->type = DEV_LOCK_WRITE; |
410 | 0 | h->refcnt = 1; |
411 | |
|
412 | 0 | log_dbg(cd, "WRITE lock for resource %s taken.", resource); |
413 | |
|
414 | 0 | *lock = h; |
415 | |
|
416 | 0 | return 0; |
417 | 0 | } |
418 | | |
419 | | static void unlock_internal(struct crypt_device *cd, struct crypt_lock_handle *h) |
420 | 10.7k | { |
421 | 10.7k | if (flock(h->flock_fd, LOCK_UN)) |
422 | 0 | log_dbg(cd, "flock on fd %d failed.", h->flock_fd); |
423 | 10.7k | release_lock_handle(cd, h); |
424 | 10.7k | free(h); |
425 | 10.7k | } |
426 | | |
427 | | void crypt_unlock_internal(struct crypt_device *cd, struct crypt_lock_handle *h) |
428 | 0 | { |
429 | 0 | if (!h) |
430 | 0 | return; |
431 | | |
432 | | /* nested locks are illegal */ |
433 | 0 | assert(!device_lock_dec(h)); |
434 | |
|
435 | 0 | log_dbg(cd, "Unlocking %s lock for resource %s.", |
436 | 0 | device_locked_readonly(h) ? "READ" : "WRITE", h->u.name.name); |
437 | |
|
438 | 0 | unlock_internal(cd, h); |
439 | 0 | } |
440 | | |
441 | | void device_unlock_internal(struct crypt_device *cd, struct device *device) |
442 | 10.7k | { |
443 | 10.7k | bool readonly; |
444 | 10.7k | struct crypt_lock_handle *h = device_get_lock_handle(device); |
445 | 10.7k | unsigned u = device_lock_dec(h); |
446 | | |
447 | 10.7k | if (u) |
448 | 0 | return; |
449 | | |
450 | 10.7k | readonly = device_locked_readonly(h); |
451 | | |
452 | 10.7k | unlock_internal(cd, h); |
453 | | |
454 | 10.7k | log_dbg(cd, "Device %s %s lock released.", device_path(device), |
455 | 10.7k | readonly ? "READ" : "WRITE"); |
456 | | |
457 | 10.7k | device_set_lock_handle(device, NULL); |
458 | 10.7k | } |
459 | | |
460 | | int device_locked_verify(struct crypt_device *cd, int dev_fd, struct crypt_lock_handle *h) |
461 | 10.7k | { |
462 | 10.7k | char res[PATH_MAX]; |
463 | 10.7k | struct stat dev_st, lck_st, st; |
464 | | |
465 | 10.7k | if (fstat(dev_fd, &dev_st) || fstat(h->flock_fd, &lck_st)) |
466 | 0 | return 1; |
467 | | |
468 | | /* if device handle is regular file the handle must match the lock handle */ |
469 | 10.7k | if (S_ISREG(dev_st.st_mode)) { |
470 | 10.7k | log_dbg(cd, "Verifying locked device handle (regular file)"); |
471 | 10.7k | if (!same_inode(dev_st, lck_st)) |
472 | 0 | return 1; |
473 | 10.7k | } else if (S_ISBLK(dev_st.st_mode)) { |
474 | 0 | log_dbg(cd, "Verifying locked device handle (bdev)"); |
475 | 0 | if (resource_by_devno(res, sizeof(res), dev_st.st_rdev, 1) || |
476 | 0 | stat(res, &st) || |
477 | 0 | !same_inode(lck_st, st)) |
478 | 0 | return 1; |
479 | 0 | } else |
480 | 0 | return 1; |
481 | | |
482 | 10.7k | return 0; |
483 | 10.7k | } |