mirror of
https://github.com/systemd/systemd.git
synced 2024-11-24 10:43:35 +08:00
core: add ability to define arbitrary bind mounts for services
This adds two new settings BindPaths= and BindReadOnlyPaths=. They allow defining arbitrary bind mounts specific to particular services. This is particularly useful for services with RootDirectory= set as this permits making specific bits of the host directory available to chrooted services. The two new settings follow the concepts nspawn already possess in --bind= and --bind-ro=, as well as the .nspawn settings Bind= and BindReadOnly= (and these latter options should probably be renamed to BindPaths= and BindReadOnlyPaths= too). Fixes: #3439
This commit is contained in:
parent
8fceda937f
commit
d2d6c096f6
@ -967,6 +967,31 @@
|
||||
<varname>SystemCallFilter=~@mount</varname>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>BindPaths=</varname></term>
|
||||
<term><varname>BindReadOnlyPaths=</varname></term>
|
||||
|
||||
<listitem><para>Configures unit-specific bind mounts. A bind mount makes a particular file or directory
|
||||
available at an additional place in the unit's view of the file system. Any bind mounts created with this
|
||||
option are specific to the unit, and are not visible in the host's mount table. This option expects a
|
||||
whitespace separated list of bind mount definitions. Each definition consists of a colon-separated triple of
|
||||
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 parth is omitted, the option string must be omitted too.</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
|
||||
settings may be used more than once, each usage appends to the unit's list of bind mounts. If the empty string
|
||||
is assigned to either of these two options the entire list of bind mounts defined prior to this is reset. Note
|
||||
that in this case both read-only and regular bind mounts are reset, regardless which of the two settings is
|
||||
used.</para>
|
||||
|
||||
<para>This option is particularly useful when <varname>RootDirectory=</varname> is used. In this case the
|
||||
source path refers to a path on the host file system, while the destination path referes to a path below the
|
||||
root directory of the unit.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>PrivateTmp=</varname></term>
|
||||
|
||||
|
@ -675,6 +675,49 @@ static int property_get_output_fdname(
|
||||
return sd_bus_message_append(reply, "s", name);
|
||||
}
|
||||
|
||||
static int property_get_bind_paths(
|
||||
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;
|
||||
bool ro;
|
||||
int r;
|
||||
|
||||
assert(bus);
|
||||
assert(c);
|
||||
assert(property);
|
||||
assert(reply);
|
||||
|
||||
ro = !!strstr(property, "ReadOnly");
|
||||
|
||||
r = sd_bus_message_open_container(reply, 'a', "(ssbt)");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
for (i = 0; i < c->n_bind_mounts; i++) {
|
||||
|
||||
if (ro != c->bind_mounts[i].read_only)
|
||||
continue;
|
||||
|
||||
r = sd_bus_message_append(
|
||||
reply, "(ssbt)",
|
||||
c->bind_mounts[i].source,
|
||||
c->bind_mounts[i].destination,
|
||||
c->bind_mounts[i].ignore_enoent,
|
||||
c->bind_mounts[i].recursive ? MS_REC : 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return sd_bus_message_close_container(reply);
|
||||
}
|
||||
|
||||
const sd_bus_vtable bus_exec_vtable[] = {
|
||||
SD_BUS_VTABLE_START(0),
|
||||
SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
@ -783,6 +826,8 @@ const sd_bus_vtable bus_exec_vtable[] = {
|
||||
SD_BUS_PROPERTY("MemoryDenyWriteExecute", "b", bus_property_get_bool, offsetof(ExecContext, memory_deny_write_execute), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("RestrictRealtime", "b", bus_property_get_bool, offsetof(ExecContext, restrict_realtime), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
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_VTABLE_END
|
||||
};
|
||||
|
||||
@ -1364,8 +1409,8 @@ int bus_exec_context_set_transient_property(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!isempty(path) && !path_is_absolute(path))
|
||||
return sd_bus_error_set_errnof(error, EINVAL, "Path %s is not absolute.", path);
|
||||
if (!path_is_absolute(path))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Path %s is not absolute.", path);
|
||||
|
||||
if (mode != UNIT_CHECK) {
|
||||
char *buf = NULL;
|
||||
@ -1629,8 +1674,74 @@ int bus_exec_context_set_transient_property(
|
||||
unit_write_drop_in_private_format(u, mode, name, "%s=%s", name, strempty(mount_propagation_flags_to_string(flags)));
|
||||
}
|
||||
|
||||
return 1;
|
||||
} else if (STR_IN_SET(name, "BindPaths", "BindReadOnlyPaths")) {
|
||||
unsigned 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;
|
||||
|
||||
if (!path_is_absolute(source))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path %s is not absolute.", source);
|
||||
if (!path_is_absolute(destination))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is not absolute.", source);
|
||||
if (!IN_SET(mount_flags, 0, MS_REC))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown mount flags.");
|
||||
|
||||
if (mode != UNIT_CHECK) {
|
||||
r = bind_mount_add(&c->bind_mounts, &c->n_bind_mounts,
|
||||
&(BindMount) {
|
||||
.source = strdup(source),
|
||||
.destination = strdup(destination),
|
||||
.read_only = !!strstr(name, "ReadOnly"),
|
||||
.recursive = !!(mount_flags & MS_REC),
|
||||
.ignore_enoent = ignore_enoent,
|
||||
});
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
unit_write_drop_in_private_format(
|
||||
u, mode, name,
|
||||
"%s=%s%s:%s:%s",
|
||||
name,
|
||||
ignore_enoent ? "-" : "",
|
||||
source,
|
||||
destination,
|
||||
(mount_flags & MS_REC) ? "rbind" : "norbind");
|
||||
}
|
||||
|
||||
empty = false;
|
||||
}
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_message_exit_container(message);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (empty) {
|
||||
bind_mount_free_many(c->bind_mounts, c->n_bind_mounts);
|
||||
c->bind_mounts = NULL;
|
||||
c->n_bind_mounts = 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
ri = rlimit_from_string(name);
|
||||
if (ri < 0) {
|
||||
soft = endswith(name, "Soft");
|
||||
|
@ -1826,6 +1826,9 @@ static bool exec_needs_mount_namespace(
|
||||
!strv_isempty(context->inaccessible_paths))
|
||||
return true;
|
||||
|
||||
if (context->n_bind_mounts > 0)
|
||||
return true;
|
||||
|
||||
if (context->mount_flags != 0)
|
||||
return true;
|
||||
|
||||
@ -2147,6 +2150,8 @@ static int apply_mount_namespace(Unit *u, const ExecContext *context,
|
||||
r = setup_namespace(root_dir, &ns_info, rw,
|
||||
context->read_only_paths,
|
||||
context->inaccessible_paths,
|
||||
context->bind_mounts,
|
||||
context->n_bind_mounts,
|
||||
tmp,
|
||||
var,
|
||||
context->protect_home,
|
||||
@ -3086,6 +3091,8 @@ void exec_context_done(ExecContext *c) {
|
||||
c->read_write_paths = strv_free(c->read_write_paths);
|
||||
c->inaccessible_paths = strv_free(c->inaccessible_paths);
|
||||
|
||||
bind_mount_free_many(c->bind_mounts, c->n_bind_mounts);
|
||||
|
||||
if (c->cpuset)
|
||||
CPU_FREE(c->cpuset);
|
||||
|
||||
@ -3569,6 +3576,15 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
|
||||
fputs("\n", f);
|
||||
}
|
||||
|
||||
if (c->n_bind_mounts > 0)
|
||||
for (i = 0; i < c->n_bind_mounts; i++) {
|
||||
fprintf(f, "%s%s: %s:%s:%s\n", prefix,
|
||||
c->bind_mounts[i].read_only ? "BindReadOnlyPaths" : "BindPaths",
|
||||
c->bind_mounts[i].source,
|
||||
c->bind_mounts[i].destination,
|
||||
c->bind_mounts[i].recursive ? "rbind" : "norbind");
|
||||
}
|
||||
|
||||
if (c->utmp_id)
|
||||
fprintf(f,
|
||||
"%sUtmpIdentifier: %s\n",
|
||||
|
@ -161,6 +161,8 @@ struct ExecContext {
|
||||
|
||||
char **read_write_paths, **read_only_paths, **inaccessible_paths;
|
||||
unsigned long mount_flags;
|
||||
BindMount *bind_mounts;
|
||||
unsigned n_bind_mounts;
|
||||
|
||||
uint64_t capability_bounding_set;
|
||||
uint64_t capability_ambient_set;
|
||||
|
@ -89,6 +89,8 @@ $1.InaccessibleDirectories, config_parse_namespace_path_strv, 0,
|
||||
$1.ReadWritePaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_write_paths)
|
||||
$1.ReadOnlyPaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_only_paths)
|
||||
$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.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)
|
||||
|
@ -3891,6 +3891,132 @@ int config_parse_namespace_path_strv(
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_bind_paths(
|
||||
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) {
|
||||
|
||||
ExecContext *c = data;
|
||||
const char *p;
|
||||
int r;
|
||||
|
||||
assert(filename);
|
||||
assert(lvalue);
|
||||
assert(rvalue);
|
||||
assert(data);
|
||||
|
||||
if (isempty(rvalue)) {
|
||||
/* Empty assignment resets the list */
|
||||
bind_mount_free_many(c->bind_mounts, c->n_bind_mounts);
|
||||
c->bind_mounts = NULL;
|
||||
c->n_bind_mounts = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
p = rvalue;
|
||||
for (;;) {
|
||||
_cleanup_free_ char *source = NULL, *destination = NULL;
|
||||
char *s = NULL, *d = NULL;
|
||||
bool rbind = true, ignore_enoent = false;
|
||||
|
||||
r = extract_first_word(&p, &source, ":" WHITESPACE, EXTRACT_QUOTES|EXTRACT_DONT_COALESCE_SEPARATORS);
|
||||
if (r == 0)
|
||||
break;
|
||||
if (r == -ENOMEM)
|
||||
return log_oom();
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s: %s", lvalue, rvalue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
s = source;
|
||||
if (s[0] == '-') {
|
||||
ignore_enoent = true;
|
||||
s++;
|
||||
}
|
||||
|
||||
if (!utf8_is_valid(s)) {
|
||||
log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, s);
|
||||
return 0;
|
||||
}
|
||||
if (!path_is_absolute(s)) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, 0, "Not an absolute source path, ignoring: %s", s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
path_kill_slashes(s);
|
||||
|
||||
/* Optionally, the destination is specified. */
|
||||
if (p && p[-1] == ':') {
|
||||
r = extract_first_word(&p, &destination, ":" WHITESPACE, EXTRACT_QUOTES|EXTRACT_DONT_COALESCE_SEPARATORS);
|
||||
if (r == -ENOMEM)
|
||||
return log_oom();
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s: %s", lvalue, rvalue);
|
||||
return 0;
|
||||
}
|
||||
if (r == 0) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, 0, "Missing argument after ':': %s", rvalue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!utf8_is_valid(destination)) {
|
||||
log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, destination);
|
||||
return 0;
|
||||
}
|
||||
if (!path_is_absolute(destination)) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, 0, "Not an absolute destination path, ignoring: %s", destination);
|
||||
return 0;
|
||||
}
|
||||
|
||||
d = path_kill_slashes(destination);
|
||||
|
||||
/* Optionally, there's also a short option string specified */
|
||||
if (p && p[-1] == ':') {
|
||||
_cleanup_free_ char *options = NULL;
|
||||
|
||||
r = extract_first_word(&p, &options, NULL, EXTRACT_QUOTES);
|
||||
if (r == -ENOMEM)
|
||||
return log_oom();
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s: %s", lvalue, rvalue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (isempty(options) || streq(options, "rbind"))
|
||||
rbind = true;
|
||||
else if (streq(options, "norbind"))
|
||||
rbind = false;
|
||||
else {
|
||||
log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid option string, ignoring setting: %s", options);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} else
|
||||
d = s;
|
||||
|
||||
r = bind_mount_add(&c->bind_mounts, &c->n_bind_mounts,
|
||||
&(BindMount) {
|
||||
.source = s,
|
||||
.destination = d,
|
||||
.read_only = !!strstr(lvalue, "ReadOnly"),
|
||||
.recursive = rbind,
|
||||
.ignore_enoent = ignore_enoent,
|
||||
});
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_no_new_privileges(
|
||||
const char* unit,
|
||||
const char *filename,
|
||||
@ -4388,6 +4514,7 @@ void unit_dump_config_items(FILE *f) {
|
||||
{ config_parse_sec, "SECONDS" },
|
||||
{ config_parse_nsec, "NANOSECONDS" },
|
||||
{ config_parse_namespace_path_strv, "PATH [...]" },
|
||||
{ config_parse_bind_paths, "PATH[:PATH[:OPTIONS]] [...]" },
|
||||
{ config_parse_unit_requires_mounts_for, "PATH [...]" },
|
||||
{ config_parse_exec_mount_flags, "MOUNTFLAG [...]" },
|
||||
{ config_parse_unit_string_printf, "STRING" },
|
||||
|
@ -117,6 +117,7 @@ int config_parse_sec_fix_0(const char *unit, const char *filename, unsigned line
|
||||
int config_parse_user_group(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_user_group_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_restrict_namespaces(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_bind_paths(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);
|
||||
|
||||
/* gperf prototypes */
|
||||
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, unsigned length);
|
||||
|
@ -50,6 +50,8 @@
|
||||
typedef enum MountMode {
|
||||
/* This is ordered by priority! */
|
||||
INACCESSIBLE,
|
||||
BIND_MOUNT,
|
||||
BIND_MOUNT_RECURSIVE,
|
||||
READONLY,
|
||||
PRIVATE_TMP,
|
||||
PRIVATE_VAR_TMP,
|
||||
@ -64,6 +66,8 @@ typedef struct MountEntry {
|
||||
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 */
|
||||
const char *source_const; /* The source path, for bind mounts */
|
||||
char *source_malloc;
|
||||
} MountEntry;
|
||||
|
||||
/*
|
||||
@ -166,6 +170,12 @@ static bool mount_entry_read_only(const MountEntry *p) {
|
||||
return p->read_only || IN_SET(p->mode, READONLY, INACCESSIBLE);
|
||||
}
|
||||
|
||||
static const char *mount_entry_source(const MountEntry *p) {
|
||||
assert(p);
|
||||
|
||||
return p->source_malloc ?: p->source_const;
|
||||
}
|
||||
|
||||
static int append_access_mounts(MountEntry **p, char **strv, MountMode mode) {
|
||||
char **i;
|
||||
|
||||
@ -201,6 +211,25 @@ static int append_access_mounts(MountEntry **p, char **strv, MountMode mode) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int append_bind_mounts(MountEntry **p, const BindMount *binds, unsigned n) {
|
||||
unsigned i;
|
||||
|
||||
assert(p);
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
const BindMount *b = binds + i;
|
||||
|
||||
*((*p)++) = (MountEntry) {
|
||||
.path_const = b->destination,
|
||||
.mode = b->recursive ? BIND_MOUNT_RECURSIVE : BIND_MOUNT,
|
||||
.read_only = b->read_only,
|
||||
.source_const = b->source,
|
||||
};
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int append_static_mounts(MountEntry **p, const MountEntry *mounts, unsigned n, bool ignore_protect) {
|
||||
unsigned i;
|
||||
|
||||
@ -568,27 +597,33 @@ fail:
|
||||
return r;
|
||||
}
|
||||
|
||||
static int mount_entry_chase(MountEntry *m, const char *root_directory) {
|
||||
static int mount_entry_chase(
|
||||
const char *root_directory,
|
||||
MountEntry *m,
|
||||
const char *path,
|
||||
char **location) {
|
||||
|
||||
char *chased;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
/* Since mount() will always follow symlinks and we need to take the different root directory into account we
|
||||
* chase the symlinks on our own first. */
|
||||
* 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". */
|
||||
|
||||
r = chase_symlinks(mount_entry_path(m), root_directory, 0, &chased);
|
||||
r = chase_symlinks(path, root_directory, 0, &chased);
|
||||
if (r == -ENOENT && m->ignore) {
|
||||
log_debug_errno(r, "Path %s does not exist, ignoring.", mount_entry_path(m));
|
||||
log_debug_errno(r, "Path %s does not exist, ignoring.", path);
|
||||
return 0;
|
||||
}
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to follow symlinks on %s: %m", mount_entry_path(m));
|
||||
return log_debug_errno(r, "Failed to follow symlinks on %s: %m", path);
|
||||
|
||||
log_debug("Followed symlinks %s → %s.", mount_entry_path(m), chased);
|
||||
log_debug("Followed symlinks %s → %s.", path, chased);
|
||||
|
||||
free(m->path_malloc);
|
||||
m->path_malloc = chased;
|
||||
free(*location);
|
||||
*location = chased;
|
||||
|
||||
return 1;
|
||||
}
|
||||
@ -600,11 +635,12 @@ static int apply_mount(
|
||||
const char *var_tmp_dir) {
|
||||
|
||||
const char *what;
|
||||
bool rbind = true;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
r = mount_entry_chase(m, root_directory);
|
||||
r = mount_entry_chase(root_directory, m, mount_entry_path(m), &m->path_malloc);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
@ -633,7 +669,6 @@ static int apply_mount(
|
||||
|
||||
case READONLY:
|
||||
case READWRITE:
|
||||
|
||||
r = path_is_mount_point(mount_entry_path(m), root_directory, 0);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to determine whether %s is already a mount point: %m", mount_entry_path(m));
|
||||
@ -643,6 +678,19 @@ static int apply_mount(
|
||||
what = mount_entry_path(m);
|
||||
break;
|
||||
|
||||
case BIND_MOUNT:
|
||||
rbind = false;
|
||||
/* fallthrough */
|
||||
|
||||
case BIND_MOUNT_RECURSIVE:
|
||||
/* Also chase the source mount */
|
||||
r = mount_entry_chase(root_directory, m, mount_entry_source(m), &m->source_malloc);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
what = mount_entry_source(m);
|
||||
break;
|
||||
|
||||
case PRIVATE_TMP:
|
||||
what = tmp_dir;
|
||||
break;
|
||||
@ -660,7 +708,7 @@ static int apply_mount(
|
||||
|
||||
assert(what);
|
||||
|
||||
if (mount(what, mount_entry_path(m), NULL, MS_BIND|MS_REC, NULL) < 0)
|
||||
if (mount(what, mount_entry_path(m), NULL, MS_BIND|(rbind ? MS_REC : 0), NULL) < 0)
|
||||
return log_debug_errno(errno, "Failed to mount %s to %s: %m", what, mount_entry_path(m));
|
||||
|
||||
log_debug("Successfully mounted %s to %s", what, mount_entry_path(m));
|
||||
@ -695,6 +743,8 @@ static unsigned namespace_calculate_mounts(
|
||||
char** read_write_paths,
|
||||
char** read_only_paths,
|
||||
char** inaccessible_paths,
|
||||
const BindMount *bind_mounts,
|
||||
unsigned n_bind_mounts,
|
||||
const char* tmp_dir,
|
||||
const char* var_tmp_dir,
|
||||
ProtectHome protect_home,
|
||||
@ -719,6 +769,7 @@ static unsigned namespace_calculate_mounts(
|
||||
strv_length(read_write_paths) +
|
||||
strv_length(read_only_paths) +
|
||||
strv_length(inaccessible_paths) +
|
||||
n_bind_mounts +
|
||||
ns_info->private_dev +
|
||||
(ns_info->protect_kernel_tunables ? ELEMENTSOF(protect_kernel_tunables_table) : 0) +
|
||||
(ns_info->protect_control_groups ? 1 : 0) +
|
||||
@ -732,6 +783,8 @@ int setup_namespace(
|
||||
char** read_write_paths,
|
||||
char** read_only_paths,
|
||||
char** inaccessible_paths,
|
||||
const BindMount *bind_mounts,
|
||||
unsigned n_bind_mounts,
|
||||
const char* tmp_dir,
|
||||
const char* var_tmp_dir,
|
||||
ProtectHome protect_home,
|
||||
@ -751,6 +804,7 @@ int setup_namespace(
|
||||
read_write_paths,
|
||||
read_only_paths,
|
||||
inaccessible_paths,
|
||||
bind_mounts, n_bind_mounts,
|
||||
tmp_dir, var_tmp_dir,
|
||||
protect_home, protect_system);
|
||||
|
||||
@ -772,6 +826,10 @@ int setup_namespace(
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
r = append_bind_mounts(&m, bind_mounts, n_bind_mounts);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
if (tmp_dir) {
|
||||
*(m++) = (MountEntry) {
|
||||
.path_const = "/tmp",
|
||||
@ -911,6 +969,53 @@ finish:
|
||||
return r;
|
||||
}
|
||||
|
||||
void bind_mount_free_many(BindMount *b, unsigned n) {
|
||||
unsigned i;
|
||||
|
||||
assert(b || n == 0);
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
free(b[i].source);
|
||||
free(b[i].destination);
|
||||
}
|
||||
|
||||
free(b);
|
||||
}
|
||||
|
||||
int bind_mount_add(BindMount **b, unsigned *n, const BindMount *item) {
|
||||
_cleanup_free_ char *s = NULL, *d = NULL;
|
||||
BindMount *c;
|
||||
|
||||
assert(b);
|
||||
assert(n);
|
||||
assert(item);
|
||||
|
||||
s = strdup(item->source);
|
||||
if (!s)
|
||||
return -ENOMEM;
|
||||
|
||||
d = strdup(item->destination);
|
||||
if (!d)
|
||||
return -ENOMEM;
|
||||
|
||||
c = realloc_multiply(*b, sizeof(BindMount), *n + 1);
|
||||
if (!c)
|
||||
return -ENOMEM;
|
||||
|
||||
*b = c;
|
||||
|
||||
c[(*n) ++] = (BindMount) {
|
||||
.source = s,
|
||||
.destination = d,
|
||||
.read_only = item->read_only,
|
||||
.recursive = item->recursive,
|
||||
.ignore_enoent = item->ignore_enoent,
|
||||
};
|
||||
|
||||
s = d = 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];
|
||||
|
@ -21,6 +21,7 @@
|
||||
***/
|
||||
|
||||
typedef struct NameSpaceInfo NameSpaceInfo;
|
||||
typedef struct BindMount BindMount;
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
@ -51,20 +52,32 @@ struct NameSpaceInfo {
|
||||
bool protect_kernel_modules:1;
|
||||
};
|
||||
|
||||
int setup_namespace(const char *chroot,
|
||||
const NameSpaceInfo *ns_info,
|
||||
char **read_write_paths,
|
||||
char **read_only_paths,
|
||||
char **inaccessible_paths,
|
||||
const char *tmp_dir,
|
||||
const char *var_tmp_dir,
|
||||
ProtectHome protect_home,
|
||||
ProtectSystem protect_system,
|
||||
unsigned long mount_flags);
|
||||
struct BindMount {
|
||||
char *source;
|
||||
char *destination;
|
||||
bool read_only:1;
|
||||
bool recursive:1;
|
||||
bool ignore_enoent:1;
|
||||
};
|
||||
|
||||
int setup_tmp_dirs(const char *id,
|
||||
char **tmp_dir,
|
||||
char **var_tmp_dir);
|
||||
int setup_namespace(
|
||||
const char *root_directory,
|
||||
const NameSpaceInfo *ns_info,
|
||||
char **read_write_paths,
|
||||
char **read_only_paths,
|
||||
char **inaccessible_paths,
|
||||
const BindMount *bind_mounts,
|
||||
unsigned n_bind_mounts,
|
||||
const char *tmp_dir,
|
||||
const char *var_tmp_dir,
|
||||
ProtectHome protect_home,
|
||||
ProtectSystem protect_system,
|
||||
unsigned long mount_flags);
|
||||
|
||||
int setup_tmp_dirs(
|
||||
const char *id,
|
||||
char **tmp_dir,
|
||||
char **var_tmp_dir);
|
||||
|
||||
int setup_netns(int netns_storage_socket[2]);
|
||||
|
||||
@ -73,3 +86,6 @@ ProtectHome protect_home_from_string(const char *s) _pure_;
|
||||
|
||||
const char* protect_system_to_string(ProtectSystem p) _const_;
|
||||
ProtectSystem protect_system_from_string(const char *s) _pure_;
|
||||
|
||||
void bind_mount_free_many(BindMount *b, unsigned n);
|
||||
int bind_mount_add(BindMount **b, unsigned *n, const BindMount *item);
|
||||
|
@ -590,6 +590,76 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
|
||||
}
|
||||
|
||||
r = sd_bus_message_append(m, "v", "t", f);
|
||||
} else if (STR_IN_SET(field, "BindPaths", "BindReadOnlyPaths")) {
|
||||
const char *p = eq;
|
||||
|
||||
r = sd_bus_message_open_container(m, 'v', "a(ssbt)");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_message_open_container(m, 'a', "(ssbt)");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
for (;;) {
|
||||
_cleanup_free_ char *source = NULL, *destination = NULL;
|
||||
char *s = NULL, *d = NULL;
|
||||
bool ignore_enoent = false;
|
||||
uint64_t flags = MS_REC;
|
||||
|
||||
r = extract_first_word(&p, &source, ":" WHITESPACE, EXTRACT_QUOTES|EXTRACT_DONT_COALESCE_SEPARATORS);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse argument: %m");
|
||||
if (r == 0)
|
||||
break;
|
||||
|
||||
s = source;
|
||||
if (s[0] == '-') {
|
||||
ignore_enoent = true;
|
||||
s++;
|
||||
}
|
||||
|
||||
if (p && p[-1] == ':') {
|
||||
r = extract_first_word(&p, &destination, ":" WHITESPACE, EXTRACT_QUOTES|EXTRACT_DONT_COALESCE_SEPARATORS);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse argument: %m");
|
||||
if (r == 0) {
|
||||
log_error("Missing argument after ':': %s", eq);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
d = destination;
|
||||
|
||||
if (p && p[-1] == ':') {
|
||||
_cleanup_free_ char *options = NULL;
|
||||
|
||||
r = extract_first_word(&p, &options, NULL, EXTRACT_QUOTES);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse argument: %m");
|
||||
|
||||
if (isempty(options) || streq(options, "rbind"))
|
||||
flags = MS_REC;
|
||||
else if (streq(options, "norbind"))
|
||||
flags = 0;
|
||||
else {
|
||||
log_error("Unknown options: %s", eq);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
} else
|
||||
d = s;
|
||||
|
||||
|
||||
r = sd_bus_message_append(m, "(ssbt)", s, d, ignore_enoent, flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = sd_bus_message_close_container(m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_message_close_container(m);
|
||||
} else {
|
||||
log_error("Unknown assignment %s.", assignment);
|
||||
return -EINVAL;
|
||||
|
@ -81,6 +81,7 @@ int main(int argc, char *argv[]) {
|
||||
(char **) writable,
|
||||
(char **) readonly,
|
||||
(char **) inaccessible,
|
||||
&(BindMount) { .source = (char*) "/usr/bin", .destination = (char*) "/etc/systemd", .read_only = true }, 1,
|
||||
tmp_dir,
|
||||
var_tmp_dir,
|
||||
PROTECT_HOME_NO,
|
||||
|
Loading…
Reference in New Issue
Block a user