Commit message: Fix S4U2Self KDC crash when anon is restricted

In validate_as_request(), when enforcing restrict_anonymous_to_tgt,
use client.princ instead of request->client; the latter is NULL when
validating S4U2Self requests.

CVE-2016-3120:

In MIT krb5 1.9 and later, an authenticated attacker can cause krb5kdc
to dereference a null pointer if the restrict_anonymous_to_tgt option
is set to true, by making an S4U2Self request.

  CVSSv2 Vector: AV:N/AC:H/Au:S/C:N/I:N/A:C/E:H/RL:OF/RC:C

ticket: 8458 (new)
target_version: 1.14-next
target_version: 1.13-next
CVE: CVE-2016-3120
CWE: CWE-476
Vulnerability description: The validate_as_request function in kdc_util.c in the Key Distribution Center (KDC) in MIT Kerberos 5 (aka krb5) before 1.13.6 and 1.4.x before 1.14.3, when restrict_anonymous_to_tgt is enabled, uses an incorrect client data structure, which allows remote authenticated users to cause a denial of service (NULL pointer dereference and daemon crash) via an S4U2Self request.

Project: krb5
File: kdc_util.c

dbentry_supports_enctype(kdc_realm_t *kdc_active_realm, krb5_db_entry *server,
                         krb5_enctype enctype)
{
    krb5_error_code     retval;
    krb5_key_data       *datap;
    char                *etypes_str = NULL;
    krb5_enctype        default_enctypes[1] = { 0 };
    krb5_enctype        *etypes = NULL;
    krb5_boolean        in_list;

    /* Look up the supported session key enctypes list in the KDB. */
    retval = krb5_dbe_get_string(kdc_context, server,
                                 KRB5_KDB_SK_SESSION_ENCTYPES,
                                 &etypes_str);
    if (retval == 0 && etypes_str != NULL && *etypes_str != '\0') {
        /* Pass a fake profile key for tracing of unrecognized tokens. */
        retval = krb5int_parse_enctype_list(kdc_context, "KDB-session_etypes",
                                            etypes_str, default_enctypes,
                                            &etypes);
        if (retval == 0 && etypes != NULL && etypes[0]) {
            in_list = k5_etypes_contains(etypes, enctype);
            free(etypes_str);
            free(etypes);
            return in_list;
        }
        /* Fall through on error or empty list */
    }
    free(etypes_str);
    free(etypes);

    /* If configured to, assume every server without a session_enctypes
     * attribute supports DES_CBC_CRC. */
    if (kdc_active_realm->realm_assume_des_crc_sess &&
        enctype == ENCTYPE_DES_CBC_CRC)
        return TRUE;

    /* Due to an ancient interop problem, assume nothing supports des-cbc-md5
     * unless there's a session_enctypes explicitly saying that it does. */
    if (enctype == ENCTYPE_DES_CBC_MD5)
        return FALSE;

    /* Assume the server supports any enctype it has a long-term key for. */
    return !krb5_dbe_find_enctype(kdc_context, server, enctype, -1, 0, &datap);
}

Project: krb5
File: kdc_util.c

errcode_to_protocol(krb5_error_code code)
{
    int protcode;

    protcode = code - ERROR_TABLE_BASE_krb5;
    return (protcode >= 0 && protcode <= 128) ? protcode : KRB_ERR_GENERIC;
}

Project: krb5
File: kdc_util.c

find_server_key(krb5_context context,
                krb5_db_entry *server, krb5_enctype enctype, krb5_kvno kvno,
                krb5_keyblock **key_out, krb5_kvno *kvno_out)
{
    krb5_error_code       retval;
    krb5_key_data       * server_key;
    krb5_keyblock       * key;

    *key_out = NULL;
    retval = krb5_dbe_find_enctype(context, server, enctype, -1,
                                   kvno ? (krb5_int32)kvno : -1, &server_key);
    if (retval)
        return retval;
    if (!server_key)
        return KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
    if ((key = (krb5_keyblock *)malloc(sizeof *key)) == NULL)
        return ENOMEM;
    retval = krb5_dbe_decrypt_key_data(context, NULL, server_key,
                                       key, NULL);
    if (retval)
        goto errout;
    if (enctype != -1) {
        krb5_boolean similar;
        retval = krb5_c_enctype_compare(context, enctype, key->enctype,
                                        &similar);
        if (retval)
            goto errout;
        if (!similar) {
            retval = KRB5_KDB_NO_PERMITTED_KEY;
            goto errout;
        }
        key->enctype = enctype;
    }
    *key_out = key;
    key = NULL;
    if (kvno_out)
        *kvno_out = server_key->key_data_kvno;
errout:
    krb5_free_keyblock(context, key);
    return retval;
}

Project: krb5
File: kdc_util.c

kdc_process_s4u2self_req(kdc_realm_t *kdc_active_realm,
                         krb5_kdc_req *request,
                         krb5_const_principal client_princ,
                         const krb5_db_entry *server,
                         krb5_keyblock *tgs_subkey,
                         krb5_keyblock *tgs_session,
                         krb5_timestamp kdc_time,
                         krb5_pa_s4u_x509_user **s4u_x509_user,
                         krb5_db_entry **princ_ptr,
                         const char **status)
{
    krb5_error_code             code;
    krb5_pa_data                *pa_data;
    int                         flags;
    krb5_db_entry               *princ;

    *princ_ptr = NULL;

    pa_data = krb5int_find_pa_data(kdc_context,
                                   request->padata, KRB5_PADATA_S4U_X509_USER);
    if (pa_data != NULL) {
        code = kdc_process_s4u_x509_user(kdc_context,
                                         request,
                                         pa_data,
                                         tgs_subkey,
                                         tgs_session,
                                         s4u_x509_user,
                                         status);
        if (code != 0)
            return code;
    } else {
        pa_data = krb5int_find_pa_data(kdc_context,
                                       request->padata, KRB5_PADATA_FOR_USER);
        if (pa_data != NULL) {
            code = kdc_process_for_user(kdc_active_realm,
                                        pa_data,
                                        tgs_session,
                                        s4u_x509_user,
                                        status);
            if (code != 0)
                return code;
        } else
            return 0;
    }

    /*
     * We need to compare the client name in the TGT with the requested
     * server name. Supporting server name aliases without assuming a
     * global name service makes this difficult to do.
     *
     * The comparison below handles the following cases (note that the
     * term "principal name" below excludes the realm).
     *
     * (1) The requested service is a host-based service with two name
     *     components, in which case we assume the principal name to
     *     contain sufficient qualifying information. The realm is
     *     ignored for the purpose of comparison.
     *
     * (2) The requested service name is an enterprise principal name:
     *     the service principal name is compared with the unparsed
     *     form of the client name (including its realm).
     *
     * (3) The requested service is some other name type: an exact
     *     match is required.
     *
     * An alternative would be to look up the server once again with
     * FLAG_CANONICALIZE | FLAG_CLIENT_REFERRALS_ONLY set, do an exact
     * match between the returned name and client_princ. However, this
     * assumes that the client set FLAG_CANONICALIZE when requesting
     * the TGT and that we have a global name service.
     */
    flags = 0;
    switch (krb5_princ_type(kdc_context, request->server)) {
    case KRB5_NT_SRV_HST:                   /* (1) */
        if (krb5_princ_size(kdc_context, request->server) == 2)
            flags |= KRB5_PRINCIPAL_COMPARE_IGNORE_REALM;
        break;
    case KRB5_NT_ENTERPRISE_PRINCIPAL:      /* (2) */
        flags |= KRB5_PRINCIPAL_COMPARE_ENTERPRISE;
        break;
    default:                                /* (3) */
        break;
    }

    if (!krb5_principal_compare_flags(kdc_context,
                                      request->server,
                                      client_princ,
                                      flags)) {
        *status = "INVALID_S4U2SELF_REQUEST";
        return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; /* match Windows error code */
    }

    /*
     * Protocol transition is mutually exclusive with renew/forward/etc
     * as well as user-to-user and constrained delegation. This check
     * is also made in validate_as_request().
     *
     * We can assert from this check that the header ticket was a TGT, as
     * that is validated previously in validate_tgs_request().
     */
    if (request->kdc_options & AS_INVALID_OPTIONS) {
        *status = "INVALID AS OPTIONS";
        return KRB5KDC_ERR_BADOPTION;
    }

    /*
     * Do not attempt to lookup principals in foreign realms.
     */
    if (is_local_principal(kdc_active_realm,
                           (*s4u_x509_user)->user_id.user)) {
        krb5_db_entry no_server;
        krb5_pa_data **e_data = NULL;

        code = krb5_db_get_principal(kdc_context,
                                     (*s4u_x509_user)->user_id.user,
                                     KRB5_KDB_FLAG_INCLUDE_PAC, &princ);
        if (code == KRB5_KDB_NOENTRY) {
            *status = "UNKNOWN_S4U2SELF_PRINCIPAL";
            return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
        } else if (code) {
            *status = "LOOKING_UP_S4U2SELF_PRINCIPAL";
            return code; /* caller can free for_user */
        }

        memset(&no_server, 0, sizeof(no_server));

        code = validate_as_request(kdc_active_realm, request, *princ,
                                   no_server, kdc_time, status, &e_data);
        if (code) {
            krb5_db_free_principal(kdc_context, princ);
            krb5_free_pa_data(kdc_context, e_data);
            return code;
        }

        *princ_ptr = princ;
    }

    return 0;
}

Project: krb5
File: kdc_util.c

void limit_string(char *name)
{
    int     i;

    if (!name)
        return;

    if (strlen(name) < NAME_LENGTH_LIMIT)
        return;

    i = NAME_LENGTH_LIMIT-4;
    name[i++] = '.';
    name[i++] = '.';
    name[i++] = '.';
    name[i] = '\0';
    return;
}