1
0
mirror of https://github.com/systemd/systemd.git synced 2025-06-02 17:07:47 +03:00

pam_systemd: process the two new capabilities user records fields in pam_systemd

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
This commit is contained in:
Lennart Poettering 2023-02-17 22:49:16 +01:00
parent fada2c75a4
commit bf1b9ae487
2 changed files with 151 additions and 13 deletions

View File

@ -122,6 +122,22 @@
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>

View 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"
@ -47,20 +49,79 @@
#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)