Repository navigation
Fix: Resolve C# alias variable detection for cipher properties - #542
sachin9058 wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Moderate issues remain in nested-scope propagation, chained alias resolution, and initializer validation.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Fixes C# crypto property detection when objects are accessed through alias variables.
Changes:
- Stores and tracks local C# aliases.
- Resolves aliases during variable matching.
- Adds AES alias regression coverage and a test fixture.
File summaries
| File | Summary | Findings |
|---|---|---|
engine/src/main/java/com/ibm/engine/language/csharp/tree/CSharpBlockTree.java |
Stores block-level aliases. | None. |
engine/src/main/java/com/ibm/engine/language/csharp/CSharpTreeConverter.java |
Collects local alias assignments. | Moderate (2 votes): alias recording must require the complete initializer to be a single identifier; this applies at lines 141 and 161. |
engine/src/main/java/com/ibm/engine/language/csharp/CSharpDetectionEngine.java |
Resolves aliases during detection matching. | Moderate (1 vote): propagate enclosing alias scopes into nested blocks. Moderate (3 votes): resolve multi-hop alias chains transitively with cycle protection. |
csharp/src/test/java/com/ibm/plugin/rules/detection/dotnet/DotNetAESAliasTest.java |
Tests AES detection through aliases. | None. |
csharp/src/test/files/rules/detection/dotnet/ProbeCSharpAliasTestFile.cs |
Provides the alias test fixture. | None. |
Review details
Suppressed comments (2)
engine/src/main/java/com/ibm/engine/language/csharp/CSharpDetectionEngine.java:101
- Because the converter creates a separate
CSharpBlockTreefor every nested block and the sensor analyzes each block independently, this map contains only aliases declared in the current block. For example,var aes = Aes.Create(); var alias = aes; if (ok) { alias.Mode = CipherMode.CBC; }is analyzed with an empty alias map for the setter, so the property is still lost. Propagate the enclosing alias scope to nested blocks (and add a regression test for this case).
Map<String, String> aliases = blockTree.getAliases();
for (CSharpTree statement : blockTree.getStatements()) {
processStatement(traceSymbol, statement, aliases);
engine/src/main/java/com/ibm/engine/language/csharp/CSharpTreeConverter.java:168
- The alias map is populated from the declaration but is never updated when the local is reassigned. For example, after
var alias = first; alias = second; alias.Mode = ...;, this map still resolvesaliastofirst, so the setting is attached to the wrong detection (or omitted fromsecond). Track simple identifier reassignments or invalidate the alias before resolving subsequent uses.
String aliasName = ctx.identifier().getText();
String originalName =
((CSharpParser.SimpleNameExpressionContext)
primaryExpr.primary_expression_start())
.identifier()
.getText();
if (!aliasName.equals(originalName)) {
aliases.put(aliasName, originalName);
- Files reviewed: 5/5 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| CSharpParser.Primary_expressionContext primaryExpr = | ||
| findPrimaryExpression(initExpr); | ||
| if (primaryExpr != null | ||
| && primaryExpr.primary_expression_start() | ||
| instanceof CSharpParser.SimpleNameExpressionContext) { | ||
| // Ensure it's truly a simple variable reference, not a method call | ||
| // or member access like Aes.Create() | ||
| boolean hasComplexChildren = false; | ||
| List<ParseTree> children = primaryExpr.children; | ||
| if (children != null) { | ||
| for (int i = 1; i < children.size(); i++) { | ||
| if (children.get(i) instanceof CSharpParser.Member_accessContext | ||
| || children.get(i) | ||
| instanceof CSharpParser.Method_invocationContext) { | ||
| hasComplexChildren = true; | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| if (!hasComplexChildren) { |
2720e33 to
1d07ae2
Compare
|
@san-zrl @n1ckl0sk0rtge Hey can u please take a look on this PR ? |
|
Passing this on to @fynnth. Could you review? Thanks. |
1d07ae2 to
9ee1c75
Compare
|
Hi @sachin9058 Nice fix for the direct-alias case, Thank you! Repro: Mode=CBC gets attached to first (AES) instead of second (TripleDES), which is a misattribution, not just a miss. That's a regression in kind relative to pre-PR behaviour: before this change the setter was simply dropped for both objects (data loss); now it's silently attached to the wrong one (wrong data) It would be great if you could handle that as well, otherwise i will include it my next Major pr and we can merge this for now as it is :) |
9ee1c75 to
64ad9c3
Compare
|
Hi @fynnth Got it thanks for flagging this. The reassignment case is now handled:
|
|
HI @sachin9058 , var first = Aes.Create(); --> Could you have another look at your fix? |
When a C# crypto object is used through a second variable (e.g. `var alias = aes;` followed by `alias.Mode = CipherMode.CBC;`), the engine failed to link the alias back to the original detection, losing the cipher settings. Changes: - Add alias tracking to CSharpBlockTree: stores a map of alias names → original variable names - Track alias assignments in CSharpTreeConverter.StatementCollector: when `var X = Y;` is encountered where Y is a simple variable reference (not a method call or type name), record the mapping - Resolve aliases in CSharpDetectionEngine: when checking isInvocationOnVariable and isInitForVariable, resolve the variable name through the alias map before comparing Fixes cbomkit#511: AES-256-CBC now correctly detected when using aliased variables. Signed-off-by: Sachin Kumar <sachinkumar905846@gmail.com>
The alias map was built per block and applied after the fact, so 'alias = second;' left stale mappings and the engine attached setters to both objects. Resolve the receiver while converting each statement instead, and drop a mapping whenever its variable is reassigned. The engine no longer needs alias state. Signed-off-by: Sachin Kumar <sachinkumar905846@gmail.com>
4ad7940 to
76d15d0
Compare
|
Hi @fynnth thanks for catching this !!! The issue was that aliases were being tracked per block and applied afterward. Because of this, when an alias was reassigned (like alias = second;), the old mapping wasn't removed, so the setter ended up being applied to both objects. I've updated the logic to resolve the receiver as each statement is processed and remove the old alias mapping whenever a variable is reassigned. I also removed the alias state from CSharpDetectionEngine and CSharpBlockTree. I've updated the reassignment test to use your exact repro. It now verifies that only TripleDES gets Mode = CBC and AES remains unchanged. All 39 C# tests are passing. One limitation is that reassignments inside if/else branches are still handled in source order rather than based on control flow. We can look into that separately if needed. |
When a crypto object is used through an alias variable (e.g., var alias = aes; alias.Mode = CipherMode.CBC;), the engine lost the property settings because it compared variable names directly — alias never matched the tracked symbol aes.
Now the engine tracks alias assignments during tree conversion and resolves them when checking variable invocations.
Changes:
Tests: Added DotNetAESAliasTest + ProbeCSharpAliasTestFile.cs. All 38 C# tests pass.
FIXES: #511