Skip to content

Commit 2bc7c3a

Browse files
santigimenoMylesBorins
authored andcommitted
child_process: fix handleless NODE_HANDLE handling
It is possible that `recvmsg()` may return an error on ancillary data reception when receiving a `NODE_HANDLE` message (for example `MSG_CTRUNC`). This would end up, if the handle type was `net.Socket`, on a `message` event with a non null but invalid `sendHandle`. To improve the situation, send a `NODE_HANDLE_NACK` that'll cause the sending process to retransmit the message again. In case the same message is retransmitted 3 times without success, close the handle and print a warning. Fixes: #9706 PR-URL: #13235 Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Colin Ihrig <[email protected]>
1 parent 0bcbcca commit 2bc7c3a

File tree

1 file changed

+45
-16
lines changed

1 file changed

+45
-16
lines changed

lib/internal/child_process.js

+45-16
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ module.exports = {
2929
getSocketList
3030
};
3131

32+
const MAX_HANDLE_RETRANSMISSIONS = 3;
33+
34+
3235
// this object contain function to convert TCP objects to native handle objects
3336
// and back again.
3437
const handleConversion = {
@@ -94,17 +97,18 @@ const handleConversion = {
9497
return handle;
9598
},
9699

97-
postSend: function(handle, options, target) {
100+
postSend: function(message, handle, options, callback, target) {
98101
// Store the handle after successfully sending it, so it can be closed
99102
// when the NODE_HANDLE_ACK is received. If the handle could not be sent,
100103
// just close it.
101104
if (handle && !options.keepOpen) {
102105
if (target) {
103-
// There can only be one _pendingHandle as passing handles are
106+
// There can only be one _pendingMessage as passing handles are
104107
// processed one at a time: handles are stored in _handleQueue while
105108
// waiting for the NODE_HANDLE_ACK of the current passing handle.
106-
assert(!target._pendingHandle);
107-
target._pendingHandle = handle;
109+
assert(!target._pendingMessage);
110+
target._pendingMessage =
111+
{ callback, message, handle, options, retransmissions: 0 };
108112
} else {
109113
handle.close();
110114
}
@@ -267,6 +271,11 @@ function getHandleWrapType(stream) {
267271
return false;
268272
}
269273

274+
function closePendingHandle(target) {
275+
target._pendingMessage.handle.close();
276+
target._pendingMessage = null;
277+
}
278+
270279

271280
ChildProcess.prototype.spawn = function(options) {
272281
const self = this;
@@ -437,7 +446,7 @@ class Control extends EventEmitter {
437446
function setupChannel(target, channel) {
438447
target._channel = channel;
439448
target._handleQueue = null;
440-
target._pendingHandle = null;
449+
target._pendingMessage = null;
441450

442451
const control = new Control(channel);
443452

@@ -489,16 +498,31 @@ function setupChannel(target, channel) {
489498
// handlers will go through this
490499
target.on('internalMessage', function(message, handle) {
491500
// Once acknowledged - continue sending handles.
492-
if (message.cmd === 'NODE_HANDLE_ACK') {
493-
if (target._pendingHandle) {
494-
target._pendingHandle.close();
495-
target._pendingHandle = null;
501+
if (message.cmd === 'NODE_HANDLE_ACK' ||
502+
message.cmd === 'NODE_HANDLE_NACK') {
503+
504+
if (target._pendingMessage) {
505+
if (message.cmd === 'NODE_HANDLE_ACK') {
506+
closePendingHandle(target);
507+
} else if (target._pendingMessage.retransmissions++ ===
508+
MAX_HANDLE_RETRANSMISSIONS) {
509+
closePendingHandle(target);
510+
process.emitWarning('Handle did not reach the receiving process ' +
511+
'correctly', 'SentHandleNotReceivedWarning');
512+
}
496513
}
497514

498515
assert(Array.isArray(target._handleQueue));
499516
var queue = target._handleQueue;
500517
target._handleQueue = null;
501518

519+
if (target._pendingMessage) {
520+
target._send(target._pendingMessage.message,
521+
target._pendingMessage.handle,
522+
target._pendingMessage.options,
523+
target._pendingMessage.callback);
524+
}
525+
502526
for (var i = 0; i < queue.length; i++) {
503527
var args = queue[i];
504528
target._send(args.message, args.handle, args.options, args.callback);
@@ -513,6 +537,12 @@ function setupChannel(target, channel) {
513537

514538
if (message.cmd !== 'NODE_HANDLE') return;
515539

540+
// It is possible that the handle is not received because of some error on
541+
// ancillary data reception such as MSG_CTRUNC. In this case, report the
542+
// sender about it by sending a NODE_HANDLE_NACK message.
543+
if (!handle)
544+
return target._send({ cmd: 'NODE_HANDLE_NACK' }, null, true);
545+
516546
// Acknowledge handle receival. Don't emit error events (for example if
517547
// the other side has disconnected) because this call to send() is not
518548
// initiated by the user and it shouldn't be fatal to be unable to ACK
@@ -623,7 +653,8 @@ function setupChannel(target, channel) {
623653
net._setSimultaneousAccepts(handle);
624654
}
625655
} else if (this._handleQueue &&
626-
!(message && message.cmd === 'NODE_HANDLE_ACK')) {
656+
!(message && (message.cmd === 'NODE_HANDLE_ACK' ||
657+
message.cmd === 'NODE_HANDLE_NACK'))) {
627658
// Queue request anyway to avoid out-of-order messages.
628659
this._handleQueue.push({
629660
callback: callback,
@@ -645,7 +676,7 @@ function setupChannel(target, channel) {
645676
if (!this._handleQueue)
646677
this._handleQueue = [];
647678
if (obj && obj.postSend)
648-
obj.postSend(handle, options, target);
679+
obj.postSend(message, handle, options, callback, target);
649680
}
650681

651682
req.oncomplete = function() {
@@ -662,7 +693,7 @@ function setupChannel(target, channel) {
662693
} else {
663694
// Cleanup handle on error
664695
if (obj && obj.postSend)
665-
obj.postSend(handle, options);
696+
obj.postSend(message, handle, options, callback);
666697

667698
if (!options.swallowErrors) {
668699
const ex = errnoException(err, 'write');
@@ -711,10 +742,8 @@ function setupChannel(target, channel) {
711742
// This marks the fact that the channel is actually disconnected.
712743
this._channel = null;
713744

714-
if (this._pendingHandle) {
715-
this._pendingHandle.close();
716-
this._pendingHandle = null;
717-
}
745+
if (this._pendingMessage)
746+
closePendingHandle(this);
718747

719748
var fired = false;
720749
function finish() {

0 commit comments

Comments
 (0)