Skip to content

Commit d75fdd9

Browse files
committed
child_process: improve killSignal validations
As it is, the `killSignal` is just retrieved from an object and used. If the signal passed is actually one of the inherited properties of that object, Node.js will die. For example, ➜ node -e "child_process.spawnSync('ls', {killSignal: 'toString'})" Assertion failed: (0), function uv_close, file ....core.c, line 166. [1] 58938 abort node -e "child_process.spawnSync(...)" 1. This patch makes sure that the signal is actually a own property of the constants object. 2. Extends the killSignal validation to all the other functions. PR-URL: #10423 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent a5f91ab commit d75fdd9

4 files changed

Lines changed: 54 additions & 40 deletions

File tree

‎lib/child_process.js‎

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@
2222
'use strict';
2323

2424
constutil=require('util');
25-
constinternalUtil=require('internal/util');
25+
const{ deprecate, convertToValidSignal }=require('internal/util');
2626
constdebug=util.debuglog('child_process');
27-
constconstants=process.binding('constants').os.signals;
2827

2928
constuv=process.binding('uv');
3029
constspawn_sync=process.binding('spawn_sync');
@@ -181,6 +180,8 @@ exports.execFile = function(file /*, args, options, callback*/) {
181180
// Validate maxBuffer, if present.
182181
validateMaxBuffer(options.maxBuffer);
183182

183+
options.killSignal=sanitizeKillSignal(options.killSignal);
184+
184185
varchild=spawn(file,args,{
185186
cwd: options.cwd,
186187
env: options.env,
@@ -332,7 +333,7 @@ exports.execFile = function(file /*, args, options, callback*/) {
332333
returnchild;
333334
};
334335

335-
const_deprecatedCustomFds=internalUtil.deprecate(
336+
const_deprecatedCustomFds=deprecate(
336337
functiondeprecateCustomFds(options){
337338
options.stdio=options.customFds.map(functionmapCustomFds(fd){
338339
returnfd===-1 ? 'pipe' : fd;
@@ -474,18 +475,6 @@ var spawn = exports.spawn = function(/*file, args, options*/) {
474475
returnchild;
475476
};
476477

477-
478-
functionlookupSignal(signal){
479-
if(typeofsignal==='number')
480-
returnsignal;
481-
482-
if(!(signalinconstants))
483-
thrownewError('Unknown signal: '+signal);
484-
485-
returnconstants[signal];
486-
}
487-
488-
489478
functionspawnSync(/*file, args, options*/){
490479
varopts=normalizeSpawnArguments.apply(null,arguments);
491480

@@ -506,7 +495,7 @@ function spawnSync(/*file, args, options*/) {
506495
options.envPairs=opts.envPairs;
507496

508497
// Validate and translate the kill signal, if present.
509-
options.killSignal=validateKillSignal(options.killSignal);
498+
options.killSignal=sanitizeKillSignal(options.killSignal);
510499

511500
options.stdio=_validateStdio(options.stdio||'pipe',true).stdio;
512501

@@ -632,15 +621,10 @@ function validateMaxBuffer(maxBuffer) {
632621
}
633622

634623

635-
functionvalidateKillSignal(killSignal){
624+
functionsanitizeKillSignal(killSignal){
636625
if(typeofkillSignal==='string'||typeofkillSignal==='number'){
637-
killSignal=lookupSignal(killSignal);
638-
639-
if(killSignal===0)
640-
thrownewRangeError('"killSignal" cannot be 0');
626+
returnconvertToValidSignal(killSignal);
641627
}elseif(killSignal!=null){
642628
thrownewTypeError('"killSignal" must be a string or number');
643629
}
644-
645-
returnkillSignal;
646630
}

‎lib/internal/child_process.js‎

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ const EventEmitter = require('events');
55
constnet=require('net');
66
constdgram=require('dgram');
77
constutil=require('util');
8-
constconstants=process.binding('constants').os.signals;
98
constassert=require('assert');
109

1110
constProcess=process.binding('process_wrap').Process;
@@ -17,6 +16,7 @@ const TCP = process.binding('tcp_wrap').TCP;
1716
constUDP=process.binding('udp_wrap').UDP;
1817
constSocketList=require('internal/socket_list');
1918
const{ isUint8Array }=process.binding('util');
19+
const{ convertToValidSignal }=require('internal/util');
2020

2121
consterrnoException=util._errnoException;
2222
constSocketListSend=SocketList.SocketListSend;
@@ -362,19 +362,8 @@ function onErrorNT(self, err) {
362362

363363

364364
ChildProcess.prototype.kill=function(sig){
365-
varsignal;
366-
367-
if(sig===0){
368-
signal=0;
369-
}elseif(!sig){
370-
signal=constants['SIGTERM'];
371-
}else{
372-
signal=constants[sig];
373-
}
374-
375-
if(signal===undefined){
376-
thrownewError('Unknown signal: '+sig);
377-
}
365+
constsignal=sig===0 ? sig :
366+
convertToValidSignal(sig===undefined ? 'SIGTERM' : sig);
378367

379368
if(this._handle){
380369
varerr=this._handle.kill(signal);

‎lib/internal/util.js‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
constbinding=process.binding('util');
4+
constsignals=process.binding('constants').os.signals;
45

56
constkArrowMessagePrivateSymbolIndex=binding['arrow_message_private_symbol'];
67
constkDecoratedPrivateSymbolIndex=binding['decorated_private_symbol'];
@@ -179,3 +180,28 @@ exports.createClassWrapper = function createClassWrapper(type) {
179180
fn.prototype=type.prototype;
180181
returnfn;
181182
};
183+
184+
letsignalsToNamesMapping;
185+
functiongetSignalsToNamesMapping(){
186+
if(signalsToNamesMapping!==undefined)
187+
returnsignalsToNamesMapping;
188+
189+
signalsToNamesMapping=Object.create(null);
190+
for(constkeyinsignals){
191+
signalsToNamesMapping[signals[key]]=key;
192+
}
193+
194+
returnsignalsToNamesMapping;
195+
}
196+
197+
exports.convertToValidSignal=functionconvertToValidSignal(signal){
198+
if(typeofsignal==='number'&&getSignalsToNamesMapping()[signal])
199+
returnsignal;
200+
201+
if(typeofsignal==='string'){
202+
constsignalName=signals[signal.toUpperCase()];
203+
if(signalName)returnsignalName;
204+
}
205+
206+
thrownewError('Unknown signal: '+signal);
207+
};

‎test/parallel/test-child-process-spawnsync-validation-errors.js‎

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
constcommon=require('../common');
33
constassert=require('assert');
44
constspawnSync=require('child_process').spawnSync;
5+
constsignals=process.binding('constants').os.signals;
56

67
functionpass(option,value){
78
// Run the command with the specified option. Since it's not a real command,
@@ -184,18 +185,32 @@ if (!common.isWindows) {
184185
{
185186
// Validate the killSignal option
186187
consttypeErr=/^TypeError:"killSignal"mustbeastringornumber$/;
187-
constrangeErr=/^RangeError:"killSignal"cannotbe0$/;
188188
constunknownSignalErr=/^Error:Unknownsignal:/;
189189

190190
pass('killSignal',undefined);
191191
pass('killSignal',null);
192192
pass('killSignal','SIGKILL');
193-
pass('killSignal',500);
194-
fail('killSignal',0,rangeErr);
195193
fail('killSignal','SIGNOTAVALIDSIGNALNAME',unknownSignalErr);
196194
fail('killSignal',true,typeErr);
197195
fail('killSignal',false,typeErr);
198196
fail('killSignal',[],typeErr);
199197
fail('killSignal',{},typeErr);
200198
fail('killSignal',common.noop,typeErr);
199+
200+
// Invalid signal names and numbers should fail
201+
fail('killSignal',500,unknownSignalErr);
202+
fail('killSignal',0,unknownSignalErr);
203+
fail('killSignal',-200,unknownSignalErr);
204+
fail('killSignal',3.14,unknownSignalErr);
205+
206+
Object.getOwnPropertyNames(Object.prototype).forEach((property)=>{
207+
fail('killSignal',property,unknownSignalErr);
208+
});
209+
210+
// Valid signal names and numbers should pass
211+
for(constsignalNameinsignals){
212+
pass('killSignal',signals[signalName]);
213+
pass('killSignal',signalName);
214+
pass('killSignal',signalName.toLowerCase());
215+
}
201216
}

0 commit comments

Comments
 (0)