diff --git a/TRANSIENT-SETTINGS.md b/TRANSIENT-SETTINGS.md
index 3614456acf..ca9e8387b7 100644
--- a/TRANSIENT-SETTINGS.md
+++ b/TRANSIENT-SETTINGS.md
@@ -170,6 +170,7 @@ All execution-related settings are available for transient units.
✓ InaccessiblePaths=
✓ BindPaths=
✓ BindReadOnlyPaths=
+✓ TemporaryFileSystem=
✓ PrivateTmp=
✓ PrivateDevices=
✓ ProtectKernelTunables=
diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
index d4dc2843ec..ba07d0feb2 100644
--- a/man/systemd.exec.xml
+++ b/man/systemd.exec.xml
@@ -175,7 +175,9 @@
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 path is omitted, the option string must be omitted too.
+ mount. If the destination path is omitted, the option string must be omitted too.
+ Each bind mount definition may be prefixed with -, in which case it will be ignored
+ when its source path does not exist.
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
@@ -786,14 +788,24 @@ CapabilityBoundingSet=~CAP_B CAP_C
ProtectHome=
- Takes a boolean argument or read-only. If true, the directories
- /home, /root and /run/user are made inaccessible
- and empty for processes invoked by this unit. If set to read-only, the three directories are
- made read-only instead. It is recommended to enable this setting for all long-running services (in particular
- network-facing ones), to ensure they cannot get access to private user data, unless the services actually
- require access to the user's private data. This setting is implied if DynamicUser= is
- set. For this setting the same restrictions regarding mount propagation and privileges apply as for
- ReadOnlyPaths= and related calls, see below.
+ Takes a boolean argument or the special values read-only or
+ tmpfs. If true, the directories /home, /root and
+ /run/user are made inaccessible and empty for processes invoked by this unit. If set to
+ read-only, the three directories are made read-only instead. If set to tmpfs,
+ temporary file systems are mounted on the three directories in read-only mode. The value tmpfs
+ is useful to hide home directories not relevant to the processes invoked by the unit, while necessary directories
+ are still visible by combining with BindPaths= or BindReadOnlyPaths=.
+
+ Setting this to yes is mostly equivalent to set the three directories in
+ InaccessiblePaths=. Similary, read-only is mostly equivalent to
+ ReadOnlyPaths=, and tmpfs is mostly equivalent to
+ TemporaryFileSystem=.
+
+ It is recommended to enable this setting for all long-running services (in particular network-facing ones),
+ to ensure they cannot get access to private user data, unless the services actually require access to the user's
+ private data. This setting is implied if DynamicUser= is set. For this setting the same
+ restrictions regarding mount propagation and privileges apply as for ReadOnlyPaths= and related
+ calls, see below.
@@ -930,6 +942,29 @@ CapabilityBoundingSet=~CAP_B CAP_C
SystemCallFilter=~@mount.
+
+ TemporaryFileSystem=
+
+ Takes a space-separated list of mount points for temporary file systems (tmpfs). If set, a new file
+ system namespace is set up for executed processes, and a temporary file system is mounted on each mount point.
+ This option may be specified more than once, in which case temporary file systems are mounted on all listed mount
+ points. If the empty string is assigned to this option, the list is reset, and all prior assignments have no effect.
+ Each mount point may optionally be suffixed with a colon (:) and mount options such as
+ size=10% or ro. By default, each temporary file system is mounted
+ with nodev,strictatime,mode=0755. These can be disabled by explicitly specifying the corresponding
+ mount options, e.g., dev or nostrictatime.
+
+ This is useful to hide files or directories not relevant to the processes invoked by the unit, while necessary
+ files or directories can be still accessed by combining with BindPaths= or
+ BindReadOnlyPaths=. See the example below.
+
+ Example: if a unit has the following,
+ TemporaryFileSystem=/var:ro
+BindReadOnlyPaths=/var/lib/systemd
+ then the invoked processes by the unit cannot see any files or directories under /var except for
+ /var/lib/systemd or its contents.
+
+
PrivateTmp=
diff --git a/src/basic/meson.build b/src/basic/meson.build
index 44cd31ecbe..d0e499d0d2 100644
--- a/src/basic/meson.build
+++ b/src/basic/meson.build
@@ -317,6 +317,7 @@ libbasic = static_library(
dependencies : [threads,
libcap,
libblkid,
+ libmount,
libselinux],
c_args : ['-fvisibility=default'],
install : false)
diff --git a/src/basic/mount-util.c b/src/basic/mount-util.c
index b124345659..8151b3a4e4 100644
--- a/src/basic/mount-util.c
+++ b/src/basic/mount-util.c
@@ -27,8 +27,12 @@
#include
#include
+/* Include later */
+#include
+
#include "alloc-util.h"
#include "escape.h"
+#include "extract-word.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
@@ -810,29 +814,37 @@ int mount_verbose(
unsigned long flags,
const char *options) {
- _cleanup_free_ char *fl = NULL;
+ _cleanup_free_ char *fl = NULL, *o = NULL;
+ unsigned long f;
+ int r;
- fl = mount_flags_to_string(flags);
+ r = mount_option_mangle(options, flags, &f, &o);
+ if (r < 0)
+ return log_full_errno(error_log_level, r,
+ "Failed to mangle mount options %s: %m",
+ strempty(options));
- if ((flags & MS_REMOUNT) && !what && !type)
+ fl = mount_flags_to_string(f);
+
+ if ((f & MS_REMOUNT) && !what && !type)
log_debug("Remounting %s (%s \"%s\")...",
- where, strnull(fl), strempty(options));
+ where, strnull(fl), strempty(o));
else if (!what && !type)
log_debug("Mounting %s (%s \"%s\")...",
- where, strnull(fl), strempty(options));
- else if ((flags & MS_BIND) && !type)
+ where, strnull(fl), strempty(o));
+ else if ((f & MS_BIND) && !type)
log_debug("Bind-mounting %s on %s (%s \"%s\")...",
- what, where, strnull(fl), strempty(options));
- else if (flags & MS_MOVE)
+ what, where, strnull(fl), strempty(o));
+ else if (f & MS_MOVE)
log_debug("Moving mount %s → %s (%s \"%s\")...",
- what, where, strnull(fl), strempty(options));
+ what, where, strnull(fl), strempty(o));
else
log_debug("Mounting %s on %s (%s \"%s\")...",
- strna(type), where, strnull(fl), strempty(options));
- if (mount(what, where, type, flags, options) < 0)
+ strna(type), where, strnull(fl), strempty(o));
+ if (mount(what, where, type, f, o) < 0)
return log_full_errno(error_log_level, errno,
"Failed to mount %s on %s (%s \"%s\"): %m",
- strna(type), where, strnull(fl), strempty(options));
+ strna(type), where, strnull(fl), strempty(o));
return 0;
}
@@ -874,3 +886,73 @@ int mount_propagation_flags_from_string(const char *name, unsigned long *ret) {
return -EINVAL;
return 0;
}
+
+int mount_option_mangle(
+ const char *options,
+ unsigned long mount_flags,
+ unsigned long *ret_mount_flags,
+ char **ret_remaining_options) {
+
+ const struct libmnt_optmap *map;
+ _cleanup_free_ char *ret = NULL;
+ const char *p;
+ int r;
+
+ /* This extracts mount flags from the mount options, and store
+ * non-mount-flag options to '*ret_remaining_options'.
+ * E.g.,
+ * "rw,nosuid,nodev,relatime,size=1630748k,mode=700,uid=1000,gid=1000"
+ * is split to MS_NOSUID|MS_NODEV|MS_RELATIME and
+ * "size=1630748k,mode=700,uid=1000,gid=1000".
+ * See more examples in test-mount-utils.c.
+ *
+ * Note that if 'options' does not contain any non-mount-flag options,
+ * then '*ret_remaining_options' is set to NULL instread of empty string.
+ * Note that this does not check validity of options stored in
+ * '*ret_remaining_options'.
+ * Note that if 'options' is NULL, then this just copies 'mount_flags'
+ * to '*ret_mount_flags'. */
+
+ assert(ret_mount_flags);
+ assert(ret_remaining_options);
+
+ map = mnt_get_builtin_optmap(MNT_LINUX_MAP);
+ if (!map)
+ return -EINVAL;
+
+ p = options;
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+ const struct libmnt_optmap *ent;
+
+ r = extract_first_word(&p, &word, ",", EXTRACT_QUOTES);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ for (ent = map; ent->name; ent++) {
+ /* All entries in MNT_LINUX_MAP do not take any argument.
+ * Thus, ent->name does not contain "=" or "[=]". */
+ if (!streq(word, ent->name))
+ continue;
+
+ if (!(ent->mask & MNT_INVERT))
+ mount_flags |= ent->id;
+ else if (mount_flags & ent->id)
+ mount_flags ^= ent->id;
+
+ break;
+ }
+
+ /* If 'word' is not a mount flag, then store it in '*ret_remaining_options'. */
+ if (!ent->name && !strextend_with_separator(&ret, ",", word, NULL))
+ return -ENOMEM;
+ }
+
+ *ret_mount_flags = mount_flags;
+ *ret_remaining_options = ret;
+ ret = NULL;
+
+ return 0;
+}
diff --git a/src/basic/mount-util.h b/src/basic/mount-util.h
index a81d019277..fea3e938a8 100644
--- a/src/basic/mount-util.h
+++ b/src/basic/mount-util.h
@@ -67,3 +67,9 @@ int umount_verbose(const char *where);
const char *mount_propagation_flags_to_string(unsigned long flags);
int mount_propagation_flags_from_string(const char *name, unsigned long *ret);
+
+int mount_option_mangle(
+ const char *options,
+ unsigned long mount_flags,
+ unsigned long *ret_mount_flags,
+ char **ret_remaining_options);
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
index 628fdcd1e5..f1361ca20b 100644
--- a/src/core/dbus-execute.c
+++ b/src/core/dbus-execute.c
@@ -781,6 +781,42 @@ static int property_get_bind_paths(
return sd_bus_message_close_container(reply);
}
+static int property_get_temporary_filesystems(
+ 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;
+ int r;
+
+ assert(bus);
+ assert(c);
+ assert(property);
+ assert(reply);
+
+ r = sd_bus_message_open_container(reply, 'a', "(ss)");
+ if (r < 0)
+ return r;
+
+ for (i = 0; i < c->n_temporary_filesystems; i++) {
+ TemporaryFileSystem *t = c->temporary_filesystems + i;
+
+ r = sd_bus_message_append(
+ reply, "(ss)",
+ t->path,
+ t->options);
+ if (r < 0)
+ return r;
+ }
+
+ return sd_bus_message_close_container(reply);
+}
+
static int property_get_log_extra_fields(
sd_bus *bus,
const char *path,
@@ -934,6 +970,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
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_PROPERTY("TemporaryFileSystem", "a(ss)", property_get_temporary_filesystems, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("MountAPIVFS", "b", bus_property_get_bool, offsetof(ExecContext, mount_apivfs), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("KeyringMode", "s", property_get_exec_keyring_mode, offsetof(ExecContext, keyring_mode), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -2293,11 +2330,7 @@ int bus_exec_context_set_transient_property(
int ignore;
const char *s;
- r = sd_bus_message_enter_container(message, 'r', "bs");
- if (r < 0)
- return r;
-
- r = sd_bus_message_read(message, "bs", &ignore, &s);
+ r = sd_bus_message_read(message, "(bs)", &ignore, &s);
if (r < 0)
return r;
@@ -2328,24 +2361,16 @@ int bus_exec_context_set_transient_property(
return 1;
} else if (STR_IN_SET(name, "BindPaths", "BindReadOnlyPaths")) {
- unsigned empty = true;
+ const char *source, *destination;
+ int ignore_enoent;
+ uint64_t mount_flags;
+ bool 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;
+ while ((r = sd_bus_message_read(message, "(ssbt)", &source, &destination, &ignore_enoent, &mount_flags)) > 0) {
if (!path_is_absolute(source))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path %s is not absolute.", source);
@@ -2393,6 +2418,51 @@ int bus_exec_context_set_transient_property(
unit_write_settingf(u, flags, name, "%s=", name);
}
+ return 1;
+
+ } else if (streq(name, "TemporaryFileSystem")) {
+ const char *path, *options;
+ bool empty = true;
+
+ r = sd_bus_message_enter_container(message, 'a', "(ss)");
+ if (r < 0)
+ return r;
+
+ while ((r = sd_bus_message_read(message, "(ss)", &path, &options)) > 0) {
+
+ if (!path_is_absolute(path))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Mount point %s is not absolute.", path);
+
+ if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+ r = temporary_filesystem_add(&c->temporary_filesystems, &c->n_temporary_filesystems, path, options);
+ if (r < 0)
+ return r;
+
+ unit_write_settingf(
+ u, flags|UNIT_ESCAPE_SPECIFIERS, name,
+ "%s=%s:%s",
+ name,
+ path,
+ options);
+ }
+
+ empty = false;
+ }
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_exit_container(message);
+ if (r < 0)
+ return r;
+
+ if (empty) {
+ temporary_filesystem_free_many(c->temporary_filesystems, c->n_temporary_filesystems);
+ c->temporary_filesystems = NULL;
+ c->n_temporary_filesystems = 0;
+
+ unit_write_settingf(u, flags, name, "%s=", name);
+ }
+
return 1;
}
diff --git a/src/core/execute.c b/src/core/execute.c
index bebd4eca80..4b041edc15 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -1793,6 +1793,9 @@ static bool exec_needs_mount_namespace(
if (context->n_bind_mounts > 0)
return true;
+ if (context->n_temporary_filesystems > 0)
+ return true;
+
if (context->mount_flags != 0)
return true;
@@ -2258,10 +2261,8 @@ static int compile_bind_mounts(
}
r = strv_consume(&empty_directories, private_root);
- if (r < 0) {
- r = -ENOMEM;
+ if (r < 0)
goto finish;
- }
}
STRV_FOREACH(suffix, context->directories[t].paths) {
@@ -2373,6 +2374,8 @@ static int apply_mount_namespace(
empty_directories,
bind_mounts,
n_bind_mounts,
+ context->temporary_filesystems,
+ context->n_temporary_filesystems,
tmp,
var,
needs_sandboxing ? context->protect_home : PROTECT_HOME_NO,
@@ -3623,6 +3626,11 @@ void exec_context_done(ExecContext *c) {
c->inaccessible_paths = strv_free(c->inaccessible_paths);
bind_mount_free_many(c->bind_mounts, c->n_bind_mounts);
+ c->bind_mounts = NULL;
+ c->n_bind_mounts = 0;
+ temporary_filesystem_free_many(c->temporary_filesystems, c->n_temporary_filesystems);
+ c->temporary_filesystems = NULL;
+ c->n_temporary_filesystems = 0;
c->cpuset = cpu_set_mfree(c->cpuset);
@@ -4173,12 +4181,22 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
}
if (c->n_bind_mounts > 0)
- for (i = 0; i < c->n_bind_mounts; i++) {
- fprintf(f, "%s%s: %s:%s:%s\n", prefix,
+ for (i = 0; i < c->n_bind_mounts; i++)
+ fprintf(f, "%s%s: %s%s:%s:%s\n", prefix,
c->bind_mounts[i].read_only ? "BindReadOnlyPaths" : "BindPaths",
+ c->bind_mounts[i].ignore_enoent ? "-": "",
c->bind_mounts[i].source,
c->bind_mounts[i].destination,
c->bind_mounts[i].recursive ? "rbind" : "norbind");
+
+ if (c->n_temporary_filesystems > 0)
+ for (i = 0; i < c->n_temporary_filesystems; i++) {
+ TemporaryFileSystem *t = c->temporary_filesystems + i;
+
+ fprintf(f, "%sTemporaryFileSystem: %s%s%s\n", prefix,
+ t->path,
+ isempty(t->options) ? "" : ":",
+ strempty(t->options));
}
if (c->utmp_id)
diff --git a/src/core/execute.h b/src/core/execute.h
index 4023b30164..a34cf0b873 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -219,6 +219,8 @@ struct ExecContext {
unsigned long mount_flags;
BindMount *bind_mounts;
unsigned n_bind_mounts;
+ TemporaryFileSystem *temporary_filesystems;
+ unsigned n_temporary_filesystems;
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 dde5010e02..5d90a7c054 100644
--- a/src/core/load-fragment-gperf.gperf.m4
+++ b/src/core/load-fragment-gperf.gperf.m4
@@ -104,6 +104,7 @@ $1.ReadOnlyPaths, config_parse_namespace_path_strv, 0,
$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.TemporaryFileSystem, config_parse_temporary_filesystems, 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 c4f91fb262..1b9888c10a 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -4174,6 +4174,83 @@ int config_parse_namespace_path_strv(
return 0;
}
+int config_parse_temporary_filesystems(
+ 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) {
+
+ Unit *u = userdata;
+ ExecContext *c = data;
+ const char *cur;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (isempty(rvalue)) {
+ /* Empty assignment resets the list */
+ temporary_filesystem_free_many(c->temporary_filesystems, c->n_temporary_filesystems);
+ c->temporary_filesystems = NULL;
+ c->n_temporary_filesystems = 0;
+ return 0;
+ }
+
+ cur = rvalue;
+ for (;;) {
+ _cleanup_free_ char *word = NULL, *path = NULL, *resolved = NULL;
+ const char *w;
+
+ r = extract_first_word(&cur, &word, NULL, EXTRACT_QUOTES);
+ if (r == 0)
+ break;
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract first word, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ w = word;
+ r = extract_first_word(&w, &path, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EINVAL;
+
+ r = unit_full_printf(u, path, &resolved);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers in %s, ignoring: %m", word);
+ continue;
+ }
+
+ if (!path_is_absolute(resolved)) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Not an absolute path, ignoring: %s", resolved);
+ continue;
+ }
+
+ path_kill_slashes(resolved);
+
+ r = temporary_filesystem_add(&c->temporary_filesystems, &c->n_temporary_filesystems, path, w);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse mount options, ignoring: %s", word);
+ continue;
+ }
+ }
+
+ return 0;
+}
+
int config_parse_bind_paths(
const char *unit,
const char *filename,
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
index cb17bdd3c3..163b5ce485 100644
--- a/src/core/load-fragment.h
+++ b/src/core/load-fragment.h
@@ -106,6 +106,7 @@ int config_parse_runtime_preserve_mode(const char *unit, const char *filename, u
int config_parse_exec_directories(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_set_status(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_namespace_path_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_temporary_filesystems(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_no_new_privileges(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_cpu_quota(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_protect_home(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);
diff --git a/src/core/namespace.c b/src/core/namespace.c
index 72f0d038d1..f605d239bc 100644
--- a/src/core/namespace.c
+++ b/src/core/namespace.c
@@ -58,7 +58,6 @@ typedef enum MountMode {
BIND_MOUNT,
BIND_MOUNT_RECURSIVE,
PRIVATE_TMP,
- PRIVATE_VAR_TMP,
PRIVATE_DEV,
BIND_DEV,
EMPTY_DIR,
@@ -66,6 +65,7 @@ typedef enum MountMode {
PROCFS,
READONLY,
READWRITE,
+ TMPFS,
} MountMode;
typedef struct MountEntry {
@@ -74,9 +74,12 @@ typedef struct MountEntry {
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 */
+ char *path_malloc; /* Use this instead of 'path_const' if we had to allocate memory */
const char *source_const; /* The source path, for bind mounts */
char *source_malloc;
+ 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. */
} MountEntry;
/* If MountAPIVFS= is used, let's mount /sys and /proc into the it, but only as a fallback if the user hasn't mounted
@@ -125,6 +128,13 @@ static const MountEntry protect_home_read_only_table[] = {
{ "/root", READONLY, true },
};
+/* ProtectHome=tmpfs table */
+static const MountEntry protect_home_tmpfs_table[] = {
+ { "/home", TMPFS, true, .read_only = true, .options_const = "mode=0755", .flags = MS_NODEV|MS_STRICTATIME },
+ { "/run/user", TMPFS, true, .read_only = true, .options_const = "mode=0755", .flags = MS_NODEV|MS_STRICTATIME },
+ { "/root", TMPFS, true, .read_only = true, .options_const = "mode=0700", .flags = MS_NODEV|MS_STRICTATIME },
+};
+
/* ProtectHome=yes table */
static const MountEntry protect_home_yes_table[] = {
{ "/home", INACCESSIBLE, true },
@@ -186,11 +196,18 @@ static const char *mount_entry_source(const MountEntry *p) {
return p->source_malloc ?: p->source_const;
}
+static const char *mount_entry_options(const MountEntry *p) {
+ assert(p);
+
+ return p->options_malloc ?: p->options_const;
+}
+
static void mount_entry_done(MountEntry *p) {
assert(p);
p->path_malloc = mfree(p->path_malloc);
p->source_malloc = mfree(p->source_malloc);
+ p->options_malloc = mfree(p->options_malloc);
}
static int append_access_mounts(MountEntry **p, char **strv, MountMode mode, bool forcibly_require_prefix) {
@@ -244,6 +261,8 @@ static int append_empty_dir_mounts(MountEntry **p, char **strv) {
.ignore = false,
.has_prefix = false,
.read_only = true,
+ .options_const = "mode=755",
+ .flags = MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME,
};
}
@@ -263,12 +282,56 @@ static int append_bind_mounts(MountEntry **p, const BindMount *binds, unsigned n
.mode = b->recursive ? BIND_MOUNT_RECURSIVE : BIND_MOUNT,
.read_only = b->read_only,
.source_const = b->source,
+ .ignore = b->ignore_enoent,
};
}
return 0;
}
+static int append_tmpfs_mounts(MountEntry **p, const TemporaryFileSystem *tmpfs, unsigned n) {
+ unsigned i;
+ int r;
+
+ assert(p);
+
+ 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;
+ bool ro = false;
+
+ if (!path_is_absolute(t->path))
+ return -EINVAL;
+
+ if (!isempty(t->options)) {
+ 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;
+
+ ro = !!(flags & MS_RDONLY);
+ if (ro)
+ flags ^= MS_RDONLY;
+ }
+
+ *((*p)++) = (MountEntry) {
+ .path_const = t->path,
+ .mode = TMPFS,
+ .read_only = ro,
+ .options_malloc = o,
+ .flags = flags,
+ };
+
+ o = NULL;
+ }
+
+ return 0;
+}
+
static int append_static_mounts(MountEntry **p, const MountEntry *mounts, unsigned n, bool ignore_protect) {
unsigned i;
@@ -298,6 +361,9 @@ static int append_protect_home(MountEntry **p, ProtectHome protect_home, bool ig
case PROTECT_HOME_READ_ONLY:
return append_static_mounts(p, protect_home_read_only_table, ELEMENTSOF(protect_home_read_only_table), ignore_protect);
+ case PROTECT_HOME_TMPFS:
+ return append_static_mounts(p, protect_home_tmpfs_table, ELEMENTSOF(protect_home_tmpfs_table), ignore_protect);
+
case PROTECT_HOME_YES:
return append_static_mounts(p, protect_home_yes_table, ELEMENTSOF(protect_home_yes_table), ignore_protect);
@@ -366,9 +432,7 @@ static int prefix_where_needed(MountEntry *m, unsigned n, const char *root_direc
if (!s)
return -ENOMEM;
- free(m[i].path_malloc);
- m[i].path_malloc = s;
-
+ free_and_replace(m[i].path_malloc, s);
m[i].has_prefix = true;
}
@@ -651,7 +715,7 @@ fail:
return r;
}
-static int mount_bind_dev(MountEntry *m) {
+static int mount_bind_dev(const MountEntry *m) {
int r;
assert(m);
@@ -673,7 +737,7 @@ static int mount_bind_dev(MountEntry *m) {
return 1;
}
-static int mount_sysfs(MountEntry *m) {
+static int mount_sysfs(const MountEntry *m) {
int r;
assert(m);
@@ -693,7 +757,7 @@ static int mount_sysfs(MountEntry *m) {
return 1;
}
-static int mount_procfs(MountEntry *m) {
+static int mount_procfs(const MountEntry *m) {
int r;
assert(m);
@@ -713,15 +777,15 @@ static int mount_procfs(MountEntry *m) {
return 1;
}
-static int mount_empty_dir(MountEntry *m) {
+static int mount_tmpfs(const MountEntry *m) {
assert(m);
- /* First, get rid of everything that is below if there is anything. Then, overmount with our new empty dir */
+ /* First, get rid of everything that is below if there is anything. Then, overmount with our new tmpfs */
(void) mkdir_p_label(mount_entry_path(m), 0755);
(void) umount_recursive(mount_entry_path(m), 0);
- if (mount("tmpfs", mount_entry_path(m), "tmpfs", MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, "mode=755") < 0)
+ if (mount("tmpfs", mount_entry_path(m), "tmpfs", m->flags, mount_entry_options(m)) < 0)
return log_debug_errno(errno, "Failed to mount %s: %m", mount_entry_path(m));
return 1;
@@ -729,13 +793,13 @@ static int mount_empty_dir(MountEntry *m) {
static int mount_entry_chase(
const char *root_directory,
- MountEntry *m,
+ const MountEntry *m,
const char *path,
+ bool chase_nonexistent,
char **location) {
char *chased;
int r;
- unsigned flags = 0;
assert(m);
@@ -743,19 +807,7 @@ static int mount_entry_chase(
* 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". */
- if (IN_SET(m->mode,
- BIND_MOUNT,
- BIND_MOUNT_RECURSIVE,
- PRIVATE_TMP,
- PRIVATE_VAR_TMP,
- PRIVATE_DEV,
- BIND_DEV,
- EMPTY_DIR,
- SYSFS,
- PROCFS))
- flags |= CHASE_NONEXISTENT;
-
- r = chase_symlinks(path, root_directory, flags, &chased);
+ r = chase_symlinks(path, root_directory, chase_nonexistent ? CHASE_NONEXISTENT : 0, &chased);
if (r == -ENOENT && m->ignore) {
log_debug_errno(r, "Path %s does not exist, ignoring.", path);
return 0;
@@ -773,9 +825,7 @@ static int mount_entry_chase(
static int apply_mount(
const char *root_directory,
- MountEntry *m,
- const char *tmp_dir,
- const char *var_tmp_dir) {
+ MountEntry *m) {
bool rbind = true, make = false;
const char *what;
@@ -783,7 +833,7 @@ static int apply_mount(
assert(m);
- r = mount_entry_chase(root_directory, m, mount_entry_path(m), &m->path_malloc);
+ r = mount_entry_chase(root_directory, m, mount_entry_path(m), !IN_SET(m->mode, INACCESSIBLE, READONLY, READWRITE), &m->path_malloc);
if (r <= 0)
return r;
@@ -828,7 +878,7 @@ static int apply_mount(
case BIND_MOUNT_RECURSIVE:
/* Also chase the source mount */
- r = mount_entry_chase(root_directory, m, mount_entry_source(m), &m->source_malloc);
+ r = mount_entry_chase(root_directory, m, mount_entry_source(m), false, &m->source_malloc);
if (r <= 0)
return r;
@@ -837,15 +887,11 @@ static int apply_mount(
break;
case EMPTY_DIR:
- return mount_empty_dir(m);
+ case TMPFS:
+ return mount_tmpfs(m);
case PRIVATE_TMP:
- what = tmp_dir;
- make = true;
- break;
-
- case PRIVATE_VAR_TMP:
- what = var_tmp_dir;
+ what = mount_entry_source(m);
make = true;
break;
@@ -902,15 +948,21 @@ static int apply_mount(
return 0;
}
-static int make_read_only(MountEntry *m, char **blacklist, FILE *proc_self_mountinfo) {
+static int make_read_only(const MountEntry *m, char **blacklist, FILE *proc_self_mountinfo) {
int r = 0;
assert(m);
assert(proc_self_mountinfo);
- if (mount_entry_read_only(m))
- 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_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 = 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;
} else
@@ -949,8 +1001,8 @@ static unsigned namespace_calculate_mounts(
char** read_only_paths,
char** inaccessible_paths,
char** empty_directories,
- const BindMount *bind_mounts,
unsigned n_bind_mounts,
+ unsigned n_temporary_filesystems,
const char* tmp_dir,
const char* var_tmp_dir,
ProtectHome protect_home,
@@ -969,7 +1021,9 @@ static unsigned namespace_calculate_mounts(
(protect_home == PROTECT_HOME_YES ?
ELEMENTSOF(protect_home_yes_table) :
((protect_home == PROTECT_HOME_READ_ONLY) ?
- ELEMENTSOF(protect_home_read_only_table) : 0));
+ ELEMENTSOF(protect_home_read_only_table) :
+ ((protect_home == PROTECT_HOME_TMPFS) ?
+ ELEMENTSOF(protect_home_tmpfs_table) : 0)));
return !!tmp_dir + !!var_tmp_dir +
strv_length(read_write_paths) +
@@ -977,6 +1031,7 @@ static unsigned namespace_calculate_mounts(
strv_length(inaccessible_paths) +
strv_length(empty_directories) +
n_bind_mounts +
+ n_temporary_filesystems +
ns_info->private_dev +
(ns_info->protect_kernel_tunables ? ELEMENTSOF(protect_kernel_tunables_table) : 0) +
(ns_info->protect_control_groups ? 1 : 0) +
@@ -995,6 +1050,8 @@ int setup_namespace(
char** empty_directories,
const BindMount *bind_mounts,
unsigned n_bind_mounts,
+ const TemporaryFileSystem *temporary_filesystems,
+ unsigned n_temporary_filesystems,
const char* tmp_dir,
const char* var_tmp_dir,
ProtectHome protect_home,
@@ -1046,7 +1103,7 @@ int setup_namespace(
if (root_directory)
root = root_directory;
- else if (root_image || n_bind_mounts > 0) {
+ else if (root_image || n_bind_mounts > 0 || n_temporary_filesystems > 0) {
/* If we are booting from an image, create a mount point for the image, if it's still missing. We use
* the same mount point for all images, which is safe, since they all live in their own namespaces
@@ -1067,7 +1124,8 @@ int setup_namespace(
read_only_paths,
inaccessible_paths,
empty_directories,
- bind_mounts, n_bind_mounts,
+ n_bind_mounts,
+ n_temporary_filesystems,
tmp_dir, var_tmp_dir,
protect_home, protect_system);
@@ -1097,17 +1155,23 @@ int setup_namespace(
if (r < 0)
goto finish;
+ r = append_tmpfs_mounts(&m, temporary_filesystems, n_temporary_filesystems);
+ if (r < 0)
+ goto finish;
+
if (tmp_dir) {
*(m++) = (MountEntry) {
.path_const = "/tmp",
.mode = PRIVATE_TMP,
+ .source_const = tmp_dir,
};
}
if (var_tmp_dir) {
*(m++) = (MountEntry) {
.path_const = "/var/tmp",
- .mode = PRIVATE_VAR_TMP,
+ .mode = PRIVATE_TMP,
+ .source_const = var_tmp_dir,
};
}
@@ -1235,7 +1299,7 @@ int setup_namespace(
/* First round, add in all special mounts we need */
for (m = mounts; m < mounts + n_mounts; ++m) {
- r = apply_mount(root, m, tmp_dir, var_tmp_dir);
+ r = apply_mount(root, m);
if (r < 0)
goto finish;
}
@@ -1261,7 +1325,7 @@ int setup_namespace(
goto finish;
}
- /* Remount / as the desired mode. Not that this will not
+ /* 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) {
@@ -1325,6 +1389,57 @@ int bind_mount_add(BindMount **b, unsigned *n, const BindMount *item) {
return 0;
}
+void temporary_filesystem_free_many(TemporaryFileSystem *t, unsigned n) {
+ unsigned i;
+
+ 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,
+ unsigned *n,
+ 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;
+ }
+
+ c = realloc_multiply(*t, sizeof(TemporaryFileSystem), *n + 1);
+ if (!c)
+ return -ENOMEM;
+
+ *t = c;
+
+ c[(*n) ++] = (TemporaryFileSystem) {
+ .path = p,
+ .options = o,
+ };
+
+ p = o = 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];
@@ -1473,6 +1588,7 @@ static const char *const protect_home_table[_PROTECT_HOME_MAX] = {
[PROTECT_HOME_NO] = "no",
[PROTECT_HOME_YES] = "yes",
[PROTECT_HOME_READ_ONLY] = "read-only",
+ [PROTECT_HOME_TMPFS] = "tmpfs",
};
DEFINE_STRING_TABLE_LOOKUP(protect_home, ProtectHome);
diff --git a/src/core/namespace.h b/src/core/namespace.h
index 42d841c4d2..3d56a7302d 100644
--- a/src/core/namespace.h
+++ b/src/core/namespace.h
@@ -23,6 +23,7 @@
typedef struct NamespaceInfo NamespaceInfo;
typedef struct BindMount BindMount;
+typedef struct TemporaryFileSystem TemporaryFileSystem;
#include
@@ -33,6 +34,7 @@ typedef enum ProtectHome {
PROTECT_HOME_NO,
PROTECT_HOME_YES,
PROTECT_HOME_READ_ONLY,
+ PROTECT_HOME_TMPFS,
_PROTECT_HOME_MAX,
_PROTECT_HOME_INVALID = -1
} ProtectHome;
@@ -75,6 +77,11 @@ struct BindMount {
bool ignore_enoent:1;
};
+struct TemporaryFileSystem {
+ char *path;
+ char *options;
+};
+
int setup_namespace(
const char *root_directory,
const char *root_image,
@@ -85,6 +92,8 @@ int setup_namespace(
char **empty_directories,
const BindMount *bind_mounts,
unsigned n_bind_mounts,
+ const TemporaryFileSystem *temporary_filesystems,
+ unsigned n_temporary_filesystems,
const char *tmp_dir,
const char *var_tmp_dir,
ProtectHome protect_home,
@@ -110,6 +119,10 @@ ProtectSystem parse_protect_system_or_bool(const char *s);
void bind_mount_free_many(BindMount *b, unsigned n);
int bind_mount_add(BindMount **b, unsigned *n, const BindMount *item);
+void temporary_filesystem_free_many(TemporaryFileSystem *t, unsigned n);
+int temporary_filesystem_add(TemporaryFileSystem **t, unsigned *n,
+ const char *path, const char *options);
+
const char* namespace_type_to_string(NamespaceType t) _const_;
NamespaceType namespace_type_from_string(const char *s) _pure_;
diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c
index c9236ea3d1..5193f1f69d 100644
--- a/src/nspawn/nspawn-mount.c
+++ b/src/nspawn/nspawn-mount.c
@@ -545,21 +545,21 @@ int mount_all(const char *dest,
{ "/proc/sys", "/proc/sys", NULL, NULL, MS_BIND, MOUNT_FATAL|MOUNT_IN_USERNS|MOUNT_APPLY_APIVFS_RO }, /* Bind mount first ... */
{ "/proc/sys/net", "/proc/sys/net", NULL, NULL, MS_BIND, MOUNT_FATAL|MOUNT_IN_USERNS|MOUNT_APPLY_APIVFS_RO|MOUNT_APPLY_APIVFS_NETNS }, /* (except for this) */
{ NULL, "/proc/sys", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, MOUNT_FATAL|MOUNT_IN_USERNS|MOUNT_APPLY_APIVFS_RO }, /* ... then, make it r/o */
- { "/proc/sysrq-trigger", "/proc/sysrq-trigger", NULL, NULL, MS_BIND, MOUNT_IN_USERNS|MOUNT_APPLY_APIVFS_RO }, /* Bind mount first ... */
- { NULL, "/proc/sysrq-trigger", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, MOUNT_IN_USERNS|MOUNT_APPLY_APIVFS_RO }, /* ... then, make it r/o */
+ { "/proc/sysrq-trigger", "/proc/sysrq-trigger", NULL, NULL, MS_BIND, MOUNT_IN_USERNS|MOUNT_APPLY_APIVFS_RO }, /* Bind mount first ... */
+ { NULL, "/proc/sysrq-trigger", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, MOUNT_IN_USERNS|MOUNT_APPLY_APIVFS_RO }, /* ... then, make it r/o */
/* outer child mounts */
- { "tmpfs", "/tmp", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, MOUNT_FATAL },
+ { "tmpfs", "/tmp", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, MOUNT_FATAL },
{ "tmpfs", "/sys", "tmpfs", "mode=755", MS_NOSUID|MS_NOEXEC|MS_NODEV, MOUNT_FATAL|MOUNT_APPLY_APIVFS_NETNS },
{ "sysfs", "/sys", "sysfs", NULL, MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, MOUNT_FATAL|MOUNT_APPLY_APIVFS_RO }, /* skipped if above was mounted */
- { "sysfs", "/sys", "sysfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, MOUNT_FATAL }, /* skipped if above was mounted */
+ { "sysfs", "/sys", "sysfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, MOUNT_FATAL }, /* skipped if above was mounted */
{ "tmpfs", "/dev", "tmpfs", "mode=755", MS_NOSUID|MS_STRICTATIME, MOUNT_FATAL },
{ "tmpfs", "/dev/shm", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, MOUNT_FATAL },
{ "tmpfs", "/run", "tmpfs", "mode=755", MS_NOSUID|MS_NODEV|MS_STRICTATIME, MOUNT_FATAL },
#if HAVE_SELINUX
- { "/sys/fs/selinux", "/sys/fs/selinux", NULL, NULL, MS_BIND, 0 }, /* Bind mount first */
- { NULL, "/sys/fs/selinux", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, 0 }, /* Then, make it r/o */
+ { "/sys/fs/selinux", "/sys/fs/selinux", NULL, NULL, MS_BIND, 0 }, /* Bind mount first */
+ { NULL, "/sys/fs/selinux", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, 0 }, /* Then, make it r/o */
#endif
};
@@ -634,56 +634,15 @@ int mount_all(const char *dest,
return 0;
}
-static int parse_mount_bind_options(const char *options, unsigned long *mount_flags, char **mount_opts) {
- const char *p = options;
- unsigned long flags = *mount_flags;
- char *opts = NULL;
- int r;
-
- assert(options);
-
- for (;;) {
- _cleanup_free_ char *word = NULL;
-
- r = extract_first_word(&p, &word, ",", 0);
- if (r < 0)
- return log_error_errno(r, "Failed to extract mount option: %m");
- if (r == 0)
- break;
-
- if (streq(word, "rbind"))
- flags |= MS_REC;
- else if (streq(word, "norbind"))
- flags &= ~MS_REC;
- else {
- log_error("Invalid bind mount option: %s", word);
- return -EINVAL;
- }
- }
-
- *mount_flags = flags;
- /* in the future mount_opts will hold string options for mount(2) */
- *mount_opts = opts;
-
- return 0;
-}
-
static int mount_bind(const char *dest, CustomMount *m) {
- _cleanup_free_ char *mount_opts = NULL, *where = NULL;
- unsigned long mount_flags = MS_BIND | MS_REC;
+ _cleanup_free_ char *where = NULL;
struct stat source_st, dest_st;
int r;
assert(dest);
assert(m);
- if (m->options) {
- r = parse_mount_bind_options(m->options, &mount_flags, &mount_opts);
- if (r < 0)
- return r;
- }
-
if (stat(m->source, &source_st) < 0)
return log_error_errno(errno, "Failed to stat %s: %m", m->source);
@@ -723,7 +682,7 @@ static int mount_bind(const char *dest, CustomMount *m) {
}
- r = mount_verbose(LOG_ERR, m->source, where, NULL, mount_flags, mount_opts);
+ r = mount_verbose(LOG_ERR, m->source, where, NULL, MS_BIND | MS_REC, m->options);
if (r < 0)
return r;
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index 78b9b69557..31260b732f 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -1135,6 +1135,62 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
return 1;
}
+ if (streq(field, "TemporaryFileSystem")) {
+ const char *p = eq;
+
+ r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_open_container(m, 'v', "a(ss)");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_open_container(m, 'a', "(ss)");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL, *path = NULL;
+ const char *w;
+
+ r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse argument: %m");
+ if (r == 0)
+ break;
+
+ w = word;
+ r = extract_first_word(&w, &path, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse argument: %m");
+ if (r == 0)
+ return log_error("Failed to parse argument: %m");
+
+ r = sd_bus_message_append(m, "(ss)", path, w);
+ if (r < 0)
+ return bus_log_create_error(r);
+ }
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ return 1;
+ }
+
return 0;
}
diff --git a/src/test/test-execute.c b/src/test/test-execute.c
index fba798e22b..f2223e1d3a 100644
--- a/src/test/test-execute.c
+++ b/src/test/test-execute.c
@@ -276,6 +276,14 @@ static void test_exec_inaccessiblepaths(Manager *m) {
test(m, "exec-inaccessiblepaths-mount-propagation.service", 0, CLD_EXITED);
}
+static void test_exec_temporaryfilesystem(Manager *m) {
+
+ test(m, "exec-temporaryfilesystem-options.service", 0, CLD_EXITED);
+ test(m, "exec-temporaryfilesystem-ro.service", 0, CLD_EXITED);
+ test(m, "exec-temporaryfilesystem-rw.service", 0, CLD_EXITED);
+ test(m, "exec-temporaryfilesystem-usr.service", 0, CLD_EXITED);
+}
+
static void test_exec_systemcallfilter(Manager *m) {
#if HAVE_SECCOMP
if (!is_seccomp_available()) {
@@ -569,6 +577,7 @@ int main(int argc, char *argv[]) {
test_exec_supplementarygroups,
test_exec_systemcallerrornumber,
test_exec_systemcallfilter,
+ test_exec_temporaryfilesystem,
test_exec_umask,
test_exec_unsetenvironment,
test_exec_user,
diff --git a/src/test/test-mount-util.c b/src/test/test-mount-util.c
index 09a624842c..c95baa81a7 100644
--- a/src/test/test-mount-util.c
+++ b/src/test/test-mount-util.c
@@ -261,6 +261,60 @@ static void test_path_is_mount_point(void) {
assert_se(rm_rf(tmp_dir, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
}
+static void test_mount_option_mangle(void) {
+ char *opts = NULL;
+ unsigned long f;
+
+ assert_se(mount_option_mangle(NULL, MS_RDONLY|MS_NOSUID, &f, &opts) == 0);
+ assert_se(f == (MS_RDONLY|MS_NOSUID));
+ assert_se(opts == NULL);
+
+ assert_se(mount_option_mangle("", MS_RDONLY|MS_NOSUID, &f, &opts) == 0);
+ assert_se(f == (MS_RDONLY|MS_NOSUID));
+ assert_se(opts == NULL);
+
+ assert_se(mount_option_mangle("ro,nosuid,nodev,noexec", 0, &f, &opts) == 0);
+ assert_se(f == (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC));
+ assert_se(opts == NULL);
+
+ assert_se(mount_option_mangle("ro,nosuid,nodev,noexec,mode=755", 0, &f, &opts) == 0);
+ assert_se(f == (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC));
+ assert_se(streq(opts, "mode=755"));
+ opts = mfree(opts);
+
+ assert_se(mount_option_mangle("rw,nosuid,foo,hogehoge,nodev,mode=755", 0, &f, &opts) == 0);
+ assert_se(f == (MS_NOSUID|MS_NODEV));
+ assert_se(streq(opts, "foo,hogehoge,mode=755"));
+ opts = mfree(opts);
+
+ assert_se(mount_option_mangle("rw,nosuid,nodev,noexec,relatime,net_cls,net_prio", MS_RDONLY, &f, &opts) == 0);
+ assert_se(f == (MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_RELATIME));
+ assert_se(streq(opts, "net_cls,net_prio"));
+ opts = mfree(opts);
+
+ assert_se(mount_option_mangle("rw,nosuid,nodev,relatime,size=1630748k,mode=700,uid=1000,gid=1000", MS_RDONLY, &f, &opts) == 0);
+ assert_se(f == (MS_NOSUID|MS_NODEV|MS_RELATIME));
+ assert_se(streq(opts, "size=1630748k,mode=700,uid=1000,gid=1000"));
+ opts = mfree(opts);
+
+ assert_se(mount_option_mangle("size=1630748k,rw,gid=1000,,,nodev,relatime,,mode=700,nosuid,uid=1000", MS_RDONLY, &f, &opts) == 0);
+ assert_se(f == (MS_NOSUID|MS_NODEV|MS_RELATIME));
+ assert_se(streq(opts, "size=1630748k,gid=1000,mode=700,uid=1000"));
+ opts = mfree(opts);
+
+ assert_se(mount_option_mangle("rw,exec,size=8143984k,nr_inodes=2035996,mode=755", MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, &f, &opts) == 0);
+ assert_se(f == (MS_NOSUID|MS_NODEV));
+ assert_se(streq(opts, "size=8143984k,nr_inodes=2035996,mode=755"));
+ opts = mfree(opts);
+
+ assert_se(mount_option_mangle("rw,relatime,fmask=0022,,,dmask=0022", MS_RDONLY, &f, &opts) == 0);
+ assert_se(f == MS_RELATIME);
+ assert_se(streq(opts, "fmask=0022,dmask=0022"));
+ opts = mfree(opts);
+
+ assert_se(mount_option_mangle("rw,relatime,fmask=0022,dmask=0022,\"hogehoge", MS_RDONLY, &f, &opts) < 0);
+}
+
int main(int argc, char *argv[]) {
log_set_max_level(LOG_DEBUG);
@@ -275,6 +329,7 @@ int main(int argc, char *argv[]) {
test_mnt_id();
test_path_is_mount_point();
+ test_mount_option_mangle();
return 0;
}
diff --git a/src/test/test-ns.c b/src/test/test-ns.c
index 87b4facb85..3ab3c1ab95 100644
--- a/src/test/test-ns.c
+++ b/src/test/test-ns.c
@@ -86,6 +86,7 @@ int main(int argc, char *argv[]) {
(char **) inaccessible,
NULL,
&(BindMount) { .source = (char*) "/usr/bin", .destination = (char*) "/etc/systemd", .read_only = true }, 1,
+ &(TemporaryFileSystem) { .path = (char*) "/var", .options = (char*) "ro" }, 1,
tmp_dir,
var_tmp_dir,
PROTECT_HOME_NO,
diff --git a/test/meson.build b/test/meson.build
index 4667628b24..060e7ee73d 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -136,6 +136,10 @@ test_data_files = '''
test-execute/exec-systemcallfilter-system-user.service
test-execute/exec-systemcallfilter-with-errno-name.service
test-execute/exec-systemcallfilter-with-errno-number.service
+ test-execute/exec-temporaryfilesystem-options.service
+ test-execute/exec-temporaryfilesystem-ro.service
+ test-execute/exec-temporaryfilesystem-rw.service
+ test-execute/exec-temporaryfilesystem-usr.service
test-execute/exec-umask-0177.service
test-execute/exec-umask-default.service
test-execute/exec-unsetenvironment.service
diff --git a/test/test-execute/exec-temporaryfilesystem-options.service b/test/test-execute/exec-temporaryfilesystem-options.service
new file mode 100644
index 0000000000..1d5d76c81c
--- /dev/null
+++ b/test/test-execute/exec-temporaryfilesystem-options.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=Test for TemporaryFileSystem with mount options
+
+[Service]
+Type=oneshot
+
+# Check /proc/self/mountinfo
+ExecStart=/bin/sh -c 'test $$(awk \'$$5 == "/var" { print $$6 }\' /proc/self/mountinfo) = "ro,nodev,relatime"'
+ExecStart=/bin/sh -c 'test $$(awk \'$$5 == "/var" { print $$11 }\' /proc/self/mountinfo) = "ro,mode=700"'
+
+TemporaryFileSystem=/var:ro,mode=0700,nostrictatime
diff --git a/test/test-execute/exec-temporaryfilesystem-ro.service b/test/test-execute/exec-temporaryfilesystem-ro.service
new file mode 100644
index 0000000000..c0e3721a01
--- /dev/null
+++ b/test/test-execute/exec-temporaryfilesystem-ro.service
@@ -0,0 +1,33 @@
+[Unit]
+Description=Test for TemporaryFileSystem with read-only mode
+
+[Service]
+Type=oneshot
+
+# Check directories exist
+ExecStart=/bin/sh -c 'test -d /var/test-exec-temporaryfilesystem/rw && test -d /var/test-exec-temporaryfilesystem/ro'
+
+# Check TemporaryFileSystem= are empty
+ExecStart=/bin/sh -c 'for i in $$(ls -A /var); do test $$i = test-exec-temporaryfilesystem || false; done'
+
+# Cannot create a file in /var
+ExecStart=/bin/sh -c '! touch /var/hoge'
+
+# Create a file in /var/test-exec-temporaryfilesystem/rw
+ExecStart=/bin/sh -c 'touch /var/test-exec-temporaryfilesystem/rw/thisisasimpletest-temporaryfilesystem'
+
+# Then, the file can be access through /tmp
+ExecStart=/bin/sh -c 'test -f /tmp/thisisasimpletest-temporaryfilesystem'
+
+# Also, through /var/test-exec-temporaryfilesystem/ro
+ExecStart=/bin/sh -c 'test -f /var/test-exec-temporaryfilesystem/ro/thisisasimpletest-temporaryfilesystem'
+
+# The file cannot modify through /var/test-exec-temporaryfilesystem/ro
+ExecStart=/bin/sh -c '! touch /var/test-exec-temporaryfilesystem/ro/thisisasimpletest-temporaryfilesystem'
+
+# Cleanup
+ExecStart=/bin/sh -c 'rm /tmp/thisisasimpletest-temporaryfilesystem'
+
+TemporaryFileSystem=/var:ro
+BindPaths=/tmp:/var/test-exec-temporaryfilesystem/rw
+BindReadOnlyPaths=/tmp:/var/test-exec-temporaryfilesystem/ro
diff --git a/test/test-execute/exec-temporaryfilesystem-rw.service b/test/test-execute/exec-temporaryfilesystem-rw.service
new file mode 100644
index 0000000000..fc02ceab1c
--- /dev/null
+++ b/test/test-execute/exec-temporaryfilesystem-rw.service
@@ -0,0 +1,33 @@
+[Unit]
+Description=Test for TemporaryFileSystem
+
+[Service]
+Type=oneshot
+
+# Check directories exist
+ExecStart=/bin/sh -c 'test -d /var/test-exec-temporaryfilesystem/rw && test -d /var/test-exec-temporaryfilesystem/ro'
+
+# Check TemporaryFileSystem= are empty
+ExecStart=/bin/sh -c 'for i in $$(ls -A /var); do test $$i = test-exec-temporaryfilesystem || false; done'
+
+# Create a file in /var
+ExecStart=/bin/sh -c 'touch /var/hoge'
+
+# Create a file in /var/test-exec-temporaryfilesystem/rw
+ExecStart=/bin/sh -c 'touch /var/test-exec-temporaryfilesystem/rw/thisisasimpletest-temporaryfilesystem'
+
+# Then, the file can be access through /tmp
+ExecStart=/bin/sh -c 'test -f /tmp/thisisasimpletest-temporaryfilesystem'
+
+# Also, through /var/test-exec-temporaryfilesystem/ro
+ExecStart=/bin/sh -c 'test -f /var/test-exec-temporaryfilesystem/ro/thisisasimpletest-temporaryfilesystem'
+
+# The file cannot modify through /var/test-exec-temporaryfilesystem/ro
+ExecStart=/bin/sh -c '! touch /var/test-exec-temporaryfilesystem/ro/thisisasimpletest-temporaryfilesystem'
+
+# Cleanup
+ExecStart=/bin/sh -c 'rm /tmp/thisisasimpletest-temporaryfilesystem'
+
+TemporaryFileSystem=/var
+BindPaths=/tmp:/var/test-exec-temporaryfilesystem/rw
+BindReadOnlyPaths=/tmp:/var/test-exec-temporaryfilesystem/ro
diff --git a/test/test-execute/exec-temporaryfilesystem-usr.service b/test/test-execute/exec-temporaryfilesystem-usr.service
new file mode 100644
index 0000000000..05c1ec0694
--- /dev/null
+++ b/test/test-execute/exec-temporaryfilesystem-usr.service
@@ -0,0 +1,15 @@
+[Unit]
+Description=Test for TemporaryFileSystem on /usr
+
+[Service]
+Type=oneshot
+
+# Check TemporaryFileSystem= are empty
+ExecStart=/bin/sh -c 'for i in $$(ls -A /usr); do test $$i = lib -o $$i = lib64 -o $$i = bin -o $$i = sbin || false; done'
+
+# Cannot create files under /usr
+ExecStart=/bin/sh -c '! touch /usr/hoge'
+ExecStart=/bin/sh -c '! touch /usr/bin/hoge'
+
+TemporaryFileSystem=/usr:ro
+BindReadOnlyPaths=-/usr/lib -/usr/lib64 /usr/bin /usr/sbin