From: Luca Boccassi Date: Fri, 10 Jul 2026 18:08:57 +0000 (+0100) Subject: sysext: validate work directory metadata before removal X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9d4cb85cf5fb71242d705363812adebf92720c79;p=thirdparty%2Fsystemd.git sysext: validate work directory metadata before removal unmerge_hierarchy() joined the persisted work_dir value directly with --root=. An empty value therefore resolved to the root itself and was passed to rm_rf(). Require the decoded metadata to name a non-empty, safe, normalized relative path before constructing the removal target. Add coverage using a disposable root with deliberately emptied metadata. Follow-up for 9cfad502f4aa103ef0d2191cbb6b82fecfbc5044 --- diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 0f93322af04..0cb55a53615 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -1808,6 +1808,10 @@ static int unmerge_hierarchy(const Context *c, const char *p, const char *submou l = cunescape_length(escaped_work_dir_in_root, r, 0, &work_dir_in_root); if (l < 0) return log_error_errno(l, "Failed to unescape work directory path: %m"); + if (path_is_absolute(work_dir_in_root) || !path_is_normalized(work_dir_in_root)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid work directory path '%s'.", work_dir_in_root); + work_dir = path_join(c->root, work_dir_in_root); if (!work_dir) return log_oom(); diff --git a/test/units/TEST-50-DISSECT.sysext.sh b/test/units/TEST-50-DISSECT.sysext.sh index fd7a164478b..142c7649bbc 100755 --- a/test/units/TEST-50-DISSECT.sysext.sh +++ b/test/units/TEST-50-DISSECT.sysext.sh @@ -798,6 +798,49 @@ test ! -f "$fake_root$hierarchy/now-is-mutable" ) +( init_trap +: "Malformed work_dir metadata is rejected without removing unrelated paths" +[[ -z "$roots_dir" ]] && exit 0 + +fake_root="$roots_dir/empty-work-dir" +hierarchy=/opt +extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" +work_dir_file="$fake_root$hierarchy/.systemd-sysext/work_dir" +traversal_target="$roots_dir/work-dir-traversal-target" +absolute_target="$fake_root/work-dir-absolute-target" + +[[ "$FSTYPE" == "fuseblk" ]] && exit 0 + +prepare_root "$fake_root" "$hierarchy" +prepare_extension_image "$fake_root" "$hierarchy" +prepare_read_only_hierarchy "$fake_root" "$hierarchy" +touch "$fake_root/root-sentinel" +mkdir "$traversal_target" "$absolute_target" +touch "$traversal_target/sentinel" "$absolute_target/sentinel" +prepend_trap "rm -rf ${traversal_target@Q} ${absolute_target@Q}" + +run_systemd_sysext "$fake_root" --mutable=yes merge +prepend_trap "rm -rf ${extension_data_dir@Q}" + +# Mutable overlays bind-mount the metadata directory read-only. Unmount that bind first to simulate +# corrupted on-disk metadata, then try malformed work_dir values. +umount "$fake_root$hierarchy/.systemd-sysext" +work_dir=$(<"$work_dir_file") + +for invalid_work_dir in "" ../work-dir-traversal-target /work-dir-absolute-target; do + printf '%s\n' "$invalid_work_dir" >"$work_dir_file" + (! run_systemd_sysext "$fake_root" unmerge) + test -f "$fake_root/root-sentinel" + test -f "$traversal_target/sentinel" + test -f "$absolute_target/sentinel" +done + +# Restore valid metadata and unmerge normally, so the test case leaves no mounted hierarchy behind. +printf '%s\n' "$work_dir" >"$work_dir_file" +run_systemd_sysext "$fake_root" unmerge +) + + ( init_trap : "/var/lib/extensions.mutable/… does not exist, auto-mutability, read-only merged" fake_root=${roots_dir:+"$roots_dir/simple-read-only-explicit"}