circle-ir 4.7.2 → 4.8.2
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/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +23 -0
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/passes/insecure-deserialization-config-pass.d.ts +2 -0
- package/dist/analysis/passes/insecure-deserialization-config-pass.d.ts.map +1 -1
- package/dist/analysis/passes/insecure-deserialization-config-pass.js +45 -2
- package/dist/analysis/passes/insecure-deserialization-config-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +59 -25
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +14 -0
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +252 -67
- package/dist/core/circle-ir-core.cjs +194 -66
- package/dist/core/circle-ir-core.js +194 -66
- package/dist/core/extractors/calls.js +17 -3
- package/dist/core/extractors/calls.js.map +1 -1
- package/dist/core/extractors/cfg.d.ts.map +1 -1
- package/dist/core/extractors/cfg.js +140 -40
- package/dist/core/extractors/cfg.js.map +1 -1
- package/package.json +1 -1
|
@@ -5980,13 +5980,21 @@ function extractCSharpCalls(tree, cache) {
|
|
|
5980
5980
|
const left = asn.childForFieldName("left");
|
|
5981
5981
|
if (left?.type !== "member_access_expression") continue;
|
|
5982
5982
|
const nameNode = left.childForFieldName("name");
|
|
5983
|
-
if (!nameNode
|
|
5983
|
+
if (!nameNode) continue;
|
|
5984
|
+
const propName = getNodeText(nameNode);
|
|
5985
|
+
const exprNode = left.childForFieldName("expression");
|
|
5986
|
+
if (propName === "Filter") {
|
|
5987
|
+
const recv = exprNode ? getNodeText(exprNode) : null;
|
|
5988
|
+
const recvType = recv ? typeMap.get(recv) : void 0;
|
|
5989
|
+
if (recvType !== "DirectorySearcher") continue;
|
|
5990
|
+
} else if (propName !== "CommandText") {
|
|
5991
|
+
continue;
|
|
5992
|
+
}
|
|
5984
5993
|
const right = asn.childForFieldName("right");
|
|
5985
5994
|
if (!right) continue;
|
|
5986
5995
|
const rhsText = getNodeText(right);
|
|
5987
|
-
const exprNode = left.childForFieldName("expression");
|
|
5988
5996
|
calls.push({
|
|
5989
|
-
method_name:
|
|
5997
|
+
method_name: propName,
|
|
5990
5998
|
receiver: exprNode ? getNodeText(exprNode) : null,
|
|
5991
5999
|
receiver_type: null,
|
|
5992
6000
|
receiver_type_fqn: null,
|
|
@@ -8552,6 +8560,9 @@ function buildCFG(tree, language, cache) {
|
|
|
8552
8560
|
if (effectiveLanguage === "go") {
|
|
8553
8561
|
return buildGoCFG(tree, blockIdCounter, cache);
|
|
8554
8562
|
}
|
|
8563
|
+
if (effectiveLanguage === "csharp") {
|
|
8564
|
+
return buildCSharpCFG(tree, blockIdCounter, cache);
|
|
8565
|
+
}
|
|
8555
8566
|
if (isJavaScript) {
|
|
8556
8567
|
const functions = [
|
|
8557
8568
|
...getNodesFromCache(tree.rootNode, "function_declaration", cache),
|
|
@@ -8564,7 +8575,7 @@ function buildCFG(tree, language, cache) {
|
|
|
8564
8575
|
const body2 = func2.childForFieldName("body");
|
|
8565
8576
|
if (!body2) continue;
|
|
8566
8577
|
if (body2.type === "statement_block") {
|
|
8567
|
-
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter,
|
|
8578
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "js");
|
|
8568
8579
|
allBlocks.push(...blocks);
|
|
8569
8580
|
allEdges.push(...edges);
|
|
8570
8581
|
blockIdCounter = nextId;
|
|
@@ -8586,7 +8597,7 @@ function buildCFG(tree, language, cache) {
|
|
|
8586
8597
|
for (const method of methods) {
|
|
8587
8598
|
const body2 = method.childForFieldName("body");
|
|
8588
8599
|
if (!body2) continue;
|
|
8589
|
-
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter,
|
|
8600
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "java");
|
|
8590
8601
|
allBlocks.push(...blocks);
|
|
8591
8602
|
allEdges.push(...edges);
|
|
8592
8603
|
blockIdCounter = nextId;
|
|
@@ -8594,7 +8605,28 @@ function buildCFG(tree, language, cache) {
|
|
|
8594
8605
|
}
|
|
8595
8606
|
return { blocks: allBlocks, edges: allEdges };
|
|
8596
8607
|
}
|
|
8597
|
-
function
|
|
8608
|
+
function buildCSharpCFG(tree, blockIdCounter, cache) {
|
|
8609
|
+
const allBlocks = [];
|
|
8610
|
+
const allEdges = [];
|
|
8611
|
+
const containers = [
|
|
8612
|
+
...getNodesFromCache(tree.rootNode, "method_declaration", cache),
|
|
8613
|
+
...getNodesFromCache(tree.rootNode, "constructor_declaration", cache),
|
|
8614
|
+
...getNodesFromCache(tree.rootNode, "destructor_declaration", cache),
|
|
8615
|
+
...getNodesFromCache(tree.rootNode, "operator_declaration", cache),
|
|
8616
|
+
...getNodesFromCache(tree.rootNode, "local_function_statement", cache),
|
|
8617
|
+
...getNodesFromCache(tree.rootNode, "accessor_declaration", cache)
|
|
8618
|
+
];
|
|
8619
|
+
for (const container of containers) {
|
|
8620
|
+
const body2 = container.childForFieldName("body");
|
|
8621
|
+
if (!body2 || body2.type !== "block") continue;
|
|
8622
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "csharp");
|
|
8623
|
+
allBlocks.push(...blocks);
|
|
8624
|
+
allEdges.push(...edges);
|
|
8625
|
+
blockIdCounter = nextId;
|
|
8626
|
+
}
|
|
8627
|
+
return { blocks: allBlocks, edges: allEdges };
|
|
8628
|
+
}
|
|
8629
|
+
function buildMethodCFG(body2, startId, dialect) {
|
|
8598
8630
|
const blocks = [];
|
|
8599
8631
|
const edges = [];
|
|
8600
8632
|
let currentId = startId;
|
|
@@ -8605,7 +8637,7 @@ function buildMethodCFG(body2, startId, isJavaScript) {
|
|
|
8605
8637
|
end_line: body2.startPosition.row + 1
|
|
8606
8638
|
};
|
|
8607
8639
|
blocks.push(entryBlock);
|
|
8608
|
-
const result = processStatements(body2, currentId, blocks, edges,
|
|
8640
|
+
const result = processStatements(body2, currentId, blocks, edges, dialect);
|
|
8609
8641
|
currentId = result.nextId;
|
|
8610
8642
|
if (result.entryId !== -1) {
|
|
8611
8643
|
edges.push({
|
|
@@ -8637,15 +8669,15 @@ function buildMethodCFG(body2, startId, isJavaScript) {
|
|
|
8637
8669
|
}
|
|
8638
8670
|
return { blocks, edges, nextId: currentId };
|
|
8639
8671
|
}
|
|
8640
|
-
function processStatements(container, startId, blocks, edges,
|
|
8672
|
+
function processStatements(container, startId, blocks, edges, dialect) {
|
|
8641
8673
|
let currentId = startId;
|
|
8642
8674
|
let firstBlockId = -1;
|
|
8643
8675
|
let lastExitIds = [];
|
|
8644
8676
|
for (let i2 = 0; i2 < container.childCount; i2++) {
|
|
8645
8677
|
const stmt = container.child(i2);
|
|
8646
8678
|
if (!stmt) continue;
|
|
8647
|
-
if (!isStatement(stmt,
|
|
8648
|
-
const result = processStatement(stmt, currentId, blocks, edges,
|
|
8679
|
+
if (!isStatement(stmt, dialect)) continue;
|
|
8680
|
+
const result = processStatement(stmt, currentId, blocks, edges, dialect);
|
|
8649
8681
|
currentId = result.nextId;
|
|
8650
8682
|
if (firstBlockId === -1) {
|
|
8651
8683
|
firstBlockId = result.entryId;
|
|
@@ -8666,31 +8698,49 @@ function processStatements(container, startId, blocks, edges, isJavaScript) {
|
|
|
8666
8698
|
nextId: currentId
|
|
8667
8699
|
};
|
|
8668
8700
|
}
|
|
8669
|
-
function processStatement(stmt, startId, blocks, edges,
|
|
8701
|
+
function processStatement(stmt, startId, blocks, edges, dialect) {
|
|
8670
8702
|
switch (stmt.type) {
|
|
8671
8703
|
case "if_statement":
|
|
8672
|
-
return processIfStatement(stmt, startId, blocks, edges,
|
|
8704
|
+
return processIfStatement(stmt, startId, blocks, edges, dialect);
|
|
8673
8705
|
case "for_statement":
|
|
8674
8706
|
case "enhanced_for_statement":
|
|
8675
8707
|
case "for_in_statement":
|
|
8676
8708
|
case "for_of_statement":
|
|
8677
|
-
|
|
8709
|
+
case "foreach_statement":
|
|
8710
|
+
return processForStatement(stmt, startId, blocks, edges, dialect);
|
|
8678
8711
|
case "while_statement":
|
|
8679
|
-
return processWhileStatement(stmt, startId, blocks, edges,
|
|
8712
|
+
return processWhileStatement(stmt, startId, blocks, edges, dialect);
|
|
8680
8713
|
case "do_statement":
|
|
8681
|
-
return processDoWhileStatement(stmt, startId, blocks, edges,
|
|
8714
|
+
return processDoWhileStatement(stmt, startId, blocks, edges, dialect);
|
|
8682
8715
|
case "try_statement":
|
|
8683
|
-
return processTryStatement(stmt, startId, blocks, edges,
|
|
8716
|
+
return processTryStatement(stmt, startId, blocks, edges, dialect);
|
|
8684
8717
|
case "switch_expression":
|
|
8685
8718
|
case "switch_statement":
|
|
8686
|
-
return processSwitchStatement(stmt, startId, blocks, edges,
|
|
8719
|
+
return processSwitchStatement(stmt, startId, blocks, edges, dialect);
|
|
8720
|
+
// C# scoped blocks add no branching — flow through the inner block so its
|
|
8721
|
+
// nested statements are still captured.
|
|
8722
|
+
case "using_statement":
|
|
8723
|
+
case "lock_statement":
|
|
8724
|
+
case "checked_statement":
|
|
8725
|
+
case "unsafe_statement": {
|
|
8726
|
+
const inner = stmt.childForFieldName("body") ?? lastBlockChild(stmt);
|
|
8727
|
+
if (inner) return processStatement(inner, startId, blocks, edges, dialect);
|
|
8728
|
+
return processSimpleStatement(stmt, startId, blocks);
|
|
8729
|
+
}
|
|
8687
8730
|
case "block":
|
|
8688
8731
|
case "statement_block":
|
|
8689
|
-
return processStatements(stmt, startId, blocks, edges,
|
|
8732
|
+
return processStatements(stmt, startId, blocks, edges, dialect);
|
|
8690
8733
|
default:
|
|
8691
8734
|
return processSimpleStatement(stmt, startId, blocks);
|
|
8692
8735
|
}
|
|
8693
8736
|
}
|
|
8737
|
+
function lastBlockChild(node) {
|
|
8738
|
+
for (let i2 = node.childCount - 1; i2 >= 0; i2--) {
|
|
8739
|
+
const c = node.child(i2);
|
|
8740
|
+
if (c && c.type === "block") return c;
|
|
8741
|
+
}
|
|
8742
|
+
return null;
|
|
8743
|
+
}
|
|
8694
8744
|
function processSimpleStatement(stmt, startId, blocks) {
|
|
8695
8745
|
const block = {
|
|
8696
8746
|
id: startId,
|
|
@@ -8705,7 +8755,7 @@ function processSimpleStatement(stmt, startId, blocks) {
|
|
|
8705
8755
|
nextId: startId + 1
|
|
8706
8756
|
};
|
|
8707
8757
|
}
|
|
8708
|
-
function processIfStatement(stmt, startId, blocks, edges,
|
|
8758
|
+
function processIfStatement(stmt, startId, blocks, edges, dialect) {
|
|
8709
8759
|
let currentId = startId;
|
|
8710
8760
|
const condBlock = {
|
|
8711
8761
|
id: currentId++,
|
|
@@ -8717,7 +8767,7 @@ function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8717
8767
|
const exitIds = [];
|
|
8718
8768
|
const consequence = stmt.childForFieldName("consequence");
|
|
8719
8769
|
if (consequence) {
|
|
8720
|
-
const thenResult = processStatement(consequence, currentId, blocks, edges,
|
|
8770
|
+
const thenResult = processStatement(consequence, currentId, blocks, edges, dialect);
|
|
8721
8771
|
currentId = thenResult.nextId;
|
|
8722
8772
|
edges.push({
|
|
8723
8773
|
from: condBlock.id,
|
|
@@ -8728,7 +8778,7 @@ function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8728
8778
|
}
|
|
8729
8779
|
const alternative = stmt.childForFieldName("alternative");
|
|
8730
8780
|
if (alternative) {
|
|
8731
|
-
const elseResult = processStatement(alternative, currentId, blocks, edges,
|
|
8781
|
+
const elseResult = processStatement(alternative, currentId, blocks, edges, dialect);
|
|
8732
8782
|
currentId = elseResult.nextId;
|
|
8733
8783
|
edges.push({
|
|
8734
8784
|
from: condBlock.id,
|
|
@@ -8745,7 +8795,7 @@ function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8745
8795
|
nextId: currentId
|
|
8746
8796
|
};
|
|
8747
8797
|
}
|
|
8748
|
-
function processForStatement(stmt, startId, blocks, edges,
|
|
8798
|
+
function processForStatement(stmt, startId, blocks, edges, dialect) {
|
|
8749
8799
|
let currentId = startId;
|
|
8750
8800
|
const loopBlock = {
|
|
8751
8801
|
id: currentId++,
|
|
@@ -8756,7 +8806,7 @@ function processForStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8756
8806
|
blocks.push(loopBlock);
|
|
8757
8807
|
const body2 = stmt.childForFieldName("body");
|
|
8758
8808
|
if (body2) {
|
|
8759
|
-
const bodyResult = processStatement(body2, currentId, blocks, edges,
|
|
8809
|
+
const bodyResult = processStatement(body2, currentId, blocks, edges, dialect);
|
|
8760
8810
|
currentId = bodyResult.nextId;
|
|
8761
8811
|
edges.push({
|
|
8762
8812
|
from: loopBlock.id,
|
|
@@ -8778,16 +8828,16 @@ function processForStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8778
8828
|
nextId: currentId
|
|
8779
8829
|
};
|
|
8780
8830
|
}
|
|
8781
|
-
function processWhileStatement(stmt, startId, blocks, edges,
|
|
8782
|
-
return processForStatement(stmt, startId, blocks, edges,
|
|
8831
|
+
function processWhileStatement(stmt, startId, blocks, edges, dialect) {
|
|
8832
|
+
return processForStatement(stmt, startId, blocks, edges, dialect);
|
|
8783
8833
|
}
|
|
8784
|
-
function processDoWhileStatement(stmt, startId, blocks, edges,
|
|
8834
|
+
function processDoWhileStatement(stmt, startId, blocks, edges, dialect) {
|
|
8785
8835
|
let currentId = startId;
|
|
8786
8836
|
const body2 = stmt.childForFieldName("body");
|
|
8787
8837
|
let bodyEntryId = currentId;
|
|
8788
8838
|
let bodyExitIds = [];
|
|
8789
8839
|
if (body2) {
|
|
8790
|
-
const bodyResult = processStatement(body2, currentId, blocks, edges,
|
|
8840
|
+
const bodyResult = processStatement(body2, currentId, blocks, edges, dialect);
|
|
8791
8841
|
currentId = bodyResult.nextId;
|
|
8792
8842
|
bodyEntryId = bodyResult.entryId;
|
|
8793
8843
|
bodyExitIds = bodyResult.exitIds;
|
|
@@ -8817,13 +8867,13 @@ function processDoWhileStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8817
8867
|
nextId: currentId
|
|
8818
8868
|
};
|
|
8819
8869
|
}
|
|
8820
|
-
function processTryStatement(stmt, startId, blocks, edges,
|
|
8870
|
+
function processTryStatement(stmt, startId, blocks, edges, dialect) {
|
|
8821
8871
|
let currentId = startId;
|
|
8822
8872
|
const exitIds = [];
|
|
8823
8873
|
const body2 = stmt.childForFieldName("body");
|
|
8824
8874
|
let tryEntryId = -1;
|
|
8825
8875
|
if (body2) {
|
|
8826
|
-
const bodyResult = processStatements(body2, currentId, blocks, edges,
|
|
8876
|
+
const bodyResult = processStatements(body2, currentId, blocks, edges, dialect);
|
|
8827
8877
|
currentId = bodyResult.nextId;
|
|
8828
8878
|
tryEntryId = bodyResult.entryId;
|
|
8829
8879
|
exitIds.push(...bodyResult.exitIds);
|
|
@@ -8833,7 +8883,7 @@ function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8833
8883
|
if (child?.type === "catch_clause") {
|
|
8834
8884
|
const catchBody = child.childForFieldName("body");
|
|
8835
8885
|
if (catchBody) {
|
|
8836
|
-
const catchResult = processStatements(catchBody, currentId, blocks, edges,
|
|
8886
|
+
const catchResult = processStatements(catchBody, currentId, blocks, edges, dialect);
|
|
8837
8887
|
currentId = catchResult.nextId;
|
|
8838
8888
|
if (tryEntryId !== -1) {
|
|
8839
8889
|
edges.push({
|
|
@@ -8846,9 +8896,18 @@ function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8846
8896
|
}
|
|
8847
8897
|
}
|
|
8848
8898
|
}
|
|
8849
|
-
|
|
8899
|
+
let finallyClause = stmt.childForFieldName("finally") ?? stmt.childForFieldName("finalizer");
|
|
8900
|
+
if (!finallyClause && dialect === "csharp") {
|
|
8901
|
+
for (let i2 = 0; i2 < stmt.childCount; i2++) {
|
|
8902
|
+
const child = stmt.child(i2);
|
|
8903
|
+
if (child?.type === "finally_clause") {
|
|
8904
|
+
finallyClause = lastBlockChild(child);
|
|
8905
|
+
break;
|
|
8906
|
+
}
|
|
8907
|
+
}
|
|
8908
|
+
}
|
|
8850
8909
|
if (finallyClause) {
|
|
8851
|
-
const finallyResult = processStatements(finallyClause, currentId, blocks, edges,
|
|
8910
|
+
const finallyResult = processStatements(finallyClause, currentId, blocks, edges, dialect);
|
|
8852
8911
|
currentId = finallyResult.nextId;
|
|
8853
8912
|
for (const exitId of exitIds) {
|
|
8854
8913
|
edges.push({
|
|
@@ -8869,7 +8928,7 @@ function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8869
8928
|
nextId: currentId
|
|
8870
8929
|
};
|
|
8871
8930
|
}
|
|
8872
|
-
function processSwitchStatement(stmt, startId, blocks, edges,
|
|
8931
|
+
function processSwitchStatement(stmt, startId, blocks, edges, dialect) {
|
|
8873
8932
|
let currentId = startId;
|
|
8874
8933
|
const switchBlock = {
|
|
8875
8934
|
id: currentId++,
|
|
@@ -8881,11 +8940,11 @@ function processSwitchStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8881
8940
|
const exitIds = [];
|
|
8882
8941
|
const body2 = stmt.childForFieldName("body");
|
|
8883
8942
|
if (body2) {
|
|
8884
|
-
const caseTypes =
|
|
8943
|
+
const caseTypes = dialect === "js" ? ["switch_case", "switch_default"] : dialect === "csharp" ? ["switch_section"] : ["switch_block_statement_group", "switch_rule"];
|
|
8885
8944
|
for (let i2 = 0; i2 < body2.childCount; i2++) {
|
|
8886
8945
|
const child = body2.child(i2);
|
|
8887
8946
|
if (child && caseTypes.includes(child.type)) {
|
|
8888
|
-
const caseResult = processStatements(child, currentId, blocks, edges,
|
|
8947
|
+
const caseResult = processStatements(child, currentId, blocks, edges, dialect);
|
|
8889
8948
|
currentId = caseResult.nextId;
|
|
8890
8949
|
if (caseResult.entryId !== -1) {
|
|
8891
8950
|
edges.push({
|
|
@@ -8915,7 +8974,7 @@ function buildBashCFG(tree, startId, cache) {
|
|
|
8915
8974
|
for (const func2 of functions) {
|
|
8916
8975
|
const body2 = func2.childForFieldName("body");
|
|
8917
8976
|
if (!body2) continue;
|
|
8918
|
-
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter,
|
|
8977
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "java");
|
|
8919
8978
|
allBlocks.push(...blocks);
|
|
8920
8979
|
allEdges.push(...edges);
|
|
8921
8980
|
blockIdCounter = nextId;
|
|
@@ -8938,7 +8997,7 @@ function buildBashCFG(tree, startId, cache) {
|
|
|
8938
8997
|
let lastExitIds = [];
|
|
8939
8998
|
let firstBlockId = -1;
|
|
8940
8999
|
for (const stmt of topLevelStatements) {
|
|
8941
|
-
const result = processStatement(stmt, blockIdCounter, allBlocks, allEdges,
|
|
9000
|
+
const result = processStatement(stmt, blockIdCounter, allBlocks, allEdges, "java");
|
|
8942
9001
|
blockIdCounter = result.nextId;
|
|
8943
9002
|
if (firstBlockId === -1) {
|
|
8944
9003
|
firstBlockId = result.entryId;
|
|
@@ -8982,7 +9041,8 @@ function isBashStatement(node) {
|
|
|
8982
9041
|
]);
|
|
8983
9042
|
return bashStatementTypes.has(node.type);
|
|
8984
9043
|
}
|
|
8985
|
-
function isStatement(node,
|
|
9044
|
+
function isStatement(node, dialect) {
|
|
9045
|
+
if (dialect === "csharp") return csharpStatementTypes.has(node.type);
|
|
8986
9046
|
const javaStatementTypes = /* @__PURE__ */ new Set([
|
|
8987
9047
|
"local_variable_declaration",
|
|
8988
9048
|
"expression_statement",
|
|
@@ -9026,8 +9086,30 @@ function isStatement(node, isJavaScript) {
|
|
|
9026
9086
|
"export_statement",
|
|
9027
9087
|
"import_statement"
|
|
9028
9088
|
]);
|
|
9029
|
-
return
|
|
9030
|
-
}
|
|
9089
|
+
return dialect === "js" ? jsStatementTypes.has(node.type) : javaStatementTypes.has(node.type);
|
|
9090
|
+
}
|
|
9091
|
+
var csharpStatementTypes = /* @__PURE__ */ new Set([
|
|
9092
|
+
"local_declaration_statement",
|
|
9093
|
+
"expression_statement",
|
|
9094
|
+
"if_statement",
|
|
9095
|
+
"for_statement",
|
|
9096
|
+
"foreach_statement",
|
|
9097
|
+
"while_statement",
|
|
9098
|
+
"do_statement",
|
|
9099
|
+
"try_statement",
|
|
9100
|
+
"switch_statement",
|
|
9101
|
+
"return_statement",
|
|
9102
|
+
"throw_statement",
|
|
9103
|
+
"break_statement",
|
|
9104
|
+
"continue_statement",
|
|
9105
|
+
"using_statement",
|
|
9106
|
+
"lock_statement",
|
|
9107
|
+
"checked_statement",
|
|
9108
|
+
"unsafe_statement",
|
|
9109
|
+
"yield_statement",
|
|
9110
|
+
"goto_statement",
|
|
9111
|
+
"block"
|
|
9112
|
+
]);
|
|
9031
9113
|
function buildGoCFG(tree, blockIdCounter, cache) {
|
|
9032
9114
|
const allBlocks = [];
|
|
9033
9115
|
const allEdges = [];
|
|
@@ -9038,7 +9120,7 @@ function buildGoCFG(tree, blockIdCounter, cache) {
|
|
|
9038
9120
|
for (const func2 of functions) {
|
|
9039
9121
|
const body2 = func2.childForFieldName("body");
|
|
9040
9122
|
if (!body2 || body2.type !== "block") continue;
|
|
9041
|
-
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter,
|
|
9123
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "java");
|
|
9042
9124
|
allBlocks.push(...blocks);
|
|
9043
9125
|
allEdges.push(...edges);
|
|
9044
9126
|
blockIdCounter = nextId;
|
|
@@ -13118,6 +13200,11 @@ var DEFAULT_SINKS = [
|
|
|
13118
13200
|
// ADO.NET `cmd.CommandText = "…" + x` — emitted as a synthetic call by
|
|
13119
13201
|
// extractCSharpCalls (property-assignment sink; the ctor-arg sink misses it).
|
|
13120
13202
|
{ method: "CommandText", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13203
|
+
// `DirectorySearcher.Filter = "(uid=" + x + ")"` — LDAP injection (cognium-ai#272).
|
|
13204
|
+
// Emitted as a synthetic call by extractCSharpCalls ONLY when the receiver
|
|
13205
|
+
// resolves to a DirectorySearcher, so this classless entry never over-matches
|
|
13206
|
+
// other `.Filter =` assignments (DataView/BindingSource/collection filters).
|
|
13207
|
+
{ method: "Filter", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13121
13208
|
// ADO.NET `cmd.Execute*()` — object-carried sink (cognium-dev#271). The taint
|
|
13122
13209
|
// rides the SqlCommand receiver (CommandText set from tainted data); the
|
|
13123
13210
|
// receiver is surfaced as arg[0] by extractCSharpCalls, and the command object
|
|
@@ -13134,6 +13221,9 @@ var DEFAULT_SINKS = [
|
|
|
13134
13221
|
// injectable — the second is the argv path where taint rides arg[1] (#276).
|
|
13135
13222
|
{ method: "Start", class: "Process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
|
|
13136
13223
|
{ method: "ProcessStartInfo", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
|
|
13224
|
+
// P/Invoke of libc `system(cmd)` — lowercase `system` in C# is virtually
|
|
13225
|
+
// always the imported shell entry point (cognium-ai#275 interop/IlPinvoke).
|
|
13226
|
+
{ method: "system", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13137
13227
|
// C# path traversal — System.IO file APIs (CWE-22). Distinctive method names.
|
|
13138
13228
|
{ method: "ReadAllText", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13139
13229
|
{ method: "ReadAllBytes", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13162,6 +13252,9 @@ var DEFAULT_SINKS = [
|
|
|
13162
13252
|
{ method: "Delete", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13163
13253
|
{ method: "GetFiles", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13164
13254
|
{ method: "EnumerateFiles", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13255
|
+
// ASP.NET `ControllerBase.PhysicalFile(path, contentType)` serves a file from
|
|
13256
|
+
// an absolute disk path — attacker-controllable path is CWE-22 (cognium-ai#326/#275).
|
|
13257
|
+
{ method: "PhysicalFile", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13165
13258
|
// C# SSRF — HttpClient / WebClient / WebRequest (CWE-918).
|
|
13166
13259
|
{ method: "GetAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13167
13260
|
{ method: "PostAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13200,6 +13293,14 @@ var DEFAULT_SINKS = [
|
|
|
13200
13293
|
{ method: "Raw", class: "Html", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13201
13294
|
{ method: "Write", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13202
13295
|
{ method: "HtmlString", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13296
|
+
// Blazor `new MarkupString(x)` renders its argument as raw HTML (the framework's
|
|
13297
|
+
// documented "trusted markup" escape hatch) — attacker-controlled input is XSS. (ca#275)
|
|
13298
|
+
{ method: "MarkupString", class: "constructor", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13299
|
+
// `String.Format(fmt, …)` with an attacker-controlled FORMAT string — CWE-134.
|
|
13300
|
+
// Taint-gated on arg 0 (the format), so ordinary `String.Format("...", user)` where
|
|
13301
|
+
// only an argument is tainted never fires. .NET composite formatting can't corrupt
|
|
13302
|
+
// memory, so medium (FormatException DoS / unintended arg access), unlike C printf.
|
|
13303
|
+
{ method: "Format", class: "String", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["csharp"] },
|
|
13203
13304
|
// C# LDAP injection — System.DirectoryServices (CWE-90). The user-built
|
|
13204
13305
|
// filter is the constructor argument.
|
|
13205
13306
|
{ method: "DirectorySearcher", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13238,6 +13339,10 @@ var DEFAULT_SINKS = [
|
|
|
13238
13339
|
{ method: "SelectSingleNode", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13239
13340
|
{ method: "SelectNodes", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13240
13341
|
{ method: "Compile", class: "XPathExpression", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13342
|
+
// XPathNavigator.Select/Evaluate — class-scoped (both names collide with LINQ
|
|
13343
|
+
// `.Select`/`.Evaluate`, so they must resolve to an XPathNavigator receiver).
|
|
13344
|
+
{ method: "Select", class: "XPathNavigator", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13345
|
+
{ method: "Evaluate", class: "XPathNavigator", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13241
13346
|
// C# XXE — untrusted XML into a parser without DTD hardening (CWE-611).
|
|
13242
13347
|
{ method: "LoadXml", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13243
13348
|
{ method: "Load", class: "XmlDocument", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -14564,33 +14669,56 @@ function isSafeJSChildProcessCall(call, pattern, language) {
|
|
|
14564
14669
|
if (SHELL_PROGRAMS.has(program)) return false;
|
|
14565
14670
|
return true;
|
|
14566
14671
|
}
|
|
14567
|
-
|
|
14672
|
+
var CSHARP_SHELL_PROGRAMS = /* @__PURE__ */ new Set([
|
|
14673
|
+
"sh",
|
|
14674
|
+
"bash",
|
|
14675
|
+
"zsh",
|
|
14676
|
+
"dash",
|
|
14677
|
+
"ash",
|
|
14678
|
+
"ksh",
|
|
14679
|
+
"cmd",
|
|
14680
|
+
"powershell",
|
|
14681
|
+
"pwsh"
|
|
14682
|
+
]);
|
|
14683
|
+
function isConstNonShellExe(raw) {
|
|
14684
|
+
if (!raw) return false;
|
|
14685
|
+
const t = raw.trim();
|
|
14686
|
+
if (!/^@?"[^"]*"$/.test(t)) return false;
|
|
14687
|
+
const program = (t.replace(/^@?"|"$/g, "").split(/[\\/]/).pop() ?? "").toLowerCase().replace(/\.exe$/, "");
|
|
14688
|
+
return !CSHARP_SHELL_PROGRAMS.has(program);
|
|
14689
|
+
}
|
|
14690
|
+
var PSI_CTOR_EXE_RE = /\bnew\s+ProcessStartInfo\s*(?:<[^>]*>)?\s*\(\s*(@?"[^"]*")/;
|
|
14691
|
+
function processStartInfoExe(expr, sourceLines) {
|
|
14692
|
+
const inline = PSI_CTOR_EXE_RE.exec(expr);
|
|
14693
|
+
if (inline) return inline[1];
|
|
14694
|
+
if (sourceLines && /^[A-Za-z_]\w*$/.test(expr.trim())) {
|
|
14695
|
+
const varName = expr.trim();
|
|
14696
|
+
const assignRe = new RegExp(
|
|
14697
|
+
`\\b${varName}\\s*=\\s*new\\s+ProcessStartInfo\\s*(?:<[^>]*>)?\\s*\\(\\s*(@?"[^"]*")`
|
|
14698
|
+
);
|
|
14699
|
+
for (const line of sourceLines) {
|
|
14700
|
+
const m = assignRe.exec(line);
|
|
14701
|
+
if (m) return m[1];
|
|
14702
|
+
}
|
|
14703
|
+
}
|
|
14704
|
+
return null;
|
|
14705
|
+
}
|
|
14706
|
+
function isSafeCSharpProcessStartCall(call, pattern, language, sourceLines) {
|
|
14568
14707
|
if (language !== "csharp") return false;
|
|
14569
14708
|
if (pattern.type !== "command_injection") return false;
|
|
14570
|
-
|
|
14571
|
-
if (
|
|
14572
|
-
|
|
14573
|
-
|
|
14574
|
-
|
|
14575
|
-
|
|
14576
|
-
raw = String(fileArg.literal).trim();
|
|
14577
|
-
} else {
|
|
14578
|
-
raw = (fileArg.expression ?? "").trim();
|
|
14709
|
+
const method = call.method_name;
|
|
14710
|
+
if (method !== "Start" && method !== "ProcessStartInfo") return false;
|
|
14711
|
+
if (call.arguments.length >= 2) {
|
|
14712
|
+
const fileArg = call.arguments.find((a) => a.position === 0);
|
|
14713
|
+
const raw = fileArg?.literal != null ? String(fileArg.literal) : fileArg?.expression;
|
|
14714
|
+
return isConstNonShellExe(raw);
|
|
14579
14715
|
}
|
|
14580
|
-
if (
|
|
14581
|
-
|
|
14582
|
-
|
|
14583
|
-
|
|
14584
|
-
|
|
14585
|
-
|
|
14586
|
-
"dash",
|
|
14587
|
-
"ash",
|
|
14588
|
-
"ksh",
|
|
14589
|
-
"cmd",
|
|
14590
|
-
"powershell",
|
|
14591
|
-
"pwsh"
|
|
14592
|
-
]);
|
|
14593
|
-
return !SHELL_PROGRAMS.has(program);
|
|
14716
|
+
if (method === "Start" && call.arguments.length === 1) {
|
|
14717
|
+
const arg0 = call.arguments.find((a) => a.position === 0);
|
|
14718
|
+
const expr = (arg0?.expression ?? "").trim();
|
|
14719
|
+
return isConstNonShellExe(processStartInfoExe(expr, sourceLines));
|
|
14720
|
+
}
|
|
14721
|
+
return false;
|
|
14594
14722
|
}
|
|
14595
14723
|
function isSafeRustCommandCall(call, pattern, language) {
|
|
14596
14724
|
if (language !== "rust") return false;
|
|
@@ -14847,7 +14975,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
14847
14975
|
if (isSafeJSChildProcessCall(call, pattern, language)) {
|
|
14848
14976
|
continue;
|
|
14849
14977
|
}
|
|
14850
|
-
if (isSafeCSharpProcessStartCall(call, pattern, language)) {
|
|
14978
|
+
if (isSafeCSharpProcessStartCall(call, pattern, language, sourceLines)) {
|
|
14851
14979
|
continue;
|
|
14852
14980
|
}
|
|
14853
14981
|
if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at, types)) {
|
|
@@ -224,15 +224,29 @@ function extractCSharpCalls(tree, cache) {
|
|
|
224
224
|
if (left?.type !== 'member_access_expression')
|
|
225
225
|
continue;
|
|
226
226
|
const nameNode = left.childForFieldName('name');
|
|
227
|
-
if (!nameNode
|
|
227
|
+
if (!nameNode)
|
|
228
228
|
continue;
|
|
229
|
+
const propName = getNodeText(nameNode);
|
|
230
|
+
const exprNode = left.childForFieldName('expression');
|
|
231
|
+
// `CommandText` is distinctive to ADO.NET commands, so it fires classless.
|
|
232
|
+
// `Filter` is a common property name, so it is class-gated to a resolved
|
|
233
|
+
// `DirectorySearcher` receiver (cognium-ai#272 LDAP injection) — this keeps
|
|
234
|
+
// the classless registry sink from over-matching unrelated `.Filter =`.
|
|
235
|
+
if (propName === 'Filter') {
|
|
236
|
+
const recv = exprNode ? getNodeText(exprNode) : null;
|
|
237
|
+
const recvType = recv ? typeMap.get(recv) : undefined;
|
|
238
|
+
if (recvType !== 'DirectorySearcher')
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
else if (propName !== 'CommandText') {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
229
244
|
const right = asn.childForFieldName('right');
|
|
230
245
|
if (!right)
|
|
231
246
|
continue;
|
|
232
247
|
const rhsText = getNodeText(right);
|
|
233
|
-
const exprNode = left.childForFieldName('expression');
|
|
234
248
|
calls.push({
|
|
235
|
-
method_name:
|
|
249
|
+
method_name: propName,
|
|
236
250
|
receiver: exprNode ? getNodeText(exprNode) : null,
|
|
237
251
|
receiver_type: null,
|
|
238
252
|
receiver_type_fqn: null,
|