Blame SOURCES/Fix-flaws-in-LDAP-DN-checking.patch

665228
From 997e1bbb2ec662357089aa43763e138183860cc3 Mon Sep 17 00:00:00 2001
665228
From: Greg Hudson <ghudson@mit.edu>
665228
Date: Fri, 12 Jan 2018 11:43:01 -0500
665228
Subject: [PATCH] Fix flaws in LDAP DN checking
665228
665228
KDB_TL_USER_INFO tl-data is intended to be internal to the LDAP KDB
665228
module, and not used in disk or wire principal entries.  Prevent
665228
kadmin clients from sending KDB_TL_USER_INFO tl-data by giving it a
665228
type number less than 256 and filtering out type numbers less than 256
665228
in kadm5_create_principal_3().  (We already filter out low type
665228
numbers in kadm5_modify_principal()).
665228
665228
In the LDAP KDB module, if containerdn and linkdn are both specified
665228
in a put_principal operation, check both linkdn and the computed
665228
standalone_principal_dn for container membership.  To that end, factor
665228
out the checks into helper functions and call them on all applicable
665228
client-influenced DNs.
665228
665228
CVE-2018-5729:
665228
665228
In MIT krb5 1.6 or later, an authenticated kadmin user with permission
665228
to add principals to an LDAP Kerberos database can cause a null
665228
dereference in kadmind, or circumvent a DN container check, by
665228
supplying tagged data intended to be internal to the database module.
665228
Thanks to Sharwan Ram and Pooja Anil for discovering the potential
665228
null dereference.
665228
665228
CVE-2018-5730:
665228
665228
In MIT krb5 1.6 or later, an authenticated kadmin user with permission
665228
to add principals to an LDAP Kerberos database can circumvent a DN
665228
containership check by supplying both a "linkdn" and "containerdn"
665228
database argument, or by supplying a DN string which is a left
665228
extension of a container DN string but is not hierarchically within
665228
the container DN.
665228
665228
ticket: 8643 (new)
665228
tags: pullup
665228
target_version: 1.16-next
665228
target_version: 1.15-next
665228
665228
(cherry picked from commit e1caf6fb74981da62039846931ebdffed71309d1)
665228
[rharwood@redhat.com fuzz - didn't port tests to expected_msg]
665228
---
665228
 src/lib/kadm5/srv/svr_principal.c             |   7 +
665228
 src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.h   |   2 +-
665228
 .../kdb/ldap/libkdb_ldap/ldap_principal2.c    | 200 ++++++++++--------
665228
 src/tests/t_kdb.py                            |  14 ++
665228
 4 files changed, 128 insertions(+), 95 deletions(-)
665228
665228
diff --git a/src/lib/kadm5/srv/svr_principal.c b/src/lib/kadm5/srv/svr_principal.c
665228
index 0d4f0a632..64a4a2e97 100644
665228
--- a/src/lib/kadm5/srv/svr_principal.c
665228
+++ b/src/lib/kadm5/srv/svr_principal.c
665228
@@ -330,6 +330,13 @@ kadm5_create_principal_3(void *server_handle,
665228
         return KADM5_BAD_MASK;
665228
     if((mask & ~ALL_PRINC_MASK))
665228
         return KADM5_BAD_MASK;
665228
+    if (mask & KADM5_TL_DATA) {
665228
+        for (tl_data_tail = entry->tl_data; tl_data_tail != NULL;
665228
+             tl_data_tail = tl_data_tail->tl_data_next) {
665228
+            if (tl_data_tail->tl_data_type < 256)
665228
+                return KADM5_BAD_TL_TYPE;
665228
+        }
665228
+    }
665228
 
665228
     /*
665228
      * Check to see if the principal exists
665228
diff --git a/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.h b/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.h
665228
index 06b477537..0c19804ad 100644
665228
--- a/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.h
665228
+++ b/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.h
665228
@@ -141,7 +141,7 @@ extern int set_ldap_error (krb5_context ctx, int st, int op);
665228
 #define UNSTORE16_INT(ptr, val) (val = load_16_be(ptr))
665228
 #define UNSTORE32_INT(ptr, val) (val = load_32_be(ptr))
665228
 
665228
-#define  KDB_TL_USER_INFO      0x7ffe
665228
+#define  KDB_TL_USER_INFO      0xff
665228
 
665228
 #define KDB_TL_PRINCTYPE          0x01
665228
 #define KDB_TL_PRINCCOUNT         0x02
665228
diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
665228
index 88a170495..b7c9212cb 100644
665228
--- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
665228
+++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
665228
@@ -651,6 +651,107 @@ cleanup:
665228
     return ret;
665228
 }
665228
 
665228
+static krb5_error_code
665228
+check_dn_in_container(krb5_context context, const char *dn,
665228
+                      char *const *subtrees, unsigned int ntrees)
665228
+{
665228
+    unsigned int i;
665228
+    size_t dnlen = strlen(dn), stlen;
665228
+
665228
+    for (i = 0; i < ntrees; i++) {
665228
+        if (subtrees[i] == NULL || *subtrees[i] == '\0')
665228
+            return 0;
665228
+        stlen = strlen(subtrees[i]);
665228
+        if (dnlen >= stlen &&
665228
+            strcasecmp(dn + dnlen - stlen, subtrees[i]) == 0 &&
665228
+            (dnlen == stlen || dn[dnlen - stlen - 1] == ','))
665228
+            return 0;
665228
+    }
665228
+
665228
+    k5_setmsg(context, EINVAL, _("DN is out of the realm subtree"));
665228
+    return EINVAL;
665228
+}
665228
+
665228
+static krb5_error_code
665228
+check_dn_exists(krb5_context context,
665228
+                krb5_ldap_server_handle *ldap_server_handle,
665228
+                const char *dn, krb5_boolean nonkrb_only)
665228
+{
665228
+    krb5_error_code st = 0, tempst;
665228
+    krb5_ldap_context *ldap_context = context->dal_handle->db_context;
665228
+    LDAP *ld = ldap_server_handle->ldap_handle;
665228
+    LDAPMessage *result = NULL, *ent;
665228
+    char *attrs[] = { "krbticketpolicyreference", "krbprincipalname", NULL };
665228
+    char **values;
665228
+
665228
+    LDAP_SEARCH_1(dn, LDAP_SCOPE_BASE, 0, attrs, IGNORE_STATUS);
665228
+    if (st != LDAP_SUCCESS)
665228
+        return set_ldap_error(context, st, OP_SEARCH);
665228
+
665228
+    ent = ldap_first_entry(ld, result);
665228
+    CHECK_NULL(ent);
665228
+
665228
+    values = ldap_get_values(ld, ent, "krbticketpolicyreference");
665228
+    if (values != NULL)
665228
+        ldap_value_free(values);
665228
+
665228
+    values = ldap_get_values(ld, ent, "krbprincipalname");
665228
+    if (values != NULL) {
665228
+        ldap_value_free(values);
665228
+        if (nonkrb_only) {
665228
+            st = EINVAL;
665228
+            k5_setmsg(context, st, _("ldap object is already kerberized"));
665228
+            goto cleanup;
665228
+        }
665228
+    }
665228
+
665228
+cleanup:
665228
+    ldap_msgfree(result);
665228
+    return st;
665228
+}
665228
+
665228
+static krb5_error_code
665228
+validate_xargs(krb5_context context,
665228
+               krb5_ldap_server_handle *ldap_server_handle,
665228
+               const xargs_t *xargs, const char *standalone_dn,
665228
+               char *const *subtrees, unsigned int ntrees)
665228
+{
665228
+    krb5_error_code st;
665228
+
665228
+    if (xargs->dn != NULL) {
665228
+        /* The supplied dn must be within a realm container. */
665228
+        st = check_dn_in_container(context, xargs->dn, subtrees, ntrees);
665228
+        if (st)
665228
+            return st;
665228
+        /* The supplied dn must exist without Kerberos attributes. */
665228
+        st = check_dn_exists(context, ldap_server_handle, xargs->dn, TRUE);
665228
+        if (st)
665228
+            return st;
665228
+    }
665228
+
665228
+    if (xargs->linkdn != NULL) {
665228
+        /* The supplied linkdn must be within a realm container. */
665228
+        st = check_dn_in_container(context, xargs->linkdn, subtrees, ntrees);
665228
+        if (st)
665228
+            return st;
665228
+        /* The supplied linkdn must exist. */
665228
+        st = check_dn_exists(context, ldap_server_handle, xargs->linkdn,
665228
+                             FALSE);
665228
+        if (st)
665228
+            return st;
665228
+    }
665228
+
665228
+    if (xargs->containerdn != NULL && standalone_dn != NULL) {
665228
+        /* standalone_dn (likely composed using containerdn) must be within a
665228
+         * container. */
665228
+        st = check_dn_in_container(context, standalone_dn, subtrees, ntrees);
665228
+        if (st)
665228
+            return st;
665228
+    }
665228
+
665228
+    return 0;
665228
+}
665228
+
665228
 krb5_error_code
665228
 krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
665228
                         char **db_args)
665228
@@ -662,12 +763,12 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
665228
     LDAPMessage                 *result=NULL, *ent=NULL;
665228
     char                        **subtreelist = NULL;
665228
     char                        *user=NULL, *subtree=NULL, *principal_dn=NULL;
665228
-    char                        **values=NULL, *strval[10]={NULL}, errbuf[1024];
665228
+    char                        *strval[10]={NULL}, errbuf[1024];
665228
     char                        *filtuser=NULL;
665228
     struct berval               **bersecretkey=NULL;
665228
     LDAPMod                     **mods=NULL;
665228
     krb5_boolean                create_standalone=FALSE;
665228
-    krb5_boolean                krb_identity_exists=FALSE, establish_links=FALSE;
665228
+    krb5_boolean                establish_links=FALSE;
665228
     char                        *standalone_principal_dn=NULL;
665228
     krb5_tl_data                *tl_data=NULL;
665228
     krb5_key_data               **keys=NULL;
665228
@@ -860,24 +961,6 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
665228
      * any of the subtrees
665228
      */
665228
     if (xargs.dn_from_kbd == TRUE) {
665228
-        /* make sure the DN falls in the subtree */
665228
-        int              dnlen=0, subtreelen=0;
665228
-        char             *dn=NULL;
665228
-        krb5_boolean     outofsubtree=TRUE;
665228
-
665228
-        if (xargs.dn != NULL) {
665228
-            dn = xargs.dn;
665228
-        } else if (xargs.linkdn != NULL) {
665228
-            dn = xargs.linkdn;
665228
-        } else if (standalone_principal_dn != NULL) {
665228
-            /*
665228
-             * Even though the standalone_principal_dn is constructed
665228
-             * within this function, there is the containerdn input
665228
-             * from the user that can become part of the it.
665228
-             */
665228
-            dn = standalone_principal_dn;
665228
-        }
665228
-
665228
         /* Get the current subtree list if we haven't already done so. */
665228
         if (subtreelist == NULL) {
665228
             st = krb5_get_subtree_info(ldap_context, &subtreelist, &ntrees);
665228
@@ -885,81 +968,10 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
665228
                 goto cleanup;
665228
         }
665228
 
665228
-        for (tre=0; tre
665228
-            if (subtreelist[tre] == NULL || strlen(subtreelist[tre]) == 0) {
665228
-                outofsubtree = FALSE;
665228
-                break;
665228
-            } else {
665228
-                dnlen = strlen (dn);
665228
-                subtreelen = strlen(subtreelist[tre]);
665228
-                if ((dnlen >= subtreelen) && (strcasecmp((dn + dnlen - subtreelen), subtreelist[tre]) == 0)) {
665228
-                    outofsubtree = FALSE;
665228
-                    break;
665228
-                }
665228
-            }
665228
-        }
665228
-
665228
-        if (outofsubtree == TRUE) {
665228
-            st = EINVAL;
665228
-            k5_setmsg(context, st, _("DN is out of the realm subtree"));
665228
+        st = validate_xargs(context, ldap_server_handle, &xargs,
665228
+                            standalone_principal_dn, subtreelist, ntrees);
665228
+        if (st)
665228
             goto cleanup;
665228
-        }
665228
-
665228
-        /*
665228
-         * dn value will be set either by dn, linkdn or the standalone_principal_dn
665228
-         * In the first 2 cases, the dn should be existing and in the last case we
665228
-         * are supposed to create the ldap object. so the below should not be
665228
-         * executed for the last case.
665228
-         */
665228
-
665228
-        if (standalone_principal_dn == NULL) {
665228
-            /*
665228
-             * If the ldap object is missing, this results in an error.
665228
-             */
665228
-
665228
-            /*
665228
-             * Search for krbprincipalname attribute here.
665228
-             * This is to find if a kerberos identity is already present
665228
-             * on the ldap object, in which case adding a kerberos identity
665228
-             * on the ldap object should result in an error.
665228
-             */
665228
-            char  *attributes[]={"krbticketpolicyreference", "krbprincipalname", NULL};
665228
-
665228
-            ldap_msgfree(result);
665228
-            result = NULL;
665228
-            LDAP_SEARCH_1(dn, LDAP_SCOPE_BASE, 0, attributes, IGNORE_STATUS);
665228
-            if (st == LDAP_SUCCESS) {
665228
-                ent = ldap_first_entry(ld, result);
665228
-                if (ent != NULL) {
665228
-                    if ((values=ldap_get_values(ld, ent, "krbticketpolicyreference")) != NULL) {
665228
-                        ldap_value_free(values);
665228
-                    }
665228
-
665228
-                    if ((values=ldap_get_values(ld, ent, "krbprincipalname")) != NULL) {
665228
-                        krb_identity_exists = TRUE;
665228
-                        ldap_value_free(values);
665228
-                    }
665228
-                }
665228
-            } else {
665228
-                st = set_ldap_error(context, st, OP_SEARCH);
665228
-                goto cleanup;
665228
-            }
665228
-        }
665228
-    }
665228
-
665228
-    /*
665228
-     * If xargs.dn is set then the request is to add a
665228
-     * kerberos principal on a ldap object, but if
665228
-     * there is one already on the ldap object this
665228
-     * should result in an error.
665228
-     */
665228
-
665228
-    if (xargs.dn != NULL && krb_identity_exists == TRUE) {
665228
-        st = EINVAL;
665228
-        snprintf(errbuf, sizeof(errbuf),
665228
-                 _("ldap object is already kerberized"));
665228
-        k5_setmsg(context, st, "%s", errbuf);
665228
-        goto cleanup;
665228
     }
665228
 
665228
     if (xargs.linkdn != NULL) {
665228
diff --git a/src/tests/t_kdb.py b/src/tests/t_kdb.py
665228
index c0eeb0118..319687ff3 100755
665228
--- a/src/tests/t_kdb.py
665228
+++ b/src/tests/t_kdb.py
665228
@@ -171,6 +171,14 @@ out = realm.run([kadminl, 'ank', '-randkey', '-x', 'dn=cn=krb5', 'princ1'],
665228
                 expected_code=1)
665228
 if 'DN is out of the realm subtree' not in out:
665228
     fail('Unexpected kadmin.local output for out-of-realm dn')
665228
+
665228
+# Check that the DN container check is a hierarchy test, not a simple
665228
+# suffix match (CVE-2018-5730).  We expect this operation to fail
665228
+# either way (because "xcn" isn't a valid DN tag) but the container
665228
+# check should happen before the DN is parsed.
665228
+realm.run([kadminl, 'ank', '-randkey', '-x', 'dn=xcn=t1,cn=krb5', 'princ1'],
665228
+          expected_code=1, expected_msg='DN is out of the realm subtree')
665228
+
665228
 realm.run([kadminl, 'ank', '-randkey', '-x', 'dn=cn=t2,cn=krb5', 'princ1'])
665228
 out = realm.run([kadminl, 'getprinc', 'princ1'])
665228
 if 'Principal: princ1' not in out:
665228
@@ -209,6 +217,12 @@ out = realm.run([kadminl, 'modprinc', '-x', 'containerdn=cn=t2,cn=krb5',
665228
 if 'containerdn option not supported' not in out:
665228
     fail('Unexpected kadmin.local output trying to reset containerdn')
665228
 
665228
+# Verify that containerdn is checked when linkdn is also supplied
665228
+# (CVE-2018-5730).
665228
+realm.run([kadminl, 'ank', '-randkey', '-x', 'containerdn=cn=krb5',
665228
+           '-x', 'linkdn=cn=t2,cn=krb5', 'princ4'], expected_code=1,
665228
+          expected_msg='DN is out of the realm subtree')
665228
+
665228
 # Create and modify a ticket policy.
665228
 kldaputil(['create_policy', '-maxtktlife', '3hour', '-maxrenewlife', '6hour',
665228
            '-allow_forwardable', 'tktpol'])