Skip to content

Commit 0d72a51

Browse files
authored
Merge pull request #342 from github/improve-xss-isAdditionalFlowStep
Improve `XSS::Shared::isAdditionalFlowStep` performance
2 parents 287046e + e2db11b commit 0d72a51

File tree

1 file changed

+113
-55
lines changed

1 file changed

+113
-55
lines changed

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

Lines changed: 113 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -123,79 +123,137 @@ private module Shared {
123123
}
124124

125125
/**
126-
* An additional step that is preserves dataflow in the context of XSS.
126+
* Holds if `call` is a method call in ERB file `erb`, targeting a method
127+
* named `name`.
127128
*/
128-
predicate isAdditionalXSSFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
129-
// node1 is a `locals` argument to a render call...
130-
exists(RenderCall call, Pair kvPair, string hashKey |
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 |
131142
call.getLocals().getAKeyValuePair() = kvPair and
132-
kvPair.getValue() = node1.asExpr().getExpr() and
133-
kvPair.getKey().(StringlikeLiteral).getValueText() = hashKey and
134-
// `node2` appears in the rendered template file
135-
call.getTemplateFile() = node2.getLocation().getFile() and
136-
(
137-
// node2 is an element reference against `local_assigns`
138-
exists(
139-
CfgNodes::ExprNodes::ElementReferenceCfgNode refNode, DataFlow::Node argNode,
140-
CfgNodes::ExprNodes::StringlikeLiteralCfgNode strNode
141-
|
142-
refNode = node2.asExpr() and
143-
argNode.asExpr() = refNode.getArgument(0) and
144-
refNode.getReceiver().getExpr().(MethodCall).getMethodName() = "local_assigns" and
145-
argNode.getALocalSource() = DataFlow::exprNode(strNode) and
146-
strNode.getExpr().getValueText() = hashKey
147-
)
148-
or
149-
// ...node2 is a "method call" to a "method" with `hashKey` as its name
150-
// TODO: This may be a variable read in reality that we interpret as a method call
151-
exists(MethodCall varAcc |
152-
varAcc = node2.asExpr().(CfgNodes::ExprNodes::MethodCallCfgNode).getExpr() and
153-
varAcc.getMethodName() = hashKey
154-
)
155-
)
143+
kvPair.getValue() = value and
144+
kvPair.getKey().getValueText() = hashKey and
145+
call.getTemplateFile() = erb
156146
)
157-
or
158-
// instance variables in the controller
159-
exists(
160-
ActionControllerActionMethod action, VariableReadAccess viewVarRead, AssignExpr ae,
161-
FinalInstanceVarWrite controllerVarWrite
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)
162166
|
163-
viewVarRead = node2.asExpr().(CfgNodes::ExprNodes::VariableReadAccessCfgNode).getExpr() and
164-
action.getDefaultTemplateFile() = viewVarRead.getLocation().getFile() and
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)
173+
)
174+
}
175+
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+
199+
private predicate isFlowFromControllerInstanceVariable(DataFlow::Node node1, DataFlow::Node node2) {
200+
// instance variables in the controller
201+
exists(ActionControllerActionMethod action, string name, ErbFile template |
165202
// match read to write on variable name
166-
viewVarRead.getVariable().getName() = controllerVarWrite.getVariable().getName() and
203+
actionAssigns(action, name, node1.asExpr().getExpr(), template) and
167204
// propagate taint from assignment RHS expr to variable read access in view
168-
ae = controllerVarWrite.getAnAssignExpr() and
169-
node1.asExpr().getExpr() = ae.getRightOperand() and
170-
ae.getParent+() = action
205+
isVariableReadAccess(node2.asExpr().getExpr(), name, template)
171206
)
172-
or
207+
}
208+
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+
221+
private predicate isFlowIntoHelperMethod(DataFlow::Node node1, DataFlow::Node node2) {
173222
// flow from template into controller helper method
174223
exists(
175-
ErbFile template, ActionControllerHelperMethod helperMethod,
224+
ErbFile template, ActionControllerHelperMethod helperMethod, string name,
176225
CfgNodes::ExprNodes::MethodCallCfgNode helperMethodCall, int argIdx
177226
|
178-
template = node1.getLocation().getFile() and
179-
helperMethod.getName() = helperMethodCall.getExpr().getMethodName() and
180-
helperMethod.getControllerClass() = getAssociatedControllerClass(template) and
181-
helperMethodCall.getArgument(argIdx) = node1.asExpr() and
182-
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()
183231
)
184-
or
232+
}
233+
234+
private predicate isFlowFromHelperMethod(DataFlow::Node node1, DataFlow::Node node2) {
185235
// flow out of controller helper method into template
186-
exists(
187-
ErbFile template, ActionControllerHelperMethod helperMethod,
188-
CfgNodes::ExprNodes::MethodCallCfgNode helperMethodCall
189-
|
190-
template = node2.getLocation().getFile() and
191-
helperMethod.getName() = helperMethodCall.getExpr().getMethodName() and
192-
helperMethod.getControllerClass() = getAssociatedControllerClass(template) and
236+
exists(ErbFile template, ActionControllerHelperMethod helperMethod, string name |
193237
// `node1` is an expr node that may be returned by the helper method
194238
exprNodeReturnedFrom(node1, helperMethod) and
195239
// `node2` is a call to the helper method
196-
node2.asExpr() = helperMethodCall
240+
isHelperMethod(helperMethod, name, template) and
241+
isMethodCall(node2.asExpr().getExpr(), name, template)
197242
)
198243
}
244+
245+
/**
246+
* An additional step that is preserves dataflow in the context of XSS.
247+
*/
248+
predicate isAdditionalXSSFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
249+
isFlowFromLocals(node1, node2)
250+
or
251+
isFlowFromControllerInstanceVariable(node1, node2)
252+
or
253+
isFlowIntoHelperMethod(node1, node2)
254+
or
255+
isFlowFromHelperMethod(node1, node2)
256+
}
199257
}
200258

201259
/**

0 commit comments

Comments
 (0)