diff --git a/NEWS b/NEWS index a5f882c5ec..4ef7fb2af3 100644 --- a/NEWS +++ b/NEWS @@ -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: diff --git a/man/homectl.xml b/man/homectl.xml index 634b95a081..36c1741eb5 100644 --- a/man/homectl.xml +++ b/man/homectl.xml @@ -271,6 +271,19 @@ the user will be removed from the group. + + CAPABILITIES + CAPABILITIES + + These options take a space separated list of process capabilities + (e.g. CAP_WAKE_ALARM, CAP_BLOCK_SUSPEND, …) that shall be + set in the capability bounding and ambient sets for all the user's sessions. See capabilities7 + 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 ~ character the + effect is inverted: the specified capability is dropped from the specific set. + + PATH diff --git a/man/pam_systemd.xml b/man/pam_systemd.xml index 60b8577822..f2bd3de0b0 100644 --- a/man/pam_systemd.xml +++ b/man/pam_systemd.xml @@ -122,6 +122,22 @@ further details. + + default-capability-bounding-set= + default-capability-ambient-set= + + Takes a comma-separated list of process capabilities + (e.g. CAP_WAKE_ALARM, CAP_BLOCK_SUSPEND, …) to set for the + invoked session's processes, if the user record does not encode appropriate sets of capabilities + directly. See capabilities7 + 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 + CAP_WAKE_ALARM for regular users if the PAM session is associated with a local + seat or if it is invoked for the systemd-user service. Otherwise defaults to the + empty set. + + debug= diff --git a/mkosi.conf.d/arch/10-arch.conf b/mkosi.conf.d/arch/10-arch.conf index ee51396e04..a1bff77096 100644 --- a/mkosi.conf.d/arch/10-arch.conf +++ b/mkosi.conf.d/arch/10-arch.conf @@ -32,6 +32,7 @@ Packages= popt python-pefile quota-tools + shadow tpm2-tss vim diff --git a/mkosi.conf.d/centos/10-centos.conf b/mkosi.conf.d/centos/10-centos.conf index 94aeaed9c9..91099a7f0a 100644 --- a/mkosi.conf.d/centos/10-centos.conf +++ b/mkosi.conf.d/centos/10-centos.conf @@ -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 diff --git a/mkosi.conf.d/debian/10-debian.conf b/mkosi.conf.d/debian/10-debian.conf index 905378a9b4..0712a70bea 100644 --- a/mkosi.conf.d/debian/10-debian.conf +++ b/mkosi.conf.d/debian/10-debian.conf @@ -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 diff --git a/mkosi.conf.d/fedora/10-fedora.conf b/mkosi.conf.d/fedora/10-fedora.conf index 5d4959ba16..5c85eebc03 100644 --- a/mkosi.conf.d/fedora/10-fedora.conf +++ b/mkosi.conf.d/fedora/10-fedora.conf @@ -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 diff --git a/mkosi.conf.d/opensuse/10-opensuse.conf b/mkosi.conf.d/opensuse/10-opensuse.conf index 6034b8a87e..3b0a643efd 100644 --- a/mkosi.conf.d/opensuse/10-opensuse.conf +++ b/mkosi.conf.d/opensuse/10-opensuse.conf @@ -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 diff --git a/mkosi.conf.d/ubuntu/10-ubuntu.conf b/mkosi.conf.d/ubuntu/10-ubuntu.conf index 647a71826c..a34fe8d94f 100644 --- a/mkosi.conf.d/ubuntu/10-ubuntu.conf +++ b/mkosi.conf.d/ubuntu/10-ubuntu.conf @@ -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 diff --git a/src/home/homectl.c b/src/home/homectl.c index cd24b5e9a5..531b685e22 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -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; diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index 2cfe52288e..65d09886be 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -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) diff --git a/src/shared/user-record-show.c b/src/shared/user-record-show.c index da181c6078..28fa7a8632 100644 --- a/src/shared/user-record-show.c +++ b/src/shared/user-record-show.c @@ -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))); diff --git a/src/shared/user-record.c b/src/shared/user-record.c index 06bc699572..b51d8add1a 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -2,6 +2,7 @@ #include +#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); diff --git a/src/shared/user-record.h b/src/shared/user-record.h index 73fb86cc94..298dc244fe 100644 --- a/src/shared/user-record.h +++ b/src/shared/user-record.h @@ -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); diff --git a/test/units/testsuite-35.sh b/test/units/testsuite-35.sh index 85925f2471..02f22cf2a2 100755 --- a/test/units/testsuite-35.sh +++ b/test/units/testsuite-35.sh @@ -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" < "$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