Skip to content

Commit 64a8ced

Browse files
committed
Generalise the concept of a Kernel method call
1 parent 599dc28 commit 64a8ced

File tree

1 file changed

+63
-36
lines changed

1 file changed

+63
-36
lines changed

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

Lines changed: 63 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,63 @@ private import codeql.ruby.ApiGraphs
55
private import codeql.ruby.dataflow.internal.DataFlowDispatch
66
private import codeql.ruby.dataflow.internal.DataFlowImplCommon
77

8+
/**
9+
* The `Kernel` module is included by the `Object` class, so its methods are available
10+
* in every Ruby object. In addition, its module methods can be called by
11+
* providing a specific receiver as in `Kernel.exit`.
12+
*/
13+
class KernelMethodCall extends MethodCall {
14+
KernelMethodCall() {
15+
this = API::getTopLevelMember("Kernel").getAMethodCall(_).asExpr().getExpr()
16+
or
17+
// we assume that if there's no obvious target for this method call
18+
// and the method name matches a Kernel method, then it is a Kernel method call.
19+
// TODO: ApiGraphs should ideally handle this case
20+
not exists(DataFlowCallable method, DataFlowCall call |
21+
viableCallable(call) = method and call.getExpr() = this
22+
) and
23+
(
24+
this.getReceiver() instanceof Self and isPrivateKernelMethod(this.getMethodName())
25+
or
26+
isPublicKernelMethod(this.getMethodName())
27+
)
28+
}
29+
}
30+
31+
/**
32+
* Public methods in the `Kernel` module. These can be invoked on any object via the usual dot syntax.
33+
* ```ruby
34+
* arr = []
35+
* arr.send("push", 5) # => [5]
36+
* ```
37+
*/
38+
private predicate isPublicKernelMethod(string method) {
39+
method in ["class", "clone", "frozen?", "tap", "then", "yield_self", "send"]
40+
}
41+
42+
/**
43+
* Private methods in the `Kernel` module.
44+
* These can be be invoked on `self`, on `Kernel`, or using a low-level primitive like `send` or `instance_eval`.
45+
* ```ruby
46+
* puts "hello world"
47+
* Kernel.puts "hello world"
48+
* 5.instance_eval { puts "hello world" }
49+
* 5.send("puts", "hello world")
50+
* ```
51+
*/
52+
private predicate isPrivateKernelMethod(string method) {
53+
method in [
54+
"Array", "Complex", "Float", "Hash", "Integer", "Rational", "String", "__callee__", "__dir__",
55+
"__method__", "`", "abort", "at_exit", "autoload", "autoload?", "binding", "block_given?",
56+
"callcc", "caller", "caller_locations", "catch", "chomp", "chop", "eval", "exec", "exit",
57+
"exit!", "fail", "fork", "format", "gets", "global_variables", "gsub", "iterator?", "lambda",
58+
"load", "local_variables", "loop", "open", "p", "pp", "print", "printf", "proc", "putc",
59+
"puts", "raise", "rand", "readline", "readlines", "require", "require_relative", "select",
60+
"set_trace_func", "sleep", "spawn", "sprintf", "srand", "sub", "syscall", "system", "test",
61+
"throw", "trace_var", "trap", "untrace_var", "warn"
62+
]
63+
}
64+
865
/**
966
* A system command executed via subshell literal syntax.
1067
* E.g.
@@ -73,21 +130,11 @@ class SubshellHeredocExecution extends SystemCommandExecution::Range {
73130
* Ruby documentation: https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-system
74131
*/
75132
class KernelSystemCall extends SystemCommandExecution::Range {
76-
MethodCall methodCall;
133+
KernelMethodCall methodCall;
77134

78135
KernelSystemCall() {
79136
methodCall.getMethodName() = "system" and
80-
this.asExpr().getExpr() = methodCall and
81-
// `Kernel.system` can be reached via `Kernel.system` or just `system`
82-
// (if there's no other method by the same name in scope).
83-
(
84-
this = API::getTopLevelMember("Kernel").getAMethodCall("system")
85-
or
86-
// we assume that if there's no obvious target for this method call, then it must refer to Kernel.system.
87-
not exists(DataFlowCallable method, DataFlowCall call |
88-
viableCallable(call) = method and call.getExpr() = methodCall
89-
)
90-
)
137+
this.asExpr().getExpr() = methodCall
91138
}
92139

93140
override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() }
@@ -104,22 +151,11 @@ class KernelSystemCall extends SystemCommandExecution::Range {
104151
* Ruby documentation: https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-exec
105152
*/
106153
class KernelExecCall extends SystemCommandExecution::Range {
107-
MethodCall methodCall;
154+
KernelMethodCall methodCall;
108155

109156
KernelExecCall() {
110157
methodCall.getMethodName() = "exec" and
111-
this.asExpr().getExpr() = methodCall and
112-
// `Kernel.exec` can be reached via `Kernel.exec`, `Process.exec` or just `exec`
113-
// (if there's no other method by the same name in scope).
114-
(
115-
this = API::getTopLevelMember(["Kernel", "Process"]).getAMethodCall("exec")
116-
or
117-
// we assume that if there's no obvious target for this method call, then
118-
// it must refer to Kernel.exec.
119-
not exists(DataFlowCallable method, DataFlowCall call |
120-
viableCallable(call) = method and call.getExpr() = methodCall
121-
)
122-
)
158+
this.asExpr().getExpr() = methodCall
123159
}
124160

125161
override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() }
@@ -141,20 +177,11 @@ class KernelExecCall extends SystemCommandExecution::Range {
141177
* ```
142178
*/
143179
class KernelSpawnCall extends SystemCommandExecution::Range {
144-
MethodCall methodCall;
180+
KernelMethodCall methodCall;
145181

146182
KernelSpawnCall() {
147183
methodCall.getMethodName() = "spawn" and
148-
this.asExpr().getExpr() = methodCall and
149-
// `Kernel.spawn` can be reached via `Kernel.spawn`, `Process.spawn` or just `spawn`
150-
// (if there's no other method by the same name in scope).
151-
(
152-
this = API::getTopLevelMember(["Kernel", "Process"]).getAMethodCall("spawn")
153-
or
154-
not exists(DataFlowCallable method, DataFlowCall call |
155-
viableCallable(call) = method and call.getExpr() = methodCall
156-
)
157-
)
184+
this.asExpr().getExpr() = methodCall
158185
}
159186

160187
override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() }

0 commit comments

Comments
 (0)