Commit 6652f98839 for openssl.org

commit 6652f988399b171e113e07d7f4ec7f46d49598e5
Author: Mounir IDRASSI <mounir.idrassi@idrix.fr>
Date:   Wed Sep 2 18:47:32 2026 +0900

    x509: filter get1 certificate and CRL results by name

    As reported in PR #31720 by @huiyuexu,
    X509_STORE_CTX_get1_certs() and X509_STORE_CTX_get1_crls() can return
    unrelated objects after X509_STORE_get0_objects() converts a store to
    its flat representation.

    Both helpers obtain the first matching index and exact match count from
    x509_object_idx_cnt(). OPENSSL_sk_find_all() returns an exact count for
    both sorted and unsorted stacks, but unsorted matches need not be
    contiguous. The flat store is not guaranteed to be sorted, and a hash
    collision can likewise mix distinct names in one per-hash stack.

    Keep the first-match index, re-check each candidate subject or issuer
    name, decrement the count only for genuine matches, and stop after
    collecting them all. This avoids rescanning the prefix or changing
    insertion and sorting behavior.

    Extend the store lookup test with exact object identity checks in both
    store representations. Switch a second store to the flat representation
    before insertion, then place a foreign certificate between two matches
    and do the same for CRLs. This deterministic layout fails if either name
    check is omitted.

    Reported-by: huiyuexu <xuhuiyue@huawei.com>
    Fixes: 04589b59ef50 ("x509store: reduce lock contention in X509_STORE")
    Assisted-by: Codex:gpt-5.6-sol
    Reviewed-by: Nikola Pajkovsky <nikolap@openssl.org>
    Reviewed-by: Richard Levitte <levitte@openssl.foundation>
    Merge-date: Tue Nov 10 10:43:59 2026
    Merged-from: https://github.com/openssl/openssl/pull/32659

diff --git a/crypto/x509/x509_lu.c b/crypto/x509/x509_lu.c
index 09efa02445..6b4ebe60d9 100644
--- a/crypto/x509/x509_lu.c
+++ b/crypto/x509/x509_lu.c
@@ -953,16 +953,25 @@ STACK_OF(X509) *X509_STORE_CTX_get1_certs(const X509_STORE_CTX *ctx,
     sk = sk_X509_new_null();
     if (idx < 0 || sk == NULL)
         goto end;
-    for (i = idx; i < sk_X509_OBJECT_num(objs); i++) {
+    for (i = idx; cnt > 0 && i < sk_X509_OBJECT_num(objs); i++) {
         obj = sk_X509_OBJECT_value(objs, i);
-        x = obj->data.x509;
         if (obj->type != X509_LU_X509)
             continue;
+        x = obj->data.x509;
+        /*
+         * x509_object_idx_cnt() returns the exact number of matches, but they
+         * need not be contiguous when |objs| is unsorted.  Hash collisions
+         * can likewise mix several names into one hash-table stack. Re-check
+         * the subject name and stop once all |cnt| matches have been added.
+         */
+        if (X509_NAME_cmp(X509_get_subject_name(x), nm) != 0)
+            continue;
         if (X509_add_cert(sk, x, X509_ADD_FLAG_UP_REF) == 0) {
             X509_STORE_unlock(store);
             OSSL_STACK_OF_X509_free(sk);
             return NULL;
         }
+        cnt--;
     }

 end:
@@ -1003,11 +1012,14 @@ STACK_OF(X509_CRL) *X509_STORE_CTX_get1_crls(const X509_STORE_CTX *ctx,
     if (idx < 0)
         goto end;

-    for (i = idx; i < sk_X509_OBJECT_num(objs); i++) {
+    for (i = idx; cnt > 0 && i < sk_X509_OBJECT_num(objs); i++) {
         obj = sk_X509_OBJECT_value(objs, i);
-        x = obj->data.crl;
         if (obj->type != X509_LU_CRL)
             continue;
+        x = obj->data.crl;
+        /* See the comment in X509_STORE_CTX_get1_certs() */
+        if (X509_NAME_cmp(X509_CRL_get_issuer(x), nm) != 0)
+            continue;
         if (!X509_CRL_up_ref(x)) {
             X509_STORE_unlock(store);
             sk_X509_CRL_pop_free(sk, X509_CRL_free);
@@ -1019,6 +1031,7 @@ STACK_OF(X509_CRL) *X509_STORE_CTX_get1_crls(const X509_STORE_CTX *ctx,
             sk_X509_CRL_pop_free(sk, X509_CRL_free);
             return NULL;
         }
+        cnt--;
     }
 end:
     X509_STORE_unlock(store);
diff --git a/test/x509_load_cert_file_test.c b/test/x509_load_cert_file_test.c
index bfcd3521f5..b1c205ede6 100644
--- a/test/x509_load_cert_file_test.c
+++ b/test/x509_load_cert_file_test.c
@@ -113,62 +113,150 @@ err:
     return ret;
 }

-/*
- * Test loading multiple certificates and a CRL with identical CN into
- * X509_STORE.
- */
-static int test_load_same_cn_certs(void)
+static int check_get1_certs(X509_STORE_CTX *ctx, const X509_NAME *name,
+    X509 *cert1, X509 *cert2)
 {
-    X509 *cert1 = NULL, *cert2 = NULL;
-    X509_CRL *crl2 = NULL;
+    STACK_OF(X509) *certs = NULL;
+    X509 *got1, *got2;
     int ret = 0;
-    X509_STORE *store = NULL;
-    X509_STORE_CTX *s_ctx = NULL;
-#ifndef OPENSSL_NO_DEPRECATED_4_0
-    STACK_OF(X509_OBJECT) *objs = NULL;
-#endif
-    STACK_OF(X509_CRL) *sk_x509_crl = NULL;
-    STACK_OF(X509) *sk_x509 = NULL;
-    X509_NAME *nm = NULL;
-
-    if (!TEST_ptr(nm = X509_NAME_new())
-        || !TEST_true(X509_NAME_add_entry_by_txt(nm, "CN", MBSTRING_ASC,
-            (unsigned char *)"www.example.test",
-            -1, -1, 0)))
+
+    if (!TEST_ptr(certs = X509_STORE_CTX_get1_certs(ctx, name))
+        || !TEST_int_eq(sk_X509_num(certs), cert2 == NULL ? 1 : 2))
         goto err;

+    got1 = sk_X509_value(certs, 0);
+    if (cert2 == NULL) {
+        if (!TEST_ptr_eq(got1, cert1))
+            goto err;
+    } else {
+        got2 = sk_X509_value(certs, 1);
+        if (!TEST_true((got1 == cert1 && got2 == cert2)
+                || (got1 == cert2 && got2 == cert1)))
+            goto err;
+    }
+    ret = 1;
+
+err:
+    OSSL_STACK_OF_X509_free(certs);
+    return ret;
+}
+
+static int check_get1_crls(X509_STORE_CTX *ctx, const X509_NAME *name,
+    X509_CRL *crl1, X509_CRL *crl2)
+{
+    STACK_OF(X509_CRL) *crls = NULL;
+    X509_CRL *got1, *got2;
+    int ret = 0;
+
+    if (!TEST_ptr(crls = X509_STORE_CTX_get1_crls(ctx, name))
+        || !TEST_int_eq(sk_X509_CRL_num(crls), crl2 == NULL ? 1 : 2))
+        goto err;
+
+    got1 = sk_X509_CRL_value(crls, 0);
+    if (crl2 == NULL) {
+        if (!TEST_ptr_eq(got1, crl1))
+            goto err;
+    } else {
+        got2 = sk_X509_CRL_value(crls, 1);
+        if (!TEST_true((got1 == crl1 && got2 == crl2)
+                || (got1 == crl2 && got2 == crl1)))
+            goto err;
+    }
+    ret = 1;
+
+err:
+    sk_X509_CRL_pop_free(crls, X509_CRL_free);
+    return ret;
+}
+
+static int check_store_get1_by_name(X509_STORE_CTX *ctx, const X509_NAME *name1,
+    const X509_NAME *nameA, X509 *cert1, X509 *cert2, X509 *certA,
+    X509_CRL *crl1, X509_CRL *crl2, X509_CRL *crlA)
+{
+    return check_get1_certs(ctx, name1, cert1, cert2)
+        && check_get1_certs(ctx, nameA, certA, NULL)
+        && check_get1_crls(ctx, name1, crl1, crl2)
+        && check_get1_crls(ctx, nameA, crlA, NULL);
+}
+
+/* Check exact lookup results in hash table and flat store representations. */
+static int test_load_same_cn_certs(void)
+{
+    X509 *cert1 = NULL, *cert2 = NULL, *certA = NULL;
+    X509_CRL *crl1 = NULL, *crl2 = NULL, *crlA = NULL;
+    X509_STORE *store = NULL;
+    X509_STORE_CTX *ctx = NULL;
+    const X509_NAME *name1;
+    X509_NAME *nameA = NULL;
+    int ret = 0;
+
     if (!TEST_ptr(cert1 = X509_from_strings(cn_cert1))
         || !TEST_ptr(cert2 = X509_from_strings(cn_cert2))
-        || !TEST_ptr(crl2 = CRL_from_strings(cn_crl2))
-        || !TEST_ptr(s_ctx = X509_STORE_CTX_new())
+        || !TEST_ptr(crl1 = CRL_from_strings(cn_crl2))
+        || !TEST_ptr(name1 = X509_get_subject_name(cert1))
+        || !TEST_ptr(nameA = X509_NAME_new())
+        || !TEST_true(X509_NAME_add_entry_by_txt(nameA, "CN", MBSTRING_ASC,
+            (unsigned char *)"alpha.test", -1, -1, 0))
+        || !TEST_ptr(certA = X509_dup(cert1))
+        || !TEST_true(X509_set_subject_name(certA, nameA))
+        || !TEST_ptr(crl2 = X509_CRL_new())
+        || !TEST_true(X509_CRL_set_issuer_name(crl2, name1))
+        || !TEST_ptr(crlA = X509_CRL_new())
+        || !TEST_true(X509_CRL_set_issuer_name(crlA, nameA))
+        || !TEST_ptr(ctx = X509_STORE_CTX_new())
         || !TEST_ptr(store = X509_STORE_new())
-        || !TEST_true(X509_STORE_CTX_init(s_ctx, store, NULL, NULL))
+        || !TEST_true(X509_STORE_CTX_init(ctx, store, NULL, NULL))
         || !TEST_true(X509_STORE_add_cert(store, cert1))
-        || !TEST_true(X509_STORE_add_crl(store, crl2))
+        || !TEST_true(X509_STORE_add_crl(store, crl1))
         || !TEST_true(X509_STORE_add_cert(store, cert2))
-    /* deliberately not taking lock in a single thread */
+        || !TEST_true(X509_STORE_add_crl(store, crl2))
+        || !TEST_true(X509_STORE_add_cert(store, certA))
+        || !TEST_true(X509_STORE_add_crl(store, crlA))
+        || !check_store_get1_by_name(ctx, name1, nameA, cert1, cert2, certA,
+            crl1, crl2, crlA))
+        goto err;
+
 #ifndef OPENSSL_NO_DEPRECATED_4_0
-        || !TEST_ptr(objs = X509_STORE_get0_objects(store))
-        || !TEST_int_eq(sk_X509_OBJECT_num(objs), 3)
-#endif
-        || !TEST_ptr(sk_x509 = X509_STORE_CTX_get1_certs(s_ctx, nm))
-        || !TEST_int_eq(sk_X509_num(sk_x509), 2)
-        || !TEST_ptr(sk_x509_crl = X509_STORE_CTX_get1_crls(s_ctx, nm))
-        || !TEST_int_eq(sk_X509_CRL_num(sk_x509_crl), 1))
+    /* Exercise conversion of an already populated store. */
+    if (!TEST_ptr(X509_STORE_get0_objects(store))
+        || !check_store_get1_by_name(ctx, name1, nameA, cert1, cert2, certA,
+            crl1, crl2, crlA))
+        goto err;
+
+    X509_STORE_CTX_free(ctx);
+    ctx = NULL;
+    X509_STORE_free(store);
+    store = NULL;
+
+    /* Put a foreign object between the two matching objects of each type. */
+    if (!TEST_ptr(ctx = X509_STORE_CTX_new())
+        || !TEST_ptr(store = X509_STORE_new())
+        || !TEST_true(X509_STORE_CTX_init(ctx, store, NULL, NULL))
+        /* Deliberately single-threaded: this call exposes internal state. */
+        || !TEST_ptr(X509_STORE_get0_objects(store))
+        || !TEST_true(X509_STORE_add_cert(store, cert1))
+        || !TEST_true(X509_STORE_add_cert(store, certA))
+        || !TEST_true(X509_STORE_add_cert(store, cert2))
+        || !TEST_true(X509_STORE_add_crl(store, crl1))
+        || !TEST_true(X509_STORE_add_crl(store, crlA))
+        || !TEST_true(X509_STORE_add_crl(store, crl2))
+        || !check_store_get1_by_name(ctx, name1, nameA, cert1, cert2, certA,
+            crl1, crl2, crlA))
         goto err;
+#endif

     ret = 1;

 err:
-    X509_NAME_free(nm);
-    OSSL_STACK_OF_X509_free(sk_x509);
-    sk_X509_CRL_pop_free(sk_x509_crl, X509_CRL_free);
+    X509_NAME_free(nameA);
+    X509_STORE_CTX_free(ctx);
     X509_STORE_free(store);
-    X509_STORE_CTX_free(s_ctx);
     X509_free(cert1);
     X509_free(cert2);
+    X509_free(certA);
+    X509_CRL_free(crl1);
     X509_CRL_free(crl2);
-
+    X509_CRL_free(crlA);
     return ret;
 }