This question is related to the following issues on GitHub:
https://github.com/nodejs/node-gyp/issues/155
https://github.com/nodejs/help/issues/1724
https://github.com/JCMais/node-libcurl/issues/164
So, if possible, read there too (and post your recommended action there too, in case you have one).
Some background:
I wrote a libcurl binding for Node.js, which uses OpenSSL by default (node-libcurl).
On Windows I have a step that builds a statically linked version of libcurl with OpenSSL, to use it as a dependency when building the addon:
https://github.com/JCMais/node-libcurl/blob/56c2be4/binding.gyp#L49-L51
The above basically returns a string with a path to the following curl.gyp
file (the <(library)
are replaced with static_library
during the build):
https://github.com/JCMais/curl-for-windows/blob/445e537cb2f5656e3a3ede09e9e4782a6b62c299/curl.gyp
That same file includes the OpenSSL
dependency: https://github.com/JCMais/curl-for-windows/blob/445e537cb2f5656e3a3ede09e9e4782a6b62c299/curl.gyp#L33
The problem is that on Node.js 10 the OpenSSL being used at runtime is in fact the one Node.js was built with, instead of the one above, and this is causing a segmentation fault. Because Node.js 10 exports OpenSSL 1.1.0, and my addon is still using 1.0.0.
I would love some ideas on how to correctly link the addon to the OpenSSL version I supplied, instead of using the one available from Node.js.
EDIT - After hours and hours of reading / debugging / messing around:
This issue is not Windows specific, it was there all the time on other platforms, I just didn't look for it previously.
I have just compiled my findings below.
The following tests were done using the following code:
const Curl = require('node-libcurl').Curl
curl = new Curl()
console.log(Curl.getVersion())
curl.setOpt('URL', 'https://www.google.com')
curl.setOpt('SSL_VERIFYPEER', false)
curl.on('end', () => {
console.log('Works')
curl.close()
})
curl.on('error', error => {
console.log('err', error)
curl.close()
})
curl.perform()
And by running the following inside the project repo:
yarn pregyp rebuild && node ssl.js
Keep in mind I have libcurl 7.64.1 static linked against OpenSSL 1.1.1b (also static linked) at:
/home/jcm/curl/build/
OpenSSL 1.1.1b is at:
/home/jcm/openssl/build/
Scenarios:
1
Node.js 8.15.1 (with OpenSSL 1.0.2r)
Addon deps:
static linked only with libcurl 7.64.1, which itself is static linked against OpenSSL 1.1.1b
Output:
# Bunch of output for rebuild command
node: symbol lookup error: /mnt/e/jc/node-libcurl/lib/binding/node_libcurl.node: undefined symbol: OpenSSL_version_num
Makes sense, OpenSSL_version_num
is a new function on OpenSSL 1.1.x, libcurl tried to use it because it was compiled using OpenSSL 1.1.1b headers.
2
Node.js 10.9.0 (with OpenSSL 1.1.0i)
Addon deps:
static linked only with libcurl 7.64.1, witch itself is static linked against OpenSSL 1.1.1b
Output:
# Bunch of output for rebuild command
libcurl/7.64.1 OpenSSL/1.1.0i
node: symbol lookup error: /mnt/e/jc/node-libcurl/lib/binding/node_libcurl.node: undefined symbol: SSL_CTX_set_post_handshake_auth
From the OpenSSL version above we can see libcurl used the one supplied by node, however there was an undefined symbol. This function only exists on OpenSSL 1.1.1, there is a guard on libcurl here and it's called here. Again expected, I've built libcurl using OpenSSL 1.1.1b headers.
3
Node.js 10.9.0 (with OpenSSL 1.1.0i)
Addon deps:
static linked with libcurl 7.64.1 (also static linked against OpenSSL 1.1.1b)
static linked with OpenSSL 1.1.1b
Output:
# Bunch of output for rebuild command
libcurl/7.64.1 OpenSSL/1.1.0i
Works
It worked, looks like the OpenSSL version we static linked with the addon was used to provide the undefined symbol, however Node.js own OpenSSL still takes precedence for all other symbols. This probably worked because both OpenSSL versions, 1.1.1b and 1.1.0i, are compatible.
4
Node.js 8.15.1 (with OpenSSL 1.0.2r)
Addon deps:
static linked with libcurl 7.64.1 (also static linked against OpenSSL 1.1.1b)
static linked with OpenSSL 1.1.1b
Output:
# Bunch of output for rebuild command
libcurl/7.64.1 OpenSSL/1.1.1b
[1] 20063 segmentation fault (core dumped) node ssl.js
Segfault:
(gdb) backtrace
#0 0x0000000002174920 in TLSv1_2_enc_data ()
#1 0x0000000001181dcd in SSL_CTX_new ()
#2 0x00007fffefa7a89b in ossl_connect_step1 (conn=0x22621c8, sockindex=0) at vtls/openssl.c:2380
#3 0x00007fffefa7e495 in ossl_connect_common (conn=0x22621c8, sockindex=0, nonblocking=true, done=0x7ffffffe9ca9) at vtls/openssl.c:3552
#4 0x00007fffefa7e70f in Curl_ossl_connect_nonblocking (conn=0x22621c8, sockindex=0, done=0x7ffffffe9ca9) at vtls/openssl.c:3638
#5 0x00007fffefa2d3f0 in Curl_ssl_connect_nonblocking (conn=0x22621c8, sockindex=0, done=0x7ffffffe9ca9) at vtls/vtls.c:275
# omitted...
#16 0x00000000008d59c0 in node::Start(int, char**) ()
#17 0x00007ffffe091b97 in __libc_start_main (main=0x89f010 <main>, argc=2, argv=0x7ffffffee1b8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7ffffffee1a8)
at ../csu/libc-start.c:310
#18 0x000000000089f101 in _start ()
(gdb) info symbol 0x0000000001181dcd
SSL_CTX_new + 189 in section .text of /home/jcm/.nvm/versions/node/v8.15.1/bin/node
(gdb) info symbol 0x0000000002174920
TLSv1_2_enc_data in section .data of /home/jcm/.nvm/versions/node/v8.15.1/bin/node
Again expected, since 1.1.1b
and 1.0.2r
are not compatible.
Since looks like there is no way to make Node.js stop exporting OpenSSL symbols, the only feasible solution is to always use the corresponding OpenSSL version for given Node.js version, so they match the closest possible.
If anyone knows a better solution, please let me know.
Those questions / answers are probably somewhat related:
Linking two shared libraries with some of the same symbols
Which function is used when loading two shared libraries with same statically linked functions
Program linked against libraries that share symbol names runs wrong implementation