]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
pam_systemd: process the two new capabilities user records fields in pam_systemd
authorLennart Poettering <lennart@poettering.net>
Fri, 17 Feb 2023 21:49:16 +0000 (22:49 +0100)
committerLennart Poettering <lennart@poettering.net>
Tue, 28 Feb 2023 20:42:29 +0000 (21:42 +0100)
And also: by default, for the systemd-user service and for local
sessions (i.e. those assigned to a seat): let's imply CAP_WAKE_SYSTEM
for them by default. Yes, let's pass one specific capability by default to local
unprivileged users.

The capability services exactly once purpose: to allow system wake-up
from suspend via alarm clocks, hence is relatively limited in focus. By
adding this tools such as GNOME's Alarm Clock app can simply allocate a
CLOCK_REALTIME_ALARM (or ask systemd --user to do this) timer and it
will wake up the system as necessary.

Note that systemd --user will not pass the ambient caps on by default,
so even with this change, individual services need to use
AmbientCapabilities= to pass this on to the individual programs.

Fixes: #17564 #21382
man/pam_systemd.xml
src/login/pam_systemd.c

index 60b85778220ba5fb96beaac88512f9a38a3f8ada..f2bd3de0b0e03aebb92547d07d502feab2970d4e 100644 (file)
         further details.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>default-capability-bounding-set=</varname></term>
+        <term><varname>default-capability-ambient-set=</varname></term>
+
+        <listitem><para>Takes a comma-separated list of process capabilities
+        (e.g. <constant>CAP_WAKE_ALARM</constant>, <constant>CAP_BLOCK_SUSPEND</constant>, …) to set for the
+        invoked session's processes, if the user record does not encode appropriate sets of capabilities
+        directly. See <citerefentry
+        project='man-pages'><refentrytitle>capabilities</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+        for details on the capabilities concept. If not specified, the default bounding set is left as is
+        (i.e. usually contains the full set of capabilities). The default ambient set is set to
+        <constant>CAP_WAKE_ALARM</constant> for regular users if the PAM session is associated with a local
+        seat or if it is invoked for the <literal>systemd-user</literal> service. Otherwise defaults to the
+        empty set.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>debug</varname><optional>=</optional></term>
 
index 2cfe52288ea10a9d53235a2533cdd76b15176d49..65d09886bee0e101ec5daebf17b694c50772c9fb 100644 (file)
@@ -21,6 +21,8 @@
 #include "bus-error.h"
 #include "bus-internal.h"
 #include "bus-locator.h"
+#include "cap-list.h"
+#include "capability-util.h"
 #include "cgroup-setup.h"
 #include "devnum-util.h"
 #include "errno-util.h"
 
 #define LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC (2*USEC_PER_MINUTE)
 
+static int parse_caps(
+                pam_handle_t *handle,
+                const char *value,
+                uint64_t *caps) {
+
+        bool subtract;
+        int r;
+
+        assert(handle);
+        assert(value);
+
+        if (value[0] == '~') {
+                subtract = true;
+                value++;
+        } else
+                subtract = false;
+
+        for (;;) {
+                _cleanup_free_ char *s = NULL;
+                uint64_t b, m;
+                int c;
+
+                /* We can't use spaces as separators here, as PAM's simplistic argument parser doesn't allow
+                 * spaces inside of arguments. We use commas instead (which is similar to cap_from_text(),
+                 * which also uses commas). */
+                r = extract_first_word(&value, &s, ",", EXTRACT_DONT_COALESCE_SEPARATORS);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                c = capability_from_name(s);
+                if (c < 0) {
+                        pam_syslog(handle, LOG_WARNING, "Unknown capability, ignoring: %s", s);
+                        continue;
+                }
+
+                m = UINT64_C(1) << c;
+
+                if (!caps)
+                        continue;
+
+                if (*caps == UINT64_MAX)
+                        b = subtract ? all_capabilities() : 0;
+                else
+                        b = *caps;
+
+                if (subtract)
+                        *caps = b & ~m;
+                else
+                        *caps = b | m;
+        }
+
+        return 0;
+}
+
 static int parse_argv(
                 pam_handle_t *handle,
                 int argc, const char **argv,
                 const char **class,
                 const char **type,
                 const char **desktop,
-                bool *debug) {
+                bool *debug,
+                uint64_t *default_capability_bounding_set,
+                uint64_t *default_capability_ambient_set) {
 
-        unsigned i;
+        int r;
 
+        assert(handle);
         assert(argc >= 0);
         assert(argc == 0 || argv);
 
-        for (i = 0; i < (unsigned) argc; i++) {
+        for (int i = 0; i < argc; i++) {
                 const char *p;
 
                 if ((p = startswith(argv[i], "class="))) {
@@ -80,16 +141,24 @@ static int parse_argv(
                                 *debug = true;
 
                 } else if ((p = startswith(argv[i], "debug="))) {
-                        int k;
-
-                        k = parse_boolean(p);
-                        if (k < 0)
+                        r = parse_boolean(p);
+                        if (r < 0)
                                 pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring: %s", p);
                         else if (debug)
-                                *debug = k;
+                                *debug = r;
+
+                } else if ((p = startswith(argv[i], "default-capability-bounding-set="))) {
+                        r = parse_caps(handle, p, default_capability_bounding_set);
+                        if (r < 0)
+                                pam_syslog(handle, LOG_WARNING, "Failed to parse default-capability-bounding-set= argument, ignoring: %s", p);
+
+                } else if ((p = startswith(argv[i], "default-capability-ambient-set="))) {
+                        r = parse_caps(handle, p, default_capability_ambient_set);
+                        if (r < 0)
+                                pam_syslog(handle, LOG_WARNING, "Failed to parse default-capability-ambient-set= argument, ignoring: %s", p);
 
                 } else
-                        pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
+                        pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]);
         }
 
         return 0;
@@ -531,7 +600,12 @@ static int pam_putenv_and_log(pam_handle_t *handle, const char *e, bool debug) {
         return PAM_SUCCESS;
 }
 
-static int apply_user_record_settings(pam_handle_t *handle, UserRecord *ur, bool debug) {
+static int apply_user_record_settings(
+                pam_handle_t *handle,
+                UserRecord *ur,
+                bool debug,
+                uint64_t default_capability_bounding_set,
+                uint64_t default_capability_ambient_set) {
         int r;
 
         assert(handle);
@@ -645,6 +719,31 @@ static int apply_user_record_settings(pam_handle_t *handle, UserRecord *ur, bool
                                    "Resource limit %s set, based on user record.", rlimit_to_string(rl));
         }
 
+        uint64_t a, b;
+        a = user_record_capability_ambient_set(ur);
+        if (a == UINT64_MAX)
+                a = default_capability_ambient_set;
+
+        b = user_record_capability_bounding_set(ur);
+        if (b == UINT64_MAX)
+                b = default_capability_bounding_set;
+
+        if (a != UINT64_MAX && a != 0) {
+                a &= b;
+
+                r = capability_ambient_set_apply(a, /* also_inherit= */ true);
+                if (r < 0)
+                        pam_syslog_errno(handle, LOG_ERR, r,
+                                         "Failed to set ambient capabilities, ignoring: %m");
+        }
+
+        if (b != UINT64_MAX && !cap_test_all(b)) {
+                r = capability_bounding_set_drop(b, /* right_now= */ false);
+                if (r < 0)
+                        pam_syslog_errno(handle, LOG_ERR, r,
+                                         "Failed to set bounding capabilities, ignoring: %m");
+        }
+
         return PAM_SUCCESS;
 }
 
@@ -669,6 +768,21 @@ static int configure_runtime_directory(
         return export_legacy_dbus_address(handle, rt);
 }
 
+static uint64_t pick_default_capability_ambient_set(
+                UserRecord *ur,
+                const char *service,
+                const char *seat) {
+
+        /* If not configured otherwise, let's enable CAP_WAKE_ALARM for regular users when logging in on a
+         * seat (i.e. when they are present physically on the device), or when invoked for the systemd --user
+         * instances. This allows desktops to install CAP_WAKE_ALARM to implement alarm clock apps without
+         * much fuss. */
+
+        return ur &&
+                user_record_disposition(ur) == USER_REGULAR &&
+                (streq_ptr(service, "systemd-user") || !isempty(seat)) ? (UINT64_C(1) << CAP_WAKE_ALARM) : UINT64_MAX;
+}
+
 _public_ PAM_EXTERN int pam_sm_open_session(
                 pam_handle_t *handle,
                 int flags,
@@ -685,6 +799,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
                 *type = NULL, *class = NULL,
                 *class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL, *desktop_pam = NULL,
                 *memory_max = NULL, *tasks_max = NULL, *cpu_weight = NULL, *io_weight = NULL, *runtime_max_sec = NULL;
+        uint64_t default_capability_bounding_set = UINT64_MAX, default_capability_ambient_set = UINT64_MAX;
         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
         int session_fd = -EBADF, existing, r;
@@ -699,7 +814,9 @@ _public_ PAM_EXTERN int pam_sm_open_session(
                        &class_pam,
                        &type_pam,
                        &desktop_pam,
-                       &debug) < 0)
+                       &debug,
+                       &default_capability_bounding_set,
+                       &default_capability_ambient_set) < 0)
                 return PAM_SESSION_ERR;
 
         if (debug)
@@ -988,7 +1105,10 @@ _public_ PAM_EXTERN int pam_sm_open_session(
         }
 
 success:
-        r = apply_user_record_settings(handle, ur, debug);
+        if (default_capability_ambient_set == UINT64_MAX)
+                default_capability_ambient_set = pick_default_capability_ambient_set(ur, service, seat);
+
+        r = apply_user_record_settings(handle, ur, debug, default_capability_bounding_set, default_capability_ambient_set);
         if (r != PAM_SUCCESS)
                 return r;
 
@@ -1016,7 +1136,9 @@ _public_ PAM_EXTERN int pam_sm_close_session(
                        NULL,
                        NULL,
                        NULL,
-                       &debug) < 0)
+                       &debug,
+                       NULL,
+                       NULL) < 0)
                 return PAM_SESSION_ERR;
 
         if (debug)