Thread (4 messages) 4 messages, 3 authors, 1d ago

Re: [PATCH v2] keys: Protect the update method check against type removal

flat view

From: Cen Zhang <hidden>
Date: 2026-10-31 09:17:34
Also in: keyrings, lkml

Hi Jarkko,

Jarkko Sakkinen [off-list ref] 于2026年10月30日周五 04:20写道:
On Fri, Oct 30, 2026 at 12:07:08AM +0800, Cen Zhang wrote:
quoted
key_update() reads key->type->update, ->def_datalen and ->preparse
before it takes key->sem. A reference on the key does not pin the
module that provides its type, so KEYCTL_UPDATE can race with the
unloading of that module, for example dns_resolver:

  KEYCTL_UPDATE task                  Module unload and key GC
  ------------------                  ------------------------
  keyctl_update_key()
    key_update()
      key_permission()
      load key->type
                                      exit_dns_resolver()
                                        unregister_key_type()
                                          GC: down_write(&key->sem)
                                          GC: retype to key_type_dead
                                          GC: up_write(&key->sem)
                                        unregister_key_type() returns
                                      free_module()
      read saved type's update member

The final access reads freed module memory. With the updating task
held after the key->type load, it faults:

  BUG: unable to handle page fault for address: fffffbfff804107f
  #PF: supervisor read access in kernel mode
  RIP: 0010:key_update+0x12d/0x550
  Call Trace:
   keyctl_update_key+0x6b/0x110
   __do_sys_keyctl+0x21d/0x4f0

This is the same problem that commit 925c84e3cc21 ("keys: Protect the
read method check against type removal") fixed for KEYCTL_READ.

For dns_resolver, which has no update method, key_update() returns at
the check, so doing the check with key->sem held would be enough. A
key type that is built as a module and has an update method, like
encrypted or trusted, gets past the check. If its module is unloaded
after the check and before key_update() takes key->sem, the GC has
already moved the key to key_type_dead, and key_update() calls the
update method of key_type_dead, which is NULL. So key->sem has to stay
held from the check until the update is done.

Take key->sem before the first access to key->type and hold it until
the preparsed payload has been freed. Once a key exists, only the GC
changes key->type, and it does so with key->sem held for writing.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Cen Zhang <redacted>
---
Changes in v2:
- Refine the commit message.
- Remove the code comment in key_update().
- Drop the Assisted-by tag.

Link to v1: https://lore.kernel.org/r/pm-keys-update-type-removal-20261029-v6-3d92b2f9693854230a02@gmail.com (local)

 security/keys/key.c | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/security/keys/key.c b/security/keys/key.c
index e88548f1a704..994a34a80c29 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -1101,9 +1101,12 @@ int key_update(key_ref_t key_ref, const void *payload, size_t plen)
      if (ret < 0)
              return ret;

+     down_write(&key->sem);
+
      /* attempt to update it if supported */
+     ret = -EOPNOTSUPP;
      if (!key->type->update)
-             return -EOPNOTSUPP;
+             goto out;

      memset(&prep, 0, sizeof(prep));
      prep.data = payload;
@@ -1116,8 +1119,6 @@ int key_update(key_ref_t key_ref, const void *payload, size_t plen)
                      goto error;
      }

-     down_write(&key->sem);
-
      ret = key->type->update(key, &prep);
      if (ret == 0) {
              /* Updating a negative key positively instantiates it */
@@ -1125,11 +1126,11 @@ int key_update(key_ref_t key_ref, const void *payload, size_t plen)
              notify_key(key, NOTIFY_KEY_UPDATED, 0);
      }

-     up_write(&key->sem);
-
 error:
      if (key->type->preparse)
              key->type->free_preparse(&prep);
Hmm.. a nitpick but here should be empty line, or at is least common
to have that before the next label.
quoted
+out:
+     up_write(&key->sem);
And also these labels don't have coherency in naming. Not claiming
there is overall but it would be good to start improving the
situation.

So I think this would add more clarity:

out_free_preparse:
        if (key->type->preparse)
                key->type->free_preparse(&prep);

out:
        up_write(&key->sem);
        return ret;
}
EXPORT_SYMBOL(key_update);

And is more aligned with our defacto conventions.
Thanks. I'll refine them in v3.

Best regards,
Cen Zhang
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help