Skip to content

Commit 09eb588

Browse files
santigimenoaddaleax
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. PR-URL: #13235 Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Colin Ihrig <[email protected]>
1 parent 820d97d commit 09eb588

File tree

1 file changed

+44
-16
lines changed

1 file changed

+44
-16
lines changed

lib/internal/child_process.js

+44-16
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ const errnoException = util._errnoException;
2323
const SocketListSend = SocketList.SocketListSend;
2424
const SocketListReceive = SocketList.SocketListReceive;
2525

26+
const MAX_HANDLE_RETRANSMISSIONS = 3;
27+
2628
// this object contain function to convert TCP objects to native handle objects
2729
// and back again.
2830
const handleConversion = {
@@ -88,17 +90,18 @@ const handleConversion = {
8890
return handle;
8991
},
9092

91-
postSend: function(handle, options, target) {
93+
postSend: function(message, handle, options, callback, target) {
9294
// Store the handle after successfully sending it, so it can be closed
9395
// when the NODE_HANDLE_ACK is received. If the handle could not be sent,
9496
// just close it.
9597
if (handle && !options.keepOpen) {
9698
if (target) {
97-
// There can only be one _pendingHandle as passing handles are
99+
// There can only be one _pendingMessage as passing handles are
98100
// processed one at a time: handles are stored in _handleQueue while
99101
// waiting for the NODE_HANDLE_ACK of the current passing handle.
100-
assert(!target._pendingHandle);
101-
target._pendingHandle = handle;
102+
assert(!target._pendingMessage);
103+
target._pendingMessage =
104+
{ callback, message, handle, options, retransmissions: 0 };
102105
} else {
103106
handle.close();
104107
}
@@ -249,6 +252,11 @@ function getHandleWrapType(stream) {
249252
return false;
250253
}
251254

255+
function closePendingHandle(target) {
256+
target._pendingMessage.handle.close();
257+
target._pendingMessage = null;
258+
}
259+
252260

253261
ChildProcess.prototype.spawn = function(options) {
254262
var ipc;
@@ -434,7 +442,7 @@ function setupChannel(target, channel) {
434442
});
435443

436444
target._handleQueue = null;
437-
target._pendingHandle = null;
445+
target._pendingMessage = null;
438446

439447
const control = new Control(channel);
440448

@@ -490,16 +498,31 @@ function setupChannel(target, channel) {
490498
// handlers will go through this
491499
target.on('internalMessage', function(message, handle) {
492500
// Once acknowledged - continue sending handles.
493-
if (message.cmd === 'NODE_HANDLE_ACK') {
494-
if (target._pendingHandle) {
495-
target._pendingHandle.close();
496-
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+
}
497513
}
498514

499515
assert(Array.isArray(target._handleQueue));
500516
var queue = target._handleQueue;
501517
target._handleQueue = null;
502518

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

515538
if (message.cmd !== 'NODE_HANDLE') return;
516539

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+
517546
// Acknowledge handle receival. Don't emit error events (for example if
518547
// the other side has disconnected) because this call to send() is not
519548
// initiated by the user and it shouldn't be fatal to be unable to ACK
@@ -624,7 +653,8 @@ function setupChannel(target, channel) {
624653
net._setSimultaneousAccepts(handle);
625654
}
626655
} else if (this._handleQueue &&
627-
!(message && message.cmd === 'NODE_HANDLE_ACK')) {
656+
!(message && (message.cmd === 'NODE_HANDLE_ACK' ||
657+
message.cmd === 'NODE_HANDLE_NACK'))) {
628658
// Queue request anyway to avoid out-of-order messages.
629659
this._handleQueue.push({
630660
callback: callback,
@@ -646,7 +676,7 @@ function setupChannel(target, channel) {
646676
if (!this._handleQueue)
647677
this._handleQueue = [];
648678
if (obj && obj.postSend)
649-
obj.postSend(handle, options, target);
679+
obj.postSend(message, handle, options, callback, target);
650680
}
651681

652682
if (req.async) {
@@ -662,7 +692,7 @@ function setupChannel(target, channel) {
662692
} else {
663693
// Cleanup handle on error
664694
if (obj && obj.postSend)
665-
obj.postSend(handle, options);
695+
obj.postSend(message, handle, options, callback);
666696

667697
if (!options.swallowErrors) {
668698
const ex = errnoException(err, 'write');
@@ -711,10 +741,8 @@ function setupChannel(target, channel) {
711741
// This marks the fact that the channel is actually disconnected.
712742
this.channel = null;
713743

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

719747
var fired = false;
720748
function finish() {

0 commit comments

Comments
 (0)