]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: quote each exec directory entry when serializing
authorYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 29 Jul 2026 11:16:51 +0000 (20:16 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 31 Jul 2026 01:46:50 +0000 (10:46 +0900)
Quote each serialized exec directory entry, and use extract flags
compatible with config_parse_exec_directories() when deserializing.
This allows paths containing spaces and escaped characters to round-trip
correctly.

Fixes #41853.
Replaces #42686.

src/core/execute-serialize.c
src/test/test-execute.c
test/test-execute/exec-runtimedirectory@.service [new file with mode: 0644]

index 55a521604255e86e81c7704fadef1bf8ef721034..5503925226e238bc039346bf1055a744367c7a0c 100644 (file)
@@ -1867,11 +1867,11 @@ static int exec_context_serialize(const ExecContext *c, FILE *f) {
                 FOREACH_ARRAY(i, c->directories[dt].items, c->directories[dt].n_items) {
                         _cleanup_free_ char *path_escaped = NULL;
 
-                        path_escaped = shell_escape(i->path, ":" WHITESPACE);
+                        path_escaped = shell_escape(i->path, ":\"");
                         if (!path_escaped)
                                 return log_oom_debug();
 
-                        if (!strextend(&value, " ", path_escaped))
+                        if (!strextend(&value, " \"", path_escaped))
                                 return log_oom_debug();
 
                         if (!strextend(&value, ":", yes_no(FLAGS_SET(i->flags, EXEC_DIRECTORY_ONLY_CREATE))))
@@ -1883,13 +1883,16 @@ static int exec_context_serialize(const ExecContext *c, FILE *f) {
                         STRV_FOREACH(d, i->symlinks) {
                                 _cleanup_free_ char *link_escaped = NULL;
 
-                                link_escaped = shell_escape(*d, ":" WHITESPACE);
+                                link_escaped = shell_escape(*d, ":\"");
                                 if (!link_escaped)
                                         return log_oom_debug();
 
                                 if (!strextend(&value, ":", link_escaped))
                                         return log_oom_debug();
                         }
+
+                        if (!strextend(&value, "\""))
+                                return log_oom_debug();
                 }
 
                 r = serialize_item(f, key, value);
@@ -2830,15 +2833,14 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) {
                                 ExecDirectoryFlags exec_directory_flags = 0;
                                 const char *p;
 
-                                /* Use EXTRACT_UNESCAPE_RELAX here, as we unescape the colons in subsequent calls */
-                                r = extract_first_word(&val, &tuple, WHITESPACE, EXTRACT_UNESCAPE_SEPARATORS|EXTRACT_UNESCAPE_RELAX);
+                                r = extract_first_word(&val, &tuple, WHITESPACE, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE);
                                 if (r < 0)
                                         return r;
                                 if (r == 0)
                                         break;
 
                                 p = tuple;
-                                r = extract_many_words(&p, ":", EXTRACT_UNESCAPE_SEPARATORS, &path, &only_create, &read_only);
+                                r = extract_many_words(&p, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &path, &only_create, &read_only);
                                 if (r < 0)
                                         return r;
                                 if (r < 2)
@@ -2866,7 +2868,7 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) {
                                 for (;;) {
                                         _cleanup_free_ char *link = NULL;
 
-                                        r = extract_first_word(&p, &link, ":", EXTRACT_UNESCAPE_SEPARATORS);
+                                        r = extract_first_word(&p, &link, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS);
                                         if (r < 0)
                                                 return r;
                                         if (r == 0)
index 9725fa0bf56bc761564e2ac0671f547d81bcb5ea..1c7c8c8d6f9becca5c927feac427bb88040fa847 100644 (file)
@@ -1140,6 +1140,10 @@ static void test_exec_runtimedirectory(Manager *m) {
         test(m, "exec-runtimedirectory.service", 0, CLD_EXITED);
         (void) rm_rf("/run/test-exec_runtimedirectory2", REMOVE_ROOT|REMOVE_PHYSICAL);
 
+        (void) rm_rf("/run/test-exec_runtimedirectory-escape", REMOVE_ROOT|REMOVE_PHYSICAL);
+        test(m, "exec-runtimedirectory@foo\\x2dv1.service", 0, CLD_EXITED);
+        (void) rm_rf("/run/test-exec_runtimedirectory-escape", REMOVE_ROOT|REMOVE_PHYSICAL);
+
         test(m, "exec-runtimedirectory-mode.service", 0, CLD_EXITED);
         test(m, "exec-runtimedirectory-owner.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED);
 
diff --git a/test/test-execute/exec-runtimedirectory@.service b/test/test-execute/exec-runtimedirectory@.service
new file mode 100644 (file)
index 0000000..c931656
--- /dev/null
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Test for RuntimeDirectory (escape)
+
+[Service]
+ExecStart=bash -x -c 'test -d "%t/test-exec_runtimedirectory-escape/backsl\ash"'
+ExecStart=bash -x -c 'test -d "%t/test-exec_runtimedirectory-escape/foo bar:baz\\quux"'
+ExecStart=bash -x -c 'test -d "%t/test-exec_runtimedirectory-escape/%I"'
+ExecStart=bash -x -c 'test -d "%t/test-exec_runtimedirectory-escape/%i"'
+ExecStart=bash -x -c 'test -d "%t/test-exec_runtimedirectory-escape/hoge ho:ge"'
+ExecStart=bash -x -c 'test -L "%t/test-exec_runtimedirectory-escape/foo:bar"'
+ExecStart=bash -x -c 'test "$(readlink %t/test-exec_runtimedirectory-escape/foo:bar)" = "hoge ho:ge"'
+Type=oneshot
+RuntimeDirectory=test-exec_runtimedirectory-escape/backsl\ash
+RuntimeDirectory="test-exec_runtimedirectory-escape/foo bar\:baz\\quux"
+RuntimeDirectory=test-exec_runtimedirectory-escape/%I
+RuntimeDirectory=test-exec_runtimedirectory-escape/%i
+RuntimeDirectory="test-exec_runtimedirectory-escape/hoge ho\:ge:test-exec_runtimedirectory-escape/foo\:bar"