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

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