2017-11-18 17:09:20 +01:00
|
|
|
/* SPDX-License-Identifier: LGPL-2.1+ */
|
2010-04-21 22:15:06 +02:00
|
|
|
/***
|
|
|
|
This file is part of systemd.
|
|
|
|
|
|
|
|
Copyright 2010 Lennart Poettering
|
|
|
|
|
|
|
|
systemd is free software; you can redistribute it and/or modify it
|
2012-04-12 00:20:58 +02:00
|
|
|
under the terms of the GNU Lesser General Public License as published by
|
|
|
|
the Free Software Foundation; either version 2.1 of the License, or
|
2010-04-21 22:15:06 +02:00
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
systemd is distributed in the hope that it will be useful, but
|
|
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
2012-04-12 00:20:58 +02:00
|
|
|
Lesser General Public License for more details.
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2012-04-12 00:20:58 +02:00
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
2010-04-21 22:15:06 +02:00
|
|
|
along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
***/
|
|
|
|
|
|
|
|
#include <errno.h>
|
2015-10-24 22:58:24 +02:00
|
|
|
#include <sched.h>
|
2010-04-21 22:15:06 +02:00
|
|
|
#include <stdio.h>
|
2015-10-24 22:58:24 +02:00
|
|
|
#include <string.h>
|
|
|
|
#include <sys/mount.h>
|
2010-04-21 22:15:06 +02:00
|
|
|
#include <sys/stat.h>
|
2015-10-24 22:58:24 +02:00
|
|
|
#include <unistd.h>
|
2010-04-24 05:05:01 +02:00
|
|
|
#include <linux/fs.h>
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2015-10-27 03:01:06 +01:00
|
|
|
#include "alloc-util.h"
|
2017-03-05 21:19:29 +01:00
|
|
|
#include "base-filesystem.h"
|
2014-01-20 19:54:51 +01:00
|
|
|
#include "dev-setup.h"
|
2015-10-25 13:14:12 +01:00
|
|
|
#include "fd-util.h"
|
2016-09-24 12:41:30 +02:00
|
|
|
#include "fs-util.h"
|
2017-09-28 18:30:55 +02:00
|
|
|
#include "label.h"
|
2016-12-23 14:26:05 +01:00
|
|
|
#include "loop-util.h"
|
2015-10-24 22:58:24 +02:00
|
|
|
#include "loopback-setup.h"
|
|
|
|
#include "missing.h"
|
|
|
|
#include "mkdir.h"
|
2015-10-26 18:44:13 +01:00
|
|
|
#include "mount-util.h"
|
2015-10-25 13:14:12 +01:00
|
|
|
#include "namespace.h"
|
2015-10-24 22:58:24 +02:00
|
|
|
#include "path-util.h"
|
2014-12-27 18:46:36 +01:00
|
|
|
#include "selinux-util.h"
|
2015-10-26 01:09:02 +01:00
|
|
|
#include "socket-util.h"
|
2018-01-23 19:36:55 +01:00
|
|
|
#include "stat-util.h"
|
2015-10-26 22:31:05 +01:00
|
|
|
#include "string-table.h"
|
2015-10-24 22:58:24 +02:00
|
|
|
#include "string-util.h"
|
|
|
|
#include "strv.h"
|
2015-10-26 23:20:41 +01:00
|
|
|
#include "umask-util.h"
|
2015-10-27 00:42:07 +01:00
|
|
|
#include "user-util.h"
|
2015-10-24 22:58:24 +02:00
|
|
|
#include "util.h"
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2016-05-16 04:34:05 +02:00
|
|
|
#define DEV_MOUNT_OPTIONS (MS_NOSUID|MS_STRICTATIME|MS_NOEXEC)
|
|
|
|
|
2013-03-14 18:12:27 +01:00
|
|
|
typedef enum MountMode {
|
2010-04-21 22:15:06 +02:00
|
|
|
/* This is ordered by priority! */
|
|
|
|
INACCESSIBLE,
|
2016-11-23 22:21:40 +01:00
|
|
|
BIND_MOUNT,
|
|
|
|
BIND_MOUNT_RECURSIVE,
|
2012-08-13 15:27:04 +02:00
|
|
|
PRIVATE_TMP,
|
|
|
|
PRIVATE_VAR_TMP,
|
2014-01-20 19:54:51 +01:00
|
|
|
PRIVATE_DEV,
|
2016-12-22 23:34:35 +01:00
|
|
|
BIND_DEV,
|
execute: make StateDirectory= and friends compatible with DynamicUser=1 and RootDirectory=/RootImage=
Let's clean up the interaction of StateDirectory= (and friends) to
DynamicUser=1: instead of creating these directories directly below
/var/lib, place them in /var/lib/private instead if DynamicUser=1 is
set, making that directory 0700 and owned by root:root. This way, if a
dynamic UID is later reused, access to the old run's state directory is
prohibited for that user. Then, use file system namespacing inside the
service to make /var/lib/private a readable tmpfs, hiding all state
directories that are not listed in StateDirectory=, and making access to
the actual state directory possible. Mount all directories listed in
StateDirectory= to the same places inside the service (which means
they'll now be mounted into the tmpfs instance). Finally, add a symlink
from the state directory name in /var/lib/ to the one in
/var/lib/private, so that both the host and the service can access the
path under the same location.
Here's an example: let's say a service runs with StateDirectory=foo.
When DynamicUser=0 is set, it will get the following setup, and no
difference between what the unit and what the host sees:
/var/lib/foo (created as directory)
Now, if DynamicUser=1 is set, we'll instead get this on the host:
/var/lib/private (created as directory with mode 0700, root:root)
/var/lib/private/foo (created as directory)
/var/lib/foo → private/foo (created as symlink)
And from inside the unit:
/var/lib/private (a tmpfs mount with mode 0755, root:root)
/var/lib/private/foo (bind mounted from the host)
/var/lib/foo → private/foo (the same symlink as above)
This takes inspiration from how container trees are protected below
/var/lib/machines: they generally reuse UIDs/GIDs of the host, but
because /var/lib/machines itself is set to 0700 host users cannot access
files in the container tree even if the UIDs/GIDs are reused. However,
for this commit we add one further trick: inside and outside of the unit
/var/lib/private is a different thing: outside it is a plain,
inaccessible directory, and inside it is a world-readable tmpfs mount
with only the whitelisted subdirs below it, bind mounte din. This
means, from the outside the dir acts as an access barrier, but from the
inside it does not. And the symlink created in /var/lib/foo itself
points across the barrier in both cases, so that root and the unit's
user always have access to these dirs without knowing the details of
this mounting magic.
This logic resolves a major shortcoming of DynamicUser=1 units:
previously they couldn't safely store persistant data. With this change
they can have their own private state, log and data directories, which
they can write to, but which are protected from UID recycling.
With this change, if RootDirectory= or RootImage= are used it is ensured
that the specified state/log/cache directories are always mounted in
from the host. This change of semantics I think is much preferable since
this means the root directory/image logic can be used easily for
read-only resource bundling (as all writable data resides outside of the
image). Note that this is a change of behaviour, but given that we
haven't released any systemd version with StateDirectory= and friends
implemented this should be a safe change to make (in particular as
previously it wasn't clear what would actually happen when used in
combination). Moreover, by making this change we can later add a "+"
modifier to these setings too working similar to the same modifier in
ReadOnlyPaths= and friends, making specified paths relative to the
container itself.
2017-09-28 18:55:45 +02:00
|
|
|
EMPTY_DIR,
|
2016-12-22 23:34:35 +01:00
|
|
|
SYSFS,
|
|
|
|
PROCFS,
|
|
|
|
READONLY,
|
2016-08-22 18:43:59 +02:00
|
|
|
READWRITE,
|
2013-03-14 18:12:27 +01:00
|
|
|
} MountMode;
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2016-12-14 00:48:52 +01:00
|
|
|
typedef struct MountEntry {
|
2016-11-15 01:42:54 +01:00
|
|
|
const char *path_const; /* Memory allocated on stack or static */
|
2016-11-23 01:09:14 +01:00
|
|
|
MountMode mode:5;
|
2016-11-15 01:42:54 +01:00
|
|
|
bool ignore:1; /* Ignore if path does not exist? */
|
|
|
|
bool has_prefix:1; /* Already is prefixed by the root dir? */
|
2016-11-23 01:09:14 +01:00
|
|
|
bool read_only:1; /* Shall this mount point be read-only? */
|
2016-11-15 01:42:54 +01:00
|
|
|
char *path_malloc; /* Use this instead of 'path' if we had to allocate memory */
|
2016-11-23 22:21:40 +01:00
|
|
|
const char *source_const; /* The source path, for bind mounts */
|
|
|
|
char *source_malloc;
|
2016-12-14 00:48:52 +01:00
|
|
|
} MountEntry;
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2016-12-22 23:34:35 +01:00
|
|
|
/* If MountAPIVFS= is used, let's mount /sys and /proc into the it, but only as a fallback if the user hasn't mounted
|
|
|
|
* something there already. These mounts are hence overriden by any other explicitly configured mounts. */
|
|
|
|
static const MountEntry apivfs_table[] = {
|
|
|
|
{ "/proc", PROCFS, false },
|
|
|
|
{ "/dev", BIND_DEV, false },
|
|
|
|
{ "/sys", SYSFS, false },
|
|
|
|
};
|
2016-09-25 12:21:25 +02:00
|
|
|
|
2016-09-25 11:16:44 +02:00
|
|
|
/* ProtectKernelTunables= option and the related filesystem APIs */
|
2016-12-14 00:48:52 +01:00
|
|
|
static const MountEntry protect_kernel_tunables_table[] = {
|
2016-11-17 18:06:05 +01:00
|
|
|
{ "/proc/sys", READONLY, false },
|
|
|
|
{ "/proc/sysrq-trigger", READONLY, true },
|
|
|
|
{ "/proc/latency_stats", READONLY, true },
|
|
|
|
{ "/proc/mtrr", READONLY, true },
|
2016-11-17 18:10:01 +01:00
|
|
|
{ "/proc/apm", READONLY, true }, /* Obsolete API, there's no point in permitting access to this, ever */
|
2016-11-17 18:06:05 +01:00
|
|
|
{ "/proc/acpi", READONLY, true },
|
|
|
|
{ "/proc/timer_stats", READONLY, true },
|
|
|
|
{ "/proc/asound", READONLY, true },
|
|
|
|
{ "/proc/bus", READONLY, true },
|
|
|
|
{ "/proc/fs", READONLY, true },
|
|
|
|
{ "/proc/irq", READONLY, true },
|
|
|
|
{ "/sys", READONLY, false },
|
|
|
|
{ "/sys/kernel/debug", READONLY, true },
|
|
|
|
{ "/sys/kernel/tracing", READONLY, true },
|
|
|
|
{ "/sys/fs/cgroup", READWRITE, false }, /* READONLY is set by ProtectControlGroups= option */
|
2017-07-31 17:45:33 +02:00
|
|
|
{ "/sys/fs/selinux", READWRITE, true },
|
2016-09-25 11:16:44 +02:00
|
|
|
};
|
|
|
|
|
2016-10-12 14:11:16 +02:00
|
|
|
/* ProtectKernelModules= option */
|
2016-12-14 00:48:52 +01:00
|
|
|
static const MountEntry protect_kernel_modules_table[] = {
|
2017-10-03 10:41:51 +02:00
|
|
|
#if HAVE_SPLIT_USR
|
2016-11-17 18:06:05 +01:00
|
|
|
{ "/lib/modules", INACCESSIBLE, true },
|
2016-10-12 14:11:16 +02:00
|
|
|
#endif
|
2016-11-17 18:06:05 +01:00
|
|
|
{ "/usr/lib/modules", INACCESSIBLE, true },
|
2016-10-12 14:11:16 +02:00
|
|
|
};
|
|
|
|
|
2016-09-25 12:41:16 +02:00
|
|
|
/*
|
|
|
|
* ProtectHome=read-only table, protect $HOME and $XDG_RUNTIME_DIR and rest of
|
|
|
|
* system should be protected by ProtectSystem=
|
|
|
|
*/
|
2016-12-14 00:48:52 +01:00
|
|
|
static const MountEntry protect_home_read_only_table[] = {
|
2016-11-17 18:06:05 +01:00
|
|
|
{ "/home", READONLY, true },
|
|
|
|
{ "/run/user", READONLY, true },
|
|
|
|
{ "/root", READONLY, true },
|
2016-09-25 12:41:16 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/* ProtectHome=yes table */
|
2016-12-14 00:48:52 +01:00
|
|
|
static const MountEntry protect_home_yes_table[] = {
|
2016-11-17 18:06:05 +01:00
|
|
|
{ "/home", INACCESSIBLE, true },
|
|
|
|
{ "/run/user", INACCESSIBLE, true },
|
|
|
|
{ "/root", INACCESSIBLE, true },
|
2016-09-25 12:41:16 +02:00
|
|
|
};
|
|
|
|
|
2016-09-25 12:21:25 +02:00
|
|
|
/* ProtectSystem=yes table */
|
2016-12-14 00:48:52 +01:00
|
|
|
static const MountEntry protect_system_yes_table[] = {
|
2016-11-17 18:06:05 +01:00
|
|
|
{ "/usr", READONLY, false },
|
|
|
|
{ "/boot", READONLY, true },
|
|
|
|
{ "/efi", READONLY, true },
|
2016-09-25 12:21:25 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/* ProtectSystem=full includes ProtectSystem=yes */
|
2016-12-14 00:48:52 +01:00
|
|
|
static const MountEntry protect_system_full_table[] = {
|
2016-11-17 18:06:05 +01:00
|
|
|
{ "/usr", READONLY, false },
|
|
|
|
{ "/boot", READONLY, true },
|
|
|
|
{ "/efi", READONLY, true },
|
|
|
|
{ "/etc", READONLY, false },
|
2016-09-25 12:21:25 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ProtectSystem=strict table. In this strict mode, we mount everything
|
|
|
|
* read-only, except for /proc, /dev, /sys which are the kernel API VFS,
|
|
|
|
* which are left writable, but PrivateDevices= + ProtectKernelTunables=
|
|
|
|
* protect those, and these options should be fully orthogonal.
|
|
|
|
* (And of course /home and friends are also left writable, as ProtectHome=
|
|
|
|
* shall manage those, orthogonally).
|
|
|
|
*/
|
2016-12-14 00:48:52 +01:00
|
|
|
static const MountEntry protect_system_strict_table[] = {
|
2016-11-22 20:21:23 +01:00
|
|
|
{ "/", READONLY, false },
|
|
|
|
{ "/proc", READWRITE, false }, /* ProtectKernelTunables= */
|
|
|
|
{ "/sys", READWRITE, false }, /* ProtectKernelTunables= */
|
|
|
|
{ "/dev", READWRITE, false }, /* PrivateDevices= */
|
|
|
|
{ "/home", READWRITE, true }, /* ProtectHome= */
|
|
|
|
{ "/run/user", READWRITE, true }, /* ProtectHome= */
|
|
|
|
{ "/root", READWRITE, true }, /* ProtectHome= */
|
2016-09-25 12:21:25 +02:00
|
|
|
};
|
|
|
|
|
2016-12-14 00:48:52 +01:00
|
|
|
static const char *mount_entry_path(const MountEntry *p) {
|
2016-11-06 22:51:49 +01:00
|
|
|
assert(p);
|
|
|
|
|
2016-11-15 01:42:54 +01:00
|
|
|
/* Returns the path of this bind mount. If the malloc()-allocated ->path_buffer field is set we return that,
|
|
|
|
* otherwise the stack/static ->path field is returned. */
|
2016-11-06 22:51:49 +01:00
|
|
|
|
2016-11-15 01:42:54 +01:00
|
|
|
return p->path_malloc ?: p->path_const;
|
2016-11-06 22:51:49 +01:00
|
|
|
}
|
|
|
|
|
2016-12-14 00:48:52 +01:00
|
|
|
static bool mount_entry_read_only(const MountEntry *p) {
|
2016-11-23 01:09:14 +01:00
|
|
|
assert(p);
|
|
|
|
|
|
|
|
return p->read_only || IN_SET(p->mode, READONLY, INACCESSIBLE);
|
|
|
|
}
|
|
|
|
|
2016-11-23 22:21:40 +01:00
|
|
|
static const char *mount_entry_source(const MountEntry *p) {
|
|
|
|
assert(p);
|
|
|
|
|
|
|
|
return p->source_malloc ?: p->source_const;
|
|
|
|
}
|
|
|
|
|
2016-12-22 17:11:06 +01:00
|
|
|
static void mount_entry_done(MountEntry *p) {
|
|
|
|
assert(p);
|
|
|
|
|
|
|
|
p->path_malloc = mfree(p->path_malloc);
|
|
|
|
p->source_malloc = mfree(p->source_malloc);
|
|
|
|
}
|
|
|
|
|
2017-10-13 14:22:25 +02:00
|
|
|
static int append_access_mounts(MountEntry **p, char **strv, MountMode mode, bool forcibly_require_prefix) {
|
2010-04-21 22:15:06 +02:00
|
|
|
char **i;
|
|
|
|
|
2013-11-27 20:23:18 +01:00
|
|
|
assert(p);
|
|
|
|
|
2016-11-15 01:42:54 +01:00
|
|
|
/* Adds a list of user-supplied READWRITE/READONLY/INACCESSIBLE entries */
|
|
|
|
|
2010-04-21 22:15:06 +02:00
|
|
|
STRV_FOREACH(i, strv) {
|
2016-11-15 01:42:54 +01:00
|
|
|
bool ignore = false, needs_prefix = false;
|
|
|
|
const char *e = *i;
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2016-11-15 01:42:54 +01:00
|
|
|
/* Look for any prefixes */
|
|
|
|
if (startswith(e, "-")) {
|
|
|
|
e++;
|
2016-09-25 11:03:21 +02:00
|
|
|
ignore = true;
|
2013-08-21 16:43:55 +02:00
|
|
|
}
|
2016-11-15 01:42:54 +01:00
|
|
|
if (startswith(e, "+")) {
|
|
|
|
e++;
|
|
|
|
needs_prefix = true;
|
|
|
|
}
|
2013-08-21 16:43:55 +02:00
|
|
|
|
2016-11-15 01:42:54 +01:00
|
|
|
if (!path_is_absolute(e))
|
2010-04-21 22:15:06 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
2016-12-14 00:48:52 +01:00
|
|
|
*((*p)++) = (MountEntry) {
|
2016-11-15 01:42:54 +01:00
|
|
|
.path_const = e,
|
|
|
|
.mode = mode,
|
|
|
|
.ignore = ignore,
|
2017-10-13 14:22:25 +02:00
|
|
|
.has_prefix = !needs_prefix && !forcibly_require_prefix,
|
2016-11-15 01:42:54 +01:00
|
|
|
};
|
2010-04-21 22:15:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
execute: make StateDirectory= and friends compatible with DynamicUser=1 and RootDirectory=/RootImage=
Let's clean up the interaction of StateDirectory= (and friends) to
DynamicUser=1: instead of creating these directories directly below
/var/lib, place them in /var/lib/private instead if DynamicUser=1 is
set, making that directory 0700 and owned by root:root. This way, if a
dynamic UID is later reused, access to the old run's state directory is
prohibited for that user. Then, use file system namespacing inside the
service to make /var/lib/private a readable tmpfs, hiding all state
directories that are not listed in StateDirectory=, and making access to
the actual state directory possible. Mount all directories listed in
StateDirectory= to the same places inside the service (which means
they'll now be mounted into the tmpfs instance). Finally, add a symlink
from the state directory name in /var/lib/ to the one in
/var/lib/private, so that both the host and the service can access the
path under the same location.
Here's an example: let's say a service runs with StateDirectory=foo.
When DynamicUser=0 is set, it will get the following setup, and no
difference between what the unit and what the host sees:
/var/lib/foo (created as directory)
Now, if DynamicUser=1 is set, we'll instead get this on the host:
/var/lib/private (created as directory with mode 0700, root:root)
/var/lib/private/foo (created as directory)
/var/lib/foo → private/foo (created as symlink)
And from inside the unit:
/var/lib/private (a tmpfs mount with mode 0755, root:root)
/var/lib/private/foo (bind mounted from the host)
/var/lib/foo → private/foo (the same symlink as above)
This takes inspiration from how container trees are protected below
/var/lib/machines: they generally reuse UIDs/GIDs of the host, but
because /var/lib/machines itself is set to 0700 host users cannot access
files in the container tree even if the UIDs/GIDs are reused. However,
for this commit we add one further trick: inside and outside of the unit
/var/lib/private is a different thing: outside it is a plain,
inaccessible directory, and inside it is a world-readable tmpfs mount
with only the whitelisted subdirs below it, bind mounte din. This
means, from the outside the dir acts as an access barrier, but from the
inside it does not. And the symlink created in /var/lib/foo itself
points across the barrier in both cases, so that root and the unit's
user always have access to these dirs without knowing the details of
this mounting magic.
This logic resolves a major shortcoming of DynamicUser=1 units:
previously they couldn't safely store persistant data. With this change
they can have their own private state, log and data directories, which
they can write to, but which are protected from UID recycling.
With this change, if RootDirectory= or RootImage= are used it is ensured
that the specified state/log/cache directories are always mounted in
from the host. This change of semantics I think is much preferable since
this means the root directory/image logic can be used easily for
read-only resource bundling (as all writable data resides outside of the
image). Note that this is a change of behaviour, but given that we
haven't released any systemd version with StateDirectory= and friends
implemented this should be a safe change to make (in particular as
previously it wasn't clear what would actually happen when used in
combination). Moreover, by making this change we can later add a "+"
modifier to these setings too working similar to the same modifier in
ReadOnlyPaths= and friends, making specified paths relative to the
container itself.
2017-09-28 18:55:45 +02:00
|
|
|
static int append_empty_dir_mounts(MountEntry **p, char **strv) {
|
|
|
|
char **i;
|
|
|
|
|
|
|
|
assert(p);
|
|
|
|
|
|
|
|
/* Adds tmpfs mounts to provide readable but empty directories. This is primarily used to implement the
|
|
|
|
* "/private/" boundary directories for DynamicUser=1. */
|
|
|
|
|
|
|
|
STRV_FOREACH(i, strv) {
|
|
|
|
|
|
|
|
*((*p)++) = (MountEntry) {
|
|
|
|
.path_const = *i,
|
|
|
|
.mode = EMPTY_DIR,
|
|
|
|
.ignore = false,
|
|
|
|
.has_prefix = false,
|
|
|
|
.read_only = true,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-11-23 22:21:40 +01:00
|
|
|
static int append_bind_mounts(MountEntry **p, const BindMount *binds, unsigned n) {
|
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
assert(p);
|
|
|
|
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
const BindMount *b = binds + i;
|
|
|
|
|
|
|
|
*((*p)++) = (MountEntry) {
|
|
|
|
.path_const = b->destination,
|
|
|
|
.mode = b->recursive ? BIND_MOUNT_RECURSIVE : BIND_MOUNT,
|
|
|
|
.read_only = b->read_only,
|
|
|
|
.source_const = b->source,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-12-14 00:48:52 +01:00
|
|
|
static int append_static_mounts(MountEntry **p, const MountEntry *mounts, unsigned n, bool ignore_protect) {
|
2016-09-25 12:21:25 +02:00
|
|
|
unsigned i;
|
2016-09-25 11:16:44 +02:00
|
|
|
|
|
|
|
assert(p);
|
2016-09-25 12:21:25 +02:00
|
|
|
assert(mounts);
|
2016-09-25 11:16:44 +02:00
|
|
|
|
2016-11-15 01:42:54 +01:00
|
|
|
/* Adds a list of static pre-defined entries */
|
2016-09-25 12:21:25 +02:00
|
|
|
|
2016-11-15 01:42:54 +01:00
|
|
|
for (i = 0; i < n; i++)
|
2016-12-14 00:48:52 +01:00
|
|
|
*((*p)++) = (MountEntry) {
|
|
|
|
.path_const = mount_entry_path(mounts+i),
|
2016-11-15 01:42:54 +01:00
|
|
|
.mode = mounts[i].mode,
|
|
|
|
.ignore = mounts[i].ignore || ignore_protect,
|
|
|
|
};
|
2016-09-25 12:21:25 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-12-14 00:48:52 +01:00
|
|
|
static int append_protect_home(MountEntry **p, ProtectHome protect_home, bool ignore_protect) {
|
2016-10-12 14:11:16 +02:00
|
|
|
assert(p);
|
|
|
|
|
2016-11-15 01:42:54 +01:00
|
|
|
switch (protect_home) {
|
2016-09-25 12:41:16 +02:00
|
|
|
|
2016-11-15 01:42:54 +01:00
|
|
|
case PROTECT_HOME_NO:
|
2016-09-25 12:41:16 +02:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
case PROTECT_HOME_READ_ONLY:
|
2016-11-15 01:42:54 +01:00
|
|
|
return append_static_mounts(p, protect_home_read_only_table, ELEMENTSOF(protect_home_read_only_table), ignore_protect);
|
|
|
|
|
2016-09-25 12:41:16 +02:00
|
|
|
case PROTECT_HOME_YES:
|
2016-11-15 01:42:54 +01:00
|
|
|
return append_static_mounts(p, protect_home_yes_table, ELEMENTSOF(protect_home_yes_table), ignore_protect);
|
|
|
|
|
2016-09-25 12:41:16 +02:00
|
|
|
default:
|
2016-11-15 01:42:54 +01:00
|
|
|
assert_not_reached("Unexpected ProtectHome= value");
|
2016-09-25 12:41:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-14 00:48:52 +01:00
|
|
|
static int append_protect_system(MountEntry **p, ProtectSystem protect_system, bool ignore_protect) {
|
2016-09-25 12:21:25 +02:00
|
|
|
assert(p);
|
|
|
|
|
2016-11-15 01:42:54 +01:00
|
|
|
switch (protect_system) {
|
|
|
|
|
|
|
|
case PROTECT_SYSTEM_NO:
|
2016-09-25 12:21:25 +02:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
case PROTECT_SYSTEM_STRICT:
|
2016-11-15 01:42:54 +01:00
|
|
|
return append_static_mounts(p, protect_system_strict_table, ELEMENTSOF(protect_system_strict_table), ignore_protect);
|
|
|
|
|
2016-09-25 12:21:25 +02:00
|
|
|
case PROTECT_SYSTEM_YES:
|
2016-11-15 01:42:54 +01:00
|
|
|
return append_static_mounts(p, protect_system_yes_table, ELEMENTSOF(protect_system_yes_table), ignore_protect);
|
|
|
|
|
2016-09-25 12:21:25 +02:00
|
|
|
case PROTECT_SYSTEM_FULL:
|
2016-11-15 01:42:54 +01:00
|
|
|
return append_static_mounts(p, protect_system_full_table, ELEMENTSOF(protect_system_full_table), ignore_protect);
|
|
|
|
|
2016-09-25 12:21:25 +02:00
|
|
|
default:
|
2016-11-15 01:42:54 +01:00
|
|
|
assert_not_reached("Unexpected ProtectSystem= value");
|
2016-09-25 12:21:25 +02:00
|
|
|
}
|
2016-09-25 11:16:44 +02:00
|
|
|
}
|
|
|
|
|
2013-03-14 18:12:27 +01:00
|
|
|
static int mount_path_compare(const void *a, const void *b) {
|
2016-12-14 00:48:52 +01:00
|
|
|
const MountEntry *p = a, *q = b;
|
2015-03-16 22:04:21 +01:00
|
|
|
int d;
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2016-08-25 10:44:09 +02:00
|
|
|
/* If the paths are not equal, then order prefixes first */
|
2016-12-14 00:48:52 +01:00
|
|
|
d = path_compare(mount_entry_path(p), mount_entry_path(q));
|
2016-08-25 10:44:09 +02:00
|
|
|
if (d != 0)
|
|
|
|
return d;
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2016-08-25 10:44:09 +02:00
|
|
|
/* If the paths are equal, check the mode */
|
|
|
|
if (p->mode < q->mode)
|
|
|
|
return -1;
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2016-08-25 10:44:09 +02:00
|
|
|
if (p->mode > q->mode)
|
|
|
|
return 1;
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2016-08-25 10:44:09 +02:00
|
|
|
return 0;
|
2010-04-21 22:15:06 +02:00
|
|
|
}
|
|
|
|
|
2016-12-14 00:48:52 +01:00
|
|
|
static int prefix_where_needed(MountEntry *m, unsigned n, const char *root_directory) {
|
2016-11-15 01:42:54 +01:00
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
/* Prefixes all paths in the bind mount table with the root directory if it is specified and the entry needs
|
|
|
|
* that. */
|
|
|
|
|
|
|
|
if (!root_directory)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
char *s;
|
|
|
|
|
|
|
|
if (m[i].has_prefix)
|
|
|
|
continue;
|
|
|
|
|
2016-12-14 00:48:52 +01:00
|
|
|
s = prefix_root(root_directory, mount_entry_path(m+i));
|
2016-11-15 01:42:54 +01:00
|
|
|
if (!s)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
free(m[i].path_malloc);
|
|
|
|
m[i].path_malloc = s;
|
|
|
|
|
|
|
|
m[i].has_prefix = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-12-14 00:48:52 +01:00
|
|
|
static void drop_duplicates(MountEntry *m, unsigned *n) {
|
|
|
|
MountEntry *f, *t, *previous;
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2013-03-14 18:12:27 +01:00
|
|
|
assert(m);
|
2010-04-21 22:15:06 +02:00
|
|
|
assert(n);
|
|
|
|
|
2016-08-24 23:17:42 +02:00
|
|
|
/* Drops duplicate entries. Expects that the array is properly ordered already. */
|
|
|
|
|
2016-11-10 18:11:37 +01:00
|
|
|
for (f = m, t = m, previous = NULL; f < m + *n; f++) {
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2016-08-24 23:17:42 +02:00
|
|
|
/* The first one wins (which is the one with the more restrictive mode), see mount_path_compare()
|
|
|
|
* above. */
|
2016-12-14 00:48:52 +01:00
|
|
|
if (previous && path_equal(mount_entry_path(f), mount_entry_path(previous))) {
|
|
|
|
log_debug("%s is duplicate.", mount_entry_path(f));
|
|
|
|
previous->read_only = previous->read_only || mount_entry_read_only(f); /* Propagate the read-only flag to the remaining entry */
|
2016-12-22 17:11:06 +01:00
|
|
|
mount_entry_done(f);
|
2010-04-21 22:15:06 +02:00
|
|
|
continue;
|
2016-08-24 23:17:42 +02:00
|
|
|
}
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2014-07-27 16:32:13 +02:00
|
|
|
*t = *f;
|
2010-04-21 22:15:06 +02:00
|
|
|
previous = t;
|
2016-08-24 23:17:42 +02:00
|
|
|
t++;
|
|
|
|
}
|
|
|
|
|
|
|
|
*n = t - m;
|
|
|
|
}
|
|
|
|
|
2016-12-14 00:48:52 +01:00
|
|
|
static void drop_inaccessible(MountEntry *m, unsigned *n) {
|
|
|
|
MountEntry *f, *t;
|
2016-08-24 23:17:42 +02:00
|
|
|
const char *clear = NULL;
|
|
|
|
|
|
|
|
assert(m);
|
|
|
|
assert(n);
|
|
|
|
|
|
|
|
/* Drops all entries obstructed by another entry further up the tree. Expects that the array is properly
|
|
|
|
* ordered already. */
|
|
|
|
|
2016-11-10 18:11:37 +01:00
|
|
|
for (f = m, t = m; f < m + *n; f++) {
|
2016-08-24 23:17:42 +02:00
|
|
|
|
|
|
|
/* If we found a path set for INACCESSIBLE earlier, and this entry has it as prefix we should drop
|
|
|
|
* it, as inaccessible paths really should drop the entire subtree. */
|
2016-12-14 00:48:52 +01:00
|
|
|
if (clear && path_startswith(mount_entry_path(f), clear)) {
|
|
|
|
log_debug("%s is masked by %s.", mount_entry_path(f), clear);
|
2016-12-22 17:11:06 +01:00
|
|
|
mount_entry_done(f);
|
2016-08-24 23:17:42 +02:00
|
|
|
continue;
|
|
|
|
}
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2016-12-14 00:48:52 +01:00
|
|
|
clear = f->mode == INACCESSIBLE ? mount_entry_path(f) : NULL;
|
2016-08-24 23:17:42 +02:00
|
|
|
|
|
|
|
*t = *f;
|
2010-04-21 22:15:06 +02:00
|
|
|
t++;
|
|
|
|
}
|
|
|
|
|
2013-03-14 18:12:27 +01:00
|
|
|
*n = t - m;
|
2010-04-21 22:15:06 +02:00
|
|
|
}
|
|
|
|
|
2016-12-14 00:48:52 +01:00
|
|
|
static void drop_nop(MountEntry *m, unsigned *n) {
|
|
|
|
MountEntry *f, *t;
|
2016-08-25 11:29:32 +02:00
|
|
|
|
|
|
|
assert(m);
|
|
|
|
assert(n);
|
|
|
|
|
|
|
|
/* Drops all entries which have an immediate parent that has the same type, as they are redundant. Assumes the
|
|
|
|
* list is ordered by prefixes. */
|
|
|
|
|
2016-11-10 18:11:37 +01:00
|
|
|
for (f = m, t = m; f < m + *n; f++) {
|
2016-08-25 11:29:32 +02:00
|
|
|
|
|
|
|
/* Only suppress such subtrees for READONLY and READWRITE entries */
|
|
|
|
if (IN_SET(f->mode, READONLY, READWRITE)) {
|
2016-12-14 00:48:52 +01:00
|
|
|
MountEntry *p;
|
2016-08-25 11:29:32 +02:00
|
|
|
bool found = false;
|
|
|
|
|
|
|
|
/* Now let's find the first parent of the entry we are looking at. */
|
|
|
|
for (p = t-1; p >= m; p--) {
|
2016-12-14 00:48:52 +01:00
|
|
|
if (path_startswith(mount_entry_path(f), mount_entry_path(p))) {
|
2016-08-25 11:29:32 +02:00
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We found it, let's see if it's the same mode, if so, we can drop this entry */
|
|
|
|
if (found && p->mode == f->mode) {
|
2016-12-14 00:48:52 +01:00
|
|
|
log_debug("%s is redundant by %s", mount_entry_path(f), mount_entry_path(p));
|
2016-12-22 17:11:06 +01:00
|
|
|
mount_entry_done(f);
|
2016-08-25 11:29:32 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
*t = *f;
|
|
|
|
t++;
|
|
|
|
}
|
|
|
|
|
|
|
|
*n = t - m;
|
|
|
|
}
|
|
|
|
|
2016-12-14 00:48:52 +01:00
|
|
|
static void drop_outside_root(const char *root_directory, MountEntry *m, unsigned *n) {
|
|
|
|
MountEntry *f, *t;
|
2016-08-26 17:25:40 +02:00
|
|
|
|
|
|
|
assert(m);
|
|
|
|
assert(n);
|
|
|
|
|
2016-11-10 18:11:37 +01:00
|
|
|
/* Nothing to do */
|
2016-08-26 17:25:40 +02:00
|
|
|
if (!root_directory)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Drops all mounts that are outside of the root directory. */
|
|
|
|
|
2016-11-10 18:11:37 +01:00
|
|
|
for (f = m, t = m; f < m + *n; f++) {
|
2016-08-26 17:25:40 +02:00
|
|
|
|
2016-12-14 00:48:52 +01:00
|
|
|
if (!path_startswith(mount_entry_path(f), root_directory)) {
|
|
|
|
log_debug("%s is outside of root directory.", mount_entry_path(f));
|
2016-12-22 17:11:06 +01:00
|
|
|
mount_entry_done(f);
|
2016-08-26 17:25:40 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
*t = *f;
|
|
|
|
t++;
|
|
|
|
}
|
|
|
|
|
|
|
|
*n = t - m;
|
|
|
|
}
|
|
|
|
|
2018-01-16 21:50:36 +01:00
|
|
|
static int clone_device_node(const char *d, const char *temporary_mount) {
|
2018-01-23 19:36:36 +01:00
|
|
|
const char *dn;
|
2018-01-16 21:27:51 +01:00
|
|
|
struct stat st;
|
|
|
|
int r;
|
|
|
|
|
2018-01-16 21:50:36 +01:00
|
|
|
if (stat(d, &st) < 0) {
|
2018-01-16 21:27:51 +01:00
|
|
|
if (errno == ENOENT)
|
|
|
|
return 0;
|
|
|
|
return -errno;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!S_ISBLK(st.st_mode) &&
|
|
|
|
!S_ISCHR(st.st_mode))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (st.st_rdev == 0)
|
|
|
|
return 0;
|
|
|
|
|
2018-01-23 19:36:36 +01:00
|
|
|
dn = strjoina(temporary_mount, d);
|
2018-01-16 21:27:51 +01:00
|
|
|
|
|
|
|
mac_selinux_create_file_prepare(d, st.st_mode);
|
|
|
|
r = mknod(dn, st.st_mode, st.st_rdev);
|
|
|
|
mac_selinux_create_file_clear();
|
|
|
|
if (r < 0)
|
2018-01-18 14:58:13 +01:00
|
|
|
return log_debug_errno(errno, "mknod failed for %s: %m", d);
|
2018-01-16 21:27:51 +01:00
|
|
|
|
core: namespace: nitpick /dev/ptmx error handling
If /dev/tty did not exist, or had st_rdev == 0, we ignored it. And the
same is true for null, zero, full, random, urandom.
If /dev/ptmx did not exist, we treated this as a failure. If /dev/ptmx had
st_rdev == 0, we ignored it.
This was a very recent change, but there was no reason for ptmx creation
specifically to treat st_rdev == 0 differently from non-existence. This
confuses me when reading it.
Change the creation of /dev/ptmx so that st_rdev == 0 is
treated as failure.
This still leaves /dev/ptmx as a special case with stricter handling.
However it is consistent with the immediately preceding creation of
/dev/pts/, which is treated as essential, and is directly related to ptmx.
I don't know why we check st_rdev. But I'd prefer to have only one
unanswered question here, and not to have a second unanswered question
added on top.
2018-01-17 14:28:04 +01:00
|
|
|
return 1;
|
2018-01-16 21:27:51 +01:00
|
|
|
}
|
|
|
|
|
2016-12-22 23:34:35 +01:00
|
|
|
static int mount_private_dev(MountEntry *m) {
|
2014-01-20 19:54:51 +01:00
|
|
|
static const char devnodes[] =
|
|
|
|
"/dev/null\0"
|
|
|
|
"/dev/zero\0"
|
|
|
|
"/dev/full\0"
|
|
|
|
"/dev/random\0"
|
|
|
|
"/dev/urandom\0"
|
|
|
|
"/dev/tty\0";
|
|
|
|
|
2014-03-19 16:23:32 +01:00
|
|
|
char temporary_mount[] = "/tmp/namespace-dev-XXXXXX";
|
sd-bus: sync with kdbus upstream (ABI break)
kdbus has seen a larger update than expected lately, most notably with
kdbusfs, a file system to expose the kdbus control files:
* Each time a file system of this type is mounted, a new kdbus
domain is created.
* The layout inside each mount point is the same as before, except
that domains are not hierarchically nested anymore.
* Domains are therefore also unnamed now.
* Unmounting a kdbusfs will automatically also detroy the
associated domain.
* Hence, the action of creating a kdbus domain is now as
privileged as mounting a filesystem.
* This way, we can get around creating dev nodes for everything,
which is last but not least something that is not limited by
20-bit minor numbers.
The kdbus specific bits in nspawn have all been dropped now, as nspawn
can rely on the container OS to set up its own kdbus domain, simply by
mounting a new instance.
A new set of mounts has been added to mount things *after* the kernel
modules have been loaded. For now, only kdbus is in this set, which is
invoked with mount_setup_late().
2014-11-13 20:33:03 +01:00
|
|
|
const char *d, *dev = NULL, *devpts = NULL, *devshm = NULL, *devhugepages = NULL, *devmqueue = NULL, *devlog = NULL, *devptmx = NULL;
|
2014-01-20 19:54:51 +01:00
|
|
|
_cleanup_umask_ mode_t u;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(m);
|
|
|
|
|
|
|
|
u = umask(0000);
|
|
|
|
|
2014-03-19 16:23:32 +01:00
|
|
|
if (!mkdtemp(temporary_mount))
|
|
|
|
return -errno;
|
|
|
|
|
2015-02-03 02:05:59 +01:00
|
|
|
dev = strjoina(temporary_mount, "/dev");
|
2015-03-14 03:20:01 +01:00
|
|
|
(void) mkdir(dev, 0755);
|
2016-05-16 04:34:05 +02:00
|
|
|
if (mount("tmpfs", dev, "tmpfs", DEV_MOUNT_OPTIONS, "mode=755") < 0) {
|
2014-03-19 16:23:32 +01:00
|
|
|
r = -errno;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2015-02-03 02:05:59 +01:00
|
|
|
devpts = strjoina(temporary_mount, "/dev/pts");
|
2015-03-14 03:20:01 +01:00
|
|
|
(void) mkdir(devpts, 0755);
|
2014-03-19 16:23:32 +01:00
|
|
|
if (mount("/dev/pts", devpts, NULL, MS_BIND, NULL) < 0) {
|
|
|
|
r = -errno;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2018-01-16 21:50:36 +01:00
|
|
|
/* /dev/ptmx can either be a device node or a symlink to /dev/pts/ptmx
|
|
|
|
* when /dev/ptmx a device node, /dev/pts/ptmx has 000 permissions making it inaccessible
|
|
|
|
* thus, in that case make a clone
|
|
|
|
*
|
|
|
|
* in nspawn and other containers it will be a symlink, in that case make it a symlink
|
|
|
|
*/
|
2018-01-23 19:36:55 +01:00
|
|
|
r = is_symlink("/dev/ptmx");
|
|
|
|
if (r < 0)
|
2015-03-14 03:20:47 +01:00
|
|
|
goto fail;
|
2018-01-23 19:36:55 +01:00
|
|
|
if (r > 0) {
|
2018-01-16 21:50:36 +01:00
|
|
|
devptmx = strjoina(temporary_mount, "/dev/ptmx");
|
|
|
|
if (symlink("pts/ptmx", devptmx) < 0) {
|
|
|
|
r = -errno;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
r = clone_device_node("/dev/ptmx", temporary_mount);
|
2018-01-23 19:37:59 +01:00
|
|
|
if (r < 0)
|
|
|
|
goto fail;
|
|
|
|
if (r == 0) {
|
|
|
|
r = -ENXIO;
|
2018-01-16 21:50:36 +01:00
|
|
|
goto fail;
|
2018-01-23 19:37:59 +01:00
|
|
|
}
|
2018-01-16 21:50:36 +01:00
|
|
|
}
|
2014-06-04 17:21:18 +02:00
|
|
|
|
2015-02-03 02:05:59 +01:00
|
|
|
devshm = strjoina(temporary_mount, "/dev/shm");
|
2018-01-17 13:53:26 +01:00
|
|
|
(void) mkdir(devshm, 0755);
|
2014-03-19 16:23:32 +01:00
|
|
|
r = mount("/dev/shm", devshm, NULL, MS_BIND, NULL);
|
|
|
|
if (r < 0) {
|
|
|
|
r = -errno;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2015-02-03 02:05:59 +01:00
|
|
|
devmqueue = strjoina(temporary_mount, "/dev/mqueue");
|
2015-03-14 03:20:01 +01:00
|
|
|
(void) mkdir(devmqueue, 0755);
|
2015-03-14 03:20:47 +01:00
|
|
|
(void) mount("/dev/mqueue", devmqueue, NULL, MS_BIND, NULL);
|
2014-03-19 16:23:32 +01:00
|
|
|
|
2015-02-03 02:05:59 +01:00
|
|
|
devhugepages = strjoina(temporary_mount, "/dev/hugepages");
|
2015-03-14 03:20:01 +01:00
|
|
|
(void) mkdir(devhugepages, 0755);
|
2015-03-14 03:20:47 +01:00
|
|
|
(void) mount("/dev/hugepages", devhugepages, NULL, MS_BIND, NULL);
|
2014-03-19 16:23:32 +01:00
|
|
|
|
2015-02-03 02:05:59 +01:00
|
|
|
devlog = strjoina(temporary_mount, "/dev/log");
|
2015-03-14 03:20:47 +01:00
|
|
|
(void) symlink("/run/systemd/journal/dev-log", devlog);
|
2014-06-04 16:59:13 +02:00
|
|
|
|
2014-01-20 19:54:51 +01:00
|
|
|
NULSTR_FOREACH(d, devnodes) {
|
2018-01-16 21:27:51 +01:00
|
|
|
r = clone_device_node(d, temporary_mount);
|
|
|
|
if (r < 0)
|
2014-03-19 16:23:32 +01:00
|
|
|
goto fail;
|
2014-01-20 19:54:51 +01:00
|
|
|
}
|
|
|
|
|
2015-05-21 16:30:58 +02:00
|
|
|
dev_setup(temporary_mount, UID_INVALID, GID_INVALID);
|
2014-01-20 19:54:51 +01:00
|
|
|
|
2015-05-18 12:20:28 +02:00
|
|
|
/* Create the /dev directory if missing. It is more likely to be
|
|
|
|
* missing when the service is started with RootDirectory. This is
|
|
|
|
* consistent with mount units creating the mount points when missing.
|
|
|
|
*/
|
2016-12-14 00:48:52 +01:00
|
|
|
(void) mkdir_p_label(mount_entry_path(m), 0755);
|
2015-05-18 12:20:28 +02:00
|
|
|
|
2016-05-14 18:46:23 +02:00
|
|
|
/* Unmount everything in old /dev */
|
2016-12-14 00:48:52 +01:00
|
|
|
umount_recursive(mount_entry_path(m), 0);
|
|
|
|
if (mount(dev, mount_entry_path(m), NULL, MS_MOVE, NULL) < 0) {
|
2014-03-19 16:23:32 +01:00
|
|
|
r = -errno;
|
|
|
|
goto fail;
|
|
|
|
}
|
2014-01-20 19:54:51 +01:00
|
|
|
|
2014-03-19 16:23:32 +01:00
|
|
|
rmdir(dev);
|
|
|
|
rmdir(temporary_mount);
|
2014-01-20 19:54:51 +01:00
|
|
|
|
2014-03-19 16:23:32 +01:00
|
|
|
return 0;
|
2014-01-20 19:54:51 +01:00
|
|
|
|
2014-03-19 16:23:32 +01:00
|
|
|
fail:
|
|
|
|
if (devpts)
|
|
|
|
umount(devpts);
|
2014-01-20 19:54:51 +01:00
|
|
|
|
2014-03-19 16:23:32 +01:00
|
|
|
if (devshm)
|
|
|
|
umount(devshm);
|
2014-01-20 19:54:51 +01:00
|
|
|
|
2014-03-19 16:23:32 +01:00
|
|
|
if (devhugepages)
|
|
|
|
umount(devhugepages);
|
2014-01-20 19:54:51 +01:00
|
|
|
|
2014-03-19 16:23:32 +01:00
|
|
|
if (devmqueue)
|
|
|
|
umount(devmqueue);
|
2014-01-20 19:54:51 +01:00
|
|
|
|
2014-10-04 01:17:56 +02:00
|
|
|
umount(dev);
|
|
|
|
rmdir(dev);
|
2014-03-19 16:23:32 +01:00
|
|
|
rmdir(temporary_mount);
|
2014-01-20 19:54:51 +01:00
|
|
|
|
2014-03-19 16:23:32 +01:00
|
|
|
return r;
|
2014-01-20 19:54:51 +01:00
|
|
|
}
|
|
|
|
|
2016-12-22 23:34:35 +01:00
|
|
|
static int mount_bind_dev(MountEntry *m) {
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(m);
|
|
|
|
|
|
|
|
/* Implements the little brother of mount_private_dev(): simply bind mounts the host's /dev into the service's
|
|
|
|
* /dev. This is only used when RootDirectory= is set. */
|
|
|
|
|
2017-09-28 18:28:23 +02:00
|
|
|
(void) mkdir_p_label(mount_entry_path(m), 0755);
|
|
|
|
|
2016-12-22 23:34:35 +01:00
|
|
|
r = path_is_mount_point(mount_entry_path(m), NULL, 0);
|
|
|
|
if (r < 0)
|
|
|
|
return log_debug_errno(r, "Unable to determine whether /dev is already mounted: %m");
|
|
|
|
if (r > 0) /* make this a NOP if /dev is already a mount point */
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (mount("/dev", mount_entry_path(m), NULL, MS_BIND|MS_REC, NULL) < 0)
|
|
|
|
return log_debug_errno(errno, "Failed to bind mount %s: %m", mount_entry_path(m));
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mount_sysfs(MountEntry *m) {
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(m);
|
|
|
|
|
2017-09-28 18:28:23 +02:00
|
|
|
(void) mkdir_p_label(mount_entry_path(m), 0755);
|
|
|
|
|
2016-12-22 23:34:35 +01:00
|
|
|
r = path_is_mount_point(mount_entry_path(m), NULL, 0);
|
|
|
|
if (r < 0)
|
|
|
|
return log_debug_errno(r, "Unable to determine whether /sys is already mounted: %m");
|
|
|
|
if (r > 0) /* make this a NOP if /sys is already a mount point */
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* Bind mount the host's version so that we get all child mounts of it, too. */
|
|
|
|
if (mount("/sys", mount_entry_path(m), NULL, MS_BIND|MS_REC, NULL) < 0)
|
|
|
|
return log_debug_errno(errno, "Failed to mount %s: %m", mount_entry_path(m));
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mount_procfs(MountEntry *m) {
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(m);
|
|
|
|
|
2017-09-28 18:28:23 +02:00
|
|
|
(void) mkdir_p_label(mount_entry_path(m), 0755);
|
|
|
|
|
2016-12-22 23:34:35 +01:00
|
|
|
r = path_is_mount_point(mount_entry_path(m), NULL, 0);
|
|
|
|
if (r < 0)
|
|
|
|
return log_debug_errno(r, "Unable to determine whether /proc is already mounted: %m");
|
|
|
|
if (r > 0) /* make this a NOP if /proc is already a mount point */
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* Mount a new instance, so that we get the one that matches our user namespace, if we are running in one */
|
|
|
|
if (mount("proc", mount_entry_path(m), "proc", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) < 0)
|
|
|
|
return log_debug_errno(errno, "Failed to mount %s: %m", mount_entry_path(m));
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
execute: make StateDirectory= and friends compatible with DynamicUser=1 and RootDirectory=/RootImage=
Let's clean up the interaction of StateDirectory= (and friends) to
DynamicUser=1: instead of creating these directories directly below
/var/lib, place them in /var/lib/private instead if DynamicUser=1 is
set, making that directory 0700 and owned by root:root. This way, if a
dynamic UID is later reused, access to the old run's state directory is
prohibited for that user. Then, use file system namespacing inside the
service to make /var/lib/private a readable tmpfs, hiding all state
directories that are not listed in StateDirectory=, and making access to
the actual state directory possible. Mount all directories listed in
StateDirectory= to the same places inside the service (which means
they'll now be mounted into the tmpfs instance). Finally, add a symlink
from the state directory name in /var/lib/ to the one in
/var/lib/private, so that both the host and the service can access the
path under the same location.
Here's an example: let's say a service runs with StateDirectory=foo.
When DynamicUser=0 is set, it will get the following setup, and no
difference between what the unit and what the host sees:
/var/lib/foo (created as directory)
Now, if DynamicUser=1 is set, we'll instead get this on the host:
/var/lib/private (created as directory with mode 0700, root:root)
/var/lib/private/foo (created as directory)
/var/lib/foo → private/foo (created as symlink)
And from inside the unit:
/var/lib/private (a tmpfs mount with mode 0755, root:root)
/var/lib/private/foo (bind mounted from the host)
/var/lib/foo → private/foo (the same symlink as above)
This takes inspiration from how container trees are protected below
/var/lib/machines: they generally reuse UIDs/GIDs of the host, but
because /var/lib/machines itself is set to 0700 host users cannot access
files in the container tree even if the UIDs/GIDs are reused. However,
for this commit we add one further trick: inside and outside of the unit
/var/lib/private is a different thing: outside it is a plain,
inaccessible directory, and inside it is a world-readable tmpfs mount
with only the whitelisted subdirs below it, bind mounte din. This
means, from the outside the dir acts as an access barrier, but from the
inside it does not. And the symlink created in /var/lib/foo itself
points across the barrier in both cases, so that root and the unit's
user always have access to these dirs without knowing the details of
this mounting magic.
This logic resolves a major shortcoming of DynamicUser=1 units:
previously they couldn't safely store persistant data. With this change
they can have their own private state, log and data directories, which
they can write to, but which are protected from UID recycling.
With this change, if RootDirectory= or RootImage= are used it is ensured
that the specified state/log/cache directories are always mounted in
from the host. This change of semantics I think is much preferable since
this means the root directory/image logic can be used easily for
read-only resource bundling (as all writable data resides outside of the
image). Note that this is a change of behaviour, but given that we
haven't released any systemd version with StateDirectory= and friends
implemented this should be a safe change to make (in particular as
previously it wasn't clear what would actually happen when used in
combination). Moreover, by making this change we can later add a "+"
modifier to these setings too working similar to the same modifier in
ReadOnlyPaths= and friends, making specified paths relative to the
container itself.
2017-09-28 18:55:45 +02:00
|
|
|
static int mount_empty_dir(MountEntry *m) {
|
|
|
|
assert(m);
|
|
|
|
|
|
|
|
/* First, get rid of everything that is below if there is anything. Then, overmount with our new empty dir */
|
|
|
|
|
|
|
|
(void) mkdir_p_label(mount_entry_path(m), 0755);
|
|
|
|
(void) umount_recursive(mount_entry_path(m), 0);
|
|
|
|
|
|
|
|
if (mount("tmpfs", mount_entry_path(m), "tmpfs", MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, "mode=755") < 0)
|
|
|
|
return log_debug_errno(errno, "Failed to mount %s: %m", mount_entry_path(m));
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2016-11-23 22:21:40 +01:00
|
|
|
static int mount_entry_chase(
|
|
|
|
const char *root_directory,
|
|
|
|
MountEntry *m,
|
|
|
|
const char *path,
|
|
|
|
char **location) {
|
|
|
|
|
2016-12-14 00:51:37 +01:00
|
|
|
char *chased;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(m);
|
|
|
|
|
|
|
|
/* Since mount() will always follow symlinks and we need to take the different root directory into account we
|
2016-11-23 22:21:40 +01:00
|
|
|
* chase the symlinks on our own first. This is called for the destination path, as well as the source path (if
|
|
|
|
* that applies). The result is stored in "location". */
|
2016-12-14 00:51:37 +01:00
|
|
|
|
2017-09-28 18:35:51 +02:00
|
|
|
r = chase_symlinks(path, root_directory,
|
|
|
|
IN_SET(m->mode, BIND_MOUNT, BIND_MOUNT_RECURSIVE, PRIVATE_TMP, PRIVATE_VAR_TMP, PRIVATE_DEV, BIND_DEV, EMPTY_DIR, SYSFS, PROCFS) ? CHASE_NONEXISTENT : 0,
|
|
|
|
&chased);
|
2016-12-14 00:51:37 +01:00
|
|
|
if (r == -ENOENT && m->ignore) {
|
2016-11-23 22:21:40 +01:00
|
|
|
log_debug_errno(r, "Path %s does not exist, ignoring.", path);
|
2016-12-14 00:51:37 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (r < 0)
|
2016-11-23 22:21:40 +01:00
|
|
|
return log_debug_errno(r, "Failed to follow symlinks on %s: %m", path);
|
2016-12-14 00:51:37 +01:00
|
|
|
|
2016-11-23 22:21:40 +01:00
|
|
|
log_debug("Followed symlinks %s → %s.", path, chased);
|
2016-12-14 00:51:37 +01:00
|
|
|
|
2016-11-23 22:21:40 +01:00
|
|
|
free(*location);
|
|
|
|
*location = chased;
|
2016-12-14 00:51:37 +01:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-08-13 15:27:04 +02:00
|
|
|
static int apply_mount(
|
2016-12-14 00:51:37 +01:00
|
|
|
const char *root_directory,
|
2016-12-14 00:48:52 +01:00
|
|
|
MountEntry *m,
|
2012-08-13 15:27:04 +02:00
|
|
|
const char *tmp_dir,
|
2013-03-14 18:12:27 +01:00
|
|
|
const char *var_tmp_dir) {
|
2012-08-13 15:27:04 +02:00
|
|
|
|
2017-09-28 18:35:51 +02:00
|
|
|
bool rbind = true, make = false;
|
2010-04-21 22:15:06 +02:00
|
|
|
const char *what;
|
|
|
|
int r;
|
|
|
|
|
2013-03-14 18:12:27 +01:00
|
|
|
assert(m);
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2016-11-23 22:21:40 +01:00
|
|
|
r = mount_entry_chase(root_directory, m, mount_entry_path(m), &m->path_malloc);
|
2016-12-14 00:51:37 +01:00
|
|
|
if (r <= 0)
|
|
|
|
return r;
|
|
|
|
|
2016-12-14 00:48:52 +01:00
|
|
|
log_debug("Applying namespace mount on %s", mount_entry_path(m));
|
2016-08-24 23:17:42 +02:00
|
|
|
|
2013-03-14 18:12:27 +01:00
|
|
|
switch (m->mode) {
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2016-08-25 15:51:37 +02:00
|
|
|
case INACCESSIBLE: {
|
|
|
|
struct stat target;
|
2014-06-05 21:35:35 +02:00
|
|
|
|
|
|
|
/* First, get rid of everything that is below if there
|
|
|
|
* is anything... Then, overmount it with an
|
2016-07-06 09:48:58 +02:00
|
|
|
* inaccessible path. */
|
2016-12-14 00:48:52 +01:00
|
|
|
(void) umount_recursive(mount_entry_path(m), 0);
|
2014-06-05 21:35:35 +02:00
|
|
|
|
2016-12-14 00:48:52 +01:00
|
|
|
if (lstat(mount_entry_path(m), &target) < 0)
|
|
|
|
return log_debug_errno(errno, "Failed to lstat() %s to determine what to mount over it: %m", mount_entry_path(m));
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2016-07-06 09:48:58 +02:00
|
|
|
what = mode_to_inaccessible_node(target.st_mode);
|
2016-07-20 08:57:25 +02:00
|
|
|
if (!what) {
|
|
|
|
log_debug("File type not supported for inaccessible mounts. Note that symlinks are not allowed");
|
2016-07-06 09:48:58 +02:00
|
|
|
return -ELOOP;
|
|
|
|
}
|
|
|
|
break;
|
2016-08-25 15:51:37 +02:00
|
|
|
}
|
2016-08-24 23:17:42 +02:00
|
|
|
|
2010-04-21 22:15:06 +02:00
|
|
|
case READONLY:
|
|
|
|
case READWRITE:
|
2016-12-14 00:51:37 +01:00
|
|
|
r = path_is_mount_point(mount_entry_path(m), root_directory, 0);
|
2016-09-24 12:41:30 +02:00
|
|
|
if (r < 0)
|
2016-12-14 00:48:52 +01:00
|
|
|
return log_debug_errno(r, "Failed to determine whether %s is already a mount point: %m", mount_entry_path(m));
|
namespace: rework how ReadWritePaths= is applied
Previously, if ReadWritePaths= was nested inside a ReadOnlyPaths=
specification, then we'd first recursively apply the ReadOnlyPaths= paths, and
make everything below read-only, only in order to then flip the read-only bit
again for the subdirs listed in ReadWritePaths= below it.
This is not only ugly (as for the dirs in question we first turn on the RO bit,
only to turn it off again immediately after), but also problematic in
containers, where a container manager might have marked a set of dirs read-only
and this code will undo this is ReadWritePaths= is set for any.
With this patch behaviour in this regard is altered: ReadOnlyPaths= will not be
applied to the children listed in ReadWritePaths= in the first place, so that
we do not need to turn off the RO bit for those after all.
This means that ReadWritePaths=/ReadOnlyPaths= may only be used to turn on the
RO bit, but never to turn it off again. Or to say this differently: if some
dirs are marked read-only via some external tool, then ReadWritePaths= will not
undo it.
This is not only the safer option, but also more in-line with what the man page
currently claims:
"Entries (files or directories) listed in ReadWritePaths= are
accessible from within the namespace with the same access rights as
from outside."
To implement this change bind_remount_recursive() gained a new "blacklist"
string list parameter, which when passed may contain subdirs that shall be
excluded from the read-only mounting.
A number of functions are updated to add more debug logging to make this more
digestable.
2016-09-25 10:40:51 +02:00
|
|
|
if (r > 0) /* Nothing to do here, it is already a mount. We just later toggle the MS_RDONLY bit for the mount point if needed. */
|
|
|
|
return 0;
|
|
|
|
/* This isn't a mount point yet, let's make it one. */
|
2016-12-14 00:48:52 +01:00
|
|
|
what = mount_entry_path(m);
|
namespace: rework how ReadWritePaths= is applied
Previously, if ReadWritePaths= was nested inside a ReadOnlyPaths=
specification, then we'd first recursively apply the ReadOnlyPaths= paths, and
make everything below read-only, only in order to then flip the read-only bit
again for the subdirs listed in ReadWritePaths= below it.
This is not only ugly (as for the dirs in question we first turn on the RO bit,
only to turn it off again immediately after), but also problematic in
containers, where a container manager might have marked a set of dirs read-only
and this code will undo this is ReadWritePaths= is set for any.
With this patch behaviour in this regard is altered: ReadOnlyPaths= will not be
applied to the children listed in ReadWritePaths= in the first place, so that
we do not need to turn off the RO bit for those after all.
This means that ReadWritePaths=/ReadOnlyPaths= may only be used to turn on the
RO bit, but never to turn it off again. Or to say this differently: if some
dirs are marked read-only via some external tool, then ReadWritePaths= will not
undo it.
This is not only the safer option, but also more in-line with what the man page
currently claims:
"Entries (files or directories) listed in ReadWritePaths= are
accessible from within the namespace with the same access rights as
from outside."
To implement this change bind_remount_recursive() gained a new "blacklist"
string list parameter, which when passed may contain subdirs that shall be
excluded from the read-only mounting.
A number of functions are updated to add more debug logging to make this more
digestable.
2016-09-25 10:40:51 +02:00
|
|
|
break;
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2016-11-23 22:21:40 +01:00
|
|
|
case BIND_MOUNT:
|
|
|
|
rbind = false;
|
|
|
|
|
2017-11-19 19:06:10 +01:00
|
|
|
_fallthrough_;
|
2016-11-23 22:21:40 +01:00
|
|
|
case BIND_MOUNT_RECURSIVE:
|
|
|
|
/* Also chase the source mount */
|
2016-12-22 23:34:35 +01:00
|
|
|
|
2016-11-23 22:21:40 +01:00
|
|
|
r = mount_entry_chase(root_directory, m, mount_entry_source(m), &m->source_malloc);
|
|
|
|
if (r <= 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
what = mount_entry_source(m);
|
2017-09-28 18:35:51 +02:00
|
|
|
make = true;
|
2016-11-23 22:21:40 +01:00
|
|
|
break;
|
|
|
|
|
execute: make StateDirectory= and friends compatible with DynamicUser=1 and RootDirectory=/RootImage=
Let's clean up the interaction of StateDirectory= (and friends) to
DynamicUser=1: instead of creating these directories directly below
/var/lib, place them in /var/lib/private instead if DynamicUser=1 is
set, making that directory 0700 and owned by root:root. This way, if a
dynamic UID is later reused, access to the old run's state directory is
prohibited for that user. Then, use file system namespacing inside the
service to make /var/lib/private a readable tmpfs, hiding all state
directories that are not listed in StateDirectory=, and making access to
the actual state directory possible. Mount all directories listed in
StateDirectory= to the same places inside the service (which means
they'll now be mounted into the tmpfs instance). Finally, add a symlink
from the state directory name in /var/lib/ to the one in
/var/lib/private, so that both the host and the service can access the
path under the same location.
Here's an example: let's say a service runs with StateDirectory=foo.
When DynamicUser=0 is set, it will get the following setup, and no
difference between what the unit and what the host sees:
/var/lib/foo (created as directory)
Now, if DynamicUser=1 is set, we'll instead get this on the host:
/var/lib/private (created as directory with mode 0700, root:root)
/var/lib/private/foo (created as directory)
/var/lib/foo → private/foo (created as symlink)
And from inside the unit:
/var/lib/private (a tmpfs mount with mode 0755, root:root)
/var/lib/private/foo (bind mounted from the host)
/var/lib/foo → private/foo (the same symlink as above)
This takes inspiration from how container trees are protected below
/var/lib/machines: they generally reuse UIDs/GIDs of the host, but
because /var/lib/machines itself is set to 0700 host users cannot access
files in the container tree even if the UIDs/GIDs are reused. However,
for this commit we add one further trick: inside and outside of the unit
/var/lib/private is a different thing: outside it is a plain,
inaccessible directory, and inside it is a world-readable tmpfs mount
with only the whitelisted subdirs below it, bind mounte din. This
means, from the outside the dir acts as an access barrier, but from the
inside it does not. And the symlink created in /var/lib/foo itself
points across the barrier in both cases, so that root and the unit's
user always have access to these dirs without knowing the details of
this mounting magic.
This logic resolves a major shortcoming of DynamicUser=1 units:
previously they couldn't safely store persistant data. With this change
they can have their own private state, log and data directories, which
they can write to, but which are protected from UID recycling.
With this change, if RootDirectory= or RootImage= are used it is ensured
that the specified state/log/cache directories are always mounted in
from the host. This change of semantics I think is much preferable since
this means the root directory/image logic can be used easily for
read-only resource bundling (as all writable data resides outside of the
image). Note that this is a change of behaviour, but given that we
haven't released any systemd version with StateDirectory= and friends
implemented this should be a safe change to make (in particular as
previously it wasn't clear what would actually happen when used in
combination). Moreover, by making this change we can later add a "+"
modifier to these setings too working similar to the same modifier in
ReadOnlyPaths= and friends, making specified paths relative to the
container itself.
2017-09-28 18:55:45 +02:00
|
|
|
case EMPTY_DIR:
|
|
|
|
return mount_empty_dir(m);
|
|
|
|
|
2012-08-13 15:27:04 +02:00
|
|
|
case PRIVATE_TMP:
|
|
|
|
what = tmp_dir;
|
2017-09-28 18:35:51 +02:00
|
|
|
make = true;
|
2012-08-13 15:27:04 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case PRIVATE_VAR_TMP:
|
|
|
|
what = var_tmp_dir;
|
2017-09-28 18:35:51 +02:00
|
|
|
make = true;
|
2010-04-21 22:15:06 +02:00
|
|
|
break;
|
2010-08-11 22:04:22 +02:00
|
|
|
|
2014-06-06 11:42:25 +02:00
|
|
|
case PRIVATE_DEV:
|
2016-12-22 23:34:35 +01:00
|
|
|
return mount_private_dev(m);
|
|
|
|
|
|
|
|
case BIND_DEV:
|
|
|
|
return mount_bind_dev(m);
|
|
|
|
|
|
|
|
case SYSFS:
|
|
|
|
return mount_sysfs(m);
|
|
|
|
|
|
|
|
case PROCFS:
|
|
|
|
return mount_procfs(m);
|
2014-06-06 11:42:25 +02:00
|
|
|
|
2010-08-11 22:04:22 +02:00
|
|
|
default:
|
|
|
|
assert_not_reached("Unknown mode");
|
2010-04-21 22:15:06 +02:00
|
|
|
}
|
|
|
|
|
2012-08-13 15:27:04 +02:00
|
|
|
assert(what);
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2017-09-28 18:35:51 +02:00
|
|
|
if (mount(what, mount_entry_path(m), NULL, MS_BIND|(rbind ? MS_REC : 0), NULL) < 0) {
|
|
|
|
bool try_again = false;
|
|
|
|
r = -errno;
|
|
|
|
|
|
|
|
if (r == -ENOENT && make) {
|
|
|
|
struct stat st;
|
|
|
|
|
|
|
|
/* Hmm, either the source or the destination are missing. Let's see if we can create the destination, then try again */
|
|
|
|
|
|
|
|
if (stat(what, &st) >= 0) {
|
|
|
|
|
|
|
|
(void) mkdir_parents(mount_entry_path(m), 0755);
|
|
|
|
|
|
|
|
if (S_ISDIR(st.st_mode))
|
|
|
|
try_again = mkdir(mount_entry_path(m), 0755) >= 0;
|
|
|
|
else
|
|
|
|
try_again = touch(mount_entry_path(m)) >= 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (try_again) {
|
|
|
|
if (mount(what, mount_entry_path(m), NULL, MS_BIND|(rbind ? MS_REC : 0), NULL) < 0)
|
|
|
|
r = -errno;
|
|
|
|
else
|
|
|
|
r = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (r < 0)
|
|
|
|
return log_debug_errno(r, "Failed to mount %s to %s: %m", what, mount_entry_path(m));
|
|
|
|
}
|
namespace: rework how ReadWritePaths= is applied
Previously, if ReadWritePaths= was nested inside a ReadOnlyPaths=
specification, then we'd first recursively apply the ReadOnlyPaths= paths, and
make everything below read-only, only in order to then flip the read-only bit
again for the subdirs listed in ReadWritePaths= below it.
This is not only ugly (as for the dirs in question we first turn on the RO bit,
only to turn it off again immediately after), but also problematic in
containers, where a container manager might have marked a set of dirs read-only
and this code will undo this is ReadWritePaths= is set for any.
With this patch behaviour in this regard is altered: ReadOnlyPaths= will not be
applied to the children listed in ReadWritePaths= in the first place, so that
we do not need to turn off the RO bit for those after all.
This means that ReadWritePaths=/ReadOnlyPaths= may only be used to turn on the
RO bit, but never to turn it off again. Or to say this differently: if some
dirs are marked read-only via some external tool, then ReadWritePaths= will not
undo it.
This is not only the safer option, but also more in-line with what the man page
currently claims:
"Entries (files or directories) listed in ReadWritePaths= are
accessible from within the namespace with the same access rights as
from outside."
To implement this change bind_remount_recursive() gained a new "blacklist"
string list parameter, which when passed may contain subdirs that shall be
excluded from the read-only mounting.
A number of functions are updated to add more debug logging to make this more
digestable.
2016-09-25 10:40:51 +02:00
|
|
|
|
2016-12-14 00:48:52 +01:00
|
|
|
log_debug("Successfully mounted %s to %s", what, mount_entry_path(m));
|
namespace: rework how ReadWritePaths= is applied
Previously, if ReadWritePaths= was nested inside a ReadOnlyPaths=
specification, then we'd first recursively apply the ReadOnlyPaths= paths, and
make everything below read-only, only in order to then flip the read-only bit
again for the subdirs listed in ReadWritePaths= below it.
This is not only ugly (as for the dirs in question we first turn on the RO bit,
only to turn it off again immediately after), but also problematic in
containers, where a container manager might have marked a set of dirs read-only
and this code will undo this is ReadWritePaths= is set for any.
With this patch behaviour in this regard is altered: ReadOnlyPaths= will not be
applied to the children listed in ReadWritePaths= in the first place, so that
we do not need to turn off the RO bit for those after all.
This means that ReadWritePaths=/ReadOnlyPaths= may only be used to turn on the
RO bit, but never to turn it off again. Or to say this differently: if some
dirs are marked read-only via some external tool, then ReadWritePaths= will not
undo it.
This is not only the safer option, but also more in-line with what the man page
currently claims:
"Entries (files or directories) listed in ReadWritePaths= are
accessible from within the namespace with the same access rights as
from outside."
To implement this change bind_remount_recursive() gained a new "blacklist"
string list parameter, which when passed may contain subdirs that shall be
excluded from the read-only mounting.
A number of functions are updated to add more debug logging to make this more
digestable.
2016-09-25 10:40:51 +02:00
|
|
|
return 0;
|
2012-08-13 15:27:04 +02:00
|
|
|
}
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2017-05-19 14:38:40 +02:00
|
|
|
static int make_read_only(MountEntry *m, char **blacklist, FILE *proc_self_mountinfo) {
|
namespace: rework how ReadWritePaths= is applied
Previously, if ReadWritePaths= was nested inside a ReadOnlyPaths=
specification, then we'd first recursively apply the ReadOnlyPaths= paths, and
make everything below read-only, only in order to then flip the read-only bit
again for the subdirs listed in ReadWritePaths= below it.
This is not only ugly (as for the dirs in question we first turn on the RO bit,
only to turn it off again immediately after), but also problematic in
containers, where a container manager might have marked a set of dirs read-only
and this code will undo this is ReadWritePaths= is set for any.
With this patch behaviour in this regard is altered: ReadOnlyPaths= will not be
applied to the children listed in ReadWritePaths= in the first place, so that
we do not need to turn off the RO bit for those after all.
This means that ReadWritePaths=/ReadOnlyPaths= may only be used to turn on the
RO bit, but never to turn it off again. Or to say this differently: if some
dirs are marked read-only via some external tool, then ReadWritePaths= will not
undo it.
This is not only the safer option, but also more in-line with what the man page
currently claims:
"Entries (files or directories) listed in ReadWritePaths= are
accessible from within the namespace with the same access rights as
from outside."
To implement this change bind_remount_recursive() gained a new "blacklist"
string list parameter, which when passed may contain subdirs that shall be
excluded from the read-only mounting.
A number of functions are updated to add more debug logging to make this more
digestable.
2016-09-25 10:40:51 +02:00
|
|
|
int r = 0;
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2013-03-14 18:12:27 +01:00
|
|
|
assert(m);
|
2017-05-19 14:38:40 +02:00
|
|
|
assert(proc_self_mountinfo);
|
2012-08-13 15:27:04 +02:00
|
|
|
|
2016-12-14 00:48:52 +01:00
|
|
|
if (mount_entry_read_only(m))
|
2017-05-19 14:38:40 +02:00
|
|
|
r = bind_remount_recursive_with_mountinfo(mount_entry_path(m), true, blacklist, proc_self_mountinfo);
|
2017-02-24 18:14:02 +01:00
|
|
|
else if (m->mode == PRIVATE_DEV) { /* Superblock can be readonly but the submounts can't */
|
2016-12-14 00:48:52 +01:00
|
|
|
if (mount(NULL, mount_entry_path(m), NULL, MS_REMOUNT|DEV_MOUNT_OPTIONS|MS_RDONLY, NULL) < 0)
|
namespace: rework how ReadWritePaths= is applied
Previously, if ReadWritePaths= was nested inside a ReadOnlyPaths=
specification, then we'd first recursively apply the ReadOnlyPaths= paths, and
make everything below read-only, only in order to then flip the read-only bit
again for the subdirs listed in ReadWritePaths= below it.
This is not only ugly (as for the dirs in question we first turn on the RO bit,
only to turn it off again immediately after), but also problematic in
containers, where a container manager might have marked a set of dirs read-only
and this code will undo this is ReadWritePaths= is set for any.
With this patch behaviour in this regard is altered: ReadOnlyPaths= will not be
applied to the children listed in ReadWritePaths= in the first place, so that
we do not need to turn off the RO bit for those after all.
This means that ReadWritePaths=/ReadOnlyPaths= may only be used to turn on the
RO bit, but never to turn it off again. Or to say this differently: if some
dirs are marked read-only via some external tool, then ReadWritePaths= will not
undo it.
This is not only the safer option, but also more in-line with what the man page
currently claims:
"Entries (files or directories) listed in ReadWritePaths= are
accessible from within the namespace with the same access rights as
from outside."
To implement this change bind_remount_recursive() gained a new "blacklist"
string list parameter, which when passed may contain subdirs that shall be
excluded from the read-only mounting.
A number of functions are updated to add more debug logging to make this more
digestable.
2016-09-25 10:40:51 +02:00
|
|
|
r = -errno;
|
2016-05-16 04:34:05 +02:00
|
|
|
} else
|
namespace: rework how ReadWritePaths= is applied
Previously, if ReadWritePaths= was nested inside a ReadOnlyPaths=
specification, then we'd first recursively apply the ReadOnlyPaths= paths, and
make everything below read-only, only in order to then flip the read-only bit
again for the subdirs listed in ReadWritePaths= below it.
This is not only ugly (as for the dirs in question we first turn on the RO bit,
only to turn it off again immediately after), but also problematic in
containers, where a container manager might have marked a set of dirs read-only
and this code will undo this is ReadWritePaths= is set for any.
With this patch behaviour in this regard is altered: ReadOnlyPaths= will not be
applied to the children listed in ReadWritePaths= in the first place, so that
we do not need to turn off the RO bit for those after all.
This means that ReadWritePaths=/ReadOnlyPaths= may only be used to turn on the
RO bit, but never to turn it off again. Or to say this differently: if some
dirs are marked read-only via some external tool, then ReadWritePaths= will not
undo it.
This is not only the safer option, but also more in-line with what the man page
currently claims:
"Entries (files or directories) listed in ReadWritePaths= are
accessible from within the namespace with the same access rights as
from outside."
To implement this change bind_remount_recursive() gained a new "blacklist"
string list parameter, which when passed may contain subdirs that shall be
excluded from the read-only mounting.
A number of functions are updated to add more debug logging to make this more
digestable.
2016-09-25 10:40:51 +02:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* Not that we only turn on the MS_RDONLY flag here, we never turn it off. Something that was marked read-only
|
|
|
|
* already stays this way. This improves compatibility with container managers, where we won't attempt to undo
|
|
|
|
* read-only mounts already applied. */
|
2012-08-13 15:27:04 +02:00
|
|
|
|
2016-12-14 00:51:37 +01:00
|
|
|
if (r == -ENOENT && m->ignore)
|
|
|
|
r = 0;
|
2016-11-15 01:42:54 +01:00
|
|
|
|
2016-11-10 18:11:37 +01:00
|
|
|
return r;
|
2016-09-24 12:41:30 +02:00
|
|
|
}
|
|
|
|
|
2017-10-10 09:49:20 +02:00
|
|
|
static bool namespace_info_mount_apivfs(const char *root_directory, const NamespaceInfo *ns_info) {
|
2016-12-22 23:34:35 +01:00
|
|
|
assert(ns_info);
|
|
|
|
|
2017-03-05 21:39:43 +01:00
|
|
|
/*
|
|
|
|
* ProtectControlGroups= and ProtectKernelTunables= imply MountAPIVFS=,
|
|
|
|
* since to protect the API VFS mounts, they need to be around in the
|
|
|
|
* first place... and RootDirectory= or RootImage= need to be set.
|
|
|
|
*/
|
2016-12-22 23:34:35 +01:00
|
|
|
|
2017-03-05 21:39:43 +01:00
|
|
|
/* root_directory should point to a mount point */
|
|
|
|
return root_directory &&
|
|
|
|
(ns_info->mount_apivfs ||
|
|
|
|
ns_info->protect_control_groups ||
|
|
|
|
ns_info->protect_kernel_tunables);
|
2016-12-22 23:34:35 +01:00
|
|
|
}
|
|
|
|
|
2016-09-25 11:25:00 +02:00
|
|
|
static unsigned namespace_calculate_mounts(
|
2017-03-05 21:39:43 +01:00
|
|
|
const char* root_directory,
|
2017-10-10 09:49:20 +02:00
|
|
|
const NamespaceInfo *ns_info,
|
2016-09-25 11:25:00 +02:00
|
|
|
char** read_write_paths,
|
|
|
|
char** read_only_paths,
|
|
|
|
char** inaccessible_paths,
|
execute: make StateDirectory= and friends compatible with DynamicUser=1 and RootDirectory=/RootImage=
Let's clean up the interaction of StateDirectory= (and friends) to
DynamicUser=1: instead of creating these directories directly below
/var/lib, place them in /var/lib/private instead if DynamicUser=1 is
set, making that directory 0700 and owned by root:root. This way, if a
dynamic UID is later reused, access to the old run's state directory is
prohibited for that user. Then, use file system namespacing inside the
service to make /var/lib/private a readable tmpfs, hiding all state
directories that are not listed in StateDirectory=, and making access to
the actual state directory possible. Mount all directories listed in
StateDirectory= to the same places inside the service (which means
they'll now be mounted into the tmpfs instance). Finally, add a symlink
from the state directory name in /var/lib/ to the one in
/var/lib/private, so that both the host and the service can access the
path under the same location.
Here's an example: let's say a service runs with StateDirectory=foo.
When DynamicUser=0 is set, it will get the following setup, and no
difference between what the unit and what the host sees:
/var/lib/foo (created as directory)
Now, if DynamicUser=1 is set, we'll instead get this on the host:
/var/lib/private (created as directory with mode 0700, root:root)
/var/lib/private/foo (created as directory)
/var/lib/foo → private/foo (created as symlink)
And from inside the unit:
/var/lib/private (a tmpfs mount with mode 0755, root:root)
/var/lib/private/foo (bind mounted from the host)
/var/lib/foo → private/foo (the same symlink as above)
This takes inspiration from how container trees are protected below
/var/lib/machines: they generally reuse UIDs/GIDs of the host, but
because /var/lib/machines itself is set to 0700 host users cannot access
files in the container tree even if the UIDs/GIDs are reused. However,
for this commit we add one further trick: inside and outside of the unit
/var/lib/private is a different thing: outside it is a plain,
inaccessible directory, and inside it is a world-readable tmpfs mount
with only the whitelisted subdirs below it, bind mounte din. This
means, from the outside the dir acts as an access barrier, but from the
inside it does not. And the symlink created in /var/lib/foo itself
points across the barrier in both cases, so that root and the unit's
user always have access to these dirs without knowing the details of
this mounting magic.
This logic resolves a major shortcoming of DynamicUser=1 units:
previously they couldn't safely store persistant data. With this change
they can have their own private state, log and data directories, which
they can write to, but which are protected from UID recycling.
With this change, if RootDirectory= or RootImage= are used it is ensured
that the specified state/log/cache directories are always mounted in
from the host. This change of semantics I think is much preferable since
this means the root directory/image logic can be used easily for
read-only resource bundling (as all writable data resides outside of the
image). Note that this is a change of behaviour, but given that we
haven't released any systemd version with StateDirectory= and friends
implemented this should be a safe change to make (in particular as
previously it wasn't clear what would actually happen when used in
combination). Moreover, by making this change we can later add a "+"
modifier to these setings too working similar to the same modifier in
ReadOnlyPaths= and friends, making specified paths relative to the
container itself.
2017-09-28 18:55:45 +02:00
|
|
|
char** empty_directories,
|
2016-11-23 22:21:40 +01:00
|
|
|
const BindMount *bind_mounts,
|
|
|
|
unsigned n_bind_mounts,
|
2016-09-25 11:25:00 +02:00
|
|
|
const char* tmp_dir,
|
|
|
|
const char* var_tmp_dir,
|
|
|
|
ProtectHome protect_home,
|
|
|
|
ProtectSystem protect_system) {
|
|
|
|
|
2016-09-25 12:41:16 +02:00
|
|
|
unsigned protect_home_cnt;
|
2016-09-25 12:21:25 +02:00
|
|
|
unsigned protect_system_cnt =
|
|
|
|
(protect_system == PROTECT_SYSTEM_STRICT ?
|
|
|
|
ELEMENTSOF(protect_system_strict_table) :
|
|
|
|
((protect_system == PROTECT_SYSTEM_FULL) ?
|
|
|
|
ELEMENTSOF(protect_system_full_table) :
|
|
|
|
((protect_system == PROTECT_SYSTEM_YES) ?
|
|
|
|
ELEMENTSOF(protect_system_yes_table) : 0)));
|
|
|
|
|
2016-09-25 12:41:16 +02:00
|
|
|
protect_home_cnt =
|
|
|
|
(protect_home == PROTECT_HOME_YES ?
|
|
|
|
ELEMENTSOF(protect_home_yes_table) :
|
|
|
|
((protect_home == PROTECT_HOME_READ_ONLY) ?
|
|
|
|
ELEMENTSOF(protect_home_read_only_table) : 0));
|
|
|
|
|
2016-09-25 11:25:00 +02:00
|
|
|
return !!tmp_dir + !!var_tmp_dir +
|
|
|
|
strv_length(read_write_paths) +
|
|
|
|
strv_length(read_only_paths) +
|
|
|
|
strv_length(inaccessible_paths) +
|
execute: make StateDirectory= and friends compatible with DynamicUser=1 and RootDirectory=/RootImage=
Let's clean up the interaction of StateDirectory= (and friends) to
DynamicUser=1: instead of creating these directories directly below
/var/lib, place them in /var/lib/private instead if DynamicUser=1 is
set, making that directory 0700 and owned by root:root. This way, if a
dynamic UID is later reused, access to the old run's state directory is
prohibited for that user. Then, use file system namespacing inside the
service to make /var/lib/private a readable tmpfs, hiding all state
directories that are not listed in StateDirectory=, and making access to
the actual state directory possible. Mount all directories listed in
StateDirectory= to the same places inside the service (which means
they'll now be mounted into the tmpfs instance). Finally, add a symlink
from the state directory name in /var/lib/ to the one in
/var/lib/private, so that both the host and the service can access the
path under the same location.
Here's an example: let's say a service runs with StateDirectory=foo.
When DynamicUser=0 is set, it will get the following setup, and no
difference between what the unit and what the host sees:
/var/lib/foo (created as directory)
Now, if DynamicUser=1 is set, we'll instead get this on the host:
/var/lib/private (created as directory with mode 0700, root:root)
/var/lib/private/foo (created as directory)
/var/lib/foo → private/foo (created as symlink)
And from inside the unit:
/var/lib/private (a tmpfs mount with mode 0755, root:root)
/var/lib/private/foo (bind mounted from the host)
/var/lib/foo → private/foo (the same symlink as above)
This takes inspiration from how container trees are protected below
/var/lib/machines: they generally reuse UIDs/GIDs of the host, but
because /var/lib/machines itself is set to 0700 host users cannot access
files in the container tree even if the UIDs/GIDs are reused. However,
for this commit we add one further trick: inside and outside of the unit
/var/lib/private is a different thing: outside it is a plain,
inaccessible directory, and inside it is a world-readable tmpfs mount
with only the whitelisted subdirs below it, bind mounte din. This
means, from the outside the dir acts as an access barrier, but from the
inside it does not. And the symlink created in /var/lib/foo itself
points across the barrier in both cases, so that root and the unit's
user always have access to these dirs without knowing the details of
this mounting magic.
This logic resolves a major shortcoming of DynamicUser=1 units:
previously they couldn't safely store persistant data. With this change
they can have their own private state, log and data directories, which
they can write to, but which are protected from UID recycling.
With this change, if RootDirectory= or RootImage= are used it is ensured
that the specified state/log/cache directories are always mounted in
from the host. This change of semantics I think is much preferable since
this means the root directory/image logic can be used easily for
read-only resource bundling (as all writable data resides outside of the
image). Note that this is a change of behaviour, but given that we
haven't released any systemd version with StateDirectory= and friends
implemented this should be a safe change to make (in particular as
previously it wasn't clear what would actually happen when used in
combination). Moreover, by making this change we can later add a "+"
modifier to these setings too working similar to the same modifier in
ReadOnlyPaths= and friends, making specified paths relative to the
container itself.
2017-09-28 18:55:45 +02:00
|
|
|
strv_length(empty_directories) +
|
2016-11-23 22:21:40 +01:00
|
|
|
n_bind_mounts +
|
2016-10-12 14:11:16 +02:00
|
|
|
ns_info->private_dev +
|
|
|
|
(ns_info->protect_kernel_tunables ? ELEMENTSOF(protect_kernel_tunables_table) : 0) +
|
|
|
|
(ns_info->protect_control_groups ? 1 : 0) +
|
|
|
|
(ns_info->protect_kernel_modules ? ELEMENTSOF(protect_kernel_modules_table) : 0) +
|
2016-12-22 23:34:35 +01:00
|
|
|
protect_home_cnt + protect_system_cnt +
|
2017-03-05 21:39:43 +01:00
|
|
|
(namespace_info_mount_apivfs(root_directory, ns_info) ? ELEMENTSOF(apivfs_table) : 0);
|
2016-09-25 11:25:00 +02:00
|
|
|
}
|
|
|
|
|
2013-11-27 20:23:18 +01:00
|
|
|
int setup_namespace(
|
2015-05-18 12:20:28 +02:00
|
|
|
const char* root_directory,
|
2016-12-23 14:26:05 +01:00
|
|
|
const char* root_image,
|
2017-10-10 09:49:20 +02:00
|
|
|
const NamespaceInfo *ns_info,
|
2016-07-07 11:17:00 +02:00
|
|
|
char** read_write_paths,
|
|
|
|
char** read_only_paths,
|
|
|
|
char** inaccessible_paths,
|
execute: make StateDirectory= and friends compatible with DynamicUser=1 and RootDirectory=/RootImage=
Let's clean up the interaction of StateDirectory= (and friends) to
DynamicUser=1: instead of creating these directories directly below
/var/lib, place them in /var/lib/private instead if DynamicUser=1 is
set, making that directory 0700 and owned by root:root. This way, if a
dynamic UID is later reused, access to the old run's state directory is
prohibited for that user. Then, use file system namespacing inside the
service to make /var/lib/private a readable tmpfs, hiding all state
directories that are not listed in StateDirectory=, and making access to
the actual state directory possible. Mount all directories listed in
StateDirectory= to the same places inside the service (which means
they'll now be mounted into the tmpfs instance). Finally, add a symlink
from the state directory name in /var/lib/ to the one in
/var/lib/private, so that both the host and the service can access the
path under the same location.
Here's an example: let's say a service runs with StateDirectory=foo.
When DynamicUser=0 is set, it will get the following setup, and no
difference between what the unit and what the host sees:
/var/lib/foo (created as directory)
Now, if DynamicUser=1 is set, we'll instead get this on the host:
/var/lib/private (created as directory with mode 0700, root:root)
/var/lib/private/foo (created as directory)
/var/lib/foo → private/foo (created as symlink)
And from inside the unit:
/var/lib/private (a tmpfs mount with mode 0755, root:root)
/var/lib/private/foo (bind mounted from the host)
/var/lib/foo → private/foo (the same symlink as above)
This takes inspiration from how container trees are protected below
/var/lib/machines: they generally reuse UIDs/GIDs of the host, but
because /var/lib/machines itself is set to 0700 host users cannot access
files in the container tree even if the UIDs/GIDs are reused. However,
for this commit we add one further trick: inside and outside of the unit
/var/lib/private is a different thing: outside it is a plain,
inaccessible directory, and inside it is a world-readable tmpfs mount
with only the whitelisted subdirs below it, bind mounte din. This
means, from the outside the dir acts as an access barrier, but from the
inside it does not. And the symlink created in /var/lib/foo itself
points across the barrier in both cases, so that root and the unit's
user always have access to these dirs without knowing the details of
this mounting magic.
This logic resolves a major shortcoming of DynamicUser=1 units:
previously they couldn't safely store persistant data. With this change
they can have their own private state, log and data directories, which
they can write to, but which are protected from UID recycling.
With this change, if RootDirectory= or RootImage= are used it is ensured
that the specified state/log/cache directories are always mounted in
from the host. This change of semantics I think is much preferable since
this means the root directory/image logic can be used easily for
read-only resource bundling (as all writable data resides outside of the
image). Note that this is a change of behaviour, but given that we
haven't released any systemd version with StateDirectory= and friends
implemented this should be a safe change to make (in particular as
previously it wasn't clear what would actually happen when used in
combination). Moreover, by making this change we can later add a "+"
modifier to these setings too working similar to the same modifier in
ReadOnlyPaths= and friends, making specified paths relative to the
container itself.
2017-09-28 18:55:45 +02:00
|
|
|
char** empty_directories,
|
2016-11-23 22:21:40 +01:00
|
|
|
const BindMount *bind_mounts,
|
|
|
|
unsigned n_bind_mounts,
|
2014-10-17 13:48:55 +02:00
|
|
|
const char* tmp_dir,
|
|
|
|
const char* var_tmp_dir,
|
2014-06-04 18:07:55 +02:00
|
|
|
ProtectHome protect_home,
|
|
|
|
ProtectSystem protect_system,
|
2016-12-23 14:26:05 +01:00
|
|
|
unsigned long mount_flags,
|
|
|
|
DissectImageFlags dissect_image_flags) {
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2016-12-23 14:26:05 +01:00
|
|
|
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
|
2016-12-23 17:10:42 +01:00
|
|
|
_cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL;
|
2016-12-23 14:26:05 +01:00
|
|
|
_cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL;
|
2016-12-23 17:10:42 +01:00
|
|
|
_cleanup_free_ void *root_hash = NULL;
|
2016-12-14 00:48:52 +01:00
|
|
|
MountEntry *m, *mounts = NULL;
|
2016-12-23 17:10:42 +01:00
|
|
|
size_t root_hash_size = 0;
|
2016-09-24 12:41:30 +02:00
|
|
|
bool make_slave = false;
|
2017-09-28 18:30:55 +02:00
|
|
|
const char *root;
|
2016-11-06 22:51:49 +01:00
|
|
|
unsigned n_mounts;
|
2017-10-13 14:22:25 +02:00
|
|
|
bool require_prefix = false;
|
2013-03-14 18:12:27 +01:00
|
|
|
int r = 0;
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2016-12-23 14:26:05 +01:00
|
|
|
assert(ns_info);
|
|
|
|
|
2013-11-27 20:23:18 +01:00
|
|
|
if (mount_flags == 0)
|
2013-03-14 18:12:27 +01:00
|
|
|
mount_flags = MS_SHARED;
|
2012-08-13 15:27:04 +02:00
|
|
|
|
2016-12-23 14:26:05 +01:00
|
|
|
if (root_image) {
|
|
|
|
dissect_image_flags |= DISSECT_IMAGE_REQUIRE_ROOT;
|
|
|
|
|
|
|
|
if (protect_system == PROTECT_SYSTEM_STRICT && strv_isempty(read_write_paths))
|
|
|
|
dissect_image_flags |= DISSECT_IMAGE_READ_ONLY;
|
|
|
|
|
|
|
|
r = loop_device_make_by_path(root_image,
|
|
|
|
dissect_image_flags & DISSECT_IMAGE_READ_ONLY ? O_RDONLY : O_RDWR,
|
|
|
|
&loop_device);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2016-12-23 17:10:42 +01:00
|
|
|
r = root_hash_load(root_image, &root_hash, &root_hash_size);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
r = dissect_image(loop_device->fd, root_hash, root_hash_size, dissect_image_flags, &dissected_image);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
r = dissected_image_decrypt(dissected_image, NULL, root_hash, root_hash_size, dissect_image_flags, &decrypted_image);
|
2016-12-23 14:26:05 +01:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2017-09-28 18:30:55 +02:00
|
|
|
if (root_directory)
|
|
|
|
root = root_directory;
|
|
|
|
else if (root_image || n_bind_mounts > 0) {
|
|
|
|
|
|
|
|
/* If we are booting from an image, create a mount point for the image, if it's still missing. We use
|
|
|
|
* the same mount point for all images, which is safe, since they all live in their own namespaces
|
|
|
|
* after all, and hence won't see each other. We also use such a root directory whenever there are bind
|
|
|
|
* mounts configured, so that their source mounts are never obstructed by mounts we already applied
|
|
|
|
* while we are applying them. */
|
|
|
|
|
|
|
|
root = "/run/systemd/unit-root";
|
|
|
|
(void) mkdir_label(root, 0700);
|
2017-10-13 14:22:25 +02:00
|
|
|
require_prefix = true;
|
2017-09-28 18:30:55 +02:00
|
|
|
} else
|
|
|
|
root = NULL;
|
|
|
|
|
2016-11-23 01:09:14 +01:00
|
|
|
n_mounts = namespace_calculate_mounts(
|
2017-09-28 18:30:55 +02:00
|
|
|
root,
|
2016-11-23 01:09:14 +01:00
|
|
|
ns_info,
|
|
|
|
read_write_paths,
|
|
|
|
read_only_paths,
|
|
|
|
inaccessible_paths,
|
execute: make StateDirectory= and friends compatible with DynamicUser=1 and RootDirectory=/RootImage=
Let's clean up the interaction of StateDirectory= (and friends) to
DynamicUser=1: instead of creating these directories directly below
/var/lib, place them in /var/lib/private instead if DynamicUser=1 is
set, making that directory 0700 and owned by root:root. This way, if a
dynamic UID is later reused, access to the old run's state directory is
prohibited for that user. Then, use file system namespacing inside the
service to make /var/lib/private a readable tmpfs, hiding all state
directories that are not listed in StateDirectory=, and making access to
the actual state directory possible. Mount all directories listed in
StateDirectory= to the same places inside the service (which means
they'll now be mounted into the tmpfs instance). Finally, add a symlink
from the state directory name in /var/lib/ to the one in
/var/lib/private, so that both the host and the service can access the
path under the same location.
Here's an example: let's say a service runs with StateDirectory=foo.
When DynamicUser=0 is set, it will get the following setup, and no
difference between what the unit and what the host sees:
/var/lib/foo (created as directory)
Now, if DynamicUser=1 is set, we'll instead get this on the host:
/var/lib/private (created as directory with mode 0700, root:root)
/var/lib/private/foo (created as directory)
/var/lib/foo → private/foo (created as symlink)
And from inside the unit:
/var/lib/private (a tmpfs mount with mode 0755, root:root)
/var/lib/private/foo (bind mounted from the host)
/var/lib/foo → private/foo (the same symlink as above)
This takes inspiration from how container trees are protected below
/var/lib/machines: they generally reuse UIDs/GIDs of the host, but
because /var/lib/machines itself is set to 0700 host users cannot access
files in the container tree even if the UIDs/GIDs are reused. However,
for this commit we add one further trick: inside and outside of the unit
/var/lib/private is a different thing: outside it is a plain,
inaccessible directory, and inside it is a world-readable tmpfs mount
with only the whitelisted subdirs below it, bind mounte din. This
means, from the outside the dir acts as an access barrier, but from the
inside it does not. And the symlink created in /var/lib/foo itself
points across the barrier in both cases, so that root and the unit's
user always have access to these dirs without knowing the details of
this mounting magic.
This logic resolves a major shortcoming of DynamicUser=1 units:
previously they couldn't safely store persistant data. With this change
they can have their own private state, log and data directories, which
they can write to, but which are protected from UID recycling.
With this change, if RootDirectory= or RootImage= are used it is ensured
that the specified state/log/cache directories are always mounted in
from the host. This change of semantics I think is much preferable since
this means the root directory/image logic can be used easily for
read-only resource bundling (as all writable data resides outside of the
image). Note that this is a change of behaviour, but given that we
haven't released any systemd version with StateDirectory= and friends
implemented this should be a safe change to make (in particular as
previously it wasn't clear what would actually happen when used in
combination). Moreover, by making this change we can later add a "+"
modifier to these setings too working similar to the same modifier in
ReadOnlyPaths= and friends, making specified paths relative to the
container itself.
2017-09-28 18:55:45 +02:00
|
|
|
empty_directories,
|
2016-11-23 22:21:40 +01:00
|
|
|
bind_mounts, n_bind_mounts,
|
2016-11-23 01:09:14 +01:00
|
|
|
tmp_dir, var_tmp_dir,
|
|
|
|
protect_home, protect_system);
|
2013-11-27 20:23:18 +01:00
|
|
|
|
2016-09-25 11:25:00 +02:00
|
|
|
/* Set mount slave mode */
|
2017-09-28 18:30:55 +02:00
|
|
|
if (root || n_mounts > 0)
|
2016-09-24 12:41:30 +02:00
|
|
|
make_slave = true;
|
|
|
|
|
2016-11-06 22:51:49 +01:00
|
|
|
if (n_mounts > 0) {
|
2016-12-14 00:48:52 +01:00
|
|
|
m = mounts = (MountEntry *) alloca0(n_mounts * sizeof(MountEntry));
|
2017-10-13 14:22:25 +02:00
|
|
|
r = append_access_mounts(&m, read_write_paths, READWRITE, require_prefix);
|
2013-11-27 20:23:18 +01:00
|
|
|
if (r < 0)
|
2016-11-06 22:51:49 +01:00
|
|
|
goto finish;
|
2013-11-27 20:23:18 +01:00
|
|
|
|
2017-10-13 14:22:25 +02:00
|
|
|
r = append_access_mounts(&m, read_only_paths, READONLY, require_prefix);
|
2013-11-27 20:23:18 +01:00
|
|
|
if (r < 0)
|
2016-11-06 22:51:49 +01:00
|
|
|
goto finish;
|
2013-11-27 20:23:18 +01:00
|
|
|
|
2017-10-13 14:22:25 +02:00
|
|
|
r = append_access_mounts(&m, inaccessible_paths, INACCESSIBLE, require_prefix);
|
2013-11-27 20:23:18 +01:00
|
|
|
if (r < 0)
|
2016-11-06 22:51:49 +01:00
|
|
|
goto finish;
|
2013-10-12 01:33:13 +02:00
|
|
|
|
execute: make StateDirectory= and friends compatible with DynamicUser=1 and RootDirectory=/RootImage=
Let's clean up the interaction of StateDirectory= (and friends) to
DynamicUser=1: instead of creating these directories directly below
/var/lib, place them in /var/lib/private instead if DynamicUser=1 is
set, making that directory 0700 and owned by root:root. This way, if a
dynamic UID is later reused, access to the old run's state directory is
prohibited for that user. Then, use file system namespacing inside the
service to make /var/lib/private a readable tmpfs, hiding all state
directories that are not listed in StateDirectory=, and making access to
the actual state directory possible. Mount all directories listed in
StateDirectory= to the same places inside the service (which means
they'll now be mounted into the tmpfs instance). Finally, add a symlink
from the state directory name in /var/lib/ to the one in
/var/lib/private, so that both the host and the service can access the
path under the same location.
Here's an example: let's say a service runs with StateDirectory=foo.
When DynamicUser=0 is set, it will get the following setup, and no
difference between what the unit and what the host sees:
/var/lib/foo (created as directory)
Now, if DynamicUser=1 is set, we'll instead get this on the host:
/var/lib/private (created as directory with mode 0700, root:root)
/var/lib/private/foo (created as directory)
/var/lib/foo → private/foo (created as symlink)
And from inside the unit:
/var/lib/private (a tmpfs mount with mode 0755, root:root)
/var/lib/private/foo (bind mounted from the host)
/var/lib/foo → private/foo (the same symlink as above)
This takes inspiration from how container trees are protected below
/var/lib/machines: they generally reuse UIDs/GIDs of the host, but
because /var/lib/machines itself is set to 0700 host users cannot access
files in the container tree even if the UIDs/GIDs are reused. However,
for this commit we add one further trick: inside and outside of the unit
/var/lib/private is a different thing: outside it is a plain,
inaccessible directory, and inside it is a world-readable tmpfs mount
with only the whitelisted subdirs below it, bind mounte din. This
means, from the outside the dir acts as an access barrier, but from the
inside it does not. And the symlink created in /var/lib/foo itself
points across the barrier in both cases, so that root and the unit's
user always have access to these dirs without knowing the details of
this mounting magic.
This logic resolves a major shortcoming of DynamicUser=1 units:
previously they couldn't safely store persistant data. With this change
they can have their own private state, log and data directories, which
they can write to, but which are protected from UID recycling.
With this change, if RootDirectory= or RootImage= are used it is ensured
that the specified state/log/cache directories are always mounted in
from the host. This change of semantics I think is much preferable since
this means the root directory/image logic can be used easily for
read-only resource bundling (as all writable data resides outside of the
image). Note that this is a change of behaviour, but given that we
haven't released any systemd version with StateDirectory= and friends
implemented this should be a safe change to make (in particular as
previously it wasn't clear what would actually happen when used in
combination). Moreover, by making this change we can later add a "+"
modifier to these setings too working similar to the same modifier in
ReadOnlyPaths= and friends, making specified paths relative to the
container itself.
2017-09-28 18:55:45 +02:00
|
|
|
r = append_empty_dir_mounts(&m, empty_directories);
|
|
|
|
if (r < 0)
|
|
|
|
goto finish;
|
|
|
|
|
2016-11-23 22:21:40 +01:00
|
|
|
r = append_bind_mounts(&m, bind_mounts, n_bind_mounts);
|
|
|
|
if (r < 0)
|
|
|
|
goto finish;
|
|
|
|
|
2013-11-27 20:23:18 +01:00
|
|
|
if (tmp_dir) {
|
2016-12-14 00:48:52 +01:00
|
|
|
*(m++) = (MountEntry) {
|
2016-11-15 01:42:54 +01:00
|
|
|
.path_const = "/tmp",
|
|
|
|
.mode = PRIVATE_TMP,
|
|
|
|
};
|
2013-11-27 20:23:18 +01:00
|
|
|
}
|
2013-10-12 01:33:13 +02:00
|
|
|
|
2013-11-27 20:23:18 +01:00
|
|
|
if (var_tmp_dir) {
|
2016-12-14 00:48:52 +01:00
|
|
|
*(m++) = (MountEntry) {
|
2016-11-15 01:42:54 +01:00
|
|
|
.path_const = "/var/tmp",
|
|
|
|
.mode = PRIVATE_VAR_TMP,
|
|
|
|
};
|
2013-10-12 01:33:13 +02:00
|
|
|
}
|
2012-08-13 15:27:04 +02:00
|
|
|
|
2016-10-12 14:11:16 +02:00
|
|
|
if (ns_info->private_dev) {
|
2016-12-14 00:48:52 +01:00
|
|
|
*(m++) = (MountEntry) {
|
2016-11-15 01:42:54 +01:00
|
|
|
.path_const = "/dev",
|
|
|
|
.mode = PRIVATE_DEV,
|
|
|
|
};
|
2014-01-20 19:54:51 +01:00
|
|
|
}
|
|
|
|
|
2016-10-12 14:11:16 +02:00
|
|
|
if (ns_info->protect_kernel_tunables) {
|
2016-11-15 01:42:54 +01:00
|
|
|
r = append_static_mounts(&m, protect_kernel_tunables_table, ELEMENTSOF(protect_kernel_tunables_table), ns_info->ignore_protect_paths);
|
2016-10-12 14:11:16 +02:00
|
|
|
if (r < 0)
|
2016-11-06 22:51:49 +01:00
|
|
|
goto finish;
|
2016-10-12 14:11:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ns_info->protect_kernel_modules) {
|
2016-11-15 01:42:54 +01:00
|
|
|
r = append_static_mounts(&m, protect_kernel_modules_table, ELEMENTSOF(protect_kernel_modules_table), ns_info->ignore_protect_paths);
|
2016-10-12 14:11:16 +02:00
|
|
|
if (r < 0)
|
2016-11-06 22:51:49 +01:00
|
|
|
goto finish;
|
2016-10-12 14:11:16 +02:00
|
|
|
}
|
2016-08-22 18:43:59 +02:00
|
|
|
|
2016-10-12 14:11:16 +02:00
|
|
|
if (ns_info->protect_control_groups) {
|
2016-12-14 00:48:52 +01:00
|
|
|
*(m++) = (MountEntry) {
|
2016-11-15 01:42:54 +01:00
|
|
|
.path_const = "/sys/fs/cgroup",
|
|
|
|
.mode = READONLY,
|
|
|
|
};
|
2016-08-22 18:43:59 +02:00
|
|
|
}
|
|
|
|
|
2016-11-15 01:42:54 +01:00
|
|
|
r = append_protect_home(&m, protect_home, ns_info->ignore_protect_paths);
|
2016-09-25 12:41:16 +02:00
|
|
|
if (r < 0)
|
2016-11-06 22:51:49 +01:00
|
|
|
goto finish;
|
2014-06-03 23:41:44 +02:00
|
|
|
|
2016-11-15 01:42:54 +01:00
|
|
|
r = append_protect_system(&m, protect_system, false);
|
2016-09-25 12:21:25 +02:00
|
|
|
if (r < 0)
|
2016-11-06 22:51:49 +01:00
|
|
|
goto finish;
|
2014-06-03 23:41:44 +02:00
|
|
|
|
2017-09-28 18:30:55 +02:00
|
|
|
if (namespace_info_mount_apivfs(root, ns_info)) {
|
2016-12-22 23:34:35 +01:00
|
|
|
r = append_static_mounts(&m, apivfs_table, ELEMENTSOF(apivfs_table), ns_info->ignore_protect_paths);
|
|
|
|
if (r < 0)
|
|
|
|
goto finish;
|
|
|
|
}
|
|
|
|
|
2016-11-06 22:51:49 +01:00
|
|
|
assert(mounts + n_mounts == m);
|
2012-08-13 15:27:04 +02:00
|
|
|
|
2016-11-15 01:42:54 +01:00
|
|
|
/* Prepend the root directory where that's necessary */
|
2017-09-28 18:30:55 +02:00
|
|
|
r = prefix_where_needed(mounts, n_mounts, root);
|
2016-11-15 01:42:54 +01:00
|
|
|
if (r < 0)
|
|
|
|
goto finish;
|
|
|
|
|
2016-12-14 00:48:52 +01:00
|
|
|
qsort(mounts, n_mounts, sizeof(MountEntry), mount_path_compare);
|
2016-08-24 23:17:42 +02:00
|
|
|
|
2016-11-06 22:51:49 +01:00
|
|
|
drop_duplicates(mounts, &n_mounts);
|
2017-09-28 18:30:55 +02:00
|
|
|
drop_outside_root(root, mounts, &n_mounts);
|
2016-11-06 22:51:49 +01:00
|
|
|
drop_inaccessible(mounts, &n_mounts);
|
|
|
|
drop_nop(mounts, &n_mounts);
|
2010-04-21 22:15:06 +02:00
|
|
|
}
|
|
|
|
|
2016-09-24 12:41:30 +02:00
|
|
|
if (unshare(CLONE_NEWNS) < 0) {
|
|
|
|
r = -errno;
|
|
|
|
goto finish;
|
|
|
|
}
|
2016-08-25 17:30:47 +02:00
|
|
|
|
2016-09-24 12:41:30 +02:00
|
|
|
if (make_slave) {
|
2014-03-20 04:16:39 +01:00
|
|
|
/* Remount / as SLAVE so that nothing now mounted in the namespace
|
|
|
|
shows up in the parent */
|
2016-09-24 12:41:30 +02:00
|
|
|
if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) {
|
|
|
|
r = -errno;
|
|
|
|
goto finish;
|
|
|
|
}
|
2015-05-18 12:20:28 +02:00
|
|
|
}
|
|
|
|
|
2016-12-23 14:26:05 +01:00
|
|
|
if (root_image) {
|
2017-09-28 18:30:55 +02:00
|
|
|
/* A root image is specified, mount it to the right place */
|
2017-11-28 16:46:26 +01:00
|
|
|
r = dissected_image_mount(dissected_image, root, UID_INVALID, dissect_image_flags);
|
2016-12-23 14:26:05 +01:00
|
|
|
if (r < 0)
|
|
|
|
goto finish;
|
|
|
|
|
2017-08-29 17:31:24 +02:00
|
|
|
if (decrypted_image) {
|
|
|
|
r = decrypted_image_relinquish(decrypted_image);
|
|
|
|
if (r < 0)
|
|
|
|
goto finish;
|
|
|
|
}
|
2016-12-23 17:10:42 +01:00
|
|
|
|
2016-12-23 14:26:05 +01:00
|
|
|
loop_device_relinquish(loop_device);
|
|
|
|
|
|
|
|
} else if (root_directory) {
|
|
|
|
|
2017-09-28 18:30:55 +02:00
|
|
|
/* A root directory is specified. Turn its directory into bind mount, if it isn't one yet. */
|
|
|
|
r = path_is_mount_point(root, NULL, AT_SYMLINK_FOLLOW);
|
2016-08-26 11:27:38 +02:00
|
|
|
if (r < 0)
|
2016-09-24 12:41:30 +02:00
|
|
|
goto finish;
|
2016-08-26 11:27:38 +02:00
|
|
|
if (r == 0) {
|
2017-09-28 18:30:55 +02:00
|
|
|
if (mount(root, root, NULL, MS_BIND|MS_REC, NULL) < 0) {
|
2016-08-26 11:27:38 +02:00
|
|
|
r = -errno;
|
|
|
|
goto finish;
|
|
|
|
}
|
2016-09-24 12:41:30 +02:00
|
|
|
}
|
2017-09-28 18:30:55 +02:00
|
|
|
|
|
|
|
} else if (root) {
|
|
|
|
|
|
|
|
/* Let's mount the main root directory to the root directory to use */
|
|
|
|
if (mount("/", root, NULL, MS_BIND|MS_REC, NULL) < 0) {
|
|
|
|
r = -errno;
|
|
|
|
goto finish;
|
|
|
|
}
|
2015-05-18 12:20:28 +02:00
|
|
|
}
|
2014-03-20 04:16:39 +01:00
|
|
|
|
2017-11-08 18:35:16 +01:00
|
|
|
/* Try to set up the new root directory before mounting anything else there. */
|
|
|
|
if (root_image || root_directory)
|
|
|
|
(void) base_filesystem_create(root, UID_INVALID, GID_INVALID);
|
|
|
|
|
2016-11-06 22:51:49 +01:00
|
|
|
if (n_mounts > 0) {
|
2017-05-19 14:38:40 +02:00
|
|
|
_cleanup_fclose_ FILE *proc_self_mountinfo = NULL;
|
namespace: rework how ReadWritePaths= is applied
Previously, if ReadWritePaths= was nested inside a ReadOnlyPaths=
specification, then we'd first recursively apply the ReadOnlyPaths= paths, and
make everything below read-only, only in order to then flip the read-only bit
again for the subdirs listed in ReadWritePaths= below it.
This is not only ugly (as for the dirs in question we first turn on the RO bit,
only to turn it off again immediately after), but also problematic in
containers, where a container manager might have marked a set of dirs read-only
and this code will undo this is ReadWritePaths= is set for any.
With this patch behaviour in this regard is altered: ReadOnlyPaths= will not be
applied to the children listed in ReadWritePaths= in the first place, so that
we do not need to turn off the RO bit for those after all.
This means that ReadWritePaths=/ReadOnlyPaths= may only be used to turn on the
RO bit, but never to turn it off again. Or to say this differently: if some
dirs are marked read-only via some external tool, then ReadWritePaths= will not
undo it.
This is not only the safer option, but also more in-line with what the man page
currently claims:
"Entries (files or directories) listed in ReadWritePaths= are
accessible from within the namespace with the same access rights as
from outside."
To implement this change bind_remount_recursive() gained a new "blacklist"
string list parameter, which when passed may contain subdirs that shall be
excluded from the read-only mounting.
A number of functions are updated to add more debug logging to make this more
digestable.
2016-09-25 10:40:51 +02:00
|
|
|
char **blacklist;
|
|
|
|
unsigned j;
|
|
|
|
|
2017-05-19 14:38:40 +02:00
|
|
|
/* Open /proc/self/mountinfo now as it may become unavailable if we mount anything on top of /proc.
|
|
|
|
* For example, this is the case with the option: 'InaccessiblePaths=/proc' */
|
|
|
|
proc_self_mountinfo = fopen("/proc/self/mountinfo", "re");
|
|
|
|
if (!proc_self_mountinfo) {
|
|
|
|
r = -errno;
|
|
|
|
goto finish;
|
|
|
|
}
|
|
|
|
|
namespace: rework how ReadWritePaths= is applied
Previously, if ReadWritePaths= was nested inside a ReadOnlyPaths=
specification, then we'd first recursively apply the ReadOnlyPaths= paths, and
make everything below read-only, only in order to then flip the read-only bit
again for the subdirs listed in ReadWritePaths= below it.
This is not only ugly (as for the dirs in question we first turn on the RO bit,
only to turn it off again immediately after), but also problematic in
containers, where a container manager might have marked a set of dirs read-only
and this code will undo this is ReadWritePaths= is set for any.
With this patch behaviour in this regard is altered: ReadOnlyPaths= will not be
applied to the children listed in ReadWritePaths= in the first place, so that
we do not need to turn off the RO bit for those after all.
This means that ReadWritePaths=/ReadOnlyPaths= may only be used to turn on the
RO bit, but never to turn it off again. Or to say this differently: if some
dirs are marked read-only via some external tool, then ReadWritePaths= will not
undo it.
This is not only the safer option, but also more in-line with what the man page
currently claims:
"Entries (files or directories) listed in ReadWritePaths= are
accessible from within the namespace with the same access rights as
from outside."
To implement this change bind_remount_recursive() gained a new "blacklist"
string list parameter, which when passed may contain subdirs that shall be
excluded from the read-only mounting.
A number of functions are updated to add more debug logging to make this more
digestable.
2016-09-25 10:40:51 +02:00
|
|
|
/* First round, add in all special mounts we need */
|
2016-11-06 22:51:49 +01:00
|
|
|
for (m = mounts; m < mounts + n_mounts; ++m) {
|
2017-09-28 18:30:55 +02:00
|
|
|
r = apply_mount(root, m, tmp_dir, var_tmp_dir);
|
2014-03-20 04:16:39 +01:00
|
|
|
if (r < 0)
|
2016-09-24 12:41:30 +02:00
|
|
|
goto finish;
|
2014-03-20 04:16:39 +01:00
|
|
|
}
|
2010-04-21 22:15:06 +02:00
|
|
|
|
namespace: rework how ReadWritePaths= is applied
Previously, if ReadWritePaths= was nested inside a ReadOnlyPaths=
specification, then we'd first recursively apply the ReadOnlyPaths= paths, and
make everything below read-only, only in order to then flip the read-only bit
again for the subdirs listed in ReadWritePaths= below it.
This is not only ugly (as for the dirs in question we first turn on the RO bit,
only to turn it off again immediately after), but also problematic in
containers, where a container manager might have marked a set of dirs read-only
and this code will undo this is ReadWritePaths= is set for any.
With this patch behaviour in this regard is altered: ReadOnlyPaths= will not be
applied to the children listed in ReadWritePaths= in the first place, so that
we do not need to turn off the RO bit for those after all.
This means that ReadWritePaths=/ReadOnlyPaths= may only be used to turn on the
RO bit, but never to turn it off again. Or to say this differently: if some
dirs are marked read-only via some external tool, then ReadWritePaths= will not
undo it.
This is not only the safer option, but also more in-line with what the man page
currently claims:
"Entries (files or directories) listed in ReadWritePaths= are
accessible from within the namespace with the same access rights as
from outside."
To implement this change bind_remount_recursive() gained a new "blacklist"
string list parameter, which when passed may contain subdirs that shall be
excluded from the read-only mounting.
A number of functions are updated to add more debug logging to make this more
digestable.
2016-09-25 10:40:51 +02:00
|
|
|
/* Create a blacklist we can pass to bind_mount_recursive() */
|
2016-11-06 22:51:49 +01:00
|
|
|
blacklist = newa(char*, n_mounts+1);
|
|
|
|
for (j = 0; j < n_mounts; j++)
|
2016-12-14 00:48:52 +01:00
|
|
|
blacklist[j] = (char*) mount_entry_path(mounts+j);
|
namespace: rework how ReadWritePaths= is applied
Previously, if ReadWritePaths= was nested inside a ReadOnlyPaths=
specification, then we'd first recursively apply the ReadOnlyPaths= paths, and
make everything below read-only, only in order to then flip the read-only bit
again for the subdirs listed in ReadWritePaths= below it.
This is not only ugly (as for the dirs in question we first turn on the RO bit,
only to turn it off again immediately after), but also problematic in
containers, where a container manager might have marked a set of dirs read-only
and this code will undo this is ReadWritePaths= is set for any.
With this patch behaviour in this regard is altered: ReadOnlyPaths= will not be
applied to the children listed in ReadWritePaths= in the first place, so that
we do not need to turn off the RO bit for those after all.
This means that ReadWritePaths=/ReadOnlyPaths= may only be used to turn on the
RO bit, but never to turn it off again. Or to say this differently: if some
dirs are marked read-only via some external tool, then ReadWritePaths= will not
undo it.
This is not only the safer option, but also more in-line with what the man page
currently claims:
"Entries (files or directories) listed in ReadWritePaths= are
accessible from within the namespace with the same access rights as
from outside."
To implement this change bind_remount_recursive() gained a new "blacklist"
string list parameter, which when passed may contain subdirs that shall be
excluded from the read-only mounting.
A number of functions are updated to add more debug logging to make this more
digestable.
2016-09-25 10:40:51 +02:00
|
|
|
blacklist[j] = NULL;
|
|
|
|
|
|
|
|
/* Second round, flip the ro bits if necessary. */
|
2016-11-06 22:51:49 +01:00
|
|
|
for (m = mounts; m < mounts + n_mounts; ++m) {
|
2017-05-19 14:38:40 +02:00
|
|
|
r = make_read_only(m, blacklist, proc_self_mountinfo);
|
2014-03-20 04:16:39 +01:00
|
|
|
if (r < 0)
|
2016-09-24 12:41:30 +02:00
|
|
|
goto finish;
|
2014-03-20 04:16:39 +01:00
|
|
|
}
|
2010-04-21 22:15:06 +02:00
|
|
|
}
|
|
|
|
|
2017-09-28 18:30:55 +02:00
|
|
|
if (root) {
|
2015-05-18 12:20:28 +02:00
|
|
|
/* MS_MOVE does not work on MS_SHARED so the remount MS_SHARED will be done later */
|
2017-09-28 18:30:55 +02:00
|
|
|
r = mount_move_root(root);
|
2016-09-24 12:41:30 +02:00
|
|
|
if (r < 0)
|
|
|
|
goto finish;
|
2015-05-18 12:20:28 +02:00
|
|
|
}
|
|
|
|
|
2014-03-20 04:16:39 +01:00
|
|
|
/* Remount / as the desired mode. Not that this will not
|
|
|
|
* reestablish propagation from our side to the host, since
|
|
|
|
* what's disconnected is disconnected. */
|
2016-09-24 12:41:30 +02:00
|
|
|
if (mount(NULL, "/", NULL, mount_flags | MS_REC, NULL) < 0) {
|
|
|
|
r = -errno;
|
|
|
|
goto finish;
|
|
|
|
}
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2016-09-24 12:41:30 +02:00
|
|
|
r = 0;
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2016-09-24 12:41:30 +02:00
|
|
|
finish:
|
2016-11-06 22:51:49 +01:00
|
|
|
for (m = mounts; m < mounts + n_mounts; m++)
|
2016-12-22 17:11:06 +01:00
|
|
|
mount_entry_done(m);
|
2013-11-27 20:23:18 +01:00
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2016-11-23 22:21:40 +01:00
|
|
|
void bind_mount_free_many(BindMount *b, unsigned n) {
|
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
assert(b || n == 0);
|
|
|
|
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
free(b[i].source);
|
|
|
|
free(b[i].destination);
|
|
|
|
}
|
|
|
|
|
|
|
|
free(b);
|
|
|
|
}
|
|
|
|
|
|
|
|
int bind_mount_add(BindMount **b, unsigned *n, const BindMount *item) {
|
|
|
|
_cleanup_free_ char *s = NULL, *d = NULL;
|
|
|
|
BindMount *c;
|
|
|
|
|
|
|
|
assert(b);
|
|
|
|
assert(n);
|
|
|
|
assert(item);
|
|
|
|
|
|
|
|
s = strdup(item->source);
|
|
|
|
if (!s)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
d = strdup(item->destination);
|
|
|
|
if (!d)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
c = realloc_multiply(*b, sizeof(BindMount), *n + 1);
|
|
|
|
if (!c)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
*b = c;
|
|
|
|
|
|
|
|
c[(*n) ++] = (BindMount) {
|
|
|
|
.source = s,
|
|
|
|
.destination = d,
|
|
|
|
.read_only = item->read_only,
|
|
|
|
.recursive = item->recursive,
|
|
|
|
.ignore_enoent = item->ignore_enoent,
|
|
|
|
};
|
|
|
|
|
|
|
|
s = d = NULL;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-11-27 20:23:18 +01:00
|
|
|
static int setup_one_tmp_dir(const char *id, const char *prefix, char **path) {
|
|
|
|
_cleanup_free_ char *x = NULL;
|
2013-12-13 02:05:04 +01:00
|
|
|
char bid[SD_ID128_STRING_MAX];
|
|
|
|
sd_id128_t boot_id;
|
|
|
|
int r;
|
2013-11-27 20:23:18 +01:00
|
|
|
|
|
|
|
assert(id);
|
|
|
|
assert(prefix);
|
|
|
|
assert(path);
|
|
|
|
|
2013-12-13 02:05:04 +01:00
|
|
|
/* We include the boot id in the directory so that after a
|
|
|
|
* reboot we can easily identify obsolete directories. */
|
|
|
|
|
|
|
|
r = sd_id128_get_boot(&boot_id);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2016-10-23 17:43:27 +02:00
|
|
|
x = strjoin(prefix, "/systemd-private-", sd_id128_to_string(boot_id, bid), "-", id, "-XXXXXX");
|
2013-11-27 20:23:18 +01:00
|
|
|
if (!x)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
RUN_WITH_UMASK(0077)
|
|
|
|
if (!mkdtemp(x))
|
|
|
|
return -errno;
|
|
|
|
|
|
|
|
RUN_WITH_UMASK(0000) {
|
|
|
|
char *y;
|
|
|
|
|
2015-02-03 02:05:59 +01:00
|
|
|
y = strjoina(x, "/tmp");
|
2013-11-27 20:23:18 +01:00
|
|
|
|
|
|
|
if (mkdir(y, 0777 | S_ISVTX) < 0)
|
|
|
|
return -errno;
|
2013-03-14 18:12:27 +01:00
|
|
|
}
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2013-11-27 20:23:18 +01:00
|
|
|
*path = x;
|
|
|
|
x = NULL;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int setup_tmp_dirs(const char *id, char **tmp_dir, char **var_tmp_dir) {
|
|
|
|
char *a, *b;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(id);
|
|
|
|
assert(tmp_dir);
|
|
|
|
assert(var_tmp_dir);
|
|
|
|
|
|
|
|
r = setup_one_tmp_dir(id, "/tmp", &a);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
r = setup_one_tmp_dir(id, "/var/tmp", &b);
|
|
|
|
if (r < 0) {
|
|
|
|
char *t;
|
|
|
|
|
2015-02-03 02:05:59 +01:00
|
|
|
t = strjoina(a, "/tmp");
|
2013-11-27 20:23:18 +01:00
|
|
|
rmdir(t);
|
|
|
|
rmdir(a);
|
|
|
|
|
|
|
|
free(a);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
*tmp_dir = a;
|
|
|
|
*var_tmp_dir = b;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int setup_netns(int netns_storage_socket[2]) {
|
|
|
|
_cleanup_close_ int netns = -1;
|
2015-09-23 01:00:04 +02:00
|
|
|
int r, q;
|
2013-11-27 20:23:18 +01:00
|
|
|
|
|
|
|
assert(netns_storage_socket);
|
|
|
|
assert(netns_storage_socket[0] >= 0);
|
|
|
|
assert(netns_storage_socket[1] >= 0);
|
|
|
|
|
|
|
|
/* We use the passed socketpair as a storage buffer for our
|
2013-11-27 20:31:51 +01:00
|
|
|
* namespace reference fd. Whatever process runs this first
|
|
|
|
* shall create a new namespace, all others should just join
|
|
|
|
* it. To serialize that we use a file lock on the socket
|
|
|
|
* pair.
|
2013-11-27 20:23:18 +01:00
|
|
|
*
|
|
|
|
* It's a bit crazy, but hey, works great! */
|
|
|
|
|
|
|
|
if (lockf(netns_storage_socket[0], F_LOCK, 0) < 0)
|
|
|
|
return -errno;
|
|
|
|
|
2015-09-23 01:00:04 +02:00
|
|
|
netns = receive_one_fd(netns_storage_socket[0], MSG_DONTWAIT);
|
|
|
|
if (netns == -EAGAIN) {
|
2013-11-27 20:23:18 +01:00
|
|
|
/* Nothing stored yet, so let's create a new namespace */
|
|
|
|
|
|
|
|
if (unshare(CLONE_NEWNET) < 0) {
|
|
|
|
r = -errno;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
loopback_setup();
|
|
|
|
|
|
|
|
netns = open("/proc/self/ns/net", O_RDONLY|O_CLOEXEC|O_NOCTTY);
|
|
|
|
if (netns < 0) {
|
|
|
|
r = -errno;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
r = 1;
|
|
|
|
|
2015-09-23 01:00:04 +02:00
|
|
|
} else if (netns < 0) {
|
|
|
|
r = netns;
|
|
|
|
goto fail;
|
2013-11-27 20:23:18 +01:00
|
|
|
|
2015-09-23 01:00:04 +02:00
|
|
|
} else {
|
|
|
|
/* Yay, found something, so let's join the namespace */
|
2013-11-27 20:23:18 +01:00
|
|
|
if (setns(netns, CLONE_NEWNET) < 0) {
|
|
|
|
r = -errno;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
r = 0;
|
|
|
|
}
|
|
|
|
|
2015-09-23 01:00:04 +02:00
|
|
|
q = send_one_fd(netns_storage_socket[1], netns, MSG_DONTWAIT);
|
|
|
|
if (q < 0) {
|
|
|
|
r = q;
|
2013-11-27 20:23:18 +01:00
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
fail:
|
2016-07-14 12:28:54 +02:00
|
|
|
(void) lockf(netns_storage_socket[0], F_ULOCK, 0);
|
2010-04-21 22:15:06 +02:00
|
|
|
return r;
|
|
|
|
}
|
2014-06-03 23:41:44 +02:00
|
|
|
|
2017-10-10 09:46:13 +02:00
|
|
|
bool ns_type_supported(NamespaceType type) {
|
|
|
|
const char *t, *ns_proc;
|
|
|
|
|
2017-10-10 09:50:23 +02:00
|
|
|
t = namespace_type_to_string(type);
|
|
|
|
if (!t) /* Don't know how to translate this? Then it's not supported */
|
2017-10-10 09:46:13 +02:00
|
|
|
return false;
|
|
|
|
|
|
|
|
ns_proc = strjoina("/proc/self/ns/", t);
|
|
|
|
return access(ns_proc, F_OK) == 0;
|
|
|
|
}
|
|
|
|
|
2014-06-04 18:07:55 +02:00
|
|
|
static const char *const protect_home_table[_PROTECT_HOME_MAX] = {
|
|
|
|
[PROTECT_HOME_NO] = "no",
|
|
|
|
[PROTECT_HOME_YES] = "yes",
|
|
|
|
[PROTECT_HOME_READ_ONLY] = "read-only",
|
2014-06-03 23:41:44 +02:00
|
|
|
};
|
|
|
|
|
2014-06-04 18:07:55 +02:00
|
|
|
DEFINE_STRING_TABLE_LOOKUP(protect_home, ProtectHome);
|
|
|
|
|
2018-01-01 16:08:40 +01:00
|
|
|
ProtectHome parse_protect_home_or_bool(const char *s) {
|
|
|
|
int r;
|
|
|
|
|
|
|
|
r = parse_boolean(s);
|
|
|
|
if (r > 0)
|
|
|
|
return PROTECT_HOME_YES;
|
|
|
|
if (r == 0)
|
|
|
|
return PROTECT_HOME_NO;
|
|
|
|
|
|
|
|
return protect_home_from_string(s);
|
|
|
|
}
|
|
|
|
|
2014-06-04 18:07:55 +02:00
|
|
|
static const char *const protect_system_table[_PROTECT_SYSTEM_MAX] = {
|
|
|
|
[PROTECT_SYSTEM_NO] = "no",
|
|
|
|
[PROTECT_SYSTEM_YES] = "yes",
|
|
|
|
[PROTECT_SYSTEM_FULL] = "full",
|
2016-08-25 15:57:21 +02:00
|
|
|
[PROTECT_SYSTEM_STRICT] = "strict",
|
2014-06-04 18:07:55 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_STRING_TABLE_LOOKUP(protect_system, ProtectSystem);
|
2017-10-10 09:46:13 +02:00
|
|
|
|
2018-01-01 16:10:22 +01:00
|
|
|
ProtectSystem parse_protect_system_or_bool(const char *s) {
|
|
|
|
int r;
|
|
|
|
|
|
|
|
r = parse_boolean(s);
|
|
|
|
if (r > 0)
|
|
|
|
return PROTECT_SYSTEM_YES;
|
|
|
|
if (r == 0)
|
|
|
|
return PROTECT_SYSTEM_NO;
|
|
|
|
|
|
|
|
return protect_system_from_string(s);
|
|
|
|
}
|
|
|
|
|
2017-10-10 09:46:13 +02:00
|
|
|
static const char* const namespace_type_table[] = {
|
|
|
|
[NAMESPACE_MOUNT] = "mnt",
|
|
|
|
[NAMESPACE_CGROUP] = "cgroup",
|
|
|
|
[NAMESPACE_UTS] = "uts",
|
|
|
|
[NAMESPACE_IPC] = "ipc",
|
|
|
|
[NAMESPACE_USER] = "user",
|
|
|
|
[NAMESPACE_PID] = "pid",
|
|
|
|
[NAMESPACE_NET] = "net",
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_STRING_TABLE_LOOKUP(namespace_type, NamespaceType);
|