KDC null deref due to referrals [CVE-2013-1417]

An authenticated remote client can cause a KDC to crash by making a
valid TGS-REQ to a KDC serving a realm with a single-component name.
The process_tgs_req() function dereferences a null pointer because an
unusual failure condition causes a helper function to return success.

While attempting to provide cross-realm referrals for host-based
service principals, the find_referral_tgs() function could return a
TGS principal for a zero-length realm name (indicating that the
hostname in the service principal has no known realm associated with
it).

Subsequently, the find_alternate_tgs() function would attempt to
construct a path to this empty-string realm, and return success along
with a null pointer in its output parameter.  This happens because
krb5_walk_realm_tree() returns a list of length one when it attempts
to construct a transit path between a single-component realm and the
empty-string realm.  This list causes a loop in find_alternate_tgs()
to iterate over zero elements, resulting in the unexpected output of a
null pointer, which process_tgs_req() proceeds to dereference because
there is no error condition.

Add an error condition to find_referral_tgs() when
krb5_get_host_realm() returns an empty realm name.  Also add an error
condition to find_alternate_tgs() to handle the length-one output from
krb5_walk_realm_tree().

The vulnerable configuration is not likely to arise in practice.
(Realm names that have a single component are likely to be test
realms.)  Releases prior to krb5-1.11 are not vulnerable.

Thanks to Sol Jerome for reporting this problem.

CVSSv2: AV:N/AC:M/Au:S/C:N/I:N/A:P/E:H/RL:O/RC:C

(cherry picked from commit 3c7f1c21ffaaf6c90f1045f0f5440303c766acc0)

ticket: 7668
version_fixed: 1.11.4
status: resolved
prepare_error_tgs (struct kdc_request_state *state,
                   krb5_kdc_req *request, krb5_ticket *ticket, int error,
                   krb5_principal canon_server,
                   krb5_data **response, const char *status,
                   krb5_pa_data **e_data)
{
    krb5_error errpkt;
    krb5_error_code retval = 0;
    krb5_data *scratch, *e_data_asn1 = NULL, *fast_edata = NULL;
    kdc_realm_t *kdc_active_realm = state->realm_data;

    errpkt.ctime = request->nonce;
    errpkt.cusec = 0;

    if ((retval = krb5_us_timeofday(kdc_context, &errpkt.stime,
                                    &errpkt.susec)))
        return(retval);
    errpkt.error = error;
    errpkt.server = request->server;
    if (ticket && ticket->enc_part2)
        errpkt.client = ticket->enc_part2->client;
    else
        errpkt.client = NULL;
    errpkt.text.length = strlen(status);
    if (!(errpkt.text.data = strdup(status)))
        return ENOMEM;

    if (!(scratch = (krb5_data *)malloc(sizeof(*scratch)))) {
        free(errpkt.text.data);
        return ENOMEM;
    }

    if (e_data != NULL) {
        retval = encode_krb5_padata_sequence(e_data, &e_data_asn1);
        if (retval) {
            free(scratch);
            free(errpkt.text.data);
            return retval;
        }
        errpkt.e_data = *e_data_asn1;
    } else
        errpkt.e_data = empty_data();

    if (state) {
        retval = kdc_fast_handle_error(kdc_context, state, request, e_data,
                                       &errpkt, &fast_edata);
    }
    if (retval) {
        free(scratch);
        free(errpkt.text.data);
        krb5_free_data(kdc_context, e_data_asn1);
        return retval;
    }
    if (fast_edata)
        errpkt.e_data = *fast_edata;
    retval = krb5_mk_error(kdc_context, &errpkt, scratch);
    free(errpkt.text.data);
    krb5_free_data(kdc_context, e_data_asn1);
    krb5_free_data(kdc_context, fast_edata);
    if (retval)
        free(scratch);
    else
        *response = scratch;

    return retval;
}

prepare_error_tgs (struct kdc_request_state *state,
                   krb5_kdc_req *request, krb5_ticket *ticket, int error,
                   krb5_principal canon_server,
                   krb5_data **response, const char *status,
                   krb5_pa_data **e_data)
{
    krb5_error errpkt;
    krb5_error_code retval = 0;
    krb5_data *scratch, *e_data_asn1 = NULL, *fast_edata = NULL;
    kdc_realm_t *kdc_active_realm = state->realm_data;

    errpkt.ctime = request->nonce;
    errpkt.cusec = 0;

    if ((retval = krb5_us_timeofday(kdc_context, &errpkt.stime,
                                    &errpkt.susec)))
        return(retval);
    errpkt.error = error;
    errpkt.server = request->server;
    if (ticket && ticket->enc_part2)
        errpkt.client = ticket->enc_part2->client;
    else
        errpkt.client = NULL;
    errpkt.text.length = strlen(status);
    if (!(errpkt.text.data = strdup(status)))
        return ENOMEM;

    if (!(scratch = (krb5_data *)malloc(sizeof(*scratch)))) {
        free(errpkt.text.data);
        return ENOMEM;
    }

    if (e_data != NULL) {
        retval = encode_krb5_padata_sequence(e_data, &e_data_asn1);
        if (retval) {
            free(scratch);
            free(errpkt.text.data);
            return retval;
        }
        errpkt.e_data = *e_data_asn1;
    } else
        errpkt.e_data = empty_data();

    if (state) {
        retval = kdc_fast_handle_error(kdc_context, state, request, e_data,
                                       &errpkt, &fast_edata);
    }
    if (retval) {
        free(scratch);
        free(errpkt.text.data);
        krb5_free_data(kdc_context, e_data_asn1);
        return retval;
    }
    if (fast_edata)
        errpkt.e_data = *fast_edata;
    retval = krb5_mk_error(kdc_context, &errpkt, scratch);
    free(errpkt.text.data);
    krb5_free_data(kdc_context, e_data_asn1);
    krb5_free_data(kdc_context, fast_edata);
    if (retval)
        free(scratch);
    else
        *response = scratch;

    return retval;
}
