| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent b3a7427 commit 821b11a
7 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -545,25 +545,36 @@ function TLSSocket(socket, opts) { | |||
| 545 | 545 | this[kPendingSession] = null; | |
| 546 | 546 | ||
| 547 | 547 | let wrap; | |
| 548 | - if ((socket instanceof net.Socket && socket._handle) || !socket) { | ||
| 549 | - // 1. connected socket | ||
| 550 | - // 2. no socket, one will be created with net.Socket().connect | ||
| 551 | - wrap = socket; | ||
| 548 | + let handle; | ||
| 549 | + let wrapHasActiveWriteFromPrevOwner; | ||
| 550 | + | ||
| 551 | + if (socket) { | ||
| 552 | + if (socket instanceof net.Socket && socket._handle) { | ||
| 553 | + // 1. connected socket | ||
| 554 | + wrap = socket; | ||
| 555 | + } else { | ||
| 556 | + // 2. socket has no handle so it is js not c++ | ||
| 557 | + // 3. unconnected sockets are wrapped | ||
| 558 | + // TLS expects to interact from C++ with a net.Socket that has a C++ stream | ||
| 559 | + // handle, but a JS stream doesn't have one. Wrap it up to make it look like | ||
| 560 | + // a socket. | ||
| 561 | + wrap = new JSStreamSocket(socket); | ||
| 562 | + } | ||
| 563 | + | ||
| 564 | + handle = wrap._handle; | ||
| 565 | + wrapHasActiveWriteFromPrevOwner = wrap.writableLength > 0; | ||
| 552 | 566 | } else { | |
| 553 | - // 3. socket has no handle so it is js not c++ | ||
| 554 | - // 4. unconnected sockets are wrapped | ||
| 555 | - // TLS expects to interact from C++ with a net.Socket that has a C++ stream | ||
| 556 | - // handle, but a JS stream doesn't have one. Wrap it up to make it look like | ||
| 557 | - // a socket. | ||
| 558 | - wrap = new JSStreamSocket(socket); | ||
| 567 | + // 4. no socket, one will be created with net.Socket().connect | ||
| 568 | + wrap = null; | ||
| 569 | + wrapHasActiveWriteFromPrevOwner = false; | ||
| 559 | 570 | } | |
| 560 | 571 | ||
| 561 | 572 | // Just a documented property to make secure sockets | |
| 562 | 573 | // distinguishable from regular ones. | |
| 563 | 574 | this.encrypted = true; | |
| 564 | 575 | ||
| 565 | 576 | ReflectApply(net.Socket, this, [{ | |
| 566 | - handle: this._wrapHandle(wrap), | ||
| 577 | + handle: this._wrapHandle(wrap, handle, wrapHasActiveWriteFromPrevOwner), | ||
| 567 | 578 | allowHalfOpen: socket ? socket.allowHalfOpen : tlsOptions.allowHalfOpen, | |
| 568 | 579 | pauseOnCreate: tlsOptions.pauseOnConnect, | |
| 569 | 580 | manualStart: true, | |
@@ -582,6 +593,21 @@ function TLSSocket(socket, opts) { | |||
| 582 | 593 | if (enableTrace && this._handle) | |
| 583 | 594 | this._handle.enableTrace(); | |
| 584 | 595 | ||
| 596 | + if (wrapHasActiveWriteFromPrevOwner) { | ||
| 597 | + // `wrap` is a streams.Writable in JS. This empty write will be queued | ||
| 598 | + // and hence finish after all existing writes, which is the timing | ||
| 599 | + // we want to start to send any tls data to `wrap`. | ||
| 600 | + wrap.write('', (err) => { | ||
| 601 | + if (err) { | ||
| 602 | + debug('error got before writing any tls data to the underlying stream'); | ||
| 603 | + this.destroy(err); | ||
| 604 | + return; | ||
| 605 | + } | ||
| 606 | + | ||
| 607 | + this._handle.writesIssuedByPrevListenerDone(); | ||
| 608 | + }); | ||
| 609 | + } | ||
| 610 | + | ||
| 585 | 611 | // Read on next tick so the caller has a chance to setup listeners | |
| 586 | 612 | process.nextTick(initRead, this, socket); | |
| 587 | 613 | } | |
@@ -642,11 +668,14 @@ TLSSocket.prototype.disableRenegotiation = function disableRenegotiation() { | |||
| 642 | 668 | this[kDisableRenegotiation] = true; | |
| 643 | 669 | }; | |
| 644 | 670 | ||
| 645 | - TLSSocket.prototype._wrapHandle = function(wrap, handle) { | ||
| 646 | - if (!handle && wrap) { | ||
| 647 | - handle = wrap._handle; | ||
| 648 | - } | ||
| 649 | - | ||
| 671 | + /** | ||
| 672 | + * | ||
| 673 | + * @param {null|net.Socket} wrap | ||
| 674 | + * @param {null|object} handle | ||
| 675 | + * @param {boolean} wrapHasActiveWriteFromPrevOwner | ||
| 676 | + * @returns {object} | ||
| 677 | + */ | ||
| 678 | + TLSSocket.prototype._wrapHandle = function(wrap, handle, wrapHasActiveWriteFromPrevOwner) { | ||
| 650 | 679 | const options = this._tlsOptions; | |
| 651 | 680 | if (!handle) { | |
| 652 | 681 | handle = options.pipe ? | |
@@ -663,7 +692,10 @@ TLSSocket.prototype._wrapHandle = function(wrap, handle) { | |||
| 663 | 692 | if (!(context.context instanceof NativeSecureContext)) { | |
| 664 | 693 | throw new ERR_TLS_INVALID_CONTEXT('context'); | |
| 665 | 694 | } | |
| 666 | - const res = tls_wrap.wrap(handle, context.context, !!options.isServer); | ||
| 695 | + | ||
| 696 | + const res = tls_wrap.wrap(handle, context.context, | ||
| 697 | + !!options.isServer, | ||
| 698 | + wrapHasActiveWriteFromPrevOwner); | ||
| 667 | 699 | res._parent = handle; // C++ "wrap" object: TCPWrap, JSStream, ... | |
| 668 | 700 | res._parentWrap = wrap; // JS object: net.Socket, JSStreamSocket, ... | |
| 669 | 701 | res._secureContext = context; | |
@@ -680,7 +712,7 @@ TLSSocket.prototype[kReinitializeHandle] = function reinitializeHandle(handle) { | |||
| 680 | 712 | const originalServername = this.ssl ? this._handle.getServername() : null; | |
| 681 | 713 | const originalSession = this.ssl ? this._handle.getSession() : null; | |
| 682 | 714 | ||
| 683 | - this.handle = this._wrapHandle(null, handle); | ||
| 715 | + this.handle = this._wrapHandle(null, handle, false); | ||
| 684 | 716 | this.ssl = this._handle; | |
| 685 | 717 | ||
| 686 | 718 | net.Socket.prototype[kReinitializeHandle].call(this, this.handle); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -357,12 +357,15 @@ TLSWrap::TLSWrap(Environment* env, | |||
| 357 | 357 | Local<Object> obj, | |
| 358 | 358 | Kind kind, | |
| 359 | 359 | StreamBase* stream, | |
| 360 | - SecureContext* sc) | ||
| 360 | + SecureContext* sc, | ||
| 361 | + UnderlyingStreamWriteStatus under_stream_ws) | ||
| 361 | 362 | : AsyncWrap(env, obj, AsyncWrap::PROVIDER_TLSWRAP), | |
| 362 | 363 | StreamBase(env), | |
| 363 | 364 | env_(env), | |
| 364 | 365 | kind_(kind), | |
| 365 | - sc_(sc) { | ||
| 366 | + sc_(sc), | ||
| 367 | + has_active_write_issued_by_prev_listener_( | ||
| 368 | + under_stream_ws == UnderlyingStreamWriteStatus::kHasActive) { | ||
| 366 | 369 | MakeWeak(); | |
| 367 | 370 | CHECK(sc_); | |
| 368 | 371 | ssl_ = sc_->CreateSSL(); | |
@@ -472,14 +475,19 @@ void TLSWrap::InitSSL() { | |||
| 472 | 475 | void TLSWrap::Wrap(const FunctionCallbackInfo<Value>& args) { | |
| 473 | 476 | Environment* env = Environment::GetCurrent(args); | |
| 474 | 477 | ||
| 475 | - CHECK_EQ(args.Length(), 3); | ||
| 478 | + CHECK_EQ(args.Length(), 4); | ||
| 476 | 479 | CHECK(args[0]->IsObject()); | |
| 477 | 480 | CHECK(args[1]->IsObject()); | |
| 478 | 481 | CHECK(args[2]->IsBoolean()); | |
| 482 | + CHECK(args[3]->IsBoolean()); | ||
| 479 | 483 | ||
| 480 | 484 | Local<Object> sc = args[1].As<Object>(); | |
| 481 | 485 | Kind kind = args[2]->IsTrue() ? Kind::kServer : Kind::kClient; | |
| 482 | 486 | ||
| 487 | + UnderlyingStreamWriteStatus under_stream_ws = | ||
| 488 | + args[3]->IsTrue() ? UnderlyingStreamWriteStatus::kHasActive | ||
| 489 | + : UnderlyingStreamWriteStatus::kVacancy; | ||
| 490 | + | ||
| 483 | 491 | StreamBase* stream = StreamBase::FromObject(args[0].As<Object>()); | |
| 484 | 492 | CHECK_NOT_NULL(stream); | |
| 485 | 493 | ||
@@ -490,7 +498,8 @@ void TLSWrap::Wrap(const FunctionCallbackInfo<Value>& args) { | |||
| 490 | 498 | return; | |
| 491 | 499 | } | |
| 492 | 500 | ||
| 493 | - TLSWrap* res = new TLSWrap(env, obj, kind, stream, Unwrap<SecureContext>(sc)); | ||
| 501 | + TLSWrap* res = new TLSWrap( | ||
| 502 | + env, obj, kind, stream, Unwrap<SecureContext>(sc), under_stream_ws); | ||
| 494 | 503 | ||
| 495 | 504 | args.GetReturnValue().Set(res->object()); | |
| 496 | 505 | } | |
@@ -596,6 +605,13 @@ void TLSWrap::EncOut() { | |||
| 596 | 605 | return; | |
| 597 | 606 | } | |
| 598 | 607 | ||
| 608 | + if (UNLIKELY(has_active_write_issued_by_prev_listener_)) { | ||
| 609 | + Debug(this, | ||
| 610 | + "Returning from EncOut(), " | ||
| 611 | + "has_active_write_issued_by_prev_listener_ is true"); | ||
| 612 | + return; | ||
| 613 | + } | ||
| 614 | + | ||
| 599 | 615 | // Split-off queue | |
| 600 | 616 | if (established_ && current_write_) { | |
| 601 | 617 | Debug(this, "EncOut() write is scheduled"); | |
@@ -666,6 +682,15 @@ void TLSWrap::EncOut() { | |||
| 666 | 682 | ||
| 667 | 683 | void TLSWrap::OnStreamAfterWrite(WriteWrap* req_wrap, int status) { | |
| 668 | 684 | Debug(this, "OnStreamAfterWrite(status = %d)", status); | |
| 685 | + | ||
| 686 | + if (UNLIKELY(has_active_write_issued_by_prev_listener_)) { | ||
| 687 | + Debug(this, "Notify write finish to the previous_listener_"); | ||
| 688 | + CHECK_EQ(write_size_, 0); // we must have restrained writes | ||
| 689 | + | ||
| 690 | + previous_listener_->OnStreamAfterWrite(req_wrap, status); | ||
| 691 | + return; | ||
| 692 | + } | ||
| 693 | + | ||
| 669 | 694 | if (current_empty_write_) { | |
| 670 | 695 | Debug(this, "Had empty write"); | |
| 671 | 696 | BaseObjectPtr<AsyncWrap> current_empty_write = | |
@@ -2021,6 +2046,16 @@ void TLSWrap::GetALPNNegotiatedProto(const FunctionCallbackInfo<Value>& args) { | |||
| 2021 | 2046 | args.GetReturnValue().Set(result); | |
| 2022 | 2047 | } | |
| 2023 | 2048 | ||
| 2049 | + void TLSWrap::WritesIssuedByPrevListenerDone( | ||
| 2050 | + const FunctionCallbackInfo<Value>& args) { | ||
| 2051 | + TLSWrap* w; | ||
| 2052 | + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); | ||
| 2053 | + | ||
| 2054 | + Debug(w, "WritesIssuedByPrevListenerDone is called"); | ||
| 2055 | + w->has_active_write_issued_by_prev_listener_ = false; | ||
| 2056 | + w->EncOut(); // resume all of our restrained writes | ||
| 2057 | + } | ||
| 2058 | + | ||
| 2024 | 2059 | void TLSWrap::Cycle() { | |
| 2025 | 2060 | // Prevent recursion | |
| 2026 | 2061 | if (++cycle_depth_ > 1) | |
@@ -2098,6 +2133,10 @@ void TLSWrap::Initialize( | |||
| 2098 | 2133 | SetProtoMethod(isolate, t, "setSession", SetSession); | |
| 2099 | 2134 | SetProtoMethod(isolate, t, "setVerifyMode", SetVerifyMode); | |
| 2100 | 2135 | SetProtoMethod(isolate, t, "start", Start); | |
| 2136 | + SetProtoMethod(isolate, | ||
| 2137 | + t, | ||
| 2138 | + "writesIssuedByPrevListenerDone", | ||
| 2139 | + WritesIssuedByPrevListenerDone); | ||
| 2101 | 2140 | ||
| 2102 | 2141 | SetProtoMethodNoSideEffect( | |
| 2103 | 2142 | isolate, t, "exportKeyingMaterial", ExportKeyingMaterial); | |
@@ -2180,6 +2219,7 @@ void TLSWrap::RegisterExternalReferences(ExternalReferenceRegistry* registry) { | |||
| 2180 | 2219 | registry->Register(GetSharedSigalgs); | |
| 2181 | 2220 | registry->Register(GetTLSTicket); | |
| 2182 | 2221 | registry->Register(VerifyError); | |
| 2222 | + registry->Register(WritesIssuedByPrevListenerDone); | ||
| 2183 | 2223 | ||
| 2184 | 2224 | #ifdef SSL_set_max_send_fragment | |
| 2185 | 2225 | registry->Register(SetMaxSendFragment); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -48,6 +48,8 @@ class TLSWrap : public AsyncWrap, | |||
| 48 | 48 | kServer | |
| 49 | 49 | }; | |
| 50 | 50 | ||
| 51 | + enum class UnderlyingStreamWriteStatus { kHasActive, kVacancy }; | ||
| 52 | + | ||
| 51 | 53 | static void Initialize(v8::Local<v8::Object> target, | |
| 52 | 54 | v8::Local<v8::Value> unused, | |
| 53 | 55 | v8::Local<v8::Context> context, | |
@@ -136,7 +138,8 @@ class TLSWrap : public AsyncWrap, | |||
| 136 | 138 | v8::Local<v8::Object> obj, | |
| 137 | 139 | Kind kind, | |
| 138 | 140 | StreamBase* stream, | |
| 139 | - SecureContext* sc); | ||
| 141 | + SecureContext* sc, | ||
| 142 | + UnderlyingStreamWriteStatus under_stream_ws); | ||
| 140 | 143 | ||
| 141 | 144 | static void SSLInfoCallback(const SSL* ssl_, int where, int ret); | |
| 142 | 145 | void InitSSL(); | |
@@ -217,6 +220,8 @@ class TLSWrap : public AsyncWrap, | |||
| 217 | 220 | static void Start(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 218 | 221 | static void VerifyError(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 219 | 222 | static void Wrap(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 223 | + static void WritesIssuedByPrevListenerDone( | ||
| 224 | + const v8::FunctionCallbackInfo<v8::Value>& args); | ||
| 220 | 225 | ||
| 221 | 226 | #ifdef SSL_set_max_send_fragment | |
| 222 | 227 | static void SetMaxSendFragment( | |
@@ -284,6 +289,8 @@ class TLSWrap : public AsyncWrap, | |||
| 284 | 289 | ||
| 285 | 290 | BIOPointer bio_trace_; | |
| 286 | 291 | ||
| 292 | + bool has_active_write_issued_by_prev_listener_ = false; | ||
| 293 | + | ||
| 287 | 294 | public: | |
| 288 | 295 | std::vector<unsigned char> alpn_protos_; // Accessed by SelectALPNCallback. | |
| 289 | 296 | bool alpn_callback_enabled_ = false; // Accessed by SelectALPNCallback. | |
| Original file line number | Diff line number | Diff 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 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments