]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: add read-only flag for exec directories
authorLuca Boccassi <luca.boccassi@gmail.com>
Mon, 28 Oct 2024 19:58:58 +0000 (19:58 +0000)
committerLuca Boccassi <luca.boccassi@gmail.com>
Fri, 1 Nov 2024 10:46:55 +0000 (10:46 +0000)
When an exec directory is shared between services, this allows one of the
service to be the producer of files, and the other the consumer, without
letting the consumer modify the shared files.
This will be especially useful in conjunction with id-mapped exec directories
so that fully sandboxed services can share directories in one direction, safely.

12 files changed:
man/org.freedesktop.systemd1.xml
man/systemd.exec.xml
src/core/dbus-execute.c
src/core/exec-invoke.c
src/core/execute-serialize.c
src/core/execute.c
src/core/execute.h
src/core/load-fragment.c
src/shared/bus-unit-util.c
src/shared/bus-unit-util.h
test/units/TEST-23-UNIT-FILE.statedir.sh
test/units/TEST-34-DYNAMICUSERMIGRATE.sh

index f484f28a70b5429ad0f293de172bb19c331f930c..7ade8c3e8bdaef0a6993f38c1d7ef6dc775c4611 100644 (file)
@@ -4847,8 +4847,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
       <varname>CacheDirectorySymlink</varname> and  <varname>LogsDirectorySymlink</varname> respectively
       implement the destination parameter of the unit files settings <varname>RuntimeDirectory</varname>,
       <varname>StateDirectory</varname>, <varname>CacheDirectory</varname> and <varname>LogsDirectory</varname>,
-      which will create a symlink of the given name to the respective directory. The messages take an unused
-      <varname>flags</varname> parameter, reserved for future backward-compatible changes.</para>
+      which will create a symlink of the given name to the respective directory. The messages take a
+      <varname>flags</varname> parameter that make the directory read only:</para>
+
+      <programlisting>
+#define SD_EXEC_DIRECTORY_READ_ONLY            (UINT64_C(1) &lt;&lt; 0)
+      </programlisting>
 
       <para><varname>ExtraFileDescriptorNames</varname> contains file descriptor names passed to the service via
       the <varname>ExtraFileDescriptors</varname> property in the <function>StartTransientUnit()</function>
index f84204d247e7cae96b71fff462585cb086054c0d..d5f85ed85c75b4821b2b36749c0662deaa2fdfdb 100644 (file)
@@ -1561,12 +1561,18 @@ CapabilityBoundingSet=~CAP_B CAP_C</programlisting>
         configuration or lifetime guarantees, please consider using
         <citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
 
-        <para><varname>RuntimeDirectory=</varname>, <varname>StateDirectory=</varname>, <varname>CacheDirectory=</varname>
-        and <varname>LogsDirectory=</varname>  optionally support a second parameter, separated by <literal>:</literal>.
-        The second parameter will be interpreted as a destination path that will be created as a symlink to the directory.
-        The symlinks will be created after any <varname>BindPaths=</varname> or <varname>TemporaryFileSystem=</varname>
-        options have been set up, to make ephemeral symlinking possible. The same source can have multiple symlinks, by
-        using the same first parameter, but a different second parameter.</para>
+        <para><varname>RuntimeDirectory=</varname>, <varname>StateDirectory=</varname>,
+        <varname>CacheDirectory=</varname> and <varname>LogsDirectory=</varname>  optionally support two
+        more parameters, separated by <literal>:</literal>. The second parameter will be interpreted as a
+        destination path that will be created as a symlink to the directory. The symlinks will be created
+        after any <varname>BindPaths=</varname> or <varname>TemporaryFileSystem=</varname> options have been
+        set up, to make ephemeral symlinking possible. The same source can have multiple symlinks, by using
+        the same first parameter, but a different second parameter. The third parameter is a flags field,
+        and since v257 can take a value of <constant>ro</constant> to make the directory read only for the
+        service. This is also supported for <varname>ConfigurationDirectory=</varname>. If multiple symlinks
+        are set up, the directory will be read only if at least one is configured to be read only. To pass a
+        flag without a destination symlink, the second parameter can be empty, for example:
+        <programlisting>ConfigurationDirectory=foo::ro</programlisting></para>
 
         <para>The directories defined by these options are always created under the standard paths used by systemd
         (<filename>/var/</filename>, <filename>/run/</filename>, <filename>/etc/</filename>, …). If the service needs
index 4f627a11e24c273f69142c652284ad1dd54a1021..a9a73b599b45ee64d43b6d371dbad15b3a3f5bf2 100644 (file)
@@ -5,6 +5,7 @@
 #include "af-list.h"
 #include "alloc-util.h"
 #include "bus-get-properties.h"
+#include "bus-unit-util.h"
 #include "bus-util.h"
 #include "cap-list.h"
 #include "capability-util.h"
@@ -981,11 +982,18 @@ static int property_get_exec_dir_symlink(
                 return r;
 
         FOREACH_ARRAY(i, d->items, d->n_items)
-                STRV_FOREACH(dst, i->symlinks) {
-                        r = sd_bus_message_append(reply, "(sst)", i->path, *dst, UINT64_C(0) /* flags, unused for now */);
+                if (strv_isempty(i->symlinks)) {
+                        /* The old exec directory properties cannot represent flags, so list them here with no
+                         * destination */
+                        r = sd_bus_message_append(reply, "(sst)", i->path, "", (uint64_t) (i->flags & _EXEC_DIRECTORY_FLAGS_PUBLIC));
                         if (r < 0)
                                 return r;
-                }
+                } else
+                        STRV_FOREACH(dst, i->symlinks) {
+                                r = sd_bus_message_append(reply, "(sst)", i->path, *dst, (uint64_t) (i->flags & _EXEC_DIRECTORY_FLAGS_PUBLIC));
+                                if (r < 0)
+                                        return r;
+                        }
 
         return sd_bus_message_close_container(reply);
 }
@@ -3444,7 +3452,7 @@ int bus_exec_context_set_transient_property(
                                 _cleanup_free_ char *joined = NULL;
 
                                 STRV_FOREACH(source, l) {
-                                        r = exec_directory_add(d, *source, NULL);
+                                        r = exec_directory_add(d, *source, /* symlink= */ NULL, /* flags= */ 0);
                                         if (r < 0)
                                                 return log_oom();
                                 }
@@ -3862,7 +3870,7 @@ int bus_exec_context_set_transient_property(
         } else if (STR_IN_SET(name, "StateDirectorySymlink", "RuntimeDirectorySymlink", "CacheDirectorySymlink", "LogsDirectorySymlink")) {
                 char *source, *destination;
                 ExecDirectory *directory;
-                uint64_t symlink_flags; /* No flags for now, reserved for future uses. */
+                uint64_t symlink_flags;
                 ExecDirectoryType i;
 
                 assert_se((i = exec_directory_type_symlink_from_string(name)) >= 0);
@@ -3873,40 +3881,50 @@ int bus_exec_context_set_transient_property(
                         return r;
 
                 while ((r = sd_bus_message_read(message, "(sst)", &source, &destination, &symlink_flags)) > 0) {
+                        if ((symlink_flags & ~_EXEC_DIRECTORY_FLAGS_PUBLIC) != 0)
+                                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid 'flags' parameter '%" PRIu64 "'", symlink_flags);
                         if (!path_is_valid(source))
                                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path %s is not valid.", source);
                         if (path_is_absolute(source))
                                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path %s is absolute.", source);
                         if (!path_is_normalized(source))
                                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path %s is not normalized.", source);
-                        if (!path_is_valid(destination))
-                                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is not valid.", destination);
-                        if (path_is_absolute(destination))
-                                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is absolute.", destination);
-                        if (!path_is_normalized(destination))
-                                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is not normalized.", destination);
-                        if (symlink_flags != 0)
-                                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be zero.");
+                        if (isempty(destination))
+                                destination = NULL;
+                        else {
+                                if (!path_is_valid(destination))
+                                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is not valid.", destination);
+                                if (path_is_absolute(destination))
+                                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is absolute.", destination);
+                                if (!path_is_normalized(destination))
+                                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is not normalized.", destination);
+                        }
 
                         if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
                                 _cleanup_free_ char *destination_escaped = NULL, *source_escaped = NULL;
 
-                                r = exec_directory_add(directory, source, destination);
+                                r = exec_directory_add(directory, source, destination, symlink_flags);
                                 if (r < 0)
                                         return r;
 
                                 /* Need to store them in the unit with the escapes, so that they can be parsed again */
                                 source_escaped = xescape(source, ":");
-                                destination_escaped = xescape(destination, ":");
-                                if (!source_escaped || !destination_escaped)
+                                if (!source_escaped)
                                         return -ENOMEM;
+                                if (destination) {
+                                        destination_escaped = xescape(destination, ":");
+                                        if (!destination_escaped)
+                                                return -ENOMEM;
+                                }
 
                                 unit_write_settingf(
                                                 u, flags|UNIT_ESCAPE_SPECIFIERS, exec_directory_type_to_string(i),
-                                                "%s=%s:%s",
+                                                "%s=%s%s%s%s",
                                                 exec_directory_type_to_string(i),
                                                 source_escaped,
-                                                destination_escaped);
+                                                destination_escaped || FLAGS_SET(symlink_flags, EXEC_DIRECTORY_READ_ONLY) ? ":" : "",
+                                                destination_escaped,
+                                                FLAGS_SET(symlink_flags, EXEC_DIRECTORY_READ_ONLY) ? ":ro" : "");
                         }
                 }
                 if (r < 0)
index feea7897eff7dda1f1a59ab6027b3e77f55dbf0f..968e9bd7c4dbdd92ada0cdf18b4d775403a14c6e 100644 (file)
@@ -2452,7 +2452,7 @@ static int setup_exec_directory(
                                         goto fail;
                         }
 
-                        if (!i->only_create) {
+                        if (!FLAGS_SET(i->flags, EXEC_DIRECTORY_ONLY_CREATE)) {
                                 /* And link it up from the original place.
                                  * Notes
                                  * 1) If a mount namespace is going to be used, then this symlink remains on
@@ -2643,7 +2643,7 @@ static int compile_bind_mounts(
                         continue;
 
                 FOREACH_ARRAY(i, context->directories[t].items, context->directories[t].n_items)
-                        n += !i->only_create;
+                        n += !FLAGS_SET(i->flags, EXEC_DIRECTORY_ONLY_CREATE) || FLAGS_SET(i->flags, EXEC_DIRECTORY_READ_ONLY);
         }
 
         if (n <= 0) {
@@ -2691,8 +2691,10 @@ static int compile_bind_mounts(
                         _cleanup_free_ char *s = NULL, *d = NULL;
 
                         /* When one of the parent directories is in the list, we cannot create the symlink
-                         * for the child directory. See also the comments in setup_exec_directory(). */
-                        if (i->only_create)
+                         * for the child directory. See also the comments in setup_exec_directory().
+                         * But if it needs to be read only, then we have to create a bind mount anyway to
+                         * make it so. */
+                        if (FLAGS_SET(i->flags, EXEC_DIRECTORY_ONLY_CREATE) && !FLAGS_SET(i->flags, EXEC_DIRECTORY_READ_ONLY))
                                 continue;
 
                         if (exec_directory_is_private(context, t))
@@ -2718,6 +2720,7 @@ static int compile_bind_mounts(
                                 .destination = TAKE_PTR(d),
                                 .nosuid = context->dynamic_user, /* don't allow suid/sgid when DynamicUser= is on */
                                 .recursive = true,
+                                .read_only = FLAGS_SET(i->flags, EXEC_DIRECTORY_READ_ONLY),
                         };
                 }
         }
@@ -2766,7 +2769,7 @@ static int compile_symlinks(
 
                         if (!exec_directory_is_private(context, dt) ||
                             exec_context_with_rootfs(context) ||
-                            i->only_create)
+                            FLAGS_SET(i->flags, EXEC_DIRECTORY_ONLY_CREATE))
                                 continue;
 
                         private_path = path_join(params->prefix[dt], "private", i->path);
index 39369e22b83bcfd5248a546543956c6ec6aa4ef0..6fa0b2196831ca5d0d20c6cb001655a7234a6182 100644 (file)
@@ -1998,7 +1998,10 @@ static int exec_context_serialize(const ExecContext *c, FILE *f) {
                         if (!strextend(&value, " ", path_escaped))
                                 return log_oom_debug();
 
-                        if (!strextend(&value, ":", yes_no(i->only_create)))
+                        if (!strextend(&value, ":", yes_no(FLAGS_SET(i->flags, EXEC_DIRECTORY_ONLY_CREATE))))
+                                return log_oom_debug();
+
+                        if (!strextend(&value, ":", yes_no(FLAGS_SET(i->flags, EXEC_DIRECTORY_READ_ONLY))))
                                 return log_oom_debug();
 
                         STRV_FOREACH(d, i->symlinks) {
@@ -2893,7 +2896,8 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) {
                                 return r;
 
                         for (;;) {
-                                _cleanup_free_ char *tuple = NULL, *path = NULL, *only_create = NULL;
+                                _cleanup_free_ char *tuple = NULL, *path = NULL, *only_create = NULL, *read_only = NULL;
+                                ExecDirectoryFlags exec_directory_flags = 0;
                                 const char *p;
 
                                 /* Use EXTRACT_UNESCAPE_RELAX here, as we unescape the colons in subsequent calls */
@@ -2904,20 +2908,27 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) {
                                         break;
 
                                 p = tuple;
-                                r = extract_many_words(&p, ":", EXTRACT_UNESCAPE_SEPARATORS, &path, &only_create);
+                                r = extract_many_words(&p, ":", EXTRACT_UNESCAPE_SEPARATORS, &path, &only_create, &read_only);
                                 if (r < 0)
                                         return r;
                                 if (r < 2)
                                         continue;
 
-                                r = exec_directory_add(&c->directories[dt], path, NULL);
+                                r = parse_boolean(only_create);
                                 if (r < 0)
                                         return r;
+                                if (r > 0)
+                                        exec_directory_flags |= EXEC_DIRECTORY_ONLY_CREATE;
 
-                                r = parse_boolean(only_create);
+                                r = parse_boolean(read_only);
+                                if (r < 0)
+                                        return r;
+                                if (r > 0)
+                                        exec_directory_flags |= EXEC_DIRECTORY_READ_ONLY;
+
+                                r = exec_directory_add(&c->directories[dt], path, /* symlink= */ NULL, exec_directory_flags);
                                 if (r < 0)
                                         return r;
-                                c->directories[dt].items[c->directories[dt].n_items - 1].only_create = r;
 
                                 if (isempty(p))
                                         continue;
index a081c429380462d85cf0fa4d36b793a8c38eaadd..1c41b39a2fd3dc373799600bc950314397d19390 100644 (file)
@@ -331,6 +331,11 @@ bool exec_needs_mount_namespace(
         if (exec_context_get_effective_bind_log_sockets(context))
                 return true;
 
+        for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++)
+                FOREACH_ARRAY(i, context->directories[t].items, context->directories[t].n_items)
+                        if (FLAGS_SET(i->flags, EXEC_DIRECTORY_READ_ONLY))
+                                return true;
+
         return false;
 }
 
@@ -1118,7 +1123,12 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
                 fprintf(f, "%s%sMode: %04o\n", prefix, exec_directory_type_to_string(dt), c->directories[dt].mode);
 
                 for (size_t i = 0; i < c->directories[dt].n_items; i++) {
-                        fprintf(f, "%s%s: %s\n", prefix, exec_directory_type_to_string(dt), c->directories[dt].items[i].path);
+                        fprintf(f,
+                                "%s%s: %s%s\n",
+                                prefix,
+                                exec_directory_type_to_string(dt),
+                                c->directories[dt].items[i].path,
+                                FLAGS_SET(c->directories[dt].items[i].flags, EXEC_DIRECTORY_READ_ONLY) ? " (ro)" : "");
 
                         STRV_FOREACH(d, c->directories[dt].items[i].symlinks)
                                 fprintf(f, "%s%s: %s:%s\n", prefix, exec_directory_type_symlink_to_string(dt), c->directories[dt].items[i].path, *d);
@@ -2731,7 +2741,7 @@ static ExecDirectoryItem *exec_directory_find(ExecDirectory *d, const char *path
         return NULL;
 }
 
-int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink) {
+int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink, ExecDirectoryFlags flags) {
         _cleanup_strv_free_ char **s = NULL;
         _cleanup_free_ char *p = NULL;
         ExecDirectoryItem *existing;
@@ -2746,6 +2756,8 @@ int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink)
                 if (r < 0)
                         return r;
 
+                existing->flags |= flags;
+
                 return 0; /* existing item is updated */
         }
 
@@ -2765,6 +2777,7 @@ int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink)
         d->items[d->n_items++] = (ExecDirectoryItem) {
                 .path = TAKE_PTR(p),
                 .symlinks = TAKE_PTR(s),
+                .flags = flags,
         };
 
         return 1; /* new item is added */
@@ -2782,7 +2795,7 @@ void exec_directory_sort(ExecDirectory *d) {
 
         /* Sort the exec directories to make always parent directories processed at first in
          * setup_exec_directory(), e.g., even if StateDirectory=foo/bar foo, we need to create foo at first,
-         * then foo/bar. Also, set .only_create flag if one of the parent directories is contained in the
+         * then foo/bar. Also, set the ONLY_CREATE flag if one of the parent directories is contained in the
          * list. See also comments in setup_exec_directory() and issue #24783. */
 
         if (d->n_items <= 1)
@@ -2793,7 +2806,7 @@ void exec_directory_sort(ExecDirectory *d) {
         for (size_t i = 1; i < d->n_items; i++)
                 for (size_t j = 0; j < i; j++)
                         if (path_startswith(d->items[i].path, d->items[j].path)) {
-                                d->items[i].only_create = true;
+                                d->items[i].flags |= EXEC_DIRECTORY_ONLY_CREATE;
                                 break;
                         }
 }
index 1fc7e6d7853e24dd1d08a3e501e63a192b8bc304..2f5abc9785bd7f68dc806e84574a72066e62ea42 100644 (file)
@@ -15,6 +15,7 @@ typedef struct Manager Manager;
 #include <stdio.h>
 #include <sys/capability.h>
 
+#include "bus-unit-util.h"
 #include "cgroup-util.h"
 #include "coredump-util.h"
 #include "cpu-set-util.h"
@@ -161,7 +162,7 @@ static inline bool EXEC_DIRECTORY_TYPE_SHALL_CHOWN(ExecDirectoryType t) {
 typedef struct ExecDirectoryItem {
         char *path;
         char **symlinks;
-        bool only_create;
+        ExecDirectoryFlags flags;
 } ExecDirectoryItem;
 
 typedef struct ExecDirectory {
@@ -585,7 +586,7 @@ void exec_params_deep_clear(ExecParameters *p);
 bool exec_context_get_cpu_affinity_from_numa(const ExecContext *c);
 
 void exec_directory_done(ExecDirectory *d);
-int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink);
+int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink, ExecDirectoryFlags flags);
 void exec_directory_sort(ExecDirectory *d);
 bool exec_directory_is_private(const ExecContext *context, ExecDirectoryType type);
 
index b059e515ef66155205fb469f5dc298ddd17cb654..1d813332b10a849aa9bf1d2afd3709fba73444f9 100644 (file)
@@ -4698,9 +4698,9 @@ int config_parse_exec_directories(
                 if (r == 0)
                         return 0;
 
-                _cleanup_free_ char *src = NULL, *dest = NULL;
+                _cleanup_free_ char *src = NULL, *dest = NULL, *flags = NULL;
                 const char *q = tuple;
-                r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &src, &dest);
+                r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS|EXTRACT_DONT_COALESCE_SEPARATORS, &src, &dest, &flags);
                 if (r == -ENOMEM)
                         return log_oom();
                 if (r <= 0) {
@@ -4727,20 +4727,20 @@ int config_parse_exec_directories(
                         continue;
                 }
 
+                if (!isempty(dest) && streq(lvalue, "ConfigurationDirectory")) {
+                        log_syntax(unit, LOG_WARNING, filename, line, 0,
+                                   "Additional parameter is not supported for ConfigurationDirectory, ignoring: %s", tuple);
+                        continue;
+                }
+
                 /* For State and Runtime directories we support an optional destination parameter, which
                  * will be used to create a symlink to the source. */
                 _cleanup_free_ char *dresolved = NULL;
                 if (!isempty(dest)) {
-                        if (streq(lvalue, "ConfigurationDirectory")) {
-                                log_syntax(unit, LOG_WARNING, filename, line, 0,
-                                           "Destination parameter is not supported for ConfigurationDirectory, ignoring: %s", tuple);
-                                continue;
-                        }
-
                         r = unit_path_printf(u, dest, &dresolved);
                         if (r < 0) {
                                 log_syntax(unit, LOG_WARNING, filename, line, r,
-                                        "Failed to resolve unit specifiers in \"%s\", ignoring: %m", dest);
+                                           "Failed to resolve unit specifiers in \"%s\", ignoring: %m", dest);
                                 continue;
                         }
 
@@ -4749,7 +4749,14 @@ int config_parse_exec_directories(
                                 continue;
                 }
 
-                r = exec_directory_add(ed, sresolved, dresolved);
+                ExecDirectoryFlags exec_directory_flags = exec_directory_flags_from_string(flags);
+                if (exec_directory_flags < 0 || (exec_directory_flags & ~_EXEC_DIRECTORY_FLAGS_PUBLIC) != 0) {
+                        log_syntax(unit, LOG_WARNING, filename, line, 0,
+                                   "Invalid flags for %s=, ignoring: %s", lvalue, flags);
+                        continue;
+                }
+
+                r = exec_directory_add(ed, sresolved, dresolved, exec_directory_flags);
                 if (r < 0)
                         return log_oom();
         }
index 2e17bae51afc677fb178f0aa130ea4bb47aa60a3..90b6f233e21d2acd7c4788813491d5055a185700 100644 (file)
@@ -2127,7 +2127,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
         }
 
         if (STR_IN_SET(field, "StateDirectory", "RuntimeDirectory", "CacheDirectory", "LogsDirectory")) {
-                _cleanup_strv_free_ char **symlinks = NULL, **sources = NULL;
+                _cleanup_strv_free_ char **symlinks = NULL, **symlinks_ro = NULL, **sources = NULL, **sources_ro = NULL;
                 const char *p = eq;
 
                 /* Adding new directories is supported from both *DirectorySymlink methods and the
@@ -2135,7 +2135,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
                  * tuple use the new method, else use the old one. */
 
                 for (;;) {
-                        _cleanup_free_ char *tuple = NULL, *source = NULL, *destination = NULL;
+                        _cleanup_free_ char *tuple = NULL, *source = NULL, *dest = NULL, *flags = NULL;
 
                         r = extract_first_word(&p, &tuple, NULL, EXTRACT_UNQUOTE);
                         if (r < 0)
@@ -2144,20 +2144,31 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
                                 break;
 
                         const char *t = tuple;
-                        r = extract_many_words(&t, ":", EXTRACT_UNQUOTE|EXTRACT_DONT_COALESCE_SEPARATORS, &source, &destination);
+                        r = extract_many_words(&t, ":", EXTRACT_UNQUOTE|EXTRACT_DONT_COALESCE_SEPARATORS, &source, &dest, &flags);
                         if (r <= 0)
                                 return log_error_errno(r ?: SYNTHETIC_ERRNO(EINVAL), "Failed to parse argument: %m");
 
                         path_simplify(source);
 
-                        if (isempty(destination)) {
+                        if (isempty(dest) && isempty(flags)) {
                                 r = strv_consume(&sources, TAKE_PTR(source));
                                 if (r < 0)
                                         return bus_log_create_error(r);
+                        } else if (isempty(flags)) {
+                                path_simplify(dest);
+                                r = strv_consume_pair(&symlinks, TAKE_PTR(source), TAKE_PTR(dest));
+                                if (r < 0)
+                                        return log_oom();
                         } else {
-                                path_simplify(destination);
-
-                                r = strv_consume_pair(&symlinks, TAKE_PTR(source), TAKE_PTR(destination));
+                                ExecDirectoryFlags exec_directory_flags = exec_directory_flags_from_string(flags);
+                                if (exec_directory_flags < 0 || (exec_directory_flags & ~_EXEC_DIRECTORY_FLAGS_PUBLIC) != 0)
+                                        return log_error_errno(r, "Failed to parse flags: %s", flags);
+
+                                if (!isempty(dest)) {
+                                        path_simplify(dest);
+                                        r = strv_consume_pair(&symlinks_ro, TAKE_PTR(source), TAKE_PTR(dest));
+                                } else
+                                        r = strv_consume(&sources_ro, TAKE_PTR(source));
                                 if (r < 0)
                                         return log_oom();
                         }
@@ -2192,7 +2203,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
                 /* For State and Runtime directories we support an optional destination parameter, which
                  * will be used to create a symlink to the source. But it is new so we cannot change the
                  * old DBUS signatures, so append a new message type. */
-                if (!strv_isempty(symlinks)) {
+                if (!strv_isempty(symlinks) || !strv_isempty(symlinks_ro) || !strv_isempty(sources_ro)) {
                         const char *symlink_field;
 
                         r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv");
@@ -2228,6 +2239,18 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
                                         return bus_log_create_error(r);
                         }
 
+                        STRV_FOREACH_PAIR(source, destination, symlinks_ro) {
+                                r = sd_bus_message_append(m, "(sst)", *source, *destination, (uint64_t) EXEC_DIRECTORY_READ_ONLY);
+                                if (r < 0)
+                                        return bus_log_create_error(r);
+                        }
+
+                        STRV_FOREACH(source, sources_ro) {
+                                r = sd_bus_message_append(m, "(sst)", *source, "", (uint64_t) EXEC_DIRECTORY_READ_ONLY);
+                                if (r < 0)
+                                        return bus_log_create_error(r);
+                        }
+
                         r = sd_bus_message_close_container(m);
                         if (r < 0)
                                 return bus_log_create_error(r);
@@ -3078,3 +3101,13 @@ int unit_freezer_freeze(UnitFreezer *f) {
 int unit_freezer_thaw(UnitFreezer *f) {
         return unit_freezer_action(f, false);
 }
+
+ExecDirectoryFlags exec_directory_flags_from_string(const char *s) {
+        if (isempty(s))
+                return 0;
+
+        if (streq(s, "ro"))
+                return EXEC_DIRECTORY_READ_ONLY;
+
+        return _EXEC_DIRECTORY_FLAGS_INVALID;
+}
index 6f0d55971ce3ccdd101ea98c0e2ed6ac3ddbaabc..8090b36e3d5249e56dd7dfcee723c40388c4bae1 100644 (file)
@@ -7,6 +7,16 @@
 #include "pidref.h"
 #include "unit-def.h"
 
+typedef enum ExecDirectoryFlags {
+        EXEC_DIRECTORY_READ_ONLY      = 1 << 0, /* Public API via DBUS, do not change */
+        EXEC_DIRECTORY_ONLY_CREATE    = 1 << 1, /* Only the private directory will be created, not the symlink to it */
+        _EXEC_DIRECTORY_FLAGS_MAX,
+        _EXEC_DIRECTORY_FLAGS_PUBLIC  = EXEC_DIRECTORY_READ_ONLY,
+        _EXEC_DIRECTORY_FLAGS_INVALID = -EINVAL,
+} ExecDirectoryFlags;
+
+ExecDirectoryFlags exec_directory_flags_from_string(const char *s) _pure_;
+
 typedef struct UnitInfo {
         const char *machine;
         const char *id;
index b592314a09ccd1e43ca13da418d7fad52a409af7..cc30f0a27df36c4ecaa33ac196370d35a649627c 100755 (executable)
@@ -56,5 +56,10 @@ test "$(readlink "$HOME"/.local/state/foo)" = ../../.config/foo
 # Check that this will work safely a second time
 systemd-run --user -p StateDirectory=foo -p ConfigurationDirectory=foo --wait /bin/true
 
+( ! systemd-run --user -p StateDirectory=foo::ro --wait sh -c "echo foo > $HOME/.local/state/foo/baz")
+( ! systemd-run --user -p StateDirectory=foo:bar:ro --wait sh -c "echo foo > $HOME/.local/state/foo/baz")
+( ! test -f "$HOME"/.local/state/foo/baz)
+test -L "$HOME"/.local/state/bar
+
 rm "$HOME"/.local/state/foo
 rmdir "$HOME"/.config/foo
index d15b675e2650cf366a0752ef1ac204d87b63bf85..d6bdfb074167203b324b81935257b5b5ff0fd2fa 100755 (executable)
@@ -23,6 +23,9 @@ test_directory() {
     systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}=zzz:xxx zzz:xxx2" -p TemporaryFileSystem="${path}" bash -c "test -f ${path}/xxx/test && test -f ${path}/xxx2/test"
     systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test
     (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing)
+    systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"="www::ro www:ro:ro" test -d "${path}"/www
+    systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"="www::ro www:ro:ro" test -L "${path}"/ro
+    (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"="www::ro www:ro:ro" sh -c "echo foo > ${path}/www/test-missing")
 
     test -d "${path}"/zzz
     test ! -L "${path}"/zzz
@@ -47,6 +50,9 @@ test_directory() {
                 -p TemporaryFileSystem="${path}" -p EnvironmentFile=-/usr/lib/systemd/systemd-asan-env bash -c "test -f ${path}/xxx/test && test -f ${path}/xxx2/test"
     systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test
     (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing)
+    systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"="www::ro www:ro:ro" test -d "${path}"/www
+    systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"="www::ro www:ro:ro" test -L "${path}"/ro
+    (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"="www::ro www:ro:ro" sh -c "echo foo > ${path}/www/test-missing")
 
     test -L "${path}"/zzz
     test -d "${path}"/private/zzz
@@ -70,6 +76,9 @@ test_directory() {
     systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}=zzz:xxx zzz:xxx2" -p TemporaryFileSystem="${path}" bash -c "test -f ${path}/xxx/test && test -f ${path}/xxx2/test"
     systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test
     (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing)
+    systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"="www::ro www:ro:ro" test -d "${path}"/www
+    systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"="www::ro www:ro:ro" test -L "${path}"/ro
+    (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"="www::ro www:ro:ro" sh -c "echo foo > ${path}/www/test-missing")
 
     test -d "${path}"/zzz
     test ! -L "${path}"/zzz
@@ -84,6 +93,8 @@ test_directory() {
 
     test -f "${path}"/zzz/test
     test ! -e "${path}"/zzz/test-missing
+    test -d "${path}"/www
+    test ! -e "${path}"/www/test-missing
 
     # Exercise the unit parsing paths too
     cat >/run/systemd/system/testservice-34.service <<EOF
@@ -91,10 +102,13 @@ test_directory() {
 Type=oneshot
 TemporaryFileSystem=${path}
 RuntimeDirectoryPreserve=yes
-${directory}=zzz:x\:yz zzz:x\:yz2
+${directory}=zzz:x\:yz zzz:x\:yz2 www::ro www:ro:ro
 ExecStart=test -f ${path}/x:yz2/test
 ExecStart=test -f ${path}/x:yz/test
 ExecStart=test -f ${path}/zzz/test
+ExecStart=test -d ${path}/www
+ExecStart=test -L ${path}/ro
+ExecStart=sh -c "! test -w ${path}/www"
 EOF
     systemctl daemon-reload
     systemctl start --wait testservice-34.service