]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: add RemoveIPC= setting
authorLennart Poettering <lennart@poettering.net>
Mon, 1 Aug 2016 17:24:40 +0000 (19:24 +0200)
committerLennart Poettering <lennart@poettering.net>
Thu, 18 Aug 2016 22:37:25 +0000 (00:37 +0200)
This adds the boolean RemoveIPC= setting to service, socket, mount and swap
units (i.e.  all unit types that may invoke processes). if turned on, and the
unit's user/group is not root, all IPC objects of the user/group are removed
when the service is shut down. The life-cycle of the IPC objects is hence bound
to the unit life-cycle.

This is particularly relevant for units with dynamic users, as it is essential
that no objects owned by the dynamic users survive the service exiting. In
fact, this patch adds code to imply RemoveIPC= if DynamicUser= is set.

In order to communicate the UID/GID of an executed process back to PID 1 this
adds a new "user lookup" socket pair, that is inherited into the forked
processes, and closed before the exec(). This is needed since we cannot do NSS
from PID 1 due to deadlock risks, However need to know the used UID/GID in
order to clean up IPC owned by it if the unit shuts down.

21 files changed:
man/systemd.exec.xml
src/core/dbus-execute.c
src/core/dbus-mount.c
src/core/dbus-service.c
src/core/dbus-socket.c
src/core/dbus-swap.c
src/core/execute.c
src/core/execute.h
src/core/manager.c
src/core/manager.h
src/core/mount.c
src/core/service.c
src/core/socket.c
src/core/swap.c
src/core/unit.c
src/core/unit.h
src/login/logind-user.c
src/shared/bus-unit-util.c
src/shared/clean-ipc.c
src/shared/clean-ipc.h
src/test/test-ipcrm.c

index bf823260961fec27e30a8a42852c040f767709f1..bcedebd5bb5e304bbb4e806939e6444b1e3f250b 100644 (file)
         use. However, UID/GIDs are recycled after a unit is terminated. Care should be taken that any processes running
         as part of a unit for which dynamic users/groups are enabled do not leave files or directories owned by these
         users/groups around, as a different unit might get the same UID/GID assigned later on, and thus gain access to
-        these files or directories. If <varname>DynamicUser=</varname> is enabled, <varname>PrivateTmp=</varname> is
-        implied. This ensures that the lifetime of temporary files created by the executed processes is bound to the
-        runtime of the service, and hence the lifetime of the dynamic user/group. Since <filename>/tmp</filename> and
-        <filename>/var/tmp</filename> are usually the only world-writable directories on a system this ensures that a
-        unit making use of dynamic user/group allocation cannot leave files around after unit termination. Use
-        <varname>RuntimeDirectory=</varname> (see below) in order to assign a writable runtime directory to a service,
-        owned by the dynamic user/group and removed automatically when the unit is terminated. Defaults to
-        off.</para></listitem>
+        these files or directories. If <varname>DynamicUser=</varname> is enabled, <varname>RemoveIPC=</varname> and
+        <varname>PrivateTmp=</varname> are implied. This ensures that the lifetime of IPC objects and temporary files
+        created by the executed processes is bound to the runtime of the service, and hence the lifetime of the dynamic
+        user/group. Since <filename>/tmp</filename> and <filename>/var/tmp</filename> are usually the only
+        world-writable directories on a system this ensures that a unit making use of dynamic user/group allocation
+        cannot leave files around after unit termination. Use <varname>RuntimeDirectory=</varname> (see below) in order
+        to assign a writable runtime directory to a service, owned by the dynamic user/group and removed automatically
+        when the unit is terminated. Defaults to off.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         user. This does not affect commands prefixed with <literal>+</literal>.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>RemoveIPC=</varname></term>
+
+        <listitem><para>Takes a boolean parameter. If set, all System V and POSIX IPC objects owned by the user and
+        group the processes of this unit are run as are removed when the unit is stopped. This setting only has an
+        effect if at least one of <varname>User=</varname>, <varname>Group=</varname> and
+        <varname>DynamicUser=</varname> are used. It has no effect on IPC objects owned by the root user. Specifically,
+        this removes System V semaphores, as well as System V and POSIX shared memory segments and message queues. If
+        multiple units use the same user or group the IPC objects are removed when the last of these units is
+        stopped. This setting is implied if <varname>DynamicUser=</varname> is set.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>Nice=</varname></term>
 
       <varlistentry>
         <term><varname>PrivateTmp=</varname></term>
 
-        <listitem><para>Takes a boolean argument. If true, sets up a
-        new file system namespace for the executed processes and
-        mounts private <filename>/tmp</filename> and
-        <filename>/var/tmp</filename> directories inside it that is
-        not shared by processes outside of the namespace. This is
-        useful to secure access to temporary files of the process, but
-        makes sharing between processes via <filename>/tmp</filename>
-        or <filename>/var/tmp</filename> impossible. If this is
-        enabled, all temporary files created by a service in these
-        directories will be removed after the service is stopped.
-        Defaults to false. It is possible to run two or more units
-        within the same private <filename>/tmp</filename> and
-        <filename>/var/tmp</filename> namespace by using the
+        <listitem><para>Takes a boolean argument. If true, sets up a new file system namespace for the executed
+        processes and mounts private <filename>/tmp</filename> and <filename>/var/tmp</filename> directories inside it
+        that is not shared by processes outside of the namespace. This is useful to secure access to temporary files of
+        the process, but makes sharing between processes via <filename>/tmp</filename> or <filename>/var/tmp</filename>
+        impossible. If this is enabled, all temporary files created by a service in these directories will be removed
+        after the service is stopped.  Defaults to false. It is possible to run two or more units within the same
+        private <filename>/tmp</filename> and <filename>/var/tmp</filename> namespace by using the
         <varname>JoinsNamespaceOf=</varname> directive, see
-        <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
-        for details. Note that using this setting will disconnect
-        propagation of mounts from the service to the host
-        (propagation in the opposite direction continues to work).
-        This means that this setting may not be used for services
-        which shall be able to install mount points in the main mount
-        namespace.</para></listitem>
+        <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
+        details. Note that using this setting will disconnect propagation of mounts from the service to the host
+        (propagation in the opposite direction continues to work).  This means that this setting may not be used for
+        services which shall be able to install mount points in the main mount namespace. This setting is implied if
+        <varname>DynamicUser=</varname> is set.</para></listitem>
       </varlistentry>
 
       <varlistentry>
index e35d3ccd2ec1acda726a61f774572da750557d74..7e33a2d20189a1d7b725d6231bfb61f9a2d851bd 100644 (file)
@@ -695,6 +695,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
         SD_BUS_PROPERTY("User", "s", NULL, offsetof(ExecContext, user), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("Group", "s", NULL, offsetof(ExecContext, group), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("DynamicUser", "b", bus_property_get_bool, offsetof(ExecContext, dynamic_user), SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("RemoveIPC", "b", bus_property_get_bool, offsetof(ExecContext, remove_ipc), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("SupplementaryGroups", "as", NULL, offsetof(ExecContext, supplementary_groups), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("PAMName", "s", NULL, offsetof(ExecContext, pam_name), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("ReadWriteDirectories", "as", NULL, offsetof(ExecContext, read_write_paths), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
@@ -1071,7 +1072,7 @@ int bus_exec_context_set_transient_property(
                               "IgnoreSIGPIPE", "TTYVHangup", "TTYReset",
                               "PrivateTmp", "PrivateDevices", "PrivateNetwork", "PrivateUsers",
                               "NoNewPrivileges", "SyslogLevelPrefix", "MemoryDenyWriteExecute",
-                              "RestrictRealtime", "DynamicUser")) {
+                              "RestrictRealtime", "DynamicUser", "RemoveIPC")) {
                 int b;
 
                 r = sd_bus_message_read(message, "b", &b);
@@ -1103,6 +1104,8 @@ int bus_exec_context_set_transient_property(
                                 c->restrict_realtime = b;
                         else if (streq(name, "DynamicUser"))
                                 c->dynamic_user = b;
+                        else if (streq(name, "RemoveIPC"))
+                                c->remove_ipc = b;
 
                         unit_write_drop_in_private_format(u, mode, name, "%s=%s", name, yes_no(b));
                 }
index b4bbee06482039198def7962379c039119c118e0..3c6bda40739a500757c01e1f2f00e87eaf85081f 100644 (file)
@@ -117,6 +117,8 @@ const sd_bus_vtable bus_mount_vtable[] = {
         SD_BUS_PROPERTY("DirectoryMode", "u", bus_property_get_mode, offsetof(Mount, directory_mode), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("SloppyOptions", "b", bus_property_get_bool, offsetof(Mount, sloppy_options), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Mount, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+        SD_BUS_PROPERTY("UID", "u", NULL, offsetof(Unit, ref_uid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+        SD_BUS_PROPERTY("GID", "u", NULL, offsetof(Unit, ref_gid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
         BUS_EXEC_COMMAND_VTABLE("ExecMount", offsetof(Mount, exec_command[MOUNT_EXEC_MOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
         BUS_EXEC_COMMAND_VTABLE("ExecUnmount", offsetof(Mount, exec_command[MOUNT_EXEC_UNMOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
         BUS_EXEC_COMMAND_VTABLE("ExecRemount", offsetof(Mount, exec_command[MOUNT_EXEC_REMOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
index 5eac876a6e6a1f71ab10e6455caf6c13e86f0650..3c55e0f7fe3e11f7d05937a1505978a8b02705e3 100644 (file)
@@ -65,6 +65,9 @@ const sd_bus_vtable bus_service_vtable[] = {
         SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Service, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
         SD_BUS_PROPERTY("USBFunctionDescriptors", "s", NULL, offsetof(Service, usb_function_descriptors), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
         SD_BUS_PROPERTY("USBFunctionStrings", "s", NULL, offsetof(Service, usb_function_strings), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+        SD_BUS_PROPERTY("UID", "u", NULL, offsetof(Unit, ref_uid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+        SD_BUS_PROPERTY("GID", "u", NULL, offsetof(Unit, ref_gid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+
         BUS_EXEC_STATUS_VTABLE("ExecMain", offsetof(Service, main_exec_status), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
         BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPre", offsetof(Service, exec_command[SERVICE_EXEC_START_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
         BUS_EXEC_COMMAND_LIST_VTABLE("ExecStart", offsetof(Service, exec_command[SERVICE_EXEC_START]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
index 9a071a1355304783177ee646d51d9502dfdb3836..21adb64e1500b11a93c0107ebf6c448702851805 100644 (file)
@@ -152,6 +152,8 @@ const sd_bus_vtable bus_socket_vtable[] = {
         SD_BUS_PROPERTY("SocketProtocol", "i", bus_property_get_int, offsetof(Socket, socket_protocol), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("TriggerLimitIntervalUSec", "t", bus_property_get_usec, offsetof(Socket, trigger_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("TriggerLimitBurst", "u", bus_property_get_unsigned, offsetof(Socket, trigger_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("UID", "u", NULL, offsetof(Unit, ref_uid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+        SD_BUS_PROPERTY("GID", "u", NULL, offsetof(Unit, ref_gid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
         BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPre", offsetof(Socket, exec_command[SOCKET_EXEC_START_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
         BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPost", offsetof(Socket, exec_command[SOCKET_EXEC_START_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
         BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPre", offsetof(Socket, exec_command[SOCKET_EXEC_STOP_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
index 292f8738c684a828c48d5556dc8ec60b7f2a8f15..85a2c26b98d640d3e7f2f4b07b8c909e55c3159e 100644 (file)
@@ -84,6 +84,8 @@ const sd_bus_vtable bus_swap_vtable[] = {
         SD_BUS_PROPERTY("TimeoutUSec", "t", bus_property_get_usec, offsetof(Swap, timeout_usec), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(Swap, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
         SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Swap, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+        SD_BUS_PROPERTY("UID", "u", NULL, offsetof(Unit, ref_uid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+        SD_BUS_PROPERTY("GID", "u", NULL, offsetof(Unit, ref_gid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
         BUS_EXEC_COMMAND_VTABLE("ExecActivate", offsetof(Swap, exec_command[SWAP_EXEC_ACTIVATE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
         BUS_EXEC_COMMAND_VTABLE("ExecDeactivate", offsetof(Swap, exec_command[SWAP_EXEC_DEACTIVATE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
         SD_BUS_VTABLE_END
index 18bb421cab1901a26a5c3d0e72f1a7f91ec2bc9c..4c786a2e33589073ea3b4887f395926a597f0e82 100644 (file)
@@ -1723,11 +1723,12 @@ static int close_remaining_fds(
                 const ExecParameters *params,
                 ExecRuntime *runtime,
                 DynamicCreds *dcreds,
+                int user_lookup_fd,
                 int socket_fd,
                 int *fds, unsigned n_fds) {
 
         unsigned n_dont_close = 0;
-        int dont_close[n_fds + 11];
+        int dont_close[n_fds + 12];
 
         assert(params);
 
@@ -1755,9 +1756,40 @@ static int close_remaining_fds(
                         append_socket_pair(dont_close, &n_dont_close, dcreds->group->storage_socket);
         }
 
+        if (user_lookup_fd >= 0)
+                dont_close[n_dont_close++] = user_lookup_fd;
+
         return close_all_fds(dont_close, n_dont_close);
 }
 
+static int send_user_lookup(
+                Unit *unit,
+                int user_lookup_fd,
+                uid_t uid,
+                gid_t gid) {
+
+        assert(unit);
+
+        /* Send the resolved UID/GID to PID 1 after we learnt it. We send a single datagram, containing the UID/GID
+         * data as well as the unit name. Note that we suppress sending this if no user/group to resolve was
+         * specified. */
+
+        if (user_lookup_fd < 0)
+                return 0;
+
+        if (!uid_is_valid(uid) && !gid_is_valid(gid))
+                return 0;
+
+        if (writev(user_lookup_fd,
+               (struct iovec[]) {
+                           { .iov_base = &uid, .iov_len = sizeof(uid) },
+                           { .iov_base = &gid, .iov_len = sizeof(gid) },
+                           { .iov_base = unit->id, .iov_len = strlen(unit->id) }}, 3) < 0)
+                return -errno;
+
+        return 0;
+}
+
 static int exec_child(
                 Unit *unit,
                 ExecCommand *command,
@@ -1769,6 +1801,7 @@ static int exec_child(
                 int socket_fd,
                 int *fds, unsigned n_fds,
                 char **files_env,
+                int user_lookup_fd,
                 int *exit_status) {
 
         _cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **accum_env = NULL, **final_argv = NULL;
@@ -1815,7 +1848,7 @@ static int exec_child(
 
         log_forget_fds();
 
-        r = close_remaining_fds(params, runtime, dcreds, socket_fd, fds, n_fds);
+        r = close_remaining_fds(params, runtime, dcreds, user_lookup_fd, socket_fd, fds, n_fds);
         if (r < 0) {
                 *exit_status = EXIT_FDS;
                 return r;
@@ -1902,6 +1935,14 @@ static int exec_child(
                 }
         }
 
+        r = send_user_lookup(unit, user_lookup_fd, uid, gid);
+        if (r < 0) {
+                *exit_status = EXIT_USER;
+                return r;
+        }
+
+        user_lookup_fd = safe_close(user_lookup_fd);
+
         /* If a socket is connected to STDIN/STDOUT/STDERR, we
          * must sure to drop O_NONBLOCK */
         if (socket_fd >= 0)
@@ -2501,6 +2542,7 @@ int exec_spawn(Unit *unit,
                                socket_fd,
                                fds, n_fds,
                                files_env,
+                               unit->manager->user_lookup_fds[1],
                                &exit_status);
                 if (r < 0) {
                         log_open();
index 106154f81a4b15973d15882a2ebfd66d8b5d8208..6082c42aba2c6af3df55ad418d9a5bb5b5b2c638 100644 (file)
@@ -178,6 +178,7 @@ struct ExecContext {
         bool no_new_privileges;
 
         bool dynamic_user;
+        bool remove_ipc;
 
         /* This is not exposed to the user but available
          * internally. We need it to make sure that whenever we spawn
index c20e185d78769ff6f55c0698cd7b3a8ebbd30515..6f2477eef49f8c184b3eace4babda3f98c349af5 100644 (file)
@@ -45,6 +45,7 @@
 #include "bus-error.h"
 #include "bus-kernel.h"
 #include "bus-util.h"
+#include "clean-ipc.h"
 #include "dbus-job.h"
 #include "dbus-manager.h"
 #include "dbus-unit.h"
@@ -81,6 +82,7 @@
 #include "transaction.h"
 #include "umask-util.h"
 #include "unit-name.h"
+#include "user-util.h"
 #include "util.h"
 #include "virt.h"
 #include "watchdog.h"
@@ -98,6 +100,7 @@ static int manager_dispatch_cgroups_agent_fd(sd_event_source *source, int fd, ui
 static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata);
 static int manager_dispatch_time_change_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata);
 static int manager_dispatch_idle_pipe_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata);
+static int manager_dispatch_user_lookup_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata);
 static int manager_dispatch_jobs_in_progress(sd_event_source *source, usec_t usec, void *userdata);
 static int manager_dispatch_run_queue(sd_event_source *source, void *userdata);
 static int manager_run_generators(Manager *m);
@@ -590,6 +593,8 @@ int manager_new(UnitFileScope scope, bool test_run, Manager **_m) {
                 m->dev_autofs_fd = m->private_listen_fd = m->kdbus_fd = m->cgroup_inotify_fd =
                 m->ask_password_inotify_fd = -1;
 
+        m->user_lookup_fds[0] = m->user_lookup_fds[1] = -1;
+
         m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */
 
         m->have_ask_password = -EINVAL; /* we don't know */
@@ -812,6 +817,59 @@ static int manager_setup_cgroups_agent(Manager *m) {
         return 0;
 }
 
+static int manager_setup_user_lookup_fd(Manager *m) {
+        int r;
+
+        assert(m);
+
+        /* Set up the socket pair used for passing UID/GID resolution results from forked off processes to PID
+         * 1. Background: we can't do name lookups (NSS) from PID 1, since it might involve IPC and thus activation,
+         * and we might hence deadlock on ourselves. Hence we do all user/group lookups asynchronously from the forked
+         * off processes right before executing the binaries to start. In order to be able to clean up any IPC objects
+         * created by a unit (see RemoveIPC=) we need to know in PID 1 the used UID/GID of the executed processes,
+         * hence we establish this communication channel so that forked off processes can pass their UID/GID
+         * information back to PID 1. The forked off processes send their resolved UID/GID to PID 1 in a simple
+         * datagram, along with their unit name, so that we can share one communication socket pair among all units for
+         * this purpose.
+         *
+         * You might wonder why we need a communication channel for this that is independent of the usual notification
+         * socket scheme (i.e. $NOTIFY_SOCKET). The primary difference is about trust: data sent via the $NOTIFY_SOCKET
+         * channel is only accepted if it originates from the right unit and if reception was enabled for it. The user
+         * lookup socket OTOH is only accessible by PID 1 and its children until they exec(), and always available.
+         *
+         * Note that this function is called under two circumstances: when we first initialize (in which case we
+         * allocate both the socket pair and the event source to listen on it), and when we deserialize after a reload
+         * (in which case the socket pair already exists but we still need to allocate the event source for it). */
+
+        if (m->user_lookup_fds[0] < 0) {
+
+                /* Free all secondary fields */
+                safe_close_pair(m->user_lookup_fds);
+                m->user_lookup_event_source = sd_event_source_unref(m->user_lookup_event_source);
+
+                if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, m->user_lookup_fds) < 0)
+                        return log_error_errno(errno, "Failed to allocate user lookup socket: %m");
+
+                (void) fd_inc_rcvbuf(m->user_lookup_fds[0], NOTIFY_RCVBUF_SIZE);
+        }
+
+        if (!m->user_lookup_event_source) {
+                r = sd_event_add_io(m->event, &m->user_lookup_event_source, m->user_lookup_fds[0], EPOLLIN, manager_dispatch_user_lookup_fd, m);
+                if (r < 0)
+                        return log_error_errno(errno, "Failed to allocate user lookup event source: %m");
+
+                /* Process even earlier than the notify event source, so that we always know first about valid UID/GID
+                 * resolutions */
+                r = sd_event_source_set_priority(m->user_lookup_event_source, SD_EVENT_PRIORITY_NORMAL-8);
+                if (r < 0)
+                        return log_error_errno(errno, "Failed to set priority ot user lookup event source: %m");
+
+                (void) sd_event_source_set_description(m->user_lookup_event_source, "user-lookup");
+        }
+
+        return 0;
+}
+
 static int manager_connect_bus(Manager *m, bool reexecuting) {
         bool try_bus_connect;
 
@@ -853,8 +911,7 @@ enum {
         _GC_OFFSET_MAX
 };
 
-static void unit_gc_mark_good(Unit *u, unsigned gc_marker)
-{
+static void unit_gc_mark_good(Unit *u, unsigned gc_marker) {
         Iterator i;
         Unit *other;
 
@@ -1021,12 +1078,14 @@ Manager* manager_free(Manager *m) {
         sd_event_source_unref(m->time_change_event_source);
         sd_event_source_unref(m->jobs_in_progress_event_source);
         sd_event_source_unref(m->run_queue_event_source);
+        sd_event_source_unref(m->user_lookup_event_source);
 
         safe_close(m->signal_fd);
         safe_close(m->notify_fd);
         safe_close(m->cgroups_agent_fd);
         safe_close(m->time_change_fd);
         safe_close(m->kdbus_fd);
+        safe_close_pair(m->user_lookup_fds);
 
         manager_close_ask_password(m);
 
@@ -1052,6 +1111,9 @@ Manager* manager_free(Manager *m) {
         assert(hashmap_isempty(m->units_requiring_mounts_for));
         hashmap_free(m->units_requiring_mounts_for);
 
+        hashmap_free(m->uid_refs);
+        hashmap_free(m->gid_refs);
+
         free(m);
         return NULL;
 }
@@ -1221,6 +1283,10 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) {
         if (q < 0 && r == 0)
                 r = q;
 
+        q = manager_setup_user_lookup_fd(m);
+        if (q < 0 && r == 0)
+                r = q;
+
         /* We might have deserialized the kdbus control fd, but if we
          * didn't, then let's create the bus now. */
         manager_connect_bus(m, !!serialization);
@@ -1232,6 +1298,10 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) {
         /* Release any dynamic users no longer referenced */
         dynamic_user_vacuum(m, true);
 
+        /* Release any references to UIDs/GIDs no longer referenced, and destroy any IPC owned by them */
+        manager_vacuum_uid_refs(m);
+        manager_vacuum_gid_refs(m);
+
         if (serialization) {
                 assert(m->n_reloading > 0);
                 m->n_reloading--;
@@ -2396,6 +2466,20 @@ int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root) {
                 fprintf(f, "cgroups-agent-fd=%i\n", copy);
         }
 
+        if (m->user_lookup_fds[0] >= 0) {
+                int copy0, copy1;
+
+                copy0 = fdset_put_dup(fds, m->user_lookup_fds[0]);
+                if (copy0 < 0)
+                        return copy0;
+
+                copy1 = fdset_put_dup(fds, m->user_lookup_fds[1]);
+                if (copy1 < 0)
+                        return copy1;
+
+                fprintf(f, "user-lookup=%i %i\n", copy0, copy1);
+        }
+
         if (m->kdbus_fd >= 0) {
                 int copy;
 
@@ -2412,6 +2496,9 @@ int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root) {
         if (r < 0)
                 return r;
 
+        manager_serialize_uid_refs(m, f);
+        manager_serialize_gid_refs(m, f);
+
         fputc('\n', f);
 
         HASHMAP_FOREACH_KEY(u, t, m->units, i) {
@@ -2578,6 +2665,18 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
                                 m->cgroups_agent_fd = fdset_remove(fds, fd);
                         }
 
+                } else if (startswith(l, "user-lookup=")) {
+                        int fd0, fd1;
+
+                        if (sscanf(l + 12, "%i %i", &fd0, &fd1) != 2 || fd0 < 0 || fd1 < 0 || fd0 == fd1 || !fdset_contains(fds, fd0) || !fdset_contains(fds, fd1))
+                                log_debug("Failed to parse user lookup fd: %s", l + 12);
+                        else {
+                                m->user_lookup_event_source = sd_event_source_unref(m->user_lookup_event_source);
+                                safe_close_pair(m->user_lookup_fds);
+                                m->user_lookup_fds[0] = fdset_remove(fds, fd0);
+                                m->user_lookup_fds[1] = fdset_remove(fds, fd1);
+                        }
+
                 } else if (startswith(l, "kdbus-fd=")) {
                         int fd;
 
@@ -2590,6 +2689,10 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
 
                 } else if (startswith(l, "dynamic-user="))
                         dynamic_user_deserialize_one(m, l + 13, fds);
+                else if (startswith(l, "destroy-ipc-uid="))
+                        manager_deserialize_uid_refs_one(m, l + 16);
+                else if (startswith(l, "destroy-ipc-gid="))
+                        manager_deserialize_gid_refs_one(m, l + 16);
                 else {
                         int k;
 
@@ -2672,6 +2775,8 @@ int manager_reload(Manager *m) {
         lookup_paths_flush_generator(&m->lookup_paths);
         lookup_paths_free(&m->lookup_paths);
         dynamic_user_vacuum(m, false);
+        m->uid_refs = hashmap_free(m->uid_refs);
+        m->gid_refs = hashmap_free(m->gid_refs);
 
         q = lookup_paths_init(&m->lookup_paths, m->unit_file_scope, 0, NULL);
         if (q < 0 && r >= 0)
@@ -2705,12 +2810,20 @@ int manager_reload(Manager *m) {
         if (q < 0 && r >= 0)
                 r = q;
 
+        q = manager_setup_user_lookup_fd(m);
+        if (q < 0 && r >= 0)
+                r = q;
+
         /* Third, fire things up! */
         manager_coldplug(m);
 
         /* Release any dynamic users no longer referenced */
         dynamic_user_vacuum(m, true);
 
+        /* Release any references to UIDs/GIDs no longer referenced, and destroy any IPC owned by them */
+        manager_vacuum_uid_refs(m);
+        manager_vacuum_gid_refs(m);
+
         /* Sync current state of bus names with our set of listening units */
         if (m->api_bus)
                 manager_sync_bus_names(m, m->api_bus);
@@ -3144,6 +3257,300 @@ ManagerState manager_state(Manager *m) {
         return MANAGER_RUNNING;
 }
 
+#define DESTROY_IPC_FLAG (UINT32_C(1) << 31)
+
+static void manager_unref_uid_internal(
+                Manager *m,
+                Hashmap **uid_refs,
+                uid_t uid,
+                bool destroy_now,
+                int (*_clean_ipc)(uid_t uid)) {
+
+        uint32_t c, n;
+
+        assert(m);
+        assert(uid_refs);
+        assert(uid_is_valid(uid));
+        assert(_clean_ipc);
+
+        /* A generic implementation, covering both manager_unref_uid() and manager_unref_gid(), under the assumption
+         * that uid_t and gid_t are actually defined the same way, with the same validity rules.
+         *
+         * We store a hashmap where the UID/GID is they key and the value is a 32bit reference counter, whose highest
+         * bit is used as flag for marking UIDs/GIDs whose IPC objects to remove when the last reference to the UID/GID
+         * is dropped. The flag is set to on, once at least one reference from a unit where RemoveIPC= is set is added
+         * on a UID/GID. It is reset when the UID's/GID's reference counter drops to 0 again. */
+
+        assert_cc(sizeof(uid_t) == sizeof(gid_t));
+        assert_cc(UID_INVALID == (uid_t) GID_INVALID);
+
+        if (uid == 0) /* We don't keep track of root, and will never destroy it */
+                return;
+
+        c = PTR_TO_UINT32(hashmap_get(*uid_refs, UID_TO_PTR(uid)));
+
+        n = c & ~DESTROY_IPC_FLAG;
+        assert(n > 0);
+        n--;
+
+        if (destroy_now && n == 0) {
+                hashmap_remove(*uid_refs, UID_TO_PTR(uid));
+
+                if (c & DESTROY_IPC_FLAG) {
+                        log_debug("%s " UID_FMT " is no longer referenced, cleaning up its IPC.",
+                                  _clean_ipc == clean_ipc_by_uid ? "UID" : "GID",
+                                  uid);
+                        (void) _clean_ipc(uid);
+                }
+        } else {
+                c = n | (c & DESTROY_IPC_FLAG);
+                assert_se(hashmap_update(*uid_refs, UID_TO_PTR(uid), UINT32_TO_PTR(c)) >= 0);
+        }
+}
+
+void manager_unref_uid(Manager *m, uid_t uid, bool destroy_now) {
+        manager_unref_uid_internal(m, &m->uid_refs, uid, destroy_now, clean_ipc_by_uid);
+}
+
+void manager_unref_gid(Manager *m, gid_t gid, bool destroy_now) {
+        manager_unref_uid_internal(m, &m->gid_refs, (uid_t) gid, destroy_now, clean_ipc_by_gid);
+}
+
+static int manager_ref_uid_internal(
+                Manager *m,
+                Hashmap **uid_refs,
+                uid_t uid,
+                bool clean_ipc) {
+
+        uint32_t c, n;
+        int r;
+
+        assert(m);
+        assert(uid_refs);
+        assert(uid_is_valid(uid));
+
+        /* A generic implementation, covering both manager_ref_uid() and manager_ref_gid(), under the assumption
+         * that uid_t and gid_t are actually defined the same way, with the same validity rules. */
+
+        assert_cc(sizeof(uid_t) == sizeof(gid_t));
+        assert_cc(UID_INVALID == (uid_t) GID_INVALID);
+
+        if (uid == 0) /* We don't keep track of root, and will never destroy it */
+                return 0;
+
+        r = hashmap_ensure_allocated(uid_refs, &trivial_hash_ops);
+        if (r < 0)
+                return r;
+
+        c = PTR_TO_UINT32(hashmap_get(*uid_refs, UID_TO_PTR(uid)));
+
+        n = c & ~DESTROY_IPC_FLAG;
+        n++;
+
+        if (n & DESTROY_IPC_FLAG) /* check for overflow */
+                return -EOVERFLOW;
+
+        c = n | (c & DESTROY_IPC_FLAG) | (clean_ipc ? DESTROY_IPC_FLAG : 0);
+
+        return hashmap_replace(*uid_refs, UID_TO_PTR(uid), UINT32_TO_PTR(c));
+}
+
+int manager_ref_uid(Manager *m, uid_t uid, bool clean_ipc) {
+        return manager_ref_uid_internal(m, &m->uid_refs, uid, clean_ipc);
+}
+
+int manager_ref_gid(Manager *m, gid_t gid, bool clean_ipc) {
+        return manager_ref_uid_internal(m, &m->gid_refs, (uid_t) gid, clean_ipc);
+}
+
+static void manager_vacuum_uid_refs_internal(
+                Manager *m,
+                Hashmap **uid_refs,
+                int (*_clean_ipc)(uid_t uid)) {
+
+        Iterator i;
+        void *p, *k;
+
+        assert(m);
+        assert(uid_refs);
+        assert(_clean_ipc);
+
+        HASHMAP_FOREACH_KEY(p, k, *uid_refs, i) {
+                uint32_t c, n;
+                uid_t uid;
+
+                uid = PTR_TO_UID(k);
+                c = PTR_TO_UINT32(p);
+
+                n = c & ~DESTROY_IPC_FLAG;
+                if (n > 0)
+                        continue;
+
+                if (c & DESTROY_IPC_FLAG) {
+                        log_debug("Found unreferenced %s " UID_FMT " after reload/reexec. Cleaning up.",
+                                  _clean_ipc == clean_ipc_by_uid ? "UID" : "GID",
+                                  uid);
+                        (void) _clean_ipc(uid);
+                }
+
+                assert_se(hashmap_remove(*uid_refs, k) == p);
+        }
+}
+
+void manager_vacuum_uid_refs(Manager *m) {
+        manager_vacuum_uid_refs_internal(m, &m->uid_refs, clean_ipc_by_uid);
+}
+
+void manager_vacuum_gid_refs(Manager *m) {
+        manager_vacuum_uid_refs_internal(m, &m->gid_refs, clean_ipc_by_gid);
+}
+
+static void manager_serialize_uid_refs_internal(
+                Manager *m,
+                FILE *f,
+                Hashmap **uid_refs,
+                const char *field_name) {
+
+        Iterator i;
+        void *p, *k;
+
+        assert(m);
+        assert(f);
+        assert(uid_refs);
+        assert(field_name);
+
+        /* Serialize the UID reference table. Or actually, just the IPC destruction flag of it, as the actual counter
+         * of it is better rebuild after a reload/reexec. */
+
+        HASHMAP_FOREACH_KEY(p, k, *uid_refs, i) {
+                uint32_t c;
+                uid_t uid;
+
+                uid = PTR_TO_UID(k);
+                c = PTR_TO_UINT32(p);
+
+                if (!(c & DESTROY_IPC_FLAG))
+                        continue;
+
+                fprintf(f, "%s=" UID_FMT "\n", field_name, uid);
+        }
+}
+
+void manager_serialize_uid_refs(Manager *m, FILE *f) {
+        manager_serialize_uid_refs_internal(m, f, &m->uid_refs, "destroy-ipc-uid");
+}
+
+void manager_serialize_gid_refs(Manager *m, FILE *f) {
+        manager_serialize_uid_refs_internal(m, f, &m->gid_refs, "destroy-ipc-gid");
+}
+
+static void manager_deserialize_uid_refs_one_internal(
+                Manager *m,
+                Hashmap** uid_refs,
+                const char *value) {
+
+        uid_t uid;
+        uint32_t c;
+        int r;
+
+        assert(m);
+        assert(uid_refs);
+        assert(value);
+
+        r = parse_uid(value, &uid);
+        if (r < 0 || uid == 0) {
+                log_debug("Unable to parse UID reference serialization");
+                return;
+        }
+
+        r = hashmap_ensure_allocated(uid_refs, &trivial_hash_ops);
+        if (r < 0) {
+                log_oom();
+                return;
+        }
+
+        c = PTR_TO_UINT32(hashmap_get(*uid_refs, UID_TO_PTR(uid)));
+        if (c & DESTROY_IPC_FLAG)
+                return;
+
+        c |= DESTROY_IPC_FLAG;
+
+        r = hashmap_replace(*uid_refs, UID_TO_PTR(uid), UINT32_TO_PTR(c));
+        if (r < 0) {
+                log_debug("Failed to add UID reference entry");
+                return;
+        }
+}
+
+void manager_deserialize_uid_refs_one(Manager *m, const char *value) {
+        manager_deserialize_uid_refs_one_internal(m, &m->uid_refs, value);
+}
+
+void manager_deserialize_gid_refs_one(Manager *m, const char *value) {
+        manager_deserialize_uid_refs_one_internal(m, &m->gid_refs, value);
+}
+
+int manager_dispatch_user_lookup_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
+        struct buffer {
+                uid_t uid;
+                gid_t gid;
+                char unit_name[UNIT_NAME_MAX+1];
+        } _packed_ buffer;
+
+        Manager *m = userdata;
+        ssize_t l;
+        size_t n;
+        Unit *u;
+
+        assert_se(source);
+        assert_se(m);
+
+        /* Invoked whenever a child process succeeded resolving its user/group to use and sent us the resulting UID/GID
+         * in a datagram. We parse the datagram here and pass it off to the unit, so that it can add a reference to the
+         * UID/GID so that it can destroy the UID/GID's IPC objects when the reference counter drops to 0. */
+
+        l = recv(fd, &buffer, sizeof(buffer), MSG_DONTWAIT);
+        if (l < 0) {
+                if (errno == EINTR || errno == EAGAIN)
+                        return 0;
+
+                return log_error_errno(errno, "Failed to read from user lookup fd: %m");
+        }
+
+        if ((size_t) l <= offsetof(struct buffer, unit_name)) {
+                log_warning("Received too short user lookup message, ignoring.");
+                return 0;
+        }
+
+        if ((size_t) l > offsetof(struct buffer, unit_name) + UNIT_NAME_MAX) {
+                log_warning("Received too long user lookup message, ignoring.");
+                return 0;
+        }
+
+        if (!uid_is_valid(buffer.uid) && !gid_is_valid(buffer.gid)) {
+                log_warning("Got user lookup message with invalid UID/GID pair, ignoring.");
+                return 0;
+        }
+
+        n = (size_t) l - offsetof(struct buffer, unit_name);
+        if (memchr(buffer.unit_name, 0, n)) {
+                log_warning("Received lookup message with embedded NUL character, ignoring.");
+                return 0;
+        }
+
+        buffer.unit_name[n] = 0;
+        u = manager_get_unit(m, buffer.unit_name);
+        if (!u) {
+                log_debug("Got user lookup message but unit doesn't exist, ignoring.");
+                return 0;
+        }
+
+        log_unit_debug(u, "User lookup succeeded: uid=" UID_FMT " gid=" GID_FMT, buffer.uid, buffer.gid);
+
+        unit_notify_user_lookup(u, buffer.uid, buffer.gid);
+        return 0;
+}
+
 static const char *const manager_state_table[_MANAGER_STATE_MAX] = {
         [MANAGER_INITIALIZING] = "initializing",
         [MANAGER_STARTING] = "starting",
index c681d5dc46297d78f9e98ac8a3c8a1704514e9e6..b9f2e4b5a1b2c21ee3ca1f3d6986af6ba8f4580b 100644 (file)
@@ -143,6 +143,9 @@ struct Manager {
 
         sd_event_source *jobs_in_progress_event_source;
 
+        int user_lookup_fds[2];
+        sd_event_source *user_lookup_event_source;
+
         UnitFileScope unit_file_scope;
         LookupPaths lookup_paths;
         Set *unit_path_cache;
@@ -234,7 +237,6 @@ struct Manager {
         bool dispatching_dbus_queue:1;
 
         bool taint_usr:1;
-
         bool test_run:1;
 
         /* If non-zero, exit with the following value when the systemd
@@ -301,6 +303,10 @@ struct Manager {
         /* Dynamic users/groups, indexed by their name */
         Hashmap *dynamic_users;
 
+        /* Keep track of all UIDs and GIDs any of our services currently use. This is useful for the RemoveIPC= logic. */
+        Hashmap *uid_refs;
+        Hashmap *gid_refs;
+
         /* When the user hits C-A-D more than 7 times per 2s, reboot immediately... */
         RateLimit ctrl_alt_del_ratelimit;
 
@@ -378,5 +384,20 @@ ManagerState manager_state(Manager *m);
 
 int manager_update_failed_units(Manager *m, Unit *u, bool failed);
 
+void manager_unref_uid(Manager *m, uid_t uid, bool destroy_now);
+int manager_ref_uid(Manager *m, uid_t uid, bool clean_ipc);
+
+void manager_unref_gid(Manager *m, gid_t gid, bool destroy_now);
+int manager_ref_gid(Manager *m, gid_t gid, bool destroy_now);
+
+void manager_vacuum_uid_refs(Manager *m);
+void manager_vacuum_gid_refs(Manager *m);
+
+void manager_serialize_uid_refs(Manager *m, FILE *f);
+void manager_deserialize_uid_refs_one(Manager *m, const char *value);
+
+void manager_serialize_gid_refs(Manager *m, FILE *f);
+void manager_deserialize_gid_refs_one(Manager *m, const char *value);
+
 const char *manager_state_to_string(ManagerState m) _const_;
 ManagerState manager_state_from_string(const char *s) _pure_;
index f3ccf6d48a91171428e7d976796beba14cd97e3c..f2ac8d171f56d04fb1ec594948411c8d23116047 100644 (file)
@@ -769,6 +769,8 @@ static void mount_enter_dead(Mount *m, MountResult f) {
 
         exec_context_destroy_runtime_directory(&m->exec_context, manager_get_runtime_prefix(UNIT(m)->manager));
 
+        unit_unref_uid_gid(UNIT(m), true);
+
         dynamic_creds_destroy(&m->dynamic_creds);
 }
 
index 4a37702f52feb2326042a1054cac8bca3cceabdc..1951ba92228b04d6ddbfbe6c540fc6495ca6ba36 100644 (file)
@@ -1471,6 +1471,9 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
         /* Also, remove the runtime directory */
         exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager));
 
+        /* Get rid of the IPC bits of the user */
+        unit_unref_uid_gid(UNIT(s), true);
+
         /* Release the user, and destroy it if we are the only remaining owner */
         dynamic_creds_destroy(&s->dynamic_creds);
 
index 50872e83662d4925523b9bee6546b00eb89c7260..70d55dd9ed9c17a86629e16ce16a6fd0274004b2 100644 (file)
@@ -1905,6 +1905,8 @@ static void socket_enter_dead(Socket *s, SocketResult f) {
 
         exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager));
 
+        unit_unref_uid_gid(UNIT(s), true);
+
         dynamic_creds_destroy(&s->dynamic_creds);
 }
 
index 2c802da3b5357af0da55165ab7e99fff54744018..fb222b6858ecc5b7ae8a5296b6bba4535f09af9b 100644 (file)
@@ -683,6 +683,8 @@ static void swap_enter_dead(Swap *s, SwapResult f) {
 
         exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager));
 
+        unit_unref_uid_gid(UNIT(s), true);
+
         dynamic_creds_destroy(&s->dynamic_creds);
 }
 
index 952604e0db6747d0aedea067e155ec5d17d0a33e..4b8d81c3f1850c5c0c8675672bd889bba3c32f52 100644 (file)
@@ -100,7 +100,8 @@ Unit *unit_new(Manager *m, size_t size) {
         u->on_failure_job_mode = JOB_REPLACE;
         u->cgroup_inotify_wd = -1;
         u->job_timeout = USEC_INFINITY;
-        u->sigchldgen = 0;
+        u->ref_uid = UID_INVALID;
+        u->ref_gid = GID_INVALID;
 
         RATELIMIT_INIT(u->start_limit, m->default_start_limit_interval, m->default_start_limit_burst);
         RATELIMIT_INIT(u->auto_stop_ratelimit, 10 * USEC_PER_SEC, 16);
@@ -550,6 +551,8 @@ void unit_free(Unit *u) {
 
         unit_release_cgroup(u);
 
+        unit_unref_uid_gid(u, false);
+
         (void) manager_update_failed_units(u->manager, u, false);
         set_remove(u->manager->startup_units, u);
 
@@ -2614,6 +2617,11 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) {
                 unit_serialize_item(u, f, "cgroup", u->cgroup_path);
         unit_serialize_item(u, f, "cgroup-realized", yes_no(u->cgroup_realized));
 
+        if (uid_is_valid(u->ref_uid))
+                unit_serialize_item_format(u, f, "ref-uid", UID_FMT, u->ref_uid);
+        if (gid_is_valid(u->ref_gid))
+                unit_serialize_item_format(u, f, "ref-gid", GID_FMT, u->ref_gid);
+
         if (serialize_jobs) {
                 if (u->job) {
                         fprintf(f, "job\n");
@@ -2851,6 +2859,28 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) {
                                 u->cgroup_realized = b;
 
                         continue;
+
+                } else if (streq(l, "ref-uid")) {
+                        uid_t uid;
+
+                        r = parse_uid(v, &uid);
+                        if (r < 0)
+                                log_unit_debug(u, "Failed to parse referenced UID %s, ignoring.", v);
+                        else
+                                unit_ref_uid_gid(u, uid, GID_INVALID);
+
+                        continue;
+
+                } else if (streq(l, "ref-gid")) {
+                        gid_t gid;
+
+                        r = parse_gid(v, &gid);
+                        if (r < 0)
+                                log_unit_debug(u, "Failed to parse referenced GID %s, ignoring.", v);
+                        else
+                                unit_ref_uid_gid(u, UID_INVALID, gid);
+
+                        continue;
                 }
 
                 if (unit_can_serialize(u)) {
@@ -3310,6 +3340,7 @@ int unit_patch_contexts(Unit *u) {
                         }
 
                         ec->private_tmp = true;
+                        ec->remove_ipc = true;
                 }
         }
 
@@ -3932,3 +3963,144 @@ pid_t unit_main_pid(Unit *u) {
 
         return 0;
 }
+
+static void unit_unref_uid_internal(
+                Unit *u,
+                uid_t *ref_uid,
+                bool destroy_now,
+                void (*_manager_unref_uid)(Manager *m, uid_t uid, bool destroy_now)) {
+
+        assert(u);
+        assert(ref_uid);
+        assert(_manager_unref_uid);
+
+        /* Generic implementation of both unit_unref_uid() and unit_unref_gid(), under the assumption that uid_t and
+         * gid_t are actually the same time, with the same validity rules.
+         *
+         * Drops a reference to UID/GID from a unit. */
+
+        assert_cc(sizeof(uid_t) == sizeof(gid_t));
+        assert_cc(UID_INVALID == (uid_t) GID_INVALID);
+
+        if (!uid_is_valid(*ref_uid))
+                return;
+
+        _manager_unref_uid(u->manager, *ref_uid, destroy_now);
+        *ref_uid = UID_INVALID;
+}
+
+void unit_unref_uid(Unit *u, bool destroy_now) {
+        unit_unref_uid_internal(u, &u->ref_uid, destroy_now, manager_unref_uid);
+}
+
+void unit_unref_gid(Unit *u, bool destroy_now) {
+        unit_unref_uid_internal(u, (uid_t*) &u->ref_gid, destroy_now, manager_unref_gid);
+}
+
+static int unit_ref_uid_internal(
+                Unit *u,
+                uid_t *ref_uid,
+                uid_t uid,
+                bool clean_ipc,
+                int (*_manager_ref_uid)(Manager *m, uid_t uid, bool clean_ipc)) {
+
+        int r;
+
+        assert(u);
+        assert(ref_uid);
+        assert(uid_is_valid(uid));
+        assert(_manager_ref_uid);
+
+        /* Generic implementation of both unit_ref_uid() and unit_ref_guid(), under the assumption that uid_t and gid_t
+         * are actually the same type, and have the same validity rules.
+         *
+         * Adds a reference on a specific UID/GID to this unit. Each unit referencing the same UID/GID maintains a
+         * reference so that we can destroy the UID/GID's IPC resources as soon as this is requested and the counter
+         * drops to zero. */
+
+        assert_cc(sizeof(uid_t) == sizeof(gid_t));
+        assert_cc(UID_INVALID == (uid_t) GID_INVALID);
+
+        if (*ref_uid == uid)
+                return 0;
+
+        if (uid_is_valid(*ref_uid)) /* Already set? */
+                return -EBUSY;
+
+        r = _manager_ref_uid(u->manager, uid, clean_ipc);
+        if (r < 0)
+                return r;
+
+        *ref_uid = uid;
+        return 1;
+}
+
+int unit_ref_uid(Unit *u, uid_t uid, bool clean_ipc) {
+        return unit_ref_uid_internal(u, &u->ref_uid, uid, clean_ipc, manager_ref_uid);
+}
+
+int unit_ref_gid(Unit *u, gid_t gid, bool clean_ipc) {
+        return unit_ref_uid_internal(u, (uid_t*) &u->ref_gid, (uid_t) gid, clean_ipc, manager_ref_gid);
+}
+
+static int unit_ref_uid_gid_internal(Unit *u, uid_t uid, gid_t gid, bool clean_ipc) {
+        int r = 0, q = 0;
+
+        assert(u);
+
+        /* Reference both a UID and a GID in one go. Either references both, or neither. */
+
+        if (uid_is_valid(uid)) {
+                r = unit_ref_uid(u, uid, clean_ipc);
+                if (r < 0)
+                        return r;
+        }
+
+        if (gid_is_valid(gid)) {
+                q = unit_ref_gid(u, gid, clean_ipc);
+                if (q < 0) {
+                        if (r > 0)
+                                unit_unref_uid(u, false);
+
+                        return q;
+                }
+        }
+
+        return r > 0 || q > 0;
+}
+
+int unit_ref_uid_gid(Unit *u, uid_t uid, gid_t gid) {
+        ExecContext *c;
+        int r;
+
+        assert(u);
+
+        c = unit_get_exec_context(u);
+
+        r = unit_ref_uid_gid_internal(u, uid, gid, c ? c->remove_ipc : false);
+        if (r < 0)
+                return log_unit_warning_errno(u, r, "Couldn't add UID/GID reference to unit, proceeding without: %m");
+
+        return r;
+}
+
+void unit_unref_uid_gid(Unit *u, bool destroy_now) {
+        assert(u);
+
+        unit_unref_uid(u, destroy_now);
+        unit_unref_gid(u, destroy_now);
+}
+
+void unit_notify_user_lookup(Unit *u, uid_t uid, gid_t gid) {
+        int r;
+
+        assert(u);
+
+        /* This is invoked whenever one of the forked off processes let's us know the UID/GID its user name/group names
+         * resolved to. We keep track of which UID/GID is currently assigned in order to be able to destroy its IPC
+         * objects when no service references the UID/GID anymore. */
+
+        r = unit_ref_uid_gid(u, uid, gid);
+        if (r > 0)
+                bus_unit_send_change_signal(u);
+}
index 513ea1614cab3cb3778a125742d9d1aa6c48040b..53875653d7b465c8f063e0e0451a29f55630fdc9 100644 (file)
@@ -180,6 +180,10 @@ struct Unit {
         /* Make sure we never enter endless loops with the check unneeded logic, or the BindsTo= logic */
         RateLimit auto_stop_ratelimit;
 
+        /* Reference to a specific UID/GID */
+        uid_t ref_uid;
+        gid_t ref_gid;
+
         /* Cached unit file state and preset */
         UnitFileState unit_file_state;
         int unit_file_preset;
@@ -371,8 +375,7 @@ struct UnitVTable {
         /* Called whenever a process of this unit sends us a message */
         void (*notify_message)(Unit *u, pid_t pid, char **tags, FDSet *fds);
 
-        /* Called whenever a name this Unit registered for comes or
-         * goes away. */
+        /* Called whenever a name this Unit registered for comes or goes away. */
         void (*bus_name_owner_change)(Unit *u, const char *name, const char *old_owner, const char *new_owner);
 
         /* Called for each property that is being set */
@@ -621,6 +624,17 @@ int unit_fail_if_symlink(Unit *u, const char* where);
 
 int unit_start_limit_test(Unit *u);
 
+void unit_unref_uid(Unit *u, bool destroy_now);
+int unit_ref_uid(Unit *u, uid_t uid, bool clean_ipc);
+
+void unit_unref_gid(Unit *u, bool destroy_now);
+int unit_ref_gid(Unit *u, gid_t gid, bool clean_ipc);
+
+int unit_ref_uid_gid(Unit *u, uid_t uid, gid_t gid);
+void unit_unref_uid_gid(Unit *u, bool destroy_now);
+
+void unit_notify_user_lookup(Unit *u, uid_t uid, gid_t gid);
+
 /* Macros which append UNIT= or USER_UNIT= to the message */
 
 #define log_unit_full(unit, level, error, ...)                          \
index 63363035e715433cd8c4411e325c9c4dad73881b..11951aca5bc2c21b867848cf8954a391f0e0669c 100644 (file)
@@ -612,9 +612,14 @@ int user_finalize(User *u) {
         if (k < 0)
                 r = k;
 
-        /* Clean SysV + POSIX IPC objects */
-        if (u->manager->remove_ipc) {
-                k = clean_ipc(u->uid);
+        /* Clean SysV + POSIX IPC objects, but only if this is not a system user. Background: in many setups cronjobs
+         * are run in full PAM and thus logind sessions, even if the code run doesn't belong to actual users but to
+         * system components. Since enable RemoveIPC= globally for all users, we need to be a bit careful with such
+         * cases, as we shouldn't accidentally remove a system service's IPC objects while it is running, just because
+         * a cronjob running as the same user just finished. Hence: exclude system users generally from IPC clean-up,
+         * and do it only for normal users. */
+        if (u->manager->remove_ipc && u->uid > SYSTEM_UID_MAX) {
+                k = clean_ipc_by_uid(u->uid);
                 if (k < 0)
                         r = k;
         }
index f9e12e0578ebc93eb880d2125440238e1602e7b5..ab30afb5275d9070f156a7227ec4349f6f6b2cee 100644 (file)
@@ -204,7 +204,7 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
                               "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit",
                               "PrivateTmp", "PrivateDevices", "PrivateNetwork", "PrivateUsers", "NoNewPrivileges",
                               "SyslogLevelPrefix", "Delegate", "RemainAfterElapse", "MemoryDenyWriteExecute",
-                              "RestrictRealtime", "DynamicUser")) {
+                              "RestrictRealtime", "DynamicUser", "RemoveIPC")) {
 
                 r = parse_boolean(eq);
                 if (r < 0)
index 95686348c1b9ee6952a572187b12fa2ef72d643a..64f9b94641d8424293cebbf53d1306cc4ce1c055 100644 (file)
 #include "macro.h"
 #include "string-util.h"
 #include "strv.h"
+#include "user-util.h"
 
-static int clean_sysvipc_shm(uid_t delete_uid) {
+static bool match_uid_gid(uid_t subject_uid, gid_t subject_gid, uid_t delete_uid, gid_t delete_gid) {
+
+        if (uid_is_valid(delete_uid) && subject_uid == delete_uid)
+                return true;
+
+        if (gid_is_valid(delete_gid) && subject_gid == delete_gid)
+                return true;
+
+        return false;
+}
+
+static int clean_sysvipc_shm(uid_t delete_uid, gid_t delete_gid) {
         _cleanup_fclose_ FILE *f = NULL;
         char line[LINE_MAX];
         bool first = true;
@@ -77,7 +89,7 @@ static int clean_sysvipc_shm(uid_t delete_uid) {
                 if (n_attached > 0)
                         continue;
 
-                if (uid != delete_uid)
+                if (!match_uid_gid(uid, gid, delete_uid, delete_gid))
                         continue;
 
                 if (shmctl(shmid, IPC_RMID, NULL) < 0) {
@@ -98,7 +110,7 @@ fail:
         return log_warning_errno(errno, "Failed to read /proc/sysvipc/shm: %m");
 }
 
-static int clean_sysvipc_sem(uid_t delete_uid) {
+static int clean_sysvipc_sem(uid_t delete_uid, gid_t delete_gid) {
         _cleanup_fclose_ FILE *f = NULL;
         char line[LINE_MAX];
         bool first = true;
@@ -128,7 +140,7 @@ static int clean_sysvipc_sem(uid_t delete_uid) {
                            &semid, &uid, &gid, &cuid, &cgid) != 5)
                         continue;
 
-                if (uid != delete_uid)
+                if (!match_uid_gid(uid, gid, delete_uid, delete_gid))
                         continue;
 
                 if (semctl(semid, 0, IPC_RMID) < 0) {
@@ -149,7 +161,7 @@ fail:
         return log_warning_errno(errno, "Failed to read /proc/sysvipc/sem: %m");
 }
 
-static int clean_sysvipc_msg(uid_t delete_uid) {
+static int clean_sysvipc_msg(uid_t delete_uid, gid_t delete_gid) {
         _cleanup_fclose_ FILE *f = NULL;
         char line[LINE_MAX];
         bool first = true;
@@ -180,7 +192,7 @@ static int clean_sysvipc_msg(uid_t delete_uid) {
                            &msgid, &cpid, &lpid, &uid, &gid, &cuid, &cgid) != 7)
                         continue;
 
-                if (uid != delete_uid)
+                if (!match_uid_gid(uid, gid, delete_uid, delete_gid))
                         continue;
 
                 if (msgctl(msgid, IPC_RMID, NULL) < 0) {
@@ -201,7 +213,7 @@ fail:
         return log_warning_errno(errno, "Failed to read /proc/sysvipc/msg: %m");
 }
 
-static int clean_posix_shm_internal(DIR *dir, uid_t uid) {
+static int clean_posix_shm_internal(DIR *dir, uid_t uid, gid_t gid) {
         struct dirent *de;
         int ret = 0, r;
 
@@ -221,7 +233,7 @@ static int clean_posix_shm_internal(DIR *dir, uid_t uid) {
                         continue;
                 }
 
-                if (st.st_uid != uid)
+                if (!match_uid_gid(st.st_uid, st.st_gid, uid, gid))
                         continue;
 
                 if (S_ISDIR(st.st_mode)) {
@@ -232,7 +244,7 @@ static int clean_posix_shm_internal(DIR *dir, uid_t uid) {
                                 if (errno != ENOENT)
                                         ret = log_warning_errno(errno, "Failed to enter shared memory directory %s: %m", de->d_name);
                         } else {
-                                r = clean_posix_shm_internal(kid, uid);
+                                r = clean_posix_shm_internal(kid, uid, gid);
                                 if (r < 0)
                                         ret = r;
                         }
@@ -262,7 +274,7 @@ fail:
         return log_warning_errno(errno, "Failed to read /dev/shm: %m");
 }
 
-static int clean_posix_shm(uid_t uid) {
+static int clean_posix_shm(uid_t uid, gid_t gid) {
         _cleanup_closedir_ DIR *dir = NULL;
 
         dir = opendir("/dev/shm");
@@ -273,10 +285,10 @@ static int clean_posix_shm(uid_t uid) {
                 return log_warning_errno(errno, "Failed to open /dev/shm: %m");
         }
 
-        return clean_posix_shm_internal(dir, uid);
+        return clean_posix_shm_internal(dir, uid, gid);
 }
 
-static int clean_posix_mq(uid_t uid) {
+static int clean_posix_mq(uid_t uid, gid_t gid) {
         _cleanup_closedir_ DIR *dir = NULL;
         struct dirent *de;
         int ret = 0;
@@ -306,7 +318,7 @@ static int clean_posix_mq(uid_t uid) {
                         continue;
                 }
 
-                if (st.st_uid != uid)
+                if (!match_uid_gid(st.st_uid, st.st_gid, uid, gid))
                         continue;
 
                 fn[0] = '/';
@@ -328,32 +340,44 @@ fail:
         return log_warning_errno(errno, "Failed to read /dev/mqueue: %m");
 }
 
-int clean_ipc(uid_t uid) {
+int clean_ipc(uid_t uid, gid_t gid) {
         int ret = 0, r;
 
-        /* Refuse to clean IPC of the root and system users */
-        if (uid <= SYSTEM_UID_MAX)
+        /* Anything to do? */
+        if (!uid_is_valid(uid) && !gid_is_valid(gid))
                 return 0;
 
-        r = clean_sysvipc_shm(uid);
+        /* Refuse to clean IPC of the root user */
+        if (uid == 0 && gid == 0)
+                return 0;
+
+        r = clean_sysvipc_shm(uid, gid);
         if (r < 0)
                 ret = r;
 
-        r = clean_sysvipc_sem(uid);
+        r = clean_sysvipc_sem(uid, gid);
         if (r < 0)
                 ret = r;
 
-        r = clean_sysvipc_msg(uid);
+        r = clean_sysvipc_msg(uid, gid);
         if (r < 0)
                 ret = r;
 
-        r = clean_posix_shm(uid);
+        r = clean_posix_shm(uid, gid);
         if (r < 0)
                 ret = r;
 
-        r = clean_posix_mq(uid);
+        r = clean_posix_mq(uid, gid);
         if (r < 0)
                 ret = r;
 
         return ret;
 }
+
+int clean_ipc_by_uid(uid_t uid) {
+        return clean_ipc(uid, GID_INVALID);
+}
+
+int clean_ipc_by_gid(gid_t gid) {
+        return clean_ipc(UID_INVALID, gid);
+}
index 44a83afcf703dc1a1f960aba6dd51ef12abfa014..6ca57f44fdd0e53e15363f11957a63dbd787ae10 100644 (file)
@@ -21,4 +21,6 @@
 
 #include <sys/types.h>
 
-int clean_ipc(uid_t uid);
+int clean_ipc(uid_t uid, gid_t gid);
+int clean_ipc_by_uid(uid_t uid);
+int clean_ipc_by_gid(gid_t gid);
index c5bcaf47bb1a2694555dea58ca5f5a648a924abf..551eba7215dd4883c86cd10b89d8dc2e16112283 100644 (file)
@@ -32,5 +32,5 @@ int main(int argc, char *argv[]) {
                 return EXIT_FAILURE;
         }
 
-        return clean_ipc(uid) < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+        return clean_ipc_by_uid(uid) < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
 }