Thread safety issue in krb5_verify_init_creds() when passing ccache pointer to null?
Thomas Sondergaard
thomas at sondergaard.cc
Fri May 28 05:14:48 EDT 2021
On Thu, May 27, 2021 at 5:23 PM Greg Hudson <ghudson at mit.edu> wrote:
> On 5/27/21 4:52 AM, Thomas Sondergaard wrote:
> > krb5_ccache cc = nullptr;
> > const auto err = krb5_verify_init_creds(context, &creds,
> > server_princ, kt, &cc, &opts);
> > krb5_principal princ;
> > krb5_cc_get_principal(context, &princ);
> > ...
> > krb5_cc_close(context, cc);
>
> Out of curiosity, what does the code do with cc? This is the first time
> I've heard of a caller using the credentials acquired by
> krb5_verify_init_creds().
>
It is a little curious. I didn't write the code, so I am not completely
sure why it is the way it is. The signature of function that contains this
code is:
ResultType verifyCredentials(std::string princ_name, std::string password,
std::string keytab_path, std::string server_principal)
It calls krb5_parse_name to create a principal from princ_name and then
krb5_get_init_creds_passwords to create a krb5_creds. It then calls
krb5_verify_init_creds(). If that checks out it then calls
krb5_cc_get_principal to dig the principal out of the returned krb5_ccache
and then calls krb5_unparse_name and returns that to the caller. Is there
any situation where this returned principal will be different from the one
the caller of the function provided?
>
> > The program occasionally crashes in the call to krb5_cc_get_principal.
>
> With what version of the krb5 libraries? The issue you identified below
> is a concern, but a crash indicates a problem in the ccache layer.
> Ticket #8202 (fixed in 1.17) addresses a bug that could explain the crash.
>
It is on CentOS 8.3.2011 with krb5-libs-1.18.2-5.el8.x86_64
>
> > If I understand this correctly I get a ticket cache "MEMORY:rd_req2",
> which
> > is local to the process. So when this function is called from multiple
> > threads with ccache being a pointer with the value NULL the threads will
> > stomp on the same ticket cache. Is this correct?
>
> It sounds like it. You could of course work around this issue by either
> passing NULL if you don't need the credentials, or creating a ccache in
> the caller.
>
I'm going to to either create my own ccache - or just drop reading back the
default principal depending on the feedback I get to the question I asked
above.
> > Shouldn't the ticket cache "MEMORY:rd_req2" in get_vfy_cred() be changed
> to
> > use krb5_cc_new_unique() too?
> [...]
> > Will calling
> > just krb5_cc_close(context, cc) on a ccache created with
> > krb5_cc_new_unique() cause it to be leaked or will it automatically be
> > destroyed when all references to it have been closed?
>
> It will be leaked. (Well, the "leaked" objects would still be
> theoretically accessible via the process-global memory ccache table, but
> with names that nobody remembers.)
>
krb5_mcc_close(krb5_context context, krb5_ccache id) in
krb5/src/lib/krb5/ccache/cc_memory.c is the function that is called when
calling krb5_cc_close on a "MEMORY" type ccache, isn't it?
In krb5-1.82.2-final it looks like this:
krb5_error_code KRB5_CALLCONV
krb5_mcc_close(krb5_context context, krb5_ccache id)
{
krb5_mcc_data *d = id->data;
int count;
free(id);
k5_cc_mutex_lock(context, &d->lock);
count = --d->refcount;
k5_cc_mutex_unlock(context, &d->lock);
if (count == 0) {
/* This is the last active handle referencing d and d has been
removed
* from the table, so we can release it. */
empty_mcc_cache(context, d);
free(d->name);
k5_cc_mutex_destroy(&d->lock);
free(d);
}
return KRB5_OK;
}
When I call krb5_cc_close() on the only and therefore last handle to my
ccache created with krb5_cc_new_unique() I expect the refcount to reach 0
therefore calling empty_mcc_cache() to free the cache? So I was a little
unsure what is leaked. I also looked at krb5_mcc_destroy() in the same file
and it mentions removing the ccache from a table. Is it this empty table
entry that is leaked?
So the conclusion is that I should call krb5_cc_destroy() on the ccache
created with krb5_cc_new_unique() to get rid of it, right?
Thank you for your invaluable help!
Thomas
More information about the krbdev
mailing list