Commit message: Fix LDAP key data segmentation [CVE-2014-4345]

For principal entries having keys with multiple kvnos (due to use of
-keepold), the LDAP KDB module makes an attempt to store all the keys
having the same kvno into a single krbPrincipalKey attribute value.
There is a fencepost error in the loop, causing currkvno to be set to
the just-processed value instead of the next kvno.  As a result, the
second and all following groups of multiple keys by kvno are each
stored in two krbPrincipalKey attribute values.  Fix the loop to use
the correct kvno value.

CVE-2014-4345:

In MIT krb5, when kadmind is configured to use LDAP for the KDC
database, an authenticated remote attacker can cause it to perform an
out-of-bounds write (buffer overrun) by performing multiple cpw
-keepold operations.  An off-by-one error while copying key
information to the new database entry results in keys sharing a common
kvno being written to different array buckets, in an array whose size
is determined by the number of kvnos present.  After sufficient
iterations, the extra writes extend past the end of the
(NULL-terminated) array.  The NULL terminator is always written after
the end of the loop, so no out-of-bounds data is read, it is only
written.

Historically, it has been possible to convert an out-of-bounds write
into remote code execution in some cases, though the necessary
exploits must be tailored to the individual application and are
usually quite complicated.  Depending on the allocated length of the
array, an out-of-bounds write may also cause a segmentation fault
and/or application crash.

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

[ghudson@mit.edu: clarified commit message]
[kaduk@mit.edu: CVE summary, CVSSv2 vector]

(cherry picked from commit 81c332e29f10887c6b9deb065f81ba259f4c7e03)

ticket: 7980
version_fixed: 1.12.2
status: resolved
CVE: CVE-2014-4345
CWE: CWE-189
Vulnerability description: Off-by-one error in the krb5_encode_krbsecretkey function in plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c in the LDAP KDB module in kadmind in MIT Kerberos 5 (aka krb5) 1.6.x through 1.11.x before 1.11.6 and 1.12.x before 1.12.2 allows remote authenticated users to cause a denial of service (buffer overflow) or possibly execute arbitrary code via a series of "cpw -keepold" commands.

Project: krb5
File: ldap_principal2.c

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

    if (n_key_data <= 0)
        return NULL;

    /* 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_in == 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;
        }
    }

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

    ret = (struct berval **) calloc (num_versions + 1, sizeof (struct berval *));
    if (ret == NULL) {
        err = ENOMEM;
        goto cleanup;
    }
    for (i = 0, last = 0, j = 0, currkvno = key_data[0].key_data_kvno; i < n_key_data; i++) {
        krb5_data *code;
        if (i == n_key_data - 1 || key_data[i + 1].key_data_kvno != currkvno) {
            ret[j] = k5alloc(sizeof(struct berval), &err);
            if (ret[j] == NULL)
                goto cleanup;
            err = asn1_encode_sequence_of_keys(key_data + last,
                                               (krb5_int16)i - last + 1,
                                               mkvno, &code);
            if (err)
                goto cleanup;
            /*CHECK_NULL(ret[j]); */
            ret[j]->bv_len = code->length;
            ret[j]->bv_val = code->data;
            free(code);
             j++;
             last = i + 1;
 
            currkvno = key_data[i].key_data_kvno;
         }
     }
     ret[num_versions] = NULL;

cleanup:

    free(key_data);
    if (err != 0) {
        if (ret != NULL) {
            for (i = 0; i <= num_versions; i++)
                if (ret[i] != NULL)
                    free (ret[i]);
            free (ret);
            ret = NULL;
        }
    }

    return ret;
}