Skip to content

Commit d96083b

Browse files
jasnellgengjiawen
authored andcommitted
quic: introduce QuicCallbackScope
Alternative to `CallbackScope` that handles destroying the `QuicSession` in the try_catch cleanup. PR-URL: #34541 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Jiawen Geng <technicalcute@gmail.com>
1 parent 4b0275a commit d96083b

File tree

3 files changed

+100
-35
lines changed

3 files changed

+100
-35
lines changed

src/quic/node_quic_crypto.cc

+12-2
Original file line numberDiff line numberDiff line change
@@ -351,8 +351,14 @@ Local<Value> GetALPNProtocol(const QuicSession& session) {
351351
namespace {
352352
int CertCB(SSL* ssl, void* arg) {
353353
QuicSession* session = static_cast<QuicSession*>(arg);
354-
return SSL_get_tlsext_status_type(ssl) == TLSEXT_STATUSTYPE_ocsp ?
355-
session->crypto_context()->OnOCSP() : 1;
354+
int ret;
355+
switch (SSL_get_tlsext_status_type(ssl)) {
356+
case TLSEXT_STATUSTYPE_ocsp:
357+
ret = session->crypto_context()->OnOCSP();
358+
return UNLIKELY(session->is_destroyed()) ? 0 : ret;
359+
default:
360+
return 1;
361+
}
356362
}
357363

358364
void Keylog_CB(const SSL* ssl, const char* line) {
@@ -366,6 +372,10 @@ int Client_Hello_CB(
366372
void* arg) {
367373
QuicSession* session = static_cast<QuicSession*>(SSL_get_app_data(ssl));
368374
int ret = session->crypto_context()->OnClientHello();
375+
if (UNLIKELY(session->is_destroyed())) {
376+
*tls_alert = SSL_R_SSL_HANDSHAKE_FAILURE;
377+
return 0;
378+
}
369379
switch (ret) {
370380
case 0:
371381
return 1;

src/quic/node_quic_session.cc

+72-33
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,40 @@ using v8::String;
5757
using v8::Undefined;
5858
using v8::Value;
5959

60+
using TryCatchScope = node::errors::TryCatchScope;
61+
6062
namespace quic {
6163

64+
QuicCallbackScope::QuicCallbackScope(QuicSession* session)
65+
: session_(session),
66+
private_(new InternalCallbackScope(
67+
session->env(),
68+
session->object(),
69+
{
70+
session->get_async_id(),
71+
session->get_trigger_async_id()
72+
})),
73+
try_catch_(session->env()->isolate()) {
74+
try_catch_.SetVerbose(true);
75+
}
76+
77+
QuicCallbackScope::~QuicCallbackScope() {
78+
Environment* env = session_->env();
79+
if (UNLIKELY(try_catch_.HasCaught())) {
80+
session_->crypto_context()->set_in_client_hello(false);
81+
session_->crypto_context()->set_in_ocsp_request(false);
82+
if (!try_catch_.HasTerminated() && env->can_call_into_js()) {
83+
session_->set_last_error({
84+
QUIC_ERROR_SESSION,
85+
uint64_t{NGTCP2_INTERNAL_ERROR}
86+
});
87+
session_->Close();
88+
CHECK(session_->is_destroyed());
89+
}
90+
private_->MarkAsFailed();
91+
}
92+
}
93+
6294
typedef ssize_t(*ngtcp2_close_fn)(
6395
ngtcp2_conn* conn,
6496
ngtcp2_path* path,
@@ -393,53 +425,59 @@ void JSQuicSessionListener::OnClientHello(
393425
HandleScope scope(env->isolate());
394426
Context::Scope context_scope(env->context());
395427

428+
// Why this instead of using MakeCallback? We need to catch any
429+
// errors that happen both when preparing the arguments and
430+
// invoking the callback so that we can properly signal a failure
431+
// to the peer.
432+
QuicCallbackScope cb_scope(session());
433+
396434
Local<Array> ciphers;
397435
Local<Value> alpn_string = Undefined(env->isolate());
398436
Local<Value> servername = Undefined(env->isolate());
399437

400-
// TODO(@jasnell): Need to decide how to handle the possible
401-
// ToLocal failures more gracefully than crashing.
402-
403-
CHECK(session()->crypto_context()->hello_ciphers().ToLocal(&ciphers));
404-
405-
if (alpn != nullptr)
406-
CHECK(String::NewFromUtf8(env->isolate(), alpn).ToLocal(&alpn_string));
407-
408-
if (server_name != nullptr)
409-
CHECK(String::NewFromUtf8(env->isolate(), server_name)
410-
.ToLocal(&servername));
411-
412-
Local<Value> argv[] = {
413-
alpn_string,
414-
servername,
415-
ciphers
416-
};
438+
if (session()->crypto_context()->hello_ciphers().ToLocal(&ciphers) &&
439+
(alpn == nullptr ||
440+
String::NewFromUtf8(env->isolate(), alpn).ToLocal(&alpn_string)) &&
441+
(server_name == nullptr ||
442+
String::NewFromUtf8(env->isolate(), server_name).ToLocal(&servername))) {
443+
Local<Value> argv[] = {
444+
alpn_string,
445+
servername,
446+
ciphers
447+
};
417448

418-
// Grab a shared pointer to this to prevent the QuicSession
419-
// from being freed while the MakeCallback is running.
420-
BaseObjectPtr<QuicSession> ptr(session());
421-
session()->MakeCallback(
422-
env->quic_on_session_client_hello_function(),
423-
arraysize(argv), argv);
449+
// Grab a shared pointer to this to prevent the QuicSession
450+
// from being freed while the MakeCallback is running.
451+
BaseObjectPtr<QuicSession> ptr(session());
452+
env->quic_on_session_client_hello_function()->Call(
453+
env->context(),
454+
session()->object(),
455+
arraysize(argv),
456+
argv);
457+
}
424458
}
425459

426460
void JSQuicSessionListener::OnCert(const char* server_name) {
427461
Environment* env = session()->env();
428462
HandleScope handle_scope(env->isolate());
429463
Context::Scope context_scope(env->context());
430464

465+
QuicCallbackScope cb_scope(session());
466+
431467
Local<Value> servername = Undefined(env->isolate());
432-
if (server_name != nullptr) {
433-
servername = OneByteString(
434-
env->isolate(),
435-
server_name,
436-
strlen(server_name));
437-
}
438468

439-
// Grab a shared pointer to this to prevent the QuicSession
440-
// from being freed while the MakeCallback is running.
441469
BaseObjectPtr<QuicSession> ptr(session());
442-
session()->MakeCallback(env->quic_on_session_cert_function(), 1, &servername);
470+
if (server_name == nullptr ||
471+
String::NewFromUtf8(
472+
env->isolate(),
473+
server_name,
474+
v8::NewStringType::kNormal,
475+
strlen(server_name)).ToLocal(&servername)) {
476+
env->quic_on_session_cert_function()->Call(
477+
env->context(),
478+
session()->object(),
479+
1, &servername);
480+
}
443481
}
444482

445483
void JSQuicSessionListener::OnStreamHeaders(
@@ -2913,11 +2951,12 @@ int QuicSession::OnReceiveCryptoData(
29132951
return NGTCP2_ERR_CALLBACK_FAILURE;
29142952

29152953
QuicSession::NgCallbackScope callback_scope(session);
2916-
return session->crypto_context()->Receive(
2954+
int ret = session->crypto_context()->Receive(
29172955
crypto_level,
29182956
offset,
29192957
data,
29202958
datalen);
2959+
return ret == 0 ? 0 : NGTCP2_ERR_CALLBACK_FAILURE;
29212960
}
29222961

29232962
// Called by ngtcp2 for both client and server connections

src/quic/node_quic_session.h

+16
Original file line numberDiff line numberDiff line change
@@ -1507,6 +1507,22 @@ class QuicSession final : public AsyncWrap,
15071507
friend class JSQuicSessionListener;
15081508
};
15091509

1510+
class QuicCallbackScope {
1511+
public:
1512+
explicit QuicCallbackScope(QuicSession* session);
1513+
~QuicCallbackScope();
1514+
1515+
void operator=(const QuicCallbackScope&) = delete;
1516+
void operator=(QuicCallbackScope&&) = delete;
1517+
QuicCallbackScope(const QuicCallbackScope&) = delete;
1518+
QuicCallbackScope(QuicCallbackScope&&) = delete;
1519+
1520+
private:
1521+
BaseObjectPtr<QuicSession> session_;
1522+
std::unique_ptr<InternalCallbackScope> private_;
1523+
v8::TryCatch try_catch_;
1524+
};
1525+
15101526
} // namespace quic
15111527
} // namespace node
15121528

0 commit comments

Comments
 (0)