xorl %eax, %eax

CVE-2009-3295: MIT Kerberos 5 KDC Remote NULL Pointer Dereference

leave a comment »

On 28 December 2009, MIT Kerberos Team released this advisory. The issue affects MIT Kerberos 5 releases 1.7 before 1.7.1 and it can be triggered through a ticket request remotely. As the advisory explains, this vulnerability was independently discovered by Jeff Blaine, Radoslav Bodo, Jakob Haufe and Jorgen Wahlsten. Here is the susceptible code found in src/kdc/do_tgs_req.c and specifically, in the function below…

static krb5_int32
prep_reprocess_req(krb5_kdc_req *request, krb5_principal *krbtgt_princ) 
{
    krb5_error_code retval = KRB5KRB_AP_ERR_BADMATCH;
    size_t len = 0;
    char **realms, **cpp, *temp_buf=NULL;
    krb5_data *comp1 = NULL, *comp2 = NULL; 
    char *comp1_str = NULL; 
     ...
        comp2 = krb5_princ_component(kdc_context, request->server, 1);
     ...
            strlcpy(temp_buf, comp2->data,comp2->length+1);
            retval = krb5int_get_domain_realm_mapping(kdc_context, temp_buf, &realms);
            free(temp_buf);
            if (retval) {
                /* no match found */
                kdc_err(kdc_context, retval, 0);
                goto cleanup;
            }
     ...
cleanup:
    free(comp1_str);
    return retval;
}

What we can see here is that strlcpy(3) is used to copy ‘comp2->data’ which is the first element of the principal retrived using krb5_princ_component() and then krb5int_get_domain_realm_mapping() routine is being called. This function resides in src/lib/krb5/os/def_realm.c and it is used to fetch the domain realm mapping as it is implied by its name. If it returns a non-zero value it will call kdc_err() as you can see in the above routine. However, if we have a quick look at src/kdc/main.c we can easily notice the following:

/*
 * We use krb5_klog_init to set up a com_err callback to log error
 * messages.  The callback also pulls the error message out of the
 * context we pass to krb5_klog_init; however, we use realm-specific
 * contexts for most of our krb5 library calls, so the error message
 * isn't present in the global context.  This wrapper ensures that the
 * error message state from the call context is copied into the
 * context known by krb5_klog.  call_context can be NULL if the error
 * code did not come from a krb5 library function.
 */
void
kdc_err(krb5_context call_context, errcode_t code, const char *fmt, ...)
{
    va_list ap;

    if (call_context)
	krb5_copy_error_message(kdc_err_context, call_context);
    va_start(ap, fmt);
    com_err_va(kdc_progname, code, fmt, ap);
    va_end(ap);
}

This means that com_err_va() will be invoked with a ‘fmt’ pointer which is NULL since prep_reprocess_req() called it with a zero third argument. This is an exported routine from src/lib/kadm5/logger.c library which will lead to this code:

static void
klog_com_err_proc(const char *whoami, long int code, const char *format, va_list ap)
{
    char	outbuf[KRB5_KLOG_MAX_ERRMSG_SIZE];
    int		lindex;
    const char	*actual_format;
#ifdef	HAVE_SYSLOG
    int		log_pri = -1;
#endif	/* HAVE_SYSLOG */
    char	*cp;
    char	*syslogp;

    /* Make the header */
    snprintf(outbuf, sizeof(outbuf), "%s: ", whoami);
      ...
    actual_format = format;
#ifdef	HAVE_SYSLOG
    /*
     * This is an unpleasant hack.  If the first character is less than
     * 8, then we assume that it is a priority.
     *
     * Since it is not guaranteed that there is a direct mapping between
     * syslog priorities (e.g. Ultrix and old BSD), we resort to this
     * intermediate representation.
     */
    if ((((unsigned char) *format) > 0) && (((unsigned char) *format) <= 8)) {
	actual_format = (format + 1);
	switch ((unsigned char) *format) {
      ...
#endif	/* HAVE_SYSLOG */

    /* Now format the actual message */
    vsnprintf(cp, sizeof(outbuf) - (cp - outbuf), actual_format, ap);
      ...
    }
}

So it’s not really difficult to imagine what will happen on an error situation since ‘format’ is a NULL pointer. To fix this, the kdc_err() call was changed to include some error message like this:

             if (retval) {
                 /* no match found */
-                kdc_err(kdc_context, retval, 0);
+                kdc_err(kdc_context, retval, "unable to find realm of host");
                 goto cleanup;
             }

And the klog_com_err_proc() logging routine was also updated to be able to handle such cases as you can read in the diff file here:

     char	*syslogp;
 
+    if (whoami == NULL || format == NULL)
+        return;
+
     /* Make the header */

So, in order to trigger this vulnerability the attacker must make krb5int_get_domain_realm_mapping() return a non-zero value which should not be really difficult since it has many possible code paths in it…

krb5_error_code
krb5int_get_domain_realm_mapping(krb5_context context, const char *host, char ***realmsp)
{
      ...
    /* do sanity check and lower-case */
    retval = krb5int_clean_hostname(context, host, temp_host, sizeof temp_host);
    if (retval)
        return retval;
      ...
        retval = profile_get_string(context->profile, KRB5_CONF_DOMAIN_REALM, cp,
                                    0, (char *)NULL, &temp_realm);
        if (retval)
            return retval;
      ...
    if (temp_realm != (char*)NULL) {
        realm = strdup(temp_realm);
        profile_release_string(temp_realm);
        if (!realm) {
            return ENOMEM;
        }
    }
    retrealms = (char **)calloc(2, sizeof(*retrealms));
    if (!retrealms) {
        if (realm != (char *)NULL)
            free(realm);
        return ENOMEM;
    }
      ...
}

For further research, here is a useful email from krbdev mailing list…

Written by xorl

January 1, 2010 at 08:47

Posted in bugs

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s