CVE-2022-4254: Improper Neutralization of Special Elements used in an LDAP Query ('LDAP Injection') (CWE-90)
value: sssd: libsss_certmap fails to sanitise certificate data used in LDAP filters
CWE description: The product constructs all or part of an LDAP query using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended LDAP query when it is sent to a downstream component.
CVSS v3 base score: 8.8
Published: 2023-02-01T17:15Z
Repository: https://github.com/SSSD/sssd
Language: C
Files changed: Makefile.am, src/lib/certmap/sss_certmap.c, src/lib/certmap/sss_certmap.exports, src/lib/certmap/sss_certmap.h, src/responder/pam/pamsrv_p11.c, src/tests/cmocka/test_certmap.c, src/util/util.c, src/util/util_ext.c
Commit date: 2020-07-24 12:33:36 +0200
Commit message: certmap: sanitize LDAP search filter

The sss_certmap_get_search_filter() will now sanitize the values read
from the certificates before adding them to a search filter. To be able
to get the plain values as well sss_certmap_expand_mapping_rule() is
added.

Resolves:
https://github.com/SSSD/sssd/issues/5135

Reviewed-by: Alexey Tikhonov <atikhono@redhat.com>

Diff: diff --git a/Makefile.am b/Makefile.am
index 059e1eaf6..4bacabdda 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2161,11 +2161,11 @@ libsss_certmap_la_CFLAGS = \
 libsss_certmap_la_LIBADD = \
     $(TALLOC_LIBS) \
     $(NULL)
 libsss_certmap_la_LDFLAGS = \
     -Wl,--version-script,$(srcdir)/src/lib/certmap/sss_certmap.exports \
-    -version-info 1:0:1
+    -version-info 2:0:2
 
 if HAVE_NSS
 libsss_certmap_la_SOURCES += \
     src/lib/certmap/sss_cert_content_nss.c \
     src/util/crypto/nss/nss_base64.c \
diff --git a/src/lib/certmap/sss_certmap.c b/src/lib/certmap/sss_certmap.c
index 703782b53..f19e57732 100644
--- a/src/lib/certmap/sss_certmap.c
+++ b/src/lib/certmap/sss_certmap.c
@@ -439,24 +439,28 @@ static int expand_san(struct sss_certmap_ctx *ctx,
 }
 
 static int expand_template(struct sss_certmap_ctx *ctx,
                            struct parsed_template *parsed_template,
                            struct sss_cert_content *cert_content,
+                           bool sanitize,
                            char **expanded)
 {
     int ret;
     char *exp = NULL;
+    char *exp_sanitized = NULL;
 
     if (strcmp("issuer_dn", parsed_template->name) == 0) {
         ret = rdn_list_2_dn_str(ctx, parsed_template->conversion,
                                 cert_content->issuer_rdn_list, &exp);
     } else if (strcmp("subject_dn", parsed_template->name) == 0) {
         ret = rdn_list_2_dn_str(ctx, parsed_template->conversion,
                                 cert_content->subject_rdn_list, &exp);
     } else if (strncmp("subject_", parsed_template->name, 8) == 0) {
         ret = expand_san(ctx, parsed_template, cert_content->san_list, &exp);
     } else if (strcmp("cert", parsed_template->name) == 0) {
+        /* cert blob is already sanitized */
+        sanitize = false;
         ret = expand_cert(ctx, parsed_template, cert_content, &exp);
     } else {
         CM_DEBUG(ctx, "Unsupported template name.");
         ret = EINVAL;
         goto done;
@@ -469,10 +473,20 @@ static int expand_template(struct sss_certmap_ctx *ctx,
     if (exp == NULL) {
         ret = ENOMEM;
         goto done;
     }
 
+    if (sanitize) {
+        ret = sss_filter_sanitize(ctx, exp, &exp_sanitized);
+        if (ret != EOK) {
+            CM_DEBUG(ctx, "Failed to sanitize expanded template.");
+            goto done;
+        }
+        talloc_free(exp);
+        exp = exp_sanitized;
+    }
+
     ret = 0;
 
 done:
     if (ret == 0) {
         *expanded = exp;
@@ -483,11 +497,11 @@ done:
     return ret;
 }
 
 static int get_filter(struct sss_certmap_ctx *ctx,
                       struct ldap_mapping_rule *parsed_mapping_rule,
-                      struct sss_cert_content *cert_content,
+                      struct sss_cert_content *cert_content, bool sanitize,
                       char **filter)
 {
     struct ldap_mapping_rule_comp *comp;
     char *result = NULL;
     char *expanded = NULL;
@@ -501,11 +515,11 @@ static int get_filter(struct sss_certmap_ctx *ctx,
     for (comp = parsed_mapping_rule->list; comp != NULL; comp = comp->next) {
         if (comp->type == comp_string) {
             result = talloc_strdup_append(result, comp->val);
         } else if (comp->type == comp_template) {
             ret = expand_template(ctx, comp->parsed_template, cert_content,
-                                  &expanded);
+                                  sanitize, &expanded);
             if (ret != 0) {
                 CM_DEBUG(ctx, "Failed to expanded template.");
                 goto done;
             }
 
@@ -789,12 +803,13 @@ done:
     talloc_free(cert_content);
 
     return ret;
 }
 
-int sss_certmap_get_search_filter(struct sss_certmap_ctx *ctx,
+static int expand_mapping_rule_ex(struct sss_certmap_ctx *ctx,
                                   const uint8_t *der_cert, size_t der_size,
+                                  bool sanitize,
                                   char **_filter, char ***_domains)
 {
     int ret;
     struct match_map_rule *r;
     struct priority_list *p;
@@ -817,21 +832,22 @@ int sss_certmap_get_search_filter(struct sss_certmap_ctx *ctx,
         if (ctx->default_mapping_rule == NULL) {
             CM_DEBUG(ctx, "No matching or mapping rules available.");
             return EINVAL;
         }
 
-        ret = get_filter(ctx, ctx->default_mapping_rule, cert_content, &filter);
+        ret = get_filter(ctx, ctx->default_mapping_rule, cert_content, sanitize,
+                         &filter);
         goto done;
     }
 
     for (p = ctx->prio_list; p != NULL; p = p->next) {
         for (r = p->rule_list; r != NULL; r = r->next) {
             ret = do_match(ctx, r->parsed_match_rule, cert_content);
             if (ret == 0) {
                 /* match */
                 ret = get_filter(ctx, r->parsed_mapping_rule, cert_content,
-                                 &filter);
+                                 sanitize, &filter);
                 if (ret != 0) {
                     CM_DEBUG(ctx, "Failed to get filter");
                     goto done;
                 }
 
@@ -871,10 +887,26 @@ done:
     }
 
     return ret;
 }
 
+int sss_certmap_get_search_filter(struct sss_certmap_ctx *ctx,
+                                  const uint8_t *der_cert, size_t der_size,
+                                  char **_filter, char ***_domains)
+{
+    return expand_mapping_rule_ex(ctx, der_cert, der_size, true,
+                                  _filter, _domains);
+}
+
+int sss_certmap_expand_mapping_rule(struct sss_certmap_ctx *ctx,
+                                    const uint8_t *der_cert, size_t der_size,
+                                    char **_expanded, char ***_domains)
+{
+    return expand_mapping_rule_ex(ctx, der_cert, der_size, false,
+                                  _expanded, _domains);
+}
+
 int sss_certmap_init(TALLOC_CTX *mem_ctx,
                      sss_certmap_ext_debug *debug, void *debug_priv,
                      struct sss_certmap_ctx **ctx)
 {
     int ret;
diff --git a/src/lib/certmap/sss_certmap.exports b/src/lib/certmap/sss_certmap.exports
index a9e48d6d0..7d7667738 100644
--- a/src/lib/certmap/sss_certmap.exports
+++ b/src/lib/certmap/sss_certmap.exports
@@ -14,5 +14,10 @@ SSS_CERTMAP_0.0 {
 
 SSS_CERTMAP_0.1 {
     global:
         sss_certmap_display_cert_content;
 } SSS_CERTMAP_0.0;
+
+SSS_CERTMAP_0.2 {
+    global:
+        sss_certmap_expand_mapping_rule;
+} SSS_CERTMAP_0.1;
diff --git a/src/lib/certmap/sss_certmap.h b/src/lib/certmap/sss_certmap.h
index 7da2d1c58..058d4f9e4 100644
--- a/src/lib/certmap/sss_certmap.h
+++ b/src/lib/certmap/sss_certmap.h
@@ -101,11 +101,11 @@ int sss_certmap_add_rule(struct sss_certmap_ctx *ctx,
 /**
  * @brief Check if a certificate matches any of the applied rules
  *
  * @param[in] ctx      certmap context previously initialized with
  *                     @ref sss_certmap_init
- * @param[in] der_cert binary blog with the DER encoded certificate
+ * @param[in] der_cert binary blob with the DER encoded certificate
  * @param[in] der_size size of the certificate blob
  *
  * @return
  *  - 0:      certificate matches a rule
  *  - ENOENT: certificate does not match
@@ -117,14 +117,15 @@ int sss_certmap_match_cert(struct sss_certmap_ctx *ctx,
 /**
  * @brief Get the LDAP filter string for a certificate
  *
  * @param[in] ctx      certmap context previously initialized with
  *                     @ref sss_certmap_init
- * @param[in] der_cert binary blog with the DER encoded certificate
+ * @param[in] der_cert binary blob with the DER encoded certificate
  * @param[in] der_size size of the certificate blob
- * @param[out] filter  LDAP filter string, caller should free the data by
- *                     calling sss_certmap_free_filter_and_domains
+ * @param[out] filter  LDAP filter string, expanded templates are sanitized,
+ *                     caller should free the data by calling
+ *                     sss_certmap_free_filter_and_domains
  * @param[out] domains NULL-terminated array of strings with the domains the
  *                     rule applies, caller should free the data by calling
  *                     sss_certmap_free_filter_and_domains
  *
  * @return
@@ -134,12 +135,36 @@ int sss_certmap_match_cert(struct sss_certmap_ctx *ctx,
  */
 int sss_certmap_get_search_filter(struct sss_certmap_ctx *ctx,
                                   const uint8_t *der_cert, size_t der_size,
                                   char **filter, char ***domains);
 
+/**
+ * @brief Expand the mapping rule by replacing the templates
+ *
+ * @param[in] ctx        certmap context previously initialized with
+ *                       @ref sss_certmap_init
+ * @param[in] der_cert   binary blob with the DER encoded certificate
+ * @param[in] der_size   size of the certificate blob
+ * @param[out] expanded  expanded mapping rule, templates are filled in
+ *                       verbatim in contrast to sss_certmap_get_search_filter,
+ *                       caller should free the data by
+ *                       calling sss_certmap_free_filter_and_domains
+ * @param[out] domains   NULL-terminated array of strings with the domains the
+ *                       rule applies, caller should free the data by calling
+ *                       sss_certmap_free_filter_and_domains
+ *
+ * @return
+ *  - 0:      certificate matches a rule
+ *  - ENOENT: certificate does not match
+ *  - EINVAL: internal error
+ */
+int sss_certmap_expand_mapping_rule(struct sss_certmap_ctx *ctx,
+                                    const uint8_t *der_cert, size_t der_size,
+                                    char **_expanded, char ***_domains);
 /**
  * @brief Free data returned by @ref sss_certmap_get_search_filter
+ *        and @ref sss_certmap_expand_mapping_rule
  *
  * @param[in] filter  LDAP filter strings returned by
  *                    sss_certmap_get_search_filter
  * @param[in] domains string array of domains returned by
  *                     sss_certmap_get_search_filter
@@ -148,11 +173,11 @@ void sss_certmap_free_filter_and_domains(char *filter, char **domains);
 
 /**
  * @brief Get a string with the content of the certificate used by the library
  *
  * @param[in]  mem_ctx    Talloc memory context, may be NULL
- * @param[in]  der_cert   binary blog with the DER encoded certificate
+ * @param[in]  der_cert   binary blob with the DER encoded certificate
  * @param[in]  der_size   size of the certificate blob
  * @param[out] desc       Multiline string showing the certificate content
  *                        which is used by libsss_certmap
  *
  * @return
diff --git a/src/responder/pam/pamsrv_p11.c b/src/responder/pam/pamsrv_p11.c
index 3f0afaeff..cdf239e07 100644
--- a/src/responder/pam/pamsrv_p11.c
+++ b/src/responder/pam/pamsrv_p11.c
@@ -1047,13 +1047,14 @@ static char *get_cert_prompt(TALLOC_CTX *mem_ctx,
     if (der == NULL) {
         DEBUG(SSSDBG_OP_FAILURE, "sss_base64_decode failed.\n");
         goto done;
     }
 
-    ret = sss_certmap_get_search_filter(ctx, der, der_size, &filter, &domains);
+    ret = sss_certmap_expand_mapping_rule(ctx, der, der_size,
+                                          &filter, &domains);
     if (ret != 0) {
-        DEBUG(SSSDBG_OP_FAILURE, "sss_certmap_get_search_filter failed.\n");
+        DEBUG(SSSDBG_OP_FAILURE, "sss_certmap_expand_mapping_rule failed.\n");
         goto done;
     }
 
     prompt = talloc_asprintf(mem_ctx, "%s\n%s", sss_cai_get_label(cert_info),
                                                 filter);
diff --git a/src/tests/cmocka/test_certmap.c b/src/tests/cmocka/test_certmap.c
index c882202a0..232ff7878 100644
--- a/src/tests/cmocka/test_certmap.c
+++ b/src/tests/cmocka/test_certmap.c
@@ -1429,10 +1429,19 @@ static void test_sss_certmap_get_search_filter(void **state)
     ret = sss_certmap_get_search_filter(ctx, discard_const(test_cert_der),
                                         sizeof(test_cert_der),
                                         &filter, &domains);
     assert_int_equal(ret, 0);
     assert_non_null(filter);
+    assert_string_equal(filter, "rule100=<I>CN=Certificate\\20Authority,O=IPA.DEVEL"
+                                "<S>CN=ipa-devel.ipa.devel,O=IPA.DEVEL");
+    assert_null(domains);
+
+    ret = sss_certmap_expand_mapping_rule(ctx, discard_const(test_cert_der),
+                                          sizeof(test_cert_der),
+                                          &filter, &domains);
+    assert_int_equal(ret, 0);
+    assert_non_null(filter);
     assert_string_equal(filter, "rule100=<I>CN=Certificate Authority,O=IPA.DEVEL"
                                 "<S>CN=ipa-devel.ipa.devel,O=IPA.DEVEL");
     assert_null(domains);
 
     ret = sss_certmap_add_rule(ctx, 99,
@@ -1443,10 +1452,21 @@ static void test_sss_certmap_get_search_filter(void **state)
     ret = sss_certmap_get_search_filter(ctx, discard_const(test_cert_der),
                                         sizeof(test_cert_der),
                                         &filter, &domains);
     assert_int_equal(ret, 0);
     assert_non_null(filter);
+    assert_string_equal(filter, "rule99=<I>CN=Certificate\\20Authority,O=IPA.DEVEL"
+                                "<S>CN=ipa-devel.ipa.devel,O=IPA.DEVEL");
+    assert_non_null(domains);
+    assert_string_equal(domains[0], "test.dom");
+    assert_null(domains[1]);
+
+    ret = sss_certmap_expand_mapping_rule(ctx, discard_const(test_cert_der),
+                                          sizeof(test_cert_der),
+                                          &filter, &domains);
+    assert_int_equal(ret, 0);
+    assert_non_null(filter);
     assert_string_equal(filter, "rule99=<I>CN=Certificate Authority,O=IPA.DEVEL"
                                 "<S>CN=ipa-devel.ipa.devel,O=IPA.DEVEL");
     assert_non_null(domains);
     assert_string_equal(domains[0], "test.dom");
     assert_null(domains[1]);
@@ -1464,20 +1484,41 @@ static void test_sss_certmap_get_search_filter(void **state)
     assert_string_equal(filter, "rule98=userCertificate;binary=" TEST_CERT_BIN);
     assert_non_null(domains);
     assert_string_equal(domains[0], "test.dom");
     assert_null(domains[1]);
 
+    ret = sss_certmap_expand_mapping_rule(ctx, discard_const(test_cert_der),
+                                          sizeof(test_cert_der),
+                                          &filter, &domains);
+    assert_int_equal(ret, 0);
+    assert_non_null(filter);
+    assert_string_equal(filter, "rule98=userCertificate;binary=" TEST_CERT_BIN);
+    assert_non_null(domains);
+    assert_string_equal(domains[0], "test.dom");
+    assert_null(domains[1]);
+
     ret = sss_certmap_add_rule(ctx, 97,
                             "KRB5:<ISSUER>CN=Certificate Authority,O=IPA.DEVEL",
                             "LDAP:rule97=<I>{issuer_dn!nss_x500}<S>{subject_dn}",
                             dom_list);
     assert_int_equal(ret, 0);
     ret = sss_certmap_get_search_filter(ctx, discard_const(test_cert_der),
                                         sizeof(test_cert_der),
                                         &filter, &domains);
     assert_int_equal(ret, 0);
     assert_non_null(filter);
+    assert_string_equal(filter, "rule97=<I>O=IPA.DEVEL,CN=Certificate\\20Authority"
+                                "<S>CN=ipa-devel.ipa.devel,O=IPA.DEVEL");
+    assert_non_null(domains);
+    assert_string_equal(domains[0], "test.dom");
+    assert_null(domains[1]);
+
+    ret = sss_certmap_expand_mapping_rule(ctx, discard_const(test_cert_der),
+                                          sizeof(test_cert_der),
+                                          &filter, &domains);
+    assert_int_equal(ret, 0);
+    assert_non_null(filter);
     assert_string_equal(filter, "rule97=<I>O=IPA.DEVEL,CN=Certificate Authority"
                                 "<S>CN=ipa-devel.ipa.devel,O=IPA.DEVEL");
     assert_non_null(domains);
     assert_string_equal(domains[0], "test.dom");
     assert_null(domains[1]);
@@ -1490,10 +1531,21 @@ static void test_sss_certmap_get_search_filter(void **state)
     ret = sss_certmap_get_search_filter(ctx, discard_const(test_cert_der),
                                         sizeof(test_cert_der),
                                         &filter, &domains);
     assert_int_equal(ret, 0);
     assert_non_null(filter);
+    assert_string_equal(filter, "rule96=<I>O=IPA.DEVEL,CN=Certificate\\20Authority"
+                                "<S>O=IPA.DEVEL,CN=ipa-devel.ipa.devel");
+    assert_non_null(domains);
+    assert_string_equal(domains[0], "test.dom");
+    assert_null(domains[1]);
+
+    ret = sss_certmap_expand_mapping_rule(ctx, discard_const(test_cert_der),
+                                          sizeof(test_cert_der),
+                                          &filter, &domains);
+    assert_int_equal(ret, 0);
+    assert_non_null(filter);
     assert_string_equal(filter, "rule96=<I>O=IPA.DEVEL,CN=Certificate Authority"
                                 "<S>O=IPA.DEVEL,CN=ipa-devel.ipa.devel");
     assert_non_null(domains);
     assert_string_equal(domains[0], "test.dom");
     assert_null(domains[1]);
@@ -1508,26 +1560,44 @@ static void test_sss_certmap_get_search_filter(void **state)
     assert_int_equal(ret, 0);
     assert_non_null(filter);
     assert_string_equal(filter, "(userCertificate;binary=" TEST_CERT_BIN ")");
     assert_null(domains);
 
+    ret = sss_certmap_expand_mapping_rule(ctx, discard_const(test_cert_der),
+                                          sizeof(test_cert_der),
+                                          &filter, &domains);
+    assert_int_equal(ret, 0);
+    assert_non_null(filter);
+    assert_string_equal(filter, "(userCertificate;binary=" TEST_CERT_BIN ")");
+    assert_null(domains);
+
     ret = sss_certmap_add_rule(ctx, 94,
                       "KRB5:<ISSUER>CN=Certificate Authority,O=IPA.DEVEL",
                       "LDAP:rule94=<I>{issuer_dn!ad_x500}<S>{subject_dn!ad_x500}",
                             dom_list);
     assert_int_equal(ret, 0);
     ret = sss_certmap_get_search_filter(ctx, discard_const(test_cert_der),
                                         sizeof(test_cert_der),
                                         &filter, &domains);
     assert_int_equal(ret, 0);
     assert_non_null(filter);
-    assert_string_equal(filter, "rule94=<I>O=IPA.DEVEL,CN=Certificate Authority"
+    assert_string_equal(filter, "rule94=<I>O=IPA.DEVEL,CN=Certificate\\20Authority"
                                 "<S>O=IPA.DEVEL,CN=ipa-devel.ipa.devel");
     assert_non_null(domains);
     assert_string_equal(domains[0], "test.dom");
     assert_null(domains[1]);
 
+    ret = sss_certmap_expand_mapping_rule(ctx, discard_const(test_cert_der),
+                                          sizeof(test_cert_der),
+                                          &filter, &domains);
+    assert_int_equal(ret, 0);
+    assert_non_null(filter);
+    assert_string_equal(filter, "rule94=<I>O=IPA.DEVEL,CN=Certificate Authority"
+                                "<S>O=IPA.DEVEL,CN=ipa-devel.ipa.devel");
+    assert_non_null(domains);
+    assert_string_equal(domains[0], "test.dom");
+    assert_null(domains[1]);
 
     ret = sss_certmap_add_rule(ctx, 89, NULL,
                             "(rule89={subject_nt_principal})",
                             NULL);
     assert_int_equal(ret, 0);
@@ -1537,10 +1607,18 @@ static void test_sss_certmap_get_search_filter(void **state)
     assert_int_equal(ret, 0);
     assert_non_null(filter);
     assert_string_equal(filter, "(rule89=tu1@ad.devel)");
     assert_null(domains);
 
+    ret = sss_certmap_expand_mapping_rule(ctx, discard_const(test_cert2_der),
+                                          sizeof(test_cert2_der),
+                                          &filter, &domains);
+    assert_int_equal(ret, 0);
+    assert_non_null(filter);
+    assert_string_equal(filter, "(rule89=tu1@ad.devel)");
+    assert_null(domains);
+
     ret = sss_certmap_add_rule(ctx, 88, NULL,
                             "(rule88={subject_nt_principal.short_name})",
                             NULL);
     assert_int_equal(ret, 0);
     ret = sss_certmap_get_search_filter(ctx, discard_const(test_cert2_der),
@@ -1558,10 +1636,19 @@ static void test_sss_certmap_get_search_filter(void **state)
     ret = sss_certmap_get_search_filter(ctx, discard_const(test_cert2_der),
                                         sizeof(test_cert2_der),
                                         &filter, &domains);
     assert_int_equal(ret, 0);
     assert_non_null(filter);
+    assert_string_equal(filter, "rule87=<I>DC=devel,DC=ad,CN=ad-AD-SERVER-CA"
+                  "<S>DC=devel,DC=ad,CN=Users,CN=t\\20u,E=test.user@email.domain");
+    assert_null(domains);
+
+    ret = sss_certmap_expand_mapping_rule(ctx, discard_const(test_cert2_der),
+                                          sizeof(test_cert2_der),
+                                          &filter, &domains);
+    assert_int_equal(ret, 0);
+    assert_non_null(filter);
     assert_string_equal(filter, "rule87=<I>DC=devel,DC=ad,CN=ad-AD-SERVER-CA"
                   "<S>DC=devel,DC=ad,CN=Users,CN=t u,E=test.user@email.domain");
     assert_null(domains);
 
     ret = sss_certmap_add_rule(ctx, 86, NULL,
@@ -1571,10 +1658,19 @@ static void test_sss_certmap_get_search_filter(void **state)
     ret = sss_certmap_get_search_filter(ctx, discard_const(test_cert2_der),
                                         sizeof(test_cert2_der),
                                         &filter, &domains);
     assert_int_equal(ret, 0);
     assert_non_null(filter);
+    assert_string_equal(filter, "rule86=<I>DC=devel,DC=ad,CN=ad-AD-SERVER-CA"
+                  "<S>DC=devel,DC=ad,CN=Users,CN=t\\20u,E=test.user@email.domain");
+    assert_null(domains);
+
+    ret = sss_certmap_expand_mapping_rule(ctx, discard_const(test_cert2_der),
+                                          sizeof(test_cert2_der),
+                                          &filter, &domains);
+    assert_int_equal(ret, 0);
+    assert_non_null(filter);
     assert_string_equal(filter, "rule86=<I>DC=devel,DC=ad,CN=ad-AD-SERVER-CA"
                   "<S>DC=devel,DC=ad,CN=Users,CN=t u,E=test.user@email.domain");
     assert_null(domains);
 
 
diff --git a/src/util/util.c b/src/util/util.c
index d9bd3cb59..19d447328 100644
--- a/src/util/util.c
+++ b/src/util/util.c
@@ -434,104 +434,10 @@ errno_t sss_hash_create(TALLOC_CTX *mem_ctx, unsigned long count,
                         hash_table_t **tbl)
 {
     return sss_hash_create_ex(mem_ctx, count, tbl, 0, 0, 0, 0, NULL, NULL);
 }
 
-errno_t sss_filter_sanitize_ex(TALLOC_CTX *mem_ctx,
-                               const char *input,
-                               char **sanitized,
-                               const char *ignore)
-{
-    char *output;
-    size_t i = 0;
-    size_t j = 0;
-    char *allowed;
-
-    /* Assume the worst-case. We'll resize it later, once */
-    output = talloc_array(mem_ctx, char, strlen(input) * 3 + 1);
-    if (!output) {
-        return ENOMEM;
-    }
-
-    while (input[i]) {
-        /* Even though this character might have a special meaning, if it's
-         * explicitly allowed, just copy it and move on
-         */
-        if (ignore == NULL) {
-            allowed = NULL;
-        } else {
-            allowed = strchr(ignore, input[i]);
-        }
-        if (allowed) {
-            output[j++] = input[i++];
-            continue;
-        }
-
-        switch(input[i]) {
-        case '\t':
-            output[j++] = '\\';
-            output[j++] = '0';
-            output[j++] = '9';
-            break;
-        case ' ':
-            output[j++] = '\\';
-            output[j++] = '2';
-            output[j++] = '0';
-            break;
-        case '*':
-            output[j++] = '\\';
-            output[j++] = '2';
-            output[j++] = 'a';
-            break;
-        case '(':
-            output[j++] = '\\';
-            output[j++] = '2';
-            output[j++] = '8';
-            break;
-        case ')':
-            output[j++] = '\\';
-            output[j++] = '2';
-            output[j++] = '9';
-            break;
-        case '\\':
-            output[j++] = '\\';
-            output[j++] = '5';
-            output[j++] = 'c';
-            break;
-        case '\r':
-            output[j++] = '\\';
-            output[j++] = '0';
-            output[j++] = 'd';
-            break;
-        case '\n':
-            output[j++] = '\\';
-            output[j++] = '0';
-            output[j++] = 'a';
-            break;
-        default:
-            output[j++] = input[i];
-        }
-
-        i++;
-    }
-    output[j] = '\0';
-    *sanitized = talloc_realloc(mem_ctx, output, char, j+1);
-    if (!*sanitized) {
-        talloc_free(output);
-        return ENOMEM;
-    }
-
-    return EOK;
-}
-
-errno_t sss_filter_sanitize(TALLOC_CTX *mem_ctx,
-                            const char *input,
-                            char **sanitized)
-{
-    return sss_filter_sanitize_ex(mem_ctx, input, sanitized, NULL);
-}
-
 char *
 sss_escape_ip_address(TALLOC_CTX *mem_ctx, int family, const char *addr)
 {
     return family == AF_INET6 ? talloc_asprintf(mem_ctx, "[%s]", addr) :
                                 talloc_strdup(mem_ctx, addr);
diff --git a/src/util/util_ext.c b/src/util/util_ext.c
index 04dc02a8a..a89b60f76 100644
--- a/src/util/util_ext.c
+++ b/src/util/util_ext.c
@@ -27,10 +27,15 @@
 #include <string.h>
 #include <strings.h>
 
 #define EOK 0
 
+#ifndef HAVE_ERRNO_T
+#define HAVE_ERRNO_T
+typedef int errno_t;
+#endif
+
 int split_on_separator(TALLOC_CTX *mem_ctx, const char *str,
                        const char sep, bool trim, bool skip_empty,
                        char ***_list, int *size)
 {
     int ret;
@@ -139,5 +144,99 @@ bool string_in_list(const char *string, char **list, bool case_sensitive)
         }
     }
 
     return false;
 }
+
+errno_t sss_filter_sanitize_ex(TALLOC_CTX *mem_ctx,
+                               const char *input,
+                               char **sanitized,
+                               const char *ignore)
+{
+    char *output;
+    size_t i = 0;
+    size_t j = 0;
+    char *allowed;
+
+    /* Assume the worst-case. We'll resize it later, once */
+    output = talloc_array(mem_ctx, char, strlen(input) * 3 + 1);
+    if (!output) {
+        return ENOMEM;
+    }
+
+    while (input[i]) {
+        /* Even though this character might have a special meaning, if it's
+         * explicitly allowed, just copy it and move on
+         */
+        if (ignore == NULL) {
+            allowed = NULL;
+        } else {
+            allowed = strchr(ignore, input[i]);
+        }
+        if (allowed) {
+            output[j++] = input[i++];
+            continue;
+        }
+
+        switch(input[i]) {
+        case '\t':
+            output[j++] = '\\';
+            output[j++] = '0';
+            output[j++] = '9';
+            break;
+        case ' ':
+            output[j++] = '\\';
+            output[j++] = '2';
+            output[j++] = '0';
+            break;
+        case '*':
+            output[j++] = '\\';
+            output[j++] = '2';
+            output[j++] = 'a';
+            break;
+        case '(':
+            output[j++] = '\\';
+            output[j++] = '2';
+            output[j++] = '8';
+            break;
+        case ')':
+            output[j++] = '\\';
+            output[j++] = '2';
+            output[j++] = '9';
+            break;
+        case '\\':
+            output[j++] = '\\';
+            output[j++] = '5';
+            output[j++] = 'c';
+            break;
+        case '\r':
+            output[j++] = '\\';
+            output[j++] = '0';
+            output[j++] = 'd';
+            break;
+        case '\n':
+            output[j++] = '\\';
+            output[j++] = '0';
+            output[j++] = 'a';
+            break;
+        default:
+            output[j++] = input[i];
+        }
+
+        i++;
+    }
+    output[j] = '\0';
+    *sanitized = talloc_realloc(mem_ctx, output, char, j+1);
+    if (!*sanitized) {
+        talloc_free(output);
+        return ENOMEM;
+    }
+
+    return EOK;
+}
+
+errno_t sss_filter_sanitize(TALLOC_CTX *mem_ctx,
+                            const char *input,
+                            char **sanitized)
+{
+    return sss_filter_sanitize_ex(mem_ctx, input, sanitized, NULL);
+}

Vulnerable code:     -version-info 1:0:1
                      struct sss_cert_content *cert_content,
                                  &expanded);
int sss_certmap_get_search_filter(struct sss_certmap_ctx *ctx,
        ret = get_filter(ctx, ctx->default_mapping_rule, cert_content, &filter);
                                 &filter);
 * @param[in] der_cert binary blog with the DER encoded certificate
 * @param[in] der_cert binary blog with the DER encoded certificate
 * @param[out] filter  LDAP filter string, caller should free the data by
 *                     calling sss_certmap_free_filter_and_domains
 * @param[in]  der_cert   binary blog with the DER encoded certificate
    ret = sss_certmap_get_search_filter(ctx, der, der_size, &filter, &domains);
        DEBUG(SSSDBG_OP_FAILURE, "sss_certmap_get_search_filter failed.\n");
    assert_string_equal(filter, "rule94=<I>O=IPA.DEVEL,CN=Certificate Authority"
errno_t sss_filter_sanitize_ex(TALLOC_CTX *mem_ctx,
                               const char *input,
                               char **sanitized,
                               const char *ignore)
{
    char *output;
    size_t i = 0;
    size_t j = 0;
    char *allowed;

    /* Assume the worst-case. We'll resize it later, once */
    output = talloc_array(mem_ctx, char, strlen(input) * 3 + 1);
    if (!output) {
        return ENOMEM;
    }

    while (input[i]) {
        /* Even though this character might have a special meaning, if it's
         * explicitly allowed, just copy it and move on
         */
        if (ignore == NULL) {
            allowed = NULL;
        } else {
            allowed = strchr(ignore, input[i]);
        }
        if (allowed) {
            output[j++] = input[i++];
            continue;
        }

        switch(input[i]) {
        case '\t':
            output[j++] = '\\';
            output[j++] = '0';
            output[j++] = '9';
            break;
        case ' ':
            output[j++] = '\\';
            output[j++] = '2';
            output[j++] = '0';
            break;
        case '*':
            output[j++] = '\\';
            output[j++] = '2';
            output[j++] = 'a';
            break;
        case '(':
            output[j++] = '\\';
            output[j++] = '2';
            output[j++] = '8';
            break;
        case ')':
            output[j++] = '\\';
            output[j++] = '2';
            output[j++] = '9';
            break;
        case '\\':
            output[j++] = '\\';
            output[j++] = '5';
            output[j++] = 'c';
            break;
        case '\r':
            output[j++] = '\\';
            output[j++] = '0';
            output[j++] = 'd';
            break;
        case '\n':
            output[j++] = '\\';
            output[j++] = '0';
            output[j++] = 'a';
            break;
        default:
            output[j++] = input[i];
        }

        i++;
    }
    output[j] = '\0';
    *sanitized = talloc_realloc(mem_ctx, output, char, j+1);
    if (!*sanitized) {
        talloc_free(output);
        return ENOMEM;
    }

    return EOK;
}

errno_t sss_filter_sanitize(TALLOC_CTX *mem_ctx,
                            const char *input,
                            char **sanitized)
{
    return sss_filter_sanitize_ex(mem_ctx, input, sanitized, NULL);
}

Fixed code:     -version-info 2:0:2
                           bool sanitize,
    char *exp_sanitized = NULL;
        /* cert blob is already sanitized */
        sanitize = false;
    if (sanitize) {
        ret = sss_filter_sanitize(ctx, exp, &exp_sanitized);
        if (ret != EOK) {
            CM_DEBUG(ctx, "Failed to sanitize expanded template.");
            goto done;
        }
        talloc_free(exp);
        exp = exp_sanitized;
    }

                      struct sss_cert_content *cert_content, bool sanitize,
                                  sanitize, &expanded);
static int expand_mapping_rule_ex(struct sss_certmap_ctx *ctx,
                                  bool sanitize,
        ret = get_filter(ctx, ctx->default_mapping_rule, cert_content, sanitize,
                         &filter);
                                 sanitize, &filter);
int sss_certmap_get_search_filter(struct sss_certmap_ctx *ctx,
                                  const uint8_t *der_cert, size_t der_size,
                                  char **_filter, char ***_domains)
{
    return expand_mapping_rule_ex(ctx, der_cert, der_size, true,
                                  _filter, _domains);
}

int sss_certmap_expand_mapping_rule(struct sss_certmap_ctx *ctx,
                                    const uint8_t *der_cert, size_t der_size,
                                    char **_expanded, char ***_domains)
{
    return expand_mapping_rule_ex(ctx, der_cert, der_size, false,
                                  _expanded, _domains);
}


SSS_CERTMAP_0.2 {
    global:
        sss_certmap_expand_mapping_rule;
} SSS_CERTMAP_0.1;
 * @param[in] der_cert binary blob with the DER encoded certificate
 * @param[in] der_cert binary blob with the DER encoded certificate
 * @param[out] filter  LDAP filter string, expanded templates are sanitized,
 *                     caller should free the data by calling
 *                     sss_certmap_free_filter_and_domains
/**
 * @brief Expand the mapping rule by replacing the templates
 *
 * @param[in] ctx        certmap context previously initialized with
 *                       @ref sss_certmap_init
 * @param[in] der_cert   binary blob with the DER encoded certificate
 * @param[in] der_size   size of the certificate blob
 * @param[out] expanded  expanded mapping rule, templates are filled in
 *                       verbatim in contrast to sss_certmap_get_search_filter,
 *                       caller should free the data by
 *                       calling sss_certmap_free_filter_and_domains
 * @param[out] domains   NULL-terminated array of strings with the domains the
 *                       rule applies, caller should free the data by calling
 *                       sss_certmap_free_filter_and_domains
 *
 * @return
 *  - 0:      certificate matches a rule
 *  - ENOENT: certificate does not match
 *  - EINVAL: internal error
 */
int sss_certmap_expand_mapping_rule(struct sss_certmap_ctx *ctx,
                                    const uint8_t *der_cert, size_t der_size,
                                    char **_expanded, char ***_domains);
 *        and @ref sss_certmap_expand_mapping_rule
 * @param[in]  der_cert   binary blob with the DER encoded certificate
    ret = sss_certmap_expand_mapping_rule(ctx, der, der_size,
                                          &filter, &domains);
        DEBUG(SSSDBG_OP_FAILURE, "sss_certmap_expand_mapping_rule failed.\n");
    assert_string_equal(filter, "rule100=<I>CN=Certificate\\20Authority,O=IPA.DEVEL"
                                "<S>CN=ipa-devel.ipa.devel,O=IPA.DEVEL");
    assert_null(domains);

    ret = sss_certmap_expand_mapping_rule(ctx, discard_const(test_cert_der),
                                          sizeof(test_cert_der),
                                          &filter, &domains);
    assert_int_equal(ret, 0);
    assert_non_null(filter);
    assert_string_equal(filter, "rule99=<I>CN=Certificate\\20Authority,O=IPA.DEVEL"
                                "<S>CN=ipa-devel.ipa.devel,O=IPA.DEVEL");
    assert_non_null(domains);
    assert_string_equal(domains[0], "test.dom");
    assert_null(domains[1]);

    ret = sss_certmap_expand_mapping_rule(ctx, discard_const(test_cert_der),
                                          sizeof(test_cert_der),
                                          &filter, &domains);
    assert_int_equal(ret, 0);
    assert_non_null(filter);
    ret = sss_certmap_expand_mapping_rule(ctx, discard_const(test_cert_der),
                                          sizeof(test_cert_der),
                                          &filter, &domains);
    assert_int_equal(ret, 0);
    assert_non_null(filter);
    assert_string_equal(filter, "rule98=userCertificate;binary=" TEST_CERT_BIN);
    assert_non_null(domains);
    assert_string_equal(domains[0], "test.dom");
    assert_null(domains[1]);

    assert_string_equal(filter, "rule97=<I>O=IPA.DEVEL,CN=Certificate\\20Authority"
                                "<S>CN=ipa-devel.ipa.devel,O=IPA.DEVEL");
    assert_non_null(domains);
    assert_string_equal(domains[0], "test.dom");
    assert_null(domains[1]);

    ret = sss_certmap_expand_mapping_rule(ctx, discard_const(test_cert_der),
                                          sizeof(test_cert_der),
                                          &filter, &domains);
    assert_int_equal(ret, 0);
    assert_non_null(filter);
    assert_string_equal(filter, "rule96=<I>O=IPA.DEVEL,CN=Certificate\\20Authority"
                                "<S>O=IPA.DEVEL,CN=ipa-devel.ipa.devel");
    assert_non_null(domains);
    assert_string_equal(domains[0], "test.dom");
    assert_null(domains[1]);

    ret = sss_certmap_expand_mapping_rule(ctx, discard_const(test_cert_der),
                                          sizeof(test_cert_der),
                                          &filter, &domains);
    assert_int_equal(ret, 0);
    assert_non_null(filter);
    ret = sss_certmap_expand_mapping_rule(ctx, discard_const(test_cert_der),
                                          sizeof(test_cert_der),
                                          &filter, &domains);
    assert_int_equal(ret, 0);
    assert_non_null(filter);
    assert_string_equal(filter, "(userCertificate;binary=" TEST_CERT_BIN ")");
    assert_null(domains);

    assert_string_equal(filter, "rule94=<I>O=IPA.DEVEL,CN=Certificate\\20Authority"
    ret = sss_certmap_expand_mapping_rule(ctx, discard_const(test_cert_der),
                                          sizeof(test_cert_der),
                                          &filter, &domains);
    assert_int_equal(ret, 0);
    assert_non_null(filter);
    assert_string_equal(filter, "rule94=<I>O=IPA.DEVEL,CN=Certificate Authority"
                                "<S>O=IPA.DEVEL,CN=ipa-devel.ipa.devel");
    assert_non_null(domains);
    assert_string_equal(domains[0], "test.dom");
    assert_null(domains[1]);
    ret = sss_certmap_expand_mapping_rule(ctx, discard_const(test_cert2_der),
                                          sizeof(test_cert2_der),
                                          &filter, &domains);
    assert_int_equal(ret, 0);
    assert_non_null(filter);
    assert_string_equal(filter, "(rule89=tu1@ad.devel)");
    assert_null(domains);

    assert_string_equal(filter, "rule87=<I>DC=devel,DC=ad,CN=ad-AD-SERVER-CA"
                  "<S>DC=devel,DC=ad,CN=Users,CN=t\\20u,E=test.user@email.domain");
    assert_null(domains);

    ret = sss_certmap_expand_mapping_rule(ctx, discard_const(test_cert2_der),
                                          sizeof(test_cert2_der),
                                          &filter, &domains);
    assert_int_equal(ret, 0);
    assert_non_null(filter);
    assert_string_equal(filter, "rule86=<I>DC=devel,DC=ad,CN=ad-AD-SERVER-CA"
                  "<S>DC=devel,DC=ad,CN=Users,CN=t\\20u,E=test.user@email.domain");
    assert_null(domains);

    ret = sss_certmap_expand_mapping_rule(ctx, discard_const(test_cert2_der),
                                          sizeof(test_cert2_der),
                                          &filter, &domains);
    assert_int_equal(ret, 0);
    assert_non_null(filter);
#ifndef HAVE_ERRNO_T
#define HAVE_ERRNO_T
typedef int errno_t;
#endif


errno_t sss_filter_sanitize_ex(TALLOC_CTX *mem_ctx,
                               const char *input,
                               char **sanitized,
                               const char *ignore)
{
    char *output;
    size_t i = 0;
    size_t j = 0;
    char *allowed;

    /* Assume the worst-case. We'll resize it later, once */
    output = talloc_array(mem_ctx, char, strlen(input) * 3 + 1);
    if (!output) {
        return ENOMEM;
    }

    while (input[i]) {
        /* Even though this character might have a special meaning, if it's
         * explicitly allowed, just copy it and move on
         */
        if (ignore == NULL) {
            allowed = NULL;
        } else {
            allowed = strchr(ignore, input[i]);
        }
        if (allowed) {
            output[j++] = input[i++];
            continue;
        }

        switch(input[i]) {
        case '\t':
            output[j++] = '\\';
            output[j++] = '0';
            output[j++] = '9';
            break;
        case ' ':
            output[j++] = '\\';
            output[j++] = '2';
            output[j++] = '0';
            break;
        case '*':
            output[j++] = '\\';
            output[j++] = '2';
            output[j++] = 'a';
            break;
        case '(':
            output[j++] = '\\';
            output[j++] = '2';
            output[j++] = '8';
            break;
        case ')':
            output[j++] = '\\';
            output[j++] = '2';
            output[j++] = '9';
            break;
        case '\\':
            output[j++] = '\\';
            output[j++] = '5';
            output[j++] = 'c';
            break;
        case '\r':
            output[j++] = '\\';
            output[j++] = '0';
            output[j++] = 'd';
            break;
        case '\n':
            output[j++] = '\\';
            output[j++] = '0';
            output[j++] = 'a';
            break;
        default:
            output[j++] = input[i];
        }

        i++;
    }
    output[j] = '\0';
    *sanitized = talloc_realloc(mem_ctx, output, char, j+1);
    if (!*sanitized) {
        talloc_free(output);
        return ENOMEM;
    }

    return EOK;
}

errno_t sss_filter_sanitize(TALLOC_CTX *mem_ctx,
                            const char *input,
                            char **sanitized)
{
    return sss_filter_sanitize_ex(mem_ctx, input, sanitized, NULL);
}