Commit message: Fix LDAP null deref on empty arg [CVE-2016-3119]

In the LDAP KDB module's process_db_args(), strtok_r() may return NULL
if there is an empty string in the db_args array.  Check for this case
and avoid dereferencing a null pointer.

CVE-2016-3119:

In MIT krb5 1.6 and later, an authenticated attacker with permission
to modify a principal entry can cause kadmind to dereference a null
pointer by supplying an empty DB argument to the modify_principal
command, if kadmind is configured to use the LDAP KDB module.

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

ticket: 8383 (new)
target_version: 1.14-next
target_version: 1.13-next
tags: pullup
CVE: CVE-2016-3119
CWE: CWE-703
Vulnerability description: The process_db_args function in plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c in the LDAP KDB module in kadmind in MIT Kerberos 5 (aka krb5) through 1.13.4 and 1.14.x through 1.14.1 mishandles the DB argument, which allows remote authenticated users to cause a denial of service (NULL pointer dereference and daemon crash) via a crafted request to modify a principal.

Project: krb5
File: ldap_principal2.c

compare_osa_pw_hist_ent(const void *left_in, const void *right_in)
{
    int kvno_left, kvno_right;
    osa_pw_hist_ent *left = (osa_pw_hist_ent *)left_in;
    osa_pw_hist_ent *right = (osa_pw_hist_ent *)right_in;

    kvno_left = left->n_key_data ? left->key_data[0].key_data_kvno : 0;
    kvno_right = right->n_key_data ? right->key_data[0].key_data_kvno : 0;
    return kvno_left - kvno_right;
}

Project: krb5
File: ldap_principal2.c

decode_keys(struct berval **bvalues, ldap_seqof_key_data **keysets_out,
            krb5_int16 *n_keysets_out, krb5_int16 *total_keys_out)
{
    krb5_error_code err = 0;
    krb5_int16 n_keys, i, ki, total_keys;
    ldap_seqof_key_data *keysets = NULL;

    *keysets_out = NULL;
    *n_keysets_out = 0;
    if (total_keys_out)
        *total_keys_out = 0;

    /* Precount the number of keys. */
    for (n_keys = 0, i = 0; bvalues[i] != NULL; i++) {
        if (bvalues[i]->bv_len > 0)
            n_keys++;
    }

    keysets = k5calloc(n_keys, sizeof(ldap_seqof_key_data), &err);
    if (keysets == NULL)
        goto cleanup;
    memset(keysets, 0, n_keys * sizeof(ldap_seqof_key_data));

    for (i = 0, ki = 0, total_keys = 0; bvalues[i] != NULL; i++) {
        krb5_data in;

        if (bvalues[i]->bv_len == 0)
            continue;
        in.length = bvalues[i]->bv_len;
        in.data = bvalues[i]->bv_val;

        err = asn1_decode_sequence_of_keys(&in, &keysets[ki]);
        if (err)
            goto cleanup;

        if (total_keys_out)
            total_keys += keysets[ki].n_key_data;
        ki++;
    }

    if (total_keys_out)
        *total_keys_out = total_keys;

    *n_keysets_out = n_keys;
    *keysets_out = keysets;
    keysets = NULL;
    n_keys = 0;

cleanup:
    free_ldap_seqof_key_data(keysets, n_keys);
    return err;
}

Project: krb5
File: ldap_principal2.c

encode_keys(krb5_key_data *key_data_in, int n_key_data, krb5_kvno mkvno,
            struct berval **bval_out)
{
    krb5_error_code err = 0;
    int i;
    krb5_key_data *key_data = NULL;
    struct berval *bval = NULL;
    krb5_data *code;

    *bval_out = NULL;
    if (n_key_data <= 0) {
        err = EINVAL;
        goto cleanup;
    }

    /* Make a shallow copy of the key data so we can alter it. */
    key_data = k5calloc(n_key_data, sizeof(*key_data), &err);
    if (key_data == NULL)
        goto cleanup;
    memcpy(key_data, key_data_in, n_key_data * sizeof(*key_data));

    /* Unpatched krb5 1.11 and 1.12 cannot decode KrbKey sequences with no salt
     * field.  For compatibility, always encode a salt field. */
    for (i = 0; i < n_key_data; i++) {
        if (key_data[i].key_data_ver == 1) {
            key_data[i].key_data_ver = 2;
            key_data[i].key_data_type[1] = KRB5_KDB_SALTTYPE_NORMAL;
            key_data[i].key_data_length[1] = 0;
            key_data[i].key_data_contents[1] = NULL;
        }
    }

    bval = k5alloc(sizeof(struct berval), &err);
    if (bval == NULL)
        goto cleanup;

    err = asn1_encode_sequence_of_keys(key_data, n_key_data, mkvno, &code);
    if (err)
        goto cleanup;

    /* Steal the data pointer from code for bval and discard code. */
    bval->bv_len = code->length;
    bval->bv_val = code->data;
    free(code);

    *bval_out = bval;
    bval = NULL;

cleanup:
    free(key_data);
    free(bval);
    return err;
}

Project: krb5
File: ldap_principal2.c

free_berdata(struct berval **array)
{
    int i;

    if (array != NULL) {
        for (i = 0; array[i] != NULL; i++) {
            if (array[i]->bv_val != NULL)
                free(array[i]->bv_val);
            free(array[i]);
        }
        free(array);
    }
}

Project: krb5
File: ldap_principal2.c

krb5_decode_histkey(krb5_context context, struct berval **bvalues,
                    osa_princ_ent_rec *princ_ent)
{
    krb5_error_code err = 0;
    krb5_int16 i, n_keysets = 0;
    ldap_seqof_key_data *keysets = NULL;

    err = decode_keys(bvalues, &keysets, &n_keysets, NULL);
    if (err != 0) {
        k5_prependmsg(context, err,
                      _("unable to decode stored principal pw history"));
        goto cleanup;
    }

    princ_ent->old_keys = k5calloc(n_keysets, sizeof(osa_pw_hist_ent), &err);
    if (princ_ent->old_keys == NULL)
        goto cleanup;
    princ_ent->old_key_len = n_keysets;

    if (n_keysets > 0)
        princ_ent->admin_history_kvno = keysets[0].mkvno;

    /* Transfer key data pointers from keysets to princ_ent. */
    for (i = 0; i < n_keysets; i++) {
        princ_ent->old_keys[i].n_key_data = keysets[i].n_key_data;
        princ_ent->old_keys[i].key_data = keysets[i].key_data;
        keysets[i].n_key_data = 0;
        keysets[i].key_data = NULL;
    }

    /* Sort the principal entries by kvno in ascending order. */
    qsort(princ_ent->old_keys, princ_ent->old_key_len, sizeof(osa_pw_hist_ent),
          &compare_osa_pw_hist_ent);

    princ_ent->aux_attributes |= KADM5_KEY_HIST;

    /* Set the next key to the end of the list.  The queue will be lengthened
     * if it isn't full yet; the first entry will be replaced if it is full. */
    princ_ent->old_key_next = princ_ent->old_key_len;

cleanup:
    free_ldap_seqof_key_data(keysets, n_keysets);
    return err;
}

Project: krb5
File: ldap_principal2.c

krb5_decode_krbsecretkey(krb5_context context, krb5_db_entry *entries,
                         struct berval **bvalues, krb5_kvno *mkvno)
{
    krb5_key_data *key_data = NULL, *tmp;
    krb5_error_code err = 0;
    ldap_seqof_key_data *keysets = NULL;
    krb5_int16 i, n_keysets = 0, total_keys = 0;

    err = decode_keys(bvalues, &keysets, &n_keysets, &total_keys);
    if (err != 0) {
        k5_prependmsg(context, err,
                      _("unable to decode stored principal key data"));
        goto cleanup;
    }

    key_data = k5calloc(total_keys, sizeof(krb5_key_data), &err);
    if (key_data == NULL)
        goto cleanup;
    memset(key_data, 0, total_keys * sizeof(krb5_key_data));

    if (n_keysets > 0)
        *mkvno = keysets[0].mkvno;

    /* Transfer key data values from keysets to a flat list in entries. */
    tmp = key_data;
    for (i = 0; i < n_keysets; i++) {
        memcpy(tmp, keysets[i].key_data,
               sizeof(krb5_key_data) * keysets[i].n_key_data);
        tmp += keysets[i].n_key_data;
        keysets[i].n_key_data = 0;
    }
    entries->n_key_data = total_keys;
    entries->key_data = key_data;
    key_data = NULL;

cleanup:
    free_ldap_seqof_key_data(keysets, n_keysets);
    k5_free_key_data(total_keys, key_data);
    return err;
}

Project: krb5
File: ldap_principal2.c

krb5_encode_histkey(osa_princ_ent_rec *princ_ent)
{
    unsigned int i;
    krb5_error_code err = 0;
    struct berval **ret = NULL;

    if (princ_ent->old_key_len <= 0)
        return NULL;

    ret = k5calloc(princ_ent->old_key_len + 1, sizeof(struct berval *), &err);
    if (ret == NULL)
        goto cleanup;

    for (i = 0; i < princ_ent->old_key_len; i++) {
        if (princ_ent->old_keys[i].n_key_data <= 0) {
            err = EINVAL;
            goto cleanup;
        }
        err = encode_keys(princ_ent->old_keys[i].key_data,
                          princ_ent->old_keys[i].n_key_data,
                          princ_ent->admin_history_kvno, &ret[i]);
        if (err)
            goto cleanup;
    }

    ret[princ_ent->old_key_len] = NULL;

cleanup:
    if (err != 0) {
        free_berdata(ret);
        ret = NULL;
    }

    return ret;
}

Project: krb5
File: ldap_principal2.c

krb5_encode_krbsecretkey(krb5_key_data *key_data, int n_key_data,
                         krb5_kvno mkvno)
{
    struct berval **ret = NULL;
    int currkvno;
    int num_versions = 0;
    int i, j, last;
    krb5_error_code err = 0;

    if (n_key_data < 0)
        return NULL;

    /* Find the number of key versions */
    if (n_key_data > 0) {
        for (i = 0, num_versions = 1; i < n_key_data - 1; i++) {
            if (key_data[i].key_data_kvno != key_data[i + 1].key_data_kvno)
                num_versions++;
        }
    }

    ret = calloc(num_versions + 1, sizeof(struct berval *));
    if (ret == NULL) {
        err = ENOMEM;
        goto cleanup;
    }
    ret[num_versions] = NULL;

    /* n_key_data may be 0 if a principal is created without a key. */
    if (n_key_data == 0)
        goto cleanup;

    currkvno = key_data[0].key_data_kvno;
    for (i = 0, last = 0, j = 0; i < n_key_data; i++) {
        if (i == n_key_data - 1 || key_data[i + 1].key_data_kvno != currkvno) {
            err = encode_keys(key_data + last, (krb5_int16)i - last + 1, mkvno,
                              &ret[j]);
            if (err)
                goto cleanup;
            j++;
            last = i + 1;

            if (i < n_key_data - 1)
                currkvno = key_data[i + 1].key_data_kvno;
        }
    }

cleanup:
    if (err != 0) {
        free_berdata(ret);
        ret = NULL;
    }

    return ret;
}

Project: krb5
File: ldap_principal2.c

update_ldap_mod_auth_ind(krb5_context context, krb5_db_entry *entry,
                         LDAPMod ***mods)
{
    int i = 0;
    krb5_error_code ret;
    char *auth_ind = NULL;
    char *strval[10] = {};
    char *ai, *ai_save = NULL;
    int sv_num = sizeof(strval) / sizeof(*strval);

    ret = krb5_dbe_get_string(context, entry, KRB5_KDB_SK_REQUIRE_AUTH,
                              &auth_ind);
    if (ret || auth_ind == NULL)
        goto cleanup;

    ai = strtok_r(auth_ind, " ", &ai_save);
    while (ai != NULL && i < sv_num) {
        strval[i++] = ai;
        ai = strtok_r(NULL, " ", &ai_save);
    }

    ret = krb5_add_str_mem_ldap_mod(mods, "krbPrincipalAuthInd",
                                    LDAP_MOD_REPLACE, strval);

cleanup:
    krb5_dbe_free_string(context, auth_ind);
    return ret;
}