| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 6de7aa1 commit 3eeca52
7 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -502,25 +502,36 @@ function TLSSocket(socket, opts) { | |||
| 502 | 502 | this[kPendingSession] = null; | |
| 503 | 503 | ||
| 504 | 504 | 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; | ||
| 509 | 523 | } 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; | ||
| 516 | 527 | } | |
| 517 | 528 | ||
| 518 | 529 | // Just a documented property to make secure sockets | |
| 519 | 530 | // distinguishable from regular ones. | |
| 520 | 531 | this.encrypted = true; | |
| 521 | 532 | ||
| 522 | 533 | ReflectApply(net.Socket, this, [{ | |
| 523 | - handle: this._wrapHandle(wrap), | ||
| 534 | + handle: this._wrapHandle(wrap, handle, wrapHasActiveWriteFromPrevOwner), | ||
| 524 | 535 | allowHalfOpen: socket ? socket.allowHalfOpen : tlsOptions.allowHalfOpen, | |
| 525 | 536 | pauseOnCreate: tlsOptions.pauseOnConnect, | |
| 526 | 537 | manualStart: true, | |
@@ -539,6 +550,21 @@ function TLSSocket(socket, opts) { | |||
| 539 | 550 | if (enableTrace && this._handle) | |
| 540 | 551 | this._handle.enableTrace(); | |
| 541 | 552 | ||
| 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 | + wrap.write('', (err) => { | ||
| 558 | + if (err) { | ||
| 559 | + debug('error got before writing any tls data to the underlying stream'); | ||
| 560 | + this.destroy(err); | ||
| 561 | + return; | ||
| 562 | + } | ||
| 563 | + | ||
| 564 | + this._handle.writesIssuedByPrevListenerDone(); | ||
| 565 | + }); | ||
| 566 | + } | ||
| 567 | + | ||
| 542 | 568 | // Read on next tick so the caller has a chance to setup listeners | |
| 543 | 569 | process.nextTick(initRead, this, socket); | |
| 544 | 570 | } | |
@@ -599,11 +625,14 @@ TLSSocket.prototype.disableRenegotiation = function disableRenegotiation() { | |||
| 599 | 625 | this[kDisableRenegotiation] = true; | |
| 600 | 626 | }; | |
| 601 | 627 | ||
| 602 | - TLSSocket.prototype._wrapHandle = function(wrap, handle) { | ||
| 603 | - if (!handle && wrap) { | ||
| 604 | - handle = wrap._handle; | ||
| 605 | - } | ||
| 606 | - | ||
| 628 | + /** | ||
| 629 | + * | ||
| 630 | + * @param {null|net.Socket} wrap | ||
| 631 | + * @param {null|object} handle | ||
| 632 | + * @param {boolean} wrapHasActiveWriteFromPrevOwner | ||
| 633 | + * @returns {object} | ||
| 634 | + */ | ||
| 635 | + TLSSocket.prototype._wrapHandle = function(wrap, handle, wrapHasActiveWriteFromPrevOwner) { | ||
| 607 | 636 | const options = this._tlsOptions; | |
| 608 | 637 | if (!handle) { | |
| 609 | 638 | handle = options.pipe ? | |
@@ -620,7 +649,10 @@ TLSSocket.prototype._wrapHandle = function(wrap, handle) { | |||
| 620 | 649 | if (!(context.context instanceof NativeSecureContext)) { | |
| 621 | 650 | throw new ERR_TLS_INVALID_CONTEXT('context'); | |
| 622 | 651 | } | |
| 623 | - const res = tls_wrap.wrap(handle, context.context, !!options.isServer); | ||
| 652 | + | ||
| 653 | + const res = tls_wrap.wrap(handle, context.context, | ||
| 654 | + !!options.isServer, | ||
| 655 | + wrapHasActiveWriteFromPrevOwner); | ||
| 624 | 656 | res._parent = handle; // C++ "wrap" object: TCPWrap, JSStream, ... | |
| 625 | 657 | res._parentWrap = wrap; // JS object: net.Socket, JSStreamSocket, ... | |
| 626 | 658 | res._secureContext = context; | |
@@ -637,7 +669,7 @@ TLSSocket.prototype[kReinitializeHandle] = function reinitializeHandle(handle) { | |||
| 637 | 669 | const originalServername = this.ssl ? this._handle.getServername() : null; | |
| 638 | 670 | const originalSession = this.ssl ? this._handle.getSession() : null; | |
| 639 | 671 | ||
| 640 | - this.handle = this._wrapHandle(null, handle); | ||
| 672 | + this.handle = this._wrapHandle(null, handle, false); | ||
| 641 | 673 | this.ssl = this._handle; | |
| 642 | 674 | ||
| 643 | 675 | net.Socket.prototype[kReinitializeHandle].call(this, this.handle); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -319,12 +319,15 @@ TLSWrap::TLSWrap(Environment* env, | |||
| 319 | 319 | Local<Object> obj, | |
| 320 | 320 | Kind kind, | |
| 321 | 321 | StreamBase* stream, | |
| 322 | - SecureContext* sc) | ||
| 322 | + SecureContext* sc, | ||
| 323 | + UnderlyingStreamWriteStatus under_stream_ws) | ||
| 323 | 324 | : AsyncWrap(env, obj, AsyncWrap::PROVIDER_TLSWRAP), | |
| 324 | 325 | StreamBase(env), | |
| 325 | 326 | env_(env), | |
| 326 | 327 | kind_(kind), | |
| 327 | - sc_(sc) { | ||
| 328 | + sc_(sc), | ||
| 329 | + has_active_write_issued_by_prev_listener_( | ||
| 330 | + under_stream_ws == UnderlyingStreamWriteStatus::kHasActive) { | ||
| 328 | 331 | MakeWeak(); | |
| 329 | 332 | CHECK(sc_); | |
| 330 | 333 | ssl_ = sc_->CreateSSL(); | |
@@ -434,14 +437,19 @@ void TLSWrap::InitSSL() { | |||
| 434 | 437 | void TLSWrap::Wrap(const FunctionCallbackInfo<Value>& args) { | |
| 435 | 438 | Environment* env = Environment::GetCurrent(args); | |
| 436 | 439 | ||
| 437 | - CHECK_EQ(args.Length(), 3); | ||
| 440 | + CHECK_EQ(args.Length(), 4); | ||
| 438 | 441 | CHECK(args[0]->IsObject()); | |
| 439 | 442 | CHECK(args[1]->IsObject()); | |
| 440 | 443 | CHECK(args[2]->IsBoolean()); | |
| 444 | + CHECK(args[3]->IsBoolean()); | ||
| 441 | 445 | ||
| 442 | 446 | Local<Object> sc = args[1].As<Object>(); | |
| 443 | 447 | Kind kind = args[2]->IsTrue() ? Kind::kServer : Kind::kClient; | |
| 444 | 448 | ||
| 449 | + UnderlyingStreamWriteStatus under_stream_ws = | ||
| 450 | + args[3]->IsTrue() ? UnderlyingStreamWriteStatus::kHasActive | ||
| 451 | + : UnderlyingStreamWriteStatus::kVacancy; | ||
| 452 | + | ||
| 445 | 453 | StreamBase* stream = StreamBase::FromObject(args[0].As<Object>()); | |
| 446 | 454 | CHECK_NOT_NULL(stream); | |
| 447 | 455 | ||
@@ -452,7 +460,8 @@ void TLSWrap::Wrap(const FunctionCallbackInfo<Value>& args) { | |||
| 452 | 460 | return; | |
| 453 | 461 | } | |
| 454 | 462 | ||
| 455 | - TLSWrap* res = new TLSWrap(env, obj, kind, stream, Unwrap<SecureContext>(sc)); | ||
| 463 | + TLSWrap* res = new TLSWrap( | ||
| 464 | + env, obj, kind, stream, Unwrap<SecureContext>(sc), under_stream_ws); | ||
| 456 | 465 | ||
| 457 | 466 | args.GetReturnValue().Set(res->object()); | |
| 458 | 467 | } | |
@@ -558,6 +567,13 @@ void TLSWrap::EncOut() { | |||
| 558 | 567 | return; | |
| 559 | 568 | } | |
| 560 | 569 | ||
| 570 | + if (UNLIKELY(has_active_write_issued_by_prev_listener_)) { | ||
| 571 | + Debug(this, | ||
| 572 | + "Returning from EncOut(), " | ||
| 573 | + "has_active_write_issued_by_prev_listener_ is true"); | ||
| 574 | + return; | ||
| 575 | + } | ||
| 576 | + | ||
| 561 | 577 | // Split-off queue | |
| 562 | 578 | if (established_ && current_write_) { | |
| 563 | 579 | Debug(this, "EncOut() write is scheduled"); | |
@@ -628,6 +644,15 @@ void TLSWrap::EncOut() { | |||
| 628 | 644 | ||
| 629 | 645 | void TLSWrap::OnStreamAfterWrite(WriteWrap* req_wrap, int status) { | |
| 630 | 646 | Debug(this, "OnStreamAfterWrite(status = %d)", status); | |
| 647 | + | ||
| 648 | + if (UNLIKELY(has_active_write_issued_by_prev_listener_)) { | ||
| 649 | + Debug(this, "Notify write finish to the previous_listener_"); | ||
| 650 | + CHECK_EQ(write_size_, 0); // we must have restrained writes | ||
| 651 | + | ||
| 652 | + previous_listener_->OnStreamAfterWrite(req_wrap, status); | ||
| 653 | + return; | ||
| 654 | + } | ||
| 655 | + | ||
| 631 | 656 | if (current_empty_write_) { | |
| 632 | 657 | Debug(this, "Had empty write"); | |
| 633 | 658 | BaseObjectPtr<AsyncWrap> current_empty_write = | |
@@ -1974,6 +1999,16 @@ void TLSWrap::GetALPNNegotiatedProto(const FunctionCallbackInfo<Value>& args) { | |||
| 1974 | 1999 | args.GetReturnValue().Set(result); | |
| 1975 | 2000 | } | |
| 1976 | 2001 | ||
| 2002 | + void TLSWrap::WritesIssuedByPrevListenerDone( | ||
| 2003 | + const FunctionCallbackInfo<Value>& args) { | ||
| 2004 | + TLSWrap* w; | ||
| 2005 | + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); | ||
| 2006 | + | ||
| 2007 | + Debug(w, "WritesIssuedByPrevListenerDone is called"); | ||
| 2008 | + w->has_active_write_issued_by_prev_listener_ = false; | ||
| 2009 | + w->EncOut(); // resume all of our restrained writes | ||
| 2010 | + } | ||
| 2011 | + | ||
| 1977 | 2012 | void TLSWrap::Cycle() { | |
| 1978 | 2013 | // Prevent recursion | |
| 1979 | 2014 | if (++cycle_depth_ > 1) | |
@@ -2050,6 +2085,10 @@ void TLSWrap::Initialize( | |||
| 2050 | 2085 | SetProtoMethod(isolate, t, "setSession", SetSession); | |
| 2051 | 2086 | SetProtoMethod(isolate, t, "setVerifyMode", SetVerifyMode); | |
| 2052 | 2087 | SetProtoMethod(isolate, t, "start", Start); | |
| 2088 | + SetProtoMethod(isolate, | ||
| 2089 | + t, | ||
| 2090 | + "writesIssuedByPrevListenerDone", | ||
| 2091 | + WritesIssuedByPrevListenerDone); | ||
| 2053 | 2092 | ||
| 2054 | 2093 | SetProtoMethodNoSideEffect( | |
| 2055 | 2094 | isolate, t, "exportKeyingMaterial", ExportKeyingMaterial); | |
@@ -2131,6 +2170,7 @@ void TLSWrap::RegisterExternalReferences(ExternalReferenceRegistry* registry) { | |||
| 2131 | 2170 | registry->Register(GetSharedSigalgs); | |
| 2132 | 2171 | registry->Register(GetTLSTicket); | |
| 2133 | 2172 | registry->Register(VerifyError); | |
| 2173 | + registry->Register(WritesIssuedByPrevListenerDone); | ||
| 2134 | 2174 | ||
| 2135 | 2175 | #ifdef SSL_set_max_send_fragment | |
| 2136 | 2176 | 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(); | |
@@ -216,6 +219,8 @@ class TLSWrap : public AsyncWrap, | |||
| 216 | 219 | static void Start(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 217 | 220 | static void VerifyError(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 218 | 221 | static void Wrap(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 222 | + static void WritesIssuedByPrevListenerDone( | ||
| 223 | + const v8::FunctionCallbackInfo<v8::Value>& args); | ||
| 219 | 224 | ||
| 220 | 225 | #ifdef SSL_set_max_send_fragment | |
| 221 | 226 | static void SetMaxSendFragment( | |
@@ -283,6 +288,8 @@ class TLSWrap : public AsyncWrap, | |||
| 283 | 288 | ||
| 284 | 289 | BIOPointer bio_trace_; | |
| 285 | 290 | ||
| 291 | + bool has_active_write_issued_by_prev_listener_ = false; | ||
| 292 | + | ||
| 286 | 293 | public: | |
| 287 | 294 | std::vector<unsigned char> alpn_protos_; // Accessed by SelectALPNCallback. | |
| 288 | 295 | }; | |
| 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