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:
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ Packages=
|
||||
popt
|
||||
python-pefile
|
||||
quota-tools
|
||||
shadow
|
||||
tpm2-tss
|
||||
vim
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)));
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user