Skip to content

Commit 02fd414

Browse files
committed
Optimize vulnerable call detection and summarization
Applied performance optimizations to vulnerable call detection by materializing intermediate predicates, separating static target and external reference matching, and using direct string comparisons. Updated summarization logic to handle both external and static target calls. Improved efficiency and scalability for analyzing CIL and JVM binaries.
1 parent c2f683b commit 02fd414

2 files changed

Lines changed: 179 additions & 25 deletions

File tree

binary/ql/src/VulnerableCalls/VulnerableCalls.qll

Lines changed: 168 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
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

614
private 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
*/
22108
class 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]
47135
Function 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]
62157
private 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
*/
90235
Function 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]
119264
Function getAPublicVulnerableMethod(string id) {
120265
result = getAVulnerableMethod(id) and
121266
result.isPublic()

binary/ql/src/VulnerableCalls/VulnerableCallsSummarize.ql

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ query predicate publicVulnerableCallModel(
3030

3131
/**
3232
* Lists the direct vulnerable call sites with their enclosing method context.
33+
* Handles both external reference calls and static target calls.
3334
*/
3435
query predicate vulnerableCallLocations(
3536
VulnerableMethodCall call,
@@ -40,6 +41,14 @@ query predicate vulnerableCallLocations(
4041
string id
4142
) {
4243
call.getVulnerabilityId() = id and
43-
call.getEnclosingFunction().hasFullyQualifiedName(callerNamespace, callerClassName, callerMethodName) and
44-
targetFqn = call.getTargetOperand().getAnyDef().(ExternalRefInstruction).getFullyQualifiedName()
44+
call.getEnclosingFunction()
45+
.hasFullyQualifiedName(callerNamespace, callerClassName, callerMethodName) and
46+
(
47+
// External call via ExternalRefInstruction
48+
targetFqn = call.getTargetOperand().getAnyDef().(ExternalRefInstruction).getFullyQualifiedName()
49+
or
50+
// Internal call via static target
51+
not exists(call.getTargetOperand().getAnyDef().(ExternalRefInstruction)) and
52+
targetFqn = call.getStaticTarget().getFullyQualifiedName()
53+
)
4554
}

0 commit comments

Comments
 (0)