Skip to content

Commit 7bf818f

Browse files
committed
Refactor KernelMethodCall modelling
By extending `DataFlow::CallNode` instead of `MethodCall`, we get rid of a lot of `.asExpr().getExpr()` calls.
1 parent 232fb9a commit 7bf818f

File tree

4 files changed

+55
-77
lines changed

4 files changed

+55
-77
lines changed

ql/lib/codeql/ruby/frameworks/Files.qll

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,10 @@ private DataFlow::Node fileInstanceInstantiation() {
3636
result = API::getTopLevelMember("File").getAMethodCall("open")
3737
or
3838
// Calls to `Kernel.open` can yield `File` instances
39-
exists(KernelMethodCall c |
40-
c = result.asExpr().getExpr() and
41-
c.getMethodName() = "open" and
42-
// Assume that calls that don't invoke shell commands will instead open
43-
// a file.
44-
not pathArgSpawnsSubprocess(c.getArgument(0))
45-
)
39+
result.(KernelMethodCall).getMethodName() = "open" and
40+
// Assume that calls that don't invoke shell commands will instead open
41+
// a file.
42+
not pathArgSpawnsSubprocess(result.(KernelMethodCall).getArgument(0).asExpr().getExpr())
4643
}
4744

4845
private DataFlow::Node fileInstance() {

ql/lib/codeql/ruby/frameworks/StandardLibrary.qll

Lines changed: 43 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,27 @@ private import codeql.ruby.ApiGraphs
88
* in every Ruby object. In addition, its module methods can be called by
99
* providing a specific receiver as in `Kernel.exit`.
1010
*/
11-
class KernelMethodCall extends MethodCall {
11+
class KernelMethodCall extends DataFlow::CallNode {
12+
private MethodCall methodCall;
13+
1214
KernelMethodCall() {
13-
this = API::getTopLevelMember("Kernel").getAMethodCall(_).asExpr().getExpr()
14-
or
15-
this instanceof UnknownMethodCall and
15+
methodCall = this.asExpr().getExpr() and
1616
(
17-
this.getReceiver() instanceof Self and isPrivateKernelMethod(this.getMethodName())
17+
this = API::getTopLevelMember("Kernel").getAMethodCall(_)
1818
or
19-
isPublicKernelMethod(this.getMethodName())
19+
methodCall instanceof UnknownMethodCall and
20+
(
21+
this.getReceiver().asExpr().getExpr() instanceof Self and
22+
isPrivateKernelMethod(methodCall.getMethodName())
23+
or
24+
isPublicKernelMethod(methodCall.getMethodName())
25+
)
2026
)
2127
}
28+
29+
string getMethodName() { result = methodCall.getMethodName() }
30+
31+
int getNumberOfArguments() { result = methodCall.getNumberOfArguments() }
2232
}
2333

2434
/**
@@ -161,19 +171,14 @@ class SubshellHeredocExecution extends SystemCommandExecution::Range {
161171
* ```
162172
* Ruby documentation: https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-system
163173
*/
164-
class KernelSystemCall extends SystemCommandExecution::Range {
165-
KernelMethodCall methodCall;
174+
class KernelSystemCall extends SystemCommandExecution::Range, KernelMethodCall {
175+
KernelSystemCall() { this.getMethodName() = "system" }
166176

167-
KernelSystemCall() {
168-
methodCall.getMethodName() = "system" and
169-
this.asExpr().getExpr() = methodCall
170-
}
171-
172-
override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() }
177+
override DataFlow::Node getAnArgument() { result = this.getArgument(_) }
173178

174179
override predicate isShellInterpreted(DataFlow::Node arg) {
175180
// Kernel.system invokes a subshell if you provide a single string as argument
176-
methodCall.getNumberOfArguments() = 1 and arg.asExpr().getExpr() = methodCall.getAnArgument()
181+
this.getNumberOfArguments() = 1 and arg = getAnArgument()
177182
}
178183
}
179184

@@ -182,19 +187,14 @@ class KernelSystemCall extends SystemCommandExecution::Range {
182187
* `Kernel.exec` takes the same argument forms as `Kernel.system`. See `KernelSystemCall` for details.
183188
* Ruby documentation: https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-exec
184189
*/
185-
class KernelExecCall extends SystemCommandExecution::Range {
186-
KernelMethodCall methodCall;
190+
class KernelExecCall extends SystemCommandExecution::Range, KernelMethodCall {
191+
KernelExecCall() { this.getMethodName() = "exec" }
187192

188-
KernelExecCall() {
189-
methodCall.getMethodName() = "exec" and
190-
this.asExpr().getExpr() = methodCall
191-
}
192-
193-
override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() }
193+
override DataFlow::Node getAnArgument() { result = this.getArgument(_) }
194194

195195
override predicate isShellInterpreted(DataFlow::Node arg) {
196196
// Kernel.exec invokes a subshell if you provide a single string as argument
197-
methodCall.getNumberOfArguments() = 1 and arg.asExpr().getExpr() = methodCall.getAnArgument()
197+
this.getNumberOfArguments() = 1 and arg = getAnArgument()
198198
}
199199
}
200200

@@ -208,19 +208,14 @@ class KernelExecCall extends SystemCommandExecution::Range {
208208
* spawn([env,] command... [,options]) -> pid
209209
* ```
210210
*/
211-
class KernelSpawnCall extends SystemCommandExecution::Range {
212-
KernelMethodCall methodCall;
211+
class KernelSpawnCall extends SystemCommandExecution::Range, KernelMethodCall {
212+
KernelSpawnCall() { this.getMethodName() = "spawn" }
213213

214-
KernelSpawnCall() {
215-
methodCall.getMethodName() = "spawn" and
216-
this.asExpr().getExpr() = methodCall
217-
}
218-
219-
override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() }
214+
override DataFlow::Node getAnArgument() { result = this.getArgument(_) }
220215

221216
override predicate isShellInterpreted(DataFlow::Node arg) {
222217
// Kernel.spawn invokes a subshell if you provide a single string as argument
223-
methodCall.getNumberOfArguments() = 1 and arg.asExpr().getExpr() = methodCall.getAnArgument()
218+
this.getNumberOfArguments() = 1 and arg = getAnArgument()
224219
}
225220
}
226221

@@ -284,14 +279,10 @@ class Open3PipelineCall extends SystemCommandExecution::Range {
284279
* a # => 2
285280
* ```
286281
*/
287-
class EvalCallCodeExecution extends CodeExecution::Range {
288-
KernelMethodCall methodCall;
289-
290-
EvalCallCodeExecution() {
291-
this.asExpr().getExpr() = methodCall and methodCall.getMethodName() = "eval"
292-
}
282+
class EvalCallCodeExecution extends CodeExecution::Range, KernelMethodCall {
283+
EvalCallCodeExecution() { this.getMethodName() = "eval" }
293284

294-
override DataFlow::Node getCode() { result.asExpr().getExpr() = methodCall.getArgument(0) }
285+
override DataFlow::Node getCode() { result = this.getArgument(0) }
295286
}
296287

297288
/**
@@ -302,51 +293,41 @@ class EvalCallCodeExecution extends CodeExecution::Range {
302293
* arr # => [1]
303294
* ```
304295
*/
305-
class SendCallCodeExecution extends CodeExecution::Range {
306-
KernelMethodCall methodCall;
296+
class SendCallCodeExecution extends CodeExecution::Range, KernelMethodCall {
297+
SendCallCodeExecution() { this.getMethodName() = "send" }
307298

308-
SendCallCodeExecution() {
309-
this.asExpr().getExpr() = methodCall and methodCall.getMethodName() = "send"
310-
}
311-
312-
override DataFlow::Node getCode() { result.asExpr().getExpr() = methodCall.getArgument(0) }
299+
override DataFlow::Node getCode() { result = this.getArgument(0) }
313300
}
314301

315302
/**
316303
* A call to `BasicObject#instance_eval`, which executes its first argument as Ruby code.
317304
*/
318-
class InstanceEvalCallCodeExecution extends CodeExecution::Range {
319-
BasicObjectInstanceMethodCall methodCall;
320-
305+
class InstanceEvalCallCodeExecution extends CodeExecution::Range, DataFlow::CallNode {
321306
InstanceEvalCallCodeExecution() {
322-
this.asExpr().getExpr() = methodCall and methodCall.getMethodName() = "instance_eval"
307+
this.asExpr().getExpr().(UnknownMethodCall).getMethodName() = "instance_eval"
323308
}
324309

325-
override DataFlow::Node getCode() { result.asExpr().getExpr() = methodCall.getArgument(0) }
310+
override DataFlow::Node getCode() { result = this.getArgument(0) }
326311
}
327312

328313
/**
329314
* A call to `Module#class_eval`, which executes its first argument as Ruby code.
330315
*/
331-
class ClassEvalCallCodeExecution extends CodeExecution::Range {
332-
UnknownMethodCall methodCall;
333-
316+
class ClassEvalCallCodeExecution extends CodeExecution::Range, DataFlow::CallNode {
334317
ClassEvalCallCodeExecution() {
335-
this.asExpr().getExpr() = methodCall and methodCall.getMethodName() = "class_eval"
318+
this.asExpr().getExpr().(UnknownMethodCall).getMethodName() = "class_eval"
336319
}
337320

338-
override DataFlow::Node getCode() { result.asExpr().getExpr() = methodCall.getArgument(0) }
321+
override DataFlow::Node getCode() { result = this.getArgument(0) }
339322
}
340323

341324
/**
342325
* A call to `Module#module_eval`, which executes its first argument as Ruby code.
343326
*/
344-
class ModuleEvalCallCodeExecution extends CodeExecution::Range {
345-
UnknownMethodCall methodCall;
346-
327+
class ModuleEvalCallCodeExecution extends CodeExecution::Range, DataFlow::CallNode {
347328
ModuleEvalCallCodeExecution() {
348-
this.asExpr().getExpr() = methodCall and methodCall.getMethodName() = "module_eval"
329+
this.asExpr().getExpr().(UnknownMethodCall).getMethodName() = "module_eval"
349330
}
350331

351-
override DataFlow::Node getCode() { result.asExpr().getExpr() = methodCall.getArgument(0) }
332+
override DataFlow::Node getCode() { result = this.getArgument(0) }
352333
}

ql/lib/codeql/ruby/frameworks/http_clients/OpenURI.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class OpenURIRequest extends HTTP::Client::Request::Range {
2626
or
2727
// Kernel.open("http://example.com").read
2828
// open("http://example.com").read
29-
this instanceof KernelMethodCall and
29+
request instanceof KernelMethodCall and
3030
this.getMethodName() = "open" and
3131
request.asExpr().getExpr() = this and
3232
responseBody.asExpr().getExpr().(MethodCall).getMethodName() in ["read", "readlines"] and

ql/src/queries/security/cwe-078/KernelOpen.ql

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import DataFlow::PathGraph
2525
/**
2626
* Method calls that have a suggested replacement.
2727
*/
28-
abstract class Replacement extends MethodCall {
28+
abstract class Replacement extends DataFlow::CallNode {
2929
abstract string getFrom();
3030

3131
abstract string getTo();
@@ -39,8 +39,8 @@ class KernelOpenCall extends KernelMethodCall, Replacement {
3939
override string getTo() { result = "File.open" }
4040
}
4141

42-
class IOReadCall extends MethodCall, Replacement {
43-
IOReadCall() { this = API::getTopLevelMember("IO").getAMethodCall("read").asExpr().getExpr() }
42+
class IOReadCall extends DataFlow::CallNode, Replacement {
43+
IOReadCall() { this = API::getTopLevelMember("IO").getAMethodCall("read") }
4444

4545
override string getFrom() { result = "IO.read" }
4646

@@ -53,9 +53,9 @@ class Configuration extends TaintTracking::Configuration {
5353
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
5454

5555
override predicate isSink(DataFlow::Node sink) {
56-
exists(KernelOpenCall c | c.getArgument(0) = sink.asExpr().getExpr())
56+
exists(KernelOpenCall c | c.getArgument(0) = sink)
5757
or
58-
exists(IOReadCall c | c.getArgument(0) = sink.asExpr().getExpr())
58+
exists(IOReadCall c | c.getArgument(0) = sink)
5959
}
6060

6161
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
@@ -66,11 +66,11 @@ class Configuration extends TaintTracking::Configuration {
6666

6767
from
6868
Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink,
69-
DataFlow::Node sourceNode, MethodCall call
69+
DataFlow::Node sourceNode, DataFlow::CallNode call
7070
where
7171
config.hasFlowPath(source, sink) and
7272
sourceNode = source.getNode() and
73-
call.getArgument(0) = sink.getNode().asExpr().getExpr()
73+
call.asExpr().getExpr().(MethodCall).getArgument(0) = sink.getNode().asExpr().getExpr()
7474
select sink.getNode(), source, sink,
7575
"This call to " + call.(Replacement).getFrom() +
7676
" depends on a user-provided value. Replace it with " + call.(Replacement).getTo() + "."

0 commit comments

Comments
 (0)