Repository navigation
Segfault when trying to call ExDataFreeOcspResponse after .NET module has been unloaded #123140
Description
Description
TL;DR: In EnsureOpenSsl11Initialized(), free_func pointers are registered which get dangling after the .NET module has been unloaded. When OpenSSL later tries to call this free_func, we get a segfault. It seems that CRYPTO_free_ex_index() calls are missing.
We have the following process/module scenario:
- OpenSSL is the entrypoint (
openssl cms ...command with-engine pkcs11). - => The libp11 OpenSSL engine (
pkcs11.so) gets dynamically loaded by OpenSSL. - By configuration, libp11 dynamically loads our PKCS#11 module (
libSignPath.Cryptoki.so)- This is a Native AOT .NET 9 library => CLR gets initialized and eventually runs EnsureOpenSsl11Initialized().
- (Note that the following segmentation fault can only reproduced with recent OpenSSL and libp11 versions, e.g. on Debian 13 with OpenSSL 3.5.4 and libp11 0.4.13.)
The segmentation fault happens during unloading (when OpenSSL's cms_main calls CMS_ContentInfo_free()).
Full stack trace (linked OpenSSL is 3.5.4)
func file addr ?? 0x7acfe4f0dc70 CRYPTO_free_ex_data crypto/ex_data.c:406 0x7acfe6695813 x509_cb crypto/x509/x_x509.c:85 0x7acfe6858665 ossl_asn1_item_embed_free crypto/asn1/tasn_fre.c:117 0x7acfe6520842 ossl_asn1_template_free crypto/asn1/tasn_fre.c:146 0x7acfe6520980 ossl_asn1_item_embed_free crypto/asn1/tasn_fre.c:70 0x7acfe652064f ossl_asn1_template_free crypto/asn1/tasn_fre.c:141 0x7acfe6520923 ossl_asn1_item_embed_free crypto/asn1/tasn_fre.c:114 0x7acfe6520805 ossl_asn1_template_free crypto/asn1/tasn_fre.c:146 0x7acfe6520980 ossl_asn1_item_embed_free crypto/asn1/tasn_fre.c:114 0x7acfe6520805 ASN1_item_free crypto/asn1/tasn_fre.c:20 0x7acfe652046a CMS_ContentInfo_free crypto/cms/cms_lib.c:27 0x7acfe659d29b cms_main apps/cms.c:1320 0x5fb2c34d66ea do_cmd apps/openssl.c:428 0x5fb2c34f0c50 main apps/openssl.c:309 0x5fb2c34f0812
When the segfault happens, the mentioned .NET module libSignPath.Cryptoki.so already got unloaded (confirmed via LD_DEBUG=libs tracing).
On the stack trace, OpenSSL is in the process of freeing X.509 objects, which it used in the openssl cms command. On these X.509 objects, .NET attached OpenSSL "exdata" (registered at EnsureOpenSsl11Initialized() (using class index CRYPTO_EX_INDEX_X509). .NET registers ExDataFreeOcspResponse() as free_func.
Now OpenSSL (in the process of freeing X.509 objects) tries to call the registered free_func, which got unloaded. => Segfault.
We confirmed this by remembering the free_func pointer in ossl_crypto_get_ex_new_index_ex() for ExDataFreeOcspResponse() and seeing that the segfault happens due to accessing exactly this address.
So the problem is that .NET does not unregister the registered free_funcs. That is possible via CRYPTO_free_ex_index(), which is also recommended in the OpenSSL docs "if a dynamic library can be unloaded":
If a dynamic library can be unloaded, it should call CRYPTO_free_ex_index() when this is done. This will replace the callbacks with no-ops so that applications don't crash. Any existing exdata will be leaked.
Reproduction Steps
See above for a description of the necessary steps. Unfortunately we don't have a minimal reproduction sample.
Expected behavior
No segfault 😆
Actual behavior
See above.
Regression?
No response
Known Workarounds
A (very) hacky workaround is to run CRYPTO_free_ex_index(3, 1 /* index may vary! */); CRYPTO_free_ex_index(2, 1 /* index may vary! */); from the .NET process before module unloading.
Configuration
.NET 9 (AOT) in Debian 13 Linux container, AMD64.
Other information
No response
Activity
jkotas commentedon Jan 13, 2026
.NET runtime does not support OpenSSL being unloaded.
dotnet-policy-service commentedon Jan 13, 2026
Tagging subscribers to this area: @dotnet/area-system-security, @bartonjs, @vcsjones
See info in area-owners.md if you want to be subscribed.
ulrichb commentedon Jan 13, 2026
That's totally understandable. Here it's the other way round: the (AOT compiled) .NET module gets unloaded (OpenSSL still loaded and later running the dangling
free_func).jkotas commentedon Jan 13, 2026
.NET runtime (including binaries published with NativeAOT) does not support unloading. #64629 and linked issues have discussion about it.
ulrichb commentedon Feb 3, 2026
Okay, I understand that CoreCLR doesn't fully/officially support (shared library) unloading (i.e.
dlclose()).In our case -- where a .NET AOT library is unloaded indirectly -- we unfortunately have no control about the unloading behavior.
Unloading works in all our integration test cases, except the
openssl cmsscenario described above b/c of order of unloading, producing this segfault.So I'd ask whether we could add "partial unloading support" by addressing this very specific issue by calling
CRYPTO_free_ex_index()before unloading as suggested in the OpenSSL docs.The reason I'm asking is that it's very difficult to fix this issue "from the outside" b/c it's hard to get the
g_x509_ocsp_indexvalue.jkotas commentedon Feb 3, 2026
I expect that you are going to see other types of crashes once you get this one out of the way. Fixing them all is very difficult.
ulrichb commentedon Feb 4, 2026
Indeed this is the only issue (in our scenario). We proofed this by applying the
CRYPTO_free_ex_index()-workaround mentioned above. (When applying the workaround shutdown runs w/o any failures).jeffhandley commentedon Aug 16, 2026
Verified this is fixed by #131960 (merged for
11.0-rc1), which links NativeAOT shared libraries with-Wl,-z,nodelete. WithDF_1_NODELETEset,dlclose()on the .NET.sobecomes a no-op, soExDataFreeOcspResponsecan never dangle.Confirmed with a minimal repro on Debian 13 / OpenSSL 3.5.6 — a NativeAOT
.soloaded by a C host,dlclosed, thenX509_free:CRYPTO_free_ex_data;.sounmapped afterdlclose.sostill mappedNightly verified:
11.0.100-rc.1.26413.103(commitb8c18a5c2f, ILCompiler11.0.0-rc.1.26413.103) — dated 2026-08-13, after the 2026-08-08 merge, and the-Wl,-z,nodeleteline is physically present in that build'sMicrosoft.NETCore.Native.Unix.targets..NET still never calls
CRYPTO_free_ex_index, and .NET 11 still registers the X509 ex_index. #131960 doesn't unregister the callback — it makes the library un-unloadable so the pointer stays valid.ulrichb commentedon Aug 16, 2026
@jeffhandley That's great news! Many thanks for reproducing and testing against .NET 11 RC1 ❤️