Merge pull request #24625 from yuwata/dissect-image-open-and-lock-decrypted

dissect-image: open dissected and decrypted partitions
This commit is contained in:
Yu Watanabe
2022-10-13 07:47:08 +09:00
committed by GitHub
2 changed files with 207 additions and 54 deletions
+197 -51
View File
@@ -32,6 +32,7 @@
#include "def.h"
#include "device-nodes.h"
#include "device-util.h"
#include "devnum-util.h"
#include "discover-image.h"
#include "dissect-image.h"
#include "dm-util.h"
@@ -73,19 +74,44 @@
/* how many times to wait for the device nodes to appear */
#define N_DEVICE_NODE_LIST_ATTEMPTS 10
int probe_filesystem(const char *node, char **ret_fstype) {
int probe_filesystem_full(int fd, const char *path, char **ret_fstype) {
/* Try to find device content type and return it in *ret_fstype. If nothing is found,
* 0/NULL will be returned. -EUCLEAN will be returned for ambiguous results, and an
* different error otherwise. */
#if HAVE_BLKID
_cleanup_(blkid_free_probep) blkid_probe b = NULL;
_cleanup_free_ char *path_by_fd = NULL;
_cleanup_close_ int fd_close = -1;
const char *fstype;
int r;
errno = 0;
b = blkid_new_probe_from_filename(node);
assert(fd >= 0 || path);
assert(ret_fstype);
if (fd < 0) {
fd_close = open(path, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY);
if (fd_close < 0)
return -errno;
fd = fd_close;
}
if (!path) {
r = fd_get_path(fd, &path_by_fd);
if (r < 0)
return r;
path = path_by_fd;
}
b = blkid_new_probe();
if (!b)
return -ENOMEM;
errno = 0;
r = blkid_probe_set_device(b, fd, 0, 0);
if (r != 0)
return errno_or_else(ENOMEM);
blkid_probe_enable_superblocks(b, 1);
@@ -97,16 +123,16 @@ int probe_filesystem(const char *node, char **ret_fstype) {
goto not_found;
if (r == -2)
return log_debug_errno(SYNTHETIC_ERRNO(EUCLEAN),
"Results ambiguous for partition %s", node);
"Results ambiguous for partition %s", path);
if (r != 0)
return log_debug_errno(errno_or_else(EIO), "Failed to probe partition %s: %m", node);
return log_debug_errno(errno_or_else(EIO), "Failed to probe partition %s: %m", path);
(void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
if (fstype) {
char *t;
log_debug("Probed fstype '%s' on partition %s.", fstype, node);
log_debug("Probed fstype '%s' on partition %s.", fstype, path);
t = strdup(fstype);
if (!t)
@@ -117,7 +143,7 @@ int probe_filesystem(const char *node, char **ret_fstype) {
}
not_found:
log_debug("No type detected on partition %s", node);
log_debug("No type detected on partition %s", path);
*ret_fstype = NULL;
return 0;
#else
@@ -139,8 +165,8 @@ static int dissected_image_probe_filesystem(DissectedImage *m) {
if (!p->found)
continue;
if (!p->fstype && p->node) {
r = probe_filesystem(p->node, &p->fstype);
if (!p->fstype && p->mount_node_fd >= 0 && !p->decrypted_node) {
r = probe_filesystem_full(p->mount_node_fd, p->node, &p->fstype);
if (r < 0 && r != -EUCLEAN)
return r;
}
@@ -236,6 +262,7 @@ static void dissected_partition_done(DissectedPartition *p) {
free(p->decrypted_fstype);
free(p->decrypted_node);
free(p->mount_options);
safe_close(p->mount_node_fd);
*p = DISSECTED_PARTITION_NULL;
}
@@ -264,6 +291,48 @@ static int make_partition_devname(
return asprintf(ret, "%s%s%i", whole_devname, need_p ? "p" : "", nr);
}
static int open_partition(const char *node, bool is_partition, const LoopDevice *loop) {
_cleanup_(sd_device_unrefp) sd_device *dev = NULL;
_cleanup_close_ int fd = -1;
dev_t devnum;
int r;
assert(node);
assert(loop);
fd = open(node, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY);
if (fd < 0)
return -errno;
/* Check if the block device is a child of (or equivalent to) the originally provided one. */
r = block_device_new_from_fd(fd, is_partition ? BLOCK_DEVICE_LOOKUP_WHOLE_DISK : 0, &dev);
if (r < 0)
return r;
r = sd_device_get_devnum(dev, &devnum);
if (r < 0)
return r;
if (loop->devno != devnum)
return -ENXIO;
/* Also check diskseq. */
if (loop->diskseq > 0) {
uint64_t diskseq;
r = fd_get_diskseq(fd, &diskseq);
if (r < 0)
return r;
if (loop->diskseq != diskseq)
return -ENXIO;
}
log_debug("Opened %s (fd=%i, whole_block_devnum=" DEVNUM_FORMAT_STR ", diskseq=%" PRIu64 ").",
node, fd, DEVNUM_FORMAT_VAL(loop->devno), loop->diskseq);
return TAKE_FD(fd);
}
static int dissect_image(
DissectedImage *m,
int fd,
@@ -365,6 +434,13 @@ static int dissect_image(
if (STRPTR_IN_SET(usage, "filesystem", "crypto")) {
_cleanup_free_ char *t = NULL, *n = NULL, *o = NULL;
const char *fstype = NULL, *options = NULL;
_cleanup_close_ int mount_node_fd = -1;
if (FLAGS_SET(flags, DISSECT_IMAGE_OPEN_PARTITION_DEVICES)) {
mount_node_fd = open_partition(devname, /* is_partition = */ false, m->loop);
if (mount_node_fd < 0)
return mount_node_fd;
}
/* OK, we have found a file system, that's our root partition then. */
(void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
@@ -406,6 +482,7 @@ static int dissect_image(
.fstype = TAKE_PTR(t),
.node = TAKE_PTR(n),
.mount_options = TAKE_PTR(o),
.mount_node_fd = TAKE_FD(mount_node_fd),
.offset = 0,
.size = UINT64_MAX,
};
@@ -778,6 +855,7 @@ static int dissect_image(
if (designator != _PARTITION_DESIGNATOR_INVALID) {
_cleanup_free_ char *t = NULL, *o = NULL, *l = NULL;
_cleanup_close_ int mount_node_fd = -1;
const char *options = NULL;
if (m->partitions[designator].found) {
@@ -793,6 +871,12 @@ static int dissect_image(
dissected_partition_done(m->partitions + designator);
}
if (FLAGS_SET(flags, DISSECT_IMAGE_OPEN_PARTITION_DEVICES)) {
mount_node_fd = open_partition(node, /* is_partition = */ true, m->loop);
if (mount_node_fd < 0)
return mount_node_fd;
}
if (fstype) {
t = strdup(fstype);
if (!t)
@@ -823,6 +907,7 @@ static int dissect_image(
.label = TAKE_PTR(l),
.uuid = id,
.mount_options = TAKE_PTR(o),
.mount_node_fd = TAKE_FD(mount_node_fd),
.offset = (uint64_t) start * 512,
.size = (uint64_t) size * 512,
};
@@ -851,6 +936,7 @@ static int dissect_image(
break;
case 0xEA: { /* Boot Loader Spec extended $BOOT partition */
_cleanup_close_ int mount_node_fd = -1;
_cleanup_free_ char *o = NULL;
sd_id128_t id = SD_ID128_NULL;
const char *sid, *options = NULL;
@@ -859,6 +945,12 @@ static int dissect_image(
if (m->partitions[PARTITION_XBOOTLDR].found)
continue;
if (FLAGS_SET(flags, DISSECT_IMAGE_OPEN_PARTITION_DEVICES)) {
mount_node_fd = open_partition(node, /* is_partition = */ true, m->loop);
if (mount_node_fd < 0)
return mount_node_fd;
}
sid = blkid_partition_get_uuid(pp);
if (sid)
(void) sd_id128_from_string(sid, &id);
@@ -879,6 +971,7 @@ static int dissect_image(
.node = TAKE_PTR(node),
.uuid = id,
.mount_options = TAKE_PTR(o),
.mount_node_fd = TAKE_FD(mount_node_fd),
.offset = (uint64_t) start * 512,
.size = (uint64_t) size * 512,
};
@@ -1048,9 +1141,16 @@ static int dissect_image(
/* If we didn't find a generic node, then we can't fix this up either */
if (generic_node) {
_cleanup_close_ int mount_node_fd = -1;
_cleanup_free_ char *o = NULL;
const char *options;
if (FLAGS_SET(flags, DISSECT_IMAGE_OPEN_PARTITION_DEVICES)) {
mount_node_fd = open_partition(generic_node, /* is_partition = */ true, m->loop);
if (mount_node_fd < 0)
return mount_node_fd;
}
options = mount_options_from_designator(mount_options, PARTITION_ROOT);
if (options) {
o = strdup(options);
@@ -1068,6 +1168,7 @@ static int dissect_image(
.node = TAKE_PTR(generic_node),
.uuid = generic_uuid,
.mount_options = TAKE_PTR(o),
.mount_node_fd = TAKE_FD(mount_node_fd),
.offset = UINT64_MAX,
.size = UINT64_MAX,
};
@@ -1319,12 +1420,13 @@ static int mount_partition(
assert(m);
assert(where);
if (m->mount_node_fd < 0)
return 0;
/* Use decrypted node and matching fstype if available, otherwise use the original device */
node = m->decrypted_node ?: m->node;
node = FORMAT_PROC_FD_PATH(m->mount_node_fd);
fstype = m->decrypted_node ? m->decrypted_fstype: m->fstype;
if (!m->found || !node)
return 0;
if (!fstype)
return -EAFNOSUPPORT;
@@ -1724,6 +1826,7 @@ static int decrypt_partition(
_cleanup_free_ char *node = NULL, *name = NULL;
_cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL;
_cleanup_close_ int fd = -1;
int r;
assert(m);
@@ -1767,12 +1870,17 @@ static int decrypt_partition(
return r == -EPERM ? -EKEYREJECTED : r;
}
fd = open(node, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY);
if (fd < 0)
return log_debug_errno(errno, "Failed to open %s: %m", node);
d->decrypted[d->n_decrypted++] = (DecryptedPartition) {
.name = TAKE_PTR(name),
.device = TAKE_PTR(cd),
};
m->decrypted_node = TAKE_PTR(node);
close_and_replace(m->mount_node_fd, fd);
return 0;
}
@@ -2008,6 +2116,7 @@ static int verity_partition(
_cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL;
_cleanup_(dm_deferred_remove_cleanp) char *restore_deferred_remove = NULL;
_cleanup_free_ char *node = NULL, *name = NULL;
_cleanup_close_ int mount_node_fd = -1;
int r;
assert(m);
@@ -2068,8 +2177,20 @@ static int verity_partition(
* In case of ENODEV/ENOENT, which can happen if another process is activating at the exact same time,
* retry a few times before giving up. */
for (unsigned i = 0; i < N_DEVICE_NODE_LIST_ATTEMPTS; i++) {
_cleanup_(sym_crypt_freep) struct crypt_device *existing_cd = NULL;
_cleanup_close_ int fd = -1;
/* First, check if the device already exists. */
fd = open(node, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY);
if (fd < 0 && !ERRNO_IS_DEVICE_ABSENT(errno))
return log_debug_errno(errno, "Failed to open verity device %s: %m", node);
if (fd >= 0)
goto check; /* The device already exists. Let's check it. */
/* The symlink to the device node does not exist yet. Assume not activated, and let's activate it. */
r = do_crypt_activate_verity(cd, name, verity);
if (r >= 0)
goto try_open; /* The device is activated. Let's open it. */
/* libdevmapper can return EINVAL when the device is already in the activation stage.
* There's no way to distinguish this situation from a genuine error due to invalid
* parameters, so immediately fall back to activating the device with a unique name.
@@ -2077,50 +2198,74 @@ static int verity_partition(
* https://gitlab.com/cryptsetup/cryptsetup/-/merge_requests/96 */
if (r == -EINVAL && FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE))
break;
if (r < 0 && !IN_SET(r,
-EEXIST, /* Volume is already open and ready to be used */
-EBUSY, /* Volume is being opened but not ready, crypt_init_by_name can fetch details */
-ENODEV /* Volume is being opened but not ready, crypt_init_by_name would fail, try to open again */))
return r;
if (IN_SET(r, -EEXIST, -EBUSY)) {
_cleanup_(sym_crypt_freep) struct crypt_device *existing_cd = NULL;
if (r == -ENODEV) /* Volume is being opened but not ready, crypt_init_by_name would fail, try to open again */
goto try_again;
if (!IN_SET(r,
-EEXIST, /* Volume has already been opened and ready to be used. */
-EBUSY /* Volume is being opened but not ready, crypt_init_by_name() can fetch details. */))
return log_debug_errno(r, "Failed to activate verity device %s: %m", node);
if (!restore_deferred_remove){
/* To avoid races, disable automatic removal on umount while setting up the new device. Restore it on failure. */
r = dm_deferred_remove_cancel(name);
/* If activation returns EBUSY there might be no deferred removal to cancel, that's fine */
if (r < 0 && r != -ENXIO)
return log_debug_errno(r, "Disabling automated deferred removal for verity device %s failed: %m", node);
if (r >= 0) {
restore_deferred_remove = strdup(name);
if (!restore_deferred_remove)
return -ENOMEM;
}
}
check:
if (!restore_deferred_remove){
/* To avoid races, disable automatic removal on umount while setting up the new device. Restore it on failure. */
r = dm_deferred_remove_cancel(name);
/* -EBUSY and -ENXIO: the device has already been removed or being removed. We cannot
* use the device, try to open again. See target_message() in drivers/md/dm-ioctl.c
* and dm_cancel_deferred_remove() in drivers/md/dm.c */
if (IN_SET(r, -EBUSY, -ENXIO))
goto try_again;
if (r < 0)
return log_debug_errno(r, "Failed to disable automated deferred removal for verity device %s: %m", node);
r = verity_can_reuse(verity, name, &existing_cd);
/* Same as above, -EINVAL can randomly happen when it actually means -EEXIST */
if (r == -EINVAL && FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE))
restore_deferred_remove = strdup(name);
if (!restore_deferred_remove)
return log_oom_debug();
}
r = verity_can_reuse(verity, name, &existing_cd);
/* Same as above, -EINVAL can randomly happen when it actually means -EEXIST */
if (r == -EINVAL && FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE))
break;
if (IN_SET(r,
-ENOENT, /* Removed?? */
-EBUSY, /* Volume is being opened but not ready, crypt_init_by_name() can fetch details. */
-ENODEV /* Volume is being opened but not ready, crypt_init_by_name() would fail, try to open again. */ ))
goto try_again;
if (r < 0)
return log_debug_errno(r, "Failed to check if existing verity device %s can be reused: %m", node);
if (fd < 0) {
/* devmapper might say that the device exists, but the devlink might not yet have been
* created. Check and wait for the udev event in that case. */
r = device_wait_for_devlink(node, "block", verity_timeout(), NULL);
/* Fallback to activation with a unique device if it's taking too long */
if (r == -ETIMEDOUT && FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE))
break;
if (r < 0 && !IN_SET(r, -ENODEV, -ENOENT, -EBUSY))
return log_debug_errno(r, "Checking whether existing verity device %s can be reused failed: %m", node);
if (r >= 0) {
/* devmapper might say that the device exists, but the devlink might not yet have been
* created. Check and wait for the udev event in that case. */
r = device_wait_for_devlink(node, "block", verity_timeout(), NULL);
/* Fallback to activation with a unique device if it's taking too long */
if (r == -ETIMEDOUT && FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE))
break;
if (r < 0)
return r;
if (r < 0)
return log_debug_errno(r, "Failed to wait device node symlink %s: %m", node);
}
crypt_free_and_replace(cd, existing_cd);
try_open:
if (fd < 0) {
/* Now, the device is activated and devlink is created. Let's open it. */
fd = open(node, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY);
if (fd < 0) {
if (!ERRNO_IS_DEVICE_ABSENT(errno))
return log_debug_errno(errno, "Failed to open verity device %s: %m", node);
/* The device has already been removed?? */
goto try_again;
}
}
if (r >= 0)
goto success;
/* Device is being opened by another process, but it has not finished yet, yield for 2ms */
mount_node_fd = TAKE_FD(fd);
if (existing_cd)
crypt_free_and_replace(cd, existing_cd);
goto success;
try_again:
/* Device is being removed by another process. Let's wait for a while. */
(void) usleep(2 * USEC_PER_MSEC);
}
@@ -2149,6 +2294,7 @@ success:
};
m->decrypted_node = TAKE_PTR(node);
close_and_replace(m->mount_node_fd, mount_node_fd);
return 0;
}
@@ -2205,8 +2351,8 @@ int dissected_image_decrypt(
return r;
}
if (!p->decrypted_fstype && p->decrypted_node) {
r = probe_filesystem(p->decrypted_node, &p->decrypted_fstype);
if (!p->decrypted_fstype && p->mount_node_fd >= 0 && p->decrypted_node) {
r = probe_filesystem_full(p->mount_node_fd, p->decrypted_node, &p->decrypted_fstype);
if (r < 0 && r != -EUCLEAN)
return r;
}
+10 -3
View File
@@ -29,6 +29,7 @@ struct DissectedPartition {
char *decrypted_node;
char *decrypted_fstype;
char *mount_options;
int mount_node_fd;
uint64_t size;
uint64_t offset;
};
@@ -37,6 +38,7 @@ struct DissectedPartition {
((DissectedPartition) { \
.partno = -1, \
.architecture = _ARCHITECTURE_INVALID, \
.mount_node_fd = -1, \
})
typedef enum PartitionDesignator {
@@ -207,8 +209,10 @@ typedef enum DissectImageFlags {
DISSECT_IMAGE_GROWFS = 1 << 18, /* Grow file systems in partitions marked for that to the size of the partitions after mount */
DISSECT_IMAGE_MOUNT_IDMAPPED = 1 << 19, /* Mount mounts with kernel 5.12-style userns ID mapping, if file system type doesn't support uid=/gid= */
DISSECT_IMAGE_MANAGE_PARTITION_DEVICES = 1 << 20, /* Manage partition devices, e.g. probe each partition in more detail */
DISSECT_IMAGE_BLOCK_DEVICE = DISSECT_IMAGE_MANAGE_PARTITION_DEVICES,
DISSECT_IMAGE_RELAX_SYSEXT_CHECK = 1 << 21, /* Don't insist that the extension-release file name matches the image name */
DISSECT_IMAGE_OPEN_PARTITION_DEVICES = 1 << 21, /* Open dissected partitions and decrypted partitions */
DISSECT_IMAGE_BLOCK_DEVICE = DISSECT_IMAGE_MANAGE_PARTITION_DEVICES |
DISSECT_IMAGE_OPEN_PARTITION_DEVICES,
DISSECT_IMAGE_RELAX_SYSEXT_CHECK = 1 << 22, /* Don't insist that the extension-release file name matches the image name */
} DissectImageFlags;
struct DissectedImage {
@@ -263,7 +267,10 @@ MountOptions* mount_options_free_all(MountOptions *options);
DEFINE_TRIVIAL_CLEANUP_FUNC(MountOptions*, mount_options_free_all);
const char* mount_options_from_designator(const MountOptions *options, PartitionDesignator designator);
int probe_filesystem(const char *node, char **ret_fstype);
int probe_filesystem_full(int fd, const char *path, char **ret_fstype);
static inline int probe_filesystem(const char *path, char **ret_fstype) {
return probe_filesystem_full(-1, path, ret_fstype);
}
int dissect_image_file(
const char *path,
const VeritySettings *verity,