You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
ALPN callback function sometimes leads to segfault in node.js >= 18.13.0 #47207
Linux 2a53a1799e0b 5.15.0-67-generic #74-Ubuntu SMP Wed Feb 22 14:14:39 UTC 2023 x86_64 GNU/Linux
Subsystem
tls
What steps will reproduce the bug?
I see this from logs of the my node.js server. I did not find a way to reproduce yet, and need some help with that. I've attached stacktrace, but did not yet found ALPN header value, which causes this error. According to my investigations, function SelectALPNCallback calls SSL_select_next_proto with NULL instead of correct pointer. Looks like some maliciously crafted ALPN header can lead to such error
How often does it reproduce? Is there a required condition?
It is a rare case I capture from logs (around 1 req/million). I can add some code to get more information about the bug, but don't know what to do
What is the expected behavior? Why is that the expected behavior?
It's 2AM and I finally extracted core dump from kubernetes. Does not tell much because I need debug symbols for node 18.15.0
(gdb) bt
#0 0x00000000017f0bfc in SSL_select_next_proto ()
#1 0x0000000000d243e0 in node::crypto::(anonymous namespace)::SelectALPNCallback(ssl_st*, unsigned char const**, unsigned char*, unsigned char const*, unsigned int, void*) ()
#2 0x0000000001834f83 in tls_handle_alpn ()
#3 0x00000000018176f3 in tls_parse_all_extensions ()
#4 0x0000000001835200 in tls_post_process_client_hello ()
#5 0x0000000001822b74 in state_machine.part ()
#6 0x0000000001811050 in ssl3_read_bytes ()
#7 0x00000000017e0c40 in ssl3_read ()
#8 0x00000000017ee3f7 in SSL_read ()
#9 0x0000000000d2c8f7 in node::crypto::TLSWrap::ClearOut() ()
#10 0x0000000000d2d5f8 in node::crypto::TLSWrap::OnStreamRead(long, uv_buf_t const&) ()
#11 0x0000000000c6f959 in node::LibuvStreamWrap::OnUvRead(long, uv_buf_t const*) ()
#12 0x0000000000c6fd68 in node::LibuvStreamWrap::ReadStart()::{lambda(uv_stream_s*, long, uv_buf_t const*)#2}::_FUN(uv_stream_s*, long, uv_buf_t const*) ()
#13 0x0000000001676f67 in uv__read (stream=stream@entry=0x7f4994e92e90) at ../deps/uv/src/unix/stream.c:1201
#14 0x0000000001677790 in uv__stream_io (loop=<optimized out>, w=0x7f4994e92f18, events=1) at ../deps/uv/src/unix/stream.c:1270
#15 0x000000000167d534 in uv__io_poll (loop=loop@entry=0x526dfc0 <default_loop_struct>, timeout=<optimized out>) at ../deps/uv/src/unix/epoll.c:374
#16 0x000000000166b95e in uv_run (loop=0x526dfc0 <default_loop_struct>, mode=UV_RUN_DEFAULT) at ../deps/uv/src/unix/core.c:406
#17 0x0000000000abda2d in node::SpinEventLoop(node::Environment*) ()
#18 0x0000000000bc1874 in node::NodeMainInstance::Run() ()
#19 0x0000000000b36434 in node::LoadSnapshotDataAndRun(node::SnapshotData const**, node::InitializationResult const*) ()
#20 0x0000000000b3a02f in node::Start(int, char**) ()
#21 0x00007f4ee8745d0a in __libc_start_main (main=0xab1f70 <main>, argc=3, argv=0x7ffdb3a07ad8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>,
and
(gdb) info frame
Stack level 1, frame at 0x7ffdb39ffe50:
rip = 0xd243e0 in node::crypto::(anonymous namespace)::SelectALPNCallback(ssl_st*, unsigned char const**, unsigned char*, unsigned char const*, unsigned int, void*); saved rip = 0x1834f83
called by frame at 0x7ffdb39ffe90, caller of frame at 0x7ffdb39ffe40
Arglist at 0x7ffdb39ffe38, args:
Locals at 0x7ffdb39ffe38, Previous frame's sp is 0x7ffdb39ffe50
Saved registers:
rbp at 0x7ffdb39ffe40, rip at 0x7ffdb39ffe48
A disassemble and info registers of the top frame might be enough for me to tell what's going on, and maybe a hex dump of the top 32 or 64 bytes of the stack.
I can't be 100% sure because the data is buried deeper in the stack than I anticipated (isn't it always like that?) but it looks like the ALPN string from the ClientHello packet is zero-sized.
SSL_select_next_proto() selects the first item from the client's string if there's no matching entry in the server's string but yeah, that won't work if the client's string is empty.
Do you have the opportunity to try out a patch locally? Does this one-liner fix it?
diff --git a/src/crypto/crypto_tls.cc b/src/crypto/crypto_tls.cc
index f14adec767a..fea3186c17e 100644
--- a/src/crypto/crypto_tls.cc+++ b/src/crypto/crypto_tls.cc@@ -225,6 +225,8 @@ int SelectALPNCallback(
const unsigned char* in,
unsigned int inlen,
void* arg) {
+ if (inlen == 0) return SSL_TLSEXT_ERR_ALERT_FATAL;+
TLSWrap* w = static_cast<TLSWrap*>(arg);
const std::vector<unsigned char>& alpn_protos = w->alpn_protos_;
No, but I'll try to patch openssl locally to try to reproduce this error on my dev end. If it is true it looks like a possibility DoS attack on node.js
I can't be 100% sure because the data is buried deeper in the stack than I anticipated (isn't it always like that?) but it looks like the ALPN string from the ClientHello packet is zero-sized.
SSL_select_next_proto() selects the first item from the client's string if there's no matching entry in the server's string but yeah, that won't work if the client's string is empty.
Do you have the opportunity to try out a patch locally? Does this one-liner fix it?
diff --git a/src/crypto/crypto_tls.cc b/src/crypto/crypto_tls.cc
index f14adec767a..fea3186c17e 100644
--- a/src/crypto/crypto_tls.cc+++ b/src/crypto/crypto_tls.cc@@ -225,6 +225,8 @@ int SelectALPNCallback(
const unsigned char* in,
unsigned int inlen,
void* arg) {
+ if (inlen == 0) return SSL_TLSEXT_ERR_ALERT_FATAL;+
TLSWrap* w = static_cast<TLSWrap*>(arg);
const std::vector<unsigned char>& alpn_protos = w->alpn_protos_;
inlen equal to zero does not cause any issues, the problem is somewhere in out variable I think. Instead of prod I'm trying to reproduce it in this simple program:
FWIW, I've not been able to reproduce the crash (so far at least.)
I tried crafting a ClientHello with an empty ALPN extension record but I get back a handshake_failure alert and the connection is subsequently closed. No crash.
I tried various combinations of invalid ALPN extensions last week and also wasn't able to reproduce a crash on the server side. Is there any chance you might be able to share a traffic dump of just the TLS handshake that causes the server to crash @gugu?
We can confirm that this issue still exists on node v18.18.0 and node v20.8.0.
We can also confirm that setting ALPNProtocols: [] does NOT resolve the issue.
About 1 of 2M HTTP requests in a representative production environment crashes the node.js process. Our production workload includes HTTP connection from almost all active browser versions. We do not believe this are malicious attempts as the failure rate follows seasonal patterns so its just a function of how many connections are made overall.
I confirm this bug is present with Node 18.18.2 and can be reproduced with 60% probability on the production system where there is quite a heavy load. I cannot reproduce it at all on the pre-production system which is almost identical.
Activity
gugu commentedon Mar 21, 2023
And another one few second ago:
gugu commentedon Mar 22, 2023
It's 2AM and I finally extracted core dump from kubernetes. Does not tell much because I need debug symbols for node 18.15.0
and
bnoordhuis commentedon Mar 23, 2023
A
disassembleandinfo registersof the top frame might be enough for me to tell what's going on, and maybe a hex dump of the top 32 or 64 bytes of the stack.gugu commentedon Mar 23, 2023
gugu commentedon Mar 23, 2023
ALPN callback
gugu commentedon Mar 23, 2023
OpenSSL:
bnoordhuis commentedon Mar 23, 2023
I can't be 100% sure because the data is buried deeper in the stack than I anticipated (isn't it always like that?) but it looks like the ALPN string from the ClientHello packet is zero-sized.
SSL_select_next_proto() selects the first item from the client's string if there's no matching entry in the server's string but yeah, that won't work if the client's string is empty.
Do you have the opportunity to try out a patch locally? Does this one-liner fix it?
gugu commentedon Mar 23, 2023
No, but I'll try to patch openssl locally to try to reproduce this error on my dev end. If it is true it looks like a possibility DoS attack on node.js
gugu commentedon Mar 23, 2023
Found another one, tls client:
gugu commentedon Mar 23, 2023
inlen equal to zero does not cause any issues, the problem is somewhere in out variable I think. Instead of prod I'm trying to reproduce it in this simple program:
UPD: Setting
ALPNProtocols: []in createServer solves the issue for mebnoordhuis commentedon Mar 24, 2023
What was it set to before? Which createServer method are you using (net/http/https/http2)?
gugu commentedon Mar 24, 2023
it is https.createServer(), default value is
['http/1.1']bnoordhuis commentedon Mar 27, 2023
FWIW, I've not been able to reproduce the crash (so far at least.)
I tried crafting a ClientHello with an empty ALPN extension record but I get back a handshake_failure alert and the connection is subsequently closed. No crash.
tniessen commentedon Mar 27, 2023
I tried various combinations of invalid ALPN extensions last week and also wasn't able to reproduce a crash on the server side. Is there any chance you might be able to share a traffic dump of just the TLS handshake that causes the server to crash @gugu?
32 remaining items
sempi commentedon Sep 30, 2023
We can confirm that this issue still exists on node v18.18.0 and node v20.8.0.
We can also confirm that setting ALPNProtocols: [] does NOT resolve the issue.
About 1 of 2M HTTP requests in a representative production environment crashes the node.js process. Our production workload includes HTTP connection from almost all active browser versions. We do not believe this are malicious attempts as the failure rate follows seasonal patterns so its just a function of how many connections are made overall.
sempi commentedon Oct 1, 2023
We can confirm that the fix resolves this crash in node 20.8.0.
#49635
src: fix TLSWrap lifetime bug in ALPN callback
src: fix TLSWrap lifetime bug in ALPN callback
src: fix TLSWrap lifetime bug in ALPN callback
bnoordhuis commentedon Nov 12, 2023
For posterity: the fix hasn't been released in v20.x yet. v20.10.0 is about to be released and contains the fix.
nromito commentedon Nov 16, 2023
@bnoordhuis are there plans to backport this to 18.x?
src: fix TLSWrap lifetime bug in ALPN callback
src: fix TLSWrap lifetime bug in ALPN callback
src: fix TLSWrap lifetime bug in ALPN callback
magnetnation commentedon Apr 25, 2024
We are using v20.11.0. The error is still present.
ivosh commentedon May 17, 2024
I confirm this bug is present with Node 18.18.2 and can be reproduced with 60% probability on the production system where there is quite a heavy load. I cannot reproduce it at all on the pre-production system which is almost identical.