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;
}