Re: [PATCH] KEYS: do not let an unmatched key decide the search result

From: Guixiong Wei

Date: Sun Nov 08 2026 - 20:10:23 EST



> From: "Jarkko Sakkinen"<jarkko@xxxxxxxxxx>
> Date:  Mon, Nov 9, 2026, 03:01
> Subject:  Re: [PATCH] KEYS: do not let an unmatched key decide the search result
> To: "Guixiong Wei"<weiguixiong@xxxxxxxxxxxxx>
> Cc: "David Howells"<dhowells@xxxxxxxxxx>, <keyrings@xxxxxxxxxxxxxxx>, "Paul Moore"<paul@xxxxxxxxxxxxxx>, "James Morris"<jmorris@xxxxxxxxx>, "Serge E . Hallyn"<serge@xxxxxxxxxx>, <linux-security-module@xxxxxxxxxxxxxxx>, <linux-kernel@xxxxxxxxxxxxxxx>
> On Sun, Nov 08, 2026 at 09:14:12PM +0800, Guixiong Wei wrote:
> > KEYCTL_SEARCH returns -ENOKEY, -EKEYREVOKED, or -EKEYEXPIRED for the key
> > selected by type and description.  keyring_search_iterator() currently
> > records revoked or expired state before asking the key type whether the key
> > matches.
> 
> I don't understand the "asking the key type or" part here. Please,
> explain what you mean.
> 
I meant calling ctx->match_data.cmp() to check whether a candidate key
matches the search request. For dns_resolver, this calls
dns_resolver_cmp() to compare the candidate's description with the
requested name. I'll make that explicit in v2.

> > 
> > Direct lookup visits the indexed description, so that ordering normally
> 
> "Direct lookup visits the indexed description"
> 
> I don't honestly understand this type of jargon, which is all over the
> place.
> 
I meant that assoc_array_find() looks up a candidate using the requested
type and description. The dns_resolver search instead walks the keys and
checks their descriptions using dns_resolver_cmp().
I'll rewrite the commit message around your foo/bar example.

> > does not matter.  Iterative lookup visits every key of the requested type,
> > however.  The in-tree asymmetric id/ex/dn lookups and dns_resolver use this
> > mode.  A revoked key with a different description can therefore make
> > KEYCTL_SEARCH return -EKEYREVOKED instead of continuing the search.  It can
> > also prevent request_key_and_link() from issuing an upcall because that
> > path only upcalls after -EAGAIN.  An unmatched expired key similarly
> > pollutes searches that do not set KEYRING_SEARCH_SKIP_EXPIRED; the request
> > path itself sets that flag.
> > 
> > Run the type comparator before recording state errors, so only a key that
> > matches the caller's criterion can decide the search result.
> 
> 
> Can you describe instead describe the what goes wrong with an example,
> which does not require much effort:
> 
>         keyctl new_session >/dev/null
>         A=$(printf '127.0.0.1\0' | keyctl padd dns_resolver foo @s)
>         keyctl revoke "$A"
>         keyctl search @s dns_resolver bar
> 
> Results:
> 
>         keyctl_search: Key has been revoked
> 
Thanks for the reproducer. The expected result is -ENOKEY because "bar"
is absent.

When the search encounters "foo", keyring_search_iterator() sets
ctx->result to -EKEYREVOKED and skips that candidate before calling
dns_resolver_cmp(). It therefore never checks whether "foo" matches
"bar".

The walk continues, but in this example it finishes with ctx->result
still set to -EKEYREVOKED. My wording "instead of continuing the search"
was inaccurate.

The patch moves the match check before the state checks, so "foo" is
skipped without changing ctx->result.

> This example alone is not of course enough because it is lacking
> explanation what goes wrong, which is neither clearly described by
> the commit message but you could add my reported-by because at
> least I constructe a real bug report that justifies this fix.
Thanks, I will add your Reported-by.

> 
> 
> > 
> > Fixes: b2a4df200d57 ("KEYS: Expand the capacity of a keyring")
> > Signed-off-by: Guixiong Wei <weiguixiong@xxxxxxxxxxxxx>
> > ---
> >  security/keys/keyring.c | 12 ++++++------
> >  1 file changed, 6 insertions(+), 6 deletions(-)
> > 
> > diff --git a/security/keys/keyring.c b/security/keys/keyring.c
> > index 7143f3079d200..39645b8f9b30c 100644
> > --- a/security/keys/keyring.c
> > +++ b/security/keys/keyring.c
> > @@ -598,6 +598,12 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
> >                  return 0;
> >          }
> >  
> > +        /* A key the caller did not ask for must not decide the result. */
> > +        if (!ctx->match_data.cmp(key, &ctx->match_data)) {
> > +                kleave(" = 0 [!match]");
> > +                return 0;
> > +        }
> > +
> >          /* skip invalidated, revoked and expired keys */
> >          if (ctx->flags & KEYRING_SEARCH_DO_STATE_CHECK) {
> >                  time64_t expiry = READ_ONCE(key->expiry);
> > @@ -617,12 +623,6 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
> >                  }
> >          }
> >  
> > -        /* keys that don't match */
> > -        if (!ctx->match_data.cmp(key, &ctx->match_data)) {
> > -                kleave(" = 0 [!match]");
> > -                return 0;
> > -        }
> > -
> >          /* key must have search permissions */
> >          if (!(ctx->flags & KEYRING_SEARCH_NO_CHECK_PERM) &&
> >              key_task_permission(make_key_ref(key, ctx->possessed),
> > -- 
> > 2.55.0
> 
> Br, Jarkko
> 

Thanks,
Guixiong