mirror of
https://github.com/systemd/systemd.git
synced 2024-11-24 18:53:33 +08:00
Merge pull request #7908 from yuwata/rfe-7895
core: add TemporaryFileSystem= setting and 'tmpfs' option to ProtectHome=
This commit is contained in:
commit
59e00b2a16
@ -170,6 +170,7 @@ All execution-related settings are available for transient units.
|
||||
✓ InaccessiblePaths=
|
||||
✓ BindPaths=
|
||||
✓ BindReadOnlyPaths=
|
||||
✓ TemporaryFileSystem=
|
||||
✓ PrivateTmp=
|
||||
✓ PrivateDevices=
|
||||
✓ ProtectKernelTunables=
|
||||
|
@ -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>
|
||||
|
||||
|
@ -317,6 +317,7 @@ libbasic = static_library(
|
||||
dependencies : [threads,
|
||||
libcap,
|
||||
libblkid,
|
||||
libmount,
|
||||
libselinux],
|
||||
c_args : ['-fvisibility=default'],
|
||||
install : false)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,11 +2261,9 @@ 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) {
|
||||
char *s, *d;
|
||||
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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))
|
||||
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 */
|
||||
} 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);
|
||||
|
@ -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_;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
11
test/test-execute/exec-temporaryfilesystem-options.service
Normal file
11
test/test-execute/exec-temporaryfilesystem-options.service
Normal 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
|
33
test/test-execute/exec-temporaryfilesystem-ro.service
Normal file
33
test/test-execute/exec-temporaryfilesystem-ro.service
Normal 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
|
33
test/test-execute/exec-temporaryfilesystem-rw.service
Normal file
33
test/test-execute/exec-temporaryfilesystem-rw.service
Normal 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
|
15
test/test-execute/exec-temporaryfilesystem-usr.service
Normal file
15
test/test-execute/exec-temporaryfilesystem-usr.service
Normal 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
|
Loading…
Reference in New Issue
Block a user