mirror of
https://github.com/systemd/systemd-stable.git
synced 2024-10-27 01:55:32 +03:00
Merge pull request #7275 from yuwata/fix-7070-7260
core: fixes related to RuntimeDirectory=, ReadWritePaths= and DynamicUser=
This commit is contained in:
commit
376065ee26
@ -1799,20 +1799,18 @@ CapabilityBoundingSet=~CAP_B CAP_C</programlisting>
|
||||
<varname>CacheDirectoryMode=</varname>, <varname>LogsDirectoryMode=</varname> and
|
||||
<varname>ConfigurationDirectoryMode=</varname>.</para>
|
||||
|
||||
<para>Except in case of <varname>ConfigurationDirectory=</varname>, these options imply
|
||||
<varname>ReadWritePaths=</varname> for the specified paths. When combined with
|
||||
<para>These options imply <varname>BindPaths=</varname> for the specified paths. When combined with
|
||||
<varname>RootDirectory=</varname> or <varname>RootImage=</varname> these paths always reside on the host and
|
||||
are mounted from there into the unit's file system namespace. If <varname>DynamicUser=</varname> is used in
|
||||
conjunction with <varname>RuntimeDirectory=</varname>, <varname>StateDirectory=</varname>,
|
||||
<varname>CacheDirectory=</varname> and <varname>LogsDirectory=</varname>, the behaviour of these options is
|
||||
slightly altered: the directories are created below <filename>/run/private</filename>,
|
||||
<filename>/var/lib/private</filename>, <filename>/var/cache/private</filename> and
|
||||
are mounted from there into the unit's file system namespace.</para>
|
||||
|
||||
<para>If <varname>DynamicUser=</varname> is used in conjunction with <varname>StateDirectory=</varname>,
|
||||
<varname>CacheDirectory=</varname> and <varname>LogsDirectory=</varname> is slightly altered: the directories
|
||||
are created below <filename>/var/lib/private</filename>, <filename>/var/cache/private</filename> and
|
||||
<filename>/var/log/private</filename>, respectively, which are host directories made inaccessible to
|
||||
unprivileged users, which ensures that access to these directories cannot be gained through dynamic user ID
|
||||
recycling. Symbolic links are created to hide this difference in behaviour. Both from perspective of the host
|
||||
and from inside the unit, the relevant directories hence always appear directly below
|
||||
<filename>/run</filename>, <filename>/var/lib</filename>, <filename>/var/cache</filename> and
|
||||
<filename>/var/log</filename>.</para>
|
||||
<filename>/var/lib</filename>, <filename>/var/cache</filename> and <filename>/var/log</filename>.</para>
|
||||
|
||||
<para>Use <varname>RuntimeDirectory=</varname> to manage one or more runtime directories for the unit and bind
|
||||
their lifetime to the daemon runtime. This is particularly useful for unprivileged daemons that cannot create
|
||||
|
@ -1705,7 +1705,12 @@ static bool exec_needs_mount_namespace(
|
||||
!strv_isempty(context->inaccessible_paths))
|
||||
return true;
|
||||
|
||||
if (context->n_bind_mounts > 0)
|
||||
if (context->n_bind_mounts > 0 ||
|
||||
!strv_isempty(context->directories[EXEC_DIRECTORY_RUNTIME].paths) ||
|
||||
!strv_isempty(context->directories[EXEC_DIRECTORY_STATE].paths) ||
|
||||
!strv_isempty(context->directories[EXEC_DIRECTORY_CACHE].paths) ||
|
||||
!strv_isempty(context->directories[EXEC_DIRECTORY_LOGS].paths) ||
|
||||
!strv_isempty(context->directories[EXEC_DIRECTORY_CONFIGURATION].paths))
|
||||
return true;
|
||||
|
||||
if (context->mount_flags != 0)
|
||||
@ -1725,13 +1730,6 @@ static bool exec_needs_mount_namespace(
|
||||
if (context->mount_apivfs && (context->root_image || context->root_directory))
|
||||
return true;
|
||||
|
||||
if (context->dynamic_user &&
|
||||
(!strv_isempty(context->directories[EXEC_DIRECTORY_RUNTIME].paths) ||
|
||||
!strv_isempty(context->directories[EXEC_DIRECTORY_STATE].paths) ||
|
||||
!strv_isempty(context->directories[EXEC_DIRECTORY_CACHE].paths) ||
|
||||
!strv_isempty(context->directories[EXEC_DIRECTORY_LOGS].paths)))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1941,7 +1939,8 @@ static int setup_exec_directory(
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
|
||||
if (context->dynamic_user && type != EXEC_DIRECTORY_CONFIGURATION) {
|
||||
if (context->dynamic_user &&
|
||||
!IN_SET(type, EXEC_DIRECTORY_RUNTIME, EXEC_DIRECTORY_CONFIGURATION)) {
|
||||
_cleanup_free_ char *private_root = NULL, *relative = NULL, *parent = NULL;
|
||||
|
||||
/* So, here's one extra complication when dealing with DynamicUser=1 units. In that case we
|
||||
@ -1962,7 +1961,9 @@ static int setup_exec_directory(
|
||||
* dirs it needs but no others. Tricky? Yes, absolutely, but it works!
|
||||
*
|
||||
* Note that we don't do this for EXEC_DIRECTORY_CONFIGURATION as that's assumed not to be
|
||||
* owned by the service itself. */
|
||||
* owned by the service itself.
|
||||
* Also, note that we don't do this for EXEC_DIRECTORY_RUNTIME as that's often used for sharing
|
||||
* files or sockets with other services. */
|
||||
|
||||
private_root = strjoin(params->prefix[type], "/private");
|
||||
if (!private_root) {
|
||||
@ -2071,55 +2072,6 @@ static int setup_smack(
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int compile_read_write_paths(
|
||||
const ExecContext *context,
|
||||
const ExecParameters *params,
|
||||
char ***ret) {
|
||||
|
||||
_cleanup_strv_free_ char **l = NULL;
|
||||
char **rt;
|
||||
ExecDirectoryType i;
|
||||
|
||||
/* Compile the list of writable paths. This is the combination of
|
||||
* the explicitly configured paths, plus all runtime directories. */
|
||||
|
||||
if (strv_isempty(context->read_write_paths)) {
|
||||
for (i = 0; i < _EXEC_DIRECTORY_TYPE_MAX; i++)
|
||||
if (!strv_isempty(context->directories[i].paths))
|
||||
break;
|
||||
|
||||
if (i == _EXEC_DIRECTORY_TYPE_MAX) {
|
||||
*ret = NULL; /* NOP if neither is set */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
l = strv_copy(context->read_write_paths);
|
||||
if (!l)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < _EXEC_DIRECTORY_TYPE_MAX; i++) {
|
||||
if (!params->prefix[i])
|
||||
continue;
|
||||
|
||||
STRV_FOREACH(rt, context->directories[i].paths) {
|
||||
char *s;
|
||||
|
||||
s = strjoin(params->prefix[i], "/", *rt);
|
||||
if (!s)
|
||||
return -ENOMEM;
|
||||
|
||||
if (strv_consume(&l, s) < 0)
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
*ret = l;
|
||||
l = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int compile_bind_mounts(
|
||||
const ExecContext *context,
|
||||
const ExecParameters *params,
|
||||
@ -2193,7 +2145,8 @@ static int compile_bind_mounts(
|
||||
if (strv_isempty(context->directories[t].paths))
|
||||
continue;
|
||||
|
||||
if (context->dynamic_user && t != EXEC_DIRECTORY_CONFIGURATION) {
|
||||
if (context->dynamic_user &&
|
||||
!IN_SET(t, EXEC_DIRECTORY_RUNTIME, EXEC_DIRECTORY_CONFIGURATION)) {
|
||||
char *private_root;
|
||||
|
||||
/* So this is for a dynamic user, and we need to make sure the process can access its own
|
||||
@ -2216,7 +2169,8 @@ static int compile_bind_mounts(
|
||||
STRV_FOREACH(suffix, context->directories[t].paths) {
|
||||
char *s, *d;
|
||||
|
||||
if (context->dynamic_user && t != EXEC_DIRECTORY_CONFIGURATION)
|
||||
if (context->dynamic_user &&
|
||||
!IN_SET(t, EXEC_DIRECTORY_RUNTIME, EXEC_DIRECTORY_CONFIGURATION))
|
||||
s = strjoin(params->prefix[t], "/private/", *suffix);
|
||||
else
|
||||
s = strjoin(params->prefix[t], "/", *suffix);
|
||||
@ -2264,7 +2218,7 @@ static int apply_mount_namespace(
|
||||
const ExecParameters *params,
|
||||
ExecRuntime *runtime) {
|
||||
|
||||
_cleanup_strv_free_ char **rw = NULL, **empty_directories = NULL;
|
||||
_cleanup_strv_free_ char **empty_directories = NULL;
|
||||
char *tmp = NULL, *var = NULL;
|
||||
const char *root_dir = NULL, *root_image = NULL;
|
||||
NamespaceInfo ns_info = {
|
||||
@ -2293,10 +2247,6 @@ static int apply_mount_namespace(
|
||||
var = strjoina(runtime->var_tmp_dir, "/tmp");
|
||||
}
|
||||
|
||||
r = compile_read_write_paths(context, params, &rw);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (params->flags & EXEC_APPLY_CHROOT) {
|
||||
root_image = context->root_image;
|
||||
|
||||
@ -2319,7 +2269,7 @@ static int apply_mount_namespace(
|
||||
needs_sandboxing = (params->flags & EXEC_APPLY_SANDBOXING) && !(command->flags & EXEC_COMMAND_FULLY_PRIVILEGED);
|
||||
|
||||
r = setup_namespace(root_dir, root_image,
|
||||
&ns_info, rw,
|
||||
&ns_info, context->read_write_paths,
|
||||
needs_sandboxing ? context->read_only_paths : NULL,
|
||||
needs_sandboxing ? context->inaccessible_paths : NULL,
|
||||
empty_directories,
|
||||
@ -2641,7 +2591,10 @@ static int compile_suggested_paths(const ExecContext *c, const ExecParameters *p
|
||||
STRV_FOREACH(i, c->directories[t].paths) {
|
||||
char *e;
|
||||
|
||||
e = strjoin(p->prefix[t], "/private/", *i);
|
||||
if (t == EXEC_DIRECTORY_RUNTIME)
|
||||
e = strjoin(p->prefix[t], "/", *i);
|
||||
else
|
||||
e = strjoin(p->prefix[t], "/private/", *i);
|
||||
if (!e)
|
||||
return -ENOMEM;
|
||||
|
||||
@ -3601,18 +3554,6 @@ int exec_context_destroy_runtime_directory(ExecContext *c, const char *runtime_p
|
||||
/* We execute this synchronously, since we need to be sure this is gone when we start the service
|
||||
* next. */
|
||||
(void) rm_rf(p, REMOVE_ROOT);
|
||||
|
||||
/* Also destroy any matching subdirectory below /private/. This is done to support DynamicUser=1
|
||||
* setups. Note that we don't conditionalize here on that though, as the namespace is same way, and it
|
||||
* makes us a bit more robust towards changing unit settings. Or to say this differently: in the worst
|
||||
* case this is a NOP. */
|
||||
|
||||
free(p);
|
||||
p = strjoin(runtime_prefix, "/private/", *i);
|
||||
if (!p)
|
||||
return -ENOMEM;
|
||||
|
||||
(void) rm_rf(p, REMOVE_ROOT);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -198,7 +198,7 @@ Unit.PropagateReloadFrom, config_parse_unit_deps, UNIT_RELOAD
|
||||
Unit.PartOf, config_parse_unit_deps, UNIT_PART_OF, 0
|
||||
Unit.JoinsNamespaceOf, config_parse_unit_deps, UNIT_JOINS_NAMESPACE_OF, 0
|
||||
Unit.RequiresOverridable, config_parse_obsolete_unit_deps, UNIT_REQUIRES, 0
|
||||
Unit.RequisiteOverridable, config_parse_obsolete_unit_deps, UNIT_REQUISITE, 0
|
||||
Unit.RequisiteOverridable, config_parse_obsolete_unit_deps, UNIT_REQUISITE, 0
|
||||
Unit.RequiresMountsFor, config_parse_unit_requires_mounts_for, 0, 0
|
||||
Unit.StopWhenUnneeded, config_parse_bool, 0, offsetof(Unit, stop_when_unneeded)
|
||||
Unit.RefuseManualStart, config_parse_bool, 0, offsetof(Unit, refuse_manual_start)
|
||||
|
@ -191,7 +191,7 @@ static void mount_entry_done(MountEntry *p) {
|
||||
p->source_malloc = mfree(p->source_malloc);
|
||||
}
|
||||
|
||||
static int append_access_mounts(MountEntry **p, char **strv, MountMode mode) {
|
||||
static int append_access_mounts(MountEntry **p, char **strv, MountMode mode, bool forcibly_require_prefix) {
|
||||
char **i;
|
||||
|
||||
assert(p);
|
||||
@ -219,7 +219,7 @@ static int append_access_mounts(MountEntry **p, char **strv, MountMode mode) {
|
||||
.path_const = e,
|
||||
.mode = mode,
|
||||
.ignore = ignore,
|
||||
.has_prefix = !needs_prefix,
|
||||
.has_prefix = !needs_prefix && !forcibly_require_prefix,
|
||||
};
|
||||
}
|
||||
|
||||
@ -983,6 +983,7 @@ int setup_namespace(
|
||||
bool make_slave = false;
|
||||
const char *root;
|
||||
unsigned n_mounts;
|
||||
bool require_prefix = false;
|
||||
int r = 0;
|
||||
|
||||
assert(ns_info);
|
||||
@ -1027,6 +1028,7 @@ int setup_namespace(
|
||||
|
||||
root = "/run/systemd/unit-root";
|
||||
(void) mkdir_label(root, 0700);
|
||||
require_prefix = true;
|
||||
} else
|
||||
root = NULL;
|
||||
|
||||
@ -1047,15 +1049,15 @@ int setup_namespace(
|
||||
|
||||
if (n_mounts > 0) {
|
||||
m = mounts = (MountEntry *) alloca0(n_mounts * sizeof(MountEntry));
|
||||
r = append_access_mounts(&m, read_write_paths, READWRITE);
|
||||
r = append_access_mounts(&m, read_write_paths, READWRITE, require_prefix);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
r = append_access_mounts(&m, read_only_paths, READONLY);
|
||||
r = append_access_mounts(&m, read_only_paths, READONLY, require_prefix);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
r = append_access_mounts(&m, inaccessible_paths, INACCESSIBLE);
|
||||
r = append_access_mounts(&m, inaccessible_paths, INACCESSIBLE, require_prefix);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
|
@ -225,6 +225,7 @@ static void test_exec_readonlypaths(Manager *m) {
|
||||
|
||||
test(m, "exec-readonlypaths.service", 0, CLD_EXITED);
|
||||
test(m, "exec-readonlypaths-mount-propagation.service", 0, CLD_EXITED);
|
||||
test(m, "exec-readonlypaths-with-bindpaths.service", 0, CLD_EXITED);
|
||||
}
|
||||
|
||||
static void test_exec_readwritepaths(Manager *m) {
|
||||
|
@ -81,6 +81,7 @@ test_data_files = '''
|
||||
test-execute/exec-protectkernelmodules-yes-mount-propagation.service
|
||||
test-execute/exec-read-only-path-succeed.service
|
||||
test-execute/exec-readonlypaths-mount-propagation.service
|
||||
test-execute/exec-readonlypaths-with-bindpaths.service
|
||||
test-execute/exec-readonlypaths.service
|
||||
test-execute/exec-readwritepaths-mount-propagation.service
|
||||
test-execute/exec-restrict-namespaces-mnt-blacklist.service
|
||||
|
@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=Test for ReadOnlyPaths=
|
||||
|
||||
[Service]
|
||||
ReadOnlyPaths=/etc -/i-dont-exist /usr
|
||||
# From 6c47cd7d3bf35c8158a0737f34fe2c5dc95e72d6, RuntimeDirectory= implies BindPaths=.
|
||||
RuntimeDirectory=foo
|
||||
ExecStart=/bin/sh -x -c 'test ! -w /etc && test ! -w /usr && test ! -e /i-dont-exist && test -w /var'
|
||||
Type=oneshot
|
Loading…
Reference in New Issue
Block a user