I’m writing a C library (let’s say, libA) in Linux which leverages Openssl to do base64 codec, hash, etc. Some other projects (e.g. projB) leverage libA to do something, and these projects themselves call Openssl APIs too. So, projB calls Openssl APIs in two ways:
- call Openssl APIs directly: projB -> Openssl
- call Openssl APIs indirectly: projB -> libA -> Openssl
Meanwhile, it’s officially announced that Openssl isn’t thread-safe unless at least two callback functions are registered into Openssl:
- void ssl_locking_function(int mode, int n, const char * file, int line)
- unsigned long ssl_threadid_function()
libA exposes following APIs to projB:
- int InitA(void)
- int ActionStuff()
- void DestroyA()
In order to ensure thread-safety of Openssl, there’re two solutions:
#1. libA registers callback functions into Openssl in InitA()
#2. projB registers callback functions into Openssl on initialization, before calling libA APIs, and libA doesn’t do the callback registration
With solution #1, libA itself is thread-safe, regarding calling Openssl APIs. However, projB also has to ensure its thread-safety, and it’ll take a similar action of registering callback functions into Openssl.
Assume that libA registers the following callback functions
- void ssl_locking_function_A(int mode, int n, const char * file, int line)
- unsigned long ssl_threadid_function_A()
and projB registers these
- void ssl_locking_function_B(int mode, int n, const char * file, int line)
- unsigned long ssl_threadid_function_B()
As I understand, libA’s callback functions are registered after projB’s, and eventually, projB’s callback functions are shaded by libA’s:
- projB initialization
- projB registers ssl_locking_function_B() and ssl_threadid_function_B() into Openssl
- projB calls InitA()
- libA registers ssl_locking_function_A() and ssl_threadid_function_A() into Openssl
- projB calls ActionStuff()
- …
- projB calls DestroyA()
- projB uninitialization
My question is, which solution is better, #1 or #2? Is there any better solution?
Fork safety and signal safety are even worse. See, for example, Random Fork Safety and Libcrypto Fork Safety.
I believe there is a third.... OpenSSL provides a define called
OPENSSL_THREADS
. IfOPENSSL_THREADS
is defined, then OpenSSL was built with threading support. Be sure to include<openssl/opensslconf.h>
to get accurate results.During
LibA
orLibB
's initialization, either library should install the locks if needed. I believe you can checkCRYPTO_THREADID_get_callback
, and if its null, then you should initialize the locks. You should also include a state variable so you know to cleanup the resources on shutdown.In reality, you really want to know the status of
lock_cs
in<openssl source>/crypto/th-lock.c
. That's where the lock array is held. But its static so you can't get to it.In practice, you'll probably find the libraries don't do anything, and they leave it to the developer using the library (despite the possible races). RTFM at its finest.
The safest solution will probably be: (1) if OpenSSL supports threads, (2) your library needs threads and (3) thread locks are not installed, then install the locks during initialization.