Skip to content

Commit e074574

Browse files
committed
bug detectors: track hooks of built-in modules
- in verbose mode, print module names with full path (for non built-in modules only) to make it easier to debug hooked functions
1 parent b08d067 commit e074574

File tree

7 files changed

+121
-45
lines changed

7 files changed

+121
-45
lines changed

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ const JSZip = require("jszip");
1919
// eslint-disable-next-line @typescript-eslint/no-var-requires
2020
const path = require("path");
2121

22-
// eslint-disable-next-line @typescript-eslint/no-var-requires
23-
const { FuzzedDataProvider } = require("@jazzer.js/core");
24-
2522
/**
2623
* This demonstrates the path traversal bug detector on a vulnerable version of jszip.
2724
* @param { Buffer } data
@@ -39,6 +36,7 @@ module.exports.fuzz = function (data) {
3936
path.join(__dirname, file);
4037
}
4138
})
39+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
4240
.catch(function (err) {
4341
// ignore broken zip files
4442
});

examples/bug-detectors/path-traversal/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
"jszip": "3.7.1"
88
},
99
"scripts": {
10-
"bugDetectors": "jazzer fuzz -i fuzz.js -i jszip --timeout=100000000 corpus -- -runs=10000000 -print_final_stats=1 -use_value_profile=1 -max_len=600",
10+
"bugDetectors": "jazzer fuzz -i fuzz.js -i jszip corpus -- -runs=10000000 -print_final_stats=1 -use_value_profile=1 -max_len=600",
1111
"merge": "jazzer fuzz -i jszip --timeout=100000000 --corpus=new --corpus=corpus -- -runs=1 -use_value_profile=1 -merge=1",
1212
"dryRun": "jazzer fuzz --sync -x Error -- -runs=100000 -seed=123456789"
1313
},
1414
"devDependencies": {
15-
"@jazzer.js/core": "file:../../packages/core"
15+
"@jazzer.js/core": "file:../../../packages/core"
1616
}
1717
}

packages/hooking/hook.ts

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,33 +14,85 @@
1414
* limitations under the License.
1515
*/
1616

17-
/*eslint @typescript-eslint/no-explicit-any: 0 */
17+
// Stores package names and function names [packageName -> [fnName, ...], ...].
18+
// This structure is used to keep track of all functions seen during instrumentation and execution of the fuzzing run,
19+
// to determine which hooks have been applied, are available, and have not been applied.
20+
export class TrackedHooks {
21+
hooks: Map<string, Set<string>>;
22+
constructor() {
23+
this.hooks = new Map();
24+
}
25+
26+
add(pkg: string, target: string) {
27+
if (!this.hooks.has(pkg)) {
28+
this.hooks.set(pkg, new Set());
29+
}
30+
this.hooks.get(pkg)?.add(target);
31+
}
32+
33+
has(pkg: string, target: string) {
34+
if (!this.hooks.has(pkg)) {
35+
return false;
36+
}
37+
return this.hooks.get(pkg)?.has(target);
38+
}
39+
40+
serialize(): TrackedHookData[] {
41+
const result: TrackedHookData[] = [];
42+
for (const [pkg, targets] of [...this.hooks].sort()) {
43+
for (const target of [...targets].sort()) {
44+
result.push({ pkg: pkg, target: target });
45+
}
46+
}
47+
return result;
48+
}
49+
50+
clear() {
51+
this.hooks.clear();
52+
}
53+
54+
get size() {
55+
let size = 0;
56+
for (const targets of this.hooks.values()) {
57+
size += targets.size;
58+
}
59+
return size;
60+
}
61+
}
62+
63+
export interface TrackedHookData {
64+
target: string;
65+
pkg: string;
66+
}
1867

1968
class hookTracker {
20-
public applied: Set<string> = new Set();
21-
public available: Set<string> = new Set();
22-
public notApplied: Set<string> = new Set();
69+
public applied = new TrackedHooks();
70+
public available = new TrackedHooks();
71+
public notApplied = new TrackedHooks();
2372

2473
print() {
2574
console.log("DEBUG: [Hook] Summary:");
26-
console.log("DEBUG: [Hook] Not applied:");
27-
[...this.notApplied].sort().forEach((hook) => {
28-
console.log(`DEBUG: [Hook] ${hook}`);
75+
console.log("DEBUG: [Hook] Not applied: " + this.notApplied.size);
76+
this.notApplied.serialize().forEach((hook) => {
77+
console.log(`DEBUG: [Hook] ${hook.pkg} -> ${hook.target}`);
2978
});
30-
console.log("DEBUG: [Hook] Applied:");
31-
[...this.applied].sort().forEach((hook) => {
32-
console.log(`DEBUG: [Hook] ${hook}`);
79+
console.log("DEBUG: [Hook] Applied: " + this.applied.size);
80+
this.applied.serialize().forEach((hook) => {
81+
console.log(`DEBUG: [Hook] ${hook.pkg} -> ${hook.target}`);
3382
});
34-
console.log("DEBUG: [Hook] Available:");
35-
[...this.available].sort().forEach((hook) => {
36-
console.log(`DEBUG: [Hook] ${hook}`);
83+
console.log("DEBUG: [Hook] Available: " + this.available.size);
84+
this.available.serialize().forEach((hook) => {
85+
console.log(`DEBUG: [Hook] ${hook.pkg} -> ${hook.target}`);
3786
});
3887
}
3988

4089
categorizeUnknown(requestedHooks: Hook[]): this {
4190
requestedHooks.forEach((hook) => {
42-
if (!this.applied.has(hook.target) && !this.available.has(hook.target)) {
43-
this.notApplied.add(hook.target);
91+
if (
92+
!this.applied.has(hook.pkg, hook.target) &&
93+
!this.available.has(hook.pkg, hook.target)
94+
) {
95+
this.notApplied.add(hook.pkg, hook.target);
4496
}
4597
});
4698
return this;
@@ -53,8 +105,22 @@ class hookTracker {
53105
}
54106
}
55107

108+
export function logHooks(hooks: Hook[]) {
109+
hooks.forEach((hook) => {
110+
if (process.env.JAZZER_DEBUG) {
111+
console.log(
112+
`DEBUG: Applied %s-hook in %s#%s`,
113+
HookType[hook.type],
114+
hook.pkg,
115+
hook.target
116+
);
117+
}
118+
});
119+
}
120+
56121
export const trackedHooks = new hookTracker();
57122

123+
/*eslint @typescript-eslint/no-explicit-any: 0 */
58124
export enum HookType {
59125
Before,
60126
After,

packages/hooking/manager.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import {
2121
HookFn,
2222
HookType,
2323
ReplaceHookFn,
24+
trackedHooks,
25+
logHooks,
2426
} from "./hook";
2527

2628
export class MatchingHooksResult {
@@ -247,5 +249,9 @@ export async function hookBuiltInFunction(hook: Hook): Promise<void> {
247249
const result: unknown = originalFn(...args);
248250
return (hook.hookFunction as AfterHookFn)(null, args, id, result);
249251
};
252+
} else {
253+
throw new Error(`Unknown hook type ${hook.type}`);
250254
}
255+
logHooks([hook]);
256+
trackedHooks.applied.add(hook.pkg, hook.target);
251257
}

packages/instrumentor/plugins/functionHooks.test.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@
2020
import { instrumentAndEvalWith } from "./testhelpers";
2121
import { functionHooks } from "./functionHooks";
2222
import * as hooking from "@jazzer.js/hooking";
23-
import { Hook, trackedHooks } from "@jazzer.js/hooking";
23+
import {
24+
Hook,
25+
TrackedHookData,
26+
TrackedHooks,
27+
trackedHooks,
28+
} from "@jazzer.js/hooking";
2429

2530
const expectInstrumentationEval = instrumentAndEvalWith(
2631
functionHooks("pkg/lib/a")
@@ -1012,9 +1017,22 @@ function expectLogHooks(
10121017
}
10131018
}
10141019

1015-
function expectTrackedHooks(tracker: Set<string>, entries: string[]) {
1016-
expect(entries.every((e) => tracker.has(e))).toBeTruthy();
1017-
expect(tracker.size).toEqual(entries.length);
1020+
// Only given functionNames (and none else) should be present in trackedHooks.
1021+
function expectTrackedHooks(
1022+
trackedHooks: TrackedHooks,
1023+
functionNames: string[]
1024+
) {
1025+
for (const functionName of functionNames) {
1026+
let found = false;
1027+
for (const hook of trackedHooks.serialize()) {
1028+
if (hook.target === functionName) {
1029+
found = true;
1030+
break;
1031+
}
1032+
}
1033+
expect(found).toBeTruthy();
1034+
}
1035+
expect(trackedHooks.size).toEqual(functionNames.length);
10181036
}
10191037

10201038
function expectTrackedHooksUnknown(

packages/instrumentor/plugins/functionHooks.ts

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,10 @@ import {
2121
hookManager,
2222
Hook,
2323
MatchingHooksResult,
24-
HookType,
2524
trackedHooks,
25+
logHooks,
2626
} from "@jazzer.js/hooking";
2727

28-
function logHooks(matchedHooks: MatchingHooksResult) {
29-
matchedHooks.hooks().forEach((hook) => {
30-
if (process.env.JAZZER_DEBUG) {
31-
console.log(
32-
`DEBUG: Applied %s-hook in %s#%s`,
33-
HookType[hook.type],
34-
hook.pkg,
35-
hook.target
36-
);
37-
}
38-
});
39-
}
40-
4128
export function functionHooks(filepath: string): () => PluginTarget {
4229
return () => {
4330
return {
@@ -46,8 +33,8 @@ export function functionHooks(filepath: string): () => PluginTarget {
4633
if (path.node.params.every((param) => babel.isIdentifier(param))) {
4734
const target = targetPath(path);
4835
const matchedHooks = hookManager.matchingHooks(target, filepath);
49-
if (applyHooks(target, path.node, matchedHooks)) {
50-
logHooks(matchedHooks);
36+
if (applyHooks(filepath, target, path.node, matchedHooks)) {
37+
logHooks(matchedHooks.hooks());
5138
path.skip();
5239
}
5340
}
@@ -60,6 +47,7 @@ export function functionHooks(filepath: string): () => PluginTarget {
6047
type FunctionWithBlockBody = babel.Function & { body: babel.BlockStatement };
6148

6249
function applyHooks(
50+
packageName: string,
6351
functionName: string,
6452
functionNode: babel.Function,
6553
matchesResult: MatchingHooksResult
@@ -68,12 +56,13 @@ function applyHooks(
6856
if (!functionNode.params.every((p) => babel.isIdentifier(p))) {
6957
return false;
7058
}
71-
7259
if (!matchesResult.hasHooks()) {
73-
trackedHooks.available.add(functionName);
60+
trackedHooks.available.add(packageName, functionName);
7461
return false;
75-
} else {
76-
trackedHooks.applied.add(functionName);
62+
}
63+
64+
for (const hook of matchesResult.hooks()) {
65+
trackedHooks.applied.add(hook.pkg, hook.target);
7766
}
7867

7968
// For arrow functions, the body can a single expression representing the value to be returned.

packages/jest-runner/worker.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import { formatResultsErrors } from "jest-message-util";
2626
import { inspect } from "util";
2727
import { fuzz, FuzzerStartError, skip } from "./fuzz";
2828
import { cleanupJestRunnerStack, removeTopFramesFromError } from "./errorUtils";
29-
import { Finding } from "@jazzer.js/bug-detectors";
3029

3130
function isGeneratorFunction(obj?: unknown): boolean {
3231
return (

0 commit comments

Comments
 (0)