From b0edc606618e02baff5c454151e1fcc5c70b917c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 31 Aug 2026 17:13:59 +0200 Subject: [PATCH 1/3] dissect: optionally allow attaching images that do not qualify as DDIs with --attach It's nice to use --attach since it validates our images. So far, it strictly insisted on checking that the image was a DDI however, which limits its use for certain usecases. Add "--relax" to address this limitation. --- man/systemd-dissect.xml | 15 ++++++++- shell-completion/bash/systemd-dissect | 3 +- src/dissect/dissect.c | 47 +++++++++++++++++---------- test/units/TEST-50-DISSECT.dissect.sh | 12 +++++++ 4 files changed, 58 insertions(+), 19 deletions(-) diff --git a/man/systemd-dissect.xml b/man/systemd-dissect.xml index b4ff042bb9..670fb2a574 100644 --- a/man/systemd-dissect.xml +++ b/man/systemd-dissect.xml @@ -209,7 +209,8 @@ of losetup --find --show --partscan, but will validate the image as DDI before attaching, and derive the correct sector size to use automatically. Moreover, it ensures the per-partition block devices are created before returning and attempts to enable direct IO mode, if - possible. Takes a path to a disk image file. + possible. Takes a path to a disk image file. Use to attach images that do + not qualify as DDI. If combined with output of the block device name is suppressed. @@ -541,6 +542,18 @@ + + + + If combined with , skips validation of the image and + attaches it to the loopback block device as it is, even if it does not qualify as DDI. If this + switch is used, no image dissection takes place, and thus it is not guaranteed that the + per-partition block devices are created before the command returns. May not be combined with any + other command than . + + + + diff --git a/shell-completion/bash/systemd-dissect b/shell-completion/bash/systemd-dissect index a9fa639c3f..10366b38f1 100644 --- a/shell-completion/bash/systemd-dissect +++ b/shell-completion/bash/systemd-dissect @@ -37,7 +37,8 @@ _systemd_dissect() { -r --read-only --mkdir --rmdir - --in-memory' + --in-memory + --relax' [ARG]='-m --mount -M -u --umount -U --attach diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 29bf421819..f024fe2050 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -109,6 +109,7 @@ static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID; static bool arg_all = false; static uid_t arg_uid_base = UID_INVALID; static bool arg_quiet = false; +static bool arg_relax = false; static ImageFilter *arg_image_filter = NULL; static int arg_copy_ownership = -1; @@ -355,6 +356,10 @@ static int parse_argv(int argc, char *argv[]) { arg_loop_ref_auto = true; break; + OPTION_LONG("relax", NULL, "Skip DDI validation, attach image as it is"): + arg_relax = true; + break; + OPTION_LONG("image-policy", "POLICY", "Specify image dissection policy"): r = parse_image_policy_argument(opts.arg, &arg_image_policy); if (r < 0) @@ -707,6 +712,10 @@ static int parse_argv(int argc, char *argv[]) { return r; } + if (arg_relax && arg_action != ACTION_ATTACH) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "The --relax switch is only supported in combination with --attach."); + SET_FLAG(arg_flags, DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH, isatty_safe(STDIN_FILENO)); return 1; @@ -1809,19 +1818,16 @@ static int action_discover(void) { return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend); } -static int action_attach(DissectedImage *m, LoopDevice *d) { +static int action_attach(LoopDevice *d) { int r; - assert(m); assert(d); r = loop_device_set_autoclear(d, false); if (r < 0) return log_error_errno(r, "Failed to disable auto-clear logic on loopback device: %m"); - r = dissected_image_relinquish(m); - if (r < 0) - return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m"); + loop_device_relinquish(d); if (!arg_quiet) puts(d->node); @@ -1997,7 +2003,7 @@ static int run(int argc, char *argv[]) { ; } - if (arg_image) { + if (arg_image && !arg_relax) { r = verity_settings_load( &arg_verity_settings, arg_image, @@ -2053,19 +2059,26 @@ static int run(int argc, char *argv[]) { log_warning_errno(r, "Failed to set loop reference string to '%s', ignoring: %m", arg_loop_ref); } - r = dissect_loop_device_and_warn( - d, - &arg_verity_settings, - /* mount_options= */ NULL, - arg_image_policy, - arg_image_filter, - arg_flags, - &m); - if (r < 0) - return r; + /* Unless --relax is specified we insist that the image qualifies as DDI: + * dissecting it validates it against the image policy and waits until the + * kernel created the per-partition block devices, so that callers may + * access them immediately after we return. With --relax we skip dissection + * entirely, and attach the image as it is. */ + if (!arg_relax) { + r = dissect_loop_device_and_warn( + d, + &arg_verity_settings, + /* mount_options= */ NULL, + arg_image_policy, + arg_image_filter, + arg_flags, + &m); + if (r < 0) + return r; + } if (arg_action == ACTION_ATTACH) - return action_attach(m, d); + return action_attach(d); r = dissected_image_load_verity_sig_partition( m, diff --git a/test/units/TEST-50-DISSECT.dissect.sh b/test/units/TEST-50-DISSECT.dissect.sh index a8908fcfef..7a1e7daa3b 100755 --- a/test/units/TEST-50-DISSECT.dissect.sh +++ b/test/units/TEST-50-DISSECT.dissect.sh @@ -1066,6 +1066,18 @@ test ! -e "/dev/disk/by-loop-ref/$name" systemd-dissect --detach "$MINIMAL_IMAGE.raw" (! systemd-dissect --detach "$MINIMAL_IMAGE.raw") +# --attach --relax also works on images that do not qualify as DDI, e.g. an entirely blank one +BLANK_IMAGE="$(mktemp /var/tmp/blankXXX.raw)" +truncate -s 16M "$BLANK_IMAGE" +(! systemd-dissect --attach "$BLANK_IMAGE") +LOOP="$(systemd-dissect --attach --relax "$BLANK_IMAGE")" +test -b "$LOOP" +systemd-dissect --detach "$LOOP" +rm -f "$BLANK_IMAGE" + +# --relax makes no sense for the other verbs +(! systemd-dissect --relax "$MINIMAL_IMAGE.raw") + # check for confext functionality mkdir -p /run/confexts/test/etc/extension-release.d echo "ID=_any" >/run/confexts/test/etc/extension-release.d/extension-release.test From 6a16394cd6e40f796b19619f8aae992f38065908 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 5 Sep 2026 06:35:59 +0200 Subject: [PATCH 2/3] units: use --relax in systemd-loop@.service Our primary usecase for this service is to deal with sector issues around CDROMs. For that insisting on proper DDI layout and a root partition is overkill. Let's hence relax the logic for this. Fixes: #43605 --- man/systemd-loop@.service.xml | 2 +- units/systemd-loop@.service | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/man/systemd-loop@.service.xml b/man/systemd-loop@.service.xml index 57ed181263..61d984c6db 100644 --- a/man/systemd-loop@.service.xml +++ b/man/systemd-loop@.service.xml @@ -33,7 +33,7 @@ systemd-escape1's to properly escape a file system path. - This unit invokes systemd-dissect --attach --quiet --loop-ref-auto on the + This unit invokes systemd-dissect --attach --relax --quiet --loop-ref-auto on the specified files, see systemd-dissect1 for details. diff --git a/units/systemd-loop@.service b/units/systemd-loop@.service index a063dcec86..2b31c7b2b6 100644 --- a/units/systemd-loop@.service +++ b/units/systemd-loop@.service @@ -20,5 +20,5 @@ After=modprobe@loop.service Type=oneshot RemainAfterExit=yes TimeoutSec=infinity -ExecStart=systemd-dissect --attach --loop-ref-auto --quiet %f +ExecStart=systemd-dissect --attach --relax --loop-ref-auto --quiet %f ExecStop=systemd-dissect --detach %f From b8ee13613c3d61e0a1fb34238d3707ba255e066e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 7 Sep 2026 10:30:54 +0200 Subject: [PATCH 3/3] dissect: make --detach work when passed the backing block device of a loopback device When systemd-loop@.service is instantiated for a block device (e.g. a CD-ROM drive, which is the primary usecase now that --relax is used), %f resolves to the backing block device rather than to the loopback block device that was attached on top of it. In that case ExecStop= hence invoked --detach on a block device that is not a loopback device at all, which made loop_device_set_autoclear() a silent no-op, leaking the attachment. Hence, if --detach is passed a block device that is not a loopback block device itself, search for the loopback block device backed by it, the same way we already do for regular files. The kernel reports the backing inode for block device nodes too, so the same inode matching logic applies. --- src/dissect/dissect.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index f024fe2050..eb113f4d00 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -1855,10 +1855,24 @@ static int action_detach(const char *path) { if (r < 0) return log_error_errno(r, "Failed to open '%s' as loopback block device: %m", path); - } else if (S_ISREG(st.st_mode)) { + /* If the specified block device is not a loopback block device itself, it might be the + * backing device of one (this is the case when systemd-loop@.service is instantiated for a + * block device, e.g. a CD-ROM drive, in which case it is passed the backing device on + * ExecStop= too). Hence, in that case search for the loopback block device backed by it + * below, the same way as we do for regular files. */ + if (LOOP_DEVICE_IS_FOREIGN(loop)) + loop = loop_device_unref(loop); + + } else if (!S_ISREG(st.st_mode)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "'%s' is neither a block device nor a regular file, refusing.", path); + + if (!loop) { _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; - /* If a regular file is specified, search for a loopback block device that is backed by it */ + /* If a regular file (or a block device that is not a loopback block device) is specified, + * search for a loopback block device that is backed by it. Note that the kernel reports + * the backing inode of a loopback device both for regular files and for block device + * nodes, hence we can match by inode in both cases. */ r = sd_device_enumerator_new(&e); if (r < 0)