#include "openssl-util.h"
#include "os-util.h"
#include "path-util.h"
+#include "proc-cmdline.h"
#include "process-util.h"
#include "raw-clone.h"
#include "resize-fs.h"
if (suuid) {
/* blkid will return FAT's serial number as UUID, hence it is quite possible
* that parsing this will fail. We'll ignore the ID, since it's just too
- * short to be useful as tru identifier. */
+ * short to be useful as true identifier. */
r = sd_id128_from_string(suuid, &uuid);
if (r < 0)
log_debug_errno(r, "Failed to parse file system UUID '%s', ignoring: %m", suuid);
#if HAVE_BLKID
_cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
_cleanup_close_ int fd = -EBADF;
+ struct stat st;
int r;
assert(path);
if (fd < 0)
return -errno;
- r = fd_verify_regular(fd);
+ if (fstat(fd, &st) < 0)
+ return -errno;
+
+ r = stat_verify_regular(&st);
if (r < 0)
return r;
if (r < 0)
return r;
+ m->image_size = st.st_size;
+
r = probe_sector_size(fd, &m->sector_size);
if (r < 0)
return r;
"(fsck)",
NULL,
&node_fd, 1, /* Leave the node fd open */
- FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_REARRANGE_STDIO|FORK_CLOEXEC_OFF,
+ FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_CLOEXEC_OFF,
&pid);
if (r < 0)
return log_debug_errno(r, "Failed to fork off fsck: %m");
if (node_fd < 0)
return log_debug_errno(errno, "Failed to open node device %s: %m", node_path);
- if (ioctl(node_fd, BLKGETSIZE64, &size) != 0)
- return log_debug_errno(errno, "Failed to get block device size of %s: %m", node_path);
+ r = blockdev_get_device_size(node_fd, &size);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to get block device size of %s: %m", node_path);
if (mount_fd < 0) {
assert(mount_path);
if (m->fsmount_fd >= 0) {
/* Case #1: Attach existing fsmount fd to the file system */
- if (move_mount(m->fsmount_fd, "", -EBADF, p, MOVE_MOUNT_F_EMPTY_PATH) < 0)
- return -errno;
+ r = mount_exchange_graceful(
+ m->fsmount_fd,
+ p,
+ FLAGS_SET(flags, DISSECT_IMAGE_TRY_ATOMIC_MOUNT_EXCHANGE));
+ if (r < 0)
+ return log_debug_errno(r, "Failed to mount image on '%s': %m", p);
} else {
assert(node);
(void) fs_grow(node, -EBADF, p);
if (userns_fd >= 0) {
- r = remount_idmap_fd(p, userns_fd);
+ r = remount_idmap_fd(STRV_MAKE(p), userns_fd);
if (r < 0)
return r;
}
if (userns_fd < 0 && need_user_mapping(uid_shift, uid_range) && FLAGS_SET(flags, DISSECT_IMAGE_MOUNT_IDMAPPED)) {
- my_userns_fd = make_userns(uid_shift, uid_range, UID_INVALID, REMOUNT_IDMAPPING_HOST_ROOT);
+ my_userns_fd = make_userns(uid_shift, uid_range, UID_INVALID, UID_INVALID, REMOUNT_IDMAPPING_HOST_ROOT);
if (my_userns_fd < 0)
return my_userns_fd;
if (r > 0)
ok = true;
}
- if (!ok && FLAGS_SET(flags, DISSECT_IMAGE_VALIDATE_OS_EXT)) {
+ if (!ok && FLAGS_SET(flags, DISSECT_IMAGE_VALIDATE_OS_EXT) && m->image_name) {
r = extension_has_forbidden_content(where);
if (r < 0)
return r;
}
DEFINE_TRIVIAL_CLEANUP_FUNC(char *, dm_deferred_remove_clean);
-static int validate_signature_userspace(const VeritySettings *verity) {
+static int validate_signature_userspace(const VeritySettings *verity, DissectImageFlags flags) {
+ int r;
+
+ if (!FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_USERSPACE_VERITY)) {
+ log_debug("Userspace dm-verity signature authentication disabled via flag.");
+ return 0;
+ }
+
+ r = getenv_bool_secure("SYSTEMD_ALLOW_USERSPACE_VERITY");
+ if (r < 0 && r != -ENXIO) {
+ log_debug_errno(r, "Failed to parse $SYSTEMD_ALLOW_USERSPACE_VERITY environment variable, refusing userspace dm-verity signature authentication.");
+ return 0;
+ }
+ if (!r) {
+ log_debug("Userspace dm-verity signature authentication disabled via $SYSTEMD_ALLOW_USERSPACE_VERITY environment variable.");
+ return 0;
+ }
+
+ bool b;
+ r = proc_cmdline_get_bool("systemd.allow_userspace_verity", PROC_CMDLINE_TRUE_WHEN_MISSING, &b);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to parse systemd.allow_userspace_verity= kernel command line option, refusing userspace dm-verity signature authentication.");
+ return 0;
+ }
+ if (!b) {
+ log_debug("Userspace dm-verity signature authentication disabled via systemd.allow_userspace_verity= kernel command line variable.");
+ return 0;
+ }
+
#if HAVE_OPENSSL
_cleanup_(sk_X509_free_allp) STACK_OF(X509) *sk = NULL;
_cleanup_strv_free_ char **certs = NULL;
_cleanup_(BIO_freep) BIO *bio = NULL; /* 'bio' must be freed first, 's' second, hence keep this order
* of declaration in place, please */
const unsigned char *d;
- int r;
assert(verity);
assert(verity->root_hash);
static int do_crypt_activate_verity(
struct crypt_device *cd,
const char *name,
- const VeritySettings *verity) {
+ const VeritySettings *verity,
+ DissectImageFlags flags) {
bool check_signature;
int r, k;
assert(verity);
if (verity->root_hash_sig) {
- r = getenv_bool_secure("SYSTEMD_DISSECT_VERITY_SIGNATURE");
+ r = secure_getenv_bool("SYSTEMD_DISSECT_VERITY_SIGNATURE");
if (r < 0 && r != -ENXIO)
log_debug_errno(r, "Failed to parse $SYSTEMD_DISSECT_VERITY_SIGNATURE");
/* Preferably propagate the original kernel error, so that the fallback logic can work,
* as the device-mapper is finicky around concurrent activations of the same volume */
- k = validate_signature_userspace(verity);
+ k = validate_signature_userspace(verity, flags);
if (k < 0)
return r < 0 ? r : k;
if (k == 0)
DecryptedImage *d) {
_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 = -EBADF;
int r;
* 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_(dm_deferred_remove_cleanp) char *restore_deferred_remove = NULL;
_cleanup_(sym_crypt_freep) struct crypt_device *existing_cd = NULL;
_cleanup_close_ int fd = -EBADF;
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);
+ r = do_crypt_activate_verity(cd, name, verity, flags);
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.
* https://gitlab.com/cryptsetup/cryptsetup/-/merge_requests/96 */
if (r == -EINVAL && FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE))
break;
- if (r == -ENODEV) /* Volume is being opened but not ready, crypt_init_by_name would fail, try to open again */
+ /* Volume is being opened but not ready, crypt_init_by_name would fail, try to open again if
+ * sharing is enabled. */
+ if (r == -ENODEV && FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE))
goto try_again;
if (!IN_SET(r,
-EEXIST, /* Volume has already been opened and ready to be used. */
return log_debug_errno(r, "Failed to activate verity device %s: %m", node);
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);
+ /* 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);
- restore_deferred_remove = strdup(name);
- if (!restore_deferred_remove)
- return log_oom_debug();
- }
+ 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 */
}
}
+ /* Everything looks good and we'll be able to mount the device, so deferred remove will be re-enabled at that point. */
+ restore_deferred_remove = mfree(restore_deferred_remove);
+
mount_node_fd = TAKE_FD(fd);
if (existing_cd)
crypt_free_and_replace(cd, existing_cd);
return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "All attempts to activate verity device %s failed.", name);
success:
- /* Everything looks good and we'll be able to mount the device, so deferred remove will be re-enabled at that point. */
- restore_deferred_remove = mfree(restore_deferred_remove);
-
d->decrypted[d->n_decrypted++] = (DecryptedPartition) {
.name = TAKE_PTR(name),
.device = TAKE_PTR(cd),
k = partition_verity_of(i);
if (k >= 0) {
- r = verity_partition(i, p, m->partitions + k, verity, flags | DISSECT_IMAGE_VERITY_SHARE, d);
+ flags |= getenv_bool("SYSTEMD_VERITY_SHARING") != 0 ? DISSECT_IMAGE_VERITY_SHARE : 0;
+
+ r = verity_partition(i, p, m->partitions + k, verity, flags, d);
if (r < 0)
return r;
}
return log_error_errno(SYNTHETIC_ERRNO(EKEYREJECTED),
"Too many retries.");
- z = strv_free(z);
+ z = strv_free_erase(z);
+
+ static const AskPasswordRequest req = {
+ .message = "Please enter image passphrase:",
+ .id = "dissect",
+ .keyring = "dissect",
+ .credential = "dissect.passphrase",
+ };
- r = ask_password_auto("Please enter image passphrase:", NULL, "dissect", "dissect", "dissect.passphrase", USEC_INFINITY, 0, &z);
+ r = ask_password_auto(&req, USEC_INFINITY, /* flags= */ 0, &z);
if (r < 0)
return log_error_errno(r, "Failed to query for passphrase: %m");
if (is_device_path(image))
return 0;
- r = getenv_bool_secure("SYSTEMD_DISSECT_VERITY_SIDECAR");
+ r = secure_getenv_bool("SYSTEMD_DISSECT_VERITY_SIDECAR");
if (r < 0 && r != -ENXIO)
log_debug_errno(r, "Failed to parse $SYSTEMD_DISSECT_VERITY_SIDECAR, ignoring: %m");
if (r == 0)
}
if (text) {
- r = unhexmem(text, strlen(text), &root_hash, &root_hash_size);
+ r = unhexmem(text, &root_hash, &root_hash_size);
if (r < 0)
return r;
if (root_hash_size < sizeof(sd_id128_t))
if (verity->root_hash && verity->root_hash_sig) /* Already loaded? */
return 0;
- r = getenv_bool_secure("SYSTEMD_DISSECT_VERITY_EMBEDDED");
+ r = secure_getenv_bool("SYSTEMD_DISSECT_VERITY_EMBEDDED");
if (r < 0 && r != -ENXIO)
log_debug_errno(r, "Failed to parse $SYSTEMD_DISSECT_VERITY_EMBEDDED, ignoring: %m");
if (r == 0)
if (!json_variant_is_string(rh))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "'rootHash' field of signature JSON object is not a string.");
- r = unhexmem(json_variant_string(rh), SIZE_MAX, &root_hash, &root_hash_size);
+ r = unhexmem(json_variant_string(rh), &root_hash, &root_hash_size);
if (r < 0)
return log_debug_errno(r, "Failed to parse root hash field: %m");
if (!json_variant_is_string(sig))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "'signature' field of signature JSON object is not a string.");
- r = unbase64mem(json_variant_string(sig), SIZE_MAX, &root_hash_sig, &root_hash_sig_size);
+ r = unbase64mem(json_variant_string(sig), &root_hash_sig, &root_hash_sig_size);
if (r < 0)
return log_debug_errno(r, "Failed to parse signature field: %m");
};
_cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL, **initrd_release = NULL, **sysext_release = NULL, **confext_release = NULL;
- _cleanup_close_pair_ int error_pipe[2] = PIPE_EBADF;
- _cleanup_(rmdir_and_freep) char *t = NULL;
+ _cleanup_free_ char *hostname = NULL, *t = NULL;
+ _cleanup_close_pair_ int error_pipe[2] = EBADF_PAIR;
_cleanup_(sigkill_waitp) pid_t child = 0;
sd_id128_t machine_id = SD_ID128_NULL;
- _cleanup_free_ char *hostname = NULL;
unsigned n_meta_initialized = 0;
int fds[2 * _META_MAX], r, v;
int has_init_system = -1;
assert(m);
- for (; n_meta_initialized < _META_MAX; n_meta_initialized ++) {
- if (!paths[n_meta_initialized]) {
- fds[2*n_meta_initialized] = fds[2*n_meta_initialized+1] = -EBADF;
- continue;
- }
+ for (; n_meta_initialized < _META_MAX; n_meta_initialized++) {
+ assert(paths[n_meta_initialized]);
if (pipe2(fds + 2*n_meta_initialized, O_CLOEXEC) < 0) {
r = -errno;
}
}
- r = mkdtemp_malloc("/tmp/dissect-XXXXXX", &t);
+ r = get_common_dissect_directory(&t);
if (r < 0)
goto finish;
goto finish;
}
- r = safe_fork("(sd-dissect)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, &child);
+ r = safe_fork("(sd-dissect)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, &child);
if (r < 0)
goto finish;
if (r == 0) {
for (unsigned k = 0; k < _META_MAX; k++) {
_cleanup_close_ int fd = -ENOENT;
- if (!paths[k])
- continue;
+ assert(paths[k]);
fds[2*k] = safe_close(fds[2*k]);
switch (k) {
case META_SYSEXT_RELEASE:
+ if (!m->image_name)
+ goto next;
+
/* As per the os-release spec, if the image is an extension it will have a
* file named after the image name in extension-release.d/ - we use the image
* name and try to resolve it with the extension-release helpers, as
break;
case META_CONFEXT_RELEASE:
+ if (!m->image_name)
+ goto next;
+
/* As above */
r = open_extension_release(
t,
if (r < 0)
goto inner_fail;
- continue;
+ goto next;
}
default:
if (fd < 0) {
log_debug_errno(fd, "Failed to read %s file of image, ignoring: %m", paths[k]);
- fds[2*k+1] = safe_close(fds[2*k+1]);
- continue;
+ goto next;
}
r = copy_bytes(fd, fds[2*k+1], UINT64_MAX, 0);
if (r < 0)
goto inner_fail;
+ next:
fds[2*k+1] = safe_close(fds[2*k+1]);
}
for (unsigned k = 0; k < _META_MAX; k++) {
_cleanup_fclose_ FILE *f = NULL;
- if (!paths[k])
- continue;
+ assert(paths[k]);
fds[2*k+1] = safe_close(fds[2*k+1]);
r = wait_for_terminate_and_check("(sd-dissect)", child, 0);
child = 0;
if (r < 0)
- return r;
+ goto finish;
n = read(error_pipe[0], &v, sizeof(v));
- if (n < 0)
- return -errno;
- if (n == sizeof(v))
- return v; /* propagate error sent to us from child */
- if (n != 0)
- return -EIO;
-
- if (r != EXIT_SUCCESS)
- return -EPROTO;
+ if (n < 0) {
+ r = -errno;
+ goto finish;
+ }
+ if (n == sizeof(v)) {
+ r = v; /* propagate error sent to us from child */
+ goto finish;
+ }
+ if (n != 0) {
+ r = -EIO;
+ goto finish;
+ }
+ if (r != EXIT_SUCCESS) {
+ r = -EPROTO;
+ goto finish;
+ }
free_and_replace(m->hostname, hostname);
m->machine_id = machine_id;
return r;
m->loop = loop_device_ref(loop);
+ m->image_size = m->loop->device_size;
m->sector_size = m->loop->sector_size;
r = dissect_image(m, loop->fd, loop->node, verity, mount_options, image_policy, flags);
if (r < 0)
return log_debug_errno(r, "Failed to load root hash: %m");
- dissect_image_flags = (verity.data_path ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0) |
+ dissect_image_flags =
+ (verity.data_path ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0) |
(relax_extension_release_check ? DISSECT_IMAGE_RELAX_EXTENSION_CHECK : 0) |
DISSECT_IMAGE_ADD_PARTITION_DEVICES |
- DISSECT_IMAGE_PIN_PARTITION_DEVICES;
+ DISSECT_IMAGE_PIN_PARTITION_DEVICES |
+ DISSECT_IMAGE_ALLOW_USERSPACE_VERITY;
/* Note that we don't use loop_device_make here, as the FD is most likely O_PATH which would not be
* accepted by LOOP_CONFIGURE, so just let loop_device_make_by_path reopen it as a regular FD. */
return 0;
}
+
+int get_common_dissect_directory(char **ret) {
+ _cleanup_free_ char *t = NULL;
+ int r;
+
+ /* A common location we mount dissected images to. The assumption is that everyone who uses this
+ * function runs in their own private mount namespace (with mount propagation off on /run/systemd/,
+ * and thus can mount something here without affecting anyone else). */
+
+ t = strdup("/run/systemd/dissect-root");
+ if (!t)
+ return log_oom_debug();
+
+ r = mkdir_parents(t, 0755);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to create parent dirs of mount point '%s': %m", t);
+
+ r = RET_NERRNO(mkdir(t, 0000)); /* It's supposed to be overmounted, hence let's make this inaccessible */
+ if (r < 0 && r != -EEXIST)
+ return log_debug_errno(r, "Failed to create mount point '%s': %m", t);
+
+ if (ret)
+ *ret = TAKE_PTR(t);
+
+ return 0;
+}