From: Yu Watanabe Date: Wed, 29 Jul 2026 11:16:51 +0000 (+0900) Subject: core: quote each exec directory entry when serializing X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=71a2af43f928a7aa4fd34ea568c733066a092e6d;p=thirdparty%2Fsystemd.git core: quote each exec directory entry when serializing 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. --- diff --git a/src/core/execute-serialize.c b/src/core/execute-serialize.c index 55a52160425..5503925226e 100644 --- a/src/core/execute-serialize.c +++ b/src/core/execute-serialize.c @@ -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) diff --git a/src/test/test-execute.c b/src/test/test-execute.c index 9725fa0bf56..1c7c8c8d6f9 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -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 index 00000000000..c9316561414 --- /dev/null +++ b/test/test-execute/exec-runtimedirectory@.service @@ -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"