Skip to content

Commit 08cecb4

Browse files
davidbent8m
authored andcommitted
Add X509_STORE_get1_objects
X509_STORE_get0_objects returns a pointer to the X509_STORE's storage, but this function is a bit deceptive. It is practically unusable in a multi-threaded program. See, for example, RUSTSEC-2023-0072, a security vulnerability caused by this OpenSSL API. One might think that, if no other threads are mutating the X509_STORE, it is safe to read the resulting list. However, the documention does not mention that other logically-const operations on the X509_STORE, notably certifcate verifications when a hash_dir is installed, will, under a lock, write to the X509_STORE. The X509_STORE also internally re-sorts the list on the first query. If the caller knows to call X509_STORE_lock and X509_STORE_unlock, it can work around this. But this is not obvious, and the documentation does not discuss how X509_STORE_lock is very rarely safe to use. E.g. one cannot call any APIs like X509_STORE_add_cert or X509_STORE_CTX_get1_issuer while holding the lock because those functions internally expect to take the lock. (X509_STORE_lock is another such API which is not safe to export as public API.) Rather than leave all this to the caller to figure out, the API should have returned a shallow copy of the list, refcounting the values. Then it could be internally locked and the caller can freely inspect the result without synchronization with the X509_STORE. Reviewed-by: Neil Horman <nhorman@openssl.org> Reviewed-by: Tomas Mraz <tomas@openssl.org> (Merged from #23224)
1 parent ffeae4c commit 08cecb4

5 files changed

Lines changed: 65 additions & 9 deletions

File tree

crypto/x509/x509_lu.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,36 @@ STACK_OF(X509_OBJECT) *X509_STORE_get0_objects(const X509_STORE *xs)
583583
return xs->objs;
584584
}
585585

586+
static X509_OBJECT *x509_object_dup(const X509_OBJECT *obj)
587+
{
588+
X509_OBJECT *ret = X509_OBJECT_new();
589+
if (ret == NULL)
590+
return NULL;
591+
592+
ret->type = obj->type;
593+
ret->data = obj->data;
594+
X509_OBJECT_up_ref_count(ret);
595+
return ret;
596+
}
597+
598+
STACK_OF(X509_OBJECT) *X509_STORE_get1_objects(X509_STORE *store)
599+
{
600+
STACK_OF(X509_OBJECT) *objs;
601+
602+
if (store == NULL) {
603+
ERR_raise(ERR_LIB_X509, ERR_R_PASSED_NULL_PARAMETER);
604+
return NULL;
605+
}
606+
607+
if (!x509_store_read_lock(store))
608+
return NULL;
609+
610+
objs = sk_X509_OBJECT_deep_copy(store->objs, x509_object_dup,
611+
X509_OBJECT_free);
612+
X509_STORE_unlock(store);
613+
return objs;
614+
}
615+
586616
STACK_OF(X509) *X509_STORE_get1_all_certs(X509_STORE *store)
587617
{
588618
STACK_OF(X509) *sk;

doc/man3/X509_STORE_get0_param.pod

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
=head1 NAME
44

55
X509_STORE_get0_param, X509_STORE_set1_param,
6-
X509_STORE_get0_objects, X509_STORE_get1_all_certs
6+
X509_STORE_get1_objects, X509_STORE_get0_objects, X509_STORE_get1_all_certs
77
- X509_STORE setter and getter functions
88

99
=head1 SYNOPSIS
@@ -12,6 +12,7 @@ X509_STORE_get0_objects, X509_STORE_get1_all_certs
1212

1313
X509_VERIFY_PARAM *X509_STORE_get0_param(const X509_STORE *xs);
1414
int X509_STORE_set1_param(X509_STORE *xs, const X509_VERIFY_PARAM *pm);
15+
STACK_OF(X509_OBJECT) *X509_STORE_get1_objects(X509_STORE *xs);
1516
STACK_OF(X509_OBJECT) *X509_STORE_get0_objects(const X509_STORE *xs);
1617
STACK_OF(X509) *X509_STORE_get1_all_certs(X509_STORE *xs);
1718

@@ -23,9 +24,15 @@ X509_STORE_get0_param() retrieves an internal pointer to the verification
2324
parameters for I<xs>. The returned pointer must not be freed by the
2425
calling application
2526

27+
X509_STORE_get1_objects() returns a snapshot of all objects in the store's X509
28+
cache. The cache contains B<X509> and B<X509_CRL> objects. The caller is
29+
responsible for freeing the returned list.
30+
2631
X509_STORE_get0_objects() retrieves an internal pointer to the store's
2732
X509 object cache. The cache contains B<X509> and B<X509_CRL> objects. The
28-
returned pointer must not be freed by the calling application.
33+
returned pointer must not be freed by the calling application. If the store is
34+
shared across multiple threads, it is not safe to use the result of this
35+
function. Use X509_STORE_get1_objects() instead, which avoids this problem.
2936

3037
X509_STORE_get1_all_certs() returns a list of all certificates in the store.
3138
The caller is responsible for freeing the returned list.
@@ -37,6 +44,9 @@ B<X509_VERIFY_PARAM> structure.
3744

3845
X509_STORE_set1_param() returns 1 for success and 0 for failure.
3946

47+
X509_STORE_get1_objects() returns a pointer to a stack of the retrieved
48+
objects on success, else NULL.
49+
4050
X509_STORE_get0_objects() returns a pointer to a stack of B<X509_OBJECT>.
4151

4252
X509_STORE_get1_all_certs() returns a pointer to a stack of the retrieved
@@ -51,6 +61,7 @@ L<X509_STORE_new(3)>
5161
B<X509_STORE_get0_param> and B<X509_STORE_get0_objects> were added in
5262
OpenSSL 1.1.0.
5363
B<X509_STORE_get1_certs> was added in OpenSSL 3.0.
64+
B<X509_STORE_get1_objects> was added in OpenSSL 3.3.
5465

5566
=head1 COPYRIGHT
5667

include/openssl/x509_vfy.h.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@ int X509_STORE_lock(X509_STORE *xs);
400400
int X509_STORE_unlock(X509_STORE *xs);
401401
int X509_STORE_up_ref(X509_STORE *xs);
402402
STACK_OF(X509_OBJECT) *X509_STORE_get0_objects(const X509_STORE *xs);
403+
STACK_OF(X509_OBJECT) *X509_STORE_get1_objects(X509_STORE *xs);
403404
STACK_OF(X509) *X509_STORE_get1_all_certs(X509_STORE *xs);
404405
STACK_OF(X509) *X509_STORE_CTX_get1_certs(X509_STORE_CTX *xs,
405406
const X509_NAME *nm);

test/x509_load_cert_file_test.c

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,32 @@ static const char *chain;
1515

1616
static int test_load_cert_file(void)
1717
{
18-
int ret = 0;
18+
int ret = 0, i;
1919
X509_STORE *store = NULL;
2020
X509_LOOKUP *lookup = NULL;
2121
STACK_OF(X509) *certs = NULL;
22+
STACK_OF(X509_OBJECT) *objs = NULL;
23+
24+
if (!TEST_ptr(store = X509_STORE_new())
25+
|| !TEST_ptr(lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()))
26+
|| !TEST_true(X509_load_cert_file(lookup, chain, X509_FILETYPE_PEM))
27+
|| !TEST_ptr(certs = X509_STORE_get1_all_certs(store))
28+
|| !TEST_int_eq(sk_X509_num(certs), 4)
29+
|| !TEST_ptr(objs = X509_STORE_get1_objects(store))
30+
|| !TEST_int_eq(sk_X509_OBJECT_num(objs), 4))
31+
goto err;
32+
33+
for (i = 0; i < sk_X509_OBJECT_num(objs); i++) {
34+
const X509_OBJECT *obj = sk_X509_OBJECT_value(objs, i);
35+
if (!TEST_int_eq(X509_OBJECT_get_type(obj), X509_LU_X509))
36+
goto err;
37+
}
2238

23-
if (TEST_ptr(store = X509_STORE_new())
24-
&& TEST_ptr(lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()))
25-
&& TEST_true(X509_load_cert_file(lookup, chain, X509_FILETYPE_PEM))
26-
&& TEST_ptr(certs = X509_STORE_get1_all_certs(store))
27-
&& TEST_int_eq(sk_X509_num(certs), 4))
28-
ret = 1;
39+
ret = 1;
2940

41+
err:
3042
OSSL_STACK_OF_X509_free(certs);
43+
sk_X509_OBJECT_pop_free(objs, X509_OBJECT_free);
3144
X509_STORE_free(store);
3245
return ret;
3346
}

util/libcrypto.num

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5543,3 +5543,4 @@ OSSL_CMP_ITAV_get0_certProfile ? 3_3_0 EXIST::FUNCTION:CMP
55435543
OSSL_CMP_SRV_CTX_init_trans ? 3_3_0 EXIST::FUNCTION:CMP
55445544
EVP_DigestSqueeze ? 3_3_0 EXIST::FUNCTION:
55455545
ERR_pop ? 3_3_0 EXIST::FUNCTION:
5546+
X509_STORE_get1_objects ? 3_3_0 EXIST::FUNCTION:

0 commit comments

Comments
 (0)