circle-ir 4.16.1 → 4.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis/advisories.d.ts +186 -0
- package/dist/analysis/advisories.d.ts.map +1 -0
- package/dist/analysis/advisories.js +391 -0
- package/dist/analysis/advisories.js.map +1 -0
- package/dist/analysis/comment-lines.d.ts +14 -0
- package/dist/analysis/comment-lines.d.ts.map +1 -0
- package/dist/analysis/comment-lines.js +34 -0
- package/dist/analysis/comment-lines.js.map +1 -0
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +59 -5
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/constant-caller-gate.d.ts +2 -2
- package/dist/analysis/constant-caller-gate.d.ts.map +1 -1
- package/dist/analysis/constant-caller-gate.js +205 -29
- package/dist/analysis/constant-caller-gate.js.map +1 -1
- package/dist/analysis/constant-propagation/index.d.ts +2 -0
- package/dist/analysis/constant-propagation/index.d.ts.map +1 -1
- package/dist/analysis/constant-propagation/index.js +10 -0
- package/dist/analysis/constant-propagation/index.js.map +1 -1
- package/dist/analysis/constant-propagation/propagator.d.ts +15 -0
- package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
- package/dist/analysis/constant-propagation/propagator.js +224 -20
- package/dist/analysis/constant-propagation/propagator.js.map +1 -1
- package/dist/analysis/constant-propagation/types.d.ts +2 -0
- package/dist/analysis/constant-propagation/types.d.ts.map +1 -1
- package/dist/analysis/constant-propagation.d.ts +1 -1
- package/dist/analysis/constant-propagation.d.ts.map +1 -1
- package/dist/analysis/constant-propagation.js +1 -1
- package/dist/analysis/constant-propagation.js.map +1 -1
- package/dist/analysis/entry-point-detection.d.ts.map +1 -1
- package/dist/analysis/entry-point-detection.js +72 -0
- package/dist/analysis/entry-point-detection.js.map +1 -1
- package/dist/analysis/interprocedural.d.ts.map +1 -1
- package/dist/analysis/interprocedural.js +75 -8
- package/dist/analysis/interprocedural.js.map +1 -1
- package/dist/analysis/maven-declared.d.ts +72 -0
- package/dist/analysis/maven-declared.d.ts.map +1 -0
- package/dist/analysis/maven-declared.js +216 -0
- package/dist/analysis/maven-declared.js.map +1 -0
- package/dist/analysis/maven-version.d.ts +28 -0
- package/dist/analysis/maven-version.d.ts.map +1 -0
- package/dist/analysis/maven-version.js +172 -0
- package/dist/analysis/maven-version.js.map +1 -0
- package/dist/analysis/passes/constant-propagation-pass.d.ts.map +1 -1
- package/dist/analysis/passes/constant-propagation-pass.js +29 -1
- package/dist/analysis/passes/constant-propagation-pass.js.map +1 -1
- package/dist/analysis/passes/cross-file-pass.d.ts.map +1 -1
- package/dist/analysis/passes/cross-file-pass.js +84 -2
- package/dist/analysis/passes/cross-file-pass.js.map +1 -1
- package/dist/analysis/passes/csharp-sanitizer-credit.d.ts +7 -0
- package/dist/analysis/passes/csharp-sanitizer-credit.d.ts.map +1 -1
- package/dist/analysis/passes/csharp-sanitizer-credit.js +44 -1
- package/dist/analysis/passes/csharp-sanitizer-credit.js.map +1 -1
- package/dist/analysis/passes/insecure-cookie-pass.d.ts +2 -0
- package/dist/analysis/passes/insecure-cookie-pass.d.ts.map +1 -1
- package/dist/analysis/passes/insecure-cookie-pass.js +25 -3
- package/dist/analysis/passes/insecure-cookie-pass.js.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.d.ts +11 -14
- package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.js +562 -0
- package/dist/analysis/passes/interprocedural-pass.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts +18 -1
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +1079 -14
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/param-source-flow-gate-pass.d.ts.map +1 -1
- package/dist/analysis/passes/param-source-flow-gate-pass.js +82 -3
- package/dist/analysis/passes/param-source-flow-gate-pass.js.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts +44 -2
- package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +690 -12
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.js +298 -19
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/passes/tls-verify-disabled-pass.d.ts +18 -0
- package/dist/analysis/passes/tls-verify-disabled-pass.d.ts.map +1 -1
- package/dist/analysis/passes/tls-verify-disabled-pass.js +161 -0
- package/dist/analysis/passes/tls-verify-disabled-pass.js.map +1 -1
- package/dist/analysis/passes/weak-random-pass.d.ts +6 -0
- package/dist/analysis/passes/weak-random-pass.d.ts.map +1 -1
- package/dist/analysis/passes/weak-random-pass.js +29 -0
- package/dist/analysis/passes/weak-random-pass.js.map +1 -1
- package/dist/analysis/promote-cross-file-flows.d.ts +17 -0
- package/dist/analysis/promote-cross-file-flows.d.ts.map +1 -0
- package/dist/analysis/promote-cross-file-flows.js +32 -0
- package/dist/analysis/promote-cross-file-flows.js.map +1 -0
- package/dist/analysis/rust-literal-guard.d.ts.map +1 -1
- package/dist/analysis/rust-literal-guard.js +100 -1
- package/dist/analysis/rust-literal-guard.js.map +1 -1
- package/dist/analysis/sbom.d.ts +100 -0
- package/dist/analysis/sbom.d.ts.map +1 -1
- package/dist/analysis/sbom.js +514 -3
- package/dist/analysis/sbom.js.map +1 -1
- package/dist/analysis/semver-version.d.ts +30 -0
- package/dist/analysis/semver-version.d.ts.map +1 -0
- package/dist/analysis/semver-version.js +76 -0
- package/dist/analysis/semver-version.js.map +1 -0
- package/dist/analysis/taint-matcher.d.ts +5 -0
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +226 -4
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analyzer.d.ts +57 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +69 -6
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +3360 -314
- package/dist/core/circle-ir-core.cjs +783 -220
- package/dist/core/circle-ir-core.js +783 -220
- package/dist/core/extractors/dfg.js +205 -2
- package/dist/core/extractors/dfg.js.map +1 -1
- package/dist/core/extractors/ts-declaration-detail.d.ts +39 -0
- package/dist/core/extractors/ts-declaration-detail.d.ts.map +1 -0
- package/dist/core/extractors/ts-declaration-detail.js +266 -0
- package/dist/core/extractors/ts-declaration-detail.js.map +1 -0
- package/dist/core/extractors/types.js +53 -9
- package/dist/core/extractors/types.js.map +1 -1
- package/dist/graph/analysis-pass.d.ts +4 -1
- package/dist/graph/analysis-pass.d.ts.map +1 -1
- package/dist/graph/analysis-pass.js +4 -1
- package/dist/graph/analysis-pass.js.map +1 -1
- package/dist/graph-facts/dead-code.d.ts +62 -0
- package/dist/graph-facts/dead-code.d.ts.map +1 -0
- package/dist/graph-facts/dead-code.js +90 -0
- package/dist/graph-facts/dead-code.js.map +1 -0
- package/dist/graph-facts/dependency-calls.d.ts +86 -0
- package/dist/graph-facts/dependency-calls.d.ts.map +1 -0
- package/dist/graph-facts/dependency-calls.js +93 -0
- package/dist/graph-facts/dependency-calls.js.map +1 -0
- package/dist/graph-facts/index.d.ts +10 -0
- package/dist/graph-facts/index.d.ts.map +1 -0
- package/dist/graph-facts/index.js +10 -0
- package/dist/graph-facts/index.js.map +1 -0
- package/dist/graph-facts/maven-packages.d.ts +42 -0
- package/dist/graph-facts/maven-packages.d.ts.map +1 -0
- package/dist/graph-facts/maven-packages.js +133 -0
- package/dist/graph-facts/maven-packages.js.map +1 -0
- package/dist/graph-facts/reach.d.ts +203 -0
- package/dist/graph-facts/reach.d.ts.map +1 -0
- package/dist/graph-facts/reach.js +219 -0
- package/dist/graph-facts/reach.js.map +1 -0
- package/dist/graph-facts/removal-reach.d.ts +73 -0
- package/dist/graph-facts/removal-reach.d.ts.map +1 -0
- package/dist/graph-facts/removal-reach.js +64 -0
- package/dist/graph-facts/removal-reach.js.map +1 -0
- package/dist/index.d.ts +21 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -3
- package/dist/index.js.map +1 -1
- package/dist/navigation/go-graph.d.ts +68 -0
- package/dist/navigation/go-graph.d.ts.map +1 -0
- package/dist/navigation/go-graph.js +753 -0
- package/dist/navigation/go-graph.js.map +1 -0
- package/dist/navigation/index.d.ts +7 -1
- package/dist/navigation/index.d.ts.map +1 -1
- package/dist/navigation/index.js +6 -0
- package/dist/navigation/index.js.map +1 -1
- package/dist/navigation/java-entry-tables.d.ts +43 -0
- package/dist/navigation/java-entry-tables.d.ts.map +1 -0
- package/dist/navigation/java-entry-tables.js +56 -0
- package/dist/navigation/java-entry-tables.js.map +1 -0
- package/dist/navigation/java-graph.d.ts +64 -0
- package/dist/navigation/java-graph.d.ts.map +1 -0
- package/dist/navigation/java-graph.js +466 -0
- package/dist/navigation/java-graph.js.map +1 -0
- package/dist/navigation/navigation-index.d.ts +91 -2
- package/dist/navigation/navigation-index.d.ts.map +1 -1
- package/dist/navigation/navigation-index.js +372 -45
- package/dist/navigation/navigation-index.js.map +1 -1
- package/dist/navigation/python-graph.d.ts +73 -0
- package/dist/navigation/python-graph.d.ts.map +1 -0
- package/dist/navigation/python-graph.js +867 -0
- package/dist/navigation/python-graph.js.map +1 -0
- package/dist/navigation/ts-call-edges.d.ts +193 -0
- package/dist/navigation/ts-call-edges.d.ts.map +1 -0
- package/dist/navigation/ts-call-edges.js +2089 -0
- package/dist/navigation/ts-call-edges.js.map +1 -0
- package/dist/navigation/ts-entry-tables.d.ts +88 -0
- package/dist/navigation/ts-entry-tables.d.ts.map +1 -0
- package/dist/navigation/ts-entry-tables.js +50 -0
- package/dist/navigation/ts-entry-tables.js.map +1 -0
- package/dist/path-set/branches.d.ts +52 -0
- package/dist/path-set/branches.d.ts.map +1 -0
- package/dist/path-set/branches.js +124 -0
- package/dist/path-set/branches.js.map +1 -0
- package/dist/path-set/finding-diff.d.ts +63 -0
- package/dist/path-set/finding-diff.d.ts.map +1 -0
- package/dist/path-set/finding-diff.js +150 -0
- package/dist/path-set/finding-diff.js.map +1 -0
- package/dist/path-set/index.d.ts +18 -0
- package/dist/path-set/index.d.ts.map +1 -0
- package/dist/path-set/index.js +12 -0
- package/dist/path-set/index.js.map +1 -0
- package/dist/path-set/line-diff.d.ts +19 -0
- package/dist/path-set/line-diff.d.ts.map +1 -0
- package/dist/path-set/line-diff.js +98 -0
- package/dist/path-set/line-diff.js.map +1 -0
- package/dist/path-set/path-set.d.ts +88 -0
- package/dist/path-set/path-set.d.ts.map +1 -0
- package/dist/path-set/path-set.js +716 -0
- package/dist/path-set/path-set.js.map +1 -0
- package/dist/path-set/templates.d.ts +42 -0
- package/dist/path-set/templates.d.ts.map +1 -0
- package/dist/path-set/templates.js +119 -0
- package/dist/path-set/templates.js.map +1 -0
- package/dist/path-set/types.d.ts +195 -0
- package/dist/path-set/types.d.ts.map +1 -0
- package/dist/path-set/types.js +8 -0
- package/dist/path-set/types.js.map +1 -0
- package/dist/remedy-class/index.d.ts +12 -0
- package/dist/remedy-class/index.d.ts.map +1 -0
- package/dist/remedy-class/index.js +9 -0
- package/dist/remedy-class/index.js.map +1 -0
- package/dist/remedy-class/match.d.ts +165 -0
- package/dist/remedy-class/match.d.ts.map +1 -0
- package/dist/remedy-class/match.js +705 -0
- package/dist/remedy-class/match.js.map +1 -0
- package/dist/remedy-class/output-context.d.ts +25 -0
- package/dist/remedy-class/output-context.d.ts.map +1 -0
- package/dist/remedy-class/output-context.js +150 -0
- package/dist/remedy-class/output-context.js.map +1 -0
- package/dist/remedy-class/sha256.d.ts +18 -0
- package/dist/remedy-class/sha256.d.ts.map +1 -0
- package/dist/remedy-class/sha256.js +89 -0
- package/dist/remedy-class/sha256.js.map +1 -0
- package/dist/remedy-class/table.d.ts +130 -0
- package/dist/remedy-class/table.d.ts.map +1 -0
- package/dist/remedy-class/table.js +457 -0
- package/dist/remedy-class/table.js.map +1 -0
- package/dist/resolution/cross-file.d.ts +18 -0
- package/dist/resolution/cross-file.d.ts.map +1 -1
- package/dist/resolution/cross-file.js +194 -5
- package/dist/resolution/cross-file.js.map +1 -1
- package/dist/stable-ids/builder.d.ts +20 -0
- package/dist/stable-ids/builder.d.ts.map +1 -0
- package/dist/stable-ids/builder.js +65 -0
- package/dist/stable-ids/builder.js.map +1 -0
- package/dist/stable-ids/diff.d.ts +18 -0
- package/dist/stable-ids/diff.d.ts.map +1 -0
- package/dist/stable-ids/diff.js +37 -0
- package/dist/stable-ids/diff.js.map +1 -0
- package/dist/stable-ids/go.d.ts +28 -0
- package/dist/stable-ids/go.d.ts.map +1 -0
- package/dist/stable-ids/go.js +160 -0
- package/dist/stable-ids/go.js.map +1 -0
- package/dist/stable-ids/hash.d.ts +22 -0
- package/dist/stable-ids/hash.d.ts.map +1 -0
- package/dist/stable-ids/hash.js +167 -0
- package/dist/stable-ids/hash.js.map +1 -0
- package/dist/stable-ids/index.d.ts +30 -0
- package/dist/stable-ids/index.d.ts.map +1 -0
- package/dist/stable-ids/index.js +59 -0
- package/dist/stable-ids/index.js.map +1 -0
- package/dist/stable-ids/java.d.ts +28 -0
- package/dist/stable-ids/java.d.ts.map +1 -0
- package/dist/stable-ids/java.js +224 -0
- package/dist/stable-ids/java.js.map +1 -0
- package/dist/stable-ids/moves.d.ts +87 -0
- package/dist/stable-ids/moves.d.ts.map +1 -0
- package/dist/stable-ids/moves.js +222 -0
- package/dist/stable-ids/moves.js.map +1 -0
- package/dist/stable-ids/nodes.d.ts +32 -0
- package/dist/stable-ids/nodes.d.ts.map +1 -0
- package/dist/stable-ids/nodes.js +92 -0
- package/dist/stable-ids/nodes.js.map +1 -0
- package/dist/stable-ids/normalize.d.ts +35 -0
- package/dist/stable-ids/normalize.d.ts.map +1 -0
- package/dist/stable-ids/normalize.js +174 -0
- package/dist/stable-ids/normalize.js.map +1 -0
- package/dist/stable-ids/parse-errors.d.ts +24 -0
- package/dist/stable-ids/parse-errors.d.ts.map +1 -0
- package/dist/stable-ids/parse-errors.js +46 -0
- package/dist/stable-ids/parse-errors.js.map +1 -0
- package/dist/stable-ids/python.d.ts +27 -0
- package/dist/stable-ids/python.d.ts.map +1 -0
- package/dist/stable-ids/python.js +212 -0
- package/dist/stable-ids/python.js.map +1 -0
- package/dist/stable-ids/ts-parse-errors.d.ts +27 -0
- package/dist/stable-ids/ts-parse-errors.d.ts.map +1 -0
- package/dist/stable-ids/ts-parse-errors.js +75 -0
- package/dist/stable-ids/ts-parse-errors.js.map +1 -0
- package/dist/stable-ids/types.d.ts +106 -0
- package/dist/stable-ids/types.d.ts.map +1 -0
- package/dist/stable-ids/types.js +16 -0
- package/dist/stable-ids/types.js.map +1 -0
- package/dist/stable-ids/typescript.d.ts +30 -0
- package/dist/stable-ids/typescript.d.ts.map +1 -0
- package/dist/stable-ids/typescript.js +362 -0
- package/dist/stable-ids/typescript.js.map +1 -0
- package/dist/types/config.d.ts +9 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/index.d.ts +53 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -18,6 +18,7 @@ import { attachSourceLineCode } from '../taint-matcher.js';
|
|
|
18
18
|
import { shouldGateInterproceduralParam } from '../entry-point-detection.js';
|
|
19
19
|
import { sanitizerCoversSink } from '../sanitizer-index.js';
|
|
20
20
|
import { identifierOutsideSanitizerCalls } from '../taint-propagation.js';
|
|
21
|
+
import { buildJavaTaintedVars, computeDeadJavaBranchLines } from './language-sources-pass.js';
|
|
21
22
|
export class InterproceduralPass {
|
|
22
23
|
name = 'interprocedural';
|
|
23
24
|
category = 'security';
|
|
@@ -150,6 +151,7 @@ export class InterproceduralPass {
|
|
|
150
151
|
}
|
|
151
152
|
}
|
|
152
153
|
}
|
|
154
|
+
appendCalleeSourceCallerSinkFlows(graph, sources, sinks, interProc, additionalFlows, language === 'java' ? ctx.code : undefined, language === 'java' ? constProp : undefined);
|
|
153
155
|
const taintBridges = findTaintBridges(interProc);
|
|
154
156
|
interprocedural = {
|
|
155
157
|
tainted_methods: Array.from(interProc.taintedMethods),
|
|
@@ -348,6 +350,566 @@ export class InterproceduralPass {
|
|
|
348
350
|
* The sink is reached when its statement mentions a derived name that is not
|
|
349
351
|
* sanitized or guarded there.
|
|
350
352
|
*/
|
|
353
|
+
/**
|
|
354
|
+
* True when constant propagation tracked `variable` in `method` and did not
|
|
355
|
+
* mark it tainted. Only a tracked symbol counts: "not in the tainted set" alone
|
|
356
|
+
* also means "never looked at" (see `isFalsePositive` reason 3).
|
|
357
|
+
*/
|
|
358
|
+
function assigneeIsConstPropClean(constProp, method, variable) {
|
|
359
|
+
if (!method)
|
|
360
|
+
return false;
|
|
361
|
+
const scoped = `${method}:${variable}`;
|
|
362
|
+
if (!constProp.symbols.has(scoped))
|
|
363
|
+
return false;
|
|
364
|
+
if (constProp.tainted.has(scoped))
|
|
365
|
+
return false;
|
|
366
|
+
for (const t of constProp.tainted) {
|
|
367
|
+
if (t.startsWith(`${method}:`) && t.endsWith(`:${variable}`))
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
if (constProp.sanitizedVarUncovered?.has(scoped))
|
|
371
|
+
return false;
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
function constPropTaints(constProp, method, variable) {
|
|
375
|
+
if (constProp.tainted.has(`${method}:${variable}`))
|
|
376
|
+
return true;
|
|
377
|
+
for (const t of constProp.tainted) {
|
|
378
|
+
if (t.startsWith(`${method}:`) && t.endsWith(`:${variable}`))
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
/** Does a call on `line` in `method` take an argument const-prop holds tainted? */
|
|
384
|
+
function callArgumentIsConstPropTainted(graph, constProp, method, line) {
|
|
385
|
+
if (!method)
|
|
386
|
+
return false;
|
|
387
|
+
for (const call of graph.ir.calls) {
|
|
388
|
+
if (call.location.line !== line)
|
|
389
|
+
continue;
|
|
390
|
+
for (const arg of call.arguments) {
|
|
391
|
+
const names = arg.variable
|
|
392
|
+
? [arg.variable]
|
|
393
|
+
: (arg.expression ?? '').match(/[A-Za-z_]\w*/g) ?? [];
|
|
394
|
+
if (names.some(n => constPropTaints(constProp, method, n)))
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Does the callee `return` a variable const-prop holds tainted elements or keys
|
|
402
|
+
* for (`vars[i] = "X=" + param; return vars;`)? Const-prop does not carry that
|
|
403
|
+
* into the call's result, so its "clean" assignee is not proof here.
|
|
404
|
+
*/
|
|
405
|
+
function calleeReturnsTaintedContainer(constProp, code, startLine, endLine) {
|
|
406
|
+
const lines = code.split('\n');
|
|
407
|
+
for (let l = startLine; l <= endLine; l++) {
|
|
408
|
+
const m = /^\s*return\s+([A-Za-z_]\w*)\s*;/.exec(lines[l - 1] ?? '');
|
|
409
|
+
if (!m)
|
|
410
|
+
continue;
|
|
411
|
+
if ((constProp.taintedArrayElements.get(m[1])?.size ?? 0) > 0)
|
|
412
|
+
return true;
|
|
413
|
+
if ((constProp.taintedCollections.get(m[1])?.size ?? 0) > 0)
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Sink types whose parameter-sourced helper flows are dropped when the
|
|
420
|
+
* parameter reaches the helper's return only through a constant-dead branch
|
|
421
|
+
* (`if ((7 * 42) - num > 200) bar = "safe"; else bar = param;`).
|
|
422
|
+
*/
|
|
423
|
+
const DEAD_BRANCH_CHECKED_SINKS = new Set([
|
|
424
|
+
'sql_injection', 'trust_boundary', 'path_traversal', 'ldap_injection', 'xpath_injection',
|
|
425
|
+
]);
|
|
426
|
+
/**
|
|
427
|
+
* Java: do the defs a callee source creates reach a `return` of the callee?
|
|
428
|
+
* Follows def-use chains forward (a use of a reached def reaches the locals
|
|
429
|
+
* defined on its line), so an overwrite before the return ends the chain.
|
|
430
|
+
* A source on the return line itself reaches it.
|
|
431
|
+
*/
|
|
432
|
+
function calleeSourceReachesReturn(graph, code, startLine, endLine, sourceLine) {
|
|
433
|
+
const lines = code.split('\n');
|
|
434
|
+
const returnLines = new Set();
|
|
435
|
+
for (let l = startLine; l <= endLine; l++) {
|
|
436
|
+
if (/^\s*return\b/.test(lines[l - 1] ?? ''))
|
|
437
|
+
returnLines.add(l);
|
|
438
|
+
}
|
|
439
|
+
if (returnLines.size === 0)
|
|
440
|
+
return true; // nothing to prove against: keep the source
|
|
441
|
+
if (returnLines.has(sourceLine))
|
|
442
|
+
return true;
|
|
443
|
+
const inRange = (l) => l >= startLine && l <= endLine;
|
|
444
|
+
// A statement can span lines (`String e =\n new String(\n … d.getBytes()));`):
|
|
445
|
+
// the def sits on its first line, the uses on later ones.
|
|
446
|
+
const ends = (l) => /[;{}]\s*$/.test((lines[l - 1] ?? '').replace(/\/\/.*$/, '').trimEnd());
|
|
447
|
+
const statementStart = (l) => {
|
|
448
|
+
let s = l;
|
|
449
|
+
while (s > startLine && !ends(s - 1) && (lines[s - 2] ?? '').trim() !== '')
|
|
450
|
+
s--;
|
|
451
|
+
return s;
|
|
452
|
+
};
|
|
453
|
+
const reached = new Set(graph.ir.dfg.defs.filter(d => d.line === sourceLine && d.kind === 'local').map(d => d.id));
|
|
454
|
+
if (reached.size === 0)
|
|
455
|
+
return true; // the source defines nothing we can follow
|
|
456
|
+
let changed = true;
|
|
457
|
+
while (changed) {
|
|
458
|
+
changed = false;
|
|
459
|
+
for (const u of graph.ir.dfg.uses) {
|
|
460
|
+
if (u.def_id === null || !reached.has(u.def_id) || !inRange(u.line))
|
|
461
|
+
continue;
|
|
462
|
+
const stmt = statementStart(u.line);
|
|
463
|
+
if (returnLines.has(u.line) || returnLines.has(stmt))
|
|
464
|
+
return true;
|
|
465
|
+
for (const d of graph.ir.dfg.defs) {
|
|
466
|
+
if ((d.line === u.line || d.line === stmt) && d.kind === 'local' && !reached.has(d.id)) {
|
|
467
|
+
reached.add(d.id);
|
|
468
|
+
changed = true;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
// An element or field store (`arr[i] = name + "=" + value;`, `o.f = value;`)
|
|
472
|
+
// defines no local, but the value now lives in the base: follow the base's
|
|
473
|
+
// def (plexus-utils `getEnvironmentVariables()` fills an array it returns).
|
|
474
|
+
// Container calls (`map.put`, `list.add`) are not followed: the keyed
|
|
475
|
+
// overwrite cases above depend on them ending the chain.
|
|
476
|
+
const base = storeBase(lines[stmt - 1] ?? '');
|
|
477
|
+
if (base && base !== u.variable) {
|
|
478
|
+
for (const b of graph.ir.dfg.uses) {
|
|
479
|
+
if ((b.line === u.line || b.line === stmt) && b.variable === base &&
|
|
480
|
+
b.def_id !== null && !reached.has(b.def_id)) {
|
|
481
|
+
reached.add(b.def_id);
|
|
482
|
+
changed = true;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
/** `arr[i] = …` / `arr[i][j] = …` / `obj.field = …` → the base local's name; else null. */
|
|
491
|
+
function storeBase(line) {
|
|
492
|
+
const m = /^\s*([A-Za-z_]\w*)\s*(?:\[[^\]=]*\]\s*)+=(?!=)/.exec(line) ??
|
|
493
|
+
/^\s*([A-Za-z_]\w*)\s*\.\s*[A-Za-z_]\w*\s*=(?!=)/.exec(line);
|
|
494
|
+
if (!m || m[1] === 'this')
|
|
495
|
+
return null;
|
|
496
|
+
return m[1];
|
|
497
|
+
}
|
|
498
|
+
/** cognium-dev#590 — connect a source inside a callee to a sink in the caller. */
|
|
499
|
+
function appendCalleeSourceCallerSinkFlows(graph, sources, sinks, interProc, additionalFlows, fileCode, constProp) {
|
|
500
|
+
const types = graph.ir.types ?? [];
|
|
501
|
+
for (const edge of interProc.callEdges) {
|
|
502
|
+
const callee = interProc.methodNodes.get(edge.calleeMethod);
|
|
503
|
+
if (!callee)
|
|
504
|
+
continue;
|
|
505
|
+
// Java: a source the callee overwrites before it returns
|
|
506
|
+
// (`bar = map.get("keyB"); bar = map.get("keyA"); return bar;`) does not
|
|
507
|
+
// reach the caller. Parameters are kept here; their live reach is the
|
|
508
|
+
// dead-branch check below.
|
|
509
|
+
const calleeSources = sources.filter(s => s.line >= callee.startLine && s.line <= callee.endLine &&
|
|
510
|
+
(!fileCode || s.type === 'interprocedural_param' ||
|
|
511
|
+
calleeSourceReachesReturn(graph, fileCode, callee.startLine, callee.endLine, s.line)));
|
|
512
|
+
if (calleeSources.length === 0)
|
|
513
|
+
continue;
|
|
514
|
+
const assignee = graph.ir.dfg.defs.find(d => d.line === edge.callLine && d.kind === 'local');
|
|
515
|
+
if (!assignee?.variable)
|
|
516
|
+
continue;
|
|
517
|
+
// A source inside the callee taints the caller only through the value the
|
|
518
|
+
// callee returns. Constant propagation evaluates that return with the
|
|
519
|
+
// callee's arguments (map keys, list indexes, constant branches, switch
|
|
520
|
+
// arms), and the DFG flows it vetoes are not reported either. When it has
|
|
521
|
+
// the assignee as a tracked, untainted value, the callee's source does not
|
|
522
|
+
// reach the return (OWASP `doSomething` helpers: `map.get("keyA")` after a
|
|
523
|
+
// `map.get("keyB")`, `if ((7 * 42) - num > 200)`, `valuesList.get(1)`).
|
|
524
|
+
// Its verdict is informed only when it saw tainted data go into the call:
|
|
525
|
+
// a value from another file (`scr.getTheParameter(…)`) is not a source to
|
|
526
|
+
// it, and then "clean" means "never tainted here", not "the callee drops it".
|
|
527
|
+
if (constProp &&
|
|
528
|
+
callArgumentIsConstPropTainted(graph, constProp, edge.callerMethod, edge.callLine) &&
|
|
529
|
+
assigneeIsConstPropClean(constProp, edge.callerMethod, assignee.variable) &&
|
|
530
|
+
!(fileCode && calleeReturnsTaintedContainer(constProp, fileCode, callee.startLine, callee.endLine))) {
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
const derivedLocals = localsDerivedFromAssignee(graph, assignee.variable, assignee.line);
|
|
534
|
+
for (const sink of sinks) {
|
|
535
|
+
if (sink.type === 'external_taint_escape')
|
|
536
|
+
continue;
|
|
537
|
+
if (sink.line <= edge.callLine)
|
|
538
|
+
continue;
|
|
539
|
+
if (enclosingMethodName(types, sink.line) !== edge.callerMethod)
|
|
540
|
+
continue;
|
|
541
|
+
const sinkVar = sinkVariableConsumingDerived(graph, sink, derivedLocals);
|
|
542
|
+
if (!sinkVar)
|
|
543
|
+
continue;
|
|
544
|
+
if (additionalFlows.some(f => f.source_line === calleeSources[0].line && f.sink_line === sink.line)) {
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
const source = calleeSources[0];
|
|
548
|
+
if (fileCode &&
|
|
549
|
+
source.type === 'interprocedural_param' &&
|
|
550
|
+
source.variable &&
|
|
551
|
+
DEAD_BRANCH_CHECKED_SINKS.has(sink.type) &&
|
|
552
|
+
interproceduralParamReachIsDeadBranchOnly(fileCode, callee.startLine, callee.endLine, source.variable, assignee.variable)) {
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
// cognium-dev#913 / #914 / #916 / #917 — the helper's return value cannot
|
|
556
|
+
// carry any of its parameters on a live path (constant / literal-slot /
|
|
557
|
+
// folded-ternary returns). Every source inside the callee must be a
|
|
558
|
+
// parameter source, and none may reach the return: a helper taking
|
|
559
|
+
// `(request, param)` is judged on both, and a real source inside the
|
|
560
|
+
// callee is never gated here.
|
|
561
|
+
if (fileCode &&
|
|
562
|
+
HELPER_RETURN_GATED_SINKS.has(sink.type) &&
|
|
563
|
+
calleeSources.every(s => (s.type === 'interprocedural_param' || s.type === 'plugin_param') && !!s.variable) &&
|
|
564
|
+
!calleeReturnMayCarryParam(fileCode, callee.startLine, callee.endLine, calleeSources.map(s => s.variable))) {
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
additionalFlows.push({
|
|
568
|
+
source_line: source.line,
|
|
569
|
+
sink_line: sink.line,
|
|
570
|
+
source_type: source.type,
|
|
571
|
+
sink_type: sink.type,
|
|
572
|
+
path: [
|
|
573
|
+
{ variable: source.location, line: source.line, type: 'source' },
|
|
574
|
+
{ variable: assignee.variable, line: edge.callLine, type: 'use' },
|
|
575
|
+
{ variable: sinkVar, line: sink.line, type: 'sink' },
|
|
576
|
+
],
|
|
577
|
+
confidence: sink.confidence * source.confidence * 0.85,
|
|
578
|
+
sanitized: false,
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* True when `param` would taint `local` only if dead-branch lines are counted
|
|
585
|
+
* (e.g. OWASP #913 `else bar = param`), not when live propagation also reaches
|
|
586
|
+
* `local` (list/Base64 helpers — cognium-dev#913 round 2).
|
|
587
|
+
*/
|
|
588
|
+
function interproceduralParamReachIsDeadBranchOnly(code, startLine, endLine, paramName, localName) {
|
|
589
|
+
if (paramName === localName)
|
|
590
|
+
return false;
|
|
591
|
+
const lines = code.split('\n');
|
|
592
|
+
const slice = lines.slice(startLine - 1, endLine);
|
|
593
|
+
const sliceText = slice.join('\n');
|
|
594
|
+
const dead = computeDeadJavaBranchLines(slice);
|
|
595
|
+
const onLivePaths = buildJavaTaintedVars(sliceText, new Set([paramName]), dead);
|
|
596
|
+
const ifDeadLinesCount = buildJavaTaintedVars(sliceText, new Set([paramName]), new Set());
|
|
597
|
+
return ifDeadLinesCount.has(localName) && !onLivePaths.has(localName);
|
|
598
|
+
}
|
|
599
|
+
/** Sink types whose caller-side flow is dropped when the helper cannot return the parameter. */
|
|
600
|
+
const HELPER_RETURN_GATED_SINKS = new Set([
|
|
601
|
+
'sql_injection',
|
|
602
|
+
'trust_boundary',
|
|
603
|
+
'command_injection',
|
|
604
|
+
'path_traversal',
|
|
605
|
+
// #976: the same helper shapes feed LDAP and XPath sinks (OWASP ldapi/xpathi).
|
|
606
|
+
'ldap_injection',
|
|
607
|
+
'xpath_injection',
|
|
608
|
+
]);
|
|
609
|
+
/**
|
|
610
|
+
* Can the callee's return value carry any of `sourceVars` (or a declared
|
|
611
|
+
* parameter) on a live path?
|
|
612
|
+
* cognium-dev#913 / #914 / #916 / #917.
|
|
613
|
+
*
|
|
614
|
+
* Conservative: answers false (gate the flow) only when every `return` on a live
|
|
615
|
+
* path is understood and none can carry the parameter. A return expression that
|
|
616
|
+
* mentions the parameter or anything derived from it, or a callee with no
|
|
617
|
+
* readable `return`, answers true.
|
|
618
|
+
* Derivation uses `buildJavaTaintedVars` with slot tracking, so `list.get(1)`
|
|
619
|
+
* after `list.remove(0)`, `map.get("<literal key>")` and a ternary whose
|
|
620
|
+
* condition folds to a constant pick the value actually returned.
|
|
621
|
+
*/
|
|
622
|
+
export function calleeReturnMayCarryParam(code, startLine, endLine, sourceVars) {
|
|
623
|
+
const raw = code.split('\n').slice(startLine - 1, endLine);
|
|
624
|
+
// Seed with the helper's declared parameters as well as the sources found in
|
|
625
|
+
// it: a `plugin_param` on `bar = (String) map.get("keyB")` stands for the
|
|
626
|
+
// parameter stored under that key, which the slot tables resolve.
|
|
627
|
+
const paramNames = [...sourceVars, ...javaMethodParamNames(raw)];
|
|
628
|
+
const dead = computeDeadJavaBranchLines(raw);
|
|
629
|
+
const inlined = new Set();
|
|
630
|
+
const slice = joinWrappedStatements(raw, inlined);
|
|
631
|
+
// The tracker follows assignments. A receiver mutated by a call with a carried
|
|
632
|
+
// argument (`sb.append(param)`, `parts.addAll(list)`) carries it too, so it is
|
|
633
|
+
// added as a seed and the tracker re-run until nothing new is reached.
|
|
634
|
+
const text = slice.join('\n');
|
|
635
|
+
const seeds = new Set(paramNames);
|
|
636
|
+
// Receivers that carry a value only because they were mutated with it: their
|
|
637
|
+
// declaration (`new String[n]`, `new StringBuilder()`) never does.
|
|
638
|
+
const mutated = new Set();
|
|
639
|
+
let live = new Set(seeds);
|
|
640
|
+
for (let round = 0; round < 8; round++) {
|
|
641
|
+
live = new Set([...seeds, ...buildJavaTaintedVars(text, seeds, dead, { trackSlots: true }).keys()]);
|
|
642
|
+
let grew = false;
|
|
643
|
+
for (let i = 0; i < slice.length; i++) {
|
|
644
|
+
if (dead.has(i))
|
|
645
|
+
continue;
|
|
646
|
+
for (const call of slice[i].matchAll(/([A-Za-z_]\w*)\s*\.\s*[A-Za-z_]\w*\s*\(([^;]*)\)/g)) {
|
|
647
|
+
if (live.has(call[1]))
|
|
648
|
+
continue;
|
|
649
|
+
const args = call[2].replace(/"(?:[^"\\]|\\.)*"/g, ' ').match(/[\p{L}\p{N}_]+/gu) ?? [];
|
|
650
|
+
if (args.some(t => live.has(t))) {
|
|
651
|
+
seeds.add(call[1]);
|
|
652
|
+
mutated.add(call[1]);
|
|
653
|
+
grew = true;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
// An element or field store (`vars[i] = name + "=" + value;`, `o.f = value;`)
|
|
657
|
+
// carries the value into its base (plexus-utils getEnvironmentVariables()).
|
|
658
|
+
const store = /^\s*([A-Za-z_]\w*)\s*(?:(?:\[[^\]=]*\]\s*)+|\.\s*[A-Za-z_]\w*\s*)=(?!=)\s*(.*?);?\s*$/.exec(slice[i]);
|
|
659
|
+
if (store && store[1] !== 'this' && !live.has(store[1])) {
|
|
660
|
+
const rhs = store[2].replace(/"(?:[^"\\]|\\.)*"/g, ' ').match(/[\p{L}\p{N}_]+/gu) ?? [];
|
|
661
|
+
if (rhs.some(t => live.has(t))) {
|
|
662
|
+
seeds.add(store[1]);
|
|
663
|
+
mutated.add(store[1]);
|
|
664
|
+
grew = true;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
if (!grew)
|
|
669
|
+
break;
|
|
670
|
+
}
|
|
671
|
+
let sawReturn = false;
|
|
672
|
+
for (let i = 0; i < slice.length; i++) {
|
|
673
|
+
if (dead.has(i) || !/^\s*return\b/.test(slice[i]))
|
|
674
|
+
continue;
|
|
675
|
+
const m = /^\s*return\b\s*(.*?)\s*;\s*(?:\/\/.*)?$/.exec(slice[i]);
|
|
676
|
+
if (!m)
|
|
677
|
+
return true; // could not read this return
|
|
678
|
+
sawReturn = true;
|
|
679
|
+
const expr = m[1];
|
|
680
|
+
const bare = /^[A-Za-z_]\w*$/.test(expr) ? expr : null;
|
|
681
|
+
if (bare && mutated.has(bare))
|
|
682
|
+
return true;
|
|
683
|
+
if (bare && live.has(bare)) {
|
|
684
|
+
// The tracker is flow-insensitive: `bar = map.get("tainted-key"); bar =
|
|
685
|
+
// map.get("safe-key"); return bar;` reaches it either way. Judge the
|
|
686
|
+
// assignment that actually reaches this return instead, when there is one.
|
|
687
|
+
const def = mustReachingAssignment(slice, dead, i, bare, inlined);
|
|
688
|
+
if (!def)
|
|
689
|
+
return true;
|
|
690
|
+
const upTo = slice.slice(0, def.line);
|
|
691
|
+
upTo.push(`__helperReturn = ${def.rhs};`);
|
|
692
|
+
const atDef = buildJavaTaintedVars(upTo.join('\n'), seeds, dead, { trackSlots: true });
|
|
693
|
+
if (atDef.has('__helperReturn'))
|
|
694
|
+
return true;
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
const idents = (expr.replace(/"(?:[^"\\]|\\.)*"/g, ' ').replace(/'(?:[^'\\]|\\.)'/g, ' ').match(/[\p{L}\p{N}_]+/gu) ?? []);
|
|
698
|
+
if (idents.some(t => live.has(t)))
|
|
699
|
+
return true;
|
|
700
|
+
}
|
|
701
|
+
return !sawReturn;
|
|
702
|
+
}
|
|
703
|
+
/** Parameter names from a Java method header (the lines before its body's `{`). */
|
|
704
|
+
function javaMethodParamNames(lines) {
|
|
705
|
+
const header = lines.join('\n').split('{')[0];
|
|
706
|
+
const open = header.indexOf('(');
|
|
707
|
+
if (open < 0)
|
|
708
|
+
return [];
|
|
709
|
+
let depth = 0;
|
|
710
|
+
let end = -1;
|
|
711
|
+
for (let k = open; k < header.length; k++) {
|
|
712
|
+
if (header[k] === '(')
|
|
713
|
+
depth++;
|
|
714
|
+
else if (header[k] === ')' && --depth === 0) {
|
|
715
|
+
end = k;
|
|
716
|
+
break;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
if (end < 0)
|
|
720
|
+
return [];
|
|
721
|
+
const names = [];
|
|
722
|
+
let angle = 0;
|
|
723
|
+
let piece = '';
|
|
724
|
+
for (const c of header.slice(open + 1, end) + ',') {
|
|
725
|
+
if (c === '<')
|
|
726
|
+
angle++;
|
|
727
|
+
else if (c === '>')
|
|
728
|
+
angle--;
|
|
729
|
+
if (c === ',' && angle === 0) {
|
|
730
|
+
const m = /([A-Za-z_]\w*)\s*(?:\[\s*\])*\s*$/.exec(piece.trim());
|
|
731
|
+
if (m)
|
|
732
|
+
names.push(m[1]);
|
|
733
|
+
piece = '';
|
|
734
|
+
}
|
|
735
|
+
else
|
|
736
|
+
piece += c;
|
|
737
|
+
}
|
|
738
|
+
return names;
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Join each statement that wraps over several lines onto its first line and
|
|
742
|
+
* leave the continuation lines blank, so line indices (and dead-branch lines)
|
|
743
|
+
* still line up: `bar =\n new String(\n param.getBytes());` reads as one
|
|
744
|
+
* assignment. Control-flow headers (`if (x)` with the statement on the next
|
|
745
|
+
* line) and annotations are left as they are, as are comment lines.
|
|
746
|
+
*/
|
|
747
|
+
function joinWrappedStatements(lines, inlined) {
|
|
748
|
+
const out = [...lines];
|
|
749
|
+
const ends = /[;{}:]\s*(?:\/\/.*)?$/;
|
|
750
|
+
const header = /^\s*(?:if|else|for|while|do|switch|case|default|try|catch|finally|synchronized)\b|^\s*@|^\s*(?:\/\/|\/?\*)/;
|
|
751
|
+
for (let i = 0; i < out.length; i++) {
|
|
752
|
+
const line = out[i];
|
|
753
|
+
if (line.trim() === '' || ends.test(line) || header.test(line))
|
|
754
|
+
continue;
|
|
755
|
+
let joined = line;
|
|
756
|
+
let j = i + 1;
|
|
757
|
+
for (; j < out.length && j < i + 15; j++) {
|
|
758
|
+
joined += ' ' + out[j].trim();
|
|
759
|
+
if (ends.test(out[j]))
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
if (j >= out.length || j >= i + 15)
|
|
763
|
+
continue; // never terminated: leave it alone
|
|
764
|
+
out[i] = joined;
|
|
765
|
+
for (let k = i + 1; k <= j; k++)
|
|
766
|
+
out[k] = '';
|
|
767
|
+
i = j;
|
|
768
|
+
}
|
|
769
|
+
return out.map((line, i) => {
|
|
770
|
+
const body = inlineControlBody(line);
|
|
771
|
+
if (body !== line)
|
|
772
|
+
inlined.add(i);
|
|
773
|
+
return body;
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* `if (c) bar = param;` → `bar = param;` (also `else …`, `else if (c) …`,
|
|
778
|
+
* `for (…) …`, `while (…) …`). The tracker anchors assignments at the start of
|
|
779
|
+
* the line, so a one-line branch body was invisible to it. Dead branches were
|
|
780
|
+
* already computed on the original lines, so only the reading changes.
|
|
781
|
+
*/
|
|
782
|
+
function inlineControlBody(line) {
|
|
783
|
+
let rest = line;
|
|
784
|
+
const indent = /^\s*/.exec(line)[0];
|
|
785
|
+
for (let guard = 0; guard < 4; guard++) {
|
|
786
|
+
const t = rest.trimStart();
|
|
787
|
+
const elseM = /^(?:\}\s*)?else\b\s*/.exec(t);
|
|
788
|
+
if (elseM && !/^(?:\}\s*)?else\s+if\b/.test(t)) {
|
|
789
|
+
rest = t.slice(elseM[0].length);
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
const head = /^(?:\}\s*)?(?:else\s+)?(?:if|for|while)\s*\(/.exec(t);
|
|
793
|
+
if (!head)
|
|
794
|
+
break;
|
|
795
|
+
// Skip the balanced condition.
|
|
796
|
+
let depth = 1;
|
|
797
|
+
let k = head[0].length;
|
|
798
|
+
for (; k < t.length && depth > 0; k++) {
|
|
799
|
+
if (t[k] === '(')
|
|
800
|
+
depth++;
|
|
801
|
+
else if (t[k] === ')')
|
|
802
|
+
depth--;
|
|
803
|
+
}
|
|
804
|
+
if (depth !== 0)
|
|
805
|
+
return line;
|
|
806
|
+
rest = t.slice(k);
|
|
807
|
+
}
|
|
808
|
+
const body = rest.trim();
|
|
809
|
+
if (body === '' || body === rest.trim() && body === line.trim() || body.startsWith('{'))
|
|
810
|
+
return line;
|
|
811
|
+
return indent + body;
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* The assignment to `name` that must reach the `return` on slice line `retLine`:
|
|
815
|
+
* the closest preceding single-line `name = …;` in the same block, with no
|
|
816
|
+
* assignment to `name` in a nested block after it and no exit from the block in
|
|
817
|
+
* between. `null` when there is no such assignment (the caller then stays
|
|
818
|
+
* conservative).
|
|
819
|
+
*/
|
|
820
|
+
function mustReachingAssignment(slice, dead, retLine, name,
|
|
821
|
+
/** lines that were a one-line branch body: an assignment there is conditional */
|
|
822
|
+
inlined) {
|
|
823
|
+
// Brace depth at the start of each line, string and char literals skipped.
|
|
824
|
+
const depth = [];
|
|
825
|
+
let d = 0;
|
|
826
|
+
for (const line of slice) {
|
|
827
|
+
depth.push(d);
|
|
828
|
+
const code = line.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)'/g, "''").replace(/\/\/.*$/, '');
|
|
829
|
+
for (const c of code) {
|
|
830
|
+
if (c === '{')
|
|
831
|
+
d++;
|
|
832
|
+
else if (c === '}')
|
|
833
|
+
d--;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
const target = depth[retLine];
|
|
837
|
+
const esc = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
838
|
+
const assign = new RegExp(`^\\s*(?:(?:final\\s+)?[A-Za-z_][\\w.]*(?:\\s*<[^>]*>)?(?:\\s*\\[\\s*\\])*\\s+)?${esc}\\s*=\\s*(.+?);\\s*(?://.*)?$`);
|
|
839
|
+
const mentionsAssign = new RegExp(`(?<![\\w.])${esc}\\s*=(?!=)`);
|
|
840
|
+
for (let j = retLine - 1; j >= 0; j--) {
|
|
841
|
+
if (depth[j] < target)
|
|
842
|
+
return null; // left the return's block
|
|
843
|
+
if (dead.has(j))
|
|
844
|
+
continue;
|
|
845
|
+
if (!mentionsAssign.test(slice[j]))
|
|
846
|
+
continue;
|
|
847
|
+
if (depth[j] > target || inlined.has(j))
|
|
848
|
+
return null; // a conditional reassignment
|
|
849
|
+
const m = assign.exec(slice[j]);
|
|
850
|
+
if (!m)
|
|
851
|
+
return null; // wrapped or compound assignment
|
|
852
|
+
return { line: j, rhs: m[1] };
|
|
853
|
+
}
|
|
854
|
+
return null;
|
|
855
|
+
}
|
|
856
|
+
/** DFG-forward names reachable from a helper-return assignment LHS (same file). */
|
|
857
|
+
function localsDerivedFromAssignee(graph, seedVar, seedLine) {
|
|
858
|
+
const defs = graph.ir.dfg.defs;
|
|
859
|
+
const chains = graph.ir.dfg.chains ?? [];
|
|
860
|
+
const seedDefs = defs.filter(d => d.variable === seedVar && d.line === seedLine && d.kind === 'local');
|
|
861
|
+
if (seedDefs.length === 0)
|
|
862
|
+
return new Set([seedVar]);
|
|
863
|
+
const defIds = new Set(seedDefs.map(d => d.id));
|
|
864
|
+
const frontier = seedDefs.map(d => d.id);
|
|
865
|
+
const visited = new Set(frontier);
|
|
866
|
+
while (frontier.length > 0) {
|
|
867
|
+
const cur = frontier.shift();
|
|
868
|
+
for (const ch of chains) {
|
|
869
|
+
if (ch.from_def !== cur || visited.has(ch.to_def))
|
|
870
|
+
continue;
|
|
871
|
+
visited.add(ch.to_def);
|
|
872
|
+
defIds.add(ch.to_def);
|
|
873
|
+
frontier.push(ch.to_def);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
const names = new Set([seedVar]);
|
|
877
|
+
for (const d of defs) {
|
|
878
|
+
if (d.variable && defIds.has(d.id))
|
|
879
|
+
names.add(d.variable);
|
|
880
|
+
}
|
|
881
|
+
return names;
|
|
882
|
+
}
|
|
883
|
+
function sinkVariableConsumingDerived(graph, sink, derived) {
|
|
884
|
+
for (const u of graph.usesAtLine(sink.line)) {
|
|
885
|
+
if (u.variable && derived.has(u.variable))
|
|
886
|
+
return u.variable;
|
|
887
|
+
}
|
|
888
|
+
for (const call of graph.callsByLine.get(sink.line) ?? []) {
|
|
889
|
+
for (const arg of call.arguments ?? []) {
|
|
890
|
+
if (arg.variable && derived.has(arg.variable))
|
|
891
|
+
return arg.variable;
|
|
892
|
+
if (arg.expression) {
|
|
893
|
+
for (const v of derived) {
|
|
894
|
+
const re = new RegExp(`\\b${v.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`);
|
|
895
|
+
if (re.test(arg.expression))
|
|
896
|
+
return v;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
return undefined;
|
|
902
|
+
}
|
|
903
|
+
function enclosingMethodName(types, line) {
|
|
904
|
+
for (const type of types) {
|
|
905
|
+
for (const method of type.methods) {
|
|
906
|
+
if (line >= method.start_line && line <= method.end_line) {
|
|
907
|
+
return method.name;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
351
913
|
export function csharpParamReachesSink(code, method, taintedArgs, sink, credit) {
|
|
352
914
|
// CRLF files leave `\r` on every line, which `.` and `$` will not cross.
|
|
353
915
|
const lines = code.split(/\r?\n/);
|