11/**
22 * Provides predicates for finding calls that transitively make a call to a
3- * known-vulnerable method in CIL (C# IL) binaries.
3+ * known-vulnerable method in CIL (C# IL) and JVM binaries.
4+ *
5+ * Performance optimizations applied:
6+ * - Materialized intermediate predicates with pragma[nomagic] to prevent
7+ * expensive cross-product computations
8+ * - Direct string matching on getExternalName() instead of SSA traversal
9+ * where possible
10+ * - Separated external reference matching from static target matching
11+ * for better join ordering
412 */
513
614private import binary
@@ -16,19 +24,98 @@ extensible predicate vulnerableCallModel(
1624 string namespace , string className , string methodName , string id
1725) ;
1826
27+ /*
28+ * ============================================================================
29+ * Materialized helper predicates for performance
30+ * ============================================================================
31+ */
32+
33+ /**
34+ * Materialized: builds the fully qualified name string for each vulnerable method
35+ * in the model. This enables efficient string comparison.
36+ */
37+ pragma [ nomagic]
38+ private predicate vulnerableMethodFqn ( string fqn , string id ) {
39+ exists ( string namespace , string className , string methodName |
40+ vulnerableCallModel ( namespace , className , methodName , id ) and
41+ fqn = namespace + "." + className + "." + methodName
42+ )
43+ }
44+
45+ /**
46+ * Materialized: gets ExternalRefInstructions that directly reference vulnerable methods.
47+ * Uses direct string comparison against getExternalName() to avoid expensive
48+ * hasFullyQualifiedName() regex parsing.
49+ */
50+ pragma [ nomagic]
51+ private predicate isVulnerableExternalRef ( ExternalRefInstruction eri , string id ) {
52+ exists ( string fqn |
53+ vulnerableMethodFqn ( fqn , id ) and
54+ eri .getExternalName ( ) = fqn
55+ )
56+ }
57+
58+ /**
59+ * Materialized: gets Functions that are marked as vulnerable in the model.
60+ * Used for matching calls with static targets.
61+ */
62+ pragma [ nomagic]
63+ private predicate isVulnerableFunction ( Function f , string id ) {
64+ exists ( string namespace , string className , string methodName |
65+ vulnerableCallModel ( namespace , className , methodName , id ) and
66+ f .hasFullyQualifiedName ( namespace , className , methodName )
67+ )
68+ }
69+
70+ /**
71+ * Materialized: maps ExternalRefInstructions to their enclosing functions.
72+ * This avoids repeated getEnclosingFunction() lookups in the recursive predicate.
73+ */
74+ pragma [ nomagic]
75+ private predicate externalRefInFunction ( ExternalRefInstruction eri , Function f ) {
76+ f = eri .getEnclosingFunction ( )
77+ }
78+
79+ /**
80+ * Materialized: maps CallInstructions to their enclosing functions.
81+ */
82+ pragma [ nomagic]
83+ private predicate callInFunction ( CallInstruction call , Function f ) {
84+ f = call .getEnclosingFunction ( )
85+ }
86+
87+ /**
88+ * Materialized: maps ExternalRefInstructions to their external names.
89+ */
90+ pragma [ nomagic]
91+ private predicate externalRefName ( ExternalRefInstruction eri , string name ) {
92+ name = eri .getExternalName ( )
93+ }
94+
95+ /*
96+ * ============================================================================
97+ * Direct vulnerable call detection
98+ * ============================================================================
99+ */
100+
19101/**
20102 * A method call that has been marked as vulnerable by a model.
103+ *
104+ * This class matches calls where either:
105+ * 1. The static target is a vulnerable function (internal calls)
106+ * 2. The external reference points to a vulnerable method (external calls)
21107 */
22108class VulnerableMethodCall extends CallInstruction {
23109 string vulnerabilityId ;
24110
25111 VulnerableMethodCall ( ) {
26- exists ( string namespace , string className , string methodName |
27- vulnerableCallModel ( namespace , className , methodName , vulnerabilityId ) and
28- this .getTargetOperand ( )
29- .getAnyDef ( )
30- .( ExternalRefInstruction )
31- .hasFullyQualifiedName ( namespace , className , methodName )
112+ // Match via static target (more efficient, no SSA traversal)
113+ isVulnerableFunction ( this .getStaticTarget ( ) , vulnerabilityId )
114+ or
115+ // Match via external reference for external calls
116+ exists ( ExternalRefInstruction eri |
117+ isVulnerableExternalRef ( eri , vulnerabilityId ) and
118+ this .getTargetOperand ( ) .getAnyDef ( ) = eri
32119 )
33120 }
34121
@@ -44,21 +131,29 @@ VulnerableMethodCall getAVulnerableCallFromModel(string id) { result.getVulnerab
44131/**
45132 * Gets a method that directly contains a vulnerable call.
46133 */
134+ pragma [ nomagic]
47135Function getADirectlyVulnerableMethod ( string id ) {
48136 result = getAVulnerableCallFromModel ( id ) .getEnclosingFunction ( )
49137}
50138
139+ /*
140+ * ============================================================================
141+ * C# Iterator/Async State Machine handling
142+ * ============================================================================
143+ */
144+
51145/**
52146 * Holds if `stub` is an iterator/async stub method and `stateMachine` is its
53147 * corresponding state machine implementation (the MoveNext method).
54- *
148+ *
55149 * Iterator/async methods in C# are compiled to:
56150 * 1. A stub method that creates a state machine object
57151 * 2. A nested class with a MoveNext method containing the actual implementation
58- *
59- * The pattern is: method `Foo` in class `Bar` creates `Bar.<Foo>d__N`
152+ *
153+ * The pattern is: method `Foo` in class `Bar` creates `Bar.<Foo>d__N`
60154 * and the impl is in `Bar.<Foo>d__N.MoveNext`
61155 */
156+ pragma [ nomagic]
62157private predicate isStateMachineImplementation ( Function stub , Function stateMachine ) {
63158 exists ( string stubName , Type stubType , string stateMachineTypeName |
64159 stubName = stub .getName ( ) and
@@ -67,7 +162,7 @@ private predicate isStateMachineImplementation(Function stub, Function stateMach
67162 // The state machine type is nested in the same type as the stub
68163 // and named <MethodName>d__N
69164 stateMachineTypeName = stateMachine .getDeclaringType ( ) .getName ( ) and
70- stateMachineTypeName .matches ( "<" + stubName + ">d__%" ) and
165+ stateMachineTypeName .matches ( "<" + stubName + ">d__%" ) and
71166 // The state machine's declaring type's namespace should be the stub's type full name
72167 stateMachine .getDeclaringType ( ) .getNamespace ( ) = stubType .getFullName ( )
73168 )
@@ -80,32 +175,81 @@ Function getStateMachineImplementation(Function stub) {
80175 isStateMachineImplementation ( stub , result )
81176}
82177
178+ /*
179+ * ============================================================================
180+ * Optimized call graph edges for transitive closure
181+ * ============================================================================
182+ */
183+
184+ /**
185+ * Materialized: holds if function `caller` contains a call to function `callee`
186+ * via a static target (direct call, no SSA traversal needed).
187+ */
188+ pragma [ nomagic]
189+ private predicate callsViaStaticTarget ( Function caller , Function callee ) {
190+ exists ( CallInstruction call |
191+ callInFunction ( call , caller ) and
192+ callee = call .getStaticTarget ( )
193+ )
194+ }
195+
196+ /**
197+ * Materialized: holds if function `caller` contains a call to a function
198+ * with fully qualified name `calleeFqn` via an external reference.
199+ */
200+ pragma [ nomagic]
201+ private predicate callsViaExternalRef ( Function caller , string calleeFqn ) {
202+ exists ( CallInstruction call , ExternalRefInstruction eri |
203+ callInFunction ( call , caller ) and
204+ call .getTargetOperand ( ) .getAnyDef ( ) = eri and
205+ externalRefName ( eri , calleeFqn )
206+ )
207+ }
208+
209+ /**
210+ * Materialized: maps functions to their fully qualified names for join efficiency.
211+ */
212+ pragma [ nomagic]
213+ private predicate functionFqn ( Function f , string fqn ) {
214+ fqn = f .getFullyQualifiedName ( )
215+ }
216+
217+ /*
218+ * ============================================================================
219+ * Transitive vulnerable method detection
220+ * ============================================================================
221+ */
222+
83223/**
84224 * Gets a method that transitively calls a vulnerable method.
85225 * This computes the transitive closure of the call graph.
86- *
226+ *
87227 * Also handles iterator/async methods by linking stub methods to their
88228 * state machine implementations.
229+ *
230+ * Performance notes:
231+ * - Uses materialized helper predicates to avoid repeated expensive operations
232+ * - Separates static target calls from external reference calls for better join ordering
233+ * - The recursion is bounded by the call graph depth
89234 */
90235Function getAVulnerableMethod ( string id ) {
91- // Direct call to vulnerable method
236+ // Base case: direct call to vulnerable method
92237 result = getADirectlyVulnerableMethod ( id )
93238 or
94- // Transitive: method calls another method that is vulnerable (via ExternalRef for external calls)
95- exists ( CallInstruction call , Function callee |
96- call .getEnclosingFunction ( ) = result and
239+ // Transitive case 1: method calls a vulnerable method via static target
240+ exists ( Function callee |
97241 callee = getAVulnerableMethod ( id ) and
98- call .getTargetOperand ( ) .getAnyDef ( ) .( ExternalRefInstruction ) .getFullyQualifiedName ( ) =
99- callee .getFullyQualifiedName ( )
242+ callsViaStaticTarget ( result , callee )
100243 )
101244 or
102- // Transitive: method calls another method that is vulnerable (via static target for direct calls)
103- exists ( CallInstruction call |
104- call .getEnclosingFunction ( ) = result and
105- call .getStaticTarget ( ) = getAVulnerableMethod ( id )
245+ // Transitive case 2: method calls a vulnerable method via external reference
246+ exists ( Function callee , string calleeFqn |
247+ callee = getAVulnerableMethod ( id ) and
248+ functionFqn ( callee , calleeFqn ) and
249+ callsViaExternalRef ( result , calleeFqn )
106250 )
107251 or
108- // Iterator/async: if a state machine's MoveNext is vulnerable,
252+ // Iterator/async: if a state machine's MoveNext is vulnerable,
109253 // the stub method that creates it is also vulnerable
110254 exists ( Function stateMachine |
111255 stateMachine = getAVulnerableMethod ( id ) and
@@ -116,6 +260,7 @@ Function getAVulnerableMethod(string id) {
116260/**
117261 * Gets a public method that transitively calls a vulnerable method.
118262 */
263+ pragma [ nomagic]
119264Function getAPublicVulnerableMethod ( string id ) {
120265 result = getAVulnerableMethod ( id ) and
121266 result .isPublic ( )
0 commit comments