Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit b2e6048

Browse filesBrowse files
RaisinTenRafaelGSS
authored andcommitted
child_process: validate arguments for null bytes
This change adds validation to reject an edge case where the child_process API argument strings might contain null bytes somewhere in between. Such strings were being silently truncated before, so throwing an error should prevent misuses of this API. Fixes: #44768 Signed-off-by: Darshan Sen <raisinten@gmail.com> PR-URL: #44782 Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 07bf389 commit b2e6048
Copy full SHA for b2e6048

File tree

Expand file treeCollapse file tree

2 files changed

+322
-0
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

2 files changed

+322
-0
lines changed
Open diff view settings
Collapse file

‎lib/child_process.js‎

Copy file name to clipboardExpand all lines: lib/child_process.js
+28Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const {
3939
ObjectPrototypeHasOwnProperty,
4040
RegExpPrototypeExec,
4141
SafeSet,
42+
StringPrototypeIncludes,
4243
StringPrototypeSlice,
4344
StringPrototypeToUpperCase,
4445
} = primordials;
@@ -137,9 +138,11 @@ function fork(modulePath, args = [], options) {
137138
}
138139
options = { ...options, shell: false };
139140
options.execPath = options.execPath || process.execPath;
141+
validateArgumentNullCheck(options.execPath, 'options.execPath');
140142

141143
// Prepare arguments for fork:
142144
execArgv = options.execArgv || process.execArgv;
145+
validateArgumentsNullCheck(execArgv, 'options.execArgv');
143146

144147
if (execArgv === process.execArgv && process._eval != null) {
145148
const index = ArrayPrototypeLastIndexOf(execArgv, process._eval);
@@ -182,6 +185,9 @@ function _forkChild(fd, serializationMode) {
182185
}
183186

184187
function normalizeExecArgs(command, options, callback) {
188+
validateString(command, 'command');
189+
validateArgumentNullCheck(command, 'command');
190+
185191
if (typeof options === 'function') {
186192
callback = options;
187193
options = undefined;
@@ -286,6 +292,7 @@ function normalizeExecFileArgs(file, args, options, callback) {
286292
// Validate argv0, if present.
287293
if (options.argv0 != null) {
288294
validateString(options.argv0, 'options.argv0');
295+
validateArgumentNullCheck(options.argv0, 'options.argv0');
289296
}
290297

291298
return { file, args, options, callback };
@@ -536,6 +543,7 @@ function copyProcessEnvToEnv(env, name, optionEnv) {
536543

537544
function normalizeSpawnArguments(file, args, options) {
538545
validateString(file, 'file');
546+
validateArgumentNullCheck(file, 'file');
539547

540548
if (file.length === 0)
541549
throw new ERR_INVALID_ARG_VALUE('file', file, 'cannot be empty');
@@ -551,6 +559,8 @@ function normalizeSpawnArguments(file, args, options) {
551559
args = [];
552560
}
553561

562+
validateArgumentsNullCheck(args, 'args');
563+
554564
if (options === undefined)
555565
options = kEmptyObject;
556566
else
@@ -589,6 +599,7 @@ function normalizeSpawnArguments(file, args, options) {
589599
// Validate argv0, if present.
590600
if (options.argv0 != null) {
591601
validateString(options.argv0, 'options.argv0');
602+
validateArgumentNullCheck(options.argv0, 'options.argv0');
592603
}
593604

594605
// Validate windowsHide, if present.
@@ -604,6 +615,7 @@ function normalizeSpawnArguments(file, args, options) {
604615
}
605616

606617
if (options.shell) {
618+
validateArgumentNullCheck(options.shell, 'options.shell');
607619
const command = ArrayPrototypeJoin([file, ...args], ' ');
608620
// Set the shell, switches, and commands.
609621
if (process.platform === 'win32') {
@@ -681,6 +693,8 @@ function normalizeSpawnArguments(file, args, options) {
681693
for (const key of envKeys) {
682694
const value = env[key];
683695
if (value !== undefined) {
696+
validateArgumentNullCheck(key, `options.env['${key}']`);
697+
validateArgumentNullCheck(value, `options.env['${key}']`);
684698
ArrayPrototypePush(envPairs, `${key}=${value}`);
685699
}
686700
}
@@ -949,6 +963,20 @@ function execSync(command, options) {
949963
}
950964

951965

966+
function validateArgumentNullCheck(arg, propName) {
967+
if (typeof arg === 'string' && StringPrototypeIncludes(arg, '\u0000')) {
968+
throw new ERR_INVALID_ARG_VALUE(propName, arg, 'must be a string without null bytes');
969+
}
970+
}
971+
972+
973+
function validateArgumentsNullCheck(args, propName) {
974+
for (let i = 0; i < args.length; ++i) {
975+
validateArgumentNullCheck(args[i], `${propName}[${i}]`);
976+
}
977+
}
978+
979+
952980
function validateTimeout(timeout) {
953981
if (timeout != null && !(NumberIsInteger(timeout) && timeout >= 0)) {
954982
throw new ERR_OUT_OF_RANGE('timeout', 'an unsigned integer', timeout);
Collapse file
+294Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
'use strict';
2+
const { mustNotCall } = require('../common');
3+
4+
// Regression test for https://github.com/nodejs/node/issues/44768
5+
6+
const { throws } = require('assert');
7+
const {
8+
exec,
9+
execFile,
10+
execFileSync,
11+
execSync,
12+
fork,
13+
spawn,
14+
spawnSync,
15+
} = require('child_process');
16+
17+
// Tests for the 'command' argument
18+
19+
throws(() => exec(`${process.execPath} ${__filename} AAA BBB\0XXX CCC`, mustNotCall()), {
20+
code: 'ERR_INVALID_ARG_VALUE',
21+
message: /The argument 'command' must be a string without null bytes/
22+
});
23+
24+
throws(() => exec('BBB\0XXX AAA CCC', mustNotCall()), {
25+
code: 'ERR_INVALID_ARG_VALUE',
26+
message: /The argument 'command' must be a string without null bytes/
27+
});
28+
29+
throws(() => execSync(`${process.execPath} ${__filename} AAA BBB\0XXX CCC`), {
30+
code: 'ERR_INVALID_ARG_VALUE',
31+
message: /The argument 'command' must be a string without null bytes/
32+
});
33+
34+
throws(() => execSync('BBB\0XXX AAA CCC'), {
35+
code: 'ERR_INVALID_ARG_VALUE',
36+
message: /The argument 'command' must be a string without null bytes/
37+
});
38+
39+
// Tests for the 'file' argument
40+
41+
throws(() => spawn('BBB\0XXX'), {
42+
code: 'ERR_INVALID_ARG_VALUE',
43+
message: /The argument 'file' must be a string without null bytes/
44+
});
45+
46+
throws(() => execFile('BBB\0XXX', mustNotCall()), {
47+
code: 'ERR_INVALID_ARG_VALUE',
48+
message: /The argument 'file' must be a string without null bytes/
49+
});
50+
51+
throws(() => execFileSync('BBB\0XXX'), {
52+
code: 'ERR_INVALID_ARG_VALUE',
53+
message: /The argument 'file' must be a string without null bytes/
54+
});
55+
56+
throws(() => spawn('BBB\0XXX'), {
57+
code: 'ERR_INVALID_ARG_VALUE',
58+
message: /The argument 'file' must be a string without null bytes/
59+
});
60+
61+
throws(() => spawnSync('BBB\0XXX'), {
62+
code: 'ERR_INVALID_ARG_VALUE',
63+
message: /The argument 'file' must be a string without null bytes/
64+
});
65+
66+
// Tests for the 'modulePath' argument
67+
68+
throws(() => fork('BBB\0XXX'), {
69+
code: 'ERR_INVALID_ARG_VALUE',
70+
message: /The argument 'modulePath' must be a string or Uint8Array without null bytes/
71+
});
72+
73+
// Tests for the 'args' argument
74+
75+
// Not testing exec() and execSync() because these accept 'args' as a part of
76+
// 'command' as space-separated arguments.
77+
78+
throws(() => execFile(process.execPath, [__filename, 'AAA', 'BBB\0XXX', 'CCC'], mustNotCall()), {
79+
code: 'ERR_INVALID_ARG_VALUE',
80+
message: /The argument 'args\[2\]' must be a string without null bytes/
81+
});
82+
83+
throws(() => execFileSync(process.execPath, [__filename, 'AAA', 'BBB\0XXX', 'CCC']), {
84+
code: 'ERR_INVALID_ARG_VALUE',
85+
message: /The argument 'args\[2\]' must be a string without null bytes/
86+
});
87+
88+
throws(() => fork(__filename, ['AAA', 'BBB\0XXX', 'CCC']), {
89+
code: 'ERR_INVALID_ARG_VALUE',
90+
message: /The argument 'args\[2\]' must be a string without null bytes/
91+
});
92+
93+
throws(() => spawn(process.execPath, [__filename, 'AAA', 'BBB\0XXX', 'CCC']), {
94+
code: 'ERR_INVALID_ARG_VALUE',
95+
message: /The argument 'args\[2\]' must be a string without null bytes/
96+
});
97+
98+
throws(() => spawnSync(process.execPath, [__filename, 'AAA', 'BBB\0XXX', 'CCC']), {
99+
code: 'ERR_INVALID_ARG_VALUE',
100+
message: /The argument 'args\[2\]' must be a string without null bytes/
101+
});
102+
103+
// Tests for the 'options.cwd' argument
104+
105+
throws(() => exec(process.execPath, { cwd: 'BBB\0XXX' }, mustNotCall()), {
106+
code: 'ERR_INVALID_ARG_VALUE',
107+
message: /The property 'options\.cwd' must be a string or Uint8Array without null bytes/
108+
});
109+
110+
throws(() => execFile(process.execPath, { cwd: 'BBB\0XXX' }, mustNotCall()), {
111+
code: 'ERR_INVALID_ARG_VALUE',
112+
message: /The property 'options\.cwd' must be a string or Uint8Array without null bytes/
113+
});
114+
115+
throws(() => execFileSync(process.execPath, { cwd: 'BBB\0XXX' }), {
116+
code: 'ERR_INVALID_ARG_VALUE',
117+
message: /The property 'options\.cwd' must be a string or Uint8Array without null bytes/
118+
});
119+
120+
throws(() => execSync(process.execPath, { cwd: 'BBB\0XXX' }), {
121+
code: 'ERR_INVALID_ARG_VALUE',
122+
message: /The property 'options\.cwd' must be a string or Uint8Array without null bytes/
123+
});
124+
125+
throws(() => fork(__filename, { cwd: 'BBB\0XXX' }), {
126+
code: 'ERR_INVALID_ARG_VALUE',
127+
message: /The property 'options\.cwd' must be a string or Uint8Array without null bytes/
128+
});
129+
130+
throws(() => spawn(process.execPath, { cwd: 'BBB\0XXX' }), {
131+
code: 'ERR_INVALID_ARG_VALUE',
132+
message: /The property 'options\.cwd' must be a string or Uint8Array without null bytes/
133+
});
134+
135+
throws(() => spawnSync(process.execPath, { cwd: 'BBB\0XXX' }), {
136+
code: 'ERR_INVALID_ARG_VALUE',
137+
message: /The property 'options\.cwd' must be a string or Uint8Array without null bytes/
138+
});
139+
140+
// Tests for the 'options.argv0' argument
141+
142+
throws(() => exec(process.execPath, { argv0: 'BBB\0XXX' }, mustNotCall()), {
143+
code: 'ERR_INVALID_ARG_VALUE',
144+
message: /The property 'options\.argv0' must be a string without null bytes/
145+
});
146+
147+
throws(() => execFile(process.execPath, { argv0: 'BBB\0XXX' }, mustNotCall()), {
148+
code: 'ERR_INVALID_ARG_VALUE',
149+
message: /The property 'options\.argv0' must be a string without null bytes/
150+
});
151+
152+
throws(() => execFileSync(process.execPath, { argv0: 'BBB\0XXX' }), {
153+
code: 'ERR_INVALID_ARG_VALUE',
154+
message: /The property 'options\.argv0' must be a string without null bytes/
155+
});
156+
157+
throws(() => execSync(process.execPath, { argv0: 'BBB\0XXX' }), {
158+
code: 'ERR_INVALID_ARG_VALUE',
159+
message: /The property 'options\.argv0' must be a string without null bytes/
160+
});
161+
162+
throws(() => fork(__filename, { argv0: 'BBB\0XXX' }), {
163+
code: 'ERR_INVALID_ARG_VALUE',
164+
message: /The property 'options\.argv0' must be a string without null bytes/
165+
});
166+
167+
throws(() => spawn(process.execPath, { argv0: 'BBB\0XXX' }), {
168+
code: 'ERR_INVALID_ARG_VALUE',
169+
message: /The property 'options\.argv0' must be a string without null bytes/
170+
});
171+
172+
throws(() => spawnSync(process.execPath, { argv0: 'BBB\0XXX' }), {
173+
code: 'ERR_INVALID_ARG_VALUE',
174+
message: /The property 'options\.argv0' must be a string without null bytes/
175+
});
176+
177+
// Tests for the 'options.shell' argument
178+
179+
throws(() => exec(process.execPath, { shell: 'BBB\0XXX' }, mustNotCall()), {
180+
code: 'ERR_INVALID_ARG_VALUE',
181+
message: /The property 'options\.shell' must be a string without null bytes/
182+
});
183+
184+
throws(() => execFile(process.execPath, { shell: 'BBB\0XXX' }, mustNotCall()), {
185+
code: 'ERR_INVALID_ARG_VALUE',
186+
message: /The property 'options\.shell' must be a string without null bytes/
187+
});
188+
189+
throws(() => execFileSync(process.execPath, { shell: 'BBB\0XXX' }), {
190+
code: 'ERR_INVALID_ARG_VALUE',
191+
message: /The property 'options\.shell' must be a string without null bytes/
192+
});
193+
194+
throws(() => execSync(process.execPath, { shell: 'BBB\0XXX' }), {
195+
code: 'ERR_INVALID_ARG_VALUE',
196+
message: /The property 'options\.shell' must be a string without null bytes/
197+
});
198+
199+
// Not testing fork() because it doesn't accept the shell option (internally it
200+
// explicitly sets shell to false).
201+
202+
throws(() => spawn(process.execPath, { shell: 'BBB\0XXX' }), {
203+
code: 'ERR_INVALID_ARG_VALUE',
204+
message: /The property 'options\.shell' must be a string without null bytes/
205+
});
206+
207+
throws(() => spawnSync(process.execPath, { shell: 'BBB\0XXX' }), {
208+
code: 'ERR_INVALID_ARG_VALUE',
209+
message: /The property 'options\.shell' must be a string without null bytes/
210+
});
211+
212+
// Tests for the 'options.env' argument
213+
214+
throws(() => exec(process.execPath, { env: { 'AAA': 'BBB\0XXX' } }, mustNotCall()), {
215+
code: 'ERR_INVALID_ARG_VALUE',
216+
message: /The property 'options\.env\['AAA'\]' must be a string without null bytes/
217+
});
218+
219+
throws(() => exec(process.execPath, { env: { 'BBB\0XXX': 'AAA' } }, mustNotCall()), {
220+
code: 'ERR_INVALID_ARG_VALUE',
221+
message: /The property 'options\.env\['BBB\0XXX'\]' must be a string without null bytes/
222+
});
223+
224+
throws(() => execFile(process.execPath, { env: { 'AAA': 'BBB\0XXX' } }, mustNotCall()), {
225+
code: 'ERR_INVALID_ARG_VALUE',
226+
message: /The property 'options\.env\['AAA'\]' must be a string without null bytes/
227+
});
228+
229+
throws(() => execFile(process.execPath, { env: { 'BBB\0XXX': 'AAA' } }, mustNotCall()), {
230+
code: 'ERR_INVALID_ARG_VALUE',
231+
message: /The property 'options\.env\['BBB\0XXX'\]' must be a string without null bytes/
232+
});
233+
234+
throws(() => execFileSync(process.execPath, { env: { 'AAA': 'BBB\0XXX' } }), {
235+
code: 'ERR_INVALID_ARG_VALUE',
236+
message: /The property 'options\.env\['AAA'\]' must be a string without null bytes/
237+
});
238+
239+
throws(() => execFileSync(process.execPath, { env: { 'BBB\0XXX': 'AAA' } }), {
240+
code: 'ERR_INVALID_ARG_VALUE',
241+
message: /The property 'options\.env\['BBB\0XXX'\]' must be a string without null bytes/
242+
});
243+
244+
throws(() => execSync(process.execPath, { env: { 'AAA': 'BBB\0XXX' } }), {
245+
code: 'ERR_INVALID_ARG_VALUE',
246+
message: /The property 'options\.env\['AAA'\]' must be a string without null bytes/
247+
});
248+
249+
throws(() => execSync(process.execPath, { env: { 'BBB\0XXX': 'AAA' } }), {
250+
code: 'ERR_INVALID_ARG_VALUE',
251+
message: /The property 'options\.env\['BBB\0XXX'\]' must be a string without null bytes/
252+
});
253+
254+
throws(() => fork(__filename, { env: { 'AAA': 'BBB\0XXX' } }), {
255+
code: 'ERR_INVALID_ARG_VALUE',
256+
message: /The property 'options\.env\['AAA'\]' must be a string without null bytes/
257+
});
258+
259+
throws(() => fork(__filename, { env: { 'BBB\0XXX': 'AAA' } }), {
260+
code: 'ERR_INVALID_ARG_VALUE',
261+
message: /The property 'options\.env\['BBB\0XXX'\]' must be a string without null bytes/
262+
});
263+
264+
throws(() => spawn(process.execPath, { env: { 'AAA': 'BBB\0XXX' } }), {
265+
code: 'ERR_INVALID_ARG_VALUE',
266+
message: /The property 'options\.env\['AAA'\]' must be a string without null bytes/
267+
});
268+
269+
throws(() => spawn(process.execPath, { env: { 'BBB\0XXX': 'AAA' } }), {
270+
code: 'ERR_INVALID_ARG_VALUE',
271+
message: /The property 'options\.env\['BBB\0XXX'\]' must be a string without null bytes/
272+
});
273+
274+
throws(() => spawnSync(process.execPath, { env: { 'AAA': 'BBB\0XXX' } }), {
275+
code: 'ERR_INVALID_ARG_VALUE',
276+
message: /The property 'options\.env\['AAA'\]' must be a string without null bytes/
277+
});
278+
279+
throws(() => spawnSync(process.execPath, { env: { 'BBB\0XXX': 'AAA' } }), {
280+
code: 'ERR_INVALID_ARG_VALUE',
281+
message: /The property 'options\.env\['BBB\0XXX'\]' must be a string without null bytes/
282+
});
283+
284+
// Tests for the 'options.execPath' argument
285+
throws(() => fork(__filename, { execPath: 'BBB\0XXX' }), {
286+
code: 'ERR_INVALID_ARG_VALUE',
287+
message: /The property 'options\.execPath' must be a string without null bytes/
288+
});
289+
290+
// Tests for the 'options.execArgv' argument
291+
throws(() => fork(__filename, { execArgv: ['AAA', 'BBB\0XXX', 'CCC'] }), {
292+
code: 'ERR_INVALID_ARG_VALUE',
293+
message: /The property 'options\.execArgv\[1\]' must be a string without null bytes/
294+
});

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.