Skip to content

Commit 8e5d23f

Browse files
committed
Ruby: WIP instantiation for Ruby
1 parent bad9d4b commit 8e5d23f

File tree

6 files changed

+208
-9
lines changed

6 files changed

+208
-9
lines changed

ruby/ql/lib/codeql/ruby/ApiGraphs.qll

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,15 +1047,29 @@ module API {
10471047

10481048
import MkShared
10491049

1050-
/** Gets the API node corresponding to the module/class object for `mod`. */
1050+
/** Gets the API node corresponding to the module/class object for `mod`, with epsilon edges to descendent modules/classes. */
10511051
bindingset[mod]
10521052
pragma[inline_late]
10531053
Node getModuleNode(DataFlow::ModuleNode mod) { result = Impl::MkModuleObjectDown(mod) }
10541054

1055-
/** Gets the API node corresponding to instances of `mod`. */
1055+
/** Gets the API node corresponding to instances of `mod`, with epsilon edges to instances of descendent modules/classes. */
10561056
bindingset[mod]
10571057
pragma[inline_late]
10581058
Node getModuleInstance(DataFlow::ModuleNode mod) { result = getModuleNode(mod).getInstance() }
1059+
1060+
/** Gets the API node corresponding to instances of `mod` with epsilon edges to ancestor modules/classes. */
1061+
bindingset[mod]
1062+
pragma[inline_late]
1063+
Node getModuleNodeUp(DataFlow::ModuleNode mod) { result = Impl::MkModuleObjectUp(mod) }
1064+
1065+
/** Gets the API node corresponding to instances of `mod`, with epsilon edges to instances of ancestor modules/classes. */
1066+
bindingset[mod]
1067+
pragma[inline_late]
1068+
Node getModuleInstanceUp(DataFlow::ModuleNode mod) {
1069+
result = getModuleNodeUp(mod).getInstance()
1070+
}
1071+
1072+
import Impl
10591073
}
10601074

10611075
private import Internal

ruby/ql/lib/codeql/ruby/frameworks/data/ModelsAsData.qll

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,117 @@ private class SummarizedCallableFromModel extends SummarizedCallable {
6060
)
6161
}
6262
}
63+
64+
/**
65+
* Specifies which parts of the API graph to export in `ModelExport`.
66+
*/
67+
signature module ModelExportSig {
68+
/**
69+
* Holds if the exported model should contain `node`, if it is publicly accessible.
70+
*
71+
* This ensures that all ways to access `node` will be exported in type models.
72+
*/
73+
predicate shouldContain(API::Node node);
74+
75+
/**
76+
* Holds if a named must be generated for `node` if it is to be included in the exported graph.
77+
*/
78+
default predicate mustBeNamed(API::Node node) { none() }
79+
80+
/**
81+
* Holds if the exported model should preserve all paths leading to an instance of `type`,
82+
* including partial ones. It does not need to be closed transitively, `ModelExport` will
83+
* extend this to include type models from which `type` can be derived.
84+
*/
85+
default predicate shouldContainType(string type) { none() }
86+
}
87+
88+
/**
89+
* Module for exporting type models for a given set of nodes in the API graph.
90+
*/
91+
module ModelExport<ModelExportSig S> {
92+
private import codeql.mad.dynamic.GraphExport
93+
private import internal.ApiGraphModelsExport
94+
95+
private module GraphExportConfig implements GraphExportSig<Location, API::Node> {
96+
predicate edge = Specific::apiGraphHasEdge/3;
97+
98+
predicate shouldContain = S::shouldContain/1;
99+
100+
predicate shouldNotContain(API::Node node) {
101+
// Only export def-nodes, exclude use-nodes
102+
node instanceof API::Internal::MkModuleObjectDown
103+
or
104+
node instanceof API::Internal::MkModuleInstanceDown
105+
or
106+
node instanceof API::Internal::MkForwardNode
107+
or
108+
node instanceof API::Internal::MkMethodAccessNode
109+
}
110+
111+
predicate mustBeNamed(API::Node node) { S::mustBeNamed(node) }
112+
113+
predicate exposedName(API::Node node, string type, string path) {
114+
path = "" and
115+
exists(DataFlow::ModuleNode mod |
116+
node = API::Internal::MkModuleObjectUp(mod) and
117+
type = mod.getQualifiedName() + "!"
118+
or
119+
node = API::Internal::MkModuleInstanceUp(mod) and
120+
type = mod.getQualifiedName()
121+
)
122+
}
123+
124+
private string suggestedMethodName(DataFlow::MethodNode method) {
125+
exists(DataFlow::ModuleNode mod, string name |
126+
method = mod.getOwnSingletonMethod(name) and
127+
result = mod.getQualifiedName() + "." + name
128+
or
129+
method = mod.getOwnInstanceMethod(name) and
130+
result = mod.getQualifiedName() + "#" + name
131+
)
132+
}
133+
134+
predicate suggestedName(API::Node node, string type) {
135+
// exists(DataFlow::MethodNode method |
136+
// node.asSink() = method.getAReturnNode() and type = suggestedMethodName(method) + "()"
137+
// )
138+
none()
139+
}
140+
141+
bindingset[host]
142+
predicate hasTypeSummary(API::Node host, string path) {
143+
exists(string methodName |
144+
methodReturnsReceiver(host.getMethod(methodName).asCallable()) and
145+
path = "Method[" + methodName + "].ReturnValue"
146+
)
147+
}
148+
149+
pragma[nomagic]
150+
private predicate methodReturnsReceiver(DataFlow::MethodNode func) {
151+
getAReceiverRef(func).flowsTo(func.getAReturnNode())
152+
}
153+
154+
pragma[nomagic]
155+
private DataFlow::CallNode getAReceiverCall(DataFlow::MethodNode func) {
156+
result = getAReceiverRef(func).getAMethodCall()
157+
}
158+
159+
pragma[nomagic]
160+
private predicate callReturnsReceiver(DataFlow::CallNode call) {
161+
methodReturnsReceiver(call.getATarget())
162+
}
163+
164+
pragma[nomagic]
165+
private DataFlow::LocalSourceNode getAReceiverRef(DataFlow::MethodNode func) {
166+
result = func.getSelfParameter()
167+
or
168+
result = getAReceiverCall(func) and
169+
callReturnsReceiver(result)
170+
}
171+
}
172+
173+
private module ExportedGraph = TypeGraphExport<GraphExportConfig, S::shouldContainType/1>;
174+
175+
import ExportedGraph
176+
}

ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsSpecific.qll

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import codeql.ruby.ApiGraphs
2727
import codeql.ruby.DataFlow::DataFlow as DataFlow
2828
private import FlowSummaryImpl::Public
2929
private import codeql.ruby.dataflow.internal.DataFlowDispatch as DataFlowDispatch
30+
import codeql.Locations // re-export Location
3031

3132
pragma[nomagic]
3233
private predicate isUsedTopLevelConstant(string name) {
@@ -248,3 +249,54 @@ predicate isExtraValidTokenArgumentInIdentifyingAccessPath(string name, string a
248249
}
249250

250251
module ModelOutputSpecific { }
252+
253+
/**
254+
* Holds if the value of `source` is exposed at `sink`.
255+
*/
256+
bindingset[source]
257+
predicate sourceFlowsToSink(API::Node source, API::Node sink) {
258+
// TODO: also establish subclass relationship
259+
source.getAValueReachableFromSource() = sink.asSink()
260+
}
261+
262+
/**
263+
* Holds if the edge `pred -> succ` labelled with `path` exists in the API graph.
264+
*/
265+
bindingset[pred]
266+
predicate apiGraphHasEdge(API::Node pred, string path, API::Node succ) {
267+
exists(string name |
268+
API::Internal::methodEdge(pred, name, succ) and path = "Method[" + name + "]"
269+
)
270+
or
271+
API::Internal::elementEdge(pred, succ) and path = "Element"
272+
or
273+
API::Internal::instanceEdge(pred, succ) and path = "Instance"
274+
or
275+
API::Internal::returnEdge(pred, succ) and path = "ReturnValue"
276+
or
277+
exists(DataFlowDispatch::ArgumentPosition pos |
278+
not pos.isSelf() and
279+
API::Internal::argumentEdge(pred, pos, succ) and
280+
path = "Argument[" + FlowSummaryImpl::Input::encodeArgumentPosition(pos) + "]"
281+
)
282+
or
283+
exists(DataFlowDispatch::ParameterPosition pos |
284+
not pos.isSelf() and
285+
API::Internal::parameterEdge(pred, pos, succ) and
286+
path = "Parameter[" + FlowSummaryImpl::Input::encodeParameterPosition(pos) + "]"
287+
)
288+
or
289+
path = "" and
290+
API::Internal::epsilonEdge(pred, succ)
291+
}
292+
293+
pragma[nomagic]
294+
private predicate inheritanceEdge(API::Node pred, API::Node succ) {
295+
exists(DataFlow::ModuleNode mod |
296+
pred = API::Internal::getModuleNodeUp(mod) and
297+
succ = API::Internal::getModuleNodeUp(mod.getAnImmediateAncestor())
298+
or
299+
pred = API::Internal::getModuleInstanceUp(mod) and
300+
succ = API::Internal::getModuleInstanceUp(mod.getAnImmediateAncestor())
301+
)
302+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ module ApiGraphShared<ApiGraphSharedSig S> {
144144

145145
private import Cached
146146

147+
predicate epsilonEdge = Cached::epsilonEdge/2;
148+
147149
/** Gets an API node corresponding to the end of forward-tracking to `localSource`. */
148150
pragma[nomagic]
149151
private ApiNode forwardEndNode(DataFlow::LocalSourceNode localSource) {

ruby/ql/src/queries/modeling/GenerateModel.ql

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,23 @@
88

99
private import internal.Types
1010
private import internal.Summaries
11+
private import codeql.ruby.ApiGraphs
12+
private import codeql.ruby.DataFlow
13+
private import codeql.ruby.frameworks.data.ModelsAsData
1114

12-
/**
13-
* Holds if `(type2, path)` should be seen as an instance of `type1`.
14-
*/
15-
query predicate typeModel = Types::typeModel/3;
15+
module ModelExportConfig implements ModelExportSig {
16+
predicate shouldContain(API::Node node) {
17+
exists(DataFlow::MethodNode method | node = method.backtrack())
18+
}
19+
20+
predicate shouldContainType(string type) {
21+
type = any(DataFlow::ModuleNode mod).getQualifiedName() + ["", "!"]
22+
}
23+
}
24+
25+
module ExportedModel = ModelExport<ModelExportConfig>;
26+
27+
query predicate typeModel = ExportedModel::typeModel/3;
1628

1729
/**
1830
* Holds if the value at `(type, path)` should be seen as a flow
@@ -35,7 +47,11 @@ query predicate sinkModel(string type, string path, string kind) { none() }
3547
* `kind` should be either `value` or `taint`, for value-preserving or taint-preserving steps,
3648
* respectively.
3749
*/
38-
query predicate summaryModel = Summaries::summaryModel/5;
50+
query predicate summaryModel(string type, string path, string input, string output, string kind) {
51+
Summaries::summaryModel(type, path, input, output, kind)
52+
or
53+
ExportedModel::summaryModel(type, path, input, output, kind)
54+
}
3955

4056
/**
4157
* Holds if `path` can be substituted for a token `TypeVar[name]`.
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
sourceModel
22
sinkModel
3+
summaryModel
4+
| A! | Method[new] | Argument[0] | ReturnValue | value |
35
typeVariableModel
46
typeModel
57
| M1 | B | |
6-
summaryModel
7-
| A! | Method[new] | Argument[0] | ReturnValue | value |
8+
| M1! | B! | |

0 commit comments

Comments
 (0)