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
23 changed files with 741 additions and 142 deletions
+1
View File
@@ -317,6 +317,7 @@ libbasic = static_library(
dependencies : [threads,
libcap,
libblkid,
libmount,
libselinux],
c_args : ['-fvisibility=default'],
install : false)
+94 -12
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;
}
+6
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);
+88 -18
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;
}
+23 -5
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)
+2
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;
+1
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)
+77
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,
+1
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);
+165 -49
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);
+13
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_;
+8 -49
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;
+56
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;
}
+9
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,
+55
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;
}
+1
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,