Skip to content

Commit 16c5edd

Browse files
committed
Ruby: add a query and script for autogenerating typeModel and summaryModel data extensions entries
1 parent 9d719aa commit 16c5edd

File tree

6 files changed

+427
-1
lines changed

6 files changed

+427
-1
lines changed

ruby/ql/lib/codeql/ruby/typetracking/ApiGraphShared.qll

+1-1
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ module ApiGraphShared<ApiGraphSharedSig S> {
185185
bindingset[sink]
186186
pragma[inline_late]
187187
Node getAValueReachingSinkInline(ApiNode sink) {
188-
result = asSinkInline(getAnEpsilonSuccessorInline(sink))
188+
backwardStartNode(result) = getAnEpsilonSuccessorInline(sink)
189189
}
190190

191191
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
private import internal.Types
2+
private import internal.Summaries
3+
4+
/**
5+
* Holds if `(type2, path)` should be seen as an instance of `type1`.
6+
*/
7+
query predicate typeModel = Types::typeModel/3;
8+
9+
/**
10+
* Holds if the value at `(type, path)` should be seen as a flow
11+
* source of the given `kind`.
12+
*
13+
* The kind `remote` represents a general remote flow source.
14+
*/
15+
query predicate sourceModel(string type, string path, string kind) { none() }
16+
17+
/**
18+
* Holds if the value at `(type, path)` should be seen as a sink
19+
* of the given `kind`.
20+
*/
21+
query predicate sinkModel(string type, string path, string kind) { none() }
22+
23+
/**
24+
* Holds if calls to `(type, path)`, the value referred to by `input`
25+
* can flow to the value referred to by `output`.
26+
*
27+
* `kind` should be either `value` or `taint`, for value-preserving or taint-preserving steps,
28+
* respectively.
29+
*/
30+
query predicate summaryModel = Summaries::summaryModel/5;
31+
32+
/**
33+
* Holds if `path` can be substituted for a token `TypeVar[name]`.
34+
*/
35+
query predicate typeVariableModel(string name, string path) { none() }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* Contains predicates for generating `summaryModel`s to summarize flow through methods.
3+
*/
4+
5+
private import ruby
6+
private import codeql.ruby.ApiGraphs
7+
private import codeql.ruby.TaintTracking
8+
private import Util as Util
9+
10+
/**
11+
* Contains predicates for generating `summaryModel`s to summarize flow through methods.
12+
*/
13+
module Summaries {
14+
private module Config implements DataFlow::ConfigSig {
15+
predicate isSource(DataFlow::Node source) { source instanceof DataFlow::ParameterNode }
16+
17+
predicate isSink(DataFlow::Node sink) { sink = any(DataFlow::MethodNode m).getAReturnNode() }
18+
}
19+
20+
API::Node getAnyParameterNode(DataFlow::MethodNode methodNode) {
21+
result.asSource() =
22+
[
23+
methodNode.getParameter(_), methodNode.getKeywordParameter(_),
24+
methodNode.getBlockParameter(), methodNode.getSelfParameter()
25+
]
26+
}
27+
28+
private module ValueFlow {
29+
import DataFlow::Global<Config>
30+
31+
predicate summaryModel(string type, string path, string input, string output) {
32+
exists(DataFlow::MethodNode methodNode, API::Node paramNode |
33+
methodNode.getLocation().getFile() instanceof Util::RelevantFile and
34+
paramNode.getAValueReachableFromSource() = methodNode.getAReturnNode() and
35+
paramNode = getAnyParameterNode(methodNode)
36+
|
37+
Util::pathToMethod(methodNode, type, path) and
38+
input = Util::getArgumentPath(paramNode.asSource()) and
39+
output = "ReturnValue"
40+
)
41+
}
42+
}
43+
44+
private module TaintFlow {
45+
import TaintTracking::Global<Config>
46+
47+
predicate summaryModel(string type, string path, string input, string output) {
48+
not ValueFlow::summaryModel(type, path, input, output) and
49+
exists(DataFlow::MethodNode methodNode, API::Node paramNode |
50+
methodNode.getLocation().getFile() instanceof Util::RelevantFile and
51+
flow(paramNode.asSource(), methodNode.getAReturnNode()) and
52+
paramNode = getAnyParameterNode(methodNode)
53+
|
54+
Util::pathToMethod(methodNode, type, path) and
55+
input = Util::getArgumentPath(paramNode.asSource()) and
56+
output = "ReturnValue"
57+
)
58+
}
59+
}
60+
61+
/**
62+
* Holds if calls to `(type, path)`, the value referred to by `input`
63+
* can flow to the value referred to by `output`.
64+
*
65+
* `kind` should be either `value` or `taint`, for value-preserving or taint-preserving steps,
66+
* respectively.
67+
*/
68+
predicate summaryModel(string type, string path, string input, string output, string kind) {
69+
ValueFlow::summaryModel(type, path, input, output) and kind = "value"
70+
or
71+
TaintFlow::summaryModel(type, path, input, output) and kind = "taint"
72+
}
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* Contains predicates for generating `typeModel`s that contain typing
3+
* information for API nodes.
4+
*/
5+
6+
private import ruby
7+
private import codeql.ruby.ApiGraphs
8+
private import Util as Util
9+
10+
/**
11+
* Contains predicates for generating `typeModel`s that contain typing
12+
* information for API nodes.
13+
*/
14+
module Types {
15+
/**
16+
* Holds `node` should be seen as having the given `type`.
17+
*/
18+
private predicate valueHasTypeName(DataFlow::LocalSourceNode node, string type) {
19+
node.getLocation().getFile() instanceof Util::RelevantFile and
20+
exists(DataFlow::ModuleNode mod |
21+
(
22+
node = mod.getAnImmediateReference().getAMethodCall("new")
23+
or
24+
node = mod.getAnOwnInstanceSelf()
25+
) and
26+
type = mod.getQualifiedName()
27+
or
28+
(
29+
node = mod.getAnImmediateReference()
30+
or
31+
node = mod.getAnOwnModuleSelf()
32+
) and
33+
type = mod.getQualifiedName() + "!"
34+
)
35+
}
36+
37+
/**
38+
* Holds if `(type2, path)` should be seen as an instance of `type1`.
39+
*/
40+
predicate typeModel(string type1, string type2, string path) {
41+
exists(API::Node node |
42+
valueHasTypeName(node.getAValueReachingSink(), type1) and
43+
Util::pathToNode(node, type2, path, true)
44+
)
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/**
2+
* Contains utility methods and classes to assist with generating data extensions models.
3+
*/
4+
5+
private import ruby
6+
private import codeql.ruby.ApiGraphs
7+
8+
/**
9+
* A file that is relevant in the context of library modeling.
10+
*
11+
* In practice, this means a file that is not part of test code.
12+
*/
13+
class RelevantFile extends File {
14+
RelevantFile() { not this.getRelativePath().regexpMatch(".*/?test(case)?s?/.*") }
15+
}
16+
17+
/**
18+
* Gets an access path of an argument corresponding to the given `paramNode`.
19+
*/
20+
string getArgumentPath(DataFlow::ParameterNode paramNode) {
21+
paramNode.getLocation().getFile() instanceof RelevantFile and
22+
exists(Ast::Parameter param, string paramSpecifier |
23+
param = paramNode.asParameter() and
24+
(
25+
paramSpecifier = param.getPosition().toString()
26+
or
27+
paramSpecifier = param.(Ast::KeywordParameter).getName() + ":"
28+
or
29+
param instanceof Ast::BlockParameter and
30+
paramSpecifier = "block"
31+
)
32+
|
33+
result = "Argument[" + paramSpecifier + "]"
34+
)
35+
}
36+
37+
/**
38+
* Holds if `(type,path)` evaluates to the given method, when evalauted from a client of the current library.
39+
*/
40+
predicate pathToMethod(DataFlow::MethodNode method, string type, string path) {
41+
method.getLocation().getFile() instanceof RelevantFile and
42+
exists(DataFlow::ModuleNode mod, string methodName |
43+
method = mod.getOwnInstanceMethod(methodName) and
44+
if methodName = "initialize"
45+
then (
46+
type = mod.getQualifiedName() + "!" and
47+
path = "Method[new]"
48+
) else (
49+
type = mod.getQualifiedName() and
50+
path = "Method[" + methodName + "]"
51+
)
52+
or
53+
method = mod.getOwnSingletonMethod(methodName) and
54+
type = mod.getQualifiedName() + "!" and
55+
path = "Method[" + methodName + "]"
56+
)
57+
}
58+
59+
/** Gets any parameter to `method`. This may be a positional, keyword, or block parameter. */
60+
private DataFlow::ParameterNode getAnyParameter(DataFlow::MethodNode method) {
61+
result = [method.getParameter(_), method.getKeywordParameter(_), method.getBlockParameter()]
62+
}
63+
64+
private predicate pathToNodeBase(API::Node node, string type, string path, boolean isOutput) {
65+
exists(DataFlow::MethodNode method, string prevPath | pathToMethod(method, type, prevPath) |
66+
isOutput = true and
67+
node = method.getAReturnNode().backtrack() and
68+
path = prevPath + ".ReturnValue" and
69+
not method.getMethodName() = "initialize" // ignore return value of initialize method
70+
or
71+
isOutput = false and
72+
exists(DataFlow::ParameterNode paramNode |
73+
paramNode = getAnyParameter(method) and
74+
node = paramNode.track()
75+
|
76+
path = prevPath + "." + getArgumentPath(paramNode)
77+
)
78+
)
79+
}
80+
81+
private predicate pathToNodeRec(
82+
API::Node node, string type, string path, boolean isOutput, int pathLength
83+
) {
84+
pathLength < 8 and
85+
(
86+
pathToNodeBase(node, type, path, isOutput) and
87+
pathLength = 1
88+
or
89+
exists(API::Node prevNode, string prevPath, boolean prevIsOutput, int prevPathLength |
90+
pathToNodeRec(prevNode, type, prevPath, prevIsOutput, prevPathLength) and
91+
pathLength = prevPathLength + 1
92+
|
93+
node = prevNode.getAnElement() and
94+
path = prevPath + ".Element" and
95+
isOutput = prevIsOutput
96+
or
97+
node = prevNode.getReturn() and
98+
path = prevPath + ".ReturnValue" and
99+
isOutput = prevIsOutput
100+
or
101+
prevIsOutput = false and
102+
isOutput = true and
103+
(
104+
exists(int n |
105+
node = prevNode.getParameter(n) and
106+
path = prevPath + ".Parameter[" + n + "]"
107+
)
108+
or
109+
exists(string name |
110+
node = prevNode.getKeywordParameter(name) and
111+
path = prevPath + ".Parameter[" + name + ":]"
112+
)
113+
or
114+
node = prevNode.getBlock() and
115+
path = prevPath + ".Parameter[block]"
116+
)
117+
)
118+
)
119+
}
120+
121+
/**
122+
* Holds if `(type,path)` evaluates to a value corresponding to `node`, when evaluated from a client of the current library.
123+
*/
124+
predicate pathToNode(API::Node node, string type, string path, boolean isOutput) {
125+
pathToNodeRec(node, type, path, isOutput, _)
126+
}

0 commit comments

Comments
 (0)