Commit message: Check for null kadm5 policy name [CVE-2015-8630]

In kadm5_create_principal_3() and kadm5_modify_principal(), check for
entry->policy being null when KADM5_POLICY is included in the mask.

CVE-2015-8630:

In MIT krb5 1.12 and later, an authenticated attacker with permission
to modify a principal entry can cause kadmind to dereference a null
pointer by supplying a null policy value but including KADM5_POLICY in
the mask.

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

ticket: 8342 (new)
target_version: 1.14-next
target_version: 1.13-next
tags: pullup
CVE: CVE-2015-8630
CWE: CWE-703
Vulnerability description: The (1) kadm5_create_principal_3 and (2) kadm5_modify_principal functions in lib/kadm5/srv/svr_principal.c in kadmind in MIT Kerberos 5 (aka krb5) 1.12.x and 1.13.x before 1.13.4 and 1.14.x before 1.14.1 allow remote authenticated users to cause a denial of service (NULL pointer dereference and daemon crash) by specifying KADM5_POLICY with a NULL policy name.

Project: krb5
File: svr_principal.c

count_new_keys(int n_key_data, krb5_key_data *key_data)
{
    int n;

    for (n = 1; n < n_key_data; n++) {
        if (key_data[n - 1].key_data_kvno != key_data[n].key_data_kvno)
            return n;
    }
    return n_key_data;
}

Project: krb5
File: svr_principal.c

kadm5_randkey_principal_3(void *server_handle,
                          krb5_principal principal,
                          krb5_boolean keepold,
                          int n_ks_tuple, krb5_key_salt_tuple *ks_tuple,
                          krb5_keyblock **keyblocks,
                          int *n_keys)
{
    krb5_db_entry               *kdb;
    osa_princ_ent_rec           adb;
    krb5_int32                  now;
    kadm5_policy_ent_rec        pol;
    int                         ret, last_pwd, n_new_keys;
    krb5_boolean                have_pol = FALSE;
    kadm5_server_handle_t       handle = server_handle;
    krb5_keyblock               *act_mkey;
    krb5_kvno                   act_kvno;
    int                         new_n_ks_tuple = 0;
    krb5_key_salt_tuple         *new_ks_tuple = NULL;

    if (keyblocks)
        *keyblocks = NULL;

    CHECK_HANDLE(server_handle);

    krb5_clear_error_message(handle->context);

    if (principal == NULL)
        return EINVAL;

    if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))
        return(ret);

    ret = apply_keysalt_policy(handle, adb.policy, n_ks_tuple, ks_tuple,
                               &new_n_ks_tuple, &new_ks_tuple);
    if (ret)
        goto done;

    if (krb5_principal_compare(handle->context, principal, hist_princ)) {
        /* If changing the history entry, the new entry must have exactly one
         * key. */
        if (keepold)
            return KADM5_PROTECT_PRINCIPAL;
        new_n_ks_tuple = 1;
    }

    ret = kdb_get_active_mkey(handle, &act_kvno, &act_mkey);
    if (ret)
        goto done;

    ret = krb5_dbe_crk(handle->context, act_mkey, new_ks_tuple, new_n_ks_tuple,
                       keepold, kdb);
    if (ret)
        goto done;

    ret = krb5_dbe_update_mkvno(handle->context, kdb, act_kvno);
    if (ret)
        goto done;

    kdb->attributes &= ~KRB5_KDB_REQUIRES_PWCHANGE;

    ret = krb5_timeofday(handle->context, &now);
    if (ret)
        goto done;

    if ((adb.aux_attributes & KADM5_POLICY)) {
        ret = get_policy(handle, adb.policy, &pol, &have_pol);
        if (ret)
            goto done;
    }
    if (have_pol) {
        ret = krb5_dbe_lookup_last_pwd_change(handle->context, kdb, &last_pwd);
        if (ret)
            goto done;

#if 0
        /*
         * The spec says this check is overridden if the caller has
         * modify privilege.  The admin server therefore makes this
         * check itself (in chpass_principal_wrapper, misc.c).  A
         * local caller implicitly has all authorization bits.
         */
        if((now - last_pwd) < pol.pw_min_life &&
           !(kdb->attributes & KRB5_KDB_REQUIRES_PWCHANGE)) {
            ret = KADM5_PASS_TOOSOON;
            goto done;
        }
#endif

        if (pol.pw_max_life)
            kdb->pw_expiration = now + pol.pw_max_life;
        else
            kdb->pw_expiration = 0;
    } else {
        kdb->pw_expiration = 0;
    }

    ret = krb5_dbe_update_last_pwd_change(handle->context, kdb, now);
    if (ret)
        goto done;

    /* unlock principal on this KDC */
    kdb->fail_auth_count = 0;

    if (keyblocks) {
        /* Return only the new keys added by krb5_dbe_crk. */
        n_new_keys = count_new_keys(kdb->n_key_data, kdb->key_data);
        ret = decrypt_key_data(handle->context, n_new_keys, kdb->key_data,
                               keyblocks, n_keys);
        if (ret)
            goto done;
    }

    /* key data changed, let the database provider know */
    kdb->mask = KADM5_KEY_DATA | KADM5_FAIL_AUTH_COUNT;
    /* | KADM5_RANDKEY_USED */;

    ret = k5_kadm5_hook_chpass(handle->context, handle->hook_handles,
                               KADM5_HOOK_STAGE_PRECOMMIT, principal, keepold,
                               new_n_ks_tuple, new_ks_tuple, NULL);
    if (ret)
        goto done;
    if ((ret = kdb_put_entry(handle, kdb, &adb)))
        goto done;

    (void) k5_kadm5_hook_chpass(handle->context, handle->hook_handles,
                                KADM5_HOOK_STAGE_POSTCOMMIT, principal,
                                keepold, new_n_ks_tuple, new_ks_tuple, NULL);
    ret = KADM5_OK;
done:
    free(new_ks_tuple);
    kdb_free_entry(handle, kdb, &adb);
    if (have_pol)
        kadm5_free_policy_ent(handle->lhandle, &pol);

    return ret;
}

Project: krb5
File: svr_principal.c

kadm5_rename_principal(void *server_handle,
                       krb5_principal source, krb5_principal target)
{
    krb5_db_entry *kdb;
    osa_princ_ent_rec adb;
    krb5_error_code ret;
    kadm5_server_handle_t handle = server_handle;
    krb5_int16 stype, i;
    krb5_data *salt = NULL;
    krb5_tl_data tl;

    CHECK_HANDLE(server_handle);

    krb5_clear_error_message(handle->context);

    if (source == NULL || target == NULL)
        return EINVAL;

    if ((ret = kdb_get_entry(handle, target, &kdb, &adb)) == 0) {
        kdb_free_entry(handle, kdb, &adb);
        return(KADM5_DUP);
    }

    if ((ret = kdb_get_entry(handle, source, &kdb, &adb)))
        return ret;

    /*
     * This rename procedure does not work with the LDAP KDB module (see issue
     * #8065).  As a stopgap, look for tl-data indicating LDAP and error out.
     * 0x7FFE is KDB_TL_USER_INFO as defined in kdb_ldap.h.
     */
    tl.tl_data_type = 0x7FFE;
    if (krb5_dbe_lookup_tl_data(handle->context, kdb, &tl) == 0 &&
        tl.tl_data_length > 0) {
        ret = KRB5_PLUGIN_OP_NOTSUPP;
        goto done;
    }

    /* Transform salts as necessary. */
    for (i = 0; i < kdb->n_key_data; i++) {
        ret = krb5_dbe_compute_salt(handle->context, &kdb->key_data[i],
                                    kdb->princ, &stype, &salt);
        if (ret == KRB5_KDB_BAD_SALTTYPE)
            ret = KADM5_NO_RENAME_SALT;
        if (ret)
            goto done;
        kdb->key_data[i].key_data_type[1] = KRB5_KDB_SALTTYPE_SPECIAL;
        free(kdb->key_data[i].key_data_contents[1]);
        kdb->key_data[i].key_data_contents[1] = (krb5_octet *)salt->data;
        kdb->key_data[i].key_data_length[1] = salt->length;
        kdb->key_data[i].key_data_ver = 2;
        free(salt);
        salt = NULL;
    }

    kadm5_free_principal(handle->context, kdb->princ);
    ret = kadm5_copy_principal(handle->context, target, &kdb->princ);
    if (ret) {
        kdb->princ = NULL; /* so freeing the dbe doesn't lose */
        goto done;
    }

    ret = k5_kadm5_hook_rename(handle->context, handle->hook_handles,
                               KADM5_HOOK_STAGE_PRECOMMIT, source, target);
    if (ret)
        goto done;

    if ((ret = kdb_put_entry(handle, kdb, &adb)))
        goto done;

    (void) k5_kadm5_hook_rename(handle->context, handle->hook_handles,
                                KADM5_HOOK_STAGE_POSTCOMMIT, source, target);

    ret = kdb_delete_entry(handle, source);

done:
    krb5_free_data(handle->context, salt);
    kdb_free_entry(handle, kdb, &adb);
    return ret;
}