]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sysext: validate work directory metadata before removal 42978/head
authorLuca Boccassi <luca.boccassi@gmail.com>
Fri, 10 Jul 2026 18:08:57 +0000 (19:08 +0100)
committerLuca Boccassi <luca.boccassi@gmail.com>
Wed, 15 Jul 2026 20:13:24 +0000 (21:13 +0100)
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

src/sysext/sysext.c
test/units/TEST-50-DISSECT.sysext.sh

index 0f93322af049277133ad40209e0c0fcc299bcfc7..0cb55a5361546ca4d917af3e94ea9ba3478353b7 100644 (file)
@@ -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();
index fd7a164478bc0314f9d43cb31ecb1165485303a1..142c7649bbcd58ecd8cfffd1cb6ecb7f050512ef 100755 (executable)
@@ -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"}