Skip to content

gp_creds: fix use-after-free in gp_count_tickets() - #133

Open
prabhakarpujeri wants to merge 1 commit into
gssapi:mainfrom
prabhakarpujeri:fixes
Open

gp_creds: fix use-after-free in gp_count_tickets()#133
prabhakarpujeri wants to merge 1 commit into
gssapi:mainfrom
prabhakarpujeri:fixes

Conversation

@prabhakarpujeri

Copy link
Copy Markdown

The counting loop in gp_count_tickets() calls krb5_free_cred_contents(&creds) and increments the counter even when krb5_cc_next_cred() has already failed — including KRB5_CC_END, where it then continues iterating.

  • Empty ccache: free of an uninitialized krb5_creds.
  • Otherwise: the last credential's contents get freed a second time, and *ccsum comes out one too high.

Only free and count after a successful fetch, and leave the loop at KRB5_CC_END.

Built clean with --disable-public-libraries etc. default options (-Wall -Wextra quiet), and the counting behavior was traced for the empty-ccache, zero-ticket and multi-ticket cases against the MIT Kerberos semantics of krb5_cc_next_cred() (KRB5_CC_END is the normal end-of-cache signal and must not count nor free).

@simo5 simo5 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor nitpick but otherwise ok

Comment thread src/gp_creds.c Outdated
}

do {
for (;;) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we are going to switch to a for loop, then please use:

for (err = 0; err == 0;
     err = krb5_cc_next_cred(context, ccache, &cursor, &creds)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in fact we can actually write this as:

for (err = 0; err == 0;
     err = krb5_cc_next_cred(context, ccache, &cursor, &creds)) {
    if (err == 0) {
        krb5_free_cred_contents(context, &creds);
        (*ccsum)++;
    }
}
if (err != KRB5_CC_END) {
    ret_min = err;
    ret_maj = GSS_S_FAILURE;
    goto done;
}

^ This is my preferred and more understandable form.

@prabhakarpujeri

Copy link
Copy Markdown
Author

Thanks Simo — applied exactly that form in e45faca (amended): the loop initializer/step drives krb5_cc_next_cred(), the body only frees+counts on success, and the KRB5_CC_END / error check sits after the loop with the existing TODO comment kept. Clean build with -Wall -Wextra (warnings-as-if-clean).

@simo5

simo5 commented Sep 3, 2026

Copy link
Copy Markdown
Member

Looks like this change still has some issues, please test locally

@prabhakarpujeri

Copy link
Copy Markdown
Author

Thanks for the second look — the remaining defects were real. Amend ccf6ae7 fixes both:

  1. Dead branch: dropped the if (err == 0) inside the loop; the while condition now performs the iterator call directly (while ((err = krb5_cc_next_cred(...)) == 0)), so the body only runs on a filled creds buffer.
  2. Cursor leak on the iteration-error path: the non-END branch now calls krb5_cc_end_seq_get() before goto done. (The original code had the same leak; careless of me to carry it over.)

Local verification (no krb5kdc on this box, so make check is out — instead I extracted the loop into a standalone harness with scripted libkrb5 stubs):

  • original pattern: UAF fires on every terminator (frees=3 for 2 creds, ccsum=3 off-by-one; frees=1/ccsum=1 on an empty ccache)
  • fixed pattern: exact counts (2/0), no stale frees, cursor terminated on every path, including mid-iteration error (rc surfaces, end_seq called: pass)
  • full make with the project toolchain (-Wall -Wextra -Werror-class flags): clean, zero findings on gp_creds.c

The original do-while pattern calls krb5_free_cred_contents() on the
krb5_creds buffer even when krb5_cc_next_cred() reports KRB5_CC_END,
i.e. after the iterator already returned without writing the buffer:
the terminal iteration frees either freed or stale contents.

Reshape as a while loop whose condition performs the iterator call so
the body only runs on success, keep error handling after the loop, and
terminate the cursor on the mid-iteration error path (the earlier
goto-done skipped krb5_cc_end_seq_get, leaking the cursor).

Signed-off-by: Prabhakar Pujeri <prabhakar.pujeri@dell.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants