core: add read-only flag for exec directories

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.
This commit is contained in:
Luca Boccassi
2024-11-01 10:46:55 +00:00
parent 7fdcd903ab
commit 890bdd1d77
12 changed files with 188 additions and 63 deletions
+6 -2
View 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>
+12 -6
View 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
+36 -18
View 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)
+8 -5
View 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);
+19 -8
View 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);
if (r < 0)
return r;
r = parse_boolean(only_create);
if (r < 0)
return r;
c->directories[dt].items[c->directories[dt].n_items - 1].only_create = r;
if (r > 0)
exec_directory_flags |= EXEC_DIRECTORY_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;
if (isempty(p))
continue;
+17 -4
View 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;
}
}
+3 -2
View 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);
+17 -10
View 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();
}
+40 -7
View 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);
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);
r = strv_consume_pair(&symlinks, TAKE_PTR(source), TAKE_PTR(destination));
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;
}
+10
View 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;
+5
View File
@@ -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
+15 -1
View File
@@ -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