/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-2026 Red Hat, Inc. All rights reserved. |
6 | | * Copyright (C) 2016-2026 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 | 1.77k | ((buf1).st_ino == (buf2).st_ino && \ |
28 | 1.77k | (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 | 7.07k | { |
134 | 7.07k | char res[PATH_MAX]; |
135 | 7.07k | int dev_fd, fd; |
136 | 7.07k | struct stat st; |
137 | | |
138 | 7.07k | assert(device); |
139 | 7.07k | assert(h); |
140 | | |
141 | 7.07k | dev_fd = open(device_path(device), O_RDONLY | O_NONBLOCK | O_CLOEXEC); |
142 | 7.07k | if (dev_fd < 0) |
143 | 0 | return -EINVAL; |
144 | | |
145 | 7.07k | if (fstat(dev_fd, &st)) { |
146 | 0 | close(dev_fd); |
147 | 0 | return -EINVAL; |
148 | 0 | } |
149 | | |
150 | 7.07k | if (S_ISBLK(st.st_mode)) { |
151 | 0 | if (resource_by_devno(res, sizeof(res), st.st_rdev, 0)) { |
152 | 0 | close(dev_fd); |
153 | 0 | return -EINVAL; |
154 | 0 | } |
155 | | |
156 | 0 | fd = open_resource(cd, res); |
157 | 0 | close(dev_fd); |
158 | 0 | if (fd < 0) |
159 | 0 | return fd; |
160 | | |
161 | 0 | h->flock_fd = fd; |
162 | 0 | h->u.bdev.devno = st.st_rdev; |
163 | 0 | h->mode = DEV_LOCK_BDEV; |
164 | 7.07k | } else if (S_ISREG(st.st_mode)) { |
165 | | /* workaround for nfsv4 */ |
166 | 7.07k | fd = open(device_path(device), O_RDWR | O_NONBLOCK | O_CLOEXEC); |
167 | 7.07k | if (fd < 0) |
168 | 0 | h->flock_fd = dev_fd; |
169 | 7.07k | else { |
170 | 7.07k | h->flock_fd = fd; |
171 | 7.07k | close(dev_fd); |
172 | 7.07k | } |
173 | 7.07k | h->mode = DEV_LOCK_FILE; |
174 | 7.07k | } else { |
175 | | /* Wrong device type */ |
176 | 0 | close(dev_fd); |
177 | 0 | return -EINVAL; |
178 | 0 | } |
179 | | |
180 | 7.07k | return 0; |
181 | 7.07k | } |
182 | | |
183 | | static int acquire_lock_handle_by_name(struct crypt_device *cd, const char *name, struct crypt_lock_handle *h) |
184 | 0 | { |
185 | 0 | char res[PATH_MAX]; |
186 | 0 | int fd; |
187 | |
|
188 | 0 | assert(name); |
189 | 0 | assert(h); |
190 | |
|
191 | 0 | h->u.name.name = strdup(name); |
192 | 0 | if (!h->u.name.name) |
193 | 0 | return -ENOMEM; |
194 | | |
195 | 0 | if (resource_by_name(res, sizeof(res), name, false)) { |
196 | 0 | free(h->u.name.name); |
197 | 0 | return -EINVAL; |
198 | 0 | } |
199 | | |
200 | 0 | fd = open_resource(cd, res); |
201 | 0 | if (fd < 0) { |
202 | 0 | free(h->u.name.name); |
203 | 0 | return fd; |
204 | 0 | } |
205 | | |
206 | 0 | h->flock_fd = fd; |
207 | 0 | h->mode = DEV_LOCK_NAME; |
208 | |
|
209 | 0 | return 0; |
210 | 0 | } |
211 | | |
212 | | static void release_lock_handle(struct crypt_device *cd, struct crypt_lock_handle *h) |
213 | 7.07k | { |
214 | 7.07k | char res[PATH_MAX]; |
215 | 7.07k | struct stat buf_a, buf_b; |
216 | | |
217 | 7.07k | assert(h); |
218 | | |
219 | 7.07k | if ((h->mode == DEV_LOCK_NAME) && /* was it name lock */ |
220 | 0 | !flock(h->flock_fd, LOCK_EX | LOCK_NB) && /* lock to drop the file */ |
221 | 0 | !resource_by_name(res, sizeof(res), h->u.name.name, true) && /* acquire lock resource name */ |
222 | 0 | !fstat(h->flock_fd, &buf_a) && /* read inode id referred by fd */ |
223 | 0 | !stat(res, &buf_b) && /* does path file still exist? */ |
224 | 0 | same_inode(buf_a, buf_b)) { /* is it same id as the one referenced by fd? */ |
225 | | /* coverity[toctou] */ |
226 | 0 | if (unlink(res)) /* yes? unlink the file. lgtm[cpp/toctou-race-condition] */ |
227 | 0 | log_dbg(cd, "Failed to unlink resource file: %s", res); |
228 | 0 | } |
229 | | |
230 | 7.07k | if ((h->mode == DEV_LOCK_BDEV) && /* was it block device */ |
231 | 0 | !flock(h->flock_fd, LOCK_EX | LOCK_NB) && /* lock to drop the file */ |
232 | 0 | !resource_by_devno(res, sizeof(res), h->u.bdev.devno, 1) && /* acquire lock resource name */ |
233 | 0 | !fstat(h->flock_fd, &buf_a) && /* read inode id referred by fd */ |
234 | 0 | !stat(res, &buf_b) && /* does path file still exist? */ |
235 | 0 | same_inode(buf_a, buf_b)) { /* is it same id as the one referenced by fd? */ |
236 | | /* coverity[toctou] */ |
237 | 0 | if (unlink(res)) /* yes? unlink the file. lgtm[cpp/toctou-race-condition] */ |
238 | 0 | log_dbg(cd, "Failed to unlink resource file: %s", res); |
239 | 0 | } |
240 | | |
241 | 7.07k | if (h->mode == DEV_LOCK_NAME) |
242 | 0 | free(h->u.name.name); |
243 | | |
244 | 7.07k | if (close(h->flock_fd)) |
245 | 0 | log_dbg(cd, "Failed to close lock resource fd (%d).", h->flock_fd); |
246 | 7.07k | } |
247 | | |
248 | | int device_locked(struct crypt_lock_handle *h) |
249 | 84.7k | { |
250 | 84.7k | return (h && (h->type == DEV_LOCK_READ || h->type == DEV_LOCK_WRITE)); |
251 | 84.7k | } |
252 | | |
253 | | int device_locked_readonly(struct crypt_lock_handle *h) |
254 | 12.4k | { |
255 | 12.4k | return (h && h->type == DEV_LOCK_READ); |
256 | 12.4k | } |
257 | | |
258 | | static int verify_lock_handle(struct crypt_lock_handle *h) |
259 | 7.07k | { |
260 | 7.07k | char res[PATH_MAX]; |
261 | 7.07k | struct stat lck_st, res_st; |
262 | | |
263 | 7.07k | assert(h); |
264 | | |
265 | | /* we locked a regular file, check during device_open() instead. No reason to check now */ |
266 | 7.07k | if (h->mode == DEV_LOCK_FILE) |
267 | 7.07k | return 0; |
268 | | |
269 | 0 | if (h->mode == DEV_LOCK_NAME) { |
270 | 0 | if (resource_by_name(res, sizeof(res), h->u.name.name, true)) |
271 | 0 | return -EINVAL; |
272 | 0 | } else if (h->mode == DEV_LOCK_BDEV) { |
273 | 0 | if (resource_by_devno(res, sizeof(res), h->u.bdev.devno, true)) |
274 | 0 | return -EINVAL; |
275 | 0 | } else |
276 | 0 | return -EINVAL; |
277 | | |
278 | 0 | if (fstat(h->flock_fd, &lck_st)) |
279 | 0 | return -EINVAL; |
280 | | |
281 | 0 | return (stat(res, &res_st) || !same_inode(lck_st, res_st)) ? -EAGAIN : 0; |
282 | 0 | } |
283 | | |
284 | | static unsigned device_lock_inc(struct crypt_lock_handle *h) |
285 | 0 | { |
286 | 0 | return ++h->refcnt; |
287 | 0 | } |
288 | | |
289 | | static unsigned device_lock_dec(struct crypt_lock_handle *h) |
290 | 7.07k | { |
291 | 7.07k | assert(h->refcnt); |
292 | | |
293 | 7.07k | return --h->refcnt; |
294 | 7.07k | } |
295 | | |
296 | | static int acquire_and_verify(struct crypt_device *cd, struct device *device, const char *resource, int flock_op, struct crypt_lock_handle **lock) |
297 | 7.07k | { |
298 | 7.07k | int r; |
299 | 7.07k | struct crypt_lock_handle *h; |
300 | | |
301 | 7.07k | if (device && resource) |
302 | 0 | return -EINVAL; |
303 | | |
304 | 7.07k | if (!(h = malloc(sizeof(*h)))) |
305 | 0 | return -ENOMEM; |
306 | | |
307 | 7.07k | do { |
308 | 7.07k | r = device ? acquire_lock_handle(cd, device, h) : acquire_lock_handle_by_name(cd, resource, h); |
309 | 7.07k | if (r < 0) |
310 | 0 | break; |
311 | | |
312 | 7.07k | if (flock(h->flock_fd, flock_op)) { |
313 | 0 | log_dbg(cd, "Flock on fd %d failed with errno %d.", h->flock_fd, errno); |
314 | 0 | r = (errno == EWOULDBLOCK) ? -EBUSY : -EINVAL; |
315 | 0 | release_lock_handle(cd, h); |
316 | 0 | break; |
317 | 0 | } |
318 | | |
319 | 7.07k | log_dbg(cd, "Verifying lock handle for %s.", device ? device_path(device) : resource); |
320 | | |
321 | | /* |
322 | | * check whether another libcryptsetup process removed resource file before this |
323 | | * one managed to flock() it. See release_lock_handle() for details |
324 | | */ |
325 | 7.07k | r = verify_lock_handle(h); |
326 | 7.07k | if (r < 0) { |
327 | 0 | if (flock(h->flock_fd, LOCK_UN)) |
328 | 0 | log_dbg(cd, "flock on fd %d failed.", h->flock_fd); |
329 | 0 | release_lock_handle(cd, h); |
330 | 0 | log_dbg(cd, "Lock handle verification failed."); |
331 | 0 | } |
332 | 7.07k | } while (r == -EAGAIN); |
333 | | |
334 | 7.07k | if (r < 0) { |
335 | 0 | free(h); |
336 | 0 | return r; |
337 | 0 | } |
338 | | |
339 | 7.07k | *lock = h; |
340 | | |
341 | 7.07k | return 0; |
342 | 7.07k | } |
343 | | |
344 | | int device_read_lock_internal(struct crypt_device *cd, struct device *device) |
345 | 5.29k | { |
346 | 5.29k | int r; |
347 | 5.29k | struct crypt_lock_handle *h; |
348 | | |
349 | 5.29k | if (!device) |
350 | 0 | return -EINVAL; |
351 | | |
352 | 5.29k | h = device_get_lock_handle(device); |
353 | | |
354 | 5.29k | if (device_locked(h)) { |
355 | 0 | device_lock_inc(h); |
356 | 0 | log_dbg(cd, "Device %s READ lock (or higher) already held.", device_path(device)); |
357 | 0 | return 0; |
358 | 0 | } |
359 | | |
360 | 5.29k | log_dbg(cd, "Acquiring read lock for device %s.", device_path(device)); |
361 | | |
362 | 5.29k | r = acquire_and_verify(cd, device, NULL, LOCK_SH, &h); |
363 | 5.29k | if (r < 0) |
364 | 0 | return r; |
365 | | |
366 | 5.29k | h->type = DEV_LOCK_READ; |
367 | 5.29k | h->refcnt = 1; |
368 | 5.29k | device_set_lock_handle(device, h); |
369 | | |
370 | 5.29k | log_dbg(cd, "Device %s READ lock taken.", device_path(device)); |
371 | | |
372 | 5.29k | return 0; |
373 | 5.29k | } |
374 | | |
375 | | int device_write_lock_internal(struct crypt_device *cd, struct device *device) |
376 | 1.77k | { |
377 | 1.77k | int r; |
378 | 1.77k | struct crypt_lock_handle *h; |
379 | | |
380 | 1.77k | if (!device) |
381 | 0 | return -EINVAL; |
382 | | |
383 | 1.77k | h = device_get_lock_handle(device); |
384 | | |
385 | 1.77k | if (device_locked(h)) { |
386 | 0 | log_dbg(cd, "Device %s WRITE lock already held.", device_path(device)); |
387 | 0 | return device_lock_inc(h); |
388 | 0 | } |
389 | | |
390 | 1.77k | log_dbg(cd, "Acquiring write lock for device %s.", device_path(device)); |
391 | | |
392 | 1.77k | r = acquire_and_verify(cd, device, NULL, LOCK_EX, &h); |
393 | 1.77k | if (r < 0) |
394 | 0 | return r; |
395 | | |
396 | 1.77k | h->type = DEV_LOCK_WRITE; |
397 | 1.77k | h->refcnt = 1; |
398 | 1.77k | device_set_lock_handle(device, h); |
399 | | |
400 | 1.77k | log_dbg(cd, "Device %s WRITE lock taken.", device_path(device)); |
401 | | |
402 | 1.77k | return 1; |
403 | 1.77k | } |
404 | | |
405 | | int crypt_write_lock(struct crypt_device *cd, const char *resource, bool blocking, struct crypt_lock_handle **lock) |
406 | 0 | { |
407 | 0 | int r; |
408 | 0 | struct crypt_lock_handle *h; |
409 | |
|
410 | 0 | if (!resource) |
411 | 0 | return -EINVAL; |
412 | | |
413 | 0 | log_dbg(cd, "Acquiring %sblocking write lock for resource %s.", blocking ? "" : "non", resource); |
414 | |
|
415 | 0 | r = acquire_and_verify(cd, NULL, resource, LOCK_EX | (blocking ? 0 : LOCK_NB), &h); |
416 | 0 | if (r < 0) |
417 | 0 | return r; |
418 | | |
419 | 0 | h->type = DEV_LOCK_WRITE; |
420 | 0 | h->refcnt = 1; |
421 | |
|
422 | 0 | log_dbg(cd, "WRITE lock for resource %s taken.", resource); |
423 | |
|
424 | 0 | *lock = h; |
425 | |
|
426 | 0 | return 0; |
427 | 0 | } |
428 | | |
429 | | static void unlock_internal(struct crypt_device *cd, struct crypt_lock_handle *h) |
430 | 7.07k | { |
431 | 7.07k | if (flock(h->flock_fd, LOCK_UN)) |
432 | 0 | log_dbg(cd, "flock on fd %d failed.", h->flock_fd); |
433 | 7.07k | release_lock_handle(cd, h); |
434 | 7.07k | free(h); |
435 | 7.07k | } |
436 | | |
437 | | void crypt_unlock_internal(struct crypt_device *cd, struct crypt_lock_handle *h) |
438 | 0 | { |
439 | 0 | if (!h) |
440 | 0 | return; |
441 | | |
442 | | /* nested locks are illegal */ |
443 | 0 | assert(!device_lock_dec(h)); |
444 | |
|
445 | 0 | log_dbg(cd, "Unlocking %s lock for resource %s.", |
446 | 0 | device_locked_readonly(h) ? "READ" : "WRITE", h->u.name.name); |
447 | |
|
448 | 0 | unlock_internal(cd, h); |
449 | 0 | } |
450 | | |
451 | | void device_unlock_internal(struct crypt_device *cd, struct device *device) |
452 | 7.07k | { |
453 | 7.07k | bool readonly; |
454 | 7.07k | struct crypt_lock_handle *h = device_get_lock_handle(device); |
455 | 7.07k | unsigned u = device_lock_dec(h); |
456 | | |
457 | 7.07k | if (u) |
458 | 0 | return; |
459 | | |
460 | 7.07k | readonly = device_locked_readonly(h); |
461 | | |
462 | 7.07k | unlock_internal(cd, h); |
463 | | |
464 | 7.07k | log_dbg(cd, "Device %s %s lock released.", device_path(device), |
465 | 7.07k | readonly ? "READ" : "WRITE"); |
466 | | |
467 | 7.07k | device_set_lock_handle(device, NULL); |
468 | 7.07k | } |
469 | | |
470 | | int device_locked_verify(struct crypt_device *cd, int dev_fd, struct crypt_lock_handle *h) |
471 | 1.77k | { |
472 | 1.77k | char res[PATH_MAX]; |
473 | 1.77k | struct stat dev_st, lck_st, st; |
474 | | |
475 | 1.77k | if (fstat(dev_fd, &dev_st) || fstat(h->flock_fd, &lck_st)) |
476 | 0 | return 1; |
477 | | |
478 | | /* if device handle is regular file the handle must match the lock handle */ |
479 | 1.77k | if (S_ISREG(dev_st.st_mode)) { |
480 | 1.77k | log_dbg(cd, "Verifying locked device handle (regular file)"); |
481 | 1.77k | if (!same_inode(dev_st, lck_st)) |
482 | 0 | return 1; |
483 | 1.77k | } else if (S_ISBLK(dev_st.st_mode)) { |
484 | 0 | log_dbg(cd, "Verifying locked device handle (bdev)"); |
485 | 0 | if (resource_by_devno(res, sizeof(res), dev_st.st_rdev, 1) || |
486 | 0 | stat(res, &st) || |
487 | 0 | !same_inode(lck_st, st)) |
488 | 0 | return 1; |
489 | 0 | } else |
490 | 0 | return 1; |
491 | | |
492 | 1.77k | return 0; |
493 | 1.77k | } |