Support keyless principals in LDAP [CVE-2014-5354]

Operations like "kadmin -q 'addprinc -nokey foo'" or
"kadmin -q 'purgekeys -all foo'" result in principal entries with
no keys present, so krb5_encode_krbsecretkey() would just return
NULL, which then got unconditionally dereferenced in
krb5_add_ber_mem_ldap_mod().

Apply some fixes to krb5_encode_krbsecretkey() to handle zero-key
principals better, correct the test for an allocation failure, and
slightly restructure the cleanup handler to be shorter and more
appropriate for the usage.  Once it no longer short-circuits when
n_key_data is zero, it will produce an array of length two with both
entries NULL, which is treated as an empty list by the LDAP library,
the correct behavior for a keyless principal.

However, attributes with empty values are only handled by the LDAP
library for Modify operations, not Add operations (which only get
a sequence of Attribute, with no operation field).  Therefore, only
add an empty krbprincipalkey to the modlist when we will be performing a
Modify, and not when we will be performing an Add, which is conditional
on the (misspelled) create_standalone_prinicipal boolean.

CVE-2014-5354:

In MIT krb5, when kadmind is configured to use LDAP for the KDC
database, an authenticated remote attacker can cause a NULL
dereference by inserting into the database a principal entry which
contains no long-term keys.

In order for the LDAP KDC backend to translate a principal entry
from the database abstraction layer into the form expected by the
LDAP schema, the principal's keys are encoded into a
NULL-terminated array of length-value entries to be stored in the
LDAP database.  However, the subroutine which produced this array
did not correctly handle the case where no keys were present,
returning NULL instead of an empty array, and the array was
unconditionally dereferenced while adding to the list of LDAP
operations to perform.

Versions of MIT krb5 prior to 1.12 did not expose a way for
principal entries to have no long-term key material, and
therefore are not vulnerable.

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

ticket: 8041 (new)
tags: pullup
target_version: 1.13.1
subject: kadmind with ldap backend crashes when putting keyless entries
process_db_args(krb5_context context, char **db_args, xargs_t *xargs,
                OPERATION optype)
{
    int                   i=0;
    krb5_error_code       st=0;
    char                  *arg=NULL, *arg_val=NULL;
    char                  **dptr=NULL;
    unsigned int          arg_val_len=0;

    if (db_args) {
        for (i=0; db_args[i]; ++i) {
            arg = strtok_r(db_args[i], "=", &arg_val);
            if (strcmp(arg, TKTPOLICY_ARG) == 0) {
                dptr = &xargs->tktpolicydn;
            } else {
                if (strcmp(arg, USERDN_ARG) == 0) {
                    if (optype == MODIFY_PRINCIPAL ||
                        xargs->dn != NULL || xargs->containerdn != NULL ||
                        xargs->linkdn != NULL) {
                        st = EINVAL;
                        k5_setmsg(context, st, _("%s option not supported"),
                                  arg);
                        goto cleanup;
                    }
                    dptr = &xargs->dn;
                } else if (strcmp(arg, CONTAINERDN_ARG) == 0) {
                    if (optype == MODIFY_PRINCIPAL ||
                        xargs->dn != NULL || xargs->containerdn != NULL) {
                        st = EINVAL;
                        k5_setmsg(context, st, _("%s option not supported"),
                                  arg);
                        goto cleanup;
                    }
                    dptr = &xargs->containerdn;
                } else if (strcmp(arg, LINKDN_ARG) == 0) {
                    if (xargs->dn != NULL || xargs->linkdn != NULL) {
                        st = EINVAL;
                        k5_setmsg(context, st, _("%s option not supported"),
                                  arg);
                        goto cleanup;
                    }
                    dptr = &xargs->linkdn;
                } else {
                    st = EINVAL;
                    k5_setmsg(context, st, _("unknown option: %s"), arg);
                    goto cleanup;
                }

                xargs->dn_from_kbd = TRUE;
                if (arg_val == NULL || strlen(arg_val) == 0) {
                    st = EINVAL;
                    k5_setmsg(context, st, _("%s option value missing"), arg);
                    goto cleanup;
                }
            }

            if (arg_val == NULL) {
                st = EINVAL;
                k5_setmsg(context, st, _("%s option value missing"), arg);
                goto cleanup;
            }
            arg_val_len = strlen(arg_val) + 1;

            if (strcmp(arg, TKTPOLICY_ARG) == 0) {
                if ((st = krb5_ldap_name_to_policydn (context,
                                                      arg_val,
                                                      dptr)) != 0)
                    goto cleanup;
            } else {
                *dptr = k5memdup(arg_val, arg_val_len, &st);
                if (*dptr == NULL)
                    goto cleanup;
            }
        }
    }

cleanup:
    return st;
}

process_db_args(krb5_context context, char **db_args, xargs_t *xargs,
                OPERATION optype)
{
    int                   i=0;
    krb5_error_code       st=0;
    char                  *arg=NULL, *arg_val=NULL;
    char                  **dptr=NULL;
    unsigned int          arg_val_len=0;

    if (db_args) {
        for (i=0; db_args[i]; ++i) {
            arg = strtok_r(db_args[i], "=", &arg_val);
            if (strcmp(arg, TKTPOLICY_ARG) == 0) {
                dptr = &xargs->tktpolicydn;
            } else {
                if (strcmp(arg, USERDN_ARG) == 0) {
                    if (optype == MODIFY_PRINCIPAL ||
                        xargs->dn != NULL || xargs->containerdn != NULL ||
                        xargs->linkdn != NULL) {
                        st = EINVAL;
                        k5_setmsg(context, st, _("%s option not supported"),
                                  arg);
                        goto cleanup;
                    }
                    dptr = &xargs->dn;
                } else if (strcmp(arg, CONTAINERDN_ARG) == 0) {
                    if (optype == MODIFY_PRINCIPAL ||
                        xargs->dn != NULL || xargs->containerdn != NULL) {
                        st = EINVAL;
                        k5_setmsg(context, st, _("%s option not supported"),
                                  arg);
                        goto cleanup;
                    }
                    dptr = &xargs->containerdn;
                } else if (strcmp(arg, LINKDN_ARG) == 0) {
                    if (xargs->dn != NULL || xargs->linkdn != NULL) {
                        st = EINVAL;
                        k5_setmsg(context, st, _("%s option not supported"),
                                  arg);
                        goto cleanup;
                    }
                    dptr = &xargs->linkdn;
                } else {
                    st = EINVAL;
                    k5_setmsg(context, st, _("unknown option: %s"), arg);
                    goto cleanup;
                }

                xargs->dn_from_kbd = TRUE;
                if (arg_val == NULL || strlen(arg_val) == 0) {
                    st = EINVAL;
                    k5_setmsg(context, st, _("%s option value missing"), arg);
                    goto cleanup;
                }
            }

            if (arg_val == NULL) {
                st = EINVAL;
                k5_setmsg(context, st, _("%s option value missing"), arg);
                goto cleanup;
            }
            arg_val_len = strlen(arg_val) + 1;

            if (strcmp(arg, TKTPOLICY_ARG) == 0) {
                if ((st = krb5_ldap_name_to_policydn (context,
                                                      arg_val,
                                                      dptr)) != 0)
                    goto cleanup;
            } else {
                *dptr = k5memdup(arg_val, arg_val_len, &st);
                if (*dptr == NULL)
                    goto cleanup;
            }
        }
    }

cleanup:
    return st;
}
