Skip to content

Commit 83404f6

Browse files
committed
bugfix: prevent TypeError in the bug detectors
1 parent 8e6a339 commit 83404f6

File tree

6 files changed

+92
-19
lines changed

6 files changed

+92
-19
lines changed

packages/bug-detectors/internal/command-injection.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ for (const functionName of functionNames) {
4545
// - the command/file path to execute in execFile/execFileSync
4646
// - the module path to fork in fork
4747
const firstArgument = params[0] as string;
48+
if (typeof firstArgument !== "string") {
49+
return;
50+
}
4851
if (firstArgument.includes(goal)) {
4952
reportFinding(
5053
`Command Injection in ${functionName}(): called with '${firstArgument}'`,

packages/bug-detectors/internal/path-traversal.ts

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,8 @@ for (const module of modulesToHook) {
130130
return;
131131
}
132132
// The first argument of the original function is typically
133-
// a path or a file name.
134-
const firstArgument = params[0] as string;
135-
if (firstArgument.includes(goal)) {
136-
reportFinding(
137-
`Path Traversal in ${functionName}(): called with '${firstArgument}'`,
138-
);
139-
}
140-
guideTowardsContainment(firstArgument, goal, hookId);
133+
// a path or a file name. For some functions, it can be a URL or a Buffer.
134+
detectFindingAndGuideFuzzing(params[0], goal, hookId, functionName);
141135
};
142136

143137
registerBeforeHook(functionName, module.moduleName, false, beforeHook);
@@ -174,19 +168,15 @@ for (const module of functionsWithTwoTargets) {
174168
if (params === undefined || params.length < 2) {
175169
return;
176170
}
177-
// The first two arguments are paths.
178-
const firstArgument = params[0] as string;
179-
const secondArgument = params[1] as string;
180-
if (firstArgument.includes(goal) || secondArgument.includes(goal)) {
181-
reportFinding(
182-
`Path Traversal in ${functionName}(): called with '${firstArgument}'` +
183-
` and '${secondArgument}'`,
184-
);
185-
}
186-
guideTowardsContainment(firstArgument, goal, hookId);
187171
// We don't want to confuse the fuzzer guidance with the same hookId for both function arguments.
188172
// Therefore, we use an extra hookId for the second argument.
189-
guideTowardsContainment(secondArgument, goal, extraHookId);
173+
detectFindingAndGuideFuzzing(params[0], goal, hookId, functionName);
174+
detectFindingAndGuideFuzzing(
175+
params[1],
176+
goal,
177+
extraHookId,
178+
functionName,
179+
);
190180
};
191181
};
192182

@@ -198,3 +188,24 @@ for (const module of functionsWithTwoTargets) {
198188
);
199189
}
200190
}
191+
192+
function detectFindingAndGuideFuzzing(
193+
input: unknown,
194+
goal: string,
195+
hookId: number,
196+
functionName: string,
197+
) {
198+
if (
199+
typeof input === "string" ||
200+
input instanceof URL ||
201+
input instanceof Buffer
202+
) {
203+
const argument = input.toString();
204+
if (argument.includes(goal)) {
205+
reportFinding(
206+
`Path Traversal in ${functionName}(): called with '${argument}'`,
207+
);
208+
}
209+
guideTowardsContainment(argument, goal, hookId);
210+
}
211+
}

tests/bug-detectors/command-injection.test.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,20 @@ describe("Command injection", () => {
171171
expect(fs.existsSync(friendlyFilePath)).toBeTruthy();
172172
});
173173
});
174+
175+
describe("Invalid arguments", () => {
176+
const bugDetectorDirectory = path.join(__dirname, "command-injection");
177+
178+
it("invalid args to exec", () => {
179+
const fuzzTest = new FuzzTestBuilder()
180+
.runs(0)
181+
.sync(false)
182+
.fuzzEntryPoint("execInvalid")
183+
.dir(bugDetectorDirectory)
184+
.build();
185+
expect(() => fuzzTest.execute()).toThrow();
186+
expect(fuzzTest.stderr).toContain(
187+
'TypeError: The "file" argument must be of type string.',
188+
);
189+
});
190+
});

tests/bug-detectors/command-injection/fuzz.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,7 @@ module.exports.forkEVIL = function (data) {
9292
module.exports.forkFRIENDLY = function (data) {
9393
child_process.fork("makeFRIENDLY.js");
9494
};
95+
96+
module.exports.execInvalid = async function (data) {
97+
child_process.exec(0);
98+
};

tests/bug-detectors/path-traversal.test.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,33 @@ describe("Path Traversal", () => {
186186
fuzzTest.execute();
187187
});
188188
});
189+
190+
describe("Path Traversal invalid input", () => {
191+
const bugDetectorDirectory = path.join(__dirname, "path-traversal");
192+
193+
it("Invalid args to open", () => {
194+
const fuzzTest = new FuzzTestBuilder()
195+
.runs(0)
196+
.sync(true)
197+
.fuzzEntryPoint("invalidArgsToOpen")
198+
.dir(bugDetectorDirectory)
199+
.build();
200+
expect(() => fuzzTest.execute()).toThrow();
201+
expect(fuzzTest.stderr).toContain(
202+
'TypeError: The "path" argument must be of type string or an instance of Buffer or URL.',
203+
);
204+
});
205+
206+
it("Invalid first arg to cp", () => {
207+
const fuzzTest = new FuzzTestBuilder()
208+
.runs(0)
209+
.sync(true)
210+
.fuzzEntryPoint("invalidArgsToCp")
211+
.dir(bugDetectorDirectory)
212+
.build();
213+
expect(() => fuzzTest.execute()).toThrow();
214+
expect(fuzzTest.stderr).toContain(
215+
'TypeError: The "src" argument must be of type string or an instance of Buffer or URL.',
216+
);
217+
});
218+
});

tests/bug-detectors/path-traversal/fuzz.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,11 @@ module.exports.PathTraversalJoinEvilAsync = makeFnCalledOnce(async (data) => {
100100
module.exports.PathTraversalJoinSafeAsync = makeFnCalledOnce(async (data) => {
101101
path.join(safe_path, "SAFE");
102102
});
103+
104+
module.exports.invalidArgsToOpen = makeFnCalledOnce((data) => {
105+
fs.openSync(0);
106+
});
107+
108+
module.exports.invalidArgsToCp = makeFnCalledOnce((data) => {
109+
fs.cp(0, 0, () => {});
110+
});

0 commit comments

Comments
 (0)