Skip to content

Commit cbc14cc

Browse files
committed
Make KernelSystemCall more specific
Test that calls to`system` on modules other than `Kernel` are excluded, such as in this example: module Foo def self.system(*args); end end # This is not a call to Kernel.system Foo.system("bar")
1 parent fb23a2e commit cbc14cc

File tree

2 files changed

+36
-18
lines changed

2 files changed

+36
-18
lines changed

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

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ class SubshellHeredocExecution extends SystemCommandExecution::Range {
4848
/**
4949
* A system command executed via the `Kernel.system` method.
5050
* `Kernel.system` accepts three argument forms:
51-
* - A single string. If it contains no shell meta characters, keywords or builtins, it is executed directly in a subprocess.
51+
* - A single string. If it contains no shell meta characters, keywords or
52+
* builtins, it is executed directly in a subprocess.
5253
* Otherwise, it is executed in a subshell.
5354
* ```ruby
5455
* system("cat foo.txt | tail")
@@ -63,7 +64,8 @@ class SubshellHeredocExecution extends SystemCommandExecution::Range {
6364
* ```ruby
6465
* system(["cat", "cat"], "foo.txt")
6566
* ```
66-
* In addition, `Kernel.system` accepts an optional environment hash as the first argument and and optional options hash as the last argument.
67+
* In addition, `Kernel.system` accepts an optional environment hash as the
68+
* first argument and an optional options hash as the last argument.
6769
* We don't yet distinguish between these arguments and the command arguments.
6870
* ```ruby
6971
* system({"FOO" => "BAR"}, "cat foo.txt | tail", {unsetenv_others: true})
@@ -110,11 +112,10 @@ class KernelExecCall extends SystemCommandExecution::Range {
110112
// `Kernel.exec` can be reached via `Kernel.exec`, `Process.exec` or just `exec`
111113
// (if there's no other method by the same name in scope).
112114
(
113-
this = API::getTopLevelMember("Kernel").getAMethodCall("exec")
115+
this = API::getTopLevelMember(["Kernel", "Process"]).getAMethodCall("exec")
114116
or
115-
this = API::getTopLevelMember("Process").getAMethodCall("exec")
116-
or
117-
// we assume that if there's no obvious target for this method call, then it must refer to Kernel.exec.
117+
// we assume that if there's no obvious target for this method call, then
118+
// it must refer to Kernel.exec.
118119
not exists(DataFlowCallable method, DataFlowCall call |
119120
viableCallable(call) = method and call.getExpr() = methodCall
120121
)
@@ -131,7 +132,8 @@ class KernelExecCall extends SystemCommandExecution::Range {
131132

132133
/**
133134
* A system command executed via the `Kernel.spawn` method.
134-
* `Kernel.spawn` takes the same argument forms as `Kernel.system`. See `KernelSystemCall` for details.
135+
* `Kernel.spawn` takes the same argument forms as `Kernel.system`.
136+
* See `KernelSystemCall` for details.
135137
* Ruby documentation: https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-spawn
136138
* TODO: document and handle the env and option arguments.
137139
* ```
@@ -147,9 +149,7 @@ class KernelSpawnCall extends SystemCommandExecution::Range {
147149
// `Kernel.spawn` can be reached via `Kernel.spawn`, `Process.spawn` or just `spawn`
148150
// (if there's no other method by the same name in scope).
149151
(
150-
this = API::getTopLevelMember("Kernel").getAMethodCall("spawn")
151-
or
152-
this = API::getTopLevelMember("Process").getAMethodCall("spawn")
152+
this = API::getTopLevelMember(["Kernel", "Process"]).getAMethodCall("spawn")
153153
or
154154
not exists(DataFlowCallable method, DataFlowCall call |
155155
viableCallable(call) = method and call.getExpr() = methodCall
@@ -170,13 +170,12 @@ class Open3Call extends SystemCommandExecution::Range {
170170

171171
Open3Call() {
172172
this.asExpr().getExpr() = methodCall and
173-
exists(string methodName |
174-
methodName in [
175-
"popen3", "popen2", "popen2e", "capture3", "capture2", "capture2e", "pipeline_rw",
176-
"pipeline_r", "pipeline_w", "pipeline_start", "pipeline"
177-
] and
178-
this = API::getTopLevelMember("Open3").getAMethodCall(methodName)
179-
)
173+
this =
174+
API::getTopLevelMember("Open3")
175+
.getAMethodCall([
176+
"popen3", "popen2", "popen2e", "capture3", "capture2", "capture2e", "pipeline_rw",
177+
"pipeline_r", "pipeline_w", "pipeline_start", "pipeline"
178+
])
180179
}
181180

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

ql/test/library-tests/frameworks/CommandExecution.rb

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,23 @@
6666

6767
<<`EOF`
6868
echo foo
69-
EOF
69+
EOF
70+
71+
module MockSystem
72+
def system(*args)
73+
args
74+
end
75+
76+
def self.system(*args)
77+
args
78+
end
79+
end
80+
81+
class Foo
82+
include MockSystem
83+
84+
def run
85+
system("ls")
86+
MockSystem.system("ls")
87+
end
88+
end

0 commit comments

Comments
 (0)