Skip to content

Commit 3cb20fd

Browse files
ywave620mhdawson
authored andcommitted
tls: fix bugs of double TLS
Fixs two issues in `TLSWrap`, one of them is reported in nodejs#30896. 1. `TLSWrap` has exactly one `StreamListener`, however, that `StreamListener` can be replaced. We have not been rigorous enough here: if an active write has not been finished before the transition, the finish callback of it will be wrongly fired the successor `StreamListener`. 2. A `TLSWrap` does not allow more than one active write, as checked in the assertion about current_write in `TLSWrap::DoWrite()`. However, when users make use of an existing `tls.TLSSocket` to establish double TLS, by either tls.connect({socket: tlssock}) or tlsServer.emit('connection', tlssock) we have both of the user provided `tls.TLSSocket`, tlssock and a brand new created `TLSWrap` writing to the `TLSWrap` bound to tlssock, which easily violates the constranint because two writers have no idea of each other. The design of the fix is: when a `TLSWrap` is created on top of a user provided socket, do not send any data to the socket until all existing writes of the socket are done and ensure registered callbacks of those writes can be fired.
1 parent 1660810 commit 3cb20fd

7 files changed

+296
-24
lines changed

lib/_tls_wrap.js

+51-18
Original file line numberDiff line numberDiff line change
@@ -502,25 +502,36 @@ function TLSSocket(socket, opts) {
502502
this[kPendingSession] = null;
503503

504504
let wrap;
505-
if ((socket instanceof net.Socket && socket._handle) || !socket) {
506-
// 1. connected socket
507-
// 2. no socket, one will be created with net.Socket().connect
508-
wrap = socket;
505+
let handle;
506+
let wrapHasActiveWriteFromPrevOwner;
507+
508+
if (socket) {
509+
if (socket instanceof net.Socket && socket._handle) {
510+
// 1. connected socket
511+
wrap = socket;
512+
} else {
513+
// 2. socket has no handle so it is js not c++
514+
// 3. unconnected sockets are wrapped
515+
// TLS expects to interact from C++ with a net.Socket that has a C++ stream
516+
// handle, but a JS stream doesn't have one. Wrap it up to make it look like
517+
// a socket.
518+
wrap = new JSStreamSocket(socket);
519+
}
520+
521+
handle = wrap._handle;
522+
wrapHasActiveWriteFromPrevOwner = wrap.writableLength > 0;
509523
} else {
510-
// 3. socket has no handle so it is js not c++
511-
// 4. unconnected sockets are wrapped
512-
// TLS expects to interact from C++ with a net.Socket that has a C++ stream
513-
// handle, but a JS stream doesn't have one. Wrap it up to make it look like
514-
// a socket.
515-
wrap = new JSStreamSocket(socket);
524+
// 4. no socket, one will be created with net.Socket().connect
525+
wrap = null;
526+
wrapHasActiveWriteFromPrevOwner = false;
516527
}
517528

518529
// Just a documented property to make secure sockets
519530
// distinguishable from regular ones.
520531
this.encrypted = true;
521532

522533
ReflectApply(net.Socket, this, [{
523-
handle: this._wrapHandle(wrap),
534+
handle: this._wrapHandle(wrap, handle, wrapHasActiveWriteFromPrevOwner),
524535
allowHalfOpen: socket ? socket.allowHalfOpen : tlsOptions.allowHalfOpen,
525536
pauseOnCreate: tlsOptions.pauseOnConnect,
526537
manualStart: true,
@@ -539,6 +550,22 @@ function TLSSocket(socket, opts) {
539550
if (enableTrace && this._handle)
540551
this._handle.enableTrace();
541552

553+
if (wrapHasActiveWriteFromPrevOwner) {
554+
// `wrap` is a streams.Writable in JS. This empty write will be queued
555+
// and hence finish after all existing writes, which is the timing
556+
// we want to start to send any tls data to `wrap`.
557+
const that = this;
558+
wrap.write('', (err) => {
559+
if (err) {
560+
debug('error got before writing any tls data to the underlying stream');
561+
that.destroy(err);
562+
return;
563+
}
564+
565+
that._handle.writesIssuedByPrevListenerDone();
566+
});
567+
}
568+
542569
// Read on next tick so the caller has a chance to setup listeners
543570
process.nextTick(initRead, this, socket);
544571
}
@@ -599,11 +626,14 @@ TLSSocket.prototype.disableRenegotiation = function disableRenegotiation() {
599626
this[kDisableRenegotiation] = true;
600627
};
601628

602-
TLSSocket.prototype._wrapHandle = function(wrap, handle) {
603-
if (!handle && wrap) {
604-
handle = wrap._handle;
605-
}
606-
629+
/**
630+
*
631+
* @param {null|net.Socket} wrap
632+
* @param {null|object} handle
633+
* @param {boolean} wrapHasActiveWriteFromPrevOwner
634+
* @returns {object}
635+
*/
636+
TLSSocket.prototype._wrapHandle = function(wrap, handle, wrapHasActiveWriteFromPrevOwner) {
607637
const options = this._tlsOptions;
608638
if (!handle) {
609639
handle = options.pipe ?
@@ -620,7 +650,10 @@ TLSSocket.prototype._wrapHandle = function(wrap, handle) {
620650
if (!(context.context instanceof NativeSecureContext)) {
621651
throw new ERR_TLS_INVALID_CONTEXT('context');
622652
}
623-
const res = tls_wrap.wrap(handle, context.context, !!options.isServer);
653+
654+
const res = tls_wrap.wrap(handle, context.context,
655+
!!options.isServer,
656+
wrapHasActiveWriteFromPrevOwner);
624657
res._parent = handle; // C++ "wrap" object: TCPWrap, JSStream, ...
625658
res._parentWrap = wrap; // JS object: net.Socket, JSStreamSocket, ...
626659
res._secureContext = context;
@@ -637,7 +670,7 @@ TLSSocket.prototype[kReinitializeHandle] = function reinitializeHandle(handle) {
637670
const originalServername = this.ssl ? this._handle.getServername() : null;
638671
const originalSession = this.ssl ? this._handle.getSession() : null;
639672

640-
this.handle = this._wrapHandle(null, handle);
673+
this.handle = this._wrapHandle(null, handle, false);
641674
this.ssl = this._handle;
642675

643676
net.Socket.prototype[kReinitializeHandle].call(this, this.handle);

src/crypto/crypto_tls.cc

+37-4
Original file line numberDiff line numberDiff line change
@@ -319,12 +319,15 @@ TLSWrap::TLSWrap(Environment* env,
319319
Local<Object> obj,
320320
Kind kind,
321321
StreamBase* stream,
322-
SecureContext* sc)
322+
SecureContext* sc,
323+
bool stream_has_active_write)
323324
: AsyncWrap(env, obj, AsyncWrap::PROVIDER_TLSWRAP),
324325
StreamBase(env),
325326
env_(env),
326327
kind_(kind),
327-
sc_(sc) {
328+
sc_(sc),
329+
has_active_write_issued_by_prev_listener_(
330+
stream_has_active_write) {
328331
MakeWeak();
329332
CHECK(sc_);
330333
ssl_ = sc_->CreateSSL();
@@ -434,10 +437,11 @@ void TLSWrap::InitSSL() {
434437
void TLSWrap::Wrap(const FunctionCallbackInfo<Value>& args) {
435438
Environment* env = Environment::GetCurrent(args);
436439

437-
CHECK_EQ(args.Length(), 3);
440+
CHECK_EQ(args.Length(), 4);
438441
CHECK(args[0]->IsObject());
439442
CHECK(args[1]->IsObject());
440443
CHECK(args[2]->IsBoolean());
444+
CHECK(args[3]->IsBoolean());
441445

442446
Local<Object> sc = args[1].As<Object>();
443447
Kind kind = args[2]->IsTrue() ? Kind::kServer : Kind::kClient;
@@ -452,7 +456,8 @@ void TLSWrap::Wrap(const FunctionCallbackInfo<Value>& args) {
452456
return;
453457
}
454458

455-
TLSWrap* res = new TLSWrap(env, obj, kind, stream, Unwrap<SecureContext>(sc));
459+
TLSWrap* res = new TLSWrap(env, obj, kind, stream, Unwrap<SecureContext>(sc),
460+
args[3]->IsTrue() /* stream_has_active_write */);
456461

457462
args.GetReturnValue().Set(res->object());
458463
}
@@ -558,6 +563,12 @@ void TLSWrap::EncOut() {
558563
return;
559564
}
560565

566+
if (UNLIKELY(has_active_write_issued_by_prev_listener_)) {
567+
Debug(this, "Returning from EncOut(), "
568+
"has_active_write_issued_by_prev_listener_ is true");
569+
return;
570+
}
571+
561572
// Split-off queue
562573
if (established_ && current_write_) {
563574
Debug(this, "EncOut() write is scheduled");
@@ -628,6 +639,15 @@ void TLSWrap::EncOut() {
628639

629640
void TLSWrap::OnStreamAfterWrite(WriteWrap* req_wrap, int status) {
630641
Debug(this, "OnStreamAfterWrite(status = %d)", status);
642+
643+
if (UNLIKELY(has_active_write_issued_by_prev_listener_)) {
644+
Debug(this, "Notify write finish to the previous_listener_");
645+
CHECK_EQ(write_size_, 0); // we must have restrained writes
646+
647+
previous_listener_->OnStreamAfterWrite(req_wrap, status);
648+
return;
649+
}
650+
631651
if (current_empty_write_) {
632652
Debug(this, "Had empty write");
633653
BaseObjectPtr<AsyncWrap> current_empty_write =
@@ -1974,6 +1994,16 @@ void TLSWrap::GetALPNNegotiatedProto(const FunctionCallbackInfo<Value>& args) {
19741994
args.GetReturnValue().Set(result);
19751995
}
19761996

1997+
void TLSWrap::WritesIssuedByPrevListenerDone(
1998+
const FunctionCallbackInfo<Value>& args) {
1999+
TLSWrap* w;
2000+
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
2001+
2002+
Debug(w, "WritesIssuedByPrevListenerDone is called");
2003+
w->has_active_write_issued_by_prev_listener_ = false;
2004+
w->EncOut(); // resume all of our restrained writes
2005+
}
2006+
19772007
void TLSWrap::Cycle() {
19782008
// Prevent recursion
19792009
if (++cycle_depth_ > 1)
@@ -2050,6 +2080,8 @@ void TLSWrap::Initialize(
20502080
SetProtoMethod(isolate, t, "setSession", SetSession);
20512081
SetProtoMethod(isolate, t, "setVerifyMode", SetVerifyMode);
20522082
SetProtoMethod(isolate, t, "start", Start);
2083+
SetProtoMethod(isolate, t, "writesIssuedByPrevListenerDone",
2084+
WritesIssuedByPrevListenerDone);
20532085

20542086
SetProtoMethodNoSideEffect(
20552087
isolate, t, "exportKeyingMaterial", ExportKeyingMaterial);
@@ -2131,6 +2163,7 @@ void TLSWrap::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
21312163
registry->Register(GetSharedSigalgs);
21322164
registry->Register(GetTLSTicket);
21332165
registry->Register(VerifyError);
2166+
registry->Register(WritesIssuedByPrevListenerDone);
21342167

21352168
#ifdef SSL_set_max_send_fragment
21362169
registry->Register(SetMaxSendFragment);

src/crypto/crypto_tls.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ class TLSWrap : public AsyncWrap,
136136
v8::Local<v8::Object> obj,
137137
Kind kind,
138138
StreamBase* stream,
139-
SecureContext* sc);
139+
SecureContext* sc,
140+
bool stream_has_active_write);
140141

141142
static void SSLInfoCallback(const SSL* ssl_, int where, int ret);
142143
void InitSSL();
@@ -216,6 +217,8 @@ class TLSWrap : public AsyncWrap,
216217
static void Start(const v8::FunctionCallbackInfo<v8::Value>& args);
217218
static void VerifyError(const v8::FunctionCallbackInfo<v8::Value>& args);
218219
static void Wrap(const v8::FunctionCallbackInfo<v8::Value>& args);
220+
static void WritesIssuedByPrevListenerDone(
221+
const v8::FunctionCallbackInfo<v8::Value>& args);
219222

220223
#ifdef SSL_set_max_send_fragment
221224
static void SetMaxSendFragment(
@@ -283,6 +286,8 @@ class TLSWrap : public AsyncWrap,
283286

284287
BIOPointer bio_trace_;
285288

289+
bool has_active_write_issued_by_prev_listener_ = false;
290+
286291
public:
287292
std::vector<unsigned char> alpn_protos_; // Accessed by SelectALPNCallback.
288293
};
+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
if (!common.hasCrypto) common.skip('missing crypto');
5+
const fixtures = require('../common/fixtures');
6+
const tls = require('tls');
7+
8+
// In reality, this can be a HTTP CONNECT message, signaling the incoming
9+
// data is TLS encrypted
10+
const HEAD = 'XXXX';
11+
12+
const subserver = tls.createServer({
13+
key: fixtures.readKey('agent1-key.pem'),
14+
cert: fixtures.readKey('agent1-cert.pem'),
15+
})
16+
.on('secureConnection', common.mustCall(() => {
17+
process.exit(0);
18+
}));
19+
20+
const server = tls.createServer({
21+
key: fixtures.readKey('agent1-key.pem'),
22+
cert: fixtures.readKey('agent1-cert.pem'),
23+
})
24+
.listen(client)
25+
.on('secureConnection', (serverTlsSock) => {
26+
serverTlsSock.on('data', (chunk) => {
27+
assert.strictEqual(chunk.toString(), HEAD);
28+
subserver.emit('connection', serverTlsSock);
29+
});
30+
});
31+
32+
function client() {
33+
const down = tls.connect({
34+
host: '127.0.0.1',
35+
port: server.address().port,
36+
rejectUnauthorized: false
37+
}).on('secureConnect', () => {
38+
down.write(HEAD, common.mustSucceed());
39+
40+
// Sending tls data on a client TLSSocket with an active write led to a crash:
41+
//
42+
// node[16862]: ../src/crypto/crypto_tls.cc:963:virtual int node::crypto::TLSWrap::DoWrite(node::WriteWrap*,
43+
// uv_buf_t*, size_t, uv_stream_t*): Assertion `!current_write_' failed.
44+
// 1: 0xb090e0 node::Abort() [node]
45+
// 2: 0xb0915e [node]
46+
// 3: 0xca8413 node::crypto::TLSWrap::DoWrite(node::WriteWrap*, uv_buf_t*, unsigned long, uv_stream_s*) [node]
47+
// 4: 0xcaa549 node::StreamBase::Write(uv_buf_t*, unsigned long, uv_stream_s*, v8::Local<v8::Object>) [node]
48+
// 5: 0xca88d7 node::crypto::TLSWrap::EncOut() [node]
49+
// 6: 0xd3df3e [node]
50+
// 7: 0xd3f35f v8::internal::Builtin_HandleApiCall(int, unsigned long*, v8::internal::Isolate*) [node]
51+
// 8: 0x15d9ef9 [node]
52+
// Aborted
53+
tls.connect({
54+
socket: down,
55+
rejectUnauthorized: false
56+
});
57+
});
58+
}

0 commit comments

Comments
 (0)