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