2020-11-09 05:23:58 +01:00
|
|
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
2010-04-21 22:15:06 +02:00
|
|
|
|
|
|
|
#include <errno.h>
|
2018-12-23 19:23:58 +01:00
|
|
|
#include <linux/loop.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 <sys/mount.h>
|
|
|
|
#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"
|
2019-11-19 23:24:52 +01:00
|
|
|
#include "format-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"
|
2020-07-14 17:18:41 +02:00
|
|
|
#include "list.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 "mkdir.h"
|
2015-10-26 18:44:13 +01:00
|
|
|
#include "mount-util.h"
|
2018-11-29 10:24:39 +01:00
|
|
|
#include "mountpoint-util.h"
|
2019-03-13 11:21:49 +01:00
|
|
|
#include "namespace-util.h"
|
2015-10-25 13:14:12 +01:00
|
|
|
#include "namespace.h"
|
2019-03-14 13:14:33 +01:00
|
|
|
#include "nulstr-util.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"
|
2019-03-13 12:14:47 +01:00
|
|
|
#include "sort-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"
|
2019-11-29 20:28:35 +01:00
|
|
|
#include "tmpfile-util.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"
|
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,
|
2020-07-14 17:18:41 +02:00
|
|
|
MOUNT_IMAGES,
|
2016-11-23 22:21:40 +01:00
|
|
|
BIND_MOUNT,
|
|
|
|
BIND_MOUNT_RECURSIVE,
|
2012-08-13 15:27:04 +02:00
|
|
|
PRIVATE_TMP,
|
2020-06-28 19:54:49 +02:00
|
|
|
PRIVATE_TMP_READONLY,
|
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,
|
2018-02-21 01:17:52 +01:00
|
|
|
TMPFS,
|
2018-12-28 08:11:52 +01:00
|
|
|
READWRITE_IMPLICIT, /* Should have the lowest priority. */
|
2018-12-29 11:59:15 +01:00
|
|
|
_MOUNT_MODE_MAX,
|
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? */
|
2019-03-25 19:29:26 +01:00
|
|
|
bool nosuid:1; /* Shall set MS_NOSUID on the mount itself */
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
bool applied:1; /* Already applied */
|
2018-02-14 05:29:13 +01:00
|
|
|
char *path_malloc; /* Use this instead of 'path_const' if we had to allocate memory */
|
2020-07-14 17:18:41 +02:00
|
|
|
const char *source_const; /* The source path, for bind mounts or images */
|
2016-11-23 22:21:40 +01:00
|
|
|
char *source_malloc;
|
2018-02-21 01:17:52 +01:00
|
|
|
const char *options_const;/* Mount options for tmpfs */
|
|
|
|
char *options_malloc;
|
|
|
|
unsigned long flags; /* Mount flags used by EMPTY_DIR and TMPFS. Do not include MS_RDONLY here, but please use read_only. */
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
unsigned n_followed;
|
2020-07-31 16:06:15 +02:00
|
|
|
LIST_HEAD(MountOptions, image_options);
|
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
|
2018-06-18 22:43:12 +02:00
|
|
|
* something there already. These mounts are hence overridden by any other explicitly configured mounts. */
|
2016-12-22 23:34:35 +01:00
|
|
|
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[] = {
|
2018-12-28 08:11:52 +01:00
|
|
|
{ "/proc/acpi", READONLY, true },
|
|
|
|
{ "/proc/apm", READONLY, true }, /* Obsolete API, there's no point in permitting access to this, ever */
|
|
|
|
{ "/proc/asound", READONLY, true },
|
|
|
|
{ "/proc/bus", READONLY, true },
|
|
|
|
{ "/proc/fs", READONLY, true },
|
|
|
|
{ "/proc/irq", READONLY, true },
|
|
|
|
{ "/proc/kallsyms", INACCESSIBLE, true },
|
|
|
|
{ "/proc/kcore", INACCESSIBLE, true },
|
|
|
|
{ "/proc/latency_stats", READONLY, true },
|
|
|
|
{ "/proc/mtrr", READONLY, true },
|
|
|
|
{ "/proc/scsi", READONLY, true },
|
2020-08-06 12:51:50 +02:00
|
|
|
{ "/proc/sys", READONLY, true },
|
2018-12-28 08:11:52 +01:00
|
|
|
{ "/proc/sysrq-trigger", READONLY, true },
|
|
|
|
{ "/proc/timer_stats", READONLY, true },
|
|
|
|
{ "/sys", READONLY, false },
|
|
|
|
{ "/sys/fs/bpf", READONLY, true },
|
|
|
|
{ "/sys/fs/cgroup", READWRITE_IMPLICIT, false }, /* READONLY is set by ProtectControlGroups= option */
|
|
|
|
{ "/sys/fs/selinux", READWRITE_IMPLICIT, true },
|
|
|
|
{ "/sys/kernel/debug", READONLY, true },
|
|
|
|
{ "/sys/kernel/tracing", READONLY, 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
|
|
|
};
|
|
|
|
|
2019-11-10 10:17:01 +01:00
|
|
|
/* ProtectKernelLogs= option */
|
|
|
|
static const MountEntry protect_kernel_logs_table[] = {
|
|
|
|
{ "/proc/kmsg", INACCESSIBLE, true },
|
|
|
|
{ "/dev/kmsg", INACCESSIBLE, true },
|
|
|
|
};
|
|
|
|
|
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
|
|
|
};
|
|
|
|
|
2018-02-21 01:13:11 +01:00
|
|
|
/* ProtectHome=tmpfs table */
|
|
|
|
static const MountEntry protect_home_tmpfs_table[] = {
|
2020-04-14 15:39:36 +02:00
|
|
|
{ "/home", TMPFS, true, .read_only = true, .options_const = "mode=0755" TMPFS_LIMITS_EMPTY_OR_ALMOST, .flags = MS_NODEV|MS_STRICTATIME },
|
|
|
|
{ "/run/user", TMPFS, true, .read_only = true, .options_const = "mode=0755" TMPFS_LIMITS_EMPTY_OR_ALMOST, .flags = MS_NODEV|MS_STRICTATIME },
|
|
|
|
{ "/root", TMPFS, true, .read_only = true, .options_const = "mode=0700" TMPFS_LIMITS_EMPTY_OR_ALMOST, .flags = MS_NODEV|MS_STRICTATIME },
|
2018-02-21 01:13:11 +01:00
|
|
|
};
|
|
|
|
|
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 },
|
2014-07-24 19:38:07 +02:00
|
|
|
#if HAVE_SPLIT_USR
|
|
|
|
{ "/lib", READONLY, true },
|
|
|
|
{ "/lib64", READONLY, true },
|
|
|
|
{ "/bin", READONLY, true },
|
2018-03-01 21:48:36 +01:00
|
|
|
# if HAVE_SPLIT_BIN
|
2014-07-24 19:38:07 +02:00
|
|
|
{ "/sbin", READONLY, true },
|
2018-03-01 21:48:36 +01:00
|
|
|
# endif
|
2014-07-24 19:38:07 +02:00
|
|
|
#endif
|
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 },
|
2014-07-24 19:38:07 +02:00
|
|
|
#if HAVE_SPLIT_USR
|
|
|
|
{ "/lib", READONLY, true },
|
|
|
|
{ "/lib64", READONLY, true },
|
|
|
|
{ "/bin", READONLY, true },
|
2018-03-01 21:48:36 +01:00
|
|
|
# if HAVE_SPLIT_BIN
|
2014-07-24 19:38:07 +02:00
|
|
|
{ "/sbin", READONLY, true },
|
2018-03-01 21:48:36 +01:00
|
|
|
# endif
|
2014-07-24 19:38:07 +02:00
|
|
|
#endif
|
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[] = {
|
2018-12-28 08:11:52 +01:00
|
|
|
{ "/", READONLY, false },
|
|
|
|
{ "/proc", READWRITE_IMPLICIT, false }, /* ProtectKernelTunables= */
|
|
|
|
{ "/sys", READWRITE_IMPLICIT, false }, /* ProtectKernelTunables= */
|
|
|
|
{ "/dev", READWRITE_IMPLICIT, false }, /* PrivateDevices= */
|
|
|
|
{ "/home", READWRITE_IMPLICIT, true }, /* ProtectHome= */
|
|
|
|
{ "/run/user", READWRITE_IMPLICIT, true }, /* ProtectHome= */
|
|
|
|
{ "/root", READWRITE_IMPLICIT, true }, /* ProtectHome= */
|
2016-09-25 12:21:25 +02:00
|
|
|
};
|
|
|
|
|
2018-12-29 11:59:15 +01:00
|
|
|
static const char * const mount_mode_table[_MOUNT_MODE_MAX] = {
|
|
|
|
[INACCESSIBLE] = "inaccessible",
|
|
|
|
[BIND_MOUNT] = "bind",
|
|
|
|
[BIND_MOUNT_RECURSIVE] = "rbind",
|
|
|
|
[PRIVATE_TMP] = "private-tmp",
|
|
|
|
[PRIVATE_DEV] = "private-dev",
|
|
|
|
[BIND_DEV] = "bind-dev",
|
|
|
|
[EMPTY_DIR] = "empty",
|
|
|
|
[SYSFS] = "sysfs",
|
|
|
|
[PROCFS] = "procfs",
|
|
|
|
[READONLY] = "read-only",
|
|
|
|
[READWRITE] = "read-write",
|
|
|
|
[TMPFS] = "tmpfs",
|
2020-07-14 17:18:41 +02:00
|
|
|
[MOUNT_IMAGES] = "mount-images",
|
2018-12-29 11:59:15 +01:00
|
|
|
[READWRITE_IMPLICIT] = "rw-implicit",
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(mount_mode, MountMode);
|
|
|
|
|
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);
|
|
|
|
|
2020-06-28 19:54:49 +02:00
|
|
|
return p->read_only || IN_SET(p->mode, READONLY, INACCESSIBLE, PRIVATE_TMP_READONLY);
|
2016-11-23 01:09:14 +01:00
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-02-21 01:17:52 +01:00
|
|
|
static const char *mount_entry_options(const MountEntry *p) {
|
|
|
|
assert(p);
|
|
|
|
|
|
|
|
return p->options_malloc ?: p->options_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);
|
2018-02-21 01:17:52 +01:00
|
|
|
p->options_malloc = mfree(p->options_malloc);
|
2020-07-31 16:06:15 +02:00
|
|
|
p->image_options = mount_options_free_all(p->image_options);
|
2016-12-22 17:11:06 +01:00
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
|
2018-12-28 08:11:52 +01:00
|
|
|
/* Adds a list of user-supplied READWRITE/READWRITE_IMPLICIT/READONLY/INACCESSIBLE entries */
|
2016-11-15 01:42:54 +01:00
|
|
|
|
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
|
|
|
|
2018-11-20 23:40:44 +01:00
|
|
|
if (!path_is_absolute(e))
|
|
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
|
|
|
"Path is not absolute: %s", e);
|
2010-04-21 22:15:06 +02:00
|
|
|
|
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,
|
|
|
|
.read_only = true,
|
2020-04-14 15:39:36 +02:00
|
|
|
.options_const = "mode=755" TMPFS_LIMITS_EMPTY_OR_ALMOST,
|
2018-02-21 01:17:52 +01:00
|
|
|
.flags = MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME,
|
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
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
tree-wide: be more careful with the type of array sizes
Previously we were a bit sloppy with the index and size types of arrays,
we'd regularly use unsigned. While I don't think this ever resulted in
real issues I think we should be more careful there and follow a
stricter regime: unless there's a strong reason not to use size_t for
array sizes and indexes, size_t it should be. Any allocations we do
ultimately will use size_t anyway, and converting forth and back between
unsigned and size_t will always be a source of problems.
Note that on 32bit machines "unsigned" and "size_t" are equivalent, and
on 64bit machines our arrays shouldn't grow that large anyway, and if
they do we have a problem, however that kind of overly large allocation
we have protections for usually, but for overflows we do not have that
so much, hence let's add it.
So yeah, it's a story of the current code being already "good enough",
but I think some extra type hygiene is better.
This patch tries to be comprehensive, but it probably isn't and I missed
a few cases. But I guess we can cover that later as we notice it. Among
smaller fixes, this changes:
1. strv_length()' return type becomes size_t
2. the unit file changes array size becomes size_t
3. DNS answer and query array sizes become size_t
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=76745
2018-04-27 14:09:31 +02:00
|
|
|
static int append_bind_mounts(MountEntry **p, const BindMount *binds, size_t n) {
|
|
|
|
size_t i;
|
2016-11-23 22:21:40 +01:00
|
|
|
|
|
|
|
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,
|
2019-03-25 19:29:26 +01:00
|
|
|
.nosuid = b->nosuid,
|
2016-11-23 22:21:40 +01:00
|
|
|
.source_const = b->source,
|
2018-02-21 01:07:56 +01:00
|
|
|
.ignore = b->ignore_enoent,
|
2016-11-23 22:21:40 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-07-14 17:18:41 +02:00
|
|
|
static int append_mount_images(MountEntry **p, const MountImage *mount_images, size_t n) {
|
|
|
|
assert(p);
|
|
|
|
|
|
|
|
for (size_t i = 0; i < n; i++) {
|
|
|
|
const MountImage *m = mount_images + i;
|
|
|
|
|
|
|
|
*((*p)++) = (MountEntry) {
|
|
|
|
.path_const = m->destination,
|
|
|
|
.mode = MOUNT_IMAGES,
|
|
|
|
.source_const = m->source,
|
2020-07-31 16:06:15 +02:00
|
|
|
.image_options = m->mount_options,
|
2020-07-14 17:18:41 +02:00
|
|
|
.ignore = m->ignore_enoent,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
tree-wide: be more careful with the type of array sizes
Previously we were a bit sloppy with the index and size types of arrays,
we'd regularly use unsigned. While I don't think this ever resulted in
real issues I think we should be more careful there and follow a
stricter regime: unless there's a strong reason not to use size_t for
array sizes and indexes, size_t it should be. Any allocations we do
ultimately will use size_t anyway, and converting forth and back between
unsigned and size_t will always be a source of problems.
Note that on 32bit machines "unsigned" and "size_t" are equivalent, and
on 64bit machines our arrays shouldn't grow that large anyway, and if
they do we have a problem, however that kind of overly large allocation
we have protections for usually, but for overflows we do not have that
so much, hence let's add it.
So yeah, it's a story of the current code being already "good enough",
but I think some extra type hygiene is better.
This patch tries to be comprehensive, but it probably isn't and I missed
a few cases. But I guess we can cover that later as we notice it. Among
smaller fixes, this changes:
1. strv_length()' return type becomes size_t
2. the unit file changes array size becomes size_t
3. DNS answer and query array sizes become size_t
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=76745
2018-04-27 14:09:31 +02:00
|
|
|
static int append_tmpfs_mounts(MountEntry **p, const TemporaryFileSystem *tmpfs, size_t n) {
|
2018-02-21 01:17:52 +01:00
|
|
|
assert(p);
|
|
|
|
|
2020-07-30 13:36:10 +02:00
|
|
|
for (size_t i = 0; i < n; i++) {
|
2018-02-21 01:17:52 +01:00
|
|
|
const TemporaryFileSystem *t = tmpfs + i;
|
|
|
|
_cleanup_free_ char *o = NULL, *str = NULL;
|
2018-08-29 23:36:37 +02:00
|
|
|
unsigned long flags;
|
2018-02-21 01:17:52 +01:00
|
|
|
bool ro = false;
|
2020-07-30 13:36:10 +02:00
|
|
|
int r;
|
2018-02-21 01:17:52 +01:00
|
|
|
|
2018-11-20 23:40:44 +01:00
|
|
|
if (!path_is_absolute(t->path))
|
|
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
|
|
|
"Path is not absolute: %s",
|
|
|
|
t->path);
|
2018-02-21 01:17:52 +01:00
|
|
|
|
2020-07-30 13:36:10 +02:00
|
|
|
str = strjoin("mode=0755" NESTED_TMPFS_LIMITS ",", t->options);
|
2018-08-29 23:36:37 +02:00
|
|
|
if (!str)
|
|
|
|
return -ENOMEM;
|
2018-02-21 01:17:52 +01:00
|
|
|
|
2018-08-29 23:36:37 +02:00
|
|
|
r = mount_option_mangle(str, MS_NODEV|MS_STRICTATIME, &flags, &o);
|
|
|
|
if (r < 0)
|
|
|
|
return log_debug_errno(r, "Failed to parse mount option '%s': %m", str);
|
2018-02-21 01:17:52 +01:00
|
|
|
|
2018-08-29 23:36:37 +02:00
|
|
|
ro = flags & MS_RDONLY;
|
|
|
|
if (ro)
|
|
|
|
flags ^= MS_RDONLY;
|
2018-02-21 01:17:52 +01:00
|
|
|
|
|
|
|
*((*p)++) = (MountEntry) {
|
|
|
|
.path_const = t->path,
|
|
|
|
.mode = TMPFS,
|
|
|
|
.read_only = ro,
|
2018-08-29 23:36:37 +02:00
|
|
|
.options_malloc = TAKE_PTR(o),
|
2018-02-21 01:17:52 +01:00
|
|
|
.flags = flags,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
tree-wide: be more careful with the type of array sizes
Previously we were a bit sloppy with the index and size types of arrays,
we'd regularly use unsigned. While I don't think this ever resulted in
real issues I think we should be more careful there and follow a
stricter regime: unless there's a strong reason not to use size_t for
array sizes and indexes, size_t it should be. Any allocations we do
ultimately will use size_t anyway, and converting forth and back between
unsigned and size_t will always be a source of problems.
Note that on 32bit machines "unsigned" and "size_t" are equivalent, and
on 64bit machines our arrays shouldn't grow that large anyway, and if
they do we have a problem, however that kind of overly large allocation
we have protections for usually, but for overflows we do not have that
so much, hence let's add it.
So yeah, it's a story of the current code being already "good enough",
but I think some extra type hygiene is better.
This patch tries to be comprehensive, but it probably isn't and I missed
a few cases. But I guess we can cover that later as we notice it. Among
smaller fixes, this changes:
1. strv_length()' return type becomes size_t
2. the unit file changes array size becomes size_t
3. DNS answer and query array sizes become size_t
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=76745
2018-04-27 14:09:31 +02:00
|
|
|
static int append_static_mounts(MountEntry **p, const MountEntry *mounts, size_t n, bool ignore_protect) {
|
|
|
|
size_t 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);
|
|
|
|
|
2018-02-21 01:13:11 +01:00
|
|
|
case PROTECT_HOME_TMPFS:
|
|
|
|
return append_static_mounts(p, protect_home_tmpfs_table, ELEMENTSOF(protect_home_tmpfs_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
|
|
|
}
|
|
|
|
|
2018-09-18 01:39:24 +02:00
|
|
|
static int mount_path_compare(const MountEntry *a, const MountEntry *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 */
|
2018-09-18 01:39:24 +02:00
|
|
|
d = path_compare(mount_entry_path(a), mount_entry_path(b));
|
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 */
|
2018-09-18 01:39:24 +02:00
|
|
|
return CMP((int) a->mode, (int) b->mode);
|
2010-04-21 22:15:06 +02:00
|
|
|
}
|
|
|
|
|
tree-wide: be more careful with the type of array sizes
Previously we were a bit sloppy with the index and size types of arrays,
we'd regularly use unsigned. While I don't think this ever resulted in
real issues I think we should be more careful there and follow a
stricter regime: unless there's a strong reason not to use size_t for
array sizes and indexes, size_t it should be. Any allocations we do
ultimately will use size_t anyway, and converting forth and back between
unsigned and size_t will always be a source of problems.
Note that on 32bit machines "unsigned" and "size_t" are equivalent, and
on 64bit machines our arrays shouldn't grow that large anyway, and if
they do we have a problem, however that kind of overly large allocation
we have protections for usually, but for overflows we do not have that
so much, hence let's add it.
So yeah, it's a story of the current code being already "good enough",
but I think some extra type hygiene is better.
This patch tries to be comprehensive, but it probably isn't and I missed
a few cases. But I guess we can cover that later as we notice it. Among
smaller fixes, this changes:
1. strv_length()' return type becomes size_t
2. the unit file changes array size becomes size_t
3. DNS answer and query array sizes become size_t
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=76745
2018-04-27 14:09:31 +02:00
|
|
|
static int prefix_where_needed(MountEntry *m, size_t n, const char *root_directory) {
|
|
|
|
size_t i;
|
2016-11-15 01:42:54 +01:00
|
|
|
|
2018-08-30 00:38:40 +02:00
|
|
|
/* Prefixes all paths in the bind mount table with the root directory if the entry needs that. */
|
2016-11-15 01:42:54 +01:00
|
|
|
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
char *s;
|
|
|
|
|
|
|
|
if (m[i].has_prefix)
|
|
|
|
continue;
|
|
|
|
|
2019-06-19 15:20:13 +02:00
|
|
|
s = path_join(root_directory, mount_entry_path(m+i));
|
2016-11-15 01:42:54 +01:00
|
|
|
if (!s)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2018-02-12 06:27:49 +01:00
|
|
|
free_and_replace(m[i].path_malloc, s);
|
2016-11-15 01:42:54 +01:00
|
|
|
m[i].has_prefix = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
tree-wide: be more careful with the type of array sizes
Previously we were a bit sloppy with the index and size types of arrays,
we'd regularly use unsigned. While I don't think this ever resulted in
real issues I think we should be more careful there and follow a
stricter regime: unless there's a strong reason not to use size_t for
array sizes and indexes, size_t it should be. Any allocations we do
ultimately will use size_t anyway, and converting forth and back between
unsigned and size_t will always be a source of problems.
Note that on 32bit machines "unsigned" and "size_t" are equivalent, and
on 64bit machines our arrays shouldn't grow that large anyway, and if
they do we have a problem, however that kind of overly large allocation
we have protections for usually, but for overflows we do not have that
so much, hence let's add it.
So yeah, it's a story of the current code being already "good enough",
but I think some extra type hygiene is better.
This patch tries to be comprehensive, but it probably isn't and I missed
a few cases. But I guess we can cover that later as we notice it. Among
smaller fixes, this changes:
1. strv_length()' return type becomes size_t
2. the unit file changes array size becomes size_t
3. DNS answer and query array sizes become size_t
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=76745
2018-04-27 14:09:31 +02:00
|
|
|
static void drop_duplicates(MountEntry *m, size_t *n) {
|
2016-12-14 00:48:52 +01:00
|
|
|
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()
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
* above. Note that we only drop duplicates that haven't been mounted yet. */
|
|
|
|
if (previous &&
|
|
|
|
path_equal(mount_entry_path(f), mount_entry_path(previous)) &&
|
|
|
|
!f->applied && !previous->applied) {
|
2018-12-29 11:59:15 +01:00
|
|
|
log_debug("%s (%s) is duplicate.", mount_entry_path(f), mount_mode_to_string(f->mode));
|
2016-12-14 00:48:52 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
tree-wide: be more careful with the type of array sizes
Previously we were a bit sloppy with the index and size types of arrays,
we'd regularly use unsigned. While I don't think this ever resulted in
real issues I think we should be more careful there and follow a
stricter regime: unless there's a strong reason not to use size_t for
array sizes and indexes, size_t it should be. Any allocations we do
ultimately will use size_t anyway, and converting forth and back between
unsigned and size_t will always be a source of problems.
Note that on 32bit machines "unsigned" and "size_t" are equivalent, and
on 64bit machines our arrays shouldn't grow that large anyway, and if
they do we have a problem, however that kind of overly large allocation
we have protections for usually, but for overflows we do not have that
so much, hence let's add it.
So yeah, it's a story of the current code being already "good enough",
but I think some extra type hygiene is better.
This patch tries to be comprehensive, but it probably isn't and I missed
a few cases. But I guess we can cover that later as we notice it. Among
smaller fixes, this changes:
1. strv_length()' return type becomes size_t
2. the unit file changes array size becomes size_t
3. DNS answer and query array sizes become size_t
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=76745
2018-04-27 14:09:31 +02:00
|
|
|
static void drop_inaccessible(MountEntry *m, size_t *n) {
|
2016-12-14 00:48:52 +01:00
|
|
|
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
|
|
|
}
|
|
|
|
|
tree-wide: be more careful with the type of array sizes
Previously we were a bit sloppy with the index and size types of arrays,
we'd regularly use unsigned. While I don't think this ever resulted in
real issues I think we should be more careful there and follow a
stricter regime: unless there's a strong reason not to use size_t for
array sizes and indexes, size_t it should be. Any allocations we do
ultimately will use size_t anyway, and converting forth and back between
unsigned and size_t will always be a source of problems.
Note that on 32bit machines "unsigned" and "size_t" are equivalent, and
on 64bit machines our arrays shouldn't grow that large anyway, and if
they do we have a problem, however that kind of overly large allocation
we have protections for usually, but for overflows we do not have that
so much, hence let's add it.
So yeah, it's a story of the current code being already "good enough",
but I think some extra type hygiene is better.
This patch tries to be comprehensive, but it probably isn't and I missed
a few cases. But I guess we can cover that later as we notice it. Among
smaller fixes, this changes:
1. strv_length()' return type becomes size_t
2. the unit file changes array size becomes size_t
3. DNS answer and query array sizes become size_t
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=76745
2018-04-27 14:09:31 +02:00
|
|
|
static void drop_nop(MountEntry *m, size_t *n) {
|
2016-12-14 00:48:52 +01:00
|
|
|
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
|
|
|
|
2018-12-28 08:11:52 +01:00
|
|
|
/* Only suppress such subtrees for READONLY, READWRITE and READWRITE_IMPLICIT entries */
|
|
|
|
if (IN_SET(f->mode, READONLY, READWRITE, READWRITE_IMPLICIT)) {
|
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) {
|
2018-12-29 11:59:15 +01:00
|
|
|
log_debug("%s (%s) is made redundant by %s (%s)",
|
|
|
|
mount_entry_path(f), mount_mode_to_string(f->mode),
|
|
|
|
mount_entry_path(p), mount_mode_to_string(p->mode));
|
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;
|
|
|
|
}
|
|
|
|
|
tree-wide: be more careful with the type of array sizes
Previously we were a bit sloppy with the index and size types of arrays,
we'd regularly use unsigned. While I don't think this ever resulted in
real issues I think we should be more careful there and follow a
stricter regime: unless there's a strong reason not to use size_t for
array sizes and indexes, size_t it should be. Any allocations we do
ultimately will use size_t anyway, and converting forth and back between
unsigned and size_t will always be a source of problems.
Note that on 32bit machines "unsigned" and "size_t" are equivalent, and
on 64bit machines our arrays shouldn't grow that large anyway, and if
they do we have a problem, however that kind of overly large allocation
we have protections for usually, but for overflows we do not have that
so much, hence let's add it.
So yeah, it's a story of the current code being already "good enough",
but I think some extra type hygiene is better.
This patch tries to be comprehensive, but it probably isn't and I missed
a few cases. But I guess we can cover that later as we notice it. Among
smaller fixes, this changes:
1. strv_length()' return type becomes size_t
2. the unit file changes array size becomes size_t
3. DNS answer and query array sizes become size_t
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=76745
2018-04-27 14:09:31 +02:00
|
|
|
static void drop_outside_root(const char *root_directory, MountEntry *m, size_t *n) {
|
2016-12-14 00:48:52 +01:00
|
|
|
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-08-02 17:43:49 +02:00
|
|
|
static int clone_device_node(
|
|
|
|
const char *d,
|
|
|
|
const char *temporary_mount,
|
|
|
|
bool *make_devnode) {
|
|
|
|
|
|
|
|
_cleanup_free_ char *sl = NULL;
|
|
|
|
const char *dn, *bn, *t;
|
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-08-02 17:43:49 +02:00
|
|
|
if (errno == ENOENT) {
|
|
|
|
log_debug_errno(errno, "Device node '%s' to clone does not exist, ignoring.", d);
|
2018-04-12 17:48:22 +02:00
|
|
|
return -ENXIO;
|
2018-08-02 17:43:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return log_debug_errno(errno, "Failed to stat() device node '%s' to clone, ignoring: %m", d);
|
2018-01-16 21:27:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!S_ISBLK(st.st_mode) &&
|
2018-11-20 23:40:44 +01:00
|
|
|
!S_ISCHR(st.st_mode))
|
|
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
|
|
|
"Device node '%s' to clone is not a device node, ignoring.",
|
|
|
|
d);
|
2018-01-16 21:27:51 +01:00
|
|
|
|
2018-01-23 19:36:36 +01:00
|
|
|
dn = strjoina(temporary_mount, d);
|
2018-01-16 21:27:51 +01:00
|
|
|
|
2018-08-02 17:43:49 +02:00
|
|
|
/* First, try to create device node properly */
|
2018-03-09 14:49:15 +01:00
|
|
|
if (*make_devnode) {
|
|
|
|
mac_selinux_create_file_prepare(d, st.st_mode);
|
|
|
|
r = mknod(dn, st.st_mode, st.st_rdev);
|
|
|
|
mac_selinux_create_file_clear();
|
2018-08-02 17:43:49 +02:00
|
|
|
if (r >= 0)
|
|
|
|
goto add_symlink;
|
2018-03-09 14:49:15 +01:00
|
|
|
if (errno != EPERM)
|
|
|
|
return log_debug_errno(errno, "mknod failed for %s: %m", d);
|
|
|
|
|
2018-08-02 17:43:49 +02:00
|
|
|
/* This didn't work, let's not try this again for the next iterations. */
|
2018-03-09 14:49:15 +01:00
|
|
|
*make_devnode = false;
|
|
|
|
}
|
|
|
|
|
2020-08-20 11:23:26 +02:00
|
|
|
/* We're about to fall back to bind-mounting the device
|
2020-02-04 18:07:07 +01:00
|
|
|
* node. So create a dummy bind-mount target.
|
|
|
|
* Do not prepare device-node SELinux label (see issue 13762) */
|
2018-03-09 14:49:15 +01:00
|
|
|
r = mknod(dn, S_IFREG, 0);
|
|
|
|
if (r < 0 && errno != EEXIST)
|
2018-08-02 17:43:49 +02:00
|
|
|
return log_debug_errno(errno, "mknod() fallback failed for '%s': %m", d);
|
2018-03-09 14:49:15 +01:00
|
|
|
|
2020-09-23 10:12:56 +02:00
|
|
|
/* Fallback to bind-mounting: The assumption here is that all used device nodes carry standard
|
|
|
|
* properties. Specifically, the devices nodes we bind-mount should either be owned by root:root or
|
|
|
|
* root:tty (e.g. /dev/tty, /dev/ptmx) and should not carry ACLs. */
|
|
|
|
r = mount_nofollow_verbose(LOG_DEBUG, d, dn, NULL, MS_BIND, NULL);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
2018-08-02 17:43:49 +02:00
|
|
|
|
|
|
|
add_symlink:
|
|
|
|
bn = path_startswith(d, "/dev/");
|
|
|
|
if (!bn)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* Create symlinks like /dev/char/1:9 → ../urandom */
|
2020-06-27 13:23:08 +02:00
|
|
|
if (asprintf(&sl, "%s/dev/%s/%u:%u",
|
|
|
|
temporary_mount,
|
|
|
|
S_ISCHR(st.st_mode) ? "char" : "block",
|
|
|
|
major(st.st_rdev), minor(st.st_rdev)) < 0)
|
2018-08-02 17:43:49 +02:00
|
|
|
return log_oom();
|
|
|
|
|
|
|
|
(void) mkdir_parents(sl, 0755);
|
|
|
|
|
|
|
|
t = strjoina("../", bn);
|
|
|
|
if (symlink(t, sl) < 0)
|
2018-09-03 17:31:05 +02:00
|
|
|
log_debug_errno(errno, "Failed to symlink '%s' to '%s', ignoring: %m", t, sl);
|
2018-01-16 21:27:51 +01:00
|
|
|
|
2018-04-12 17:48:22 +02:00
|
|
|
return 0;
|
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;
|
2018-03-09 14:49:15 +01:00
|
|
|
bool can_mknod = true;
|
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))
|
2018-09-03 17:31:05 +02:00
|
|
|
return log_debug_errno(errno, "Failed to create temporary directory '%s': %m", temporary_mount);
|
2014-03-19 16:23:32 +01:00
|
|
|
|
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);
|
2020-09-23 10:12:56 +02:00
|
|
|
r = mount_nofollow_verbose(LOG_DEBUG, "tmpfs", dev, "tmpfs", DEV_MOUNT_OPTIONS, "mode=755" TMPFS_LIMITS_DEV);
|
|
|
|
if (r < 0)
|
2014-03-19 16:23:32 +01:00
|
|
|
goto fail;
|
2020-09-23 10:12:56 +02:00
|
|
|
|
2020-02-18 12:18:39 +01:00
|
|
|
r = label_fix_container(dev, "/dev", 0);
|
|
|
|
if (r < 0) {
|
|
|
|
log_debug_errno(errno, "Failed to fix label of '%s' as /dev: %m", dev);
|
|
|
|
goto fail;
|
|
|
|
}
|
2014-03-19 16:23:32 +01:00
|
|
|
|
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);
|
2020-09-23 10:12:56 +02:00
|
|
|
r = mount_nofollow_verbose(LOG_DEBUG, "/dev/pts", devpts, NULL, MS_BIND, NULL);
|
|
|
|
if (r < 0)
|
2014-03-19 16:23:32 +01:00
|
|
|
goto fail;
|
|
|
|
|
2018-09-03 17:31:05 +02: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");
|
2018-09-03 17:31:05 +02:00
|
|
|
if (r < 0) {
|
|
|
|
log_debug_errno(r, "Failed to detect whether /dev/ptmx is a symlink or not: %m");
|
2015-03-14 03:20:47 +01:00
|
|
|
goto fail;
|
2018-09-03 17:31:05 +02:00
|
|
|
} else if (r > 0) {
|
2018-01-16 21:50:36 +01:00
|
|
|
devptmx = strjoina(temporary_mount, "/dev/ptmx");
|
|
|
|
if (symlink("pts/ptmx", devptmx) < 0) {
|
2018-09-03 17:31:05 +02:00
|
|
|
r = log_debug_errno(errno, "Failed to create a symlink '%s' to pts/ptmx: %m", devptmx);
|
2018-01-16 21:50:36 +01:00
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
} else {
|
2018-03-09 14:49:15 +01:00
|
|
|
r = clone_device_node("/dev/ptmx", temporary_mount, &can_mknod);
|
2018-01-23 19:37:59 +01:00
|
|
|
if (r < 0)
|
|
|
|
goto fail;
|
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);
|
2020-09-23 10:12:56 +02:00
|
|
|
r = mount_nofollow_verbose(LOG_DEBUG, "/dev/shm", devshm, NULL, MS_BIND, NULL);
|
|
|
|
if (r < 0)
|
2014-03-19 16:23:32 +01:00
|
|
|
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);
|
2020-09-23 10:12:56 +02:00
|
|
|
(void) mount_nofollow_verbose(LOG_DEBUG, "/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);
|
2020-09-23 10:12:56 +02:00
|
|
|
(void) mount_nofollow_verbose(LOG_DEBUG, "/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");
|
2018-09-03 17:31:05 +02:00
|
|
|
if (symlink("/run/systemd/journal/dev-log", devlog) < 0)
|
|
|
|
log_debug_errno(errno, "Failed to create a symlink '%s' to /run/systemd/journal/dev-log, ignoring: %m", devlog);
|
2014-06-04 16:59:13 +02:00
|
|
|
|
2014-01-20 19:54:51 +01:00
|
|
|
NULSTR_FOREACH(d, devnodes) {
|
2018-03-09 14:49:15 +01:00
|
|
|
r = clone_device_node(d, temporary_mount, &can_mknod);
|
2020-06-30 15:24:57 +02:00
|
|
|
/* ENXIO means the *source* is not a device file, skip creation in that case */
|
2018-04-12 17:48:22 +02:00
|
|
|
if (r < 0 && r != -ENXIO)
|
2014-03-19 16:23:32 +01:00
|
|
|
goto fail;
|
2014-01-20 19:54:51 +01:00
|
|
|
}
|
|
|
|
|
2018-09-03 17:31:05 +02:00
|
|
|
r = dev_setup(temporary_mount, UID_INVALID, GID_INVALID);
|
|
|
|
if (r < 0)
|
2020-03-03 15:00:53 +01:00
|
|
|
log_debug_errno(r, "Failed to set up basic device tree at '%s', ignoring: %m", temporary_mount);
|
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 */
|
2018-09-03 17:31:05 +02:00
|
|
|
r = umount_recursive(mount_entry_path(m), 0);
|
|
|
|
if (r < 0)
|
|
|
|
log_debug_errno(r, "Failed to unmount directories below '%s', ignoring: %m", mount_entry_path(m));
|
|
|
|
|
2020-09-23 10:12:56 +02:00
|
|
|
r = mount_nofollow_verbose(LOG_DEBUG, dev, mount_entry_path(m), NULL, MS_MOVE, NULL);
|
|
|
|
if (r < 0)
|
2014-03-19 16:23:32 +01:00
|
|
|
goto fail;
|
2014-01-20 19:54:51 +01:00
|
|
|
|
2019-03-25 16:38:33 +01:00
|
|
|
(void) rmdir(dev);
|
|
|
|
(void) 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)
|
2020-09-23 10:12:56 +02:00
|
|
|
(void) umount_verbose(LOG_DEBUG, devpts, UMOUNT_NOFOLLOW);
|
2014-01-20 19:54:51 +01:00
|
|
|
|
2014-03-19 16:23:32 +01:00
|
|
|
if (devshm)
|
2020-09-23 10:12:56 +02:00
|
|
|
(void) umount_verbose(LOG_DEBUG, devshm, UMOUNT_NOFOLLOW);
|
2014-01-20 19:54:51 +01:00
|
|
|
|
2014-03-19 16:23:32 +01:00
|
|
|
if (devhugepages)
|
2020-09-23 10:12:56 +02:00
|
|
|
(void) umount_verbose(LOG_DEBUG, devhugepages, UMOUNT_NOFOLLOW);
|
2014-01-20 19:54:51 +01:00
|
|
|
|
2014-03-19 16:23:32 +01:00
|
|
|
if (devmqueue)
|
2020-09-23 10:12:56 +02:00
|
|
|
(void) umount_verbose(LOG_DEBUG, devmqueue, UMOUNT_NOFOLLOW);
|
2014-01-20 19:54:51 +01:00
|
|
|
|
2020-09-23 10:12:56 +02:00
|
|
|
(void) umount_verbose(LOG_DEBUG, dev, UMOUNT_NOFOLLOW);
|
2019-03-25 16:38:33 +01:00
|
|
|
(void) rmdir(dev);
|
|
|
|
(void) 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
|
|
|
}
|
|
|
|
|
2018-02-10 11:24:57 +01:00
|
|
|
static int mount_bind_dev(const MountEntry *m) {
|
2016-12-22 23:34:35 +01:00
|
|
|
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;
|
|
|
|
|
2020-09-23 10:12:56 +02:00
|
|
|
r = mount_nofollow_verbose(LOG_DEBUG, "/dev", mount_entry_path(m), NULL, MS_BIND|MS_REC, NULL);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
2016-12-22 23:34:35 +01:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2018-02-10 11:24:57 +01:00
|
|
|
static int mount_sysfs(const MountEntry *m) {
|
2016-12-22 23:34:35 +01:00
|
|
|
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. */
|
2020-09-23 10:12:56 +02:00
|
|
|
r = mount_nofollow_verbose(LOG_DEBUG, "/sys", mount_entry_path(m), NULL, MS_BIND|MS_REC, NULL);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
2016-12-22 23:34:35 +01:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2020-08-06 12:51:50 +02:00
|
|
|
static int mount_procfs(const MountEntry *m, const NamespaceInfo *ns_info) {
|
2020-12-06 14:29:43 +01:00
|
|
|
_cleanup_free_ char *opts = NULL;
|
2020-08-06 12:51:50 +02:00
|
|
|
const char *entry_path;
|
2020-12-06 14:29:43 +01:00
|
|
|
int r, n;
|
2016-12-22 23:34:35 +01:00
|
|
|
|
|
|
|
assert(m);
|
2020-08-06 12:51:50 +02:00
|
|
|
assert(ns_info);
|
2016-12-22 23:34:35 +01:00
|
|
|
|
2020-08-06 12:51:50 +02:00
|
|
|
if (ns_info->protect_proc != PROTECT_PROC_DEFAULT ||
|
|
|
|
ns_info->proc_subset != PROC_SUBSET_ALL) {
|
|
|
|
|
|
|
|
/* Starting with kernel 5.8 procfs' hidepid= logic is truly per-instance (previously it
|
|
|
|
* pretended to be per-instance but actually was per-namespace), hence let's make use of it
|
|
|
|
* if requested. To make sure this logic succeeds only on kernels where hidepid= is
|
|
|
|
* per-instance, we'll exclusively use the textual value for hidepid=, since support was
|
|
|
|
* added in the same commit: if it's supported it is thus also per-instance. */
|
|
|
|
|
|
|
|
opts = strjoin("hidepid=",
|
|
|
|
ns_info->protect_proc == PROTECT_PROC_DEFAULT ? "off" :
|
|
|
|
protect_proc_to_string(ns_info->protect_proc),
|
|
|
|
ns_info->proc_subset == PROC_SUBSET_PID ? ",subset=pid" : "");
|
|
|
|
if (!opts)
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2020-12-06 14:29:43 +01:00
|
|
|
entry_path = mount_entry_path(m);
|
|
|
|
(void) mkdir_p_label(entry_path, 0755);
|
|
|
|
|
|
|
|
/* Mount a new instance, so that we get the one that matches our user namespace, if we are running in
|
|
|
|
* one. i.e we don't reuse existing mounts here under any condition, we want a new instance owned by
|
|
|
|
* our user namespace and with our hidepid= settings applied. Hence, let's get rid of everything
|
|
|
|
* mounted on /proc/ first. */
|
|
|
|
|
|
|
|
n = umount_recursive(entry_path, 0);
|
|
|
|
|
|
|
|
r = mount_nofollow_verbose(LOG_DEBUG, "proc", entry_path, "proc", MS_NOSUID|MS_NOEXEC|MS_NODEV, opts);
|
|
|
|
if (r == -EINVAL && opts)
|
|
|
|
/* If this failed with EINVAL then this likely means the textual hidepid= stuff is
|
|
|
|
* not supported by the kernel, and thus the per-instance hidepid= neither, which
|
|
|
|
* means we really don't want to use it, since it would affect our host's /proc
|
|
|
|
* mount. Hence let's gracefully fallback to a classic, unrestricted version. */
|
|
|
|
r = mount_nofollow_verbose(LOG_DEBUG, "proc", entry_path, "proc", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL);
|
|
|
|
if (r == -EPERM) {
|
|
|
|
/* When we do not have enough priviledge to mount /proc, fallback to use existing /proc. */
|
|
|
|
|
|
|
|
if (n > 0)
|
|
|
|
/* /proc or some of sub-mounts are umounted in the above. Refuse incomplete tree.
|
|
|
|
* Propagate the original error code returned by mount() in the above. */
|
|
|
|
return -EPERM;
|
|
|
|
|
|
|
|
r = path_is_mount_point(entry_path, NULL, 0);
|
|
|
|
if (r < 0)
|
|
|
|
return log_debug_errno(r, "Unable to determine whether /proc is already mounted: %m");
|
|
|
|
if (r == 0)
|
|
|
|
/* /proc is not mounted. Propagate the original error code. */
|
|
|
|
return -EPERM;
|
2020-12-14 16:55:57 +01:00
|
|
|
} else if (r < 0)
|
|
|
|
return r;
|
2016-12-22 23:34:35 +01:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2018-02-21 01:17:52 +01:00
|
|
|
static int mount_tmpfs(const MountEntry *m) {
|
2020-08-06 12:49:42 +02:00
|
|
|
const char *entry_path, *inner_path;
|
2020-07-10 22:08:50 +02:00
|
|
|
int r;
|
|
|
|
|
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
|
|
|
assert(m);
|
|
|
|
|
2020-08-06 12:49:42 +02:00
|
|
|
entry_path = mount_entry_path(m);
|
|
|
|
inner_path = m->path_const;
|
|
|
|
|
2018-02-21 01:17:52 +01:00
|
|
|
/* First, get rid of everything that is below if there is anything. Then, overmount with our new tmpfs */
|
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
|
|
|
|
2020-07-10 22:08:50 +02:00
|
|
|
(void) mkdir_p_label(entry_path, 0755);
|
|
|
|
(void) umount_recursive(entry_path, 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
|
|
|
|
2020-09-23 10:12:56 +02:00
|
|
|
r = mount_nofollow_verbose(LOG_DEBUG, "tmpfs", entry_path, "tmpfs", m->flags, mount_entry_options(m));
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
2020-07-10 22:08:50 +02:00
|
|
|
|
2020-08-06 12:49:42 +02:00
|
|
|
r = label_fix_container(entry_path, inner_path, 0);
|
2020-07-10 22:08:50 +02:00
|
|
|
if (r < 0)
|
2020-08-06 12:49:42 +02:00
|
|
|
return log_debug_errno(r, "Failed to fix label of '%s' as '%s': %m", entry_path, inner_path);
|
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
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2020-07-14 17:18:41 +02:00
|
|
|
static int mount_images(const MountEntry *m) {
|
|
|
|
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
|
|
|
|
_cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL;
|
|
|
|
_cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL;
|
2020-08-22 12:21:51 +02:00
|
|
|
_cleanup_(verity_settings_done) VeritySettings verity = VERITY_SETTINGS_DEFAULT;
|
2020-09-15 22:09:08 +02:00
|
|
|
DissectImageFlags dissect_image_flags;
|
2020-07-14 17:18:41 +02:00
|
|
|
int r;
|
|
|
|
|
2020-09-15 22:09:08 +02:00
|
|
|
assert(m);
|
|
|
|
|
|
|
|
r = verity_settings_load(&verity, mount_entry_source(m), NULL, NULL);
|
2020-07-14 17:18:41 +02:00
|
|
|
if (r < 0)
|
|
|
|
return log_debug_errno(r, "Failed to load root hash: %m");
|
|
|
|
|
2020-09-15 22:09:08 +02:00
|
|
|
dissect_image_flags =
|
|
|
|
(m->read_only ? DISSECT_IMAGE_READ_ONLY : 0) |
|
|
|
|
(verity.data_path ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0);
|
|
|
|
|
|
|
|
r = loop_device_make_by_path(
|
|
|
|
mount_entry_source(m),
|
|
|
|
m->read_only ? O_RDONLY : -1 /* < 0 means writable if possible, read-only as fallback */,
|
|
|
|
verity.data_path ? 0 : LO_FLAGS_PARTSCAN,
|
|
|
|
&loop_device);
|
2020-07-14 17:18:41 +02:00
|
|
|
if (r < 0)
|
|
|
|
return log_debug_errno(r, "Failed to create loop device for image: %m");
|
|
|
|
|
2020-09-15 22:09:08 +02:00
|
|
|
r = dissect_image(
|
|
|
|
loop_device->fd,
|
|
|
|
&verity,
|
|
|
|
m->image_options,
|
|
|
|
dissect_image_flags,
|
|
|
|
&dissected_image);
|
2020-07-14 17:18:41 +02:00
|
|
|
/* No partition table? Might be a single-filesystem image, try again */
|
2020-09-15 22:09:08 +02:00
|
|
|
if (!verity.data_path && r == -ENOPKG)
|
|
|
|
r = dissect_image(
|
|
|
|
loop_device->fd,
|
|
|
|
&verity,
|
|
|
|
m->image_options,
|
|
|
|
dissect_image_flags|DISSECT_IMAGE_NO_PARTITION_TABLE,
|
|
|
|
&dissected_image);
|
2020-07-14 17:18:41 +02:00
|
|
|
if (r < 0)
|
|
|
|
return log_debug_errno(r, "Failed to dissect image: %m");
|
|
|
|
|
2020-09-15 22:09:08 +02:00
|
|
|
r = dissected_image_decrypt(
|
|
|
|
dissected_image,
|
|
|
|
NULL,
|
|
|
|
&verity,
|
|
|
|
dissect_image_flags,
|
|
|
|
&decrypted_image);
|
2020-07-14 17:18:41 +02:00
|
|
|
if (r < 0)
|
|
|
|
return log_debug_errno(r, "Failed to decrypt dissected image: %m");
|
|
|
|
|
|
|
|
r = mkdir_p_label(mount_entry_path(m), 0755);
|
|
|
|
if (r < 0)
|
|
|
|
return log_debug_errno(r, "Failed to create destination directory %s: %m", mount_entry_path(m));
|
|
|
|
r = umount_recursive(mount_entry_path(m), 0);
|
|
|
|
if (r < 0)
|
|
|
|
return log_debug_errno(r, "Failed to umount under destination directory %s: %m", mount_entry_path(m));
|
|
|
|
|
|
|
|
r = dissected_image_mount(dissected_image, mount_entry_path(m), UID_INVALID, dissect_image_flags);
|
|
|
|
if (r < 0)
|
|
|
|
return log_debug_errno(r, "Failed to mount image: %m");
|
|
|
|
|
|
|
|
if (decrypted_image) {
|
|
|
|
r = decrypted_image_relinquish(decrypted_image);
|
|
|
|
if (r < 0)
|
|
|
|
return log_debug_errno(r, "Failed to relinquish decrypted image: %m");
|
|
|
|
}
|
|
|
|
|
|
|
|
loop_device_relinquish(loop_device);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
static int follow_symlink(
|
2016-11-23 22:21:40 +01:00
|
|
|
const char *root_directory,
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
MountEntry *m) {
|
2016-11-23 22:21:40 +01:00
|
|
|
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
_cleanup_free_ char *target = NULL;
|
2016-12-14 00:51:37 +01:00
|
|
|
int r;
|
|
|
|
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
/* Let's chase symlinks, but only one step at a time. That's because depending where the symlink points we
|
|
|
|
* might need to change the order in which we mount stuff. Hence: let's normalize piecemeal, and do one step at
|
|
|
|
* a time by specifying CHASE_STEP. This function returns 0 if we resolved one step, and > 0 if we reached the
|
|
|
|
* end and already have a fully normalized name. */
|
2016-12-14 00:51:37 +01:00
|
|
|
|
2019-10-24 10:33:20 +02:00
|
|
|
r = chase_symlinks(mount_entry_path(m), root_directory, CHASE_STEP|CHASE_NONEXISTENT, &target, NULL);
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
if (r < 0)
|
|
|
|
return log_debug_errno(r, "Failed to chase symlinks '%s': %m", mount_entry_path(m));
|
|
|
|
if (r > 0) /* Reached the end, nothing more to resolve */
|
|
|
|
return 1;
|
2016-12-14 00:51:37 +01:00
|
|
|
|
2018-11-20 23:40:44 +01:00
|
|
|
if (m->n_followed >= CHASE_SYMLINKS_MAX) /* put a boundary on things */
|
|
|
|
return log_debug_errno(SYNTHETIC_ERRNO(ELOOP),
|
|
|
|
"Symlink loop on '%s'.",
|
|
|
|
mount_entry_path(m));
|
2016-12-14 00:51:37 +01:00
|
|
|
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
log_debug("Followed mount entry path symlink %s → %s.", mount_entry_path(m), target);
|
2016-12-14 00:51:37 +01:00
|
|
|
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
free_and_replace(m->path_malloc, target);
|
|
|
|
m->has_prefix = true;
|
2016-12-14 00:51:37 +01:00
|
|
|
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
m->n_followed ++;
|
|
|
|
|
|
|
|
return 0;
|
2016-12-14 00:51:37 +01:00
|
|
|
}
|
|
|
|
|
2012-08-13 15:27:04 +02:00
|
|
|
static int apply_mount(
|
2016-12-14 00:51:37 +01:00
|
|
|
const char *root_directory,
|
2020-08-06 12:51:50 +02:00
|
|
|
MountEntry *m,
|
|
|
|
const NamespaceInfo *ns_info) {
|
2012-08-13 15:27:04 +02:00
|
|
|
|
2019-11-19 23:24:52 +01:00
|
|
|
_cleanup_free_ char *inaccessible = NULL;
|
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);
|
2020-08-06 12:51:50 +02:00
|
|
|
assert(ns_info);
|
2010-04-21 22:15:06 +02:00
|
|
|
|
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: {
|
2019-11-19 23:24:52 +01:00
|
|
|
_cleanup_free_ char *tmp = NULL;
|
|
|
|
const char *runtime_dir;
|
2016-08-25 15:51:37 +02:00
|
|
|
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
|
|
|
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
if (lstat(mount_entry_path(m), &target) < 0) {
|
|
|
|
if (errno == ENOENT && m->ignore)
|
|
|
|
return 0;
|
|
|
|
|
2020-06-27 13:23:08 +02:00
|
|
|
return log_debug_errno(errno, "Failed to lstat() %s to determine what to mount over it: %m",
|
|
|
|
mount_entry_path(m));
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
}
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2019-11-19 23:24:52 +01:00
|
|
|
if (geteuid() == 0)
|
2020-06-09 16:22:24 +02:00
|
|
|
runtime_dir = "/run";
|
2019-11-19 23:24:52 +01:00
|
|
|
else {
|
2020-06-09 16:22:24 +02:00
|
|
|
if (asprintf(&tmp, "/run/user/" UID_FMT, geteuid()) < 0)
|
|
|
|
return -ENOMEM;
|
2019-11-19 23:24:52 +01:00
|
|
|
|
|
|
|
runtime_dir = tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
r = mode_to_inaccessible_node(runtime_dir, target.st_mode, &inaccessible);
|
|
|
|
if (r < 0)
|
2018-11-20 23:40:44 +01:00
|
|
|
return log_debug_errno(SYNTHETIC_ERRNO(ELOOP),
|
|
|
|
"File type not supported for inaccessible mounts. Note that symlinks are not allowed");
|
2019-11-19 23:24:52 +01:00
|
|
|
what = inaccessible;
|
2016-07-06 09:48:58 +02:00
|
|
|
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:
|
2018-12-28 08:11:52 +01:00
|
|
|
case READWRITE_IMPLICIT:
|
2016-12-14 00:51:37 +01:00
|
|
|
r = path_is_mount_point(mount_entry_path(m), root_directory, 0);
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
if (r == -ENOENT && m->ignore)
|
|
|
|
return 0;
|
2016-09-24 12:41:30 +02:00
|
|
|
if (r < 0)
|
2020-06-27 13:23:08 +02:00
|
|
|
return log_debug_errno(r, "Failed to determine whether %s is already a mount point: %m",
|
|
|
|
mount_entry_path(m));
|
|
|
|
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. */
|
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;
|
|
|
|
/* 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_;
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
case BIND_MOUNT_RECURSIVE: {
|
|
|
|
_cleanup_free_ char *chased = NULL;
|
2016-12-22 23:34:35 +01:00
|
|
|
|
2020-06-27 13:23:08 +02:00
|
|
|
/* Since mount() will always follow symlinks we chase the symlinks on our own first. Note
|
|
|
|
* that bind mount source paths are always relative to the host root, hence we pass NULL as
|
|
|
|
* root directory to chase_symlinks() here. */
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
|
2019-10-24 10:33:20 +02:00
|
|
|
r = chase_symlinks(mount_entry_source(m), NULL, CHASE_TRAIL_SLASH, &chased, NULL);
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
if (r == -ENOENT && m->ignore) {
|
|
|
|
log_debug_errno(r, "Path %s does not exist, ignoring.", mount_entry_source(m));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (r < 0)
|
|
|
|
return log_debug_errno(r, "Failed to follow symlinks on %s: %m", mount_entry_source(m));
|
|
|
|
|
|
|
|
log_debug("Followed source symlinks %s → %s.", mount_entry_source(m), chased);
|
|
|
|
|
|
|
|
free_and_replace(m->source_malloc, chased);
|
2016-11-23 22:21:40 +01:00
|
|
|
|
|
|
|
what = mount_entry_source(m);
|
2017-09-28 18:35:51 +02:00
|
|
|
make = true;
|
2016-11-23 22:21:40 +01:00
|
|
|
break;
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
}
|
2016-11-23 22:21:40 +01: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
|
|
|
case EMPTY_DIR:
|
2018-02-21 01:17:52 +01:00
|
|
|
case TMPFS:
|
|
|
|
return mount_tmpfs(m);
|
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
|
|
|
|
2012-08-13 15:27:04 +02:00
|
|
|
case PRIVATE_TMP:
|
2020-06-28 19:54:49 +02:00
|
|
|
case PRIVATE_TMP_READONLY:
|
2018-02-19 07:19:41 +01:00
|
|
|
what = mount_entry_source(m);
|
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:
|
2020-08-06 12:51:50 +02:00
|
|
|
return mount_procfs(m, ns_info);
|
2014-06-06 11:42:25 +02:00
|
|
|
|
2020-07-14 17:18:41 +02:00
|
|
|
case MOUNT_IMAGES:
|
|
|
|
return mount_images(m);
|
|
|
|
|
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
|
|
|
|
2020-09-23 10:12:56 +02:00
|
|
|
r = mount_nofollow_verbose(LOG_DEBUG, what, mount_entry_path(m), NULL, MS_BIND|(rbind ? MS_REC : 0), NULL);
|
|
|
|
if (r < 0) {
|
2017-09-28 18:35:51 +02:00
|
|
|
bool try_again = false;
|
|
|
|
|
|
|
|
if (r == -ENOENT && make) {
|
|
|
|
struct stat st;
|
|
|
|
|
2020-06-27 13:23:08 +02:00
|
|
|
/* Hmm, either the source or the destination are missing. Let's see if we can create
|
|
|
|
the destination, then try again. */
|
2017-09-28 18:35:51 +02:00
|
|
|
|
2018-04-04 11:12:49 +02:00
|
|
|
if (stat(what, &st) < 0)
|
2020-06-26 07:37:39 +02:00
|
|
|
log_error_errno(errno, "Mount point source '%s' is not accessible: %m", what);
|
2018-04-04 11:12:49 +02:00
|
|
|
else {
|
|
|
|
int q;
|
2017-09-28 18:35:51 +02:00
|
|
|
|
|
|
|
(void) mkdir_parents(mount_entry_path(m), 0755);
|
|
|
|
|
|
|
|
if (S_ISDIR(st.st_mode))
|
2018-04-04 11:12:49 +02:00
|
|
|
q = mkdir(mount_entry_path(m), 0755) < 0 ? -errno : 0;
|
2017-09-28 18:35:51 +02:00
|
|
|
else
|
2018-04-04 11:12:49 +02:00
|
|
|
q = touch(mount_entry_path(m));
|
|
|
|
|
|
|
|
if (q < 0)
|
2020-06-27 13:23:08 +02:00
|
|
|
log_error_errno(q, "Failed to create destination mount point node '%s': %m",
|
|
|
|
mount_entry_path(m));
|
2018-04-04 11:12:49 +02:00
|
|
|
else
|
|
|
|
try_again = true;
|
2017-09-28 18:35:51 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-23 10:12:56 +02:00
|
|
|
if (try_again)
|
|
|
|
r = mount_nofollow_verbose(LOG_DEBUG, what, mount_entry_path(m), NULL, MS_BIND|(rbind ? MS_REC : 0), NULL);
|
2017-09-28 18:35:51 +02:00
|
|
|
if (r < 0)
|
2020-06-26 07:37:39 +02:00
|
|
|
return log_error_errno(r, "Failed to mount %s to %s: %m", what, mount_entry_path(m));
|
2017-09-28 18:35:51 +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
|
|
|
|
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
|
|
|
|
2020-06-23 08:31:16 +02:00
|
|
|
static int make_read_only(const MountEntry *m, char **deny_list, FILE *proc_self_mountinfo) {
|
2019-03-25 19:29:26 +01:00
|
|
|
unsigned long new_flags = 0, flags_mask = 0;
|
2018-08-10 06:50:54 +02:00
|
|
|
bool submounts = false;
|
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
|
|
|
|
2019-03-25 19:29:26 +01:00
|
|
|
if (mount_entry_read_only(m) || m->mode == PRIVATE_DEV) {
|
|
|
|
new_flags |= MS_RDONLY;
|
|
|
|
flags_mask |= MS_RDONLY;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m->nosuid) {
|
|
|
|
new_flags |= MS_NOSUID;
|
|
|
|
flags_mask |= MS_NOSUID;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (flags_mask == 0) /* No Change? */
|
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;
|
|
|
|
|
2019-03-25 19:29:26 +01:00
|
|
|
/* We generally apply these changes recursively, except for /dev, and the cases we know there's
|
|
|
|
* nothing further down. Set /dev readonly, but not submounts like /dev/shm. Also, we only set the
|
|
|
|
* per-mount read-only flag. We can't set it on the superblock, if we are inside a user namespace
|
|
|
|
* and running Linux <= 4.17. */
|
|
|
|
submounts =
|
|
|
|
mount_entry_read_only(m) &&
|
|
|
|
!IN_SET(m->mode, EMPTY_DIR, TMPFS);
|
|
|
|
if (submounts)
|
2020-06-23 08:31:16 +02:00
|
|
|
r = bind_remount_recursive_with_mountinfo(mount_entry_path(m), new_flags, flags_mask, deny_list, proc_self_mountinfo);
|
2019-03-25 19:29:26 +01:00
|
|
|
else
|
2020-01-09 15:06:06 +01:00
|
|
|
r = bind_remount_one_with_mountinfo(mount_entry_path(m), new_flags, flags_mask, proc_self_mountinfo);
|
2019-03-25 19:29:26 +01:00
|
|
|
|
2019-03-25 16:50:21 +01:00
|
|
|
/* 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)
|
2019-03-25 16:50:21 +01:00
|
|
|
return 0;
|
2018-08-10 06:50:54 +02:00
|
|
|
if (r < 0)
|
2019-03-25 19:29:26 +01:00
|
|
|
return log_debug_errno(r, "Failed to re-mount '%s'%s: %m", mount_entry_path(m),
|
2018-08-10 06:50:54 +02:00
|
|
|
submounts ? " and its submounts" : "");
|
|
|
|
return 0;
|
2016-09-24 12:41:30 +02:00
|
|
|
}
|
|
|
|
|
2018-07-28 17:38:36 +02:00
|
|
|
static bool namespace_info_mount_apivfs(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
|
2018-07-28 17:38:36 +02:00
|
|
|
* first place...
|
2017-03-05 21:39:43 +01:00
|
|
|
*/
|
2016-12-22 23:34:35 +01:00
|
|
|
|
2018-07-28 17:38:36 +02:00
|
|
|
return ns_info->mount_apivfs ||
|
|
|
|
ns_info->protect_control_groups ||
|
2020-08-06 12:51:50 +02:00
|
|
|
ns_info->protect_kernel_tunables ||
|
|
|
|
ns_info->protect_proc != PROTECT_PROC_DEFAULT ||
|
|
|
|
ns_info->proc_subset != PROC_SUBSET_ALL;
|
2016-12-22 23:34:35 +01:00
|
|
|
}
|
|
|
|
|
tree-wide: be more careful with the type of array sizes
Previously we were a bit sloppy with the index and size types of arrays,
we'd regularly use unsigned. While I don't think this ever resulted in
real issues I think we should be more careful there and follow a
stricter regime: unless there's a strong reason not to use size_t for
array sizes and indexes, size_t it should be. Any allocations we do
ultimately will use size_t anyway, and converting forth and back between
unsigned and size_t will always be a source of problems.
Note that on 32bit machines "unsigned" and "size_t" are equivalent, and
on 64bit machines our arrays shouldn't grow that large anyway, and if
they do we have a problem, however that kind of overly large allocation
we have protections for usually, but for overflows we do not have that
so much, hence let's add it.
So yeah, it's a story of the current code being already "good enough",
but I think some extra type hygiene is better.
This patch tries to be comprehensive, but it probably isn't and I missed
a few cases. But I guess we can cover that later as we notice it. Among
smaller fixes, this changes:
1. strv_length()' return type becomes size_t
2. the unit file changes array size becomes size_t
3. DNS answer and query array sizes become size_t
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=76745
2018-04-27 14:09:31 +02:00
|
|
|
static size_t namespace_calculate_mounts(
|
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,
|
tree-wide: be more careful with the type of array sizes
Previously we were a bit sloppy with the index and size types of arrays,
we'd regularly use unsigned. While I don't think this ever resulted in
real issues I think we should be more careful there and follow a
stricter regime: unless there's a strong reason not to use size_t for
array sizes and indexes, size_t it should be. Any allocations we do
ultimately will use size_t anyway, and converting forth and back between
unsigned and size_t will always be a source of problems.
Note that on 32bit machines "unsigned" and "size_t" are equivalent, and
on 64bit machines our arrays shouldn't grow that large anyway, and if
they do we have a problem, however that kind of overly large allocation
we have protections for usually, but for overflows we do not have that
so much, hence let's add it.
So yeah, it's a story of the current code being already "good enough",
but I think some extra type hygiene is better.
This patch tries to be comprehensive, but it probably isn't and I missed
a few cases. But I guess we can cover that later as we notice it. Among
smaller fixes, this changes:
1. strv_length()' return type becomes size_t
2. the unit file changes array size becomes size_t
3. DNS answer and query array sizes become size_t
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=76745
2018-04-27 14:09:31 +02:00
|
|
|
size_t n_bind_mounts,
|
|
|
|
size_t n_temporary_filesystems,
|
2020-07-14 17:18:41 +02:00
|
|
|
size_t n_mount_images,
|
2016-09-25 11:25:00 +02:00
|
|
|
const char* tmp_dir,
|
|
|
|
const char* var_tmp_dir,
|
2020-08-14 15:54:48 +02:00
|
|
|
const char *creds_path,
|
2020-08-06 11:32:53 +02:00
|
|
|
const char* log_namespace) {
|
2016-09-25 11:25:00 +02:00
|
|
|
|
tree-wide: be more careful with the type of array sizes
Previously we were a bit sloppy with the index and size types of arrays,
we'd regularly use unsigned. While I don't think this ever resulted in
real issues I think we should be more careful there and follow a
stricter regime: unless there's a strong reason not to use size_t for
array sizes and indexes, size_t it should be. Any allocations we do
ultimately will use size_t anyway, and converting forth and back between
unsigned and size_t will always be a source of problems.
Note that on 32bit machines "unsigned" and "size_t" are equivalent, and
on 64bit machines our arrays shouldn't grow that large anyway, and if
they do we have a problem, however that kind of overly large allocation
we have protections for usually, but for overflows we do not have that
so much, hence let's add it.
So yeah, it's a story of the current code being already "good enough",
but I think some extra type hygiene is better.
This patch tries to be comprehensive, but it probably isn't and I missed
a few cases. But I guess we can cover that later as we notice it. Among
smaller fixes, this changes:
1. strv_length()' return type becomes size_t
2. the unit file changes array size becomes size_t
3. DNS answer and query array sizes become size_t
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=76745
2018-04-27 14:09:31 +02:00
|
|
|
size_t protect_home_cnt;
|
|
|
|
size_t protect_system_cnt =
|
2020-08-06 11:32:53 +02:00
|
|
|
(ns_info->protect_system == PROTECT_SYSTEM_STRICT ?
|
2016-09-25 12:21:25 +02:00
|
|
|
ELEMENTSOF(protect_system_strict_table) :
|
2020-08-06 11:32:53 +02:00
|
|
|
((ns_info->protect_system == PROTECT_SYSTEM_FULL) ?
|
2016-09-25 12:21:25 +02:00
|
|
|
ELEMENTSOF(protect_system_full_table) :
|
2020-08-06 11:32:53 +02:00
|
|
|
((ns_info->protect_system == PROTECT_SYSTEM_YES) ?
|
2016-09-25 12:21:25 +02:00
|
|
|
ELEMENTSOF(protect_system_yes_table) : 0)));
|
|
|
|
|
2016-09-25 12:41:16 +02:00
|
|
|
protect_home_cnt =
|
2020-08-06 11:32:53 +02:00
|
|
|
(ns_info->protect_home == PROTECT_HOME_YES ?
|
2016-09-25 12:41:16 +02:00
|
|
|
ELEMENTSOF(protect_home_yes_table) :
|
2020-08-06 11:32:53 +02:00
|
|
|
((ns_info->protect_home == PROTECT_HOME_READ_ONLY) ?
|
2018-02-21 01:13:11 +01:00
|
|
|
ELEMENTSOF(protect_home_read_only_table) :
|
2020-08-06 11:32:53 +02:00
|
|
|
((ns_info->protect_home == PROTECT_HOME_TMPFS) ?
|
2018-02-21 01:13:11 +01:00
|
|
|
ELEMENTSOF(protect_home_tmpfs_table) : 0)));
|
2016-09-25 12:41:16 +02:00
|
|
|
|
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 +
|
2020-07-14 17:18:41 +02:00
|
|
|
n_mount_images +
|
2018-02-21 01:17:52 +01:00
|
|
|
n_temporary_filesystems +
|
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_kernel_modules ? ELEMENTSOF(protect_kernel_modules_table) : 0) +
|
2019-11-10 10:17:01 +01:00
|
|
|
(ns_info->protect_kernel_logs ? ELEMENTSOF(protect_kernel_logs_table) : 0) +
|
|
|
|
(ns_info->protect_control_groups ? 1 : 0) +
|
2016-12-22 23:34:35 +01:00
|
|
|
protect_home_cnt + protect_system_cnt +
|
2019-02-08 18:25:00 +01:00
|
|
|
(ns_info->protect_hostname ? 2 : 0) +
|
2019-11-25 16:22:45 +01:00
|
|
|
(namespace_info_mount_apivfs(ns_info) ? ELEMENTSOF(apivfs_table) : 0) +
|
2020-08-14 15:54:48 +02:00
|
|
|
(creds_path ? 2 : 1) +
|
2019-11-25 16:22:45 +01:00
|
|
|
!!log_namespace;
|
2016-09-25 11:25:00 +02:00
|
|
|
}
|
|
|
|
|
tree-wide: be more careful with the type of array sizes
Previously we were a bit sloppy with the index and size types of arrays,
we'd regularly use unsigned. While I don't think this ever resulted in
real issues I think we should be more careful there and follow a
stricter regime: unless there's a strong reason not to use size_t for
array sizes and indexes, size_t it should be. Any allocations we do
ultimately will use size_t anyway, and converting forth and back between
unsigned and size_t will always be a source of problems.
Note that on 32bit machines "unsigned" and "size_t" are equivalent, and
on 64bit machines our arrays shouldn't grow that large anyway, and if
they do we have a problem, however that kind of overly large allocation
we have protections for usually, but for overflows we do not have that
so much, hence let's add it.
So yeah, it's a story of the current code being already "good enough",
but I think some extra type hygiene is better.
This patch tries to be comprehensive, but it probably isn't and I missed
a few cases. But I guess we can cover that later as we notice it. Among
smaller fixes, this changes:
1. strv_length()' return type becomes size_t
2. the unit file changes array size becomes size_t
3. DNS answer and query array sizes become size_t
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=76745
2018-04-27 14:09:31 +02:00
|
|
|
static void normalize_mounts(const char *root_directory, MountEntry *mounts, size_t *n_mounts) {
|
2018-07-28 17:38:36 +02:00
|
|
|
assert(root_directory);
|
2018-04-04 11:03:21 +02:00
|
|
|
assert(n_mounts);
|
|
|
|
assert(mounts || *n_mounts == 0);
|
|
|
|
|
2018-09-18 01:39:24 +02:00
|
|
|
typesafe_qsort(mounts, *n_mounts, mount_path_compare);
|
2018-04-04 11:03:21 +02:00
|
|
|
|
|
|
|
drop_duplicates(mounts, n_mounts);
|
|
|
|
drop_outside_root(root_directory, mounts, n_mounts);
|
|
|
|
drop_inaccessible(mounts, n_mounts);
|
|
|
|
drop_nop(mounts, n_mounts);
|
|
|
|
}
|
|
|
|
|
2020-01-07 16:25:11 +01:00
|
|
|
static bool root_read_only(
|
|
|
|
char **read_only_paths,
|
|
|
|
ProtectSystem protect_system) {
|
|
|
|
|
|
|
|
/* Determine whether the root directory is going to be read-only given the configured settings. */
|
|
|
|
|
|
|
|
if (protect_system == PROTECT_SYSTEM_STRICT)
|
|
|
|
return true;
|
|
|
|
|
2020-03-10 15:43:10 +01:00
|
|
|
if (prefixed_path_strv_contains(read_only_paths, "/"))
|
2020-01-07 16:25:11 +01:00
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool home_read_only(
|
|
|
|
char** read_only_paths,
|
|
|
|
char** inaccessible_paths,
|
|
|
|
char** empty_directories,
|
|
|
|
const BindMount *bind_mounts,
|
|
|
|
size_t n_bind_mounts,
|
|
|
|
const TemporaryFileSystem *temporary_filesystems,
|
|
|
|
size_t n_temporary_filesystems,
|
|
|
|
ProtectHome protect_home) {
|
|
|
|
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
/* Determine whether the /home directory is going to be read-only given the configured settings. Yes,
|
|
|
|
* this is a bit sloppy, since we don't bother checking for cases where / is affected by multiple
|
|
|
|
* settings. */
|
|
|
|
|
|
|
|
if (protect_home != PROTECT_HOME_NO)
|
|
|
|
return true;
|
|
|
|
|
2020-03-10 15:43:10 +01:00
|
|
|
if (prefixed_path_strv_contains(read_only_paths, "/home") ||
|
|
|
|
prefixed_path_strv_contains(inaccessible_paths, "/home") ||
|
|
|
|
prefixed_path_strv_contains(empty_directories, "/home"))
|
2020-01-07 16:25:11 +01:00
|
|
|
return true;
|
|
|
|
|
|
|
|
for (i = 0; i < n_temporary_filesystems; i++)
|
|
|
|
if (path_equal(temporary_filesystems[i].path, "/home"))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
/* If /home is overmounted with some dir from the host it's not writable. */
|
|
|
|
for (i = 0; i < n_bind_mounts; i++)
|
|
|
|
if (path_equal(bind_mounts[i].destination, "/home"))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-09-15 22:09:08 +02:00
|
|
|
static int verity_settings_prepare(
|
|
|
|
VeritySettings *verity,
|
|
|
|
const char *root_image,
|
|
|
|
const void *root_hash,
|
|
|
|
size_t root_hash_size,
|
|
|
|
const char *root_hash_path,
|
|
|
|
const void *root_hash_sig,
|
|
|
|
size_t root_hash_sig_size,
|
|
|
|
const char *root_hash_sig_path,
|
|
|
|
const char *verity_data_path) {
|
|
|
|
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(verity);
|
|
|
|
|
|
|
|
if (root_hash) {
|
|
|
|
void *d;
|
|
|
|
|
|
|
|
d = memdup(root_hash, root_hash_size);
|
|
|
|
if (!d)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
free_and_replace(verity->root_hash, d);
|
|
|
|
verity->root_hash_size = root_hash_size;
|
2020-08-22 12:21:51 +02:00
|
|
|
verity->designator = PARTITION_ROOT;
|
2020-09-15 22:09:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (root_hash_sig) {
|
|
|
|
void *d;
|
|
|
|
|
|
|
|
d = memdup(root_hash_sig, root_hash_sig_size);
|
|
|
|
if (!d)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
free_and_replace(verity->root_hash_sig, d);
|
|
|
|
verity->root_hash_sig_size = root_hash_sig_size;
|
2020-08-22 12:21:51 +02:00
|
|
|
verity->designator = PARTITION_ROOT;
|
2020-09-15 22:09:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (verity_data_path) {
|
|
|
|
r = free_and_strdup(&verity->data_path, verity_data_path);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
r = verity_settings_load(
|
|
|
|
verity,
|
|
|
|
root_image,
|
|
|
|
root_hash_path,
|
|
|
|
root_hash_sig_path);
|
|
|
|
if (r < 0)
|
|
|
|
return log_debug_errno(r, "Failed to load root hash: %m");
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
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,
|
2020-06-29 14:19:31 +02:00
|
|
|
const MountOptions *root_image_options,
|
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,
|
tree-wide: be more careful with the type of array sizes
Previously we were a bit sloppy with the index and size types of arrays,
we'd regularly use unsigned. While I don't think this ever resulted in
real issues I think we should be more careful there and follow a
stricter regime: unless there's a strong reason not to use size_t for
array sizes and indexes, size_t it should be. Any allocations we do
ultimately will use size_t anyway, and converting forth and back between
unsigned and size_t will always be a source of problems.
Note that on 32bit machines "unsigned" and "size_t" are equivalent, and
on 64bit machines our arrays shouldn't grow that large anyway, and if
they do we have a problem, however that kind of overly large allocation
we have protections for usually, but for overflows we do not have that
so much, hence let's add it.
So yeah, it's a story of the current code being already "good enough",
but I think some extra type hygiene is better.
This patch tries to be comprehensive, but it probably isn't and I missed
a few cases. But I guess we can cover that later as we notice it. Among
smaller fixes, this changes:
1. strv_length()' return type becomes size_t
2. the unit file changes array size becomes size_t
3. DNS answer and query array sizes become size_t
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=76745
2018-04-27 14:09:31 +02:00
|
|
|
size_t n_bind_mounts,
|
2018-02-21 01:17:52 +01:00
|
|
|
const TemporaryFileSystem *temporary_filesystems,
|
tree-wide: be more careful with the type of array sizes
Previously we were a bit sloppy with the index and size types of arrays,
we'd regularly use unsigned. While I don't think this ever resulted in
real issues I think we should be more careful there and follow a
stricter regime: unless there's a strong reason not to use size_t for
array sizes and indexes, size_t it should be. Any allocations we do
ultimately will use size_t anyway, and converting forth and back between
unsigned and size_t will always be a source of problems.
Note that on 32bit machines "unsigned" and "size_t" are equivalent, and
on 64bit machines our arrays shouldn't grow that large anyway, and if
they do we have a problem, however that kind of overly large allocation
we have protections for usually, but for overflows we do not have that
so much, hence let's add it.
So yeah, it's a story of the current code being already "good enough",
but I think some extra type hygiene is better.
This patch tries to be comprehensive, but it probably isn't and I missed
a few cases. But I guess we can cover that later as we notice it. Among
smaller fixes, this changes:
1. strv_length()' return type becomes size_t
2. the unit file changes array size becomes size_t
3. DNS answer and query array sizes become size_t
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=76745
2018-04-27 14:09:31 +02:00
|
|
|
size_t n_temporary_filesystems,
|
2020-07-14 17:18:41 +02:00
|
|
|
const MountImage *mount_images,
|
|
|
|
size_t n_mount_images,
|
2014-10-17 13:48:55 +02:00
|
|
|
const char* tmp_dir,
|
|
|
|
const char* var_tmp_dir,
|
2020-08-14 15:54:48 +02:00
|
|
|
const char *creds_path,
|
2019-11-25 16:22:45 +01:00
|
|
|
const char *log_namespace,
|
2016-12-23 14:26:05 +01:00
|
|
|
unsigned long mount_flags,
|
2020-06-03 10:50:45 +02:00
|
|
|
const void *root_hash,
|
|
|
|
size_t root_hash_size,
|
|
|
|
const char *root_hash_path,
|
2020-06-08 15:02:55 +02:00
|
|
|
const void *root_hash_sig,
|
|
|
|
size_t root_hash_sig_size,
|
|
|
|
const char *root_hash_sig_path,
|
2020-09-15 22:09:08 +02:00
|
|
|
const char *verity_data_path,
|
2019-05-21 20:02:34 +02:00
|
|
|
DissectImageFlags dissect_image_flags,
|
|
|
|
char **error_path) {
|
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;
|
2020-08-22 12:21:51 +02:00
|
|
|
_cleanup_(verity_settings_done) VeritySettings verity = VERITY_SETTINGS_DEFAULT;
|
2019-03-25 16:34:48 +01:00
|
|
|
MountEntry *m = NULL, *mounts = NULL;
|
2017-10-13 14:22:25 +02:00
|
|
|
bool require_prefix = false;
|
2018-07-28 17:38:36 +02:00
|
|
|
const char *root;
|
2020-09-15 22:09:08 +02:00
|
|
|
size_t n_mounts;
|
|
|
|
int r;
|
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;
|
|
|
|
|
2020-01-07 16:25:11 +01:00
|
|
|
/* Make the whole image read-only if we can determine that we only access it in a read-only fashion. */
|
|
|
|
if (root_read_only(read_only_paths,
|
2020-08-06 11:32:53 +02:00
|
|
|
ns_info->protect_system) &&
|
2020-01-07 16:25:11 +01:00
|
|
|
home_read_only(read_only_paths, inaccessible_paths, empty_directories,
|
|
|
|
bind_mounts, n_bind_mounts, temporary_filesystems, n_temporary_filesystems,
|
2020-08-06 11:32:53 +02:00
|
|
|
ns_info->protect_home) &&
|
2018-04-04 10:14:25 +02:00
|
|
|
strv_isempty(read_write_paths))
|
2016-12-23 14:26:05 +01:00
|
|
|
dissect_image_flags |= DISSECT_IMAGE_READ_ONLY;
|
|
|
|
|
2020-09-15 22:09:08 +02:00
|
|
|
r = verity_settings_prepare(
|
|
|
|
&verity,
|
|
|
|
root_image,
|
|
|
|
root_hash, root_hash_size, root_hash_path,
|
|
|
|
root_hash_sig, root_hash_sig_size, root_hash_sig_path,
|
|
|
|
verity_data_path);
|
2016-12-23 14:26:05 +01:00
|
|
|
if (r < 0)
|
2020-09-15 22:09:08 +02:00
|
|
|
return r;
|
|
|
|
|
|
|
|
SET_FLAG(dissect_image_flags, DISSECT_IMAGE_NO_PARTITION_TABLE, verity.data_path);
|
2016-12-23 14:26:05 +01:00
|
|
|
|
2020-09-15 22:09:08 +02:00
|
|
|
r = loop_device_make_by_path(
|
|
|
|
root_image,
|
|
|
|
FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_READ_ONLY) ? O_RDONLY : -1 /* < 0 means writable if possible, read-only as fallback */,
|
|
|
|
FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN,
|
|
|
|
&loop_device);
|
2016-12-23 17:10:42 +01:00
|
|
|
if (r < 0)
|
2020-09-15 22:09:08 +02:00
|
|
|
return log_debug_errno(r, "Failed to create loop device for root image: %m");
|
|
|
|
|
|
|
|
r = dissect_image(
|
|
|
|
loop_device->fd,
|
|
|
|
&verity,
|
|
|
|
root_image_options,
|
|
|
|
dissect_image_flags,
|
|
|
|
&dissected_image);
|
2016-12-23 17:10:42 +01:00
|
|
|
if (r < 0)
|
2018-08-10 06:50:54 +02:00
|
|
|
return log_debug_errno(r, "Failed to dissect image: %m");
|
2016-12-23 17:10:42 +01:00
|
|
|
|
2020-09-15 22:09:08 +02:00
|
|
|
r = dissected_image_decrypt(
|
|
|
|
dissected_image,
|
|
|
|
NULL,
|
|
|
|
&verity,
|
|
|
|
dissect_image_flags,
|
|
|
|
&decrypted_image);
|
2016-12-23 14:26:05 +01:00
|
|
|
if (r < 0)
|
2018-08-10 06:50:54 +02:00
|
|
|
return log_debug_errno(r, "Failed to decrypt dissected image: %m");
|
2016-12-23 14:26:05 +01:00
|
|
|
}
|
|
|
|
|
2017-09-28 18:30:55 +02:00
|
|
|
if (root_directory)
|
|
|
|
root = root_directory;
|
2018-06-12 10:03:08 +02:00
|
|
|
else {
|
2020-12-04 00:08:21 +01:00
|
|
|
/* /run/systemd should have been created by PID 1 early on already, but in some cases, like
|
|
|
|
* when running tests (test-execute), it might not have been created yet so let's make sure
|
|
|
|
* we create it if it doesn't already exist. */
|
|
|
|
(void) mkdir_p_label("/run/systemd", 0755);
|
|
|
|
|
2018-06-12 10:03:08 +02:00
|
|
|
/* Always create the mount namespace in a temporary directory, instead of operating
|
|
|
|
* directly in the root. The temporary directory prevents any mounts from being
|
|
|
|
* potentially obscured my other mounts we already applied.
|
|
|
|
* 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. */
|
2017-09-28 18:30:55 +02:00
|
|
|
|
|
|
|
root = "/run/systemd/unit-root";
|
|
|
|
(void) mkdir_label(root, 0700);
|
2017-10-13 14:22:25 +02:00
|
|
|
require_prefix = true;
|
2018-06-12 10:03:08 +02:00
|
|
|
}
|
2017-09-28 18:30:55 +02:00
|
|
|
|
2016-11-23 01:09:14 +01:00
|
|
|
n_mounts = namespace_calculate_mounts(
|
|
|
|
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,
|
2018-02-13 06:21:13 +01:00
|
|
|
n_bind_mounts,
|
2018-02-21 01:17:52 +01:00
|
|
|
n_temporary_filesystems,
|
2020-07-14 17:18:41 +02:00
|
|
|
n_mount_images,
|
2016-11-23 01:09:14 +01:00
|
|
|
tmp_dir, var_tmp_dir,
|
2020-08-14 15:54:48 +02:00
|
|
|
creds_path,
|
2020-08-06 11:32:53 +02:00
|
|
|
log_namespace);
|
2013-11-27 20:23:18 +01:00
|
|
|
|
2016-11-06 22:51:49 +01:00
|
|
|
if (n_mounts > 0) {
|
2019-03-25 16:34:48 +01:00
|
|
|
m = mounts = new0(MountEntry, n_mounts);
|
|
|
|
if (!mounts)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
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;
|
|
|
|
|
2018-02-21 01:17:52 +01:00
|
|
|
r = append_tmpfs_mounts(&m, temporary_filesystems, n_temporary_filesystems);
|
|
|
|
if (r < 0)
|
|
|
|
goto finish;
|
|
|
|
|
2013-11-27 20:23:18 +01:00
|
|
|
if (tmp_dir) {
|
2020-06-28 19:54:49 +02:00
|
|
|
bool ro = streq(tmp_dir, RUN_SYSTEMD_EMPTY);
|
|
|
|
|
2016-12-14 00:48:52 +01:00
|
|
|
*(m++) = (MountEntry) {
|
2016-11-15 01:42:54 +01:00
|
|
|
.path_const = "/tmp",
|
2020-06-28 19:54:49 +02:00
|
|
|
.mode = ro ? PRIVATE_TMP_READONLY : PRIVATE_TMP,
|
2018-02-19 07:19:41 +01:00
|
|
|
.source_const = tmp_dir,
|
2016-11-15 01:42:54 +01:00
|
|
|
};
|
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) {
|
2020-06-28 19:54:49 +02:00
|
|
|
bool ro = streq(var_tmp_dir, RUN_SYSTEMD_EMPTY);
|
|
|
|
|
2016-12-14 00:48:52 +01:00
|
|
|
*(m++) = (MountEntry) {
|
2016-11-15 01:42:54 +01:00
|
|
|
.path_const = "/var/tmp",
|
2020-06-28 19:54:49 +02:00
|
|
|
.mode = ro ? PRIVATE_TMP_READONLY : PRIVATE_TMP,
|
2018-02-19 07:19:41 +01:00
|
|
|
.source_const = var_tmp_dir,
|
2016-11-15 01:42:54 +01:00
|
|
|
};
|
2013-10-12 01:33:13 +02:00
|
|
|
}
|
2012-08-13 15:27:04 +02:00
|
|
|
|
2020-07-14 17:18:41 +02:00
|
|
|
r = append_mount_images(&m, mount_images, n_mount_images);
|
|
|
|
if (r < 0)
|
|
|
|
goto finish;
|
|
|
|
|
2020-10-09 15:06:34 +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,
|
2019-03-25 19:29:26 +01:00
|
|
|
.flags = DEV_MOUNT_OPTIONS,
|
2016-11-15 01:42:54 +01:00
|
|
|
};
|
2014-01-20 19:54:51 +01:00
|
|
|
|
2016-10-12 14:11:16 +02:00
|
|
|
if (ns_info->protect_kernel_tunables) {
|
2020-06-27 13:23:08 +02: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) {
|
2020-06-27 13:23:08 +02: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
|
|
|
|
2019-11-10 10:17:01 +01:00
|
|
|
if (ns_info->protect_kernel_logs) {
|
2020-06-27 13:23:08 +02:00
|
|
|
r = append_static_mounts(&m,
|
|
|
|
protect_kernel_logs_table,
|
|
|
|
ELEMENTSOF(protect_kernel_logs_table),
|
|
|
|
ns_info->ignore_protect_paths);
|
2019-11-10 10:17:01 +01:00
|
|
|
if (r < 0)
|
|
|
|
goto finish;
|
|
|
|
}
|
|
|
|
|
2020-10-09 15:06:34 +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
|
|
|
|
2020-08-06 11:32:53 +02:00
|
|
|
r = append_protect_home(&m, ns_info->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
|
|
|
|
2020-08-06 11:32:53 +02:00
|
|
|
r = append_protect_system(&m, ns_info->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
|
|
|
|
2018-07-28 17:38:36 +02:00
|
|
|
if (namespace_info_mount_apivfs(ns_info)) {
|
2020-06-27 13:23:08 +02:00
|
|
|
r = append_static_mounts(&m,
|
|
|
|
apivfs_table,
|
|
|
|
ELEMENTSOF(apivfs_table),
|
|
|
|
ns_info->ignore_protect_paths);
|
2016-12-22 23:34:35 +01:00
|
|
|
if (r < 0)
|
|
|
|
goto finish;
|
|
|
|
}
|
|
|
|
|
2019-02-08 18:25:00 +01:00
|
|
|
if (ns_info->protect_hostname) {
|
|
|
|
*(m++) = (MountEntry) {
|
|
|
|
.path_const = "/proc/sys/kernel/hostname",
|
|
|
|
.mode = READONLY,
|
|
|
|
};
|
|
|
|
*(m++) = (MountEntry) {
|
|
|
|
.path_const = "/proc/sys/kernel/domainname",
|
|
|
|
.mode = READONLY,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-08-14 15:54:48 +02:00
|
|
|
if (creds_path) {
|
|
|
|
/* If our service has a credentials store configured, then bind that one in, but hide
|
|
|
|
* everything else. */
|
|
|
|
|
|
|
|
*(m++) = (MountEntry) {
|
|
|
|
.path_const = "/run/credentials",
|
|
|
|
.mode = TMPFS,
|
|
|
|
.read_only = true,
|
|
|
|
.options_const = "mode=0755" TMPFS_LIMITS_EMPTY_OR_ALMOST,
|
|
|
|
.flags = MS_NODEV|MS_STRICTATIME|MS_NOSUID|MS_NOEXEC,
|
|
|
|
};
|
|
|
|
|
|
|
|
*(m++) = (MountEntry) {
|
|
|
|
.path_const = creds_path,
|
|
|
|
.mode = BIND_MOUNT,
|
|
|
|
.read_only = true,
|
|
|
|
.source_const = creds_path,
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
/* If our service has no credentials store configured, then make the whole
|
|
|
|
* credentials tree inaccessible wholesale. */
|
|
|
|
|
|
|
|
*(m++) = (MountEntry) {
|
|
|
|
.path_const = "/run/credentials",
|
|
|
|
.mode = INACCESSIBLE,
|
|
|
|
.ignore = true,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-11-25 16:22:45 +01:00
|
|
|
if (log_namespace) {
|
|
|
|
_cleanup_free_ char *q;
|
|
|
|
|
|
|
|
q = strjoin("/run/systemd/journal.", log_namespace);
|
|
|
|
if (!q) {
|
|
|
|
r = -ENOMEM;
|
|
|
|
goto finish;
|
|
|
|
}
|
|
|
|
|
|
|
|
*(m++) = (MountEntry) {
|
|
|
|
.path_const = "/run/systemd/journal",
|
|
|
|
.mode = BIND_MOUNT_RECURSIVE,
|
|
|
|
.read_only = true,
|
|
|
|
.source_malloc = TAKE_PTR(q),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
2018-07-28 17:42:41 +02:00
|
|
|
normalize_mounts(root, mounts, &n_mounts);
|
2010-04-21 22:15:06 +02:00
|
|
|
}
|
|
|
|
|
2018-08-10 15:07:14 +02:00
|
|
|
/* All above is just preparation, figuring out what to do. Let's now actually start doing something. */
|
|
|
|
|
2016-09-24 12:41:30 +02:00
|
|
|
if (unshare(CLONE_NEWNS) < 0) {
|
2018-08-10 06:50:54 +02:00
|
|
|
r = log_debug_errno(errno, "Failed to unshare the mount namespace: %m");
|
2018-08-10 15:07:14 +02:00
|
|
|
if (IN_SET(r, -EACCES, -EPERM, -EOPNOTSUPP, -ENOSYS))
|
2020-06-27 13:23:08 +02:00
|
|
|
/* If the kernel doesn't support namespaces, or when there's a MAC or seccomp filter
|
|
|
|
* in place that doesn't allow us to create namespaces (or a missing cap), then
|
|
|
|
* propagate a recognizable error back, which the caller can use to detect this case
|
|
|
|
* (and only this) and optionally continue without namespacing applied. */
|
2018-08-10 15:07:14 +02:00
|
|
|
r = -ENOANO;
|
|
|
|
|
2016-09-24 12:41:30 +02:00
|
|
|
goto finish;
|
|
|
|
}
|
2016-08-25 17:30:47 +02:00
|
|
|
|
2018-07-28 17:38:36 +02:00
|
|
|
/* Remount / as SLAVE so that nothing now mounted in the namespace
|
|
|
|
* shows up in the parent */
|
|
|
|
if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) {
|
2018-08-10 06:50:54 +02:00
|
|
|
r = log_debug_errno(errno, "Failed to remount '/' as SLAVE: %m");
|
2018-07-28 17:38:36 +02:00
|
|
|
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);
|
2018-08-10 06:50:54 +02:00
|
|
|
if (r < 0) {
|
|
|
|
log_debug_errno(r, "Failed to mount root image: %m");
|
2016-12-23 14:26:05 +01:00
|
|
|
goto finish;
|
2018-08-10 06:50:54 +02:00
|
|
|
}
|
2016-12-23 14:26:05 +01:00
|
|
|
|
2017-08-29 17:31:24 +02:00
|
|
|
if (decrypted_image) {
|
|
|
|
r = decrypted_image_relinquish(decrypted_image);
|
2018-08-10 06:50:54 +02:00
|
|
|
if (r < 0) {
|
|
|
|
log_debug_errno(r, "Failed to relinquish decrypted image: %m");
|
2017-08-29 17:31:24 +02:00
|
|
|
goto finish;
|
2018-08-10 06:50:54 +02:00
|
|
|
}
|
2017-08-29 17:31:24 +02:00
|
|
|
}
|
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);
|
2018-08-10 06:50:54 +02:00
|
|
|
if (r < 0) {
|
|
|
|
log_debug_errno(r, "Failed to detect that %s is a mount point or not: %m", root);
|
2016-09-24 12:41:30 +02:00
|
|
|
goto finish;
|
2018-08-10 06:50:54 +02:00
|
|
|
}
|
2016-08-26 11:27:38 +02:00
|
|
|
if (r == 0) {
|
2020-09-23 10:12:56 +02:00
|
|
|
r = mount_nofollow_verbose(LOG_DEBUG, root, root, NULL, MS_BIND|MS_REC, NULL);
|
|
|
|
if (r < 0)
|
2016-08-26 11:27:38 +02:00
|
|
|
goto finish;
|
2016-09-24 12:41:30 +02:00
|
|
|
}
|
2017-09-28 18:30:55 +02:00
|
|
|
|
2018-07-28 17:38:36 +02:00
|
|
|
} else {
|
2017-09-28 18:30:55 +02:00
|
|
|
/* Let's mount the main root directory to the root directory to use */
|
2020-09-23 10:12:56 +02:00
|
|
|
r = mount_nofollow_verbose(LOG_DEBUG, "/", root, NULL, MS_BIND|MS_REC, NULL);
|
|
|
|
if (r < 0)
|
2017-09-28 18:30:55 +02:00
|
|
|
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;
|
2020-06-23 08:31:16 +02:00
|
|
|
_cleanup_free_ char **deny_list = NULL;
|
tree-wide: be more careful with the type of array sizes
Previously we were a bit sloppy with the index and size types of arrays,
we'd regularly use unsigned. While I don't think this ever resulted in
real issues I think we should be more careful there and follow a
stricter regime: unless there's a strong reason not to use size_t for
array sizes and indexes, size_t it should be. Any allocations we do
ultimately will use size_t anyway, and converting forth and back between
unsigned and size_t will always be a source of problems.
Note that on 32bit machines "unsigned" and "size_t" are equivalent, and
on 64bit machines our arrays shouldn't grow that large anyway, and if
they do we have a problem, however that kind of overly large allocation
we have protections for usually, but for overflows we do not have that
so much, hence let's add it.
So yeah, it's a story of the current code being already "good enough",
but I think some extra type hygiene is better.
This patch tries to be comprehensive, but it probably isn't and I missed
a few cases. But I guess we can cover that later as we notice it. Among
smaller fixes, this changes:
1. strv_length()' return type becomes size_t
2. the unit file changes array size becomes size_t
3. DNS answer and query array sizes become size_t
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=76745
2018-04-27 14:09:31 +02:00
|
|
|
size_t 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
|
|
|
|
2020-06-27 13:23:08 +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'. */
|
2017-05-19 14:38:40 +02:00
|
|
|
proc_self_mountinfo = fopen("/proc/self/mountinfo", "re");
|
|
|
|
if (!proc_self_mountinfo) {
|
2018-08-10 06:50:54 +02:00
|
|
|
r = log_debug_errno(errno, "Failed to open /proc/self/mountinfo: %m");
|
2019-05-21 20:02:34 +02:00
|
|
|
if (error_path)
|
|
|
|
*error_path = strdup("/proc/self/mountinfo");
|
2017-05-19 14:38:40 +02:00
|
|
|
goto finish;
|
|
|
|
}
|
|
|
|
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
/* First round, establish all mounts we need */
|
|
|
|
for (;;) {
|
|
|
|
bool again = false;
|
|
|
|
|
|
|
|
for (m = mounts; m < mounts + n_mounts; ++m) {
|
|
|
|
|
|
|
|
if (m->applied)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
r = follow_symlink(root, m);
|
2019-05-21 20:02:34 +02:00
|
|
|
if (r < 0) {
|
|
|
|
if (error_path && mount_entry_path(m))
|
|
|
|
*error_path = strdup(mount_entry_path(m));
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
goto finish;
|
2019-05-21 20:02:34 +02:00
|
|
|
}
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
if (r == 0) {
|
2020-06-27 13:23:08 +02:00
|
|
|
/* We hit a symlinked mount point. The entry got rewritten and might
|
|
|
|
* point to a very different place now. Let's normalize the changed
|
|
|
|
* list, and start from the beginning. After all to mount the entry
|
|
|
|
* at the new location we might need some other mounts first */
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
again = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-08-06 12:51:50 +02:00
|
|
|
r = apply_mount(root, m, ns_info);
|
2019-05-21 20:02:34 +02:00
|
|
|
if (r < 0) {
|
|
|
|
if (error_path && mount_entry_path(m))
|
|
|
|
*error_path = strdup(mount_entry_path(m));
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
goto finish;
|
2019-05-21 20:02:34 +02:00
|
|
|
}
|
namespace: rework how we resolve symlinks in mount points
Before this patch we'd resolve all symlinks of bind mounts and other
mount points to establish for a service in advance, and only then start
mounting them. This is problematic, if symlink chains jump around
between directories in a namespace tree, so that to resolve a specific
symlink chain we need to establish another mount already. A typical case
where this happens is if /etc/resolv.conf is a symlink to some file in
/run: in that case we'd normally resolve and mount /etc/resolv.conf
early on, but that's broken, as to do this properly we'd need to resolve
/etc/resolv.conf first, then figure out that /run needs to be mounted
before we can proceed, and thus reorder the order in which we apply
mounts dynamically.
With this change, whenever we are about to apply a mount, we'll do a
single step of the symlink normalization process, patch the mount entry
accordingly, and then sort the list of mounts to establish again, taking
the new path into account. This means that we can correctly deal with
the example above: we might start with wanting to mount /etc/resolv.conf
early, but after resolving it to the path in /run/ we'd push it to the
end of the list, ensuring that /run is mounted first.
(Note that this also fixes another bug: we were following symlinks on
the bind mount source relative to the root directory of the service,
rather than of the host. That's wrong though as we explicitly document
tha the source of bind mounts is always on the host.)
2018-04-04 10:14:43 +02:00
|
|
|
|
|
|
|
m->applied = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!again)
|
|
|
|
break;
|
|
|
|
|
2018-07-28 17:42:41 +02:00
|
|
|
normalize_mounts(root, mounts, &n_mounts);
|
2014-03-20 04:16:39 +01:00
|
|
|
}
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2020-06-23 08:31:16 +02:00
|
|
|
/* Create a deny list we can pass to bind_mount_recursive() */
|
|
|
|
deny_list = new(char*, n_mounts+1);
|
|
|
|
if (!deny_list) {
|
2019-03-25 16:34:48 +01:00
|
|
|
r = -ENOMEM;
|
|
|
|
goto finish;
|
|
|
|
}
|
2016-11-06 22:51:49 +01:00
|
|
|
for (j = 0; j < n_mounts; j++)
|
2020-06-23 08:31:16 +02:00
|
|
|
deny_list[j] = (char*) mount_entry_path(mounts+j);
|
|
|
|
deny_list[j] = 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
|
|
|
|
|
|
|
/* Second round, flip the ro bits if necessary. */
|
2016-11-06 22:51:49 +01:00
|
|
|
for (m = mounts; m < mounts + n_mounts; ++m) {
|
2020-06-23 08:31:16 +02:00
|
|
|
r = make_read_only(m, deny_list, proc_self_mountinfo);
|
2019-05-21 20:02:34 +02:00
|
|
|
if (r < 0) {
|
|
|
|
if (error_path && mount_entry_path(m))
|
|
|
|
*error_path = strdup(mount_entry_path(m));
|
2016-09-24 12:41:30 +02:00
|
|
|
goto finish;
|
2019-05-21 20:02:34 +02:00
|
|
|
}
|
2014-03-20 04:16:39 +01:00
|
|
|
}
|
2010-04-21 22:15:06 +02:00
|
|
|
}
|
|
|
|
|
2018-07-28 17:38:36 +02:00
|
|
|
/* MS_MOVE does not work on MS_SHARED so the remount MS_SHARED will be done later */
|
|
|
|
r = mount_move_root(root);
|
2018-08-10 06:50:54 +02:00
|
|
|
if (r < 0) {
|
|
|
|
log_debug_errno(r, "Failed to mount root with MS_MOVE: %m");
|
2018-07-28 17:38:36 +02:00
|
|
|
goto finish;
|
2018-08-10 06:50:54 +02:00
|
|
|
}
|
2015-05-18 12:20:28 +02:00
|
|
|
|
2018-02-14 05:29:13 +01:00
|
|
|
/* Remount / as the desired mode. Note that this will not
|
2014-03-20 04:16:39 +01:00
|
|
|
* 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) {
|
2018-08-10 06:50:54 +02:00
|
|
|
r = log_debug_errno(errno, "Failed to remount '/' with desired mount flags: %m");
|
2016-09-24 12:41:30 +02:00
|
|
|
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:
|
2020-04-22 19:32:09 +02:00
|
|
|
if (n_mounts > 0)
|
|
|
|
for (m = mounts; m < mounts + n_mounts; m++)
|
|
|
|
mount_entry_done(m);
|
2013-11-27 20:23:18 +01:00
|
|
|
|
2019-03-25 16:34:48 +01:00
|
|
|
free(mounts);
|
|
|
|
|
2013-11-27 20:23:18 +01:00
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
tree-wide: be more careful with the type of array sizes
Previously we were a bit sloppy with the index and size types of arrays,
we'd regularly use unsigned. While I don't think this ever resulted in
real issues I think we should be more careful there and follow a
stricter regime: unless there's a strong reason not to use size_t for
array sizes and indexes, size_t it should be. Any allocations we do
ultimately will use size_t anyway, and converting forth and back between
unsigned and size_t will always be a source of problems.
Note that on 32bit machines "unsigned" and "size_t" are equivalent, and
on 64bit machines our arrays shouldn't grow that large anyway, and if
they do we have a problem, however that kind of overly large allocation
we have protections for usually, but for overflows we do not have that
so much, hence let's add it.
So yeah, it's a story of the current code being already "good enough",
but I think some extra type hygiene is better.
This patch tries to be comprehensive, but it probably isn't and I missed
a few cases. But I guess we can cover that later as we notice it. Among
smaller fixes, this changes:
1. strv_length()' return type becomes size_t
2. the unit file changes array size becomes size_t
3. DNS answer and query array sizes become size_t
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=76745
2018-04-27 14:09:31 +02:00
|
|
|
void bind_mount_free_many(BindMount *b, size_t n) {
|
|
|
|
size_t i;
|
2016-11-23 22:21:40 +01:00
|
|
|
|
|
|
|
assert(b || n == 0);
|
|
|
|
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
free(b[i].source);
|
|
|
|
free(b[i].destination);
|
|
|
|
}
|
|
|
|
|
|
|
|
free(b);
|
|
|
|
}
|
|
|
|
|
tree-wide: be more careful with the type of array sizes
Previously we were a bit sloppy with the index and size types of arrays,
we'd regularly use unsigned. While I don't think this ever resulted in
real issues I think we should be more careful there and follow a
stricter regime: unless there's a strong reason not to use size_t for
array sizes and indexes, size_t it should be. Any allocations we do
ultimately will use size_t anyway, and converting forth and back between
unsigned and size_t will always be a source of problems.
Note that on 32bit machines "unsigned" and "size_t" are equivalent, and
on 64bit machines our arrays shouldn't grow that large anyway, and if
they do we have a problem, however that kind of overly large allocation
we have protections for usually, but for overflows we do not have that
so much, hence let's add it.
So yeah, it's a story of the current code being already "good enough",
but I think some extra type hygiene is better.
This patch tries to be comprehensive, but it probably isn't and I missed
a few cases. But I guess we can cover that later as we notice it. Among
smaller fixes, this changes:
1. strv_length()' return type becomes size_t
2. the unit file changes array size becomes size_t
3. DNS answer and query array sizes become size_t
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=76745
2018-04-27 14:09:31 +02:00
|
|
|
int bind_mount_add(BindMount **b, size_t *n, const BindMount *item) {
|
2016-11-23 22:21:40 +01:00
|
|
|
_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;
|
|
|
|
|
2018-02-26 21:20:00 +01:00
|
|
|
c = reallocarray(*b, *n + 1, sizeof(BindMount));
|
2016-11-23 22:21:40 +01:00
|
|
|
if (!c)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
*b = c;
|
|
|
|
|
|
|
|
c[(*n) ++] = (BindMount) {
|
2018-04-05 07:26:26 +02:00
|
|
|
.source = TAKE_PTR(s),
|
|
|
|
.destination = TAKE_PTR(d),
|
2016-11-23 22:21:40 +01:00
|
|
|
.read_only = item->read_only,
|
2019-03-25 19:29:26 +01:00
|
|
|
.nosuid = item->nosuid,
|
2016-11-23 22:21:40 +01:00
|
|
|
.recursive = item->recursive,
|
|
|
|
.ignore_enoent = item->ignore_enoent,
|
|
|
|
};
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-07-14 17:18:41 +02:00
|
|
|
MountImage* mount_image_free_many(MountImage *m, size_t *n) {
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
assert(n);
|
|
|
|
assert(m || *n == 0);
|
|
|
|
|
|
|
|
for (i = 0; i < *n; i++) {
|
|
|
|
free(m[i].source);
|
|
|
|
free(m[i].destination);
|
2020-07-31 16:06:15 +02:00
|
|
|
mount_options_free_all(m[i].mount_options);
|
2020-07-14 17:18:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
free(m);
|
|
|
|
*n = 0;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int mount_image_add(MountImage **m, size_t *n, const MountImage *item) {
|
|
|
|
_cleanup_free_ char *s = NULL, *d = NULL;
|
2020-07-31 16:06:15 +02:00
|
|
|
_cleanup_(mount_options_free_allp) MountOptions *options = NULL;
|
|
|
|
MountOptions *i;
|
2020-07-14 17:18:41 +02:00
|
|
|
MountImage *c;
|
|
|
|
|
|
|
|
assert(m);
|
|
|
|
assert(n);
|
|
|
|
assert(item);
|
|
|
|
|
|
|
|
s = strdup(item->source);
|
|
|
|
if (!s)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
d = strdup(item->destination);
|
|
|
|
if (!d)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2020-07-31 16:06:15 +02:00
|
|
|
LIST_FOREACH(mount_options, i, item->mount_options) {
|
|
|
|
_cleanup_(mount_options_free_allp) MountOptions *o;
|
|
|
|
|
|
|
|
o = new(MountOptions, 1);
|
|
|
|
if (!o)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
*o = (MountOptions) {
|
|
|
|
.partition_designator = i->partition_designator,
|
|
|
|
.options = strdup(i->options),
|
|
|
|
};
|
|
|
|
if (!o->options)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
LIST_APPEND(mount_options, options, TAKE_PTR(o));
|
|
|
|
}
|
|
|
|
|
2020-07-14 17:18:41 +02:00
|
|
|
c = reallocarray(*m, *n + 1, sizeof(MountImage));
|
|
|
|
if (!c)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
*m = c;
|
|
|
|
|
|
|
|
c[(*n) ++] = (MountImage) {
|
|
|
|
.source = TAKE_PTR(s),
|
|
|
|
.destination = TAKE_PTR(d),
|
2020-07-31 16:06:15 +02:00
|
|
|
.mount_options = TAKE_PTR(options),
|
2020-07-14 17:18:41 +02:00
|
|
|
.ignore_enoent = item->ignore_enoent,
|
|
|
|
};
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
tree-wide: be more careful with the type of array sizes
Previously we were a bit sloppy with the index and size types of arrays,
we'd regularly use unsigned. While I don't think this ever resulted in
real issues I think we should be more careful there and follow a
stricter regime: unless there's a strong reason not to use size_t for
array sizes and indexes, size_t it should be. Any allocations we do
ultimately will use size_t anyway, and converting forth and back between
unsigned and size_t will always be a source of problems.
Note that on 32bit machines "unsigned" and "size_t" are equivalent, and
on 64bit machines our arrays shouldn't grow that large anyway, and if
they do we have a problem, however that kind of overly large allocation
we have protections for usually, but for overflows we do not have that
so much, hence let's add it.
So yeah, it's a story of the current code being already "good enough",
but I think some extra type hygiene is better.
This patch tries to be comprehensive, but it probably isn't and I missed
a few cases. But I guess we can cover that later as we notice it. Among
smaller fixes, this changes:
1. strv_length()' return type becomes size_t
2. the unit file changes array size becomes size_t
3. DNS answer and query array sizes become size_t
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=76745
2018-04-27 14:09:31 +02:00
|
|
|
void temporary_filesystem_free_many(TemporaryFileSystem *t, size_t n) {
|
|
|
|
size_t i;
|
2018-02-21 01:17:52 +01:00
|
|
|
|
|
|
|
assert(t || n == 0);
|
|
|
|
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
free(t[i].path);
|
|
|
|
free(t[i].options);
|
|
|
|
}
|
|
|
|
|
|
|
|
free(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
int temporary_filesystem_add(
|
|
|
|
TemporaryFileSystem **t,
|
tree-wide: be more careful with the type of array sizes
Previously we were a bit sloppy with the index and size types of arrays,
we'd regularly use unsigned. While I don't think this ever resulted in
real issues I think we should be more careful there and follow a
stricter regime: unless there's a strong reason not to use size_t for
array sizes and indexes, size_t it should be. Any allocations we do
ultimately will use size_t anyway, and converting forth and back between
unsigned and size_t will always be a source of problems.
Note that on 32bit machines "unsigned" and "size_t" are equivalent, and
on 64bit machines our arrays shouldn't grow that large anyway, and if
they do we have a problem, however that kind of overly large allocation
we have protections for usually, but for overflows we do not have that
so much, hence let's add it.
So yeah, it's a story of the current code being already "good enough",
but I think some extra type hygiene is better.
This patch tries to be comprehensive, but it probably isn't and I missed
a few cases. But I guess we can cover that later as we notice it. Among
smaller fixes, this changes:
1. strv_length()' return type becomes size_t
2. the unit file changes array size becomes size_t
3. DNS answer and query array sizes become size_t
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=76745
2018-04-27 14:09:31 +02:00
|
|
|
size_t *n,
|
2018-02-21 01:17:52 +01:00
|
|
|
const char *path,
|
|
|
|
const char *options) {
|
|
|
|
|
|
|
|
_cleanup_free_ char *p = NULL, *o = NULL;
|
|
|
|
TemporaryFileSystem *c;
|
|
|
|
|
|
|
|
assert(t);
|
|
|
|
assert(n);
|
|
|
|
assert(path);
|
|
|
|
|
|
|
|
p = strdup(path);
|
|
|
|
if (!p)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
if (!isempty(options)) {
|
|
|
|
o = strdup(options);
|
|
|
|
if (!o)
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2018-02-26 21:20:00 +01:00
|
|
|
c = reallocarray(*t, *n + 1, sizeof(TemporaryFileSystem));
|
2018-02-21 01:17:52 +01:00
|
|
|
if (!c)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
*t = c;
|
|
|
|
|
|
|
|
c[(*n) ++] = (TemporaryFileSystem) {
|
2018-04-05 07:26:26 +02:00
|
|
|
.path = TAKE_PTR(p),
|
|
|
|
.options = TAKE_PTR(o),
|
2018-02-21 01:17:52 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-11-29 20:28:35 +01:00
|
|
|
static int make_tmp_prefix(const char *prefix) {
|
|
|
|
_cleanup_free_ char *t = NULL;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
/* Don't do anything unless we know the dir is actually missing */
|
|
|
|
r = access(prefix, F_OK);
|
|
|
|
if (r >= 0)
|
|
|
|
return 0;
|
|
|
|
if (errno != ENOENT)
|
|
|
|
return -errno;
|
|
|
|
|
|
|
|
r = mkdir_parents(prefix, 0755);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
r = tempfn_random(prefix, NULL, &t);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
if (mkdir(t, 0777) < 0)
|
|
|
|
return -errno;
|
|
|
|
|
|
|
|
if (chmod(t, 01777) < 0) {
|
|
|
|
r = -errno;
|
|
|
|
(void) rmdir(t);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rename(t, prefix) < 0) {
|
|
|
|
r = -errno;
|
|
|
|
(void) rmdir(t);
|
|
|
|
return r == -EEXIST ? 0 : r; /* it's fine if someone else created the dir by now */
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-06-28 19:54:49 +02:00
|
|
|
static int setup_one_tmp_dir(const char *id, const char *prefix, char **path, char **tmp_path) {
|
2013-11-27 20:23:18 +01:00
|
|
|
_cleanup_free_ char *x = NULL;
|
2020-09-25 18:45:29 +02:00
|
|
|
_cleanup_free_ char *y = NULL;
|
2013-12-13 02:05:04 +01:00
|
|
|
char bid[SD_ID128_STRING_MAX];
|
|
|
|
sd_id128_t boot_id;
|
2020-06-28 19:54:49 +02:00
|
|
|
bool rw = true;
|
2013-12-13 02:05:04 +01:00
|
|
|
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;
|
|
|
|
|
2019-11-29 20:28:35 +01:00
|
|
|
r = make_tmp_prefix(prefix);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2013-11-27 20:23:18 +01:00
|
|
|
RUN_WITH_UMASK(0077)
|
2020-06-28 19:54:49 +02:00
|
|
|
if (!mkdtemp(x)) {
|
|
|
|
if (errno == EROFS || ERRNO_IS_DISK_SPACE(errno))
|
|
|
|
rw = false;
|
|
|
|
else
|
|
|
|
return -errno;
|
|
|
|
}
|
2013-11-27 20:23:18 +01:00
|
|
|
|
2020-06-28 19:54:49 +02:00
|
|
|
if (rw) {
|
2020-09-25 18:45:29 +02:00
|
|
|
y = strjoin(x, "/tmp");
|
|
|
|
if (!y)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
RUN_WITH_UMASK(0000) {
|
|
|
|
if (mkdir(y, 0777 | S_ISVTX) < 0)
|
|
|
|
return -errno;
|
|
|
|
}
|
|
|
|
|
|
|
|
r = label_fix_container(y, prefix, 0);
|
2020-06-28 19:54:49 +02:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
2020-09-25 18:45:29 +02:00
|
|
|
|
|
|
|
if (tmp_path)
|
|
|
|
*tmp_path = TAKE_PTR(y);
|
2020-06-28 19:54:49 +02:00
|
|
|
} else {
|
|
|
|
/* Trouble: we failed to create the directory. Instead of failing, let's simulate /tmp being
|
|
|
|
* read-only. This way the service will get the EROFS result as if it was writing to the real
|
|
|
|
* file system. */
|
|
|
|
r = mkdir_p(RUN_SYSTEMD_EMPTY, 0500);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
2013-11-27 20:23:18 +01:00
|
|
|
|
2020-08-14 11:07:18 +02:00
|
|
|
r = free_and_strdup(&x, RUN_SYSTEMD_EMPTY);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
2013-03-14 18:12:27 +01:00
|
|
|
}
|
2010-04-21 22:15:06 +02:00
|
|
|
|
2018-04-05 07:26:26 +02:00
|
|
|
*path = TAKE_PTR(x);
|
2013-11-27 20:23:18 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int setup_tmp_dirs(const char *id, char **tmp_dir, char **var_tmp_dir) {
|
2020-06-28 19:54:49 +02:00
|
|
|
_cleanup_(namespace_cleanup_tmpdirp) char *a = NULL;
|
|
|
|
_cleanup_(rmdir_and_freep) char *a_tmp = NULL;
|
|
|
|
char *b;
|
2013-11-27 20:23:18 +01:00
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(id);
|
|
|
|
assert(tmp_dir);
|
|
|
|
assert(var_tmp_dir);
|
|
|
|
|
2020-06-28 19:54:49 +02:00
|
|
|
r = setup_one_tmp_dir(id, "/tmp", &a, &a_tmp);
|
2013-11-27 20:23:18 +01:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2020-06-28 19:54:49 +02:00
|
|
|
r = setup_one_tmp_dir(id, "/var/tmp", &b, NULL);
|
|
|
|
if (r < 0)
|
2013-11-27 20:23:18 +01:00
|
|
|
return r;
|
|
|
|
|
2020-06-28 19:54:49 +02:00
|
|
|
a_tmp = mfree(a_tmp); /* avoid rmdir */
|
|
|
|
*tmp_dir = TAKE_PTR(a);
|
|
|
|
*var_tmp_dir = TAKE_PTR(b);
|
2013-11-27 20:23:18 +01:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-07-12 16:39:07 +02:00
|
|
|
int setup_netns(const int netns_storage_socket[static 2]) {
|
2013-11-27 20:23:18 +01:00
|
|
|
_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) {
|
2019-03-07 16:40:06 +01:00
|
|
|
/* Nothing stored yet, so let's create a new namespace. */
|
2013-11-27 20:23:18 +01:00
|
|
|
|
|
|
|
if (unshare(CLONE_NEWNET) < 0) {
|
|
|
|
r = -errno;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2019-03-07 16:40:06 +01:00
|
|
|
(void) loopback_setup();
|
2013-11-27 20:23:18 +01:00
|
|
|
|
|
|
|
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
|
|
|
|
2019-07-12 16:39:07 +02:00
|
|
|
int open_netns_path(const int netns_storage_socket[static 2], const char *path) {
|
2019-03-07 16:42:04 +01:00
|
|
|
_cleanup_close_ int netns = -1;
|
|
|
|
int q, r;
|
|
|
|
|
|
|
|
assert(netns_storage_socket);
|
|
|
|
assert(netns_storage_socket[0] >= 0);
|
|
|
|
assert(netns_storage_socket[1] >= 0);
|
|
|
|
assert(path);
|
|
|
|
|
|
|
|
/* If the storage socket doesn't contain a netns fd yet, open one via the file system and store it in
|
|
|
|
* it. This is supposed to be called ahead of time, i.e. before setup_netns() which will allocate a
|
|
|
|
* new anonymous netns if needed. */
|
|
|
|
|
|
|
|
if (lockf(netns_storage_socket[0], F_LOCK, 0) < 0)
|
|
|
|
return -errno;
|
|
|
|
|
|
|
|
netns = receive_one_fd(netns_storage_socket[0], MSG_DONTWAIT);
|
|
|
|
if (netns == -EAGAIN) {
|
|
|
|
/* Nothing stored yet. Open the file from the file system. */
|
|
|
|
|
|
|
|
netns = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
|
|
|
|
if (netns < 0) {
|
|
|
|
r = -errno;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
r = fd_is_network_ns(netns);
|
|
|
|
if (r == 0) { /* Not a netns? Refuse early. */
|
|
|
|
r = -EINVAL;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
if (r < 0 && r != -EUCLEAN) /* EUCLEAN: we don't know */
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
r = 1;
|
|
|
|
|
|
|
|
} else if (netns < 0) {
|
|
|
|
r = netns;
|
|
|
|
goto fail;
|
|
|
|
} else
|
|
|
|
r = 0; /* Already allocated */
|
|
|
|
|
|
|
|
q = send_one_fd(netns_storage_socket[1], netns, MSG_DONTWAIT);
|
|
|
|
if (q < 0) {
|
|
|
|
r = q;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
fail:
|
|
|
|
(void) lockf(netns_storage_socket[0], F_ULOCK, 0);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
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] = {
|
2020-06-27 13:23:08 +02:00
|
|
|
[PROTECT_HOME_NO] = "no",
|
|
|
|
[PROTECT_HOME_YES] = "yes",
|
2014-06-04 18:07:55 +02:00
|
|
|
[PROTECT_HOME_READ_ONLY] = "read-only",
|
2020-06-27 13:23:08 +02:00
|
|
|
[PROTECT_HOME_TMPFS] = "tmpfs",
|
2014-06-03 23:41:44 +02:00
|
|
|
};
|
|
|
|
|
2018-06-15 05:29:29 +02:00
|
|
|
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(protect_home, ProtectHome, PROTECT_HOME_YES);
|
2018-01-01 16:08:40 +01:00
|
|
|
|
2014-06-04 18:07:55 +02:00
|
|
|
static const char *const protect_system_table[_PROTECT_SYSTEM_MAX] = {
|
2020-06-27 13:23:08 +02:00
|
|
|
[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
|
|
|
};
|
|
|
|
|
2018-06-15 05:29:29 +02:00
|
|
|
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(protect_system, ProtectSystem, PROTECT_SYSTEM_YES);
|
2018-01-01 16:10:22 +01:00
|
|
|
|
2017-10-10 09:46:13 +02:00
|
|
|
static const char* const namespace_type_table[] = {
|
2020-06-27 13:23:08 +02:00
|
|
|
[NAMESPACE_MOUNT] = "mnt",
|
2017-10-10 09:46:13 +02:00
|
|
|
[NAMESPACE_CGROUP] = "cgroup",
|
2020-06-27 13:23:08 +02:00
|
|
|
[NAMESPACE_UTS] = "uts",
|
|
|
|
[NAMESPACE_IPC] = "ipc",
|
|
|
|
[NAMESPACE_USER] = "user",
|
|
|
|
[NAMESPACE_PID] = "pid",
|
|
|
|
[NAMESPACE_NET] = "net",
|
2017-10-10 09:46:13 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_STRING_TABLE_LOOKUP(namespace_type, NamespaceType);
|
2020-08-06 12:51:50 +02:00
|
|
|
|
|
|
|
static const char* const protect_proc_table[_PROTECT_PROC_MAX] = {
|
|
|
|
[PROTECT_PROC_DEFAULT] = "default",
|
|
|
|
[PROTECT_PROC_NOACCESS] = "noaccess",
|
|
|
|
[PROTECT_PROC_INVISIBLE] = "invisible",
|
|
|
|
[PROTECT_PROC_PTRACEABLE] = "ptraceable",
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_STRING_TABLE_LOOKUP(protect_proc, ProtectProc);
|
|
|
|
|
|
|
|
static const char* const proc_subset_table[_PROC_SUBSET_MAX] = {
|
|
|
|
[PROC_SUBSET_ALL] = "all",
|
|
|
|
[PROC_SUBSET_PID] = "pid",
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_STRING_TABLE_LOOKUP(proc_subset, ProcSubset);
|