Merge pull request #7908 from yuwata/rfe-7895

core: add TemporaryFileSystem= setting and 'tmpfs' option to ProtectHome=
This commit is contained in:
Alan Jenkins 2018-02-21 08:57:11 +00:00 committed by GitHub
commit 59e00b2a16
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 741 additions and 142 deletions

View file

@ -170,6 +170,7 @@ All execution-related settings are available for transient units.
✓ InaccessiblePaths=
✓ BindPaths=
✓ BindReadOnlyPaths=
✓ TemporaryFileSystem=
✓ PrivateTmp=
✓ PrivateDevices=
✓ ProtectKernelTunables=

View file

@ -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
<literal>rbind</literal> or <literal>norbind</literal> for configuring a recursive or non-recursive bind
mount. If the destination path is omitted, the option string must be omitted too.</para>
mount. If the destination path is omitted, the option string must be omitted too.
Each bind mount definition may be prefixed with <literal>-</literal>, in which case it will be ignored
when its source path does not exist.</para>
<para><varname>BindPaths=</varname> creates regular writable bind mounts (unless the source file system mount
is already marked read-only), while <varname>BindReadOnlyPaths=</varname> creates read-only bind mounts. These
@ -786,14 +788,24 @@ CapabilityBoundingSet=~CAP_B CAP_C</programlisting>
<varlistentry>
<term><varname>ProtectHome=</varname></term>
<listitem><para>Takes a boolean argument or <literal>read-only</literal>. If true, the directories
<filename>/home</filename>, <filename>/root</filename> and <filename>/run/user</filename> are made inaccessible
and empty for processes invoked by this unit. If set to <literal>read-only</literal>, 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 <varname>DynamicUser=</varname> is
set. For this setting the same restrictions regarding mount propagation and privileges apply as for
<varname>ReadOnlyPaths=</varname> and related calls, see below.</para></listitem>
<listitem><para>Takes a boolean argument or the special values <literal>read-only</literal> or
<literal>tmpfs</literal>. If true, the directories <filename>/home</filename>, <filename>/root</filename> and
<filename>/run/user</filename> are made inaccessible and empty for processes invoked by this unit. If set to
<literal>read-only</literal>, the three directories are made read-only instead. If set to <literal>tmpfs</literal>,
temporary file systems are mounted on the three directories in read-only mode. The value <literal>tmpfs</literal>
is useful to hide home directories not relevant to the processes invoked by the unit, while necessary directories
are still visible by combining with <varname>BindPaths=</varname> or <varname>BindReadOnlyPaths=</varname>.</para>
<para>Setting this to <literal>yes</literal> is mostly equivalent to set the three directories in
<varname>InaccessiblePaths=</varname>. Similary, <literal>read-only</literal> is mostly equivalent to
<varname>ReadOnlyPaths=</varname>, and <literal>tmpfs</literal> is mostly equivalent to
<varname>TemporaryFileSystem=</varname>.</para>
<para> 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 <varname>DynamicUser=</varname> is set. For this setting the same
restrictions regarding mount propagation and privileges apply as for <varname>ReadOnlyPaths=</varname> and related
calls, see below.</para></listitem>
</varlistentry>
<varlistentry>
@ -930,6 +942,29 @@ CapabilityBoundingSet=~CAP_B CAP_C</programlisting>
<varname>SystemCallFilter=~@mount</varname>.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>TemporaryFileSystem=</varname></term>
<listitem><para>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 (<literal>:</literal>) and mount options such as
<literal>size=10%</literal> or <literal>ro</literal>. By default, each temporary file system is mounted
with <literal>nodev,strictatime,mode=0755</literal>. These can be disabled by explicitly specifying the corresponding
mount options, e.g., <literal>dev</literal> or <literal>nostrictatime</literal>.</para>
<para>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 <varname>BindPaths=</varname> or
<varname>BindReadOnlyPaths=</varname>. See the example below.</para>
<para>Example: if a unit has the following,
<programlisting>TemporaryFileSystem=/var:ro
BindReadOnlyPaths=/var/lib/systemd</programlisting>
then the invoked processes by the unit cannot see any files or directories under <filename>/var</filename> except for
<filename>/var/lib/systemd</filename> or its contents.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>PrivateTmp=</varname></term>

View file

@ -317,6 +317,7 @@ libbasic = static_library(
dependencies : [threads,
libcap,
libblkid,
libmount,
libselinux],
c_args : ['-fvisibility=default'],
install : false)

View file

@ -27,8 +27,12 @@
#include <sys/statvfs.h>
#include <unistd.h>
/* Include later */
#include <libmount.h>
#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;
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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)

View file

@ -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;

View file

@ -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)

View file

@ -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,

View file

@ -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);

View file

@ -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);

View file

@ -23,6 +23,7 @@
typedef struct NamespaceInfo NamespaceInfo;
typedef struct BindMount BindMount;
typedef struct TemporaryFileSystem TemporaryFileSystem;
#include <stdbool.h>
@ -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_;

View file

@ -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;

View file

@ -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;
}

View file

@ -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,

View file

@ -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;
}

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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