1
1
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:
Lennart Poettering 2017-11-08 17:34:39 +01:00 committed by GitHub
commit 376065ee26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 47 additions and 95 deletions

View File

@ -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

View File

@ -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;

View File

@ -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)

View File

@ -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;

View File

@ -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) {

View File

@ -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

View File

@ -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