KEMBAR78
Add X509_STORE_get1_objects by davidben · Pull Request #23224 · openssl/openssl · GitHub
Skip to content

Conversation

@davidben
Copy link
Contributor

@davidben davidben commented Jan 7, 2024

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. Conversely, if a function happens not to take the lock, but then later it does, this becomes a breaking change for anyone using it with X509_STORE_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.

Checklist
  • documentation is added or updated
  • tests are added or updated

@davidben davidben force-pushed the get1-objects branch 2 times, most recently from bbadb5d to 4e19155 Compare January 7, 2024 03:00
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.
@github-actions github-actions bot added the severity: ABI change This pull request contains ABI changes label Jan 7, 2024
@davidben
Copy link
Contributor Author

davidben commented Jan 7, 2024

CIFuzz failure seems to be unrelated.

I probably should mention, given the issues with X509_STORE_get0_objects, as well as X509_STORE_lock and X509_STORE_unlock listed above, those APIs really should be deprecated at the earliest available release. They're quite dangerous.

Beyond the thread-safety and double-locking flaws listed above, X509_STORE_get0_objects actually constrains you all to keep the X509_STORE in a STACK_OF(T). However, a hash table would probably be a better representation, as you need something that supports efficient lookup by name, as well as insertion. As currently implemented, the wrong pattern of certificate verifications on a hash_dir will cause OpenSSL behave quadratically in the size of the store. To fix that, you need to first remove X509_STORE_get0_objects.

@t8m t8m added branch: master Merge to master branch approval: review pending This pull request needs review by a committer triaged: feature The issue/pr requests/adds a feature tests: present The PR has suitable tests present labels Jan 8, 2024
Copy link
Contributor

@nhorman nhorman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to me

@nhorman nhorman added approval: done This pull request has the required number of approvals and removed approval: review pending This pull request needs review by a committer labels Jan 11, 2024
@t-j-h
Copy link
Member

t-j-h commented Jan 12, 2024

I would also tend to make this a function that takes a parameter that indicates which objects you want included (certs or crls or both) and then the X509_STORE_get1_all_certs becomes a trivial convenience function and we don't have two different ways of implementing things - X509_STORE_get1_all_certs sorts, X509_STORE_get1_all_objects doesn't - and their internal paths are different and these sorts of differences I think will be unexpected for the user.

When cleaning up a mess (which is from usage of X509_STORE rather than the intended X509_STORE_CTX functions I think we need to look around the other APIs and see if we can clean things up as well so we don't end up with slightly different ways of doing this which is only "obvious" when looking at the source.

@davidben
Copy link
Contributor Author

Taking a type parameter would make it harder for OpenSSL to introduce new X509_OBJECT types in the future. The X509_LU_* constants are not suitable for a bitmask. You could introduce X509_LU_*_MASK constants and then have the code internally convert between them, but considering that all these objects are refcounted (so including it in the result is cheap), and that the caller may want to do more complex filtering anyway, it seems cleanest to simply return all of them and let the caller decide what they want.

Sorting is interesting because these values do not inherently have a sort order. The sorting is actually an internal implementation detail of how OpenSSL optimizes lookups. Except that OpenSSL does not optimize lookup very well because, as discussed above, a hash table would actually be the better structure. (You all need both insertions and deletions.) So while returning some sorted order is certainly technically possible, and you could always take your hash table order and sort afterwards, it seems something we should not be encouraging applications to rely on and instead document that the order is unspecified. That would be the most future-proof, presuming OpenSSL wishes to address X509_STORE performance in the future.

which is from usage of X509_STORE rather than the intended X509_STORE_CTX functions

This mess doesn't have anything to do with X509_STORE_CTX. There aren't any X509_STORE_CTX functions for the use case this API serves, nor would X509_STORE_CTX help with the situation. The mess is simply that this use case inherently accesses data shared across threads, and that OpenSSL did not take threading into account when designing the API.

@openssl-machine
Copy link
Collaborator

24 hours has passed since 'approval: done' was set, but as this PR has been updated in that time the label 'approval: ready to merge' is not being automatically set. Please review the updates and set the label manually.

@vdukhovni
Copy link

I agree that a get0 on the entire content of the store is a poor API and should be deprecated promptly.

But, I'd go further, and would suggest that even a get1 on the entire store content is perhaps not desirable.
Perhaps the store should only provide keyed search and and a stateful iterator that can traverse the store returning one certificate (or CRL) at a time.

@davidben
Copy link
Contributor Author

There already are APIs to search by key (namely name). There are projects that specifically want the whole list. Now, X509_LOOKUP's rather weird caching semantics make this use case slightly odd, but they're part of the public APIs of projects like CPython, so it's a bit late to do something else now. CPython needs to know all the certs and CRLs in the store. I think a CRL counterpart of X509_STORE_get1_all_certs would work for CPython, if you prefer to paint the bikeshed that color. I don't particularly care. X509_STORE_get1_objects is a bit easier to port X509_STORE_get0_objects callers, though, and only hits the lock once.

What sort of iterator did you have in mind? A pull-based iterator (i.e. one where the caller gets an object and iteratively calls "next") would still need to worry about locking. Locking around each call to "next" would be tricky because the store may be modified in between calls. If it internally saved a snapshot of the contents, it just becomes the get1 function under the hood.

Also keep in mind that get1_objects doesn't preclude writing an iterator API later. You can always implement get1_objects in terms of the iterator by collecting the iterator into a list. If there is already a preferred iterator pattern, happy to evaluate it against the open source projects that need it and, if suitable, adopt it in this PR. But, as you say, OpenSSL needs to provide a replacement for get0_objects promptly. Unless you all have a concrete preferred pattern already, we should start with get1_objects and expand to other patterns later as the needs arise.

@t8m t8m added approval: ready to merge The 24 hour grace period has passed, ready to merge and removed approval: done This pull request has the required number of approvals labels Jan 15, 2024
@t8m
Copy link
Member

t8m commented Jan 15, 2024

Merged to the master branch. Thank you for your contribution.

@t8m t8m closed this Jan 15, 2024
openssl-machine pushed a commit that referenced this pull request Jan 15, 2024
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)
davidben added a commit to davidben/cpython that referenced this pull request Jan 25, 2024
cert_store_stats and get_ca_certs query the SSLContext's X509_STORE with
X509_STORE_get0_objects, but reading the result requires a lock. See
openssl/openssl#23224 for details.

Instead, use X509_STORE_get1_objects, newly added in that PR.
X509_STORE_get1_objects does not exist in current OpenSSLs, but we can
polyfill it with X509_STORE_lock and X509_STORE_unlock.
davidben added a commit to davidben/cpython that referenced this pull request Jan 25, 2024
cert_store_stats and get_ca_certs query the SSLContext's X509_STORE with
X509_STORE_get0_objects, but reading the result requires a lock. See
openssl/openssl#23224 for details.

Instead, use X509_STORE_get1_objects, newly added in that PR.
X509_STORE_get1_objects does not exist in current OpenSSLs, but we can
polyfill it with X509_STORE_lock and X509_STORE_unlock.
alex pushed a commit to python/cpython that referenced this pull request Feb 16, 2024
* gh-114572: Fix locking in cert_store_stats and get_ca_certs

cert_store_stats and get_ca_certs query the SSLContext's X509_STORE with
X509_STORE_get0_objects, but reading the result requires a lock. See
openssl/openssl#23224 for details.

Instead, use X509_STORE_get1_objects, newly added in that PR.
X509_STORE_get1_objects does not exist in current OpenSSLs, but we can
polyfill it with X509_STORE_lock and X509_STORE_unlock.

* Work around const-correctness problem

* Add missing X509_STORE_get1_objects failure check

* Add blurb
miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Feb 16, 2024
…thonGH-114573)

* pythongh-114572: Fix locking in cert_store_stats and get_ca_certs

cert_store_stats and get_ca_certs query the SSLContext's X509_STORE with
X509_STORE_get0_objects, but reading the result requires a lock. See
openssl/openssl#23224 for details.

Instead, use X509_STORE_get1_objects, newly added in that PR.
X509_STORE_get1_objects does not exist in current OpenSSLs, but we can
polyfill it with X509_STORE_lock and X509_STORE_unlock.

* Work around const-correctness problem

* Add missing X509_STORE_get1_objects failure check

* Add blurb
(cherry picked from commit bce6931)

Co-authored-by: David Benjamin <davidben@google.com>
miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Feb 16, 2024
…thonGH-114573)

* pythongh-114572: Fix locking in cert_store_stats and get_ca_certs

cert_store_stats and get_ca_certs query the SSLContext's X509_STORE with
X509_STORE_get0_objects, but reading the result requires a lock. See
openssl/openssl#23224 for details.

Instead, use X509_STORE_get1_objects, newly added in that PR.
X509_STORE_get1_objects does not exist in current OpenSSLs, but we can
polyfill it with X509_STORE_lock and X509_STORE_unlock.

* Work around const-correctness problem

* Add missing X509_STORE_get1_objects failure check

* Add blurb
(cherry picked from commit bce6931)

Co-authored-by: David Benjamin <davidben@google.com>
miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Feb 16, 2024
…thonGH-114573)

* pythongh-114572: Fix locking in cert_store_stats and get_ca_certs

cert_store_stats and get_ca_certs query the SSLContext's X509_STORE with
X509_STORE_get0_objects, but reading the result requires a lock. See
openssl/openssl#23224 for details.

Instead, use X509_STORE_get1_objects, newly added in that PR.
X509_STORE_get1_objects does not exist in current OpenSSLs, but we can
polyfill it with X509_STORE_lock and X509_STORE_unlock.

* Work around const-correctness problem

* Add missing X509_STORE_get1_objects failure check

* Add blurb
(cherry picked from commit bce6931)

Co-authored-by: David Benjamin <davidben@google.com>
pablogsal pushed a commit to python/cpython that referenced this pull request Feb 20, 2024
…H-114573) (#115549)

gh-114572: Fix locking in cert_store_stats and get_ca_certs (GH-114573)

* gh-114572: Fix locking in cert_store_stats and get_ca_certs

cert_store_stats and get_ca_certs query the SSLContext's X509_STORE with
X509_STORE_get0_objects, but reading the result requires a lock. See
openssl/openssl#23224 for details.

Instead, use X509_STORE_get1_objects, newly added in that PR.
X509_STORE_get1_objects does not exist in current OpenSSLs, but we can
polyfill it with X509_STORE_lock and X509_STORE_unlock.

* Work around const-correctness problem

* Add missing X509_STORE_get1_objects failure check

* Add blurb
(cherry picked from commit bce6931)

Co-authored-by: David Benjamin <davidben@google.com>
pablogsal pushed a commit to python/cpython that referenced this pull request Feb 20, 2024
…H-114573) (#115548)

gh-114572: Fix locking in cert_store_stats and get_ca_certs (GH-114573)

* gh-114572: Fix locking in cert_store_stats and get_ca_certs

cert_store_stats and get_ca_certs query the SSLContext's X509_STORE with
X509_STORE_get0_objects, but reading the result requires a lock. See
openssl/openssl#23224 for details.

Instead, use X509_STORE_get1_objects, newly added in that PR.
X509_STORE_get1_objects does not exist in current OpenSSLs, but we can
polyfill it with X509_STORE_lock and X509_STORE_unlock.

* Work around const-correctness problem

* Add missing X509_STORE_get1_objects failure check

* Add blurb
(cherry picked from commit bce6931)

Co-authored-by: David Benjamin <davidben@google.com>
encukou pushed a commit to python/cpython that referenced this pull request Feb 29, 2024
…H-114573) (GH-115547)

gh-114572: Fix locking in cert_store_stats and get_ca_certs (GH-114573)

* gh-114572: Fix locking in cert_store_stats and get_ca_certs

cert_store_stats and get_ca_certs query the SSLContext's X509_STORE with
X509_STORE_get0_objects, but reading the result requires a lock. See
openssl/openssl#23224 for details.

Instead, use X509_STORE_get1_objects, newly added in that PR.
X509_STORE_get1_objects does not exist in current OpenSSLs, but we can
polyfill it with X509_STORE_lock and X509_STORE_unlock.

* Work around const-correctness problem

* Add missing X509_STORE_get1_objects failure check

* Add blurb
(cherry picked from commit bce6931)

Co-authored-by: David Benjamin <davidben@google.com>
github-actions bot pushed a commit to m-aciek/python-docs-weblate that referenced this pull request Feb 29, 2024
…H-114573) (GH-115547)

gh-114572: Fix locking in cert_store_stats and get_ca_certs (GH-114573)

* gh-114572: Fix locking in cert_store_stats and get_ca_certs

cert_store_stats and get_ca_certs query the SSLContext's X509_STORE with
X509_STORE_get0_objects, but reading the result requires a lock. See
openssl/openssl#23224 for details.

Instead, use X509_STORE_get1_objects, newly added in that PR.
X509_STORE_get1_objects does not exist in current OpenSSLs, but we can
polyfill it with X509_STORE_lock and X509_STORE_unlock.

* Work around const-correctness problem

* Add missing X509_STORE_get1_objects failure check

* Add blurb
(cherry picked from commit bce693111bff906ccf9281c22371331aaff766ab)

Co-authored-by: David Benjamin <davidben@google.com>

CPython-sync-commit-latest: 542f3272f56f31ed04e74c40635a913fbc12d286
diegorusso pushed a commit to diegorusso/cpython that referenced this pull request Apr 17, 2024
…thon#114573)

* pythongh-114572: Fix locking in cert_store_stats and get_ca_certs

cert_store_stats and get_ca_certs query the SSLContext's X509_STORE with
X509_STORE_get0_objects, but reading the result requires a lock. See
openssl/openssl#23224 for details.

Instead, use X509_STORE_get1_objects, newly added in that PR.
X509_STORE_get1_objects does not exist in current OpenSSLs, but we can
polyfill it with X509_STORE_lock and X509_STORE_unlock.

* Work around const-correctness problem

* Add missing X509_STORE_get1_objects failure check

* Add blurb
gentoo-bot pushed a commit to gentoo/cpython that referenced this pull request May 21, 2024
…rts (pythonGH-114573) (python#115548)

pythongh-114572: Fix locking in cert_store_stats and get_ca_certs (pythonGH-114573)

* pythongh-114572: Fix locking in cert_store_stats and get_ca_certs

cert_store_stats and get_ca_certs query the SSLContext's X509_STORE with
X509_STORE_get0_objects, but reading the result requires a lock. See
openssl/openssl#23224 for details.

Instead, use X509_STORE_get1_objects, newly added in that PR.
X509_STORE_get1_objects does not exist in current OpenSSLs, but we can
polyfill it with X509_STORE_lock and X509_STORE_unlock.

* Work around const-correctness problem

* Add missing X509_STORE_get1_objects failure check

* Add blurb
(cherry picked from commit bce6931)

Co-authored-by: David Benjamin <davidben@google.com>
gentoo-bot pushed a commit to gentoo/cpython that referenced this pull request May 21, 2024
…rts (pythonGH-114573) (python#115548)

pythongh-114572: Fix locking in cert_store_stats and get_ca_certs (pythonGH-114573)

* pythongh-114572: Fix locking in cert_store_stats and get_ca_certs

cert_store_stats and get_ca_certs query the SSLContext's X509_STORE with
X509_STORE_get0_objects, but reading the result requires a lock. See
openssl/openssl#23224 for details.

Instead, use X509_STORE_get1_objects, newly added in that PR.
X509_STORE_get1_objects does not exist in current OpenSSLs, but we can
polyfill it with X509_STORE_lock and X509_STORE_unlock.

* Work around const-correctness problem

* Add missing X509_STORE_get1_objects failure check

* Add blurb
(cherry picked from commit bce6931)

Co-authored-by: David Benjamin <davidben@google.com>
mcepl pushed a commit to openSUSE-Python/cpython that referenced this pull request Jun 21, 2024
cert_store_stats and get_ca_certs query the SSLContext's
X509_STORE with X509_STORE_get0_objects, but reading the result
requires a lock. See gh#openssl/openssl#23224 for details.

Instead, use X509_STORE_get1_objects, newly added in that
PR. X509_STORE_get1_objects does not exist in current
OpenSSLs, but we can polyfill it with X509_STORE_lock and
X509_STORE_unlock.

From-PR: gh#python/cpython!114573
Fixes: gh#python#114572
Fixes: bsc#1226447 (CVE-2024-0397)
Patch: CVE-2024-0397-memrace_ssl.SSLContext_cert_store.patch
@synopse
Copy link

synopse commented Jul 5, 2024

There is nothing wrong with the X509_STORE_get0_objects API itself.
It is clearly unsafe if not called within X509_STORE_lock() but ref counting should be properly taken into account by the user.
For instance, we implemented X509_STORE_get1_all_certs() ourselves in our OpenSSL wrapper, because oldest API does not publish it.

This is the whole ref counting API of OpenSSL which is confusing.
But it is at least clear that any code calling _get0_ methods should properly acquire the reference via _up_ref().

I don't know what is the current state of memory leaks of OpenSSL, but last time I checked initializing the library just made some memory leaks at shutdown. Before trying to rewrite the API or deprecate anything which is used everywhere, we could first prioritize to make the library itself without leak.

And don't forget that OpenSSL is used outside of plain C, with other languages, and we need access to the raw information via low-level helpers, since direct access to the C structs have been deprecated. Linking to several versions of OpenSSL is perhaps not possible in plain C with its .h headers, but it is manageable to link and adapt to OpenSSL 1.1 or 3.x at runtime, from other languages.

LukasWoodtli pushed a commit to LukasWoodtli/cpython that referenced this pull request Jan 22, 2025
…thon#114573)

* pythongh-114572: Fix locking in cert_store_stats and get_ca_certs

cert_store_stats and get_ca_certs query the SSLContext's X509_STORE with
X509_STORE_get0_objects, but reading the result requires a lock. See
openssl/openssl#23224 for details.

Instead, use X509_STORE_get1_objects, newly added in that PR.
X509_STORE_get1_objects does not exist in current OpenSSLs, but we can
polyfill it with X509_STORE_lock and X509_STORE_unlock.

* Work around const-correctness problem

* Add missing X509_STORE_get1_objects failure check

* Add blurb
copybara-service bot pushed a commit to dart-lang/sdk that referenced this pull request Feb 25, 2025
X509_STORE_get0_objects is not thread-safe and also constrains
X509_STORE's internals in undesirable ways. See
openssl/openssl#23224

TEST=ci

Change-Id: I0d501d92208e4af9de944b38c07cbcb66acaec66
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/407840
Auto-Submit: David Benjamin <davidben@google.com>
Commit-Queue: Brian Quinlan <bquinlan@google.com>
Reviewed-by: Brian Quinlan <bquinlan@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approval: ready to merge The 24 hour grace period has passed, ready to merge branch: master Merge to master branch severity: ABI change This pull request contains ABI changes tests: present The PR has suitable tests present triaged: feature The issue/pr requests/adds a feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants