mirror of
https://github.com/systemd/systemd.git
synced 2025-03-02 12:58:35 +03:00
pid1: allow removal of foreign-owned subcgroups of cgroups owned by some user (#35922)
This improves operation in unprivileged userns environments, where unpriv user code might invoke a container with a delegated userns UID range, and thus ends up with a subcgroup owned by another UID. With this patch any user is always allowed to remove their own cgroups even if it has subcgroups owned by other users. This removes a DoS of sorts, and enforces the rule that users strictly own everything below cgroups they own.
This commit is contained in:
commit
a241b796fa
@ -147,6 +147,9 @@ node /org/freedesktop/systemd1 {
|
||||
AttachProcessesToUnit(in s unit_name,
|
||||
in s subcgroup,
|
||||
in au pids);
|
||||
RemoveSubgroupFromUnit(in s unit_name,
|
||||
in s subcgroup,
|
||||
in t flags);
|
||||
AbandonScope(in s name);
|
||||
GetJob(in u id,
|
||||
out o job);
|
||||
@ -870,6 +873,8 @@ node /org/freedesktop/systemd1 {
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="AttachProcessesToUnit()"/>
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="RemoveSubgroupFromUnit()"/>
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="AbandonScope()"/>
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="GetJob()"/>
|
||||
@ -1599,6 +1604,13 @@ node /org/freedesktop/systemd1 {
|
||||
parameters. The possible values are <literal>configuration</literal>, <literal>state</literal>,
|
||||
<literal>logs</literal>, <literal>cache</literal>, <literal>runtime</literal>,
|
||||
<literal>fdstore</literal>, and <literal>all</literal>.</para>
|
||||
|
||||
<para><function>RemoveSubgroupFromUnit()</function> removes a subcgroup belonging to a unit's
|
||||
cgroup. Takes three arguments: the unit name (if empty defaults to the caller's unit), a cgroup path
|
||||
(which must start start with a slash <literal>/</literal>), which is taken relative to the unit's
|
||||
cgroup, and a flags argument (which must be zero for now). This is primarily useful for unprivileged
|
||||
service managers to ask the system service manager for removal of subcgroups it manages, in case one
|
||||
was delegated to other UIDs.</para>
|
||||
</refsect2>
|
||||
|
||||
<refsect2>
|
||||
@ -2704,6 +2716,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
|
||||
GetProcesses(out a(sus) processes);
|
||||
AttachProcesses(in s subcgroup,
|
||||
in au pids);
|
||||
RemoveSubgroup(in s subcgroup,
|
||||
in t flags);
|
||||
properties:
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly s Type = '...';
|
||||
@ -3398,6 +3412,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
|
||||
|
||||
<!--method AttachProcesses is not documented!-->
|
||||
|
||||
<!--method RemoveSubgroup is not documented!-->
|
||||
|
||||
<!--property Type is not documented!-->
|
||||
|
||||
<!--property ExitType is not documented!-->
|
||||
@ -4006,6 +4022,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="AttachProcesses()"/>
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="RemoveSubgroup()"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="Type"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="ExitType"/>
|
||||
@ -4901,6 +4919,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
|
||||
GetProcesses(out a(sus) processes);
|
||||
AttachProcesses(in s subcgroup,
|
||||
in au pids);
|
||||
RemoveSubgroup(in s subcgroup,
|
||||
in t flags);
|
||||
properties:
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly s BindIPv6Only = '...';
|
||||
@ -5592,6 +5612,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
|
||||
|
||||
<!--method AttachProcesses is not documented!-->
|
||||
|
||||
<!--method RemoveSubgroup is not documented!-->
|
||||
|
||||
<!--property BindIPv6Only is not documented!-->
|
||||
|
||||
<!--property Backlog is not documented!-->
|
||||
@ -6206,6 +6228,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="AttachProcesses()"/>
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="RemoveSubgroup()"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="BindIPv6Only"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="Backlog"/>
|
||||
@ -7001,6 +7025,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
|
||||
GetProcesses(out a(sus) processes);
|
||||
AttachProcesses(in s subcgroup,
|
||||
in au pids);
|
||||
RemoveSubgroup(in s subcgroup,
|
||||
in t flags);
|
||||
properties:
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly s Where = '...';
|
||||
@ -7601,6 +7627,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
|
||||
|
||||
<!--method AttachProcesses is not documented!-->
|
||||
|
||||
<!--method RemoveSubgroup is not documented!-->
|
||||
|
||||
<!--property Where is not documented!-->
|
||||
|
||||
<!--property What is not documented!-->
|
||||
@ -8141,6 +8169,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="AttachProcesses()"/>
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="RemoveSubgroup()"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="Where"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="What"/>
|
||||
@ -8991,6 +9021,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
|
||||
GetProcesses(out a(sus) processes);
|
||||
AttachProcesses(in s subcgroup,
|
||||
in au pids);
|
||||
RemoveSubgroup(in s subcgroup,
|
||||
in t flags);
|
||||
properties:
|
||||
readonly s What = '...';
|
||||
readonly i Priority = ...;
|
||||
@ -9577,6 +9609,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
|
||||
|
||||
<!--method AttachProcesses is not documented!-->
|
||||
|
||||
<!--method RemoveSubgroup is not documented!-->
|
||||
|
||||
<!--property What is not documented!-->
|
||||
|
||||
<!--property Priority is not documented!-->
|
||||
@ -10103,6 +10137,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="AttachProcesses()"/>
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="RemoveSubgroup()"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="What"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="Priority"/>
|
||||
@ -10805,6 +10841,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
|
||||
GetProcesses(out a(sus) processes);
|
||||
AttachProcesses(in s subcgroup,
|
||||
in au pids);
|
||||
RemoveSubgroup(in s subcgroup,
|
||||
in t flags);
|
||||
properties:
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
|
||||
readonly s Slice = '...';
|
||||
@ -11004,6 +11042,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
|
||||
|
||||
<!--method AttachProcesses is not documented!-->
|
||||
|
||||
<!--method RemoveSubgroup is not documented!-->
|
||||
|
||||
<!--property Slice is not documented!-->
|
||||
|
||||
<!--property ControlGroupId is not documented!-->
|
||||
@ -11196,6 +11236,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="AttachProcesses()"/>
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="RemoveSubgroup()"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="Slice"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="ControlGroup"/>
|
||||
@ -11411,6 +11453,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
|
||||
GetProcesses(out a(sus) processes);
|
||||
AttachProcesses(in s subcgroup,
|
||||
in au pids);
|
||||
RemoveSubgroup(in s subcgroup,
|
||||
in t flags);
|
||||
signals:
|
||||
RequestStop();
|
||||
properties:
|
||||
@ -11636,6 +11680,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
|
||||
|
||||
<!--method AttachProcesses is not documented!-->
|
||||
|
||||
<!--method RemoveSubgroup is not documented!-->
|
||||
|
||||
<!--property RuntimeMaxUSec is not documented!-->
|
||||
|
||||
<!--property RuntimeRandomizedExtraUSec is not documented!-->
|
||||
@ -11850,6 +11896,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="AttachProcesses()"/>
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="RemoveSubgroup()"/>
|
||||
|
||||
<variablelist class="dbus-signal" generated="True" extra-ref="RequestStop()"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="Controller"/>
|
||||
@ -12254,6 +12302,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
|
||||
<varname>ShutdownStartTimestamp</varname>,
|
||||
<varname>ShutdownStartTimestampMonotonic</varname>, and
|
||||
<varname>SoftRebootsCount</varname> were added in version 256.</para>
|
||||
<para><function>RemoveSubgroupFromUnit()</function> was added in version 258.</para>
|
||||
</refsect2>
|
||||
<refsect2>
|
||||
<title>Unit Objects</title>
|
||||
@ -12320,7 +12369,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
|
||||
<varname>ProtectControlGroupsEx</varname>,
|
||||
<varname>PrivateUsersEx</varname>, and
|
||||
<varname>PrivatePIDs</varname> were added in version 257.</para>
|
||||
<para><varname>ProtectHostnameEx</varname> was added in version 258.</para>
|
||||
<para><varname>ProtectHostnameEx</varname> and <function>RemoveSubGroup()</function> were added in version 258.</para>
|
||||
</refsect2>
|
||||
<refsect2>
|
||||
<title>Socket Unit Objects</title>
|
||||
@ -12364,7 +12413,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
|
||||
<varname>ManagedOOMMemoryPressureDurationUSec</varname>,
|
||||
<varname>ProtectControlGroupsEx</varname>, and
|
||||
<varname>PrivatePIDs</varname> were added in version 257.</para>
|
||||
<para><varname>ProtectHostnameEx</varname> was added in version 258.</para>
|
||||
<para><varname>ProtectHostnameEx</varname> and <function>RemoveSubgroup()</function> were added in version 258.</para>
|
||||
</refsect2>
|
||||
<refsect2>
|
||||
<title>Mount Unit Objects</title>
|
||||
@ -12405,7 +12454,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
|
||||
<varname>ManagedOOMMemoryPressureDurationUSec</varname>,
|
||||
<varname>ProtectControlGroupsEx</varname>, and
|
||||
<varname>PrivatePIDs</varname> were added in version 257.</para>
|
||||
<para><varname>ProtectHostnameEx</varname> was added in version 258.</para>
|
||||
<para><varname>ProtectHostnameEx</varname> and <function>RemoveSubgroup()</function> was added in version 258.</para>
|
||||
</refsect2>
|
||||
<refsect2>
|
||||
<title>Swap Unit Objects</title>
|
||||
@ -12446,7 +12495,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
|
||||
<varname>ManagedOOMMemoryPressureDurationUSec</varname>,
|
||||
<varname>ProtectControlGroupsEx</varname>, and
|
||||
<varname>PrivatePIDs</varname> were added in version 257.</para>
|
||||
<para><varname>ProtectHostnameEx</varname> was added in version 258.</para>
|
||||
<para><varname>ProtectHostnameEx</varname> and <function>RemoveSubgroup()</function> were added in version 258.</para>
|
||||
</refsect2>
|
||||
<refsect2>
|
||||
<title>Slice Unit Objects</title>
|
||||
@ -12472,6 +12521,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
|
||||
<varname>EffectiveTasksMax</varname>, and
|
||||
<varname>MemoryZSwapWriteback</varname> were added in version 256.</para>
|
||||
<para><varname>ManagedOOMMemoryPressureDurationUSec</varname> was added in version 257.</para>
|
||||
<para><function>RemoveSubgroup()</function> was added in version 258.</para>
|
||||
</refsect2>
|
||||
<refsect2>
|
||||
<title>Scope Unit Objects</title>
|
||||
@ -12498,6 +12548,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
|
||||
<varname>EffectiveTasksMax</varname>, and
|
||||
<varname>MemoryZSwapWriteback</varname> were added in version 256.</para>
|
||||
<para><varname>ManagedOOMMemoryPressureDurationUSec</varname> was added in version 257.</para>
|
||||
<para><function>RemoveSubgroup()</function> was added in version 258.</para>
|
||||
</refsect2>
|
||||
<refsect2>
|
||||
<title>Job Objects</title>
|
||||
|
@ -3126,6 +3126,49 @@ int unit_attach_pids_to_cgroup(Unit *u, Set *pids, const char *suffix_path) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
int unit_remove_subcgroup(Unit *u, const char *suffix_path) {
|
||||
int r;
|
||||
|
||||
assert(u);
|
||||
|
||||
if (!UNIT_HAS_CGROUP_CONTEXT(u))
|
||||
return -EINVAL;
|
||||
|
||||
if (!unit_cgroup_delegate(u))
|
||||
return -ENOMEDIUM;
|
||||
|
||||
r = unit_pick_cgroup_path(u);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
CGroupRuntime *crt = unit_get_cgroup_runtime(u);
|
||||
if (!crt || !crt->cgroup_path)
|
||||
return -EOWNERDEAD;
|
||||
|
||||
_cleanup_free_ char *j = NULL;
|
||||
bool delete_root;
|
||||
const char *d;
|
||||
if (empty_or_root(suffix_path)) {
|
||||
d = empty_to_root(crt->cgroup_path);
|
||||
delete_root = false; /* Don't attempt to delete the main cgroup of this unit */
|
||||
} else {
|
||||
j = path_join(crt->cgroup_path, suffix_path);
|
||||
if (!j)
|
||||
return -ENOMEM;
|
||||
|
||||
d = j;
|
||||
delete_root = true;
|
||||
}
|
||||
|
||||
log_unit_debug(u, "Removing subcgroup '%s'...", d);
|
||||
|
||||
r = cg_trim_everywhere(u->manager->cgroup_supported, d, delete_root);
|
||||
if (r < 0)
|
||||
return log_unit_debug_errno(u, r, "Failed to fully %s cgroup '%s': %m", delete_root ? "remove" : "trim", d);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool unit_has_mask_realized(
|
||||
Unit *u,
|
||||
CGroupMask target_mask,
|
||||
@ -3567,6 +3610,51 @@ static bool unit_maybe_release_cgroup(Unit *u) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static int unit_prune_cgroup_via_bus(Unit *u) {
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
int r;
|
||||
|
||||
assert(u);
|
||||
assert(u->manager);
|
||||
|
||||
if (MANAGER_IS_SYSTEM(u->manager))
|
||||
return -EINVAL;
|
||||
|
||||
if (!u->manager->system_bus)
|
||||
return -EIO;
|
||||
|
||||
CGroupRuntime *crt = unit_get_cgroup_runtime(u);
|
||||
if (!crt || !crt->cgroup_path)
|
||||
return -EOWNERDEAD;
|
||||
|
||||
/* Determine this unit's cgroup path relative to our cgroup root */
|
||||
const char *pp = path_startswith(crt->cgroup_path, u->manager->cgroup_root);
|
||||
if (!pp)
|
||||
return -EINVAL;
|
||||
|
||||
_cleanup_free_ char *absolute = NULL;
|
||||
if (!path_is_absolute(pp)) { /* RemoveSubgroupFromUnit() wants an absolute path */
|
||||
absolute = strjoin("/", pp);
|
||||
if (!absolute)
|
||||
return -ENOMEM;
|
||||
|
||||
pp = absolute;
|
||||
}
|
||||
|
||||
r = bus_call_method(u->manager->system_bus,
|
||||
bus_systemd_mgr,
|
||||
"RemoveSubgroupFromUnit",
|
||||
&error, NULL,
|
||||
"sst",
|
||||
NULL /* empty unit name means client's unit, i.e. us */,
|
||||
pp,
|
||||
(uint64_t) 0);
|
||||
if (r < 0)
|
||||
return log_unit_debug_errno(u, r, "Failed to trim cgroup via the bus: %s", bus_error_message(&error, r));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void unit_prune_cgroup(Unit *u) {
|
||||
bool is_root_slice;
|
||||
int r;
|
||||
@ -3598,13 +3686,21 @@ void unit_prune_cgroup(Unit *u) {
|
||||
is_root_slice = unit_has_name(u, SPECIAL_ROOT_SLICE);
|
||||
|
||||
r = cg_trim_everywhere(u->manager->cgroup_supported, crt->cgroup_path, !is_root_slice);
|
||||
if (r < 0)
|
||||
/* One reason we could have failed here is, that the cgroup still contains a process.
|
||||
* However, if the cgroup becomes removable at a later time, it might be removed when
|
||||
* the containing slice is stopped. So even if we failed now, this unit shouldn't assume
|
||||
* that the cgroup is still realized the next time it is started. Do not return early
|
||||
* on error, continue cleanup. */
|
||||
log_unit_full_errno(u, r == -EBUSY ? LOG_DEBUG : LOG_WARNING, r, "Failed to destroy cgroup %s, ignoring: %m", empty_to_root(crt->cgroup_path));
|
||||
if (r < 0) {
|
||||
int k = unit_prune_cgroup_via_bus(u);
|
||||
|
||||
if (k >= 0)
|
||||
log_unit_debug_errno(u, r, "Failed to destroy cgroup %s on our own (%m), but worked when talking to PID 1.", empty_to_root(crt->cgroup_path));
|
||||
else {
|
||||
/* One reason we could have failed here is, that the cgroup still contains a process.
|
||||
* However, if the cgroup becomes removable at a later time, it might be removed when
|
||||
* the containing slice is stopped. So even if we failed now, this unit shouldn't
|
||||
* assume that the cgroup is still realized the next time it is started. Do not
|
||||
* return early on error, continue cleanup. */
|
||||
log_unit_full_errno(u, r == -EBUSY ? LOG_DEBUG : LOG_WARNING, r,
|
||||
"Failed to destroy cgroup %s, ignoring: %m", empty_to_root(crt->cgroup_path));
|
||||
}
|
||||
}
|
||||
|
||||
if (is_root_slice)
|
||||
return;
|
||||
|
@ -456,6 +456,7 @@ int unit_check_oomd_kill(Unit *u);
|
||||
int unit_check_oom(Unit *u);
|
||||
|
||||
int unit_attach_pids_to_cgroup(Unit *u, Set *pids, const char *suffix_path);
|
||||
int unit_remove_subcgroup(Unit *u, const char *suffix_path);
|
||||
|
||||
int manager_setup_cgroup(Manager *m);
|
||||
void manager_shutdown_cgroup(Manager *m, bool delete);
|
||||
|
@ -960,6 +960,12 @@ static int method_attach_processes_to_unit(sd_bus_message *message, void *userda
|
||||
return method_generic_unit_operation(message, userdata, error, bus_unit_method_attach_processes, GENERIC_UNIT_VALIDATE_LOADED);
|
||||
}
|
||||
|
||||
static int method_remove_subgroup_from_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
/* Don't allow removal of subgroups from units that aren't loaded. But allow loading the unit, since
|
||||
* this is clean-up work, that is OK to do when the unit is stopped already. */
|
||||
return method_generic_unit_operation(message, userdata, error, bus_unit_method_remove_subgroup, GENERIC_UNIT_LOAD|GENERIC_UNIT_VALIDATE_LOADED);
|
||||
}
|
||||
|
||||
static int transient_unit_from_message(
|
||||
Manager *m,
|
||||
sd_bus_message *message,
|
||||
@ -3246,6 +3252,11 @@ const sd_bus_vtable bus_manager_vtable[] = {
|
||||
SD_BUS_NO_RESULT,
|
||||
method_attach_processes_to_unit,
|
||||
SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD_WITH_ARGS("RemoveSubgroupFromUnit",
|
||||
SD_BUS_ARGS("s", unit_name, "s", subcgroup, "t", flags),
|
||||
SD_BUS_NO_RESULT,
|
||||
method_remove_subgroup_from_unit,
|
||||
SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD_WITH_ARGS("AbandonScope",
|
||||
SD_BUS_ARGS("s", name),
|
||||
SD_BUS_NO_RESULT,
|
||||
|
@ -1525,7 +1525,7 @@ int bus_unit_method_attach_processes(sd_bus_message *message, void *userdata, sd
|
||||
return r;
|
||||
for (;;) {
|
||||
_cleanup_(pidref_freep) PidRef *pidref = NULL;
|
||||
uid_t process_uid, sender_uid;
|
||||
uid_t sender_uid;
|
||||
uint32_t upid;
|
||||
|
||||
r = sd_bus_message_read(message, "u", &upid);
|
||||
@ -1560,16 +1560,21 @@ int bus_unit_method_attach_processes(sd_bus_message *message, void *userdata, sd
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Let's validate security: if the sender is root, then all is OK. If the sender is any other unit,
|
||||
* then the process' UID and the target unit's UID have to match the sender's UID */
|
||||
/* Let's validate security: if the sender is root or the owner of the service manager, then
|
||||
* all is OK. If the sender is any other user, then the process in question must be owned by
|
||||
* both the sender and the target unit's UID. Note that ownership here means either direct
|
||||
* ownership, or indirect via a userns that is owned by the right UID. */
|
||||
if (sender_uid != 0 && sender_uid != getuid()) {
|
||||
r = pidref_get_uid(pidref, &process_uid);
|
||||
r = process_is_owned_by_uid(pidref, sender_uid);
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to retrieve process UID: %m");
|
||||
|
||||
if (process_uid != sender_uid)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to check if process " PID_FMT " is owned by client's UID: %m", pidref->pid);
|
||||
if (r == 0)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Process " PID_FMT " not owned by client's UID. Refusing.", pidref->pid);
|
||||
if (process_uid != u->ref_uid)
|
||||
|
||||
r = process_is_owned_by_uid(pidref, u->ref_uid);
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to check if process " PID_FMT " is owned by target unit's UID: %m", pidref->pid);
|
||||
if (r == 0)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Process " PID_FMT " not owned by target unit's UID. Refusing.", pidref->pid);
|
||||
}
|
||||
|
||||
@ -1589,6 +1594,59 @@ int bus_unit_method_attach_processes(sd_bus_message *message, void *userdata, sd
|
||||
return sd_bus_reply_method_return(message, NULL);
|
||||
}
|
||||
|
||||
int bus_unit_method_remove_subgroup(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
Unit *u = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
|
||||
/* This removes a subcgroup of the unit, regardless which user owns the subcgroup. This is useful
|
||||
* when cgroup delegation is enabled for a unit, and the unit subdelegates the cgroup further */
|
||||
|
||||
r = mac_selinux_unit_access_check(u, message, "stop", error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
const char *path;
|
||||
uint64_t flags;
|
||||
r = sd_bus_message_read(message, "st", &path, &flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* No flags defined for now. */
|
||||
if (flags != 0)
|
||||
return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, "Invalid 'flags' parameter '%" PRIu64 "'", flags);
|
||||
|
||||
if (!unit_cgroup_delegate(u))
|
||||
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Subcgroup removal not available on non-delegated units.");
|
||||
|
||||
if (!path_is_absolute(path))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Control group path is not absolute: %s", path);
|
||||
|
||||
if (!path_is_normalized(path))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Control group path is not normalized: %s", path);
|
||||
|
||||
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
|
||||
r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
uid_t sender_uid;
|
||||
r = sd_bus_creds_get_euid(creds, &sender_uid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Allow this only if the client is privileged, is us, or is the user of the unit itself. */
|
||||
if (sender_uid != 0 && sender_uid != getuid() && sender_uid != u->ref_uid)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Client is not permitted to alter cgroup.");
|
||||
|
||||
r = unit_remove_subcgroup(u, path);
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to remove subgroup %s: %m", path);
|
||||
|
||||
return sd_bus_reply_method_return(message, NULL);
|
||||
}
|
||||
|
||||
const sd_bus_vtable bus_unit_cgroup_vtable[] = {
|
||||
SD_BUS_VTABLE_START(0),
|
||||
SD_BUS_PROPERTY("Slice", "s", property_get_slice, 0, 0),
|
||||
@ -1628,6 +1686,12 @@ const sd_bus_vtable bus_unit_cgroup_vtable[] = {
|
||||
bus_unit_method_attach_processes,
|
||||
SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
|
||||
SD_BUS_METHOD_WITH_ARGS("RemoveSubgroup",
|
||||
SD_BUS_ARGS("s", subcgroup, "t", flags),
|
||||
SD_BUS_NO_RESULT,
|
||||
bus_unit_method_remove_subgroup,
|
||||
SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
|
||||
SD_BUS_VTABLE_END
|
||||
};
|
||||
|
||||
|
@ -22,6 +22,7 @@ int bus_unit_set_properties(Unit *u, sd_bus_message *message, UnitWriteFlags fla
|
||||
int bus_unit_method_set_properties(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
int bus_unit_method_get_processes(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
int bus_unit_method_attach_processes(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
int bus_unit_method_remove_subgroup(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
int bus_unit_method_ref(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
int bus_unit_method_unref(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
int bus_unit_method_clean(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
|
@ -274,6 +274,10 @@
|
||||
send_interface="org.freedesktop.systemd1.Manager"
|
||||
send_member="AttachProcessesToUnit"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.systemd1"
|
||||
send_interface="org.freedesktop.systemd1.Manager"
|
||||
send_member="RemoveSubgroupFromUnit"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.systemd1"
|
||||
send_interface="org.freedesktop.systemd1.Manager"
|
||||
send_member="CancelJob"/>
|
||||
@ -432,6 +436,10 @@
|
||||
send_interface="org.freedesktop.systemd1.Service"
|
||||
send_member="AttachProcesses"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.systemd1"
|
||||
send_interface="org.freedesktop.systemd1.Service"
|
||||
send_member="RemoveSubgroupFromUnit"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.systemd1"
|
||||
send_interface="org.freedesktop.systemd1.Service"
|
||||
send_member="BindMount"/>
|
||||
@ -446,6 +454,10 @@
|
||||
send_interface="org.freedesktop.systemd1.Scope"
|
||||
send_member="AttachProcesses"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.systemd1"
|
||||
send_interface="org.freedesktop.systemd1.Service"
|
||||
send_member="RemoveSubgroupFromUnit"/>
|
||||
|
||||
<allow receive_sender="org.freedesktop.systemd1"/>
|
||||
</policy>
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# shellcheck disable=SC2235
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
@ -87,6 +88,39 @@ testcase_scope_unpriv_delegation() {
|
||||
-w /sys/fs/cgroup/workload.slice/test-workload0.scope/cgroup.subtree_control
|
||||
}
|
||||
|
||||
testcase_user_unpriv_delegation() {
|
||||
# Check that delegation works for unpriv users, and that we can insert a
|
||||
# subcgroup owned by a different user (which can happen in case unpriv
|
||||
# userns where a UID range was delegated), which is still cleaned up
|
||||
# correctly when it goes down.
|
||||
|
||||
run0 -u testuser systemd-run --user \
|
||||
--property="Delegate=yes" \
|
||||
--unit=test-chown-subcgroup \
|
||||
--service-type=exec \
|
||||
sleep infinity
|
||||
|
||||
TESTUID=$(id -u testuser)
|
||||
CGROUP="/sys/fs/cgroup/user.slice/user-$TESTUID.slice/user@$TESTUID.service/app.slice/test-chown-subcgroup.service"
|
||||
test -d "$CGROUP"
|
||||
|
||||
# Create a subcgroup, and make it owned by some unrelated user
|
||||
SUBCGROUP="$CGROUP/subcgroup"
|
||||
mkdir "$SUBCGROUP"
|
||||
chown 1:1 "$SUBCGROUP"
|
||||
|
||||
# Make sure the subcgroup is not empty (empty dirs owned by other users can
|
||||
# be removed if one owns the dir they are contained in, after all)
|
||||
mkdir "$SUBCGROUP"/filler
|
||||
|
||||
run0 -u testuser systemctl stop --user test-chown-subcgroup.service
|
||||
|
||||
# Verify that the subcgroup got correctly removed
|
||||
(! test -e "$CGROUP")
|
||||
|
||||
systemctl stop user@testuser.service
|
||||
}
|
||||
|
||||
testcase_subgroup() {
|
||||
# Verify that DelegateSubgroup= affects ownership correctly
|
||||
unit="test-subgroup-$RANDOM.service"
|
||||
|
Loading…
x
Reference in New Issue
Block a user