summaryrefslogtreecommitdiff
path: root/src/core/namespace.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/namespace.c')
-rw-r--r--src/core/namespace.c291
1 files changed, 164 insertions, 127 deletions
diff --git a/src/core/namespace.c b/src/core/namespace.c
index e4930db15c..c2ca3e0334 100644
--- a/src/core/namespace.c
+++ b/src/core/namespace.c
@@ -20,6 +20,7 @@
#include "missing.h"
#include "mkdir.h"
#include "mount-util.h"
+#include "mountpoint-util.h"
#include "namespace.h"
#include "path-util.h"
#include "selinux-util.h"
@@ -236,7 +237,8 @@ static int append_access_mounts(MountEntry **p, char **strv, MountMode mode, boo
}
if (!path_is_absolute(e))
- return -EINVAL;
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Path is not absolute: %s", e);
*((*p)++) = (MountEntry) {
.path_const = e,
@@ -263,7 +265,6 @@ static int append_empty_dir_mounts(MountEntry **p, char **strv) {
.path_const = *i,
.mode = EMPTY_DIR,
.ignore = false,
- .has_prefix = false,
.read_only = true,
.options_const = "mode=755",
.flags = MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME,
@@ -302,35 +303,33 @@ static int append_tmpfs_mounts(MountEntry **p, const TemporaryFileSystem *tmpfs,
for (i = 0; i < n; i++) {
const TemporaryFileSystem *t = tmpfs + i;
_cleanup_free_ char *o = NULL, *str = NULL;
- unsigned long flags = MS_NODEV|MS_STRICTATIME;
+ unsigned long flags;
bool ro = false;
if (!path_is_absolute(t->path))
- return -EINVAL;
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Path is not absolute: %s",
+ t->path);
- if (!isempty(t->options)) {
- str = strjoin("mode=0755,", t->options);
- if (!str)
- return -ENOMEM;
+ str = strjoin("mode=0755,", t->options);
+ if (!str)
+ return -ENOMEM;
- r = mount_option_mangle(str, MS_NODEV|MS_STRICTATIME, &flags, &o);
- if (r < 0)
- return r;
+ 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);
- ro = flags & MS_RDONLY;
- if (ro)
- flags ^= MS_RDONLY;
- }
+ ro = flags & MS_RDONLY;
+ if (ro)
+ flags ^= MS_RDONLY;
*((*p)++) = (MountEntry) {
.path_const = t->path,
.mode = TMPFS,
.read_only = ro,
- .options_malloc = o,
+ .options_malloc = TAKE_PTR(o),
.flags = flags,
};
-
- o = NULL;
}
return 0;
@@ -398,32 +397,22 @@ static int append_protect_system(MountEntry **p, ProtectSystem protect_system, b
}
}
-static int mount_path_compare(const void *a, const void *b) {
- const MountEntry *p = a, *q = b;
+static int mount_path_compare(const MountEntry *a, const MountEntry *b) {
int d;
/* If the paths are not equal, then order prefixes first */
- d = path_compare(mount_entry_path(p), mount_entry_path(q));
+ d = path_compare(mount_entry_path(a), mount_entry_path(b));
if (d != 0)
return d;
/* If the paths are equal, check the mode */
- if (p->mode < q->mode)
- return -1;
- if (p->mode > q->mode)
- return 1;
-
- return 0;
+ return CMP((int) a->mode, (int) b->mode);
}
static int prefix_where_needed(MountEntry *m, size_t n, const char *root_directory) {
size_t i;
- /* Prefixes all paths in the bind mount table with the root directory if it is specified and the entry needs
- * that. */
-
- if (!root_directory)
- return 0;
+ /* Prefixes all paths in the bind mount table with the root directory if the entry needs that. */
for (i = 0; i < n; i++) {
char *s;
@@ -566,36 +555,44 @@ static void drop_outside_root(const char *root_directory, MountEntry *m, size_t
*n = t - m;
}
-static int clone_device_node(const char *d, const char *temporary_mount, bool *make_devnode) {
- const char *dn;
+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;
struct stat st;
int r;
if (stat(d, &st) < 0) {
- if (errno == ENOENT)
+ if (errno == ENOENT) {
+ log_debug_errno(errno, "Device node '%s' to clone does not exist, ignoring.", d);
return -ENXIO;
- return -errno;
+ }
+
+ return log_debug_errno(errno, "Failed to stat() device node '%s' to clone, ignoring: %m", d);
}
if (!S_ISBLK(st.st_mode) &&
!S_ISCHR(st.st_mode))
- return -EINVAL;
-
- if (st.st_rdev == 0)
- return -ENXIO;
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Device node '%s' to clone is not a device node, ignoring.",
+ d);
dn = strjoina(temporary_mount, d);
+ /* First, try to create device node properly */
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();
-
- if (r == 0)
- return 0;
+ if (r >= 0)
+ goto add_symlink;
if (errno != EPERM)
return log_debug_errno(errno, "mknod failed for %s: %m", d);
+ /* This didn't work, let's not try this again for the next iterations. */
*make_devnode = false;
}
@@ -604,9 +601,8 @@ static int clone_device_node(const char *d, const char *temporary_mount, bool *m
mac_selinux_create_file_prepare(d, 0);
r = mknod(dn, S_IFREG, 0);
mac_selinux_create_file_clear();
-
if (r < 0 && errno != EEXIST)
- return log_debug_errno(errno, "mknod fallback failed for %s: %m", d);
+ return log_debug_errno(errno, "mknod() fallback failed for '%s': %m", d);
/* Fallback to bind-mounting:
* The assumption here is that all used device nodes carry standard
@@ -614,7 +610,23 @@ static int clone_device_node(const char *d, const char *temporary_mount, bool *m
* either be owned by root:root or root:tty (e.g. /dev/tty, /dev/ptmx)
* and should not carry ACLs. */
if (mount(d, dn, NULL, MS_BIND, NULL) < 0)
- return log_debug_errno(errno, "mount failed for %s: %m", d);
+ return log_debug_errno(errno, "Bind mounting failed for '%s': %m", d);
+
+add_symlink:
+ bn = path_startswith(d, "/dev/");
+ if (!bn)
+ return 0;
+
+ /* Create symlinks like /dev/char/1:9 → ../urandom */
+ 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)
+ return log_oom();
+
+ (void) mkdir_parents(sl, 0755);
+
+ t = strjoina("../", bn);
+
+ if (symlink(t, sl) < 0)
+ log_debug_errno(errno, "Failed to symlink '%s' to '%s', ignoring: %m", t, sl);
return 0;
}
@@ -639,35 +651,34 @@ static int mount_private_dev(MountEntry *m) {
u = umask(0000);
if (!mkdtemp(temporary_mount))
- return -errno;
+ return log_debug_errno(errno, "Failed to create temporary directory '%s': %m", temporary_mount);
dev = strjoina(temporary_mount, "/dev");
(void) mkdir(dev, 0755);
if (mount("tmpfs", dev, "tmpfs", DEV_MOUNT_OPTIONS, "mode=755") < 0) {
- r = -errno;
+ r = log_debug_errno(errno, "Failed to mount tmpfs on '%s': %m", dev);
goto fail;
}
devpts = strjoina(temporary_mount, "/dev/pts");
(void) mkdir(devpts, 0755);
if (mount("/dev/pts", devpts, NULL, MS_BIND, NULL) < 0) {
- r = -errno;
+ r = log_debug_errno(errno, "Failed to bind mount /dev/pts on '%s': %m", devpts);
goto fail;
}
- /* /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
- */
+ /* /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. */
r = is_symlink("/dev/ptmx");
- if (r < 0)
+ if (r < 0) {
+ log_debug_errno(r, "Failed to detect whether /dev/ptmx is a symlink or not: %m");
goto fail;
- if (r > 0) {
+ } else if (r > 0) {
devptmx = strjoina(temporary_mount, "/dev/ptmx");
if (symlink("pts/ptmx", devptmx) < 0) {
- r = -errno;
+ r = log_debug_errno(errno, "Failed to create a symlink '%s' to pts/ptmx: %m", devptmx);
goto fail;
}
} else {
@@ -680,20 +691,23 @@ static int mount_private_dev(MountEntry *m) {
(void) mkdir(devshm, 0755);
r = mount("/dev/shm", devshm, NULL, MS_BIND, NULL);
if (r < 0) {
- r = -errno;
+ r = log_debug_errno(errno, "Failed to bind mount /dev/shm on '%s': %m", devshm);
goto fail;
}
devmqueue = strjoina(temporary_mount, "/dev/mqueue");
(void) mkdir(devmqueue, 0755);
- (void) mount("/dev/mqueue", devmqueue, NULL, MS_BIND, NULL);
+ if (mount("/dev/mqueue", devmqueue, NULL, MS_BIND, NULL) < 0)
+ log_debug_errno(errno, "Failed to bind mount /dev/mqueue on '%s', ignoring: %m", devmqueue);
devhugepages = strjoina(temporary_mount, "/dev/hugepages");
(void) mkdir(devhugepages, 0755);
- (void) mount("/dev/hugepages", devhugepages, NULL, MS_BIND, NULL);
+ if (mount("/dev/hugepages", devhugepages, NULL, MS_BIND, NULL) < 0)
+ log_debug_errno(errno, "Failed to bind mount /dev/hugepages on '%s', ignoring: %m", devhugepages);
devlog = strjoina(temporary_mount, "/dev/log");
- (void) symlink("/run/systemd/journal/dev-log", devlog);
+ 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);
NULSTR_FOREACH(d, devnodes) {
r = clone_device_node(d, temporary_mount, &can_mknod);
@@ -702,7 +716,9 @@ static int mount_private_dev(MountEntry *m) {
goto fail;
}
- dev_setup(temporary_mount, UID_INVALID, GID_INVALID);
+ r = dev_setup(temporary_mount, UID_INVALID, GID_INVALID);
+ if (r < 0)
+ log_debug_errno(r, "Failed to setup basic device tree at '%s', ignoring: %m", temporary_mount);
/* Create the /dev directory if missing. It is more likely to be
* missing when the service is started with RootDirectory. This is
@@ -711,9 +727,12 @@ static int mount_private_dev(MountEntry *m) {
(void) mkdir_p_label(mount_entry_path(m), 0755);
/* Unmount everything in old /dev */
- umount_recursive(mount_entry_path(m), 0);
+ 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));
+
if (mount(dev, mount_entry_path(m), NULL, MS_MOVE, NULL) < 0) {
- r = -errno;
+ r = log_debug_errno(errno, "Failed to move mount point '%s' to '%s': %m", dev, mount_entry_path(m));
goto fail;
}
@@ -836,10 +855,10 @@ static int follow_symlink(
if (r > 0) /* Reached the end, nothing more to resolve */
return 1;
- if (m->n_followed >= CHASE_SYMLINKS_MAX) { /* put a boundary on things */
- log_debug("Symlink loop on '%s'.", mount_entry_path(m));
- return -ELOOP;
- }
+ 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));
log_debug("Followed mount entry path symlink %s → %s.", mount_entry_path(m), target);
@@ -881,10 +900,9 @@ static int apply_mount(
}
what = mode_to_inaccessible_node(target.st_mode);
- if (!what) {
- log_debug("File type not supported for inaccessible mounts. Note that symlinks are not allowed");
- return -ELOOP;
- }
+ if (!what)
+ return log_debug_errno(SYNTHETIC_ERRNO(ELOOP),
+ "File type not supported for inaccessible mounts. Note that symlinks are not allowed");
break;
}
@@ -999,7 +1017,17 @@ static int apply_mount(
return 0;
}
+/* Change the per-mount readonly flag on an existing mount */
+static int remount_bind_readonly(const char *path, unsigned long orig_flags) {
+ int r;
+
+ r = mount(NULL, path, NULL, MS_REMOUNT | MS_BIND | MS_RDONLY | orig_flags, NULL);
+
+ return r < 0 ? -errno : 0;
+}
+
static int make_read_only(const MountEntry *m, char **blacklist, FILE *proc_self_mountinfo) {
+ bool submounts = false;
int r = 0;
assert(m);
@@ -1007,15 +1035,15 @@ static int make_read_only(const MountEntry *m, char **blacklist, FILE *proc_self
if (mount_entry_read_only(m)) {
if (IN_SET(m->mode, EMPTY_DIR, TMPFS)) {
- /* Make superblock readonly */
- if (mount(NULL, mount_entry_path(m), NULL, MS_REMOUNT | MS_RDONLY | m->flags, mount_entry_options(m)) < 0)
- r = -errno;
- } else
+ r = remount_bind_readonly(mount_entry_path(m), m->flags);
+ } else {
+ submounts = true;
r = bind_remount_recursive_with_mountinfo(mount_entry_path(m), true, blacklist, proc_self_mountinfo);
+ }
} else if (m->mode == PRIVATE_DEV) {
- /* Superblock can be readonly but the submounts can't */
- if (mount(NULL, mount_entry_path(m), NULL, MS_REMOUNT|DEV_MOUNT_OPTIONS|MS_RDONLY, NULL) < 0)
- r = -errno;
+ /* 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. */
+ r = remount_bind_readonly(mount_entry_path(m), DEV_MOUNT_OPTIONS);
} else
return 0;
@@ -1026,27 +1054,28 @@ static int make_read_only(const MountEntry *m, char **blacklist, FILE *proc_self
if (r == -ENOENT && m->ignore)
r = 0;
- return r;
+ if (r < 0)
+ return log_debug_errno(r, "Failed to re-mount '%s'%s read-only: %m", mount_entry_path(m),
+ submounts ? " and its submounts" : "");
+
+ return 0;
}
-static bool namespace_info_mount_apivfs(const char *root_directory, const NamespaceInfo *ns_info) {
+static bool namespace_info_mount_apivfs(const NamespaceInfo *ns_info) {
assert(ns_info);
/*
* ProtectControlGroups= and ProtectKernelTunables= imply MountAPIVFS=,
* since to protect the API VFS mounts, they need to be around in the
- * first place... and RootDirectory= or RootImage= need to be set.
+ * first place...
*/
- /* root_directory should point to a mount point */
- return root_directory &&
- (ns_info->mount_apivfs ||
- ns_info->protect_control_groups ||
- ns_info->protect_kernel_tunables);
+ return ns_info->mount_apivfs ||
+ ns_info->protect_control_groups ||
+ ns_info->protect_kernel_tunables;
}
static size_t namespace_calculate_mounts(
- const char* root_directory,
const NamespaceInfo *ns_info,
char** read_write_paths,
char** read_only_paths,
@@ -1088,14 +1117,15 @@ static size_t namespace_calculate_mounts(
(ns_info->protect_control_groups ? 1 : 0) +
(ns_info->protect_kernel_modules ? ELEMENTSOF(protect_kernel_modules_table) : 0) +
protect_home_cnt + protect_system_cnt +
- (namespace_info_mount_apivfs(root_directory, ns_info) ? ELEMENTSOF(apivfs_table) : 0);
+ (namespace_info_mount_apivfs(ns_info) ? ELEMENTSOF(apivfs_table) : 0);
}
static void normalize_mounts(const char *root_directory, MountEntry *mounts, size_t *n_mounts) {
+ assert(root_directory);
assert(n_mounts);
assert(mounts || *n_mounts == 0);
- qsort_safe(mounts, *n_mounts, sizeof(MountEntry), mount_path_compare);
+ typesafe_qsort(mounts, *n_mounts, mount_path_compare);
drop_duplicates(mounts, n_mounts);
drop_outside_root(root_directory, mounts, n_mounts);
@@ -1127,11 +1157,9 @@ int setup_namespace(
_cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL;
_cleanup_free_ void *root_hash = NULL;
MountEntry *m, *mounts = NULL;
- size_t root_hash_size = 0;
- const char *root;
- size_t n_mounts;
- bool make_slave;
+ size_t n_mounts, root_hash_size = 0;
bool require_prefix = false;
+ const char *root;
int r = 0;
assert(ns_info);
@@ -1151,19 +1179,19 @@ int setup_namespace(
dissect_image_flags & DISSECT_IMAGE_READ_ONLY ? O_RDONLY : O_RDWR,
&loop_device);
if (r < 0)
- return r;
+ return log_debug_errno(r, "Failed to create loop device for root image: %m");
r = root_hash_load(root_image, &root_hash, &root_hash_size);
if (r < 0)
- return r;
+ return log_debug_errno(r, "Failed to load root hash: %m");
r = dissect_image(loop_device->fd, root_hash, root_hash_size, dissect_image_flags, &dissected_image);
if (r < 0)
- return r;
+ return log_debug_errno(r, "Failed to dissect image: %m");
r = dissected_image_decrypt(dissected_image, NULL, root_hash, root_hash_size, dissect_image_flags, &decrypted_image);
if (r < 0)
- return r;
+ return log_debug_errno(r, "Failed to decrypt dissected image: %m");
}
if (root_directory)
@@ -1181,7 +1209,6 @@ int setup_namespace(
}
n_mounts = namespace_calculate_mounts(
- root,
ns_info,
read_write_paths,
read_only_paths,
@@ -1192,9 +1219,6 @@ int setup_namespace(
tmp_dir, var_tmp_dir,
protect_home, protect_system);
- /* Set mount slave mode */
- make_slave = root || n_mounts > 0 || ns_info->private_mounts;
-
if (n_mounts > 0) {
m = mounts = (MountEntry *) alloca0(n_mounts * sizeof(MountEntry));
r = append_access_mounts(&m, read_write_paths, READWRITE, require_prefix);
@@ -1271,7 +1295,7 @@ int setup_namespace(
if (r < 0)
goto finish;
- if (namespace_info_mount_apivfs(root, ns_info)) {
+ if (namespace_info_mount_apivfs(ns_info)) {
r = append_static_mounts(&m, apivfs_table, ELEMENTSOF(apivfs_table), ns_info->ignore_protect_paths);
if (r < 0)
goto finish;
@@ -1284,33 +1308,44 @@ int setup_namespace(
if (r < 0)
goto finish;
- normalize_mounts(root_directory, mounts, &n_mounts);
+ normalize_mounts(root, mounts, &n_mounts);
}
+ /* All above is just preparation, figuring out what to do. Let's now actually start doing something. */
+
if (unshare(CLONE_NEWNS) < 0) {
- r = -errno;
+ r = log_debug_errno(errno, "Failed to unshare the mount namespace: %m");
+ if (IN_SET(r, -EACCES, -EPERM, -EOPNOTSUPP, -ENOSYS))
+ /* 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. */
+ r = -ENOANO;
+
goto finish;
}
- if (make_slave) {
- /* 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) {
- r = -errno;
- goto finish;
- }
+ /* 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) {
+ r = log_debug_errno(errno, "Failed to remount '/' as SLAVE: %m");
+ goto finish;
}
if (root_image) {
/* A root image is specified, mount it to the right place */
r = dissected_image_mount(dissected_image, root, UID_INVALID, dissect_image_flags);
- if (r < 0)
+ if (r < 0) {
+ log_debug_errno(r, "Failed to mount root image: %m");
goto finish;
+ }
if (decrypted_image) {
r = decrypted_image_relinquish(decrypted_image);
- if (r < 0)
+ if (r < 0) {
+ log_debug_errno(r, "Failed to relinquish decrypted image: %m");
goto finish;
+ }
}
loop_device_relinquish(loop_device);
@@ -1319,20 +1354,22 @@ int setup_namespace(
/* 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);
- if (r < 0)
+ if (r < 0) {
+ log_debug_errno(r, "Failed to detect that %s is a mount point or not: %m", root);
goto finish;
+ }
if (r == 0) {
if (mount(root, root, NULL, MS_BIND|MS_REC, NULL) < 0) {
- r = -errno;
+ r = log_debug_errno(errno, "Failed to bind mount '%s': %m", root);
goto finish;
}
}
- } else if (root) {
+ } else {
/* Let's mount the main root directory to the root directory to use */
if (mount("/", root, NULL, MS_BIND|MS_REC, NULL) < 0) {
- r = -errno;
+ r = log_debug_errno(errno, "Failed to bind mount '/' on '%s': %m", root);
goto finish;
}
}
@@ -1350,7 +1387,7 @@ int setup_namespace(
* For example, this is the case with the option: 'InaccessiblePaths=/proc' */
proc_self_mountinfo = fopen("/proc/self/mountinfo", "re");
if (!proc_self_mountinfo) {
- r = -errno;
+ r = log_debug_errno(errno, "Failed to open /proc/self/mountinfo: %m");
goto finish;
}
@@ -1385,7 +1422,7 @@ int setup_namespace(
if (!again)
break;
- normalize_mounts(root_directory, mounts, &n_mounts);
+ normalize_mounts(root, mounts, &n_mounts);
}
/* Create a blacklist we can pass to bind_mount_recursive() */
@@ -1402,18 +1439,18 @@ int setup_namespace(
}
}
- if (root) {
- /* MS_MOVE does not work on MS_SHARED so the remount MS_SHARED will be done later */
- r = mount_move_root(root);
- if (r < 0)
- goto finish;
+ /* MS_MOVE does not work on MS_SHARED so the remount MS_SHARED will be done later */
+ r = mount_move_root(root);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to mount root with MS_MOVE: %m");
+ goto finish;
}
/* Remount / as the desired mode. Note that this will not
* reestablish propagation from our side to the host, since
* what's disconnected is disconnected. */
if (mount(NULL, "/", NULL, mount_flags | MS_REC, NULL) < 0) {
- r = -errno;
+ r = log_debug_errno(errno, "Failed to remount '/' with desired mount flags: %m");
goto finish;
}