/* * Metadata on-disk locking for processes serialization * * Copyright (C) 2016-2019 Red Hat, Inc. All rights reserved. * Copyright (C) 2016-2019 Ondrej Kozina * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_SYSMACROS_H # include /* for major, minor */ #endif #include #include "internal.h" #include "utils_device_locking.h" #define same_inode(buf1, buf2) \ ((buf1).st_ino == (buf2).st_ino && \ (buf1).st_dev == (buf2).st_dev) #ifndef __GNUC__ # define __typeof__ typeof #endif enum lock_type { DEV_LOCK_READ = 0, DEV_LOCK_WRITE }; struct crypt_lock_handle { dev_t devno; int flock_fd; enum lock_type type; __typeof__( ((struct stat*)0)->st_mode) mode; }; static int resource_by_devno(char *res, size_t res_size, dev_t devno, unsigned fullpath) { int r; if (fullpath) r = snprintf(res, res_size, "%s/L_%d:%d", DEFAULT_LUKS2_LOCK_PATH, major(devno), minor(devno)); else r = snprintf(res, res_size, "L_%d:%d", major(devno), minor(devno)); return (r < 0 || (size_t)r >= res_size) ? -EINVAL : 0; } static int open_lock_dir(struct crypt_device *cd, const char *dir, const char *base) { int dirfd, lockdfd; dirfd = open(dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC); if (dirfd < 0) { log_dbg(cd, "Failed to open directory %s: (%d: %s).", dir, errno, strerror(errno)); if (errno == ENOTDIR || errno == ENOENT) log_err(cd, _("Locking aborted. The locking path %s/%s is unusable (not a directory or missing)."), dir, base); return -EINVAL; } lockdfd = openat(dirfd, base, O_RDONLY | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC); if (lockdfd < 0) { if (errno == ENOENT) { log_std(cd, _("WARNING: Locking directory %s/%s is missing!\n"), dir, base); /* success or failure w/ errno == EEXIST either way just try to open the 'base' directory again */ if (mkdirat(dirfd, base, DEFAULT_LUKS2_LOCK_DIR_PERMS) && errno != EEXIST) log_dbg(cd, "Failed to create directory %s in %s (%d: %s).", base, dir, errno, strerror(errno)); else lockdfd = openat(dirfd, base, O_RDONLY | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC); } else { log_dbg(cd, "Failed to open directory %s/%s: (%d: %s)", dir, base, errno, strerror(errno)); if (errno == ENOTDIR || errno == ELOOP) log_err(cd, _("Locking aborted. The locking path %s/%s is unusable (%s is not a directory)."), dir, base, base); } } close(dirfd); return lockdfd >= 0 ? lockdfd : -EINVAL; } static int open_resource(struct crypt_device *cd, const char *res) { int err, lockdir_fd, r; char dir[] = DEFAULT_LUKS2_LOCK_PATH, base[] = DEFAULT_LUKS2_LOCK_PATH; lockdir_fd = open_lock_dir(cd, dirname(dir), basename(base)); if (lockdir_fd < 0) return -EINVAL; log_dbg(cd, "Opening lock resource file %s/%s", DEFAULT_LUKS2_LOCK_PATH, res); r = openat(lockdir_fd, res, O_CREAT | O_NOFOLLOW | O_RDWR | O_CLOEXEC, 0777); err = errno; close(lockdir_fd); return r < 0 ? -err : r; } static int acquire_lock_handle(struct crypt_device *cd, const char *device_path, struct crypt_lock_handle *h) { char res[PATH_MAX]; int dev_fd, fd; struct stat st; dev_fd = open(device_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); if (dev_fd < 0) return -EINVAL; if (fstat(dev_fd, &st)) { close(dev_fd); return -EINVAL; } if (S_ISBLK(st.st_mode)) { if (resource_by_devno(res, sizeof(res), st.st_rdev, 0)) { close(dev_fd); return -EINVAL; } fd = open_resource(cd, res); close(dev_fd); if (fd < 0) return fd; h->flock_fd = fd; h->devno = st.st_rdev; } else if (S_ISREG(st.st_mode)) { // FIXME: workaround for nfsv4 fd = open(device_path, O_RDWR | O_NONBLOCK | O_CLOEXEC); if (fd < 0) h->flock_fd = dev_fd; else { h->flock_fd = fd; close(dev_fd); } } else { /* Wrong device type */ close(dev_fd); return -EINVAL; } h->mode = st.st_mode; return 0; } static void release_lock_handle(struct crypt_device *cd, struct crypt_lock_handle *h) { char res[PATH_MAX]; struct stat buf_a, buf_b; if (S_ISBLK(h->mode) && /* was it block device */ !flock(h->flock_fd, LOCK_EX | LOCK_NB) && /* lock to drop the file */ !resource_by_devno(res, sizeof(res), h->devno, 1) && /* acquire lock resource name */ !fstat(h->flock_fd, &buf_a) && /* read inode id referred by fd */ !stat(res, &buf_b) && /* does path file still exist? */ same_inode(buf_a, buf_b)) { /* is it same id as the one referenced by fd? */ /* coverity[toctou] */ if (unlink(res)) /* yes? unlink the file */ log_dbg(cd, "Failed to unlink resource file: %s", res); } if (close(h->flock_fd)) log_dbg(cd, "Failed to close resource fd (%d).", h->flock_fd); } int device_locked(struct crypt_lock_handle *h) { return (h && (h->type == DEV_LOCK_READ || h->type == DEV_LOCK_WRITE)); } int device_locked_readonly(struct crypt_lock_handle *h) { return (h && h->type == DEV_LOCK_READ); } static int verify_lock_handle(const char *device_path, struct crypt_lock_handle *h) { char res[PATH_MAX]; struct stat lck_st, res_st; /* we locked a regular file, check during device_open() instead. No reason to check now */ if (S_ISREG(h->mode)) return 0; if (resource_by_devno(res, sizeof(res), h->devno, 1)) return -EINVAL; if (fstat(h->flock_fd, &lck_st)) return -EINVAL; return (stat(res, &res_st) || !same_inode(lck_st, res_st)) ? -EAGAIN : 0; } struct crypt_lock_handle *device_read_lock_handle(struct crypt_device *cd, const char *device_path) { int r; struct crypt_lock_handle *h = malloc(sizeof(*h)); if (!h) return NULL; do { r = acquire_lock_handle(cd, device_path, h); if (r) break; log_dbg(cd, "Acquiring read lock for device %s.", device_path); if (flock(h->flock_fd, LOCK_SH)) { log_dbg(cd, "Shared flock failed with errno %d.", errno); r = -EINVAL; release_lock_handle(cd, h); break; } log_dbg(cd, "Verifying read lock handle for device %s.", device_path); /* * check whether another libcryptsetup process removed resource file before this * one managed to flock() it. See release_lock_handle() for details */ r = verify_lock_handle(device_path, h); if (r) { flock(h->flock_fd, LOCK_UN); release_lock_handle(cd, h); log_dbg(cd, "Read lock handle verification failed."); } } while (r == -EAGAIN); if (r) { free(h); return NULL; } h->type = DEV_LOCK_READ; return h; } struct crypt_lock_handle *device_write_lock_handle(struct crypt_device *cd, const char *device_path) { int r; struct crypt_lock_handle *h = malloc(sizeof(*h)); if (!h) return NULL; do { r = acquire_lock_handle(cd, device_path, h); if (r) break; log_dbg(cd, "Acquiring write lock for device %s.", device_path); if (flock(h->flock_fd, LOCK_EX)) { log_dbg(cd, "Exclusive flock failed with errno %d.", errno); r = -EINVAL; release_lock_handle(cd, h); break; } log_dbg(cd, "Verifying write lock handle for device %s.", device_path); /* * check whether another libcryptsetup process removed resource file before this * one managed to flock() it. See release_lock_handle() for details */ r = verify_lock_handle(device_path, h); if (r) { flock(h->flock_fd, LOCK_UN); release_lock_handle(cd, h); log_dbg(cd, "Write lock handle verification failed."); } } while (r == -EAGAIN); if (r) { free(h); return NULL; } h->type = DEV_LOCK_WRITE; return h; } void device_unlock_handle(struct crypt_device *cd, struct crypt_lock_handle *h) { if (flock(h->flock_fd, LOCK_UN)) log_dbg(cd, "flock on fd %d failed.", h->flock_fd); release_lock_handle(cd, h); free(h); } int device_locked_verify(struct crypt_device *cd, int dev_fd, struct crypt_lock_handle *h) { char res[PATH_MAX]; struct stat dev_st, lck_st, st; if (fstat(dev_fd, &dev_st) || fstat(h->flock_fd, &lck_st)) return 1; /* if device handle is regular file the handle must match the lock handle */ if (S_ISREG(dev_st.st_mode)) { log_dbg(cd, "Veryfing locked device handle (regular file)"); if (!same_inode(dev_st, lck_st)) return 1; } else if (S_ISBLK(dev_st.st_mode)) { log_dbg(cd, "Veryfing locked device handle (bdev)"); if (resource_by_devno(res, sizeof(res), dev_st.st_rdev, 1) || stat(res, &st) || !same_inode(lck_st, st)) return 1; } else return 1; return 0; }