6592b9759c
This adds a new bus call to service and scope units called AttachProcesses() that moves arbitrary processes into the cgroup of the unit. The primary user for this new API is systemd itself: the systemd --user instance uses this call of the systemd --system instance to migrate processes if itself gets the request to migrate processes and the kernel refuses this due to access restrictions. The primary use-case of this is to make "systemd-run --scope --user …" invoked from user session scopes work correctly on pure cgroupsv2 environments. There, the kernel refuses to migrate processes between two unprivileged-owned cgroups unless the requestor as well as the ownership of the closest parent cgroup all match. This however is not the case between the session-XYZ.scope unit of a login session and the user@ABC.service of the systemd --user instance. The new logic always tries to move the processes on its own, but if that doesn't work when being the user manager, then the system manager is asked to do it instead. The new operation is relatively restrictive: it will only allow to move the processes like this if the caller is root, or the UID of the target unit, caller and process all match. Note that this means that unprivileged users cannot attach processes to scope units, as those do not have "owning" users (i.e. they have now User= field). Fixes: #3388
275 lines
8.7 KiB
C
275 lines
8.7 KiB
C
/* SPDX-License-Identifier: LGPL-2.1+ */
|
|
/***
|
|
This file is part of systemd.
|
|
|
|
Copyright 2013 Lennart Poettering
|
|
|
|
systemd is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU Lesser General Public License as published by
|
|
the Free Software Foundation; either version 2.1 of the License, or
|
|
(at your option) any later version.
|
|
|
|
systemd is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
|
***/
|
|
|
|
#include "alloc-util.h"
|
|
#include "bus-common-errors.h"
|
|
#include "bus-internal.h"
|
|
#include "bus-util.h"
|
|
#include "dbus-cgroup.h"
|
|
#include "dbus-kill.h"
|
|
#include "dbus-scope.h"
|
|
#include "dbus-unit.h"
|
|
#include "dbus-util.h"
|
|
#include "dbus.h"
|
|
#include "scope.h"
|
|
#include "selinux-access.h"
|
|
#include "unit.h"
|
|
|
|
static int bus_scope_abandon(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
|
Scope *s = userdata;
|
|
int r;
|
|
|
|
assert(message);
|
|
assert(s);
|
|
|
|
r = mac_selinux_unit_access_check(UNIT(s), message, "stop", error);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = bus_verify_manage_units_async(UNIT(s)->manager, 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 */
|
|
|
|
r = scope_abandon(s);
|
|
if (r == -ESTALE)
|
|
return sd_bus_error_setf(error, BUS_ERROR_SCOPE_NOT_RUNNING, "Scope %s is not running, cannot abandon.", UNIT(s)->id);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
return sd_bus_reply_method_return(message, NULL);
|
|
}
|
|
|
|
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, scope_result, ScopeResult);
|
|
|
|
const sd_bus_vtable bus_scope_vtable[] = {
|
|
SD_BUS_VTABLE_START(0),
|
|
SD_BUS_PROPERTY("Controller", "s", NULL, offsetof(Scope, controller), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
|
|
SD_BUS_PROPERTY("TimeoutStopUSec", "t", bus_property_get_usec, offsetof(Scope, timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST),
|
|
SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Scope, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
|
|
SD_BUS_SIGNAL("RequestStop", NULL, 0),
|
|
SD_BUS_METHOD("Abandon", NULL, NULL, bus_scope_abandon, SD_BUS_VTABLE_UNPRIVILEGED),
|
|
SD_BUS_VTABLE_END
|
|
};
|
|
|
|
static int bus_scope_set_transient_property(
|
|
Scope *s,
|
|
const char *name,
|
|
sd_bus_message *message,
|
|
UnitWriteFlags flags,
|
|
sd_bus_error *error) {
|
|
|
|
int r;
|
|
|
|
assert(s);
|
|
assert(name);
|
|
assert(message);
|
|
|
|
flags |= UNIT_PRIVATE;
|
|
|
|
if (streq(name, "TimeoutStopUSec"))
|
|
return bus_set_transient_usec(UNIT(s), name, &s->timeout_stop_usec, message, flags, error);
|
|
|
|
if (streq(name, "PIDs")) {
|
|
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
|
|
unsigned n = 0;
|
|
|
|
r = sd_bus_message_enter_container(message, 'a', "u");
|
|
if (r < 0)
|
|
return r;
|
|
|
|
for (;;) {
|
|
uint32_t upid;
|
|
pid_t pid;
|
|
|
|
r = sd_bus_message_read(message, "u", &upid);
|
|
if (r < 0)
|
|
return r;
|
|
if (r == 0)
|
|
break;
|
|
|
|
if (upid == 0) {
|
|
if (!creds) {
|
|
r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
r = sd_bus_creds_get_pid(creds, &pid);
|
|
if (r < 0)
|
|
return r;
|
|
} else
|
|
pid = (uid_t) upid;
|
|
|
|
r = unit_pid_attachable(UNIT(s), pid, error);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
|
|
r = unit_watch_pid(UNIT(s), pid);
|
|
if (r < 0 && r != -EEXIST)
|
|
return r;
|
|
}
|
|
|
|
n++;
|
|
}
|
|
|
|
r = sd_bus_message_exit_container(message);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (n <= 0)
|
|
return -EINVAL;
|
|
|
|
return 1;
|
|
|
|
} else if (streq(name, "Controller")) {
|
|
const char *controller;
|
|
|
|
/* We can't support direct connections with this, as direct connections know no service or unique name
|
|
* concept, but the Controller field stores exactly that. */
|
|
if (sd_bus_message_get_bus(message) != UNIT(s)->manager->api_bus)
|
|
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Sorry, Controller= logic only supported via the bus.");
|
|
|
|
r = sd_bus_message_read(message, "s", &controller);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (!isempty(controller) && !service_name_is_valid(controller))
|
|
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Controller '%s' is not a valid bus name.", controller);
|
|
|
|
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
|
|
r = free_and_strdup(&s->controller, empty_to_null(controller));
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bus_scope_set_property(
|
|
Unit *u,
|
|
const char *name,
|
|
sd_bus_message *message,
|
|
UnitWriteFlags flags,
|
|
sd_bus_error *error) {
|
|
|
|
Scope *s = SCOPE(u);
|
|
int r;
|
|
|
|
assert(s);
|
|
assert(name);
|
|
assert(message);
|
|
|
|
r = bus_cgroup_set_property(u, &s->cgroup_context, name, message, flags, error);
|
|
if (r != 0)
|
|
return r;
|
|
|
|
if (u->load_state == UNIT_STUB) {
|
|
/* While we are created we still accept PIDs */
|
|
|
|
r = bus_scope_set_transient_property(s, name, message, flags, error);
|
|
if (r != 0)
|
|
return r;
|
|
|
|
r = bus_kill_context_set_transient_property(u, &s->kill_context, name, message, flags, error);
|
|
if (r != 0)
|
|
return r;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bus_scope_commit_properties(Unit *u) {
|
|
assert(u);
|
|
|
|
unit_update_cgroup_members_masks(u);
|
|
unit_realize_cgroup(u);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bus_scope_send_request_stop(Scope *s) {
|
|
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
|
|
_cleanup_free_ char *p = NULL;
|
|
int r;
|
|
|
|
assert(s);
|
|
|
|
if (!s->controller)
|
|
return 0;
|
|
|
|
p = unit_dbus_path(UNIT(s));
|
|
if (!p)
|
|
return -ENOMEM;
|
|
|
|
r = sd_bus_message_new_signal(
|
|
UNIT(s)->manager->api_bus,
|
|
&m,
|
|
p,
|
|
"org.freedesktop.systemd1.Scope",
|
|
"RequestStop");
|
|
if (r < 0)
|
|
return r;
|
|
|
|
return sd_bus_send_to(UNIT(s)->manager->api_bus, m, s->controller, NULL);
|
|
}
|
|
|
|
static int on_controller_gone(sd_bus_track *track, void *userdata) {
|
|
Scope *s = userdata;
|
|
|
|
assert(track);
|
|
|
|
if (s->controller) {
|
|
log_unit_debug(UNIT(s), "Controller %s disappeared from bus.", s->controller);
|
|
unit_add_to_dbus_queue(UNIT(s));
|
|
s->controller = mfree(s->controller);
|
|
}
|
|
|
|
s->controller_track = sd_bus_track_unref(s->controller_track);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bus_scope_track_controller(Scope *s) {
|
|
int r;
|
|
|
|
assert(s);
|
|
|
|
if (!s->controller || s->controller_track)
|
|
return 0;
|
|
|
|
r = sd_bus_track_new(UNIT(s)->manager->api_bus, &s->controller_track, on_controller_gone, s);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = sd_bus_track_add_name(s->controller_track, s->controller);
|
|
if (r < 0) {
|
|
s->controller_track = sd_bus_track_unref(s->controller_track);
|
|
return r;
|
|
}
|
|
|
|
return 0;
|
|
}
|