Hi Eric!

On Thu, Jun 18 2026, Eric Biggers wrote:

> Change mk_users (the set of user claims to an fscrypt master key) from a
> 'struct key' keyring to a simple linked list.
>
> It's still a collection of 'struct key' for quota tracking.  It was
> originally thought to be natural that a collection of 'struct key'
> should be held in a 'struct key' keyring.  In reality, it's just been
> causing problems, similar to how using 'struct key' for the filesystem
> keyring caused problems and was removed in commit d7e7b9af104c
> ("fscrypt: stop using keyrings subsystem for fscrypt_master_key").
>
> Commit d3a7bd420076 ("fscrypt: clear keyring before calling key_put()")
> fixed mk_users cleanup to be synchronous.  But that apparently wasn't
> enough: the keyring subsystem's redundant locking is still generating
> lockdep false positives due to the interaction with filesystem reclaim.
>
> With the simple list, the redundant locking and lockdep issue goes away.
>
> Of course, searching a linked list is linear-time whereas the
> 'struct key' keyring used a fancy constant-time associative array.  But
> that's fine here, since in practice there's just one entry in the list.
> In fact the new code is much faster in practice, since it's much smaller
> and doesn't have to convert the kuid_t into a string to search for it.
>
> Reported-by: [email protected]
> Closes: https://syzkaller.appspot.com/bug?extid=f55b043dacf43776b50c
> Reported-by: Mohammed EL Kadiri <[email protected]>
> Closes: 
> https://lore.kernel.org/keyrings/[email protected]/
> Fixes: 23c688b54016 ("fscrypt: allow unprivileged users to add/remove keys 
> for v2 policies")
> Cc: [email protected]
> Signed-off-by: Eric Biggers <[email protected]>
> ---
>
> I'm planning to take this via the fscrypt tree for 7.2

I was hoping to have some more time to test this patch, but I don't think
that will happen any time soon.

I've done a review of the patch and couldn't find any obvious problem.
Though, again, a more in-depth review would require more time as it has
been a while since I took a look into this code.

I just wonder if this is really stable material.  It's a bit intrusive
(doesn't even apply cleanly in mainline), but above all it's fixing a
lockdep false positive.  Other than syzbot, has this been seen in the
wild?

Cheers,
-- 
Luís

>
>  fs/crypto/fscrypt_private.h |  32 ++++--
>  fs/crypto/keyring.c         | 216 +++++++++++++++---------------------
>  2 files changed, 113 insertions(+), 135 deletions(-)
>
> diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h
> index 4263cac24b32..0053b5c45412 100644
> --- a/fs/crypto/fscrypt_private.h
> +++ b/fs/crypto/fscrypt_private.h
> @@ -494,10 +494,23 @@ fscrypt_is_key_prepared(const struct 
> fscrypt_prepared_key *prep_key,
>  }
>  #endif /* !CONFIG_FS_ENCRYPTION_INLINE_CRYPT */
>  
>  /* keyring.c */
>  
> +/*
> + * fscrypt_master_key_user - a user's claim to a master key
> + */
> +struct fscrypt_master_key_user {
> +     struct list_head link;
> +     kuid_t uid;
> +     /*
> +      * This 'struct key' contains no secret.  It exists solely to charge the
> +      * appropriate user's key quota.
> +      */
> +     struct key *quota_key;
> +};
> +
>  /*
>   * fscrypt_master_key_secret - secret key material of an in-use master key
>   */
>  struct fscrypt_master_key_secret {
>  
> @@ -609,23 +622,22 @@ struct fscrypt_master_key {
>        * For v2 policy keys: a cryptographic hash of this key (->identifier).
>        */
>       struct fscrypt_key_specifier            mk_spec;
>  
>       /*
> -      * Keyring which contains a key of type 'key_type_fscrypt_user' for each
> -      * user who has added this key.  Normally each key will be added by just
> -      * one user, but it's possible that multiple users share a key, and in
> -      * that case we need to keep track of those users so that one user can't
> -      * remove the key before the others want it removed too.
> +      * List of user claims to this key (struct fscrypt_master_key_user).
> +      * Normally each key will be added by just one user, but it's possible
> +      * that multiple users share a key, and in that case we need to keep
> +      * track of those users so that one user can't remove the key before the
> +      * others want it removed too.
>        *
> -      * This is NULL for v1 policy keys; those can only be added by root.
> +      * Used only for v2 policy keys.  v1 policy keys can be added only by
> +      * root, so user tracking doesn't apply to them.
>        *
> -      * Locking: protected by ->mk_sem.  (We don't just rely on the keyrings
> -      * subsystem semaphore ->mk_users->sem, as we need support for atomic
> -      * search+insert along with proper synchronization with other fields.)
> +      * Locking: protected by ->mk_sem.
>        */
> -     struct key              *mk_users;
> +     struct list_head        mk_users;
>  
>       /*
>        * List of inodes that were unlocked using this key.  This allows the
>        * inodes to be evicted efficiently if the key is removed.
>        */
> diff --git a/fs/crypto/keyring.c b/fs/crypto/keyring.c
> index 6ce6b436c34f..16bc348213ca 100644
> --- a/fs/crypto/keyring.c
> +++ b/fs/crypto/keyring.c
> @@ -63,26 +63,23 @@ static void fscrypt_free_master_key(struct rcu_head *head)
>        * Nevertheless, use kfree_sensitive() in case anything was missed.
>        */
>       kfree_sensitive(mk);
>  }
>  
> +static void clear_mk_users(struct fscrypt_master_key *mk);
> +
>  void fscrypt_put_master_key(struct fscrypt_master_key *mk)
>  {
>       if (!refcount_dec_and_test(&mk->mk_struct_refs))
>               return;
>       /*
> -      * No structural references left, so free ->mk_users, and also free the
> +      * No structural references left, so clear ->mk_users, and also free the
>        * fscrypt_master_key struct itself after an RCU grace period ensures
>        * that concurrent keyring lookups can no longer find it.
>        */
>       WARN_ON_ONCE(refcount_read(&mk->mk_active_refs) != 0);
> -     if (mk->mk_users) {
> -             /* Clear the keyring so the quota gets released right away. */
> -             keyring_clear(mk->mk_users);
> -             key_put(mk->mk_users);
> -             mk->mk_users = NULL;
> -     }
> +     clear_mk_users(mk);
>       call_rcu(&mk->mk_rcu_head, fscrypt_free_master_key);
>  }
>  
>  void fscrypt_put_master_key_activeref(struct super_block *sb,
>                                     struct fscrypt_master_key *mk)
> @@ -163,12 +160,12 @@ static void fscrypt_user_key_describe(const struct key 
> *key, struct seq_file *m)
>  {
>       seq_puts(m, key->description);
>  }
>  
>  /*
> - * Type of key in ->mk_users.  Each key of this type represents a particular
> - * user who has added a particular master key.
> + * Type of fscrypt_master_key_user::quota_key.  This contains no secret; it
> + * exists solely to charge a user's key quota.
>   *
>   * Note that the name of this key type really should be something like
>   * ".fscrypt-user" instead of simply ".fscrypt".  But the shorter name is 
> chosen
>   * mainly for simplicity of presentation in /proc/keys when read by a 
> non-root
>   * user.  And it is expected to be rare that a key is actually added by 
> multiple
> @@ -178,34 +175,13 @@ static struct key_type key_type_fscrypt_user = {
>       .name                   = ".fscrypt",
>       .instantiate            = fscrypt_user_key_instantiate,
>       .describe               = fscrypt_user_key_describe,
>  };
>  
> -#define FSCRYPT_MK_USERS_DESCRIPTION_SIZE    \
> -     (CONST_STRLEN("fscrypt-") + 2 * FSCRYPT_KEY_IDENTIFIER_SIZE + \
> -      CONST_STRLEN("-users") + 1)
> -
>  #define FSCRYPT_MK_USER_DESCRIPTION_SIZE     \
>       (2 * FSCRYPT_KEY_IDENTIFIER_SIZE + CONST_STRLEN(".uid.") + 10 + 1)
>  
> -static void format_mk_users_keyring_description(
> -                     char description[FSCRYPT_MK_USERS_DESCRIPTION_SIZE],
> -                     const u8 mk_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE])
> -{
> -     sprintf(description, "fscrypt-%*phN-users",
> -             FSCRYPT_KEY_IDENTIFIER_SIZE, mk_identifier);
> -}
> -
> -static void format_mk_user_description(
> -                     char description[FSCRYPT_MK_USER_DESCRIPTION_SIZE],
> -                     const u8 mk_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE])
> -{
> -
> -     sprintf(description, "%*phN.uid.%u", FSCRYPT_KEY_IDENTIFIER_SIZE,
> -             mk_identifier, __kuid_val(current_fsuid()));
> -}
> -
>  /* Create ->s_master_keys if needed.  Synchronized by fscrypt_add_key_mutex. 
> */
>  static int allocate_filesystem_keyring(struct super_block *sb)
>  {
>       struct fscrypt_keyring *keyring;
>  
> @@ -336,95 +312,98 @@ fscrypt_find_master_key(struct super_block *sb,
>  out:
>       rcu_read_unlock();
>       return mk;
>  }
>  
> -static int allocate_master_key_users_keyring(struct fscrypt_master_key *mk)
> -{
> -     char description[FSCRYPT_MK_USERS_DESCRIPTION_SIZE];
> -     struct key *keyring;
> -
> -     format_mk_users_keyring_description(description,
> -                                         mk->mk_spec.u.identifier);
> -     keyring = keyring_alloc(description, GLOBAL_ROOT_UID, GLOBAL_ROOT_GID,
> -                             current_cred(), KEY_POS_SEARCH |
> -                               KEY_USR_SEARCH | KEY_USR_READ | KEY_USR_VIEW,
> -                             KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
> -     if (IS_ERR(keyring))
> -             return PTR_ERR(keyring);
> -
> -     mk->mk_users = keyring;
> -     return 0;
> -}
> -
> -/*
> - * Find the current user's "key" in the master key's ->mk_users.
> - * Returns ERR_PTR(-ENOKEY) if not found.
> - */
> -static struct key *find_master_key_user(struct fscrypt_master_key *mk)
> +/* Find the current user's claim in ->mk_users.  ->mk_sem must be held. */
> +static struct fscrypt_master_key_user *
> +find_master_key_user(struct fscrypt_master_key *mk)
>  {
> -     char description[FSCRYPT_MK_USER_DESCRIPTION_SIZE];
> -     key_ref_t keyref;
> +     struct fscrypt_master_key_user *mk_user;
> +     kuid_t uid = current_fsuid();
>  
> -     format_mk_user_description(description, mk->mk_spec.u.identifier);
> -
> -     /*
> -      * We need to mark the keyring reference as "possessed" so that we
> -      * acquire permission to search it, via the KEY_POS_SEARCH permission.
> -      */
> -     keyref = keyring_search(make_key_ref(mk->mk_users, true /*possessed*/),
> -                             &key_type_fscrypt_user, description, false);
> -     if (IS_ERR(keyref)) {
> -             if (PTR_ERR(keyref) == -EAGAIN || /* not found */
> -                 PTR_ERR(keyref) == -EKEYREVOKED) /* recently invalidated */
> -                     keyref = ERR_PTR(-ENOKEY);
> -             return ERR_CAST(keyref);
> +     list_for_each_entry(mk_user, &mk->mk_users, link) {
> +             if (uid_eq(mk_user->uid, uid))
> +                     return mk_user;
>       }
> -     return key_ref_to_ptr(keyref);
> +     return NULL;
>  }
>  
>  /*
> - * Give the current user a "key" in ->mk_users.  This charges the user's 
> quota
> + * Give the current user a claim in ->mk_users.  This charges the user's 
> quota
>   * and marks the master key as added by the current user, so that it cannot 
> be
>   * removed by another user with the key.  Either ->mk_sem must be held for
>   * write, or the master key must be still undergoing initialization.
>   */
>  static int add_master_key_user(struct fscrypt_master_key *mk)
>  {
> +     kuid_t uid = current_fsuid();
>       char description[FSCRYPT_MK_USER_DESCRIPTION_SIZE];
> -     struct key *mk_user;
> +     struct key *quota_key;
> +     struct fscrypt_master_key_user *mk_user;
>       int err;
>  
> -     format_mk_user_description(description, mk->mk_spec.u.identifier);
> -     mk_user = key_alloc(&key_type_fscrypt_user, description,
> -                         current_fsuid(), current_gid(), current_cred(),
> -                         KEY_POS_SEARCH | KEY_USR_VIEW, 0, NULL);
> -     if (IS_ERR(mk_user))
> -             return PTR_ERR(mk_user);
> +     snprintf(description, sizeof(description), "%*phN.uid.%u",
> +              FSCRYPT_KEY_IDENTIFIER_SIZE, mk->mk_spec.u.identifier,
> +              __kuid_val(uid));
> +     quota_key = key_alloc(&key_type_fscrypt_user, description, uid,
> +                           current_gid(), current_cred(),
> +                           KEY_POS_SEARCH | KEY_USR_VIEW, 0, NULL);
> +     if (IS_ERR(quota_key))
> +             return PTR_ERR(quota_key);
> +
> +     err = key_instantiate_and_link(quota_key, NULL, 0, NULL, NULL);
> +     if (err) {
> +             key_put(quota_key);
> +             return err;
> +     }
>  
> -     err = key_instantiate_and_link(mk_user, NULL, 0, mk->mk_users, NULL);
> -     key_put(mk_user);
> -     return err;
> +     mk_user = kzalloc_obj(*mk_user);
> +     if (!mk_user) {
> +             key_put(quota_key);
> +             return -ENOMEM;
> +     }
> +     mk_user->uid = uid;
> +     mk_user->quota_key = quota_key;
> +     list_add(&mk_user->link, &mk->mk_users);
> +     return 0;
> +}
> +
> +static void unlink_and_free_mk_user(struct fscrypt_master_key_user *mk_user)
> +{
> +     list_del(&mk_user->link);
> +     key_put(mk_user->quota_key);
> +     kfree(mk_user);
>  }
>  
>  /*
> - * Remove the current user's "key" from ->mk_users.
> + * Remove the current user's claim from ->mk_users.
>   * ->mk_sem must be held for write.
>   *
> - * Returns 0 if removed, -ENOKEY if not found, or another -errno code.
> + * Returns 0 if removed or -ENOKEY if not found.
>   */
>  static int remove_master_key_user(struct fscrypt_master_key *mk)
>  {
> -     struct key *mk_user;
> -     int err;
> +     struct fscrypt_master_key_user *mk_user;
>  
>       mk_user = find_master_key_user(mk);
> -     if (IS_ERR(mk_user))
> -             return PTR_ERR(mk_user);
> -     err = key_unlink(mk->mk_users, mk_user);
> -     key_put(mk_user);
> -     return err;
> +     if (!mk_user)
> +             return -ENOKEY;
> +     unlink_and_free_mk_user(mk_user);
> +     return 0;
> +}
> +
> +/*
> + * Clear ->mk_users.  Either ->mk_sem must be held for write, or 'mk' must 
> have
> + * no structural references left.
> + */
> +static void clear_mk_users(struct fscrypt_master_key *mk)
> +{
> +     struct fscrypt_master_key_user *mk_user, *tmp;
> +
> +     list_for_each_entry_safe(mk_user, tmp, &mk->mk_users, link)
> +             unlink_and_free_mk_user(mk_user);
>  }
>  
>  /*
>   * Allocate a new fscrypt_master_key, transfer the given secret over to it, 
> and
>   * insert it into sb->s_master_keys.
> @@ -443,19 +422,18 @@ static int add_new_master_key(struct super_block *sb,
>  
>       init_rwsem(&mk->mk_sem);
>       refcount_set(&mk->mk_struct_refs, 1);
>       mk->mk_spec = *mk_spec;
>  
> +     INIT_LIST_HEAD(&mk->mk_users);
> +
>       INIT_LIST_HEAD(&mk->mk_decrypted_inodes);
>       spin_lock_init(&mk->mk_decrypted_inodes_lock);
>  
>       INIT_LIST_HEAD(&mk->mk_mode_keys);
>  
>       if (mk_spec->type == FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER) {
> -             err = allocate_master_key_users_keyring(mk);
> -             if (err)
> -                     goto out_put;
>               err = add_master_key_user(mk);
>               if (err)
>                       goto out_put;
>       }
>  
> @@ -480,23 +458,17 @@ static int add_existing_master_key(struct 
> fscrypt_master_key *mk,
>                                  struct fscrypt_master_key_secret *secret)
>  {
>       int err;
>  
>       /*
> -      * If the current user is already in ->mk_users, then there's nothing to
> -      * do.  Otherwise, we need to add the user to ->mk_users.  (Neither is
> -      * applicable for v1 policy keys, which have NULL ->mk_users.)
> +      * For v2 policy keys (FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER): If the current
> +      * user is already in ->mk_users, then there's nothing to do.
> +      * Otherwise, add the user to ->mk_users.
>        */
> -     if (mk->mk_users) {
> -             struct key *mk_user = find_master_key_user(mk);
> -
> -             if (mk_user != ERR_PTR(-ENOKEY)) {
> -                     if (IS_ERR(mk_user))
> -                             return PTR_ERR(mk_user);
> -                     key_put(mk_user);
> +     if (mk->mk_spec.type == FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER) {
> +             if (find_master_key_user(mk) != NULL)
>                       return 0;
> -             }
>               err = add_master_key_user(mk);
>               if (err)
>                       return err;
>       }
>  
> @@ -890,11 +862,10 @@ int fscrypt_add_test_dummy_key(struct super_block *sb,
>  int fscrypt_verify_key_added(struct super_block *sb,
>                            const u8 identifier[FSCRYPT_KEY_IDENTIFIER_SIZE])
>  {
>       struct fscrypt_key_specifier mk_spec;
>       struct fscrypt_master_key *mk;
> -     struct key *mk_user;
>       int err;
>  
>       mk_spec.type = FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER;
>       memcpy(mk_spec.u.identifier, identifier, FSCRYPT_KEY_IDENTIFIER_SIZE);
>  
> @@ -902,17 +873,14 @@ int fscrypt_verify_key_added(struct super_block *sb,
>       if (!mk) {
>               err = -ENOKEY;
>               goto out;
>       }
>       down_read(&mk->mk_sem);
> -     mk_user = find_master_key_user(mk);
> -     if (IS_ERR(mk_user)) {
> -             err = PTR_ERR(mk_user);
> -     } else {
> -             key_put(mk_user);
> +     if (find_master_key_user(mk) != NULL)
>               err = 0;
> -     }
> +     else
> +             err = -ENOKEY;
>       up_read(&mk->mk_sem);
>       fscrypt_put_master_key(mk);
>  out:
>       if (err == -ENOKEY && capable(CAP_FOWNER))
>               err = 0;
> @@ -1100,20 +1068,22 @@ static int do_remove_key(struct file *filp, void 
> __user *_uarg, bool all_users)
>       if (!mk)
>               return -ENOKEY;
>       down_write(&mk->mk_sem);
>  
>       /* If relevant, remove current user's (or all users) claim to the key */
> -     if (mk->mk_users && mk->mk_users->keys.nr_leaves_on_tree != 0) {
> -             if (all_users)
> -                     err = keyring_clear(mk->mk_users);
> -             else
> +     if (!list_empty(&mk->mk_users)) {
> +             if (all_users) {
> +                     clear_mk_users(mk);
> +                     err = 0;
> +             } else {
>                       err = remove_master_key_user(mk);
> +             }
>               if (err) {
>                       up_write(&mk->mk_sem);
>                       goto out_put_key;
>               }
> -             if (mk->mk_users->keys.nr_leaves_on_tree != 0) {
> +             if (!list_empty(&mk->mk_users)) {
>                       /*
>                        * Other users have still added the key too.  We removed
>                        * the current user's claim to the key, but we still
>                        * can't remove the key itself.
>                        */
> @@ -1195,10 +1165,12 @@ EXPORT_SYMBOL_GPL(fscrypt_ioctl_remove_key_all_users);
>  int fscrypt_ioctl_get_key_status(struct file *filp, void __user *uarg)
>  {
>       struct super_block *sb = file_inode(filp)->i_sb;
>       struct fscrypt_get_key_status_arg arg;
>       struct fscrypt_master_key *mk;
> +     kuid_t uid;
> +     const struct fscrypt_master_key_user *mk_user;
>       int err;
>  
>       if (copy_from_user(&arg, uarg, sizeof(arg)))
>               return -EFAULT;
>  
> @@ -1227,23 +1199,17 @@ int fscrypt_ioctl_get_key_status(struct file *filp, 
> void __user *uarg)
>               err = 0;
>               goto out_release_key;
>       }
>  
>       arg.status = FSCRYPT_KEY_STATUS_PRESENT;
> -     if (mk->mk_users) {
> -             struct key *mk_user;
>  
> -             arg.user_count = mk->mk_users->keys.nr_leaves_on_tree;
> -             mk_user = find_master_key_user(mk);
> -             if (!IS_ERR(mk_user)) {
> +     uid = current_fsuid();
> +     list_for_each_entry(mk_user, &mk->mk_users, link) {
> +             arg.user_count++;
> +             if (uid_eq(mk_user->uid, uid))
>                       arg.status_flags |=
>                               FSCRYPT_KEY_STATUS_FLAG_ADDED_BY_SELF;
> -                     key_put(mk_user);
> -             } else if (mk_user != ERR_PTR(-ENOKEY)) {
> -                     err = PTR_ERR(mk_user);
> -                     goto out_release_key;
> -             }
>       }
>       err = 0;
>  out_release_key:
>       up_read(&mk->mk_sem);
>       fscrypt_put_master_key(mk);
>
> base-commit: 83f1454877cc292b88baf13c829c16ce6937d120
> prerequisite-patch-id: 319d2891e88c7df1ebb5ebf434d18b68f770399f
> prerequisite-patch-id: 5330c9e4b65644baae81bd177a46be6223d2b494
> -- 
> 2.54.0
>


Reply via email to