Commit message: Fix LDAP misused policy name crash [CVE-2014-5353]

In krb5_ldap_get_password_policy_from_dn, if LDAP_SEARCH returns
successfully with no results, return KRB5_KDB_NOENTRY instead of
returning success with a zeroed-out policy object.  This fixes a null
dereference when an admin attempts to use an LDAP ticket policy name
as a password policy name.

CVE-2014-5353:

In MIT krb5, when kadmind is configured to use LDAP for the KDC
database, an authenticated remote attacker can cause a NULL dereference
by attempting to use a named ticket policy object as a password policy
for a principal.  The attacker needs to be authenticated as a user who
has the elevated privilege for setting password policy by adding or
modifying principals.

Queries to LDAP scoped to the krbPwdPolicy object class will correctly
not return entries of other classes, such as ticket policy objects, but
may return success with no returned elements if an object with the
requested DN exists in a different object class.  In this case, the
routine to retrieve a password policy returned success with a password
policy object that consisted entirely of zeroed memory.  In particular,
accesses to the policy name will dereference a NULL pointer.  KDC
operation does not access the policy name field, but most kadmin
operations involving the principal with incorrect password policy
will trigger the crash.

Thanks to Patrik Kis for reporting this problem.

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

[kaduk@mit.edu: CVE description and CVSS score]

ticket: 8051 (new)
target_version: 1.13.1
tags: pullup
CVE: CVE-2014-5353
CWE: CWE-476
Vulnerability description: The krb5_ldap_get_password_policy_from_dn function in plugins/kdb/ldap/libkdb_ldap/ldap_pwd_policy.c in MIT Kerberos 5 (aka krb5) before 1.13.1, when the KDC uses LDAP, allows remote authenticated users to cause a denial of service (daemon crash) via a successful LDAP query with no results, as demonstrated by using an incorrect object type for a password policy.

Project: krb5
File: ldap_pwd_policy.c

add_policy_mods(krb5_context context, LDAPMod ***mods, osa_policy_ent_t policy,
                int op)
{
    krb5_error_code st;
    char *strval[2] = { NULL };

    st = krb5_add_int_mem_ldap_mod(mods, "krbmaxpwdlife", op,
                                   (int)policy->pw_max_life);
    if (st)
        return st;

    st = krb5_add_int_mem_ldap_mod(mods, "krbminpwdlife", op,
                                   (int)policy->pw_min_life);
    if (st)
        return st;

    st = krb5_add_int_mem_ldap_mod(mods, "krbpwdmindiffchars", op,
                                   (int)policy->pw_min_classes);
    if (st)
        return st;

    st = krb5_add_int_mem_ldap_mod(mods, "krbpwdminlength", op,
                                   (int)policy->pw_min_length);
    if (st)
        return st;

    st = krb5_add_int_mem_ldap_mod(mods, "krbpwdhistorylength", op,
                                   (int)policy->pw_history_num);
    if (st)
        return st;

    st = krb5_add_int_mem_ldap_mod(mods, "krbpwdmaxfailure", op,
                                   (int)policy->pw_max_fail);
    if (st)
        return st;

    st = krb5_add_int_mem_ldap_mod(mods, "krbpwdfailurecountinterval", op,
                                   (int)policy->pw_failcnt_interval);
    if (st)
        return st;

    st = krb5_add_int_mem_ldap_mod(mods, "krbpwdlockoutduration", op,
                                   (int)policy->pw_lockout_duration);
    if (st)
        return st;

    st = krb5_add_int_mem_ldap_mod(mods, "krbpwdattributes", op,
                                   (int)policy->attributes);
    if (st)
        return st;

    st = krb5_add_int_mem_ldap_mod(mods, "krbpwdmaxlife", op,
                                   (int)policy->max_life);
    if (st)
        return st;

    st = krb5_add_int_mem_ldap_mod(mods, "krbpwdmaxrenewablelife", op,
                                   (int)policy->max_renewable_life);
    if (st)
        return st;

    if (policy->allowed_keysalts != NULL) {
        strval[0] = policy->allowed_keysalts;
        st = krb5_add_str_mem_ldap_mod(mods, "krbpwdallowedkeysalts",
                                       op, strval);
        if (st)
            return st;
    }

    /*
     * Each policy tl-data type we add should be explicitly marshalled here.
     * Unlike principals, we do not marshal unrecognized policy tl-data.
     */

    return 0;
}

Project: krb5
File: ldap_pwd_policy.c

krb5_ldap_get_password_policy(krb5_context context, char *name,
                              osa_policy_ent_t *policy)
{
    krb5_error_code             st = 0;
    char                        *policy_dn = NULL;

    /* Clear the global error string */
    krb5_clear_error_message(context);

    /* validate the input parameters */
    if (name == NULL) {
        st = EINVAL;
        goto cleanup;
    }

    st = krb5_ldap_name_to_policydn(context, name, &policy_dn);
    if (st != 0)
        goto cleanup;

    st = krb5_ldap_get_password_policy_from_dn(context, name, policy_dn,
                                               policy);

cleanup:
    free(policy_dn);
    return st;
}

Project: krb5
File: ldap_pwd_policy.c

krb5_ldap_put_password_policy(krb5_context context, osa_policy_ent_t policy)
{
    char                        *policy_dn=NULL;
    krb5_error_code             st=0;
    LDAP                        *ld=NULL;
    LDAPMod                     **mods=NULL;
    kdb5_dal_handle             *dal_handle=NULL;
    krb5_ldap_context           *ldap_context=NULL;
    krb5_ldap_server_handle     *ldap_server_handle=NULL;

    /* Clear the global error string */
    krb5_clear_error_message(context);

    /* validate the input parameters */
    if (policy == NULL || policy->name == NULL)
        return EINVAL;

    SETUP_CONTEXT();
    GET_HANDLE();

    st = krb5_ldap_name_to_policydn (context, policy->name, &policy_dn);
    if (st != 0)
        goto cleanup;

    st = add_policy_mods(context, &mods, policy, LDAP_MOD_REPLACE);
    if (st)
        goto cleanup;

    /* modify the password policy object. */
    /*
     * This will fail if the 'policy_dn' is anywhere other than under the realm
     * container. This is correct behaviour. 'kdb5_ldap_util' will support
     * management of only such policy objects.
     */
    if ((st=ldap_modify_ext_s(ld, policy_dn, mods, NULL, NULL)) != LDAP_SUCCESS) {
        st = set_ldap_error (context, st, OP_MOD);
        goto cleanup;
    }

cleanup:
    free(policy_dn);
    ldap_mods_free(mods, 1);
    krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
    return(st);
}

Project: krb5
File: ldap_pwd_policy.c

populate_policy(krb5_context context,
                LDAP *ld,
                LDAPMessage *ent,
                char *pol_name,
                osa_policy_ent_t pol_entry)
{
    int st = 0;

    pol_entry->name = strdup(pol_name);
    CHECK_NULL(pol_entry->name);
    pol_entry->version = 1;

    get_ui4(ld, ent, "krbmaxpwdlife", &pol_entry->pw_max_life);
    get_ui4(ld, ent, "krbminpwdlife", &pol_entry->pw_min_life);
    get_ui4(ld, ent, "krbpwdmindiffchars", &pol_entry->pw_min_classes);
    get_ui4(ld, ent, "krbpwdminlength", &pol_entry->pw_min_length);
    get_ui4(ld, ent, "krbpwdhistorylength", &pol_entry->pw_history_num);
    get_ui4(ld, ent, "krbpwdmaxfailure", &pol_entry->pw_max_fail);
    get_ui4(ld, ent, "krbpwdfailurecountinterval",
            &pol_entry->pw_failcnt_interval);
    get_ui4(ld, ent, "krbpwdlockoutduration", &pol_entry->pw_lockout_duration);
    get_ui4(ld, ent, "krbpwdattributes", &pol_entry->attributes);
    get_ui4(ld, ent, "krbpwdmaxlife", &pol_entry->max_life);
    get_ui4(ld, ent, "krbpwdmaxrenewablelife", &pol_entry->max_renewable_life);

    st = krb5_ldap_get_string(ld, ent, "krbpwdallowedkeysalts",
                              &(pol_entry->allowed_keysalts), NULL);
    if (st)
        goto cleanup;
    /*
     * We don't store the policy refcnt, because principals might be maintained
     * outside of kadmin.  Instead, we will check for principal references when
     * policies are deleted.
     */
    pol_entry->policy_refcnt = 0;

cleanup:
    return st;
}