summaryrefslogtreecommitdiff
path: root/src/core/dbus-service.c
diff options
context:
space:
mode:
authorLuca Boccassi <luca.boccassi@microsoft.com>2020-07-30 19:37:10 +0100
committerLuca Boccassi <luca.boccassi@microsoft.com>2021-01-18 17:24:05 +0000
commit5e8deb94c6f05137942b10b5288a37d9b09fd43f (patch)
tree1cbed6e76c6398d4c183a6b71e0a9927386833d8 /src/core/dbus-service.c
parent94293d65cd4125347e21b3e423d0e245226b1be2 (diff)
downloadsystemd-5e8deb94c6f05137942b10b5288a37d9b09fd43f.tar.gz
core: add DBUS method to bind mount new nodes without service restart
Allow to setup new bind mounts for a service at runtime (via either DBUS or a new 'systemctl bind' verb) with a new helper that forks into the unit's mount namespace. Add a new integration test to cover this. Useful for zero-downtime addition to services that are running inside mount namespaces, especially when using RootImage/RootDirectory. If a service runs with a read-only root, a tmpfs is added on /run to ensure we can create the airlock directory for incoming mounts under /run/host/incoming.
Diffstat (limited to 'src/core/dbus-service.c')
-rw-r--r--src/core/dbus-service.c87
1 files changed, 87 insertions, 0 deletions
diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c
index 64f9d4ab36..6df93e44a4 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),