Compare commits

...

8 Commits

Author SHA1 Message Date
Adrian Vovk 3099f0dcc3
Merge 58117af9c0 into fb4c82b643 2024-09-18 17:01:19 +01:00
Antonio Alvarez Feijoo fb4c82b643 nsresourced: fix build without libbpf
```
In file included from ../src/nsresourced/nsresourced-manager.c:9:
../src/shared/bpf-link.h:5:10: fatal error: bpf/libbpf.h: No such file or directory
    5 | #include <bpf/libbpf.h>
      |          ^~~~~~~~~~~~~~
```

Follow-up for 46718d344f
2024-09-18 16:44:12 +02:00
Daan De Meyer 4d9ccdc9ae repart: Drop unprivileged subvolumes logic for btrfs
The functionality was explicitly not included in 6.11 for some
unknown reason so drop the logic from systemd-repart as well so
we don't release v257 with it included.
2024-09-18 16:41:42 +02:00
Antonio Alvarez Feijoo bf39626d61 man/repart: use <varname> instead of <variable>
Otherwise, `<variable>$BOOT</variable>` is rendered:

```
[2548/2992] Generating man/repart.d.5 with a custom command
Element variable in namespace '' encountered in para, but no template matches.
Element variable in namespace '' encountered in para, but no template matches.
```
2024-09-18 16:03:56 +02:00
Marius Hoch ff831e7c50 hwdb: Add accel orientation quirk for the IdeaPad Duet 3 10IGL5-LTE
Signed-off-by: Marius Hoch <mail@mariushoch.de>
2024-09-18 20:30:11 +09:00
Adrian Vovk 58117af9c0
test: Test user record selfModifiable behavior 2024-09-16 20:12:55 -04:00
Adrian Vovk ea9f50a0cd
homed: Allow user to change parts of their record
This allows an unprivileged user that is active at the console to change
the fields that are in the selfModifiable allowlists (introduced in a
previous commit) without authenticating as a system administrator.

Administrators can disable this behavior per-user by setting the
relevant selfModifiable allowlists, or system-wide by changing the
policy of the org.freedesktop.home1.update-home-by-owner Polkit action.
2024-09-16 20:12:53 -04:00
Adrian Vovk 1c97d3e141
user-record: Introduce selfModifiable fields
Allows the system administrator to configure what fields the user is
allowed to edit about themself, along with hard-coded defaults.
2024-09-16 20:12:50 -04:00
14 changed files with 426 additions and 80 deletions

View File

@ -597,6 +597,17 @@ The salt to pass to the FIDO2 device is found in `fido2HmacSalt`.
The only supported recovery key type at the moment is `modhex64`, for details see the description of `recoveryKey` below.
An account may have any number of recovery keys defined, and the array should have one entry for each.
`selfModifiableFields` → An array of strings, each corresponding to a field name that can appear
in the `regular` or `perMachine` sections. The user may be allowed to edit any field in this list
without authenticating as an administrator. Note that the user will only be allowed to edit fields
in `perMachine` sections that match the machine the user is performing the edit from.
`selfModifiableBlobs` → Similar to `selfModifiableFields`, but it lists blobs that the user
is allowed to edit.
`selfModifiablePrivileged` → Similar to `selfModifiableFields`, but it lists fields in
the `privileged` section that the user is allowed to edit.
`privileged` → An object, which contains the fields of the `privileged` section
of the user record, see below.
@ -754,7 +765,7 @@ All other fields that may be used in this section are identical to the equally n
`autoLogin`, `preferredSessionType`, `preferredSessionLauncher`, `stopDelayUSec`, `killProcesses`,
`passwordChangeMinUSec`, `passwordChangeMaxUSec`, `passwordChangeWarnUSec`,
`passwordChangeInactiveUSec`, `passwordChangeNow`, `pkcs11TokenUri`,
`fido2HmacCredential`.
`fido2HmacCredential`, `selfModifiableFields`, `selfModifiableBlobs`, `selfModifiablePrivileged`.
## Fields in the `binding` section

View File

@ -760,8 +760,9 @@ sensor:modalias:i2c:bmc150_accel:dmi:*:svnLENOVO:*:pvrLenovoYoga300-11IBR:*
sensor:modalias:acpi:ACCL0001*:dmi:*:svnLENOVO:pn60072:pvr851*:*
ACCEL_MOUNT_MATRIX=0, 1, 0; -1, 0, 0; 0, 0, 1
# IdeaPad Duet 3 10IGL5 (82AT)
# IdeaPad Duet 3 10IGL5 (82AT) and 10IGL5-LTE (82HK)
sensor:modalias:acpi:SMO8B30*:dmi:*:svnLENOVO*:pn82AT:*
sensor:modalias:acpi:SMO8B30*:dmi:*:svnLENOVO*:pn82HK:*
ACCEL_MOUNT_MATRIX=0, 1, 0; -1, 0, 0; 0, 0, 1
#########################################

View File

@ -922,9 +922,9 @@
target for some other supplement definition. A target cannot have more than one supplement partition
associated with it.</para>
<para>For example, distributions can use this to implement <variable>$BOOT</variable> as defined in
<para>For example, distributions can use this to implement <varname>$BOOT</varname> as defined in
the <ulink url="https://uapi-group.org/specifications/specs/boot_loader_specification/">Boot Loader
Specification</ulink>. Distributions may prefer to use the ESP as <variable>$BOOT</variable> whenever
Specification</ulink>. Distributions may prefer to use the ESP as <varname>$BOOT</varname> whenever
possible, but to adhere to the spec XBOOTLDR must sometimes be used instead. So, they should create
two definitions: the first defining an ESP big enough to hold just the bootloader, and a second for
the XBOOTLDR that's sufficiently large to hold kernels and configured as a supplement for the ESP.

View File

@ -55,7 +55,7 @@ static int property_get_state(
return sd_bus_message_append(reply, "s", home_state_to_string(home_get_state(h)));
}
int bus_home_client_is_trusted(Home *h, sd_bus_message *message) {
static int bus_home_client_is_trusted(Home *h, sd_bus_message *message, bool strict) {
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
uid_t euid;
int r;
@ -73,7 +73,7 @@ int bus_home_client_is_trusted(Home *h, sd_bus_message *message) {
if (r < 0)
return r;
return euid == 0 || h->uid == euid;
return (!strict && euid == 0) || h->uid == euid;
}
static int home_verify_polkit_async(
@ -117,7 +117,7 @@ int bus_home_get_record_json(
assert(h);
assert(ret);
trusted = bus_home_client_is_trusted(h, message);
trusted = bus_home_client_is_trusted(h, message, /* strict= */ false);
if (trusted < 0) {
log_warning_errno(trusted, "Failed to determine whether client is trusted, assuming untrusted.");
trusted = false;
@ -423,7 +423,7 @@ int bus_home_update_record(
Hashmap *blobs,
uint64_t flags,
sd_bus_error *error) {
int r;
int r, relax_access;
assert(h);
assert(message);
@ -436,10 +436,32 @@ int bus_home_update_record(
if ((flags & ~SD_HOMED_UPDATE_FLAGS_ALL) != 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags provided.");
if (blobs) {
const char *failed = NULL;
r = user_record_ensure_blob_manifest(hr, blobs, &failed);
if (r == -EINVAL)
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Provided blob files do not correspond to blob manifest.");
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to generate hash for blob %s: %m", strnull(failed));
}
relax_access = user_record_self_changes_allowed(h->record, hr);
if (relax_access < 0) {
log_warning_errno(relax_access, "Failed to determine if changes to user record are permitted, assuming not: %m");
relax_access = false;
} else if (relax_access) {
relax_access = bus_home_client_is_trusted(h, message, true);
if (relax_access < 0) {
log_warning_errno(relax_access, "Failed to determine whether client is trusted, assuming not: %m");
relax_access = false;
}
}
r = home_verify_polkit_async(
h,
message,
"org.freedesktop.home1.update-home",
relax_access ? "org.freedesktop.home1.update-home-by-owner"
: "org.freedesktop.home1.update-home",
UID_INVALID,
error);
if (r < 0)
@ -561,7 +583,7 @@ int bus_home_method_change_password(
h,
message,
"org.freedesktop.home1.passwd-home",
h->uid,
h->uid, /* Always let a user change their own password. Safe b/c homework will always re-check password */
error);
if (r < 0)
return r;

View File

@ -6,7 +6,6 @@
#include "bus-object.h"
#include "homed-home.h"
int bus_home_client_is_trusted(Home *h, sd_bus_message *message);
int bus_home_get_record_json(Home *h, sd_bus_message *message, char **ret, bool *ret_incomplete);
int bus_home_method_activate(sd_bus_message *message, void *userdata, sd_bus_error *error);

View File

@ -1727,15 +1727,6 @@ static int home_update_internal(
secret = saved_secret;
}
if (blobs) {
const char *failed = NULL;
r = user_record_ensure_blob_manifest(hr, blobs, &failed);
if (r == -EINVAL)
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Provided blob files do not correspond to blob manifest.");
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to generate hash for blob %s: %m", strnull(failed));
}
r = manager_verify_user_record(h->manager, hr);
switch (r) {

View File

@ -49,6 +49,16 @@
</defaults>
</action>
<action id="org.freedesktop.home1.update-home-by-owner">
<description gettext-domain="systemd">Update your home area</description>
<message gettext-domain="systemd">Authentication is required to update your home area.</message>
<defaults>
<allow_any>auth_admin_keep</allow_any>
<allow_inactive>auth_admin_keep</allow_inactive>
<allow_active>yes</allow_active>
</defaults>
</action>
<action id="org.freedesktop.home1.resize-home">
<description gettext-domain="systemd">Resize a home area</description>
<message gettext-domain="systemd">Authentication is required to resize a user's home area.</message>

View File

@ -6,7 +6,9 @@
#include "sd-daemon.h"
#include "bpf-dlopen.h"
#if HAVE_VMLINUX_H
#include "bpf-link.h"
#endif
#include "build-path.h"
#include "common-signal.h"
#include "env-util.h"

View File

@ -304,19 +304,6 @@ static SubvolumeFlags subvolume_flags_from_string(const char *s) {
return flags;
}
static char* subvolume_flags_to_string(SubvolumeFlags flags) {
const char *l[CONST_LOG2U(_SUBVOLUME_FLAGS_MASK + 1) + 1]; /* one string per known flag at most */
size_t m = 0;
if (FLAGS_SET(flags, SUBVOLUME_RO))
l[m++] = "ro";
assert(m < ELEMENTSOF(l));
l[m] = NULL;
return strv_join((char**) l, ",");
}
typedef struct Subvolume {
char *path;
SubvolumeFlags flags;
@ -2438,6 +2425,14 @@ static int partition_read_definition(Partition *p, const char *path, const char
"SizeMinBytes=/SizeMaxBytes= cannot be used with Verity=%s.",
verity_mode_to_string(p->verity));
if (!ordered_hashmap_isempty(p->subvolumes) && arg_offline > 0)
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EOPNOTSUPP),
"Subvolumes= cannot be used with --offline=yes.");
if (p->default_subvolume && arg_offline > 0)
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EOPNOTSUPP),
"DefaultSubvolume= cannot be used with --offline=yes.");
if (p->default_subvolume && !ordered_hashmap_contains(p->subvolumes, p->default_subvolume))
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
"DefaultSubvolume= must be one of the paths in Subvolumes=.");
@ -4286,7 +4281,7 @@ static int prepare_temporary_file(Context *context, PartitionTarget *t, uint64_t
static bool loop_device_error_is_fatal(const Partition *p, int r) {
assert(p);
return arg_offline == 0 || (r != -ENOENT && !ERRNO_IS_PRIVILEGE(r));
return arg_offline == 0 || (r != -ENOENT && !ERRNO_IS_PRIVILEGE(r)) || !ordered_hashmap_isempty(p->subvolumes) || p->default_subvolume;
}
static int partition_target_prepare(
@ -5793,38 +5788,6 @@ static int partition_populate_filesystem(Context *context, Partition *p, const c
return 0;
}
static int append_btrfs_subvols(char ***l, OrderedHashmap *subvolumes, const char *default_subvolume) {
Subvolume *subvolume;
int r;
assert(l);
ORDERED_HASHMAP_FOREACH(subvolume, subvolumes) {
_cleanup_free_ char *s = NULL, *f = NULL;
s = strdup(subvolume->path);
if (!s)
return log_oom();
f = subvolume_flags_to_string(subvolume->flags);
if (!f)
return log_oom();
if (streq_ptr(subvolume->path, default_subvolume) &&
!strextend_with_separator(&f, ",", "default"))
return log_oom();
if (!isempty(f) && !strextend_with_separator(&s, ":", f))
return log_oom();
r = strv_extend_many(l, "--subvol", s);
if (r < 0)
return log_oom();
}
return 0;
}
static int finalize_extra_mkfs_options(const Partition *p, const char *root, char ***ret) {
_cleanup_strv_free_ char **sv = NULL;
int r;
@ -5838,18 +5801,6 @@ static int finalize_extra_mkfs_options(const Partition *p, const char *root, cha
"Failed to determine mkfs command line options for '%s': %m",
p->format);
if (partition_needs_populate(p) && root && streq(p->format, "btrfs")) {
r = append_btrfs_subvols(&sv, p->subvolumes, p->default_subvolume);
if (r < 0)
return r;
if (p->suppressing) {
r = append_btrfs_subvols(&sv, p->suppressing->subvolumes, NULL);
if (r < 0)
return r;
}
}
*ret = TAKE_PTR(sv);
return 0;
}

View File

@ -585,6 +585,22 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) {
if (hr->service)
printf(" Service: %s\n", hr->service);
if (!strv_isempty(hr->self_modifiable_fields))
STRV_FOREACH(i, hr->self_modifiable_fields)
printf(i == hr->self_modifiable_fields ?
" Self Modify: %s\n" :
" %s\n", *i);
else if (!strv_isempty(hr->self_modifiable_blobs) || !strv_isempty(hr->self_modifiable_privileged))
printf(" Self Modify: %s(no fields)%s\n", ansi_grey(), ansi_normal());
STRV_FOREACH(i, hr->self_modifiable_blobs)
printf(i == hr->self_modifiable_blobs ?
" (Blobs) %s\n" :
" %s\n", *i);
STRV_FOREACH(i, hr->self_modifiable_privileged)
printf(i == hr->self_modifiable_privileged ?
" (Privileged) %s\n" :
" %s\n", *i);
}
void group_record_show(GroupRecord *gr, bool show_full_user_info) {

View File

@ -207,6 +207,10 @@ static UserRecord* user_record_free(UserRecord *h) {
for (size_t i = 0; i < h->n_recovery_key; i++)
recovery_key_done(h->recovery_key + i);
strv_free(h->self_modifiable_fields);
strv_free(h->self_modifiable_blobs);
strv_free(h->self_modifiable_privileged);
sd_json_variant_unref(h->json);
return mfree(h);
@ -1317,6 +1321,9 @@ static int dispatch_per_machine(const char *name, sd_json_variant *variant, sd_j
{ "passwordChangeNow", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(UserRecord, password_change_now), 0 },
{ "pkcs11TokenUri", SD_JSON_VARIANT_ARRAY, dispatch_pkcs11_uri_array, offsetof(UserRecord, pkcs11_token_uri), 0 },
{ "fido2HmacCredential", SD_JSON_VARIANT_ARRAY, dispatch_fido2_hmac_credential_array, 0, 0 },
{ "selfModifiableFields", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_fields), SD_JSON_STRICT },
{ "selfModifiableBlobs", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_blobs), SD_JSON_STRICT },
{ "selfModifiablePrivileged", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_privileged), SD_JSON_STRICT },
{},
};
@ -1485,7 +1492,6 @@ int user_group_record_mangle(
assert(v);
assert(ret_variant);
assert(ret_mask);
/* Note that this function is shared with the group record parser, hence we try to be generic in our
* log message wording here, to cover both cases. */
@ -1573,7 +1579,8 @@ int user_group_record_mangle(
else
*ret_variant = sd_json_variant_ref(v);
*ret_mask = m;
if (ret_mask)
*ret_mask = m;
return 0;
}
@ -1663,6 +1670,9 @@ int user_record_load(UserRecord *h, sd_json_variant *v, UserRecordLoadFlags load
{ "pkcs11TokenUri", SD_JSON_VARIANT_ARRAY, dispatch_pkcs11_uri_array, offsetof(UserRecord, pkcs11_token_uri), 0 },
{ "fido2HmacCredential", SD_JSON_VARIANT_ARRAY, dispatch_fido2_hmac_credential_array, 0, 0 },
{ "recoveryKeyType", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, recovery_key_type), 0 },
{ "selfModifiableFields", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_fields), SD_JSON_STRICT },
{ "selfModifiableBlobs", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_blobs), SD_JSON_STRICT },
{ "selfModifiablePrivileged", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_privileged), SD_JSON_STRICT },
{ "secret", SD_JSON_VARIANT_OBJECT, dispatch_secret, 0, 0 },
{ "privileged", SD_JSON_VARIANT_OBJECT, dispatch_privileged, 0, 0 },
@ -2173,6 +2183,228 @@ int user_record_languages(UserRecord *h, char ***ret) {
return 0;
}
const char** user_record_self_modifiable_fields(UserRecord *h) {
/* As a rule of thumb: a setting is safe if it cannot be used by a
* user to give themselves some unfair advantage over other users on
* a given system. */
static const char *const default_fields[] = {
/* For display purposes */
"realName",
"emailAddress", /* Just the $EMAIL env var */
"iconName",
"location",
/* Basic account settings */
"shell",
"umask",
"environment",
"timeZone",
"preferredLanguage",
"additionalLanguages",
"preferredSessionLauncher",
"preferredSessionType",
/* Authentication methods */
"pkcs11TokenUri",
"fido2HmacCredential",
"recoveryKeyType",
"lastChangeUSec", /* Necessary to be able to change record at all */
"lastPasswordChangeUSec", /* Ditto, but for authentication methods */
NULL
};
assert(h);
return (const char**) h->self_modifiable_fields ?: (const char**) default_fields;
}
const char** user_record_self_modifiable_blobs(UserRecord *h) {
static const char *const default_blobs[] = {
/* For display purposes */
"avatar",
"login-background",
NULL
};
assert(h);
return (const char**) h->self_modifiable_blobs ?: (const char**) default_blobs;
}
const char** user_record_self_modifiable_privileged(UserRecord *h) {
static const char *const default_fields[] = {
/* For display purposes */
"passwordHint",
/* Authentication methods */
"hashedPassword"
"pkcs11EncryptedKey",
"fido2HmacSalt",
"recoveryKey",
"sshAuthorizedKeys", /* Basically just ~/.ssh/authorized_keys */
NULL
};
assert(h);
return (const char**) h->self_modifiable_privileged ?: (const char**) default_fields;
}
static int remove_self_modifiable_json_fields_common(UserRecord *current, sd_json_variant **ret) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *blobs = NULL;
char **allowed;
int r;
assert(current);
assert(ret);
if (!sd_json_variant_is_object(*ret))
return -EINVAL;
v = sd_json_variant_ref(*ret);
/* Handle basic fields */
allowed = (char**) user_record_self_modifiable_fields(current);
r = sd_json_variant_filter(&v, allowed);
if (r < 0)
return r;
/* Handle blobs */
blobs = sd_json_variant_ref(sd_json_variant_by_key(v, "blobManifest"));
if (blobs) {
/* The blobManifest contains the sha256 hashes of the blobs,
* which are enforced by the service managing the user. So, by
* comparing the blob manifests like this, we're actually comparing
* the contents of the blob directories & files */
allowed = (char**) user_record_self_modifiable_blobs(current);
r = sd_json_variant_filter(&blobs, allowed);
if (r < 0)
return r;
if (sd_json_variant_is_blank_object(blobs))
r = sd_json_variant_filter(&v, STRV_MAKE("blobManifest"));
else
r = sd_json_variant_set_field(&v, "blobManifest", blobs);
if (r < 0)
return r;
}
JSON_VARIANT_REPLACE(*ret, TAKE_PTR(v));
return 0;
}
static int remove_self_modifiable_json_fields(UserRecord *current, UserRecord *h, sd_json_variant **ret) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *privileged = NULL;
sd_json_variant *per_machine;
char **allowed;
int r;
assert(current);
assert(h);
assert(ret);
r = user_group_record_mangle(h->json, USER_RECORD_EXTRACT_SIGNABLE|USER_RECORD_PERMISSIVE, &v, NULL);
if (r < 0)
return r;
/* Handle the regular section */
r = remove_self_modifiable_json_fields_common(current, &v);
if (r < 0)
return r;
/* Handle the perMachine section */
per_machine = sd_json_variant_by_key(v, "perMachine");
if (per_machine) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *new_per_machine = NULL;
sd_json_variant *e;
if (!sd_json_variant_is_array(per_machine))
return -EINVAL;
JSON_VARIANT_ARRAY_FOREACH(e, per_machine) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *z = NULL;
if (!sd_json_variant_is_object(e))
return -EINVAL;
r = per_machine_match(e, 0);
if (r < 0)
return r;
if (r == 0) {
/* It's only permissible to change anything inside of matching perMachine sections */
r = sd_json_variant_append_array(&new_per_machine, e);
if (r < 0)
return r;
continue;
}
z = sd_json_variant_ref(e);
r = remove_self_modifiable_json_fields_common(current, &z);
if (r < 0)
return r;
if (!sd_json_variant_is_blank_object(z)) {
r = sd_json_variant_append_array(&new_per_machine, z);
if (r < 0)
return r;
}
}
if (sd_json_variant_is_blank_array(new_per_machine))
r = sd_json_variant_filter(&v, STRV_MAKE("perMachine"));
else
r = sd_json_variant_set_field(&v, "perMachine", new_per_machine);
if (r < 0)
return r;
}
/* Handle the privileged section */
privileged = sd_json_variant_ref(sd_json_variant_by_key(v, "privileged"));
if (privileged) {
allowed = (char**) user_record_self_modifiable_privileged(current);
r = sd_json_variant_filter(&privileged, allowed);
if (r < 0)
return r;
if (sd_json_variant_is_blank_object(privileged))
r = sd_json_variant_filter(&v, STRV_MAKE("privileged"));
else
r = sd_json_variant_set_field(&v, "privileged", privileged);
if (r < 0)
return r;
}
JSON_VARIANT_REPLACE(*ret, TAKE_PTR(v));
return 0;
}
int user_record_self_changes_allowed(UserRecord *current, UserRecord *new) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *vc = NULL, *vn = NULL;
int r;
assert(current);
assert(new);
/* We remove the fields that are allowed to change as the user,
* and then compare the resulting JSON records. If they are not equal,
* that means an unsafe field has been changed and thus we should not
* allow the user to apply the changes to themself. */
r = remove_self_modifiable_json_fields(current, current, &vc);
if (r < 0)
return r;
r = remove_self_modifiable_json_fields(current, new, &vn);
if (r < 0)
return r;
return sd_json_variant_equal(vc, vn);
}
uint64_t user_record_ratelimit_next_try(UserRecord *h) {
assert(h);

View File

@ -383,6 +383,10 @@ typedef struct UserRecord {
char **capability_bounding_set;
char **capability_ambient_set;
char **self_modifiable_fields; /* fields a user can change about themself w/o auth */
char **self_modifiable_blobs;
char **self_modifiable_privileged;
sd_json_variant *json;
} UserRecord;
@ -431,6 +435,11 @@ uint64_t user_record_capability_bounding_set(UserRecord *h);
uint64_t user_record_capability_ambient_set(UserRecord *h);
int user_record_languages(UserRecord *h, char ***ret);
const char **user_record_self_modifiable_fields(UserRecord *h);
const char **user_record_self_modifiable_blobs(UserRecord *h);
const char **user_record_self_modifiable_privileged(UserRecord *h);
int user_record_self_changes_allowed(UserRecord *current, UserRecord *new);
int user_record_build_image_path(UserStorage storage, const char *user_name_and_realm, char **ret);
bool user_record_equal(UserRecord *a, UserRecord *b);

View File

@ -182,6 +182,7 @@ simple_tests += files(
'test-umask-util.c',
'test-unaligned.c',
'test-unit-file.c',
'test-user-record.c',
'test-user-util.c',
'test-utf8.c',
'test-verbs.c',

101
src/test/test-user-record.c Normal file
View File

@ -0,0 +1,101 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "json-util.h"
#include "macro.h"
#include "tests.h"
#include "user-record.h"
#define USER(ret, ...) \
({ \
typeof(ret) _r = (ret); \
user_record_unref(*_r); \
assert_se(user_record_build((ret), SD_JSON_BUILD_OBJECT(__VA_ARGS__)) >= 0); \
0; \
})
TEST(self_changes) {
_cleanup_(user_record_unrefp) UserRecord *curr = NULL, *new = NULL;
/* not allowlisted */
USER(&curr,
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 11111));
USER(&new,
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 99999));
assert_se(!user_record_self_changes_allowed(curr, new));
/* manually allowlisted */
USER(&curr,
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 11111),
SD_JSON_BUILD_PAIR_ARRAY("selfModifiableFields", SD_JSON_BUILD_STRING("notInHardCodedList")));
USER(&new,
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
SD_JSON_BUILD_PAIR_ARRAY("selfModifiableFields", SD_JSON_BUILD_STRING("notInHardCodedList")),
/* change in order shouldn't affect things */
SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 99999));
assert_se(user_record_self_changes_allowed(curr, new));
/* default allowlisted */
USER(&curr,
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
SD_JSON_BUILD_PAIR_STRING("realName", "Old Name"));
USER(&new,
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
SD_JSON_BUILD_PAIR_STRING("realName", "New Name"));
assert_se(user_record_self_changes_allowed(curr, new));
/* introduced new default allowlisted */
USER(&curr,
SD_JSON_BUILD_PAIR_STRING("userName", "test"));
USER(&new,
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
SD_JSON_BUILD_PAIR_STRING("realName", "New Name"));
assert_se(user_record_self_changes_allowed(curr, new));
/* introduced new not allowlisted */
USER(&curr,
SD_JSON_BUILD_PAIR_STRING("userName", "test"));
USER(&new,
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 99999));
assert_se(!user_record_self_changes_allowed(curr, new));
/* privileged section: default allowlisted */
USER(&curr,
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
SD_JSON_BUILD_PAIR_OBJECT("privileged",
SD_JSON_BUILD_PAIR_STRING("passwordHint", "Old Hint")));
USER(&new,
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
SD_JSON_BUILD_PAIR_OBJECT("privileged",
SD_JSON_BUILD_PAIR_STRING("passwordHint", "New Hint")));
assert_se(user_record_self_changes_allowed(curr, new));
/* privileged section: not allowlisted */
USER(&curr,
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
SD_JSON_BUILD_PAIR_OBJECT("privileged",
SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 11111)));
USER(&new,
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
SD_JSON_BUILD_PAIR_OBJECT("privileged",
SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 99999)));
assert_se(!user_record_self_changes_allowed(curr, new));
/* privileged section: manually allowlisted */
USER(&curr,
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
SD_JSON_BUILD_PAIR_ARRAY("selfModifiablePrivileged", SD_JSON_BUILD_STRING("notInHardCodedList")),
SD_JSON_BUILD_PAIR_OBJECT("privileged",
SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 11111)));
USER(&new,
SD_JSON_BUILD_PAIR_STRING("userName", "test"),
SD_JSON_BUILD_PAIR_ARRAY("selfModifiablePrivileged", SD_JSON_BUILD_STRING("notInHardCodedList")),
SD_JSON_BUILD_PAIR_OBJECT("privileged",
SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 99999)));
assert_se(user_record_self_changes_allowed(curr, new));
}
DEFINE_TEST_MAIN(LOG_INFO);