Skip to content

Commit e2db11b

Browse files
committed
Performance improvements in XSS.qll
Various performance improvements to make sure that we never join methods and calls (or variables and accesses) on only name (or file), but always perform a multi-join on both values.
1 parent ad5c1f9 commit e2db11b

File tree

1 file changed

+96
-63
lines changed

1 file changed

+96
-63
lines changed

ql/lib/codeql/ruby/security/XSS.qll

Lines changed: 96 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -122,96 +122,129 @@ private module Shared {
122122
AssignExpr getAnAssignExpr() { result.getLeftOperand() = this.getExpr() }
123123
}
124124

125-
private predicate isFlowFromLocals(DataFlow::Node node1, DataFlow::Node node2) {
126-
// node1 is a `locals` argument to a render call...
127-
exists(RenderCall call, Pair kvPair, string hashKey |
125+
/**
126+
* Holds if `call` is a method call in ERB file `erb`, targeting a method
127+
* named `name`.
128+
*/
129+
pragma[noinline]
130+
private predicate isMethodCall(MethodCall call, string name, ErbFile erb) {
131+
name = call.getMethodName() and
132+
erb = call.getLocation().getFile()
133+
}
134+
135+
/**
136+
* Holds if some render call passes `value` for `hashKey` in the `locals`
137+
* argument, in ERB file `erb`.
138+
*/
139+
pragma[noinline]
140+
private predicate renderCallLocals(string hashKey, Expr value, ErbFile erb) {
141+
exists(RenderCall call, Pair kvPair |
128142
call.getLocals().getAKeyValuePair() = kvPair and
129-
kvPair.getValue() = node1.asExpr().getExpr() and
130-
kvPair.getKey().(StringlikeLiteral).getValueText() = hashKey and
131-
// `node2` appears in the rendered template file
132-
call.getTemplateFile() = node2.getLocation().getFile() and
133-
(
134-
// node2 is an element reference against `local_assigns`
135-
exists(
136-
CfgNodes::ExprNodes::ElementReferenceCfgNode refNode, DataFlow::Node argNode,
137-
CfgNodes::ExprNodes::StringlikeLiteralCfgNode strNode
138-
|
139-
refNode = node2.asExpr() and
140-
argNode.asExpr() = refNode.getArgument(0) and
141-
refNode.getReceiver().getExpr().(MethodCall).getMethodName() = "local_assigns" and
142-
argNode.getALocalSource() = DataFlow::exprNode(strNode) and
143-
strNode.getExpr().getValueText() = hashKey
144-
)
145-
or
146-
// ...node2 is a "method call" to a "method" with `hashKey` as its name
147-
// TODO: This may be a variable read in reality that we interpret as a method call
148-
exists(MethodCall varAcc |
149-
varAcc = node2.asExpr().(CfgNodes::ExprNodes::MethodCallCfgNode).getExpr() and
150-
varAcc.getMethodName() = hashKey
151-
)
152-
)
143+
kvPair.getValue() = value and
144+
kvPair.getKey().getValueText() = hashKey and
145+
call.getTemplateFile() = erb
146+
)
147+
}
148+
149+
pragma[noinline]
150+
private predicate isFlowFromLocals0(
151+
CfgNodes::ExprNodes::ElementReferenceCfgNode refNode, string hashKey, ErbFile erb
152+
) {
153+
exists(DataFlow::Node argNode, CfgNodes::ExprNodes::StringlikeLiteralCfgNode strNode |
154+
argNode.asExpr() = refNode.getArgument(0) and
155+
refNode.getReceiver().getExpr().(MethodCall).getMethodName() = "local_assigns" and
156+
argNode.getALocalSource() = DataFlow::exprNode(strNode) and
157+
strNode.getExpr().getValueText() = hashKey and
158+
erb = refNode.getFile()
159+
)
160+
}
161+
162+
private predicate isFlowFromLocals(DataFlow::Node node1, DataFlow::Node node2) {
163+
exists(string hashKey, ErbFile erb |
164+
// node1 is a `locals` argument to a render call...
165+
renderCallLocals(hashKey, node1.asExpr().getExpr(), erb)
166+
|
167+
// node2 is an element reference against `local_assigns`
168+
isFlowFromLocals0(node2.asExpr(), hashKey, erb)
169+
or
170+
// ...node2 is a "method call" to a "method" with `hashKey` as its name
171+
// TODO: This may be a variable read in reality that we interpret as a method call
172+
isMethodCall(node2.asExpr().getExpr(), hashKey, erb)
153173
)
154174
}
155175

176+
/**
177+
* Holds if `action` contains an assignment of `value` to an instance
178+
* variable named `name`, in ERB file `erb`.
179+
*/
180+
pragma[noinline]
181+
private predicate actionAssigns(
182+
ActionControllerActionMethod action, string name, Expr value, ErbFile erb
183+
) {
184+
exists(AssignExpr ae, FinalInstanceVarWrite controllerVarWrite |
185+
action.getDefaultTemplateFile() = erb and
186+
ae.getParent+() = action and
187+
ae = controllerVarWrite.getAnAssignExpr() and
188+
name = controllerVarWrite.getVariable().getName() and
189+
value = ae.getRightOperand()
190+
)
191+
}
192+
193+
pragma[noinline]
194+
private predicate isVariableReadAccess(VariableReadAccess viewVarRead, string name, ErbFile erb) {
195+
erb = viewVarRead.getLocation().getFile() and
196+
viewVarRead.getVariable().getName() = name
197+
}
198+
156199
private predicate isFlowFromControllerInstanceVariable(DataFlow::Node node1, DataFlow::Node node2) {
157200
// instance variables in the controller
158-
exists(
159-
ActionControllerActionMethod action, VariableReadAccess viewVarRead, AssignExpr ae,
160-
FinalInstanceVarWrite controllerVarWrite
161-
|
162-
viewVarRead = node2.asExpr().(CfgNodes::ExprNodes::VariableReadAccessCfgNode).getExpr() and
163-
action.getDefaultTemplateFile() = viewVarRead.getLocation().getFile() and
201+
exists(ActionControllerActionMethod action, string name, ErbFile template |
164202
// match read to write on variable name
165-
viewVarRead.getVariable().getName() = controllerVarWrite.getVariable().getName() and
203+
actionAssigns(action, name, node1.asExpr().getExpr(), template) and
166204
// propagate taint from assignment RHS expr to variable read access in view
167-
ae = controllerVarWrite.getAnAssignExpr() and
168-
node1.asExpr().getExpr() = ae.getRightOperand() and
169-
ae.getParent+() = action
205+
isVariableReadAccess(node2.asExpr().getExpr(), name, template)
170206
)
171207
}
172208

209+
/**
210+
* Holds if `helperMethod` is a helper method named `name` that is associated
211+
* with ERB file `erb`.
212+
*/
213+
pragma[noinline]
214+
private predicate isHelperMethod(
215+
ActionControllerHelperMethod helperMethod, string name, ErbFile erb
216+
) {
217+
helperMethod.getName() = name and
218+
helperMethod.getControllerClass() = getAssociatedControllerClass(erb)
219+
}
220+
173221
private predicate isFlowIntoHelperMethod(DataFlow::Node node1, DataFlow::Node node2) {
174222
// flow from template into controller helper method
175223
exists(
176-
ErbFile template, ActionControllerHelperMethod helperMethod,
224+
ErbFile template, ActionControllerHelperMethod helperMethod, string name,
177225
CfgNodes::ExprNodes::MethodCallCfgNode helperMethodCall, int argIdx
178226
|
179-
template = node1.getLocation().getFile() and
180-
helperMethod.getName() = helperMethodCall.getExpr().getMethodName() and
181-
helperMethod.getControllerClass() = getAssociatedControllerClass(template) and
182-
helperMethodCall.getArgument(argIdx) = node1.asExpr() and
183-
helperMethod.getParameter(argIdx) = node2.asExpr().getExpr()
227+
isHelperMethod(helperMethod, name, template) and
228+
isMethodCall(helperMethodCall.getExpr(), name, template) and
229+
helperMethodCall.getArgument(pragma[only_bind_into](argIdx)) = node1.asExpr() and
230+
helperMethod.getParameter(pragma[only_bind_into](argIdx)) = node2.asExpr().getExpr()
184231
)
185232
}
186233

187-
pragma[noinline]
188-
private predicate isHelperMethodNameMatch(
189-
ActionControllerHelperMethod helperMethod, MethodCall call
190-
) {
191-
helperMethod.getName() = call.getMethodName()
192-
}
193-
194234
private predicate isFlowFromHelperMethod(DataFlow::Node node1, DataFlow::Node node2) {
195235
// flow out of controller helper method into template
196-
exists(ErbFile template | template = node2.getLocation().getFile() |
197-
exists(ActionControllerHelperMethod helperMethod |
198-
helperMethod.getControllerClass() = getAssociatedControllerClass(template) and
199-
// `node1` is an expr node that may be returned by the helper method
200-
exprNodeReturnedFrom(node1, helperMethod)
201-
|
202-
exists(CfgNodes::ExprNodes::MethodCallCfgNode helperMethodCall |
203-
// `node2` is a call to the helper method
204-
node2.asExpr() = helperMethodCall and
205-
isHelperMethodNameMatch(helperMethod, helperMethodCall.getExpr())
206-
)
207-
)
236+
exists(ErbFile template, ActionControllerHelperMethod helperMethod, string name |
237+
// `node1` is an expr node that may be returned by the helper method
238+
exprNodeReturnedFrom(node1, helperMethod) and
239+
// `node2` is a call to the helper method
240+
isHelperMethod(helperMethod, name, template) and
241+
isMethodCall(node2.asExpr().getExpr(), name, template)
208242
)
209243
}
210244

211245
/**
212246
* An additional step that is preserves dataflow in the context of XSS.
213247
*/
214-
pragma[noopt]
215248
predicate isAdditionalXSSFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
216249
isFlowFromLocals(node1, node2)
217250
or

0 commit comments

Comments
 (0)