Skip to content

Commit 3ef3f3f

Browse files
author
434b
committed
refactor: clean slate for path traversal
1 parent bac019a commit 3ef3f3f

File tree

25 files changed

+1220
-60
lines changed

25 files changed

+1220
-60
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2023 Code Intelligence GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
const jsonfile = "jsonfile";
18+
// eslint-disable-next-line @typescript-eslint/no-var-requires
19+
const { readFileSync } = require(jsonfile);
20+
// eslint-disable-next-line @typescript-eslint/no-var-requires
21+
const { FuzzedDataProvider } = require("@jazzer.js/core");
22+
23+
/**
24+
* @param { Buffer } data
25+
*/
26+
module.exports.fuzz = function (data) {
27+
try {
28+
if (data.length === 0) return;
29+
const provider = new FuzzedDataProvider(data);
30+
const str1 = provider.consumeString(provider.consumeIntegralInRange(1, 20));
31+
readFileSync(str1);
32+
} catch (e) {
33+
if (!ignoredError(e)) throw e;
34+
}
35+
};
36+
37+
/**
38+
* @param { Error } error
39+
*/
40+
function ignoredError(error) {
41+
return !!ignored.find((message) => error.message.includes(message));
42+
}
43+
44+
const ignored = ["ENOENT", "EISDIR", "The argument 'path'"];
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "custom-hooks-bd",
3+
"version": "1.0.0",
4+
"main": "fuzz.js",
5+
"license": "ISC",
6+
"dependencies": {
7+
"jsonfile": "^6.1.0"
8+
},
9+
"scripts": {
10+
"fuzz": "jazzer fuzz -i jsonfile --timeout=100000000 --sync -x Error -- -runs=100000",
11+
"dryRun": "jazzer fuzz --sync -x Error -- -runs=100000 -seed=123456789"
12+
},
13+
"devDependencies": {
14+
"@jazzer.js/core": "file:../../packages/core"
15+
}
16+
}

package-lock.json

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import {
2+
BugDetectorError,
3+
hookBuiltInFunction,
4+
saveFirstBugDetectorError,
5+
} from "./index";
6+
import { nextFakePC } from "@jazzer.js/instrumentor/dist/plugins/helpers";
7+
import { guideTowardsEquality } from "@jazzer.js/fuzzer";
8+
9+
export async function registerBasicBugDetector<
10+
F extends (...args: unknown[]) => unknown
11+
>(
12+
moduleName: string,
13+
targetFunctionName: string,
14+
evilCommand: string,
15+
baseErrorString: string,
16+
callOriginalFn: boolean
17+
): Promise<F> {
18+
const id = nextFakePC();
19+
return await hookBuiltInFunction(
20+
moduleName,
21+
targetFunctionName,
22+
(originalFn: F, cmdOrFileOrPath: string, ...args: unknown[]): F | void => {
23+
if (cmdOrFileOrPath.includes(evilCommand)) {
24+
const err = new BugDetectorError(
25+
baseErrorString +
26+
targetFunctionName +
27+
"() called with command: '" +
28+
cmdOrFileOrPath +
29+
"'"
30+
);
31+
// Remove the first 3 lines after the message from the stack trace.
32+
// The first lines are internal Jazzer function calls.
33+
saveFirstBugDetectorError(err, 3);
34+
}
35+
guideTowardsEquality(cmdOrFileOrPath, evilCommand, id);
36+
if (callOriginalFn) {
37+
return originalFn(cmdOrFileOrPath, ...args) as unknown as F;
38+
}
39+
}
40+
);
41+
}

packages/bug-detectors/index.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright 2023 Code Intelligence GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
export class BugDetectorError extends Error {}
18+
// Register bug detectors based on the provided list of bug detectors
19+
// eslint-disable-next-line @typescript-eslint/ban-types
20+
import { registerPathTraversalBugDetectors } from "./path-traversal";
21+
22+
interface BugDetector {
23+
[key: string]: (callOriginalFn: boolean) => Promise<void>;
24+
}
25+
26+
const bugDetectorRegistry: BugDetector = {
27+
pathTraversal: registerPathTraversalBugDetectors,
28+
};
29+
30+
/**
31+
* Registers bug detectors based on the provided list of bug detectors.
32+
*/
33+
export async function registerBugDetectors(
34+
detectorNames: string[]
35+
): Promise<void> {
36+
const registeredBugDetectors: Set<string> = new Set();
37+
38+
for (const detectorName of detectorNames) {
39+
if (registeredBugDetectors.has(detectorName)) {
40+
continue;
41+
}
42+
43+
const detector = bugDetectorRegistry[detectorName];
44+
if (detector) {
45+
registeredBugDetectors.add(detectorName);
46+
await detector(true);
47+
} else {
48+
console.error(`Unknown bug detector: ${detectorName}`);
49+
}
50+
}
51+
}
52+
53+
/**
54+
* Replaces a built-in function with a custom implementation while preserving
55+
* the original function for potential use within the replacement function.
56+
*
57+
* @param moduleName - The name of the module containing the target function.
58+
* @param targetFnName - The name of the target function to be replaced.
59+
* @param replacementFn - The replacement function that will be called instead
60+
* of the original function. The first argument passed
61+
* to the replacement function will be the original function,
62+
* followed by any arguments that were originally passed
63+
* to the target function.
64+
* @returns A promise that resolves to the original function that was replaced.
65+
* @throws Will throw an error if the module cannot be imported.
66+
*
67+
* @example
68+
* const originalExec = await hookBuiltInFunction(
69+
* "child_process",
70+
* "exec",
71+
* (originalFn: Function, cmd: string, options: object, callback: Function) => {
72+
* console.log("Custom implementation called with command:", cmd);
73+
* return originalFn(cmd, options, callback);
74+
* }
75+
* );
76+
*/
77+
export async function hookBuiltInFunction<
78+
// eslint-disable-next-line @typescript-eslint/ban-types
79+
F extends Function,
80+
// eslint-disable-next-line @typescript-eslint/ban-types
81+
K extends Function
82+
>(moduleName: string, targetFnName: string, replacementFn: F): Promise<K> {
83+
const { default: module } = await import(moduleName);
84+
const originalFn = module[targetFnName];
85+
module[targetFnName] = (...args: unknown[]) =>
86+
replacementFn(originalFn, ...args);
87+
return originalFn;
88+
}
89+
90+
// The first error to be found by any bug detector will be saved here.
91+
// This is a global variable shared between the core-library (read, reset) and the bug detectors (write).
92+
// It will be reset after the fuzzer has processed an input (only relevant for modes where the fuzzing
93+
// continues after finding an error, e.g. fork mode, Jest regression mode, fuzzing that ignores errors mode, etc.).
94+
let firstBugDetectorError: BugDetectorError | undefined;
95+
96+
export function getFirstBugDetectorError(): BugDetectorError | undefined {
97+
return firstBugDetectorError;
98+
}
99+
100+
// Clear the error saved by the bug detector before the fuzzer continues with a new input.
101+
export function clearFirstBugDetectorError(): void {
102+
firstBugDetectorError = undefined;
103+
}
104+
105+
export function saveFirstBugDetectorError(
106+
error: BugDetectorError,
107+
trimErrorStackLines = 0
108+
): void {
109+
// After an error has been saved, ignore all subsequent errors.
110+
if (firstBugDetectorError) {
111+
return;
112+
}
113+
error.stack = error.stack?.split("\n").slice(trimErrorStackLines).join("\n");
114+
firstBugDetectorError = error;
115+
throw error;
116+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "@jazzer.js/bug-detectors",
3+
"version": "1.4.0",
4+
"description": "Jazzer.js bug detectors",
5+
"homepage": "https://github.com/CodeIntelligenceTesting/jazzer.js#readme",
6+
"author": "Code Intelligence",
7+
"license": "Apache-2.0",
8+
"bugs": {
9+
"url": "https://github.com/CodeIntelligenceTesting/jazzer.js/issues"
10+
},
11+
"repository": {
12+
"type": "git",
13+
"url": "git+https://github.com/CodeIntelligenceTesting/jazzer.js.git",
14+
"directory": "packages/bug-detectors"
15+
},
16+
"main": "dist/index.js",
17+
"types": "dist/index.d.js",
18+
"dependencies": {
19+
"@jazzer.js/fuzzer": "*",
20+
"@jazzer.js/hooking": "*",
21+
"@jazzer.js/instrumentor": "*"
22+
},
23+
"devDependencies": {},
24+
"engines": {
25+
"node": ">= 14.0.0",
26+
"npm": ">= 7.0.0"
27+
}
28+
}

0 commit comments

Comments
 (0)