From ddbe041277a2b363364304ba03884e8458a882ca Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 22 Nov 2016 20:21:23 +0100 Subject: namespace: reindent protect_system_strict_table[] as well All other tables got reindented, but one was forgotten. Fix that. --- src/core/namespace.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'src/core/namespace.c') diff --git a/src/core/namespace.c b/src/core/namespace.c index aca47a4d2f..ee705af6b8 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -141,13 +141,13 @@ static const BindMount protect_system_full_table[] = { * shall manage those, orthogonally). */ static const BindMount protect_system_strict_table[] = { - { "/", READONLY, false }, - { "/proc", READWRITE, false }, /* ProtectKernelTunables= */ - { "/sys", READWRITE, false }, /* ProtectKernelTunables= */ - { "/dev", READWRITE, false }, /* PrivateDevices= */ - { "/home", READWRITE, true }, /* ProtectHome= */ - { "/run/user", READWRITE, true }, /* ProtectHome= */ - { "/root", READWRITE, true }, /* ProtectHome= */ + { "/", READONLY, false }, + { "/proc", READWRITE, false }, /* ProtectKernelTunables= */ + { "/sys", READWRITE, false }, /* ProtectKernelTunables= */ + { "/dev", READWRITE, false }, /* PrivateDevices= */ + { "/home", READWRITE, true }, /* ProtectHome= */ + { "/run/user", READWRITE, true }, /* ProtectHome= */ + { "/root", READWRITE, true }, /* ProtectHome= */ }; static const char *bind_mount_path(const BindMount *p) { -- cgit v1.2.3-54-g00ecf From cfbeb4ef8d836ad33572527b63eea383101d9aba Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 23 Nov 2016 01:09:14 +0100 Subject: namespace: add explicit read-only flag This reworks handling of the read-only management for mount points. This will become handy as soon as we add arbitrary bind mount support (which comes in a later commit). --- src/core/namespace.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) (limited to 'src/core/namespace.c') diff --git a/src/core/namespace.c b/src/core/namespace.c index ee705af6b8..e9702ae448 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -59,9 +59,10 @@ typedef enum MountMode { typedef struct BindMount { const char *path_const; /* Memory allocated on stack or static */ - MountMode mode:6; + MountMode mode:5; bool ignore:1; /* Ignore if path does not exist? */ bool has_prefix:1; /* Already is prefixed by the root dir? */ + bool read_only:1; /* Shall this mount point be read-only? */ char *path_malloc; /* Use this instead of 'path' if we had to allocate memory */ } BindMount; @@ -159,6 +160,12 @@ static const char *bind_mount_path(const BindMount *p) { return p->path_malloc ?: p->path_const; } +static bool bind_mount_read_only(const BindMount *p) { + assert(p); + + return p->read_only || IN_SET(p->mode, READONLY, INACCESSIBLE); +} + static int append_access_mounts(BindMount **p, char **strv, MountMode mode) { char **i; @@ -314,6 +321,7 @@ static void drop_duplicates(BindMount *m, unsigned *n) { * above. */ if (previous && path_equal(bind_mount_path(f), bind_mount_path(previous))) { log_debug("%s is duplicate.", bind_mount_path(f)); + previous->read_only = previous->read_only || bind_mount_read_only(f); /* Propagate the read-only flag to the remaining entry */ f->path_malloc = mfree(f->path_malloc); continue; } @@ -634,7 +642,7 @@ static int make_read_only(BindMount *m, char **blacklist) { assert(m); - if (IN_SET(m->mode, INACCESSIBLE, READONLY)) + if (bind_mount_read_only(m)) r = bind_remount_recursive(bind_mount_path(m), true, blacklist); else if (m->mode == PRIVATE_DEV) { /* Can be readonly but the submounts can't*/ if (mount(NULL, bind_mount_path(m), NULL, MS_REMOUNT|DEV_MOUNT_OPTIONS|MS_RDONLY, NULL) < 0) @@ -749,12 +757,13 @@ int setup_namespace( if (mount_flags == 0) mount_flags = MS_SHARED; - n_mounts = namespace_calculate_mounts(ns_info, - read_write_paths, - read_only_paths, - inaccessible_paths, - tmp_dir, var_tmp_dir, - protect_home, protect_system); + n_mounts = namespace_calculate_mounts( + ns_info, + read_write_paths, + read_only_paths, + inaccessible_paths, + tmp_dir, var_tmp_dir, + protect_home, protect_system); /* Set mount slave mode */ if (root_directory || n_mounts > 0) -- cgit v1.2.3-54-g00ecf From 34de407a4fca46cdb5e45aae149604fe10102ecf Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 14 Dec 2016 00:48:52 +0100 Subject: core: rename BindMount structure → MountEntry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After all, these don#t strictly encapsulate bind mounts anymore, and we are preparing this for adding arbitrary user-defined bind mounts in a later commit, at which point this would become really confusing. Let's clean this up, rename the BindMount structure to MountEntry, so that it is clear that it can contain information about any kind of mount. --- src/core/namespace.c | 148 +++++++++++++++++++++++++-------------------------- 1 file changed, 74 insertions(+), 74 deletions(-) (limited to 'src/core/namespace.c') diff --git a/src/core/namespace.c b/src/core/namespace.c index e9702ae448..1b4bcd5ba4 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -57,14 +57,14 @@ typedef enum MountMode { READWRITE, } MountMode; -typedef struct BindMount { +typedef struct MountEntry { const char *path_const; /* Memory allocated on stack or static */ MountMode mode:5; bool ignore:1; /* Ignore if path does not exist? */ bool has_prefix:1; /* Already is prefixed by the root dir? */ bool read_only:1; /* Shall this mount point be read-only? */ char *path_malloc; /* Use this instead of 'path' if we had to allocate memory */ -} BindMount; +} MountEntry; /* * The following Protect tables are to protect paths and mark some of them @@ -75,7 +75,7 @@ typedef struct BindMount { */ /* ProtectKernelTunables= option and the related filesystem APIs */ -static const BindMount protect_kernel_tunables_table[] = { +static const MountEntry protect_kernel_tunables_table[] = { { "/proc/sys", READONLY, false }, { "/proc/sysrq-trigger", READONLY, true }, { "/proc/latency_stats", READONLY, true }, @@ -94,7 +94,7 @@ static const BindMount protect_kernel_tunables_table[] = { }; /* ProtectKernelModules= option */ -static const BindMount protect_kernel_modules_table[] = { +static const MountEntry protect_kernel_modules_table[] = { #ifdef HAVE_SPLIT_USR { "/lib/modules", INACCESSIBLE, true }, #endif @@ -105,28 +105,28 @@ static const BindMount protect_kernel_modules_table[] = { * ProtectHome=read-only table, protect $HOME and $XDG_RUNTIME_DIR and rest of * system should be protected by ProtectSystem= */ -static const BindMount protect_home_read_only_table[] = { +static const MountEntry protect_home_read_only_table[] = { { "/home", READONLY, true }, { "/run/user", READONLY, true }, { "/root", READONLY, true }, }; /* ProtectHome=yes table */ -static const BindMount protect_home_yes_table[] = { +static const MountEntry protect_home_yes_table[] = { { "/home", INACCESSIBLE, true }, { "/run/user", INACCESSIBLE, true }, { "/root", INACCESSIBLE, true }, }; /* ProtectSystem=yes table */ -static const BindMount protect_system_yes_table[] = { +static const MountEntry protect_system_yes_table[] = { { "/usr", READONLY, false }, { "/boot", READONLY, true }, { "/efi", READONLY, true }, }; /* ProtectSystem=full includes ProtectSystem=yes */ -static const BindMount protect_system_full_table[] = { +static const MountEntry protect_system_full_table[] = { { "/usr", READONLY, false }, { "/boot", READONLY, true }, { "/efi", READONLY, true }, @@ -141,7 +141,7 @@ static const BindMount protect_system_full_table[] = { * (And of course /home and friends are also left writable, as ProtectHome= * shall manage those, orthogonally). */ -static const BindMount protect_system_strict_table[] = { +static const MountEntry protect_system_strict_table[] = { { "/", READONLY, false }, { "/proc", READWRITE, false }, /* ProtectKernelTunables= */ { "/sys", READWRITE, false }, /* ProtectKernelTunables= */ @@ -151,7 +151,7 @@ static const BindMount protect_system_strict_table[] = { { "/root", READWRITE, true }, /* ProtectHome= */ }; -static const char *bind_mount_path(const BindMount *p) { +static const char *mount_entry_path(const MountEntry *p) { assert(p); /* Returns the path of this bind mount. If the malloc()-allocated ->path_buffer field is set we return that, @@ -160,13 +160,13 @@ static const char *bind_mount_path(const BindMount *p) { return p->path_malloc ?: p->path_const; } -static bool bind_mount_read_only(const BindMount *p) { +static bool mount_entry_read_only(const MountEntry *p) { assert(p); return p->read_only || IN_SET(p->mode, READONLY, INACCESSIBLE); } -static int append_access_mounts(BindMount **p, char **strv, MountMode mode) { +static int append_access_mounts(MountEntry **p, char **strv, MountMode mode) { char **i; assert(p); @@ -190,7 +190,7 @@ static int append_access_mounts(BindMount **p, char **strv, MountMode mode) { if (!path_is_absolute(e)) return -EINVAL; - *((*p)++) = (BindMount) { + *((*p)++) = (MountEntry) { .path_const = e, .mode = mode, .ignore = ignore, @@ -201,7 +201,7 @@ static int append_access_mounts(BindMount **p, char **strv, MountMode mode) { return 0; } -static int append_static_mounts(BindMount **p, const BindMount *mounts, unsigned n, bool ignore_protect) { +static int append_static_mounts(MountEntry **p, const MountEntry *mounts, unsigned n, bool ignore_protect) { unsigned i; assert(p); @@ -210,8 +210,8 @@ static int append_static_mounts(BindMount **p, const BindMount *mounts, unsigned /* Adds a list of static pre-defined entries */ for (i = 0; i < n; i++) - *((*p)++) = (BindMount) { - .path_const = bind_mount_path(mounts+i), + *((*p)++) = (MountEntry) { + .path_const = mount_entry_path(mounts+i), .mode = mounts[i].mode, .ignore = mounts[i].ignore || ignore_protect, }; @@ -219,7 +219,7 @@ static int append_static_mounts(BindMount **p, const BindMount *mounts, unsigned return 0; } -static int append_protect_home(BindMount **p, ProtectHome protect_home, bool ignore_protect) { +static int append_protect_home(MountEntry **p, ProtectHome protect_home, bool ignore_protect) { assert(p); switch (protect_home) { @@ -238,7 +238,7 @@ static int append_protect_home(BindMount **p, ProtectHome protect_home, bool ign } } -static int append_protect_system(BindMount **p, ProtectSystem protect_system, bool ignore_protect) { +static int append_protect_system(MountEntry **p, ProtectSystem protect_system, bool ignore_protect) { assert(p); switch (protect_system) { @@ -261,11 +261,11 @@ static int append_protect_system(BindMount **p, ProtectSystem protect_system, bo } static int mount_path_compare(const void *a, const void *b) { - const BindMount *p = a, *q = b; + const MountEntry *p = a, *q = b; int d; /* If the paths are not equal, then order prefixes first */ - d = path_compare(bind_mount_path(p), bind_mount_path(q)); + d = path_compare(mount_entry_path(p), mount_entry_path(q)); if (d != 0) return d; @@ -279,7 +279,7 @@ static int mount_path_compare(const void *a, const void *b) { return 0; } -static int prefix_where_needed(BindMount *m, unsigned n, const char *root_directory) { +static int prefix_where_needed(MountEntry *m, unsigned n, const char *root_directory) { unsigned i; /* Prefixes all paths in the bind mount table with the root directory if it is specified and the entry needs @@ -294,7 +294,7 @@ static int prefix_where_needed(BindMount *m, unsigned n, const char *root_direct if (m[i].has_prefix) continue; - s = prefix_root(root_directory, bind_mount_path(m+i)); + s = prefix_root(root_directory, mount_entry_path(m+i)); if (!s) return -ENOMEM; @@ -307,8 +307,8 @@ static int prefix_where_needed(BindMount *m, unsigned n, const char *root_direct return 0; } -static void drop_duplicates(BindMount *m, unsigned *n) { - BindMount *f, *t, *previous; +static void drop_duplicates(MountEntry *m, unsigned *n) { + MountEntry *f, *t, *previous; assert(m); assert(n); @@ -319,9 +319,9 @@ static void drop_duplicates(BindMount *m, unsigned *n) { /* The first one wins (which is the one with the more restrictive mode), see mount_path_compare() * above. */ - if (previous && path_equal(bind_mount_path(f), bind_mount_path(previous))) { - log_debug("%s is duplicate.", bind_mount_path(f)); - previous->read_only = previous->read_only || bind_mount_read_only(f); /* Propagate the read-only flag to the remaining entry */ + if (previous && path_equal(mount_entry_path(f), mount_entry_path(previous))) { + log_debug("%s is duplicate.", mount_entry_path(f)); + previous->read_only = previous->read_only || mount_entry_read_only(f); /* Propagate the read-only flag to the remaining entry */ f->path_malloc = mfree(f->path_malloc); continue; } @@ -334,8 +334,8 @@ static void drop_duplicates(BindMount *m, unsigned *n) { *n = t - m; } -static void drop_inaccessible(BindMount *m, unsigned *n) { - BindMount *f, *t; +static void drop_inaccessible(MountEntry *m, unsigned *n) { + MountEntry *f, *t; const char *clear = NULL; assert(m); @@ -348,13 +348,13 @@ static void drop_inaccessible(BindMount *m, unsigned *n) { /* 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. */ - if (clear && path_startswith(bind_mount_path(f), clear)) { - log_debug("%s is masked by %s.", bind_mount_path(f), clear); + if (clear && path_startswith(mount_entry_path(f), clear)) { + log_debug("%s is masked by %s.", mount_entry_path(f), clear); f->path_malloc = mfree(f->path_malloc); continue; } - clear = f->mode == INACCESSIBLE ? bind_mount_path(f) : NULL; + clear = f->mode == INACCESSIBLE ? mount_entry_path(f) : NULL; *t = *f; t++; @@ -363,8 +363,8 @@ static void drop_inaccessible(BindMount *m, unsigned *n) { *n = t - m; } -static void drop_nop(BindMount *m, unsigned *n) { - BindMount *f, *t; +static void drop_nop(MountEntry *m, unsigned *n) { + MountEntry *f, *t; assert(m); assert(n); @@ -376,12 +376,12 @@ static void drop_nop(BindMount *m, unsigned *n) { /* Only suppress such subtrees for READONLY and READWRITE entries */ if (IN_SET(f->mode, READONLY, READWRITE)) { - BindMount *p; + MountEntry *p; bool found = false; /* Now let's find the first parent of the entry we are looking at. */ for (p = t-1; p >= m; p--) { - if (path_startswith(bind_mount_path(f), bind_mount_path(p))) { + if (path_startswith(mount_entry_path(f), mount_entry_path(p))) { found = true; break; } @@ -389,7 +389,7 @@ static void drop_nop(BindMount *m, unsigned *n) { /* 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) { - log_debug("%s is redundant by %s", bind_mount_path(f), bind_mount_path(p)); + log_debug("%s is redundant by %s", mount_entry_path(f), mount_entry_path(p)); f->path_malloc = mfree(f->path_malloc); continue; } @@ -402,8 +402,8 @@ static void drop_nop(BindMount *m, unsigned *n) { *n = t - m; } -static void drop_outside_root(const char *root_directory, BindMount *m, unsigned *n) { - BindMount *f, *t; +static void drop_outside_root(const char *root_directory, MountEntry *m, unsigned *n) { + MountEntry *f, *t; assert(m); assert(n); @@ -416,8 +416,8 @@ static void drop_outside_root(const char *root_directory, BindMount *m, unsigned for (f = m, t = m; f < m + *n; f++) { - if (!path_startswith(bind_mount_path(f), root_directory)) { - log_debug("%s is outside of root directory.", bind_mount_path(f)); + if (!path_startswith(mount_entry_path(f), root_directory)) { + log_debug("%s is outside of root directory.", mount_entry_path(f)); f->path_malloc = mfree(f->path_malloc); continue; } @@ -429,7 +429,7 @@ static void drop_outside_root(const char *root_directory, BindMount *m, unsigned *n = t - m; } -static int mount_dev(BindMount *m) { +static int mount_dev(MountEntry *m) { static const char devnodes[] = "/dev/null\0" "/dev/zero\0" @@ -534,11 +534,11 @@ static int mount_dev(BindMount *m) { * missing when the service is started with RootDirectory. This is * consistent with mount units creating the mount points when missing. */ - (void) mkdir_p_label(bind_mount_path(m), 0755); + (void) mkdir_p_label(mount_entry_path(m), 0755); /* Unmount everything in old /dev */ - umount_recursive(bind_mount_path(m), 0); - if (mount(dev, bind_mount_path(m), NULL, MS_MOVE, NULL) < 0) { + umount_recursive(mount_entry_path(m), 0); + if (mount(dev, mount_entry_path(m), NULL, MS_MOVE, NULL) < 0) { r = -errno; goto fail; } @@ -569,7 +569,7 @@ fail: } static int apply_mount( - BindMount *m, + MountEntry *m, const char *tmp_dir, const char *var_tmp_dir) { @@ -578,7 +578,7 @@ static int apply_mount( assert(m); - log_debug("Applying namespace mount on %s", bind_mount_path(m)); + log_debug("Applying namespace mount on %s", mount_entry_path(m)); switch (m->mode) { @@ -588,10 +588,10 @@ static int apply_mount( /* First, get rid of everything that is below if there * is anything... Then, overmount it with an * inaccessible path. */ - (void) umount_recursive(bind_mount_path(m), 0); + (void) umount_recursive(mount_entry_path(m), 0); - if (lstat(bind_mount_path(m), &target) < 0) - return log_debug_errno(errno, "Failed to lstat() %s to determine what to mount over it: %m", bind_mount_path(m)); + if (lstat(mount_entry_path(m), &target) < 0) + return log_debug_errno(errno, "Failed to lstat() %s to determine what to mount over it: %m", mount_entry_path(m)); what = mode_to_inaccessible_node(target.st_mode); if (!what) { @@ -604,13 +604,13 @@ static int apply_mount( case READONLY: case READWRITE: - r = path_is_mount_point(bind_mount_path(m), NULL, 0); + r = path_is_mount_point(mount_entry_path(m), NULL, 0); if (r < 0) - return log_debug_errno(r, "Failed to determine whether %s is already a mount point: %m", bind_mount_path(m)); + 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. */ return 0; /* This isn't a mount point yet, let's make it one. */ - what = bind_mount_path(m); + what = mount_entry_path(m); break; case PRIVATE_TMP: @@ -630,22 +630,22 @@ static int apply_mount( assert(what); - if (mount(what, bind_mount_path(m), NULL, MS_BIND|MS_REC, NULL) < 0) - return log_debug_errno(errno, "Failed to mount %s to %s: %m", what, bind_mount_path(m)); + if (mount(what, mount_entry_path(m), NULL, MS_BIND|MS_REC, NULL) < 0) + return log_debug_errno(errno, "Failed to mount %s to %s: %m", what, mount_entry_path(m)); - log_debug("Successfully mounted %s to %s", what, bind_mount_path(m)); + log_debug("Successfully mounted %s to %s", what, mount_entry_path(m)); return 0; } -static int make_read_only(BindMount *m, char **blacklist) { +static int make_read_only(MountEntry *m, char **blacklist) { int r = 0; assert(m); - if (bind_mount_read_only(m)) - r = bind_remount_recursive(bind_mount_path(m), true, blacklist); + if (mount_entry_read_only(m)) + r = bind_remount_recursive(mount_entry_path(m), true, blacklist); else if (m->mode == PRIVATE_DEV) { /* Can be readonly but the submounts can't*/ - if (mount(NULL, bind_mount_path(m), NULL, MS_REMOUNT|DEV_MOUNT_OPTIONS|MS_RDONLY, NULL) < 0) + if (mount(NULL, mount_entry_path(m), NULL, MS_REMOUNT|DEV_MOUNT_OPTIONS|MS_RDONLY, NULL) < 0) r = -errno; } else return 0; @@ -658,8 +658,8 @@ static int make_read_only(BindMount *m, char **blacklist) { } /* Chase symlinks and remove failed paths from mounts */ -static int chase_all_symlinks(const char *root_directory, BindMount *m, unsigned *n) { - BindMount *f, *t; +static int chase_all_symlinks(const char *root_directory, MountEntry *m, unsigned *n) { + MountEntry *f, *t; int r = 0; assert(m); @@ -673,20 +673,20 @@ static int chase_all_symlinks(const char *root_directory, BindMount *m, unsigned _cleanup_free_ char *chased = NULL; int k; - k = chase_symlinks(bind_mount_path(f), root_directory, 0, &chased); + k = chase_symlinks(mount_entry_path(f), root_directory, 0, &chased); if (k < 0) { /* Get only real errors */ if (r >= 0 && (k != -ENOENT || !f->ignore)) r = k; /* Doesn't exist or failed? Then remove it and continue! */ - log_debug_errno(k, "Failed to chase symlinks for %s: %m", bind_mount_path(f)); + log_debug_errno(k, "Failed to chase symlinks for %s: %m", mount_entry_path(f)); f->path_malloc = mfree(f->path_malloc); continue; } - if (!path_equal(bind_mount_path(f), chased)) { - log_debug("Chased %s → %s", bind_mount_path(f), chased); + if (!path_equal(mount_entry_path(f), chased)) { + log_debug("Chased %s → %s", mount_entry_path(f), chased); free(f->path_malloc); f->path_malloc = chased; @@ -749,7 +749,7 @@ int setup_namespace( ProtectSystem protect_system, unsigned long mount_flags) { - BindMount *m, *mounts = NULL; + MountEntry *m, *mounts = NULL; bool make_slave = false; unsigned n_mounts; int r = 0; @@ -770,7 +770,7 @@ int setup_namespace( make_slave = true; if (n_mounts > 0) { - m = mounts = (BindMount *) alloca0(n_mounts * sizeof(BindMount)); + m = mounts = (MountEntry *) alloca0(n_mounts * sizeof(MountEntry)); r = append_access_mounts(&m, read_write_paths, READWRITE); if (r < 0) goto finish; @@ -784,21 +784,21 @@ int setup_namespace( goto finish; if (tmp_dir) { - *(m++) = (BindMount) { + *(m++) = (MountEntry) { .path_const = "/tmp", .mode = PRIVATE_TMP, }; } if (var_tmp_dir) { - *(m++) = (BindMount) { + *(m++) = (MountEntry) { .path_const = "/var/tmp", .mode = PRIVATE_VAR_TMP, }; } if (ns_info->private_dev) { - *(m++) = (BindMount) { + *(m++) = (MountEntry) { .path_const = "/dev", .mode = PRIVATE_DEV, }; @@ -817,7 +817,7 @@ int setup_namespace( } if (ns_info->protect_control_groups) { - *(m++) = (BindMount) { + *(m++) = (MountEntry) { .path_const = "/sys/fs/cgroup", .mode = READONLY, }; @@ -845,7 +845,7 @@ int setup_namespace( if (r < 0) goto finish; - qsort(mounts, n_mounts, sizeof(BindMount), mount_path_compare); + qsort(mounts, n_mounts, sizeof(MountEntry), mount_path_compare); drop_duplicates(mounts, &n_mounts); drop_outside_root(root_directory, mounts, &n_mounts); @@ -894,7 +894,7 @@ int setup_namespace( /* Create a blacklist we can pass to bind_mount_recursive() */ blacklist = newa(char*, n_mounts+1); for (j = 0; j < n_mounts; j++) - blacklist[j] = (char*) bind_mount_path(mounts+j); + blacklist[j] = (char*) mount_entry_path(mounts+j); blacklist[j] = NULL; /* Second round, flip the ro bits if necessary. */ -- cgit v1.2.3-54-g00ecf From 8fceda937f3a177d9e27b403fb5e1b34138b05f5 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 14 Dec 2016 00:51:37 +0100 Subject: namespace: instead of chasing mount symlinks a priori, do so as-we-go This is relevant as many of the mounts we try to establish only can be followed when some other prior mount that is a prefix of it is established. Hence: move the symlink chasing into the actual mount functions, so that we do it as late as possibly but as early as necessary. Fixes: #4588 --- src/core/namespace.c | 86 +++++++++++++++++++++------------------------------- 1 file changed, 34 insertions(+), 52 deletions(-) (limited to 'src/core/namespace.c') diff --git a/src/core/namespace.c b/src/core/namespace.c index 1b4bcd5ba4..746ebc45b0 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -568,7 +568,33 @@ fail: return r; } +static int mount_entry_chase(MountEntry *m, const char *root_directory) { + char *chased; + int r; + + assert(m); + + /* Since mount() will always follow symlinks and we need to take the different root directory into account we + * chase the symlinks on our own first. */ + + r = chase_symlinks(mount_entry_path(m), root_directory, 0, &chased); + if (r == -ENOENT && m->ignore) { + log_debug_errno(r, "Path %s does not exist, ignoring.", mount_entry_path(m)); + return 0; + } + if (r < 0) + return log_debug_errno(r, "Failed to follow symlinks on %s: %m", mount_entry_path(m)); + + log_debug("Followed symlinks %s → %s.", mount_entry_path(m), chased); + + free(m->path_malloc); + m->path_malloc = chased; + + return 1; +} + static int apply_mount( + const char *root_directory, MountEntry *m, const char *tmp_dir, const char *var_tmp_dir) { @@ -578,6 +604,10 @@ static int apply_mount( assert(m); + r = mount_entry_chase(m, root_directory); + if (r <= 0) + return r; + log_debug("Applying namespace mount on %s", mount_entry_path(m)); switch (m->mode) { @@ -604,7 +634,7 @@ static int apply_mount( case READONLY: case READWRITE: - r = path_is_mount_point(mount_entry_path(m), NULL, 0); + r = path_is_mount_point(mount_entry_path(m), root_directory, 0); if (r < 0) 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. */ @@ -654,50 +684,9 @@ static int make_read_only(MountEntry *m, char **blacklist) { * already stays this way. This improves compatibility with container managers, where we won't attempt to undo * read-only mounts already applied. */ - return r; -} - -/* Chase symlinks and remove failed paths from mounts */ -static int chase_all_symlinks(const char *root_directory, MountEntry *m, unsigned *n) { - MountEntry *f, *t; - int r = 0; - - assert(m); - assert(n); - - /* Since mount() will always follow symlinks and we need to take the different root directory into account we - * chase the symlinks on our own first. This call wil do so for all entries and remove all entries where we - * can't resolve the path, and which have been marked for such removal. */ - - for (f = m, t = m; f < m + *n; f++) { - _cleanup_free_ char *chased = NULL; - int k; - - k = chase_symlinks(mount_entry_path(f), root_directory, 0, &chased); - if (k < 0) { - /* Get only real errors */ - if (r >= 0 && (k != -ENOENT || !f->ignore)) - r = k; - - /* Doesn't exist or failed? Then remove it and continue! */ - log_debug_errno(k, "Failed to chase symlinks for %s: %m", mount_entry_path(f)); - f->path_malloc = mfree(f->path_malloc); - continue; - } - - if (!path_equal(mount_entry_path(f), chased)) { - log_debug("Chased %s → %s", mount_entry_path(f), chased); - - free(f->path_malloc); - f->path_malloc = chased; - chased = NULL; - } - - *t = *f; - t++; - } + if (r == -ENOENT && m->ignore) + r = 0; - *n = t - m; return r; } @@ -838,13 +827,6 @@ int setup_namespace( if (r < 0) goto finish; - /* Resolve symlinks manually first, as mount() will always follow them relative to the host's - * root. Moreover we want to suppress duplicates based on the resolved paths. This of course is a bit - * racy. */ - r = chase_all_symlinks(root_directory, mounts, &n_mounts); - if (r < 0) - goto finish; - qsort(mounts, n_mounts, sizeof(MountEntry), mount_path_compare); drop_duplicates(mounts, &n_mounts); @@ -886,7 +868,7 @@ int setup_namespace( /* First round, add in all special mounts we need */ for (m = mounts; m < mounts + n_mounts; ++m) { - r = apply_mount(m, tmp_dir, var_tmp_dir); + r = apply_mount(root_directory, m, tmp_dir, var_tmp_dir); if (r < 0) goto finish; } -- cgit v1.2.3-54-g00ecf From d2d6c096f6373a76f3b303a7a116e7cfe7139c4d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 23 Nov 2016 22:21:40 +0100 Subject: core: add ability to define arbitrary bind mounts for services This adds two new settings BindPaths= and BindReadOnlyPaths=. They allow defining arbitrary bind mounts specific to particular services. This is particularly useful for services with RootDirectory= set as this permits making specific bits of the host directory available to chrooted services. The two new settings follow the concepts nspawn already possess in --bind= and --bind-ro=, as well as the .nspawn settings Bind= and BindReadOnly= (and these latter options should probably be renamed to BindPaths= and BindReadOnlyPaths= too). Fixes: #3439 --- man/systemd.exec.xml | 25 +++++++ src/core/dbus-execute.c | 115 +++++++++++++++++++++++++++++- src/core/execute.c | 16 +++++ src/core/execute.h | 2 + src/core/load-fragment-gperf.gperf.m4 | 2 + src/core/load-fragment.c | 127 ++++++++++++++++++++++++++++++++++ src/core/load-fragment.h | 1 + src/core/namespace.c | 127 +++++++++++++++++++++++++++++++--- src/core/namespace.h | 44 ++++++++---- src/shared/bus-unit-util.c | 70 +++++++++++++++++++ src/test/test-ns.c | 1 + 11 files changed, 503 insertions(+), 27 deletions(-) (limited to 'src/core/namespace.c') diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index f27e4a5c04..812e615530 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -967,6 +967,31 @@ SystemCallFilter=~@mount. + + BindPaths= + BindReadOnlyPaths= + + Configures unit-specific bind mounts. A bind mount makes a particular file or directory + available at an additional place in the unit's view of the file system. Any bind mounts created with this + option are specific to the unit, and are not visible in the host's mount table. This option expects a + whitespace separated list of bind mount definitions. Each definition consists of a colon-separated triple of + source path, destination path and option string, where the latter two are optional. If only a source path is + specified the source and destination is taken to be the same. The option string may be either + rbind or norbind for configuring a recursive or non-recursive bind + mount. If the destination parth is omitted, the option string must be omitted too. + + BindPaths= creates regular writable bind mounts (unless the source file system mount + is already marked read-only), while BindReadOnlyPaths= creates read-only bind mounts. These + settings may be used more than once, each usage appends to the unit's list of bind mounts. If the empty string + is assigned to either of these two options the entire list of bind mounts defined prior to this is reset. Note + that in this case both read-only and regular bind mounts are reset, regardless which of the two settings is + used. + + This option is particularly useful when RootDirectory= is used. In this case the + source path refers to a path on the host file system, while the destination path referes to a path below the + root directory of the unit. + + PrivateTmp= diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 78b177e107..b3fc0ff5c3 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -675,6 +675,49 @@ static int property_get_output_fdname( return sd_bus_message_append(reply, "s", name); } +static int property_get_bind_paths( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + unsigned i; + bool ro; + int r; + + assert(bus); + assert(c); + assert(property); + assert(reply); + + ro = !!strstr(property, "ReadOnly"); + + r = sd_bus_message_open_container(reply, 'a', "(ssbt)"); + if (r < 0) + return r; + + for (i = 0; i < c->n_bind_mounts; i++) { + + if (ro != c->bind_mounts[i].read_only) + continue; + + r = sd_bus_message_append( + reply, "(ssbt)", + c->bind_mounts[i].source, + c->bind_mounts[i].destination, + c->bind_mounts[i].ignore_enoent, + c->bind_mounts[i].recursive ? MS_REC : 0); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST), @@ -783,6 +826,8 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("MemoryDenyWriteExecute", "b", bus_property_get_bool, offsetof(ExecContext, memory_deny_write_execute), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RestrictRealtime", "b", bus_property_get_bool, offsetof(ExecContext, restrict_realtime), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RestrictNamespaces", "t", bus_property_get_ulong, offsetof(ExecContext, restrict_namespaces), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("BindPaths", "a(ssbt)", property_get_bind_paths, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("BindReadOnlyPaths", "a(ssbt)", property_get_bind_paths, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_VTABLE_END }; @@ -1364,8 +1409,8 @@ int bus_exec_context_set_transient_property( if (r < 0) return r; - if (!isempty(path) && !path_is_absolute(path)) - return sd_bus_error_set_errnof(error, EINVAL, "Path %s is not absolute.", path); + if (!path_is_absolute(path)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Path %s is not absolute.", path); if (mode != UNIT_CHECK) { char *buf = NULL; @@ -1629,8 +1674,74 @@ int bus_exec_context_set_transient_property( unit_write_drop_in_private_format(u, mode, name, "%s=%s", name, strempty(mount_propagation_flags_to_string(flags))); } + return 1; + } else if (STR_IN_SET(name, "BindPaths", "BindReadOnlyPaths")) { + unsigned empty = true; + + r = sd_bus_message_enter_container(message, 'a', "(ssbt)"); + if (r < 0) + return r; + + while ((r = sd_bus_message_enter_container(message, 'r', "ssbt")) > 0) { + const char *source, *destination; + int ignore_enoent; + uint64_t mount_flags; + + r = sd_bus_message_read(message, "ssbt", &source, &destination, &ignore_enoent, &mount_flags); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (!path_is_absolute(source)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path %s is not absolute.", source); + if (!path_is_absolute(destination)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is not absolute.", source); + if (!IN_SET(mount_flags, 0, MS_REC)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown mount flags."); + + if (mode != UNIT_CHECK) { + r = bind_mount_add(&c->bind_mounts, &c->n_bind_mounts, + &(BindMount) { + .source = strdup(source), + .destination = strdup(destination), + .read_only = !!strstr(name, "ReadOnly"), + .recursive = !!(mount_flags & MS_REC), + .ignore_enoent = ignore_enoent, + }); + if (r < 0) + return r; + + unit_write_drop_in_private_format( + u, mode, name, + "%s=%s%s:%s:%s", + name, + ignore_enoent ? "-" : "", + source, + destination, + (mount_flags & MS_REC) ? "rbind" : "norbind"); + } + + empty = false; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (empty) { + bind_mount_free_many(c->bind_mounts, c->n_bind_mounts); + c->bind_mounts = NULL; + c->n_bind_mounts = 0; + } + return 1; } + ri = rlimit_from_string(name); if (ri < 0) { soft = endswith(name, "Soft"); diff --git a/src/core/execute.c b/src/core/execute.c index 07ab067c05..2ee8c9a416 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -1826,6 +1826,9 @@ static bool exec_needs_mount_namespace( !strv_isempty(context->inaccessible_paths)) return true; + if (context->n_bind_mounts > 0) + return true; + if (context->mount_flags != 0) return true; @@ -2147,6 +2150,8 @@ static int apply_mount_namespace(Unit *u, const ExecContext *context, r = setup_namespace(root_dir, &ns_info, rw, context->read_only_paths, context->inaccessible_paths, + context->bind_mounts, + context->n_bind_mounts, tmp, var, context->protect_home, @@ -3086,6 +3091,8 @@ void exec_context_done(ExecContext *c) { c->read_write_paths = strv_free(c->read_write_paths); c->inaccessible_paths = strv_free(c->inaccessible_paths); + bind_mount_free_many(c->bind_mounts, c->n_bind_mounts); + if (c->cpuset) CPU_FREE(c->cpuset); @@ -3569,6 +3576,15 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { fputs("\n", f); } + if (c->n_bind_mounts > 0) + for (i = 0; i < c->n_bind_mounts; i++) { + fprintf(f, "%s%s: %s:%s:%s\n", prefix, + c->bind_mounts[i].read_only ? "BindReadOnlyPaths" : "BindPaths", + c->bind_mounts[i].source, + c->bind_mounts[i].destination, + c->bind_mounts[i].recursive ? "rbind" : "norbind"); + } + if (c->utmp_id) fprintf(f, "%sUtmpIdentifier: %s\n", diff --git a/src/core/execute.h b/src/core/execute.h index 951c8f4da3..84ab4339cf 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -161,6 +161,8 @@ struct ExecContext { char **read_write_paths, **read_only_paths, **inaccessible_paths; unsigned long mount_flags; + BindMount *bind_mounts; + unsigned n_bind_mounts; uint64_t capability_bounding_set; uint64_t capability_ambient_set; diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index 2610442b91..15f22a2681 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -89,6 +89,8 @@ $1.InaccessibleDirectories, config_parse_namespace_path_strv, 0, $1.ReadWritePaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_write_paths) $1.ReadOnlyPaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_only_paths) $1.InaccessiblePaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.inaccessible_paths) +$1.BindPaths, config_parse_bind_paths, 0, offsetof($1, exec_context) +$1.BindReadOnlyPaths, config_parse_bind_paths, 0, offsetof($1, exec_context) $1.PrivateTmp, config_parse_bool, 0, offsetof($1, exec_context.private_tmp) $1.PrivateDevices, config_parse_bool, 0, offsetof($1, exec_context.private_devices) $1.ProtectKernelTunables, config_parse_bool, 0, offsetof($1, exec_context.protect_kernel_tunables) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index a2e7097de0..f325d853c6 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -3891,6 +3891,132 @@ int config_parse_namespace_path_strv( return 0; } +int config_parse_bind_paths( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + const char *p; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + bind_mount_free_many(c->bind_mounts, c->n_bind_mounts); + c->bind_mounts = NULL; + c->n_bind_mounts = 0; + return 0; + } + + p = rvalue; + for (;;) { + _cleanup_free_ char *source = NULL, *destination = NULL; + char *s = NULL, *d = NULL; + bool rbind = true, ignore_enoent = false; + + r = extract_first_word(&p, &source, ":" WHITESPACE, EXTRACT_QUOTES|EXTRACT_DONT_COALESCE_SEPARATORS); + if (r == 0) + break; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s: %s", lvalue, rvalue); + return 0; + } + + s = source; + if (s[0] == '-') { + ignore_enoent = true; + s++; + } + + if (!utf8_is_valid(s)) { + log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, s); + return 0; + } + if (!path_is_absolute(s)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Not an absolute source path, ignoring: %s", s); + return 0; + } + + path_kill_slashes(s); + + /* Optionally, the destination is specified. */ + if (p && p[-1] == ':') { + r = extract_first_word(&p, &destination, ":" WHITESPACE, EXTRACT_QUOTES|EXTRACT_DONT_COALESCE_SEPARATORS); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s: %s", lvalue, rvalue); + return 0; + } + if (r == 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Missing argument after ':': %s", rvalue); + return 0; + } + + if (!utf8_is_valid(destination)) { + log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, destination); + return 0; + } + if (!path_is_absolute(destination)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Not an absolute destination path, ignoring: %s", destination); + return 0; + } + + d = path_kill_slashes(destination); + + /* Optionally, there's also a short option string specified */ + if (p && p[-1] == ':') { + _cleanup_free_ char *options = NULL; + + r = extract_first_word(&p, &options, NULL, EXTRACT_QUOTES); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s: %s", lvalue, rvalue); + return 0; + } + + if (isempty(options) || streq(options, "rbind")) + rbind = true; + else if (streq(options, "norbind")) + rbind = false; + else { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid option string, ignoring setting: %s", options); + return 0; + } + } + } else + d = s; + + r = bind_mount_add(&c->bind_mounts, &c->n_bind_mounts, + &(BindMount) { + .source = s, + .destination = d, + .read_only = !!strstr(lvalue, "ReadOnly"), + .recursive = rbind, + .ignore_enoent = ignore_enoent, + }); + if (r < 0) + return log_oom(); + } + + return 0; +} + int config_parse_no_new_privileges( const char* unit, const char *filename, @@ -4388,6 +4514,7 @@ void unit_dump_config_items(FILE *f) { { config_parse_sec, "SECONDS" }, { config_parse_nsec, "NANOSECONDS" }, { config_parse_namespace_path_strv, "PATH [...]" }, + { config_parse_bind_paths, "PATH[:PATH[:OPTIONS]] [...]" }, { config_parse_unit_requires_mounts_for, "PATH [...]" }, { config_parse_exec_mount_flags, "MOUNTFLAG [...]" }, { config_parse_unit_string_printf, "STRING" }, diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index 1cff815a50..bbac2d84b5 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -117,6 +117,7 @@ int config_parse_sec_fix_0(const char *unit, const char *filename, unsigned line int config_parse_user_group(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_user_group_strv(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_restrict_namespaces(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_bind_paths(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); /* gperf prototypes */ const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, unsigned length); diff --git a/src/core/namespace.c b/src/core/namespace.c index 746ebc45b0..834883267c 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -50,6 +50,8 @@ typedef enum MountMode { /* This is ordered by priority! */ INACCESSIBLE, + BIND_MOUNT, + BIND_MOUNT_RECURSIVE, READONLY, PRIVATE_TMP, PRIVATE_VAR_TMP, @@ -64,6 +66,8 @@ typedef struct MountEntry { bool has_prefix:1; /* Already is prefixed by the root dir? */ bool read_only:1; /* Shall this mount point be read-only? */ char *path_malloc; /* Use this instead of 'path' if we had to allocate memory */ + const char *source_const; /* The source path, for bind mounts */ + char *source_malloc; } MountEntry; /* @@ -166,6 +170,12 @@ static bool mount_entry_read_only(const MountEntry *p) { return p->read_only || IN_SET(p->mode, READONLY, INACCESSIBLE); } +static const char *mount_entry_source(const MountEntry *p) { + assert(p); + + return p->source_malloc ?: p->source_const; +} + static int append_access_mounts(MountEntry **p, char **strv, MountMode mode) { char **i; @@ -201,6 +211,25 @@ static int append_access_mounts(MountEntry **p, char **strv, MountMode mode) { return 0; } +static int append_bind_mounts(MountEntry **p, const BindMount *binds, unsigned n) { + unsigned i; + + assert(p); + + for (i = 0; i < n; i++) { + const BindMount *b = binds + i; + + *((*p)++) = (MountEntry) { + .path_const = b->destination, + .mode = b->recursive ? BIND_MOUNT_RECURSIVE : BIND_MOUNT, + .read_only = b->read_only, + .source_const = b->source, + }; + } + + return 0; +} + static int append_static_mounts(MountEntry **p, const MountEntry *mounts, unsigned n, bool ignore_protect) { unsigned i; @@ -568,27 +597,33 @@ fail: return r; } -static int mount_entry_chase(MountEntry *m, const char *root_directory) { +static int mount_entry_chase( + const char *root_directory, + MountEntry *m, + const char *path, + char **location) { + char *chased; int r; assert(m); /* Since mount() will always follow symlinks and we need to take the different root directory into account we - * chase the symlinks on our own first. */ + * chase the symlinks on our own first. This is called for the destination path, as well as the source path (if + * that applies). The result is stored in "location". */ - r = chase_symlinks(mount_entry_path(m), root_directory, 0, &chased); + r = chase_symlinks(path, root_directory, 0, &chased); if (r == -ENOENT && m->ignore) { - log_debug_errno(r, "Path %s does not exist, ignoring.", mount_entry_path(m)); + log_debug_errno(r, "Path %s does not exist, ignoring.", path); return 0; } if (r < 0) - return log_debug_errno(r, "Failed to follow symlinks on %s: %m", mount_entry_path(m)); + return log_debug_errno(r, "Failed to follow symlinks on %s: %m", path); - log_debug("Followed symlinks %s → %s.", mount_entry_path(m), chased); + log_debug("Followed symlinks %s → %s.", path, chased); - free(m->path_malloc); - m->path_malloc = chased; + free(*location); + *location = chased; return 1; } @@ -600,11 +635,12 @@ static int apply_mount( const char *var_tmp_dir) { const char *what; + bool rbind = true; int r; assert(m); - r = mount_entry_chase(m, root_directory); + r = mount_entry_chase(root_directory, m, mount_entry_path(m), &m->path_malloc); if (r <= 0) return r; @@ -633,7 +669,6 @@ static int apply_mount( case READONLY: case READWRITE: - r = path_is_mount_point(mount_entry_path(m), root_directory, 0); if (r < 0) return log_debug_errno(r, "Failed to determine whether %s is already a mount point: %m", mount_entry_path(m)); @@ -643,6 +678,19 @@ static int apply_mount( what = mount_entry_path(m); break; + case BIND_MOUNT: + rbind = false; + /* fallthrough */ + + case BIND_MOUNT_RECURSIVE: + /* Also chase the source mount */ + r = mount_entry_chase(root_directory, m, mount_entry_source(m), &m->source_malloc); + if (r <= 0) + return r; + + what = mount_entry_source(m); + break; + case PRIVATE_TMP: what = tmp_dir; break; @@ -660,7 +708,7 @@ static int apply_mount( assert(what); - if (mount(what, mount_entry_path(m), NULL, MS_BIND|MS_REC, NULL) < 0) + if (mount(what, mount_entry_path(m), NULL, MS_BIND|(rbind ? MS_REC : 0), NULL) < 0) return log_debug_errno(errno, "Failed to mount %s to %s: %m", what, mount_entry_path(m)); log_debug("Successfully mounted %s to %s", what, mount_entry_path(m)); @@ -695,6 +743,8 @@ static unsigned namespace_calculate_mounts( char** read_write_paths, char** read_only_paths, char** inaccessible_paths, + const BindMount *bind_mounts, + unsigned n_bind_mounts, const char* tmp_dir, const char* var_tmp_dir, ProtectHome protect_home, @@ -719,6 +769,7 @@ static unsigned namespace_calculate_mounts( strv_length(read_write_paths) + strv_length(read_only_paths) + strv_length(inaccessible_paths) + + n_bind_mounts + ns_info->private_dev + (ns_info->protect_kernel_tunables ? ELEMENTSOF(protect_kernel_tunables_table) : 0) + (ns_info->protect_control_groups ? 1 : 0) + @@ -732,6 +783,8 @@ int setup_namespace( char** read_write_paths, char** read_only_paths, char** inaccessible_paths, + const BindMount *bind_mounts, + unsigned n_bind_mounts, const char* tmp_dir, const char* var_tmp_dir, ProtectHome protect_home, @@ -751,6 +804,7 @@ int setup_namespace( read_write_paths, read_only_paths, inaccessible_paths, + bind_mounts, n_bind_mounts, tmp_dir, var_tmp_dir, protect_home, protect_system); @@ -772,6 +826,10 @@ int setup_namespace( if (r < 0) goto finish; + r = append_bind_mounts(&m, bind_mounts, n_bind_mounts); + if (r < 0) + goto finish; + if (tmp_dir) { *(m++) = (MountEntry) { .path_const = "/tmp", @@ -911,6 +969,53 @@ finish: return r; } +void bind_mount_free_many(BindMount *b, unsigned n) { + unsigned i; + + assert(b || n == 0); + + for (i = 0; i < n; i++) { + free(b[i].source); + free(b[i].destination); + } + + free(b); +} + +int bind_mount_add(BindMount **b, unsigned *n, const BindMount *item) { + _cleanup_free_ char *s = NULL, *d = NULL; + BindMount *c; + + assert(b); + assert(n); + assert(item); + + s = strdup(item->source); + if (!s) + return -ENOMEM; + + d = strdup(item->destination); + if (!d) + return -ENOMEM; + + c = realloc_multiply(*b, sizeof(BindMount), *n + 1); + if (!c) + return -ENOMEM; + + *b = c; + + c[(*n) ++] = (BindMount) { + .source = s, + .destination = d, + .read_only = item->read_only, + .recursive = item->recursive, + .ignore_enoent = item->ignore_enoent, + }; + + s = d = NULL; + return 0; +} + static int setup_one_tmp_dir(const char *id, const char *prefix, char **path) { _cleanup_free_ char *x = NULL; char bid[SD_ID128_STRING_MAX]; diff --git a/src/core/namespace.h b/src/core/namespace.h index 2c278fd457..de3edc419c 100644 --- a/src/core/namespace.h +++ b/src/core/namespace.h @@ -21,6 +21,7 @@ ***/ typedef struct NameSpaceInfo NameSpaceInfo; +typedef struct BindMount BindMount; #include @@ -51,20 +52,32 @@ struct NameSpaceInfo { bool protect_kernel_modules:1; }; -int setup_namespace(const char *chroot, - const NameSpaceInfo *ns_info, - char **read_write_paths, - char **read_only_paths, - char **inaccessible_paths, - const char *tmp_dir, - const char *var_tmp_dir, - ProtectHome protect_home, - ProtectSystem protect_system, - unsigned long mount_flags); - -int setup_tmp_dirs(const char *id, - char **tmp_dir, - char **var_tmp_dir); +struct BindMount { + char *source; + char *destination; + bool read_only:1; + bool recursive:1; + bool ignore_enoent:1; +}; + +int setup_namespace( + const char *root_directory, + const NameSpaceInfo *ns_info, + char **read_write_paths, + char **read_only_paths, + char **inaccessible_paths, + const BindMount *bind_mounts, + unsigned n_bind_mounts, + const char *tmp_dir, + const char *var_tmp_dir, + ProtectHome protect_home, + ProtectSystem protect_system, + unsigned long mount_flags); + +int setup_tmp_dirs( + const char *id, + char **tmp_dir, + char **var_tmp_dir); int setup_netns(int netns_storage_socket[2]); @@ -73,3 +86,6 @@ ProtectHome protect_home_from_string(const char *s) _pure_; const char* protect_system_to_string(ProtectSystem p) _const_; ProtectSystem protect_system_from_string(const char *s) _pure_; + +void bind_mount_free_many(BindMount *b, unsigned n); +int bind_mount_add(BindMount **b, unsigned *n, const BindMount *item); diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 6dd9f2ccd4..735b8effc9 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -590,6 +590,76 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen } r = sd_bus_message_append(m, "v", "t", f); + } else if (STR_IN_SET(field, "BindPaths", "BindReadOnlyPaths")) { + const char *p = eq; + + r = sd_bus_message_open_container(m, 'v', "a(ssbt)"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "(ssbt)"); + if (r < 0) + return r; + + for (;;) { + _cleanup_free_ char *source = NULL, *destination = NULL; + char *s = NULL, *d = NULL; + bool ignore_enoent = false; + uint64_t flags = MS_REC; + + r = extract_first_word(&p, &source, ":" WHITESPACE, EXTRACT_QUOTES|EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return log_error_errno(r, "Failed to parse argument: %m"); + if (r == 0) + break; + + s = source; + if (s[0] == '-') { + ignore_enoent = true; + s++; + } + + if (p && p[-1] == ':') { + r = extract_first_word(&p, &destination, ":" WHITESPACE, EXTRACT_QUOTES|EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return log_error_errno(r, "Failed to parse argument: %m"); + if (r == 0) { + log_error("Missing argument after ':': %s", eq); + return -EINVAL; + } + + d = destination; + + if (p && p[-1] == ':') { + _cleanup_free_ char *options = NULL; + + r = extract_first_word(&p, &options, NULL, EXTRACT_QUOTES); + if (r < 0) + return log_error_errno(r, "Failed to parse argument: %m"); + + if (isempty(options) || streq(options, "rbind")) + flags = MS_REC; + else if (streq(options, "norbind")) + flags = 0; + else { + log_error("Unknown options: %s", eq); + return -EINVAL; + } + } + } else + d = s; + + + r = sd_bus_message_append(m, "(ssbt)", s, d, ignore_enoent, flags); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); } else { log_error("Unknown assignment %s.", assignment); return -EINVAL; diff --git a/src/test/test-ns.c b/src/test/test-ns.c index da7a8b0565..c99bcb371b 100644 --- a/src/test/test-ns.c +++ b/src/test/test-ns.c @@ -81,6 +81,7 @@ int main(int argc, char *argv[]) { (char **) writable, (char **) readonly, (char **) inaccessible, + &(BindMount) { .source = (char*) "/usr/bin", .destination = (char*) "/etc/systemd", .read_only = true }, 1, tmp_dir, var_tmp_dir, PROTECT_HOME_NO, -- cgit v1.2.3-54-g00ecf