openssl: fix potential NULL dereference when loading certs (Windows)#20634
openssl: fix potential NULL dereference when loading certs (Windows)#20634vszakats wants to merge 6 commits intocurl:masterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
🔒 Aisle Security Analysis✅ We scanned this PR and did not find any security vulnerabilities. Analyzed PR: #20634 at commit |
|
augment review |
🤖 Augment PR SummarySummary: Hardens Windows system certificate loading for the OpenSSL backend by addressing a potential NULL dereference in Enhanced Key Usage (EKU) handling. Changes: Gates EKU buffer allocation/fetching on a non-zero 🤖 Was this summary useful? React with 👍 or 👎 |
lib/vtls/openssl.c
Outdated
| } | ||
|
|
||
| /* Initial call did not return data. */ | ||
| if(!req_size && !enhkey_usage) |
There was a problem hiding this comment.
If this path can happen, this continue skips the certificate entirely; per CertGetEnhancedKeyUsage docs, a cert with neither EKU extension nor EKU property is assumed valid for all uses (often surfaced via GetLastError()==CRYPT_E_NOT_FOUND). Can you confirm skipping the cert is intended here vs treating it as “all uses” and proceeding with import?
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
5f619a0 to
08c9636
Compare
|
augment review |
lib/vtls/openssl.c
Outdated
| found = TRUE; | ||
| break; | ||
| } | ||
| if(enhkey_usage) { /* We have EKU data */ |
There was a problem hiding this comment.
if(enhkey_usage) is using the pointer value as a proxy for “we have EKU data”, but req_size can be 0 for a cert with no EKU; if enhkey_usage is non-NULL from a previous iteration, the second CertGetEnhancedKeyUsage call is made with req_size == 0 and may fail and incorrectly skip the cert. Consider gating the second call on req_size > 0 (or otherwise decoupling it from prior iterations) so cert acceptance doesn’t depend on whether an earlier cert allocated the buffer.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
|
augment review |
lib/vtls/openssl.c
Outdated
| break; | ||
| } | ||
| if(req_size) { /* Cert has EKU data */ | ||
| if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) { |
There was a problem hiding this comment.
I would just make this if(req_site && CertGetEnhancedKeyUsage(...)) {
There was a problem hiding this comment.
Wouldn't that run onto the continue; line skipping the cert in
this case?
MS docs say that a cert without EKU is fit for any use, which is what
augmentcode was quoting to my first attempt which did this.
There was a problem hiding this comment.
I think it should then skip to the next cert when no more "EnhancedKeyUsage" is preset in the current certificate.
The documenation at https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certgetenhancedkeyusage is not totally clear, but I suspect that the function never succeeds with a req_size of 0. That would make any change unnecessary.
There was a problem hiding this comment.
Yes, docs doesn't say anything about this. This made me check
WINE sources and those seems to be able to return success while
returning req_size == 0. (though I also could have misread it)
My understanding is that this code cycles through all certs,
checks if the current one has EKU data, while keeping around
a single temporary buffer for that data, which is grown as needed
throughout the loop.
For each cert there is a decision to either skip it, or add it if the
EKU is fit. This patch makes it do the latter also if there is no EKU.
Before this patch a no-EKU case made CertGetEnhancedKeyUsage()
retrieve the size twice, while leaving the EKU pointer NULL. Best
case (if CertGetEnhancedKeyUsage() failed) it made these certs
skipped, which also seems wrong, assuming no-EKU means a fit
cert.
There was a problem hiding this comment.
So, changing line 2907 to
if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size) && req_size) {
should do the trick?
There was a problem hiding this comment.
If we want to skip a cert with no EKU, yes, but the docs says a cert without an EKU
should be accepted for all uses:
"If a certificate has neither an EKU extension nor EKU extended properties, it is assumed to be valid for all uses." via https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certgetenhancedkeyusage
Which suggests we should not skip but proceed to X509_STORE_add_cert?
There was a problem hiding this comment.
Well, I cannot answer that since I do not know what it really means. What you talk about is a change in how this works. Are the any reported issue with that?
Fixing a potential NULL does not require a change in behaviour, or?
There was a problem hiding this comment.
In this case it may have crashed previously. I think it's up to us to define the fixed behavior.
No one reported this, but it's also a rarely used codepath (and a rare case within). It may get
more use with native CA enabled by default, as in the upcoming curl-for-win binaries.
I went with the skip method, but this triggered a Medium severity issue with augment review.
Detected by clang-tidy:
```
lib/vtls/openssl.c:2922:15: warning: Access to field 'cUsageIdentifier' results in a dereference of a null pointer (loaded from variable 'enhkey_usage') [clang-analyzer-core.NullDereference]
2922 | if(!enhkey_usage->cUsageIdentifier) {
| ^
lib/vtls/openssl.c:2996:3: note: Loop condition is true. Entering loop body
2996 | for(i = 0; i < CURL_ARRAYSIZE(win_stores); ++i) {
| ^
lib/vtls/openssl.c:2998:14: note: Calling 'ossl_win_load_store'
2998 | result = ossl_win_load_store(data, win_stores[i], store, &store_added);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib/vtls/openssl.c:2847:6: note: Assuming 'hStore' is non-null
2847 | if(hStore) {
| ^~~~~~
lib/vtls/openssl.c:2847:3: note: Taking true branch
2847 | if(hStore) {
| ^
lib/vtls/openssl.c:2852:5: note: 'enhkey_usage' initialized to a null pointer value
2852 | CERT_ENHKEY_USAGE *enhkey_usage = NULL;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib/vtls/openssl.c:2860:5: note: Loop condition is true. Entering loop body
2860 | for(;;) {
| ^
lib/vtls/openssl.c:2867:10: note: Assuming 'pContext' is non-null
2867 | if(!pContext)
| ^~~~~~~~~
lib/vtls/openssl.c:2867:7: note: Taking false branch
2867 | if(!pContext)
| ^
lib/vtls/openssl.c:2873:12: note: Assuming the condition is false
2873 | if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2874 | NULL, cert_name, sizeof(cert_name)))
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib/vtls/openssl.c:2873:9: note: Taking false branch
2873 | if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
| ^
lib/vtls/openssl.c:2877:11: note: Assuming 'data' is null
2877 | infof(data, "SSL: Checking cert \"%s\"", cert_name);
| ^
lib/vtls/../curl_trc.h:140:8: note: expanded from macro 'infof'
140 | if(Curl_trc_is_verbose(data)) \
| ^~~~~~~~~~~~~~~~~~~~~~~~~
lib/vtls/../curl_trc.h:312:4: note: expanded from macro 'Curl_trc_is_verbose'
312 | ((data) && (data)->set.verbose && \
| ^~~~~~
lib/vtls/openssl.c:2877:11: note: Left side of '&&' is false
2877 | infof(data, "SSL: Checking cert \"%s\"", cert_name);
| ^
lib/vtls/../curl_trc.h:140:8: note: expanded from macro 'infof'
140 | if(Curl_trc_is_verbose(data)) \
| ^
lib/vtls/../curl_trc.h:312:11: note: expanded from macro 'Curl_trc_is_verbose'
312 | ((data) && (data)->set.verbose && \
| ^
lib/vtls/openssl.c:2877:11: note: Loop condition is false. Exiting loop
2877 | infof(data, "SSL: Checking cert \"%s\"", cert_name);
| ^
lib/vtls/../curl_trc.h:139:3: note: expanded from macro 'infof'
139 | do { \
| ^
lib/vtls/openssl.c:2881:10: note: Assuming 'encoded_cert' is non-null
2881 | if(!encoded_cert)
| ^~~~~~~~~~~~~
lib/vtls/openssl.c:2881:7: note: Taking false branch
2881 | if(!encoded_cert)
| ^
lib/vtls/openssl.c:2885:10: note: Assuming the condition is false
2885 | if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 ||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib/vtls/openssl.c:2885:10: note: Left side of '||' is false
lib/vtls/openssl.c:2886:10: note: Assuming the condition is false
2886 | CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0)
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib/vtls/openssl.c:2885:7: note: Taking false branch
2885 | if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 ||
| ^
lib/vtls/openssl.c:2890:10: note: Assuming the condition is false
2890 | if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2891 | pContext->pCertInfo,
| ~~~~~~~~~~~~~~~~~~~~
2892 | key_usage, sizeof(key_usage))) {
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib/vtls/openssl.c:2890:7: note: Taking false branch
2890 | if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType,
| ^
lib/vtls/openssl.c:2896:15: note: Assuming the condition is false
2896 | else if(GetLastError())
| ^~~~~~~~~~~~~~
lib/vtls/openssl.c:2896:12: note: Taking false branch
2896 | else if(GetLastError())
| ^
lib/vtls/openssl.c:2907:10: note: Assuming the condition is true
2907 | if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) {
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib/vtls/openssl.c:2907:7: note: Taking true branch
2907 | if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) {
| ^
lib/vtls/openssl.c:2908:12: note: Assuming 'req_size' is 0
2908 | if(req_size && req_size > enhkey_usage_size) {
| ^~~~~~~~
lib/vtls/openssl.c:2908:21: note: Left side of '&&' is false
2908 | if(req_size && req_size > enhkey_usage_size) {
| ^
lib/vtls/openssl.c:2921:12: note: Assuming the condition is true
2921 | if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) {
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib/vtls/openssl.c:2921:9: note: Taking true branch
2921 | if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) {
| ^
lib/vtls/openssl.c:2922:15: note: Access to field 'cUsageIdentifier' results in a dereference of a null pointer (loaded from variable 'enhkey_usage')
2922 | if(!enhkey_usage->cUsageIdentifier) {
| ^~~~~~~~~~~~
```
f63e12f to
a79d6b6
Compare
|
Settled on not importing a cert if the query returned success, but with a zero-length EKU. |
|
@aisle-analyzer augment review |
🔒 Aisle Security Analysis✅ We scanned this PR and did not find any security vulnerabilities. Aisle supplements but does not replace security review. Analyzed PR: #20634 at commit |
This could happen if the first cert to be loaded missed EKU (Enhanced
Key Usage) data, when using native CA on Windows.
Fix by skipping certs without Enhanced Key Usage data.
Detected by clang-tidy:
Refs:
https://learn.microsoft.com/windows/win32/secgloss/e-gly
https://learn.microsoft.com/windows/win32/api/wincrypt/nf-wincrypt-certgetenhancedkeyusage
https://gitlab.winehq.org/wine/wine/-/blob/wine-11.2/dlls/crypt32/cert.c?ref_type=tags#L3061-3164
Assisted-by: Stefan Eissing
https://github.com/curl/curl/pull/20634/files?w=1