Merge pull request #26548 from poettering/user-record-ambient

add ambient/bounding set fields to user records, and set CAP_WAKE_ALARM by default for local regular user sessions
This commit is contained in:
Luca Boccassi
2023-02-28 23:40:57 +00:00
committed by GitHub
15 changed files with 380 additions and 14 deletions
+18
View File
@@ -1,5 +1,23 @@
systemd System and Service Manager
CHANGES WITH 254 in spe:
Security relevant changes:
* pam_systemd will now by default pass the CAP_WAKE_ALARM ambient
process capability to invoked session processes of regular users on
local seats (as well as to systemd --user), unless configured
otherwise via data from JSON user records, or via the PAM module's
parameter list. This is useful in order allow desktop tools such as
GNOME's Alarm Clock application to set a timer for
CLOCK_REALTIME_ALARM that wakes up the system when it elapses. A
per-user service unit file may thus use AmbientCapability= to pass
the capability to invoked processes. Note that this capability is
relatively narrow in focus (in particular compared to other process
capabilities such as CAP_SYS_ADMIN) and we already — by default —
permit more impactful operations such as system suspend to local
users.
CHANGES WITH 253:
Announcements of Future Feature Removals and Incompatible Changes:
+13
View File
@@ -271,6 +271,19 @@
the user will be removed from the group.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--capability-bounding-set=</option><replaceable>CAPABILITIES</replaceable></term>
<term><option>--capability-ambient-set=</option><replaceable>CAPABILITIES</replaceable></term>
<listitem><para>These options take a space separated list of process capabilities
(e.g. <constant>CAP_WAKE_ALARM</constant>, <constant>CAP_BLOCK_SUSPEND</constant>, …) that shall be
set in the capability bounding and ambient sets for all the user's sessions. See <citerefentry
project='man-pages'><refentrytitle>capabilities</refentrytitle><manvolnum>7</manvolnum></citerefentry>
for details on the capabilities concept. These options may be used more than once, in which case the
specified lists are combined. If the parameter begins with a <literal>~</literal> character the
effect is inverted: the specified capability is dropped from the specific set.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--skel=</option><replaceable>PATH</replaceable></term>
+16
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>
+1
View File
@@ -32,6 +32,7 @@ Packages=
popt
python-pefile
quota-tools
shadow
tpm2-tss
vim
+2
View File
@@ -28,6 +28,7 @@ Packages=
libasan
libbpf
libcap-ng
libcap-ng-utils
libfido2
libmicrohttpd
libmnl
@@ -38,6 +39,7 @@ Packages=
numactl-libs
p11-kit
pam
passwd
polkit
popt
procps-ng
+2
View File
@@ -20,6 +20,7 @@ Packages=
libasound2
libbpf1
libc6-i386
libcap-ng-utils
libcap-ng0
libfido2-1
libglib2.0-0
@@ -35,6 +36,7 @@ Packages=
libqrencode4
libtss2-dev # Use the -dev package to avoid churn in updating version numbers
netcat-openbsd
passwd
policykit-1
procps
python3-pefile
+2
View File
@@ -24,6 +24,7 @@ Packages=
libasan
libbpf
libcap-ng
libcap-ng-utils
libfido2
libgcrypt
libmicrohttpd
@@ -34,6 +35,7 @@ Packages=
netcat
numactl-libs
pam
passwd
polkit
popt
procps-ng
+2
View File
@@ -18,6 +18,7 @@ Packages=
glibc-locale-base
libasound2
libbpf1
libcap-ng-utils
libcap-ng0
libcrypt1
libcryptsetup12
@@ -38,6 +39,7 @@ Packages=
libxkbcommon0
pam
python3-pefile
shadow
tpm2-0-tss
vim
+2
View File
@@ -21,6 +21,7 @@ Packages=
libasound2
libbpf0
libc6-i386
libcap-ng-utils
libcap-ng0
libfdisk1
libfido2-1
@@ -37,6 +38,7 @@ Packages=
linux-tools-common
linux-tools-generic
netcat-openbsd
passwd
policykit-1
procps
python3-pefile
+68 -1
View File
@@ -9,6 +9,8 @@
#include "bus-common-errors.h"
#include "bus-error.h"
#include "bus-locator.h"
#include "cap-list.h"
#include "capability-util.h"
#include "cgroup-util.h"
#include "dns-domain.h"
#include "env-util.h"
@@ -38,10 +40,10 @@
#include "spawn-polkit-agent.h"
#include "terminal-util.h"
#include "uid-alloc-range.h"
#include "user-record.h"
#include "user-record-pwquality.h"
#include "user-record-show.h"
#include "user-record-util.h"
#include "user-record.h"
#include "user-util.h"
#include "verbs.h"
@@ -76,6 +78,8 @@ static enum {
EXPORT_FORMAT_STRIPPED, /* strip "state" + "binding", but leave signature in place */
EXPORT_FORMAT_MINIMAL, /* also strip signature */
} arg_export_format = EXPORT_FORMAT_FULL;
static uint64_t arg_capability_bounding_set = UINT64_MAX;
static uint64_t arg_capability_ambient_set = UINT64_MAX;
STATIC_DESTRUCTOR_REGISTER(arg_identity_extra, json_variant_unrefp);
STATIC_DESTRUCTOR_REGISTER(arg_identity_extra_this_machine, json_variant_unrefp);
@@ -2210,6 +2214,10 @@ static int help(int argc, char *argv[], void *userdata) {
" -d --home-dir=PATH Home directory\n"
" -u --uid=UID Numeric UID for user\n"
" -G --member-of=GROUP Add user to group\n"
" --capability-bounding-set=CAPS\n"
" Bounding POSIX capability set\n"
" --capability-ambient-set=CAPS\n"
" Ambient POSIX capability set\n"
" --skel=PATH Skeleton directory to use\n"
" --shell=PATH Shell for account\n"
" --setenv=VARIABLE[=VALUE] Set an environment variable at log-in\n"
@@ -2402,6 +2410,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_AUTO_RESIZE_MODE,
ARG_REBALANCE_WEIGHT,
ARG_FIDO2_CRED_ALG,
ARG_CAPABILITY_BOUNDING_SET,
ARG_CAPABILITY_AMBIENT_SET,
};
static const struct option options[] = {
@@ -2492,6 +2502,8 @@ static int parse_argv(int argc, char *argv[]) {
{ "luks-extra-mount-options", required_argument, NULL, ARG_LUKS_EXTRA_MOUNT_OPTIONS },
{ "auto-resize-mode", required_argument, NULL, ARG_AUTO_RESIZE_MODE },
{ "rebalance-weight", required_argument, NULL, ARG_REBALANCE_WEIGHT },
{ "capability-bounding-set", required_argument, NULL, ARG_CAPABILITY_BOUNDING_SET },
{ "capability-ambient-set", required_argument, NULL, ARG_CAPABILITY_AMBIENT_SET },
{}
};
@@ -3735,6 +3747,61 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
case ARG_CAPABILITY_AMBIENT_SET:
case ARG_CAPABILITY_BOUNDING_SET: {
_cleanup_strv_free_ char **l = NULL;
bool subtract = false;
uint64_t parsed, *which, updated;
const char *p, *field;
if (c == ARG_CAPABILITY_AMBIENT_SET) {
which = &arg_capability_ambient_set;
field = "capabilityAmbientSet";
} else {
assert(c == ARG_CAPABILITY_BOUNDING_SET);
which = &arg_capability_bounding_set;
field = "capabilityBoundingSet";
}
if (isempty(optarg)) {
r = drop_from_identity(field);
if (r < 0)
return r;
*which = UINT64_MAX;
break;
}
p = optarg;
if (*p == '~') {
subtract = true;
p++;
}
r = capability_set_from_string(p, &parsed);
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capabilities in capability string '%s'.", p);
if (r < 0)
return log_error_errno(r, "Failed to parse capability string '%s': %m", p);
if (*which == UINT64_MAX)
updated = subtract ? all_capabilities() & ~parsed : parsed;
else if (subtract)
updated = *which & ~parsed;
else
updated = *which | parsed;
if (capability_set_to_strv(updated, &l) < 0)
return log_oom();
r = json_variant_set_field_strv(&arg_identity_extra, field, l);
if (r < 0)
return log_error_errno(r, "Failed to set %s field: %m", field);
*which = updated;
break;
}
case '?':
return -EINVAL;
+135 -13
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)
+17
View File
@@ -1,5 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "cap-list.h"
#include "format-util.h"
#include "fs-util.h"
#include "process-util.h"
@@ -287,6 +288,22 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) {
if (hr->access_mode != MODE_INVALID)
printf(" Access Mode: 0%03o\n", user_record_access_mode(hr));
uint64_t caps = user_record_capability_bounding_set(hr);
if (caps != UINT64_MAX) {
_cleanup_free_ char *scaps = NULL;
(void) capability_set_to_string_negative(caps, &scaps);
printf(" Bound. Caps: %s\n", strna(scaps));
}
caps = user_record_capability_ambient_set(hr);
if (caps != UINT64_MAX) {
_cleanup_free_ char *scaps = NULL;
(void) capability_set_to_string(caps, &scaps);
printf("Ambient Caps: %s\n", strna(scaps));
}
if (storage == USER_LUKS) {
printf("LUKS Discard: online=%s offline=%s\n", yes_no(user_record_luks_discard(hr)), yes_no(user_record_luks_offline_discard(hr)));
+44
View File
@@ -2,6 +2,7 @@
#include <sys/mount.h>
#include "cap-list.h"
#include "cgroup-util.h"
#include "dns-domain.h"
#include "env-util.h"
@@ -165,6 +166,8 @@ static UserRecord* user_record_free(UserRecord *h) {
free(h->home_directory_auto);
strv_free(h->member_of);
strv_free(h->capability_bounding_set);
strv_free(h->capability_ambient_set);
free(h->file_system_type);
free(h->luks_cipher);
@@ -1203,6 +1206,8 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 },
{ "memberOf", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(UserRecord, member_of), JSON_RELAX},
{ "capabilityBoundingSet", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(UserRecord, capability_bounding_set), JSON_SAFE },
{ "capabilityAmbientSet", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(UserRecord, capability_ambient_set), JSON_SAFE },
{ "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
{ "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, partition_uuid), 0 },
{ "luksUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, luks_uuid), 0 },
@@ -1557,6 +1562,8 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 },
{ "memberOf", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(UserRecord, member_of), JSON_RELAX},
{ "capabilityBoundingSet", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(UserRecord, capability_bounding_set), JSON_SAFE },
{ "capabilityAmbientSet", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(UserRecord, capability_ambient_set), JSON_SAFE },
{ "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
{ "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, partition_uuid), 0 },
{ "luksUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, luks_uuid), 0 },
@@ -2018,6 +2025,43 @@ uint64_t user_record_rebalance_weight(UserRecord *h) {
return h->rebalance_weight;
}
static uint64_t parse_caps_strv(char **l) {
uint64_t c = 0;
int r;
STRV_FOREACH(i, l) {
r = capability_from_name(*i);
if (r < 0)
log_debug_errno(r, "Don't know capability '%s', ignoring: %m", *i);
else
c |= UINT64_C(1) << r;
}
return c;
}
uint64_t user_record_capability_bounding_set(UserRecord *h) {
assert(h);
/* Returns UINT64_MAX if no bounding set is configured (!) */
if (!h->capability_bounding_set)
return UINT64_MAX;
return parse_caps_strv(h->capability_bounding_set);
}
uint64_t user_record_capability_ambient_set(UserRecord *h) {
assert(h);
/* Returns UINT64_MAX if no ambient set is configured (!) */
if (!h->capability_ambient_set)
return UINT64_MAX;
return parse_caps_strv(h->capability_ambient_set) & user_record_capability_bounding_set(h);
}
uint64_t user_record_ratelimit_next_try(UserRecord *h) {
assert(h);
+5
View File
@@ -366,6 +366,9 @@ typedef struct UserRecord {
RecoveryKey *recovery_key;
size_t n_recovery_key;
char **capability_bounding_set;
char **capability_ambient_set;
JsonVariant *json;
} UserRecord;
@@ -410,6 +413,8 @@ bool user_record_can_authenticate(UserRecord *h);
bool user_record_drop_caches(UserRecord *h);
AutoResizeMode user_record_auto_resize_mode(UserRecord *h);
uint64_t user_record_rebalance_weight(UserRecord *h);
uint64_t user_record_capability_bounding_set(UserRecord *h);
uint64_t user_record_capability_ambient_set(UserRecord *h);
int user_record_build_image_path(UserStorage storage, const char *user_name_and_realm, char **ret);
+53
View File
@@ -573,6 +573,58 @@ EOF
assert_eq "$(loginctl --no-legend | grep -c "logind-test-user")" 0
}
test_ambient_caps() {
local PAMSERVICE TRANSIENTUNIT SCRIPT
# Verify that pam_systemd works and assigns ambient caps as it should
if ! grep -q 'CapAmb:' /proc/self/status ; then
echo "ambient caps not available, skipping test." >&2
return
fi
typeset -i BND MASK
# Get PID 1's bounding set
BND="0x$(grep 'CapBnd:' /proc/1/status | cut -d: -f2 | tr -d '[:space:]')"
# CAP_CHOWN | CAP_KILL
MASK=$(((1 << 0) | (1 << 5)))
if [ $(("$BND" & "$MASK")) -ne "$MASK" ] ; then
echo "CAP_CHOWN or CAP_KILL not available in bounding set, skipping test." >&2
return
fi
PAMSERVICE="pamserv$RANDOM"
TRANSIENTUNIT="capwakealarm$RANDOM.service"
SCRIPT="/tmp/capwakealarm$RANDOM.sh"
cat > /etc/pam.d/"$PAMSERVICE" <<EOF
auth sufficient pam_unix.so
auth required pam_deny.so
account sufficient pam_unix.so
account required pam_permit.so
session optional pam_systemd.so default-capability-ambient-set=CAP_CHOWN,CAP_KILL debug
session required pam_unix.so
EOF
cat > "$SCRIPT" <<'EOF'
#!/bin/bash
set -ex
typeset -i AMB MASK
AMB="0x$(grep 'CapAmb:' /proc/self/status | cut -d: -f2 | tr -d '[:space:]')"
MASK=$(((1 << 0) | (1 << 5)))
test "$AMB" -eq "$MASK"
EOF
chmod +x "$SCRIPT"
systemd-run -u "$TRANSIENTUNIT" -p PAMName="$PAMSERVICE" -p Type=oneshot -p User=logind-test-user -p StandardError=tty "$SCRIPT"
rm -f "$SCRIPT" "$PAMSERVICE"
}
: >/failed
setup_test_user
@@ -587,6 +639,7 @@ test_lock_idle_action
test_session_properties
test_list_users
test_stop_idle_session
test_ambient_caps
touch /testok
rm /failed