diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml
index 78fd0b3378d..90d0d664147 100644
--- a/man/org.freedesktop.systemd1.xml
+++ b/man/org.freedesktop.systemd1.xml
@@ -116,6 +116,11 @@ node /org/freedesktop/systemd1 {
SetUnitProperties(in s name,
in b runtime,
in a(sv) properties);
+ BindMountUnit(in s name,
+ in s source,
+ in s destination,
+ in b read_only,
+ in b mkdir);
RefUnit(in s name);
UnrefUnit(in s name);
StartTransientUnit(in s name,
@@ -767,6 +772,8 @@ node /org/freedesktop/systemd1 {
+
+
@@ -1156,6 +1163,9 @@ node /org/freedesktop/systemd1 {
the "Try" flavor is used in which case a service that isn't running is not affected by the restart. The
"ReloadOrRestart" flavors attempt a reload if the unit supports it and use a restart otherwise.
+ BindMountUnit() can be used to bind mount new files or directories into
+ a running service mount namespace.
+
KillUnit() may be used to kill (i.e. send a signal to) all processes of a
unit. It takes the unit name, an enum who and a UNIX
signal number to send. The who enum is one of
@@ -2193,6 +2203,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
interface org.freedesktop.systemd1.Service {
methods:
+ BindMount(in s source,
+ in s destination,
+ in b read_only,
+ in b mkdir);
GetProcesses(out a(sus) processes);
AttachProcesses(in s subcgroup,
in au pids);
@@ -3252,6 +3266,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
+
+
@@ -3810,6 +3826,17 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
+
+ Methods
+
+ BindMount() implements the same operation as the respective method on the
+ Manager object (see above). However, this method operates on the service
+ object and hence does not take a unit name parameter. Invoking the methods directly on the Manager
+ object has the advantage of not requiring a GetUnit() call to get the unit object
+ for a specific unit name. Calling the methods on the Manager object is hence a round trip
+ optimization.
+
+
Properties
diff --git a/man/systemctl.xml b/man/systemctl.xml
index bb702cb078b..a954e8727b6 100644
--- a/man/systemctl.xml
+++ b/man/systemctl.xml
@@ -550,6 +550,23 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
+
+ bindUNITPATH [PATH]
+
+ Bind mounts a file or directory from the host into the specified unit's view. The first path
+ argument is the source file or directory on the host, the second path argument is the destination file or
+ directory in the unit's view. When the latter is omitted, the destination path in the unit's view is the same as
+ the source path on the host. When combined with the switch, a ready-only bind
+ mount is created. When combined with the switch, the destination path is first created
+ before the mount is applied. Note that this option is currently only supported for units that run within a mount
+ namespace (e.g.: with , , etc.). This command supports bind
+ mounting directories, regular files, device nodes, AF_UNIX socket nodes, as well as FIFOs.
+ The bind mount is ephemeral, and it is undone as soon as the current unit process exists.
+ Note that the namespace mentioned here, where the bind mount will be added to, is the one where the main service
+ process runs, as other processes run in distinct namespaces (e.g.: ,
+ , etc.)
+
+
service-log-levelSERVICE [LEVEL]
@@ -2246,6 +2263,21 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
+
+
+
+ When used with bind, creates the destination file or directory before
+ applying the bind mount. Note that even though the name of this option suggests that it is suitable only for
+ directories, this option also creates the destination file node to mount over if the object to mount is not
+ a directory, but a regular file, device node, socket or FIFO.
+
+
+
+
+
+ When used with bind, creates a read-only bind mount.
+
+
diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
index 568839e0d9e..9adb6a298e8 100644
--- a/man/systemd.exec.xml
+++ b/man/systemd.exec.xml
@@ -285,6 +285,10 @@
the service with a private, minimal version of /dev/, combine this option with
PrivateDevices=.
+ In order to allow propagating mounts at runtime in a safe manner, /run/systemd/propagate
+ on the host will be used to set up new mounts, and /run/host/incoming/ in the private namespace
+ will be used as an intermediate step to store them before being moved to the final mount point.
+
diff --git a/meson.build b/meson.build
index e360ec95c9a..2caf069602c 100644
--- a/meson.build
+++ b/meson.build
@@ -2198,6 +2198,8 @@ public_programs += executable(
'src/systemctl/systemctl-log-setting.h',
'src/systemctl/systemctl-logind.c',
'src/systemctl/systemctl-logind.h',
+ 'src/systemctl/systemctl-mount.c',
+ 'src/systemctl/systemctl-mount.h',
'src/systemctl/systemctl-preset-all.c',
'src/systemctl/systemctl-preset-all.h',
'src/systemctl/systemctl-reset-failed.c',
diff --git a/shell-completion/bash/systemctl.in b/shell-completion/bash/systemctl.in
index 4bd80948d64..7e99ef4bf39 100644
--- a/shell-completion/bash/systemctl.in
+++ b/shell-completion/bash/systemctl.in
@@ -214,7 +214,7 @@ _systemctl () {
list-timers list-units list-unit-files poweroff
reboot rescue show-environment suspend get-default
is-system-running preset-all'
- [FILE]='link switch-root'
+ [FILE]='link switch-root bind'
[TARGETS]='set-default'
[MACHINES]='list-machines'
[LOG_LEVEL]='log-level'
diff --git a/shell-completion/zsh/_systemctl.in b/shell-completion/zsh/_systemctl.in
index 10af6d5121e..c4ea78b3c10 100644
--- a/shell-completion/zsh/_systemctl.in
+++ b/shell-completion/zsh/_systemctl.in
@@ -31,6 +31,7 @@
"reset-failed:Reset failed state for all, one, or more units"
"list-dependencies:Show unit dependency tree"
"clean:Remove configuration, state, cache, logs or runtime data of units"
+ "bind:Bind mount a path from the host into a unit's namespace"
)
local -a machine_commands=(
@@ -378,6 +379,10 @@ done
_files
}
+(( $+functions[_systemctl_bind] )) || _systemctl_bind() {
+ _files
+ }
+
# no systemctl completion for:
# [STANDALONE]='daemon-reexec daemon-reload default
# emergency exit halt kexec list-jobs list-units
diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
index 3e1d609aa37..4b88f0d9f07 100644
--- a/src/core/dbus-manager.c
+++ b/src/core/dbus-manager.c
@@ -16,6 +16,7 @@
#include "dbus-job.h"
#include "dbus-manager.h"
#include "dbus-scope.h"
+#include "dbus-service.h"
#include "dbus-unit.h"
#include "dbus.h"
#include "env-util.h"
@@ -725,6 +726,11 @@ static int method_set_unit_properties(sd_bus_message *message, void *userdata, s
return method_generic_unit_operation(message, userdata, error, bus_unit_method_set_properties, GENERIC_UNIT_LOAD|GENERIC_UNIT_VALIDATE_LOADED);
}
+static int method_bind_mount_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ /* Only add mounts on fully loaded units */
+ return method_generic_unit_operation(message, userdata, error, bus_service_method_bind_mount, GENERIC_UNIT_VALIDATE_LOADED);
+}
+
static int method_ref_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
/* Only allow reffing of fully loaded units, and make sure reffing a unit loads it. */
return method_generic_unit_operation(message, userdata, error, bus_unit_method_ref, GENERIC_UNIT_LOAD|GENERIC_UNIT_VALIDATE_LOADED);
@@ -2760,6 +2766,16 @@ const sd_bus_vtable bus_manager_vtable[] = {
NULL,,
method_set_unit_properties,
SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_NAMES("BindMountUnit",
+ "sssbb",
+ SD_BUS_PARAM(name)
+ SD_BUS_PARAM(source)
+ SD_BUS_PARAM(destination)
+ SD_BUS_PARAM(read_only)
+ SD_BUS_PARAM(mkdir),
+ NULL,,
+ method_bind_mount_unit,
+ SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_NAMES("RefUnit",
"s",
SD_BUS_PARAM(name),
diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c
index 64f9d4ab362..6df93e44a43 100644
--- a/src/core/dbus-service.c
+++ b/src/core/dbus-service.c
@@ -11,11 +11,15 @@
#include "dbus-manager.h"
#include "dbus-service.h"
#include "dbus-util.h"
+#include "execute.h"
#include "exit-status.h"
#include "fd-util.h"
#include "fileio.h"
+#include "locale-util.h"
+#include "mount-util.h"
#include "parse-util.h"
#include "path-util.h"
+#include "selinux-access.h"
#include "service.h"
#include "signal-util.h"
#include "string-util.h"
@@ -91,6 +95,79 @@ static int property_get_exit_status_set(
return sd_bus_message_close_container(reply);
}
+int bus_service_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ int read_only, make_file_or_directory;
+ const char *dest, *src, *propagate_directory;
+ Unit *u = userdata;
+ ExecContext *c;
+ pid_t unit_pid;
+ int r;
+
+ assert(message);
+ assert(u);
+
+ if (!MANAGER_IS_SYSTEM(u->manager))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Adding bind mounts at runtime is only supported for system managers.");
+
+ r = mac_selinux_unit_access_check(u, message, "start", error);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read(message, "ssbb", &src, &dest, &read_only, &make_file_or_directory);
+ if (r < 0)
+ return r;
+
+ if (!path_is_absolute(src) || !path_is_normalized(src))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute and normalized.");
+
+ if (isempty(dest))
+ dest = src;
+ else if (!path_is_absolute(dest) || !path_is_normalized(dest))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute and normalized.");
+
+ r = bus_verify_manage_units_async_full(
+ u,
+ "bind-mount",
+ CAP_SYS_ADMIN,
+ N_("Authentication is required to bind mount on '$(unit)'."),
+ true,
+ message,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+
+ if (u->type != UNIT_SERVICE)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unit is not of type .service");
+
+ /* If it would be dropped at startup time, return an error. The context should always be available, but
+ * there's an assert in exec_needs_mount_namespace, so double-check just in case. */
+ c = unit_get_exec_context(u);
+ if (!c)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot access unit execution context");
+ if (path_startswith_strv(dest, c->inaccessible_paths))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "%s is not accessible to this unit", dest);
+
+ /* Ensure that the unit was started in a private mount namespace */
+ if (!exec_needs_mount_namespace(c, NULL, unit_get_exec_runtime(u)))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unit not running in private mount namespace, cannot activate bind mount");
+
+ unit_pid = unit_main_pid(u);
+ if (unit_pid == 0 || !UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u)))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unit is not running");
+
+ propagate_directory = strjoina("/run/systemd/propagate/", u->id);
+ r = bind_mount_in_namespace(unit_pid,
+ propagate_directory,
+ "/run/systemd/incoming/",
+ src, dest, read_only, make_file_or_directory);
+ if (r < 0)
+ return sd_bus_error_set_errnof(error, r, "Failed to mount %s on %s in unit's namespace: %m", src, dest);
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
const sd_bus_vtable bus_service_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Service, type), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -146,6 +223,16 @@ const sd_bus_vtable bus_service_vtable[] = {
BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPost", offsetof(Service, exec_command[SERVICE_EXEC_STOP_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
BUS_EXEC_EX_COMMAND_LIST_VTABLE("ExecStopPostEx", offsetof(Service, exec_command[SERVICE_EXEC_STOP_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
+ SD_BUS_METHOD_WITH_NAMES("BindMount",
+ "ssbb",
+ SD_BUS_PARAM(source)
+ SD_BUS_PARAM(destination)
+ SD_BUS_PARAM(read_only)
+ SD_BUS_PARAM(mkdir),
+ NULL,,
+ bus_service_method_bind_mount,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+
/* The following four are obsolete, and thus marked hidden here. They moved into the Unit interface */
SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_ratelimit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_ratelimit.burst), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
diff --git a/src/core/dbus-service.h b/src/core/dbus-service.h
index 69311675c9b..5b7b7b757be 100644
--- a/src/core/dbus-service.h
+++ b/src/core/dbus-service.h
@@ -9,4 +9,5 @@
extern const sd_bus_vtable bus_service_vtable[];
int bus_service_set_property(Unit *u, const char *name, sd_bus_message *i, UnitWriteFlags flags, sd_bus_error *error);
+int bus_service_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_service_commit_properties(Unit *u);
diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c
index 427152a757b..67cc58ee9e0 100644
--- a/src/core/dbus-unit.c
+++ b/src/core/dbus-unit.c
@@ -323,38 +323,6 @@ static int property_get_load_error(
return sd_bus_message_append(reply, "(ss)", NULL, NULL);
}
-static int bus_verify_manage_units_async_full(
- Unit *u,
- const char *verb,
- int capability,
- const char *polkit_message,
- bool interactive,
- sd_bus_message *call,
- sd_bus_error *error) {
-
- const char *details[9] = {
- "unit", u->id,
- "verb", verb,
- };
-
- if (polkit_message) {
- details[4] = "polkit.message";
- details[5] = polkit_message;
- details[6] = "polkit.gettext_domain";
- details[7] = GETTEXT_PACKAGE;
- }
-
- return bus_verify_polkit_async(
- call,
- capability,
- "org.freedesktop.systemd1.manage-units",
- details,
- interactive,
- UID_INVALID,
- &u->manager->polkit_registry,
- error);
-}
-
static const char *const polkit_message_for_job[_JOB_TYPE_MAX] = {
[JOB_START] = N_("Authentication is required to start '$(unit)'."),
[JOB_STOP] = N_("Authentication is required to stop '$(unit)'."),
diff --git a/src/core/dbus-util.c b/src/core/dbus-util.c
index d6223db305c..2d22bc699a3 100644
--- a/src/core/dbus-util.c
+++ b/src/core/dbus-util.c
@@ -1,5 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include "bus-polkit.h"
#include "bus-util.h"
#include "dbus-util.h"
#include "parse-util.h"
@@ -153,3 +154,35 @@ int bus_set_transient_usec_internal(
return 1;
}
+
+int bus_verify_manage_units_async_full(
+ Unit *u,
+ const char *verb,
+ int capability,
+ const char *polkit_message,
+ bool interactive,
+ sd_bus_message *call,
+ sd_bus_error *error) {
+
+ const char *details[9] = {
+ "unit", u->id,
+ "verb", verb,
+ };
+
+ if (polkit_message) {
+ details[4] = "polkit.message";
+ details[5] = polkit_message;
+ details[6] = "polkit.gettext_domain";
+ details[7] = GETTEXT_PACKAGE;
+ }
+
+ return bus_verify_polkit_async(
+ call,
+ capability,
+ "org.freedesktop.systemd1.manage-units",
+ details,
+ interactive,
+ UID_INVALID,
+ &u->manager->polkit_registry,
+ error);
+}
diff --git a/src/core/dbus-util.h b/src/core/dbus-util.h
index 4e7c68e8439..e35c632d378 100644
--- a/src/core/dbus-util.h
+++ b/src/core/dbus-util.h
@@ -248,3 +248,4 @@ static inline int bus_set_transient_usec(Unit *u, const char *name, usec_t *p, s
static inline int bus_set_transient_usec_fix_0(Unit *u, const char *name, usec_t *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error) {
return bus_set_transient_usec_internal(u, name, p, true, message, flags, error);
}
+int bus_verify_manage_units_async_full(Unit *u, const char *verb, int capability, const char *polkit_message, bool interactive, sd_bus_message *call, sd_bus_error *error);
diff --git a/src/core/execute.c b/src/core/execute.c
index ee5f082783b..5f170db8d17 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -1987,13 +1987,12 @@ static int build_pass_environment(const ExecContext *c, char ***ret) {
return 0;
}
-static bool exec_needs_mount_namespace(
+bool exec_needs_mount_namespace(
const ExecContext *context,
const ExecParameters *params,
const ExecRuntime *runtime) {
assert(context);
- assert(params);
if (context->root_image)
return true;
@@ -2035,7 +2034,7 @@ static bool exec_needs_mount_namespace(
return true;
for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) {
- if (!params->prefix[t])
+ if (params && !params->prefix[t])
continue;
if (!strv_isempty(context->directories[t].paths))
@@ -3115,7 +3114,7 @@ static int apply_mount_namespace(
_cleanup_strv_free_ char **empty_directories = NULL;
const char *tmp_dir = NULL, *var_tmp_dir = NULL;
const char *root_dir = NULL, *root_image = NULL;
- _cleanup_free_ char *creds_path = NULL;
+ _cleanup_free_ char *creds_path = NULL, *incoming_dir = NULL, *propagate_dir = NULL;
NamespaceInfo ns_info;
bool needs_sandboxing;
BindMount *bind_mounts = NULL;
@@ -3192,6 +3191,15 @@ static int apply_mount_namespace(
}
}
+ if (MANAGER_IS_SYSTEM(u->manager)) {
+ propagate_dir = path_join("/run/systemd/propagate/", u->id);
+ if (!propagate_dir)
+ return -ENOMEM;
+ incoming_dir = strdup("/run/systemd/incoming");
+ if (!incoming_dir)
+ return -ENOMEM;
+ }
+
r = setup_namespace(root_dir, root_image, context->root_image_options,
&ns_info, context->read_write_paths,
needs_sandboxing ? context->read_only_paths : NULL,
@@ -3211,6 +3219,8 @@ static int apply_mount_namespace(
context->root_hash, context->root_hash_size, context->root_hash_path,
context->root_hash_sig, context->root_hash_sig_size, context->root_hash_sig_path,
context->root_verity,
+ propagate_dir,
+ incoming_dir,
DISSECT_IMAGE_DISCARD_ON_LOOP|DISSECT_IMAGE_RELAX_VAR_CHECK|DISSECT_IMAGE_FSCK,
error_path);
diff --git a/src/core/execute.h b/src/core/execute.h
index da8d6ae2729..2da4699df18 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -471,3 +471,5 @@ ExecDirectoryType exec_directory_type_from_string(const char *s) _pure_;
const char* exec_resource_type_to_string(ExecDirectoryType i) _const_;
ExecDirectoryType exec_resource_type_from_string(const char *s) _pure_;
+
+bool exec_needs_mount_namespace(const ExecContext *context, const ExecParameters *params, const ExecRuntime *runtime);
diff --git a/src/core/namespace.c b/src/core/namespace.c
index 73a8fa73a4d..4b5519e11b2 100644
--- a/src/core/namespace.c
+++ b/src/core/namespace.c
@@ -1301,7 +1301,8 @@ static size_t namespace_calculate_mounts(
const char* tmp_dir,
const char* var_tmp_dir,
const char *creds_path,
- const char* log_namespace) {
+ const char* log_namespace,
+ bool setup_propagate) {
size_t protect_home_cnt;
size_t protect_system_cnt =
@@ -1328,6 +1329,7 @@ static size_t namespace_calculate_mounts(
n_bind_mounts +
n_mount_images +
n_temporary_filesystems +
+ (setup_propagate ? 1 : 0) + /* /run/systemd/incoming */
ns_info->private_dev +
(ns_info->protect_kernel_tunables ? ELEMENTSOF(protect_kernel_tunables_table) : 0) +
(ns_info->protect_kernel_modules ? ELEMENTSOF(protect_kernel_modules_table) : 0) +
@@ -1487,6 +1489,8 @@ int setup_namespace(
size_t root_hash_sig_size,
const char *root_hash_sig_path,
const char *verity_data_path,
+ const char *propagate_dir,
+ const char *incoming_dir,
DissectImageFlags dissect_image_flags,
char **error_path) {
@@ -1495,13 +1499,16 @@ int setup_namespace(
_cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL;
_cleanup_(verity_settings_done) VeritySettings verity = VERITY_SETTINGS_DEFAULT;
MountEntry *m = NULL, *mounts = NULL;
- bool require_prefix = false;
+ bool require_prefix = false, setup_propagate = false;
const char *root;
size_t n_mounts;
int r;
assert(ns_info);
+ if (!isempty(propagate_dir) && !isempty(incoming_dir))
+ setup_propagate = true;
+
if (mount_flags == 0)
mount_flags = MS_SHARED;
@@ -1585,7 +1592,8 @@ int setup_namespace(
n_mount_images,
tmp_dir, var_tmp_dir,
creds_path,
- log_namespace);
+ log_namespace,
+ setup_propagate);
if (n_mounts > 0) {
m = mounts = new0(MountEntry, n_mounts);
@@ -1754,6 +1762,15 @@ int setup_namespace(
};
}
+ /* Will be used to add bind mounts at runtime */
+ if (setup_propagate)
+ *(m++) = (MountEntry) {
+ .source_const = propagate_dir,
+ .path_const = incoming_dir,
+ .mode = BIND_MOUNT,
+ .read_only = true,
+ };
+
assert(mounts + n_mounts == m);
/* Prepend the root directory where that's necessary */
@@ -1778,6 +1795,10 @@ int setup_namespace(
goto finish;
}
+ /* Create the source directory to allow runtime propagation of mounts */
+ if (setup_propagate)
+ (void) mkdir_p(propagate_dir, 0600);
+
/* Remount / as SLAVE so that nothing now mounted in the namespace
* shows up in the parent */
if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) {
@@ -1919,6 +1940,16 @@ int setup_namespace(
goto finish;
}
+ /* bind_mount_in_namespace() will MS_MOVE into that directory, and that's only
+ * supported for non-shared mounts. This needs to happen after remounting / or it will fail. */
+ if (setup_propagate) {
+ r = mount(NULL, incoming_dir, NULL, MS_SLAVE, NULL);
+ if (r < 0) {
+ log_error_errno(r, "Failed to remount %s with MS_SLAVE: %m", incoming_dir);
+ goto finish;
+ }
+ }
+
r = 0;
finish:
diff --git a/src/core/namespace.h b/src/core/namespace.h
index da0861c4061..91ee44cd517 100644
--- a/src/core/namespace.h
+++ b/src/core/namespace.h
@@ -127,6 +127,8 @@ int setup_namespace(
size_t root_hash_sig_size,
const char *root_hash_sig_path,
const char *root_verity,
+ const char *propagate_dir,
+ const char *incoming_dir,
DissectImageFlags dissected_image_flags,
char **error_path);
diff --git a/src/core/org.freedesktop.systemd1.conf b/src/core/org.freedesktop.systemd1.conf
index 8b32379835d..0cea4d2b024 100644
--- a/src/core/org.freedesktop.systemd1.conf
+++ b/src/core/org.freedesktop.systemd1.conf
@@ -222,6 +222,10 @@
send_interface="org.freedesktop.systemd1.Manager"
send_member="ReloadOrTryRestartUnit"/>
+
+
@@ -392,6 +396,10 @@
send_interface="org.freedesktop.systemd1.Service"
send_member="AttachProcesses"/>
+
+
/run/testservice-57-fixed
+mkdir -p /run/inaccessible
+
+systemctl start testsuite-57-namespaced.service
+
+# Ensure that inaccessible paths aren't bypassed by the runtime setup
+set +e
+systemctl bind --mkdir testsuite-57-namespaced.service /run/testservice-57-fixed /run/inaccessible/testfile_fixed && exit 1
+set -e
+
+echo "MARKER_RUNTIME" > /run/testservice-57-runtime
+
+systemctl bind --mkdir testsuite-57-namespaced.service /run/testservice-57-runtime /tmp/testfile_runtime
+
+while systemctl show -P SubState testsuite-57-namespaced.service | grep -q running
+do
+ sleep 0.1
+done
+
+systemctl is-active testsuite-57-namespaced.service
+
+# Now test that systemctl bind fails when attempted on a non-namespaced unit
+systemctl start testsuite-57-non-namespaced.service
+
+set +e
+systemctl bind --mkdir testsuite-57-non-namespaced.service /run/testservice-57-runtime /tmp/testfile_runtime && exit 1
+set -e
+
+while systemctl show -P SubState testsuite-57-non-namespaced.service | grep -q running
+do
+ sleep 0.1
+done
+
+set +e
+systemctl is-active testsuite-57-non-namespaced.service && exit 1
+set -e
+
+echo OK > /testok
+
+exit 0