Skip to content

Commit 84f7a99

Browse files
committed
Modify instrumentation instruction system (WIP)
1 parent 1357fc7 commit 84f7a99

File tree

17 files changed

+307
-89
lines changed

17 files changed

+307
-89
lines changed

instrumentation-wasm/src/js_transformer/transformer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ impl<'a> Traverse<'a> for Transformer<'a> {
132132

133133
if instruction.inspect_args {
134134
let source_text: &'a str = self.allocator.alloc_str(&format!(
135-
"__instrumentInspectArgs('{}', arguments);",
135+
"__instrumentInspectArgs('{}', false, arguments);",
136136
self.current_function_identifier.as_ref().unwrap()
137137
));
138138

library/agent/applyHooks.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { registerNodeHooks } from "./hooks/instrumentation";
99
import {
1010
setBuiltinsToInstrument,
1111
setPackagesToInstrument,
12-
} from "./hooks/instrumentation/loadHook";
12+
} from "./hooks/instrumentation/instructions";
1313

1414
/**
1515
* Hooks allows you to register packages and then wrap specific methods on
@@ -26,7 +26,7 @@ export function applyHooks(hooks: Hooks, newInstrumentation: boolean = false) {
2626
} else {
2727
setPackagesToInstrument(hooks.getPackages());
2828
setBuiltinsToInstrument(hooks.getBuiltInModules());
29-
registerNodeHooks(hooks);
29+
registerNodeHooks();
3030
}
3131

3232
hooks.getGlobals().forEach((g) => {

library/agent/hooks/BuiltinModule.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { RequireInterceptor } from "./RequireInterceptor";
33

44
export class BuiltinModule {
55
private requireInterceptors: RequireInterceptor[] = [];
6-
private instrumentationInstructions: BuiltinInstrumentationInstruction[] = [];
6+
private instrumentationInstructions:
7+
| BuiltinInstrumentationInstruction
8+
| undefined;
79

810
constructor(private readonly name: string) {
911
if (!this.name) {
@@ -23,11 +25,13 @@ export class BuiltinModule {
2325
return this.requireInterceptors;
2426
}
2527

26-
addInstrumentation(instruction: BuiltinInstrumentationInstruction) {
27-
this.instrumentationInstructions.push(instruction);
28+
setInstrumentationInstruction(
29+
instruction: BuiltinInstrumentationInstruction
30+
) {
31+
this.instrumentationInstructions = instruction;
2832
}
2933

30-
getInstrumentationInstructions() {
34+
getInstrumentationInstruction() {
3135
return this.instrumentationInstructions;
3236
}
3337
}

library/agent/hooks/instrumentation/builtinShim.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import { getExportsForBuiltin } from "./getExportsForBuiltin";
2-
import { BuiltinInstrumentationInstruction } from "./types";
2+
import {
3+
BuiltinInstrumentationInstruction,
4+
BuiltinInstrumentationInstructionJSON,
5+
} from "./types";
36

47
export function generateBuildinShim(
5-
moduleName: string,
6-
instructions: BuiltinInstrumentationInstruction["functions"]
8+
builtinName: string,
9+
builtinNameWithoutPrefix: string,
10+
instructions: BuiltinInstrumentationInstructionJSON["functions"]
711
): string | undefined {
8-
const exports = getExportsForBuiltin(moduleName);
12+
const exports = getExportsForBuiltin(builtinName);
913

1014
// Filter out non-existing exports
1115
const methods = instructions.filter((m) => exports.has(m.name));
@@ -20,14 +24,14 @@ export function generateBuildinShim(
2024
);
2125

2226
return `
23-
const orig = process.getBuiltinModule(${JSON.stringify(moduleName)});
27+
const orig = process.getBuiltinModule(${JSON.stringify(builtinName)});
2428
const { __instrumentInspectArgs } = require('@aikidosec/firewall/instrument/internals');
2529
2630
${methods
2731
.map(
2832
(method) => `
2933
exports.${method.name} = function() {
30-
${method.inspectArgs ? `__instrumentInspectArgs("${moduleName}.${method.name}", arguments);` : ""}
34+
${method.inspectArgs ? `__instrumentInspectArgs("${builtinNameWithoutPrefix}.${method.name}", true, arguments);` : ""}
3135
return orig.${method.name}(...arguments);
3236
};`
3337
)

library/agent/hooks/instrumentation/codeTransformation.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import type { PackageFileInstrumentationInstruction } from "./types";
1+
import type { PackageFileInstrumentationInstructionJSON } from "./types";
22
// eslint-disable-next-line camelcase
33
import { wasm_transform_code_str } from "./wasm/node_code_instrumentation";
44
import { getSourceType } from "./getSourceType";
55

6+
// Todo check if caching is done by Node or if we need to cache the result
7+
68
export function transformCode(
79
path: string,
810
code: string,
911
moduleName: string,
1012
isESM: boolean,
11-
fileInstructions: PackageFileInstrumentationInstruction
13+
fileInstructions: PackageFileInstrumentationInstructionJSON
1214
): string {
1315
const result = wasm_transform_code_str(
1416
code,

library/agent/hooks/instrumentation/index.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
import { onModuleLoad } from "./loadHook";
2-
import * as module from "node:module";
2+
import * as mod from "node:module";
33
import type { RegisterHookFunction } from "./types";
44
import { Hooks } from "../Hooks";
55

6-
export function registerNodeHooks(hooks: Hooks) {
7-
if (
8-
!("registerHooks" in module) ||
9-
typeof module.registerHooks !== "function"
10-
) {
6+
export function registerNodeHooks() {
7+
if (!("registerHooks" in mod) || typeof mod.registerHooks !== "function") {
118
throw new Error("This Node.js version is not supported");
129
}
1310

1411
// Hook into the ESM & CJS module loading process
1512
// Types are required because official Node.js typings are not up-to-date
16-
(module.registerHooks as RegisterHookFunction)({
13+
(mod.registerHooks as RegisterHookFunction)({
1714
load(url, context, nextLoad) {
1815
const result = nextLoad(url, context);
1916
return onModuleLoad(url, context, result);
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1-
export function __instrumentInspectArgs(name: string, args: unknown[]): void {
1+
export function __instrumentInspectArgs(
2+
id: string,
3+
isBuiltin: boolean,
4+
args: unknown[]
5+
): void {
26
// Todo do something
37
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { Package } from "../Package";
2+
import { BuiltinModule } from "../BuiltinModule";
3+
import {
4+
BuiltinInstrumentationInstructionJSON,
5+
IntereptorFunctionsObj,
6+
PackageFileInstrumentationInstructionJSON,
7+
} from "./types";
8+
import { satisfiesVersion } from "../../../helpers/satisfiesVersion";
9+
10+
// Keys are package / builtin names
11+
let packages = new Map<string, PackageFileInstrumentationInstructionJSON[]>();
12+
let builtins = new Map<string, BuiltinInstrumentationInstructionJSON>();
13+
14+
// Stores the callbacks for the instrumented functions of builtin modules
15+
// Identifier for builtin is: moduleName.functionName
16+
let builtinCallbacks = new Map<string, IntereptorFunctionsObj>();
17+
// Stores the callbacks for the instrumented functions of package files
18+
// Identifier for package file is: packageName.relativePath.functionName.matchingVersion
19+
let packageFileCallbacks = new Map<string, IntereptorFunctionsObj>();
20+
21+
export function setPackagesToInstrument(_packages: Package[]) {
22+
// Clear the previous packages
23+
packages = new Map();
24+
packageFileCallbacks = new Map();
25+
26+
for (const pkg of _packages) {
27+
const packageInstructions = pkg
28+
.getVersions()
29+
.map((versionedPackage) => {
30+
return versionedPackage
31+
.getFileInstrumentationInstructions()
32+
.map((file) => {
33+
return {
34+
path: file.path,
35+
versionRange: versionedPackage.getRange(),
36+
functions: file.functions.map((func) => {
37+
const identifier = `${pkg.getName()}.${file.path}.${func.name}.${versionedPackage.getRange()}`;
38+
39+
return {
40+
nodeType: func.nodeType,
41+
name: func.name,
42+
identifier,
43+
inspectArgs: !!func.inspectArgs,
44+
modifyArgs: !!func.modifyArgs,
45+
modifyReturnValue: !!func.modifyReturnValue,
46+
};
47+
}),
48+
};
49+
})
50+
.flat();
51+
})
52+
.flat();
53+
54+
packages.set(pkg.getName(), packageInstructions);
55+
}
56+
}
57+
58+
export function setBuiltinsToInstrument(builtinModules: BuiltinModule[]) {
59+
// Clear the previous builtins
60+
builtins = new Map();
61+
builtinCallbacks = new Map();
62+
63+
for (const builtin of builtinModules) {
64+
const instructions = builtin.getInstrumentationInstruction();
65+
66+
if (
67+
!instructions ||
68+
!instructions.functions ||
69+
instructions.functions.length === 0
70+
) {
71+
continue;
72+
}
73+
74+
// Check if function is included twice
75+
const functionNames = new Set<string>();
76+
for (const f of instructions.functions) {
77+
if (functionNames.has(f.name)) {
78+
throw new Error(
79+
`Function ${f.name} is included twice in the instrumentation instructions for ${builtin.getName()}`
80+
);
81+
}
82+
functionNames.add(f.name);
83+
}
84+
85+
const functions = instructions.functions.map((f) => {
86+
builtinCallbacks.set(`${builtin.getName()}.${f.name}`, {
87+
inspectArgs: f.inspectArgs,
88+
modifyArgs: f.modifyArgs,
89+
modifyReturnValue: f.modifyReturnValue,
90+
});
91+
92+
return {
93+
name: f.name,
94+
inspectArgs: !!f.inspectArgs,
95+
modifyArgs: !!f.modifyArgs,
96+
modifyReturnValue: !!f.modifyReturnValue,
97+
};
98+
});
99+
100+
builtins.set(builtin.getName(), {
101+
functions,
102+
});
103+
}
104+
}
105+
106+
export function getBuiltinInstrumentationInstructions(
107+
name: string
108+
): BuiltinInstrumentationInstructionJSON | undefined {
109+
return builtins.get(name);
110+
}
111+
112+
export function shouldPatchPackage(name: string): boolean {
113+
return packages.has(name);
114+
}
115+
116+
export function getPackageFileInstrumentationInstructions(
117+
packageName: string,
118+
version: string
119+
): PackageFileInstrumentationInstructionJSON | undefined {
120+
const instructions = packages.get(packageName);
121+
if (!instructions) {
122+
return;
123+
}
124+
125+
return instructions.find((f) => satisfiesVersion(f.versionRange, version));
126+
}

0 commit comments

Comments
 (0)