Skip to content

Commit ef9442d

Browse files
authored
Merge branch 'main' into experimental-archive-api
2 parents 056fa71 + 7c5a838 commit ef9442d

File tree

16 files changed

+686
-5
lines changed

16 files changed

+686
-5
lines changed

ruby/ql/lib/codeql/ruby/Frameworks.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ private import codeql.ruby.frameworks.Files
1616
private import codeql.ruby.frameworks.HttpClients
1717
private import codeql.ruby.frameworks.XmlParsing
1818
private import codeql.ruby.frameworks.ActionDispatch
19+
private import codeql.ruby.frameworks.PosixSpawn

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

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
private import ruby
77
private import codeql.ruby.Concepts
88
private import codeql.ruby.DataFlow
9+
private import codeql.ruby.dataflow.FlowSummary
10+
private import codeql.ruby.Concepts
11+
private import codeql.ruby.ApiGraphs
12+
private import codeql.ruby.frameworks.stdlib.Logger::Logger as StdlibLogger
913

1014
/**
1115
* Modeling for `ActiveSupport`.
@@ -32,6 +36,107 @@ module ActiveSupport {
3236

3337
override DataFlow::Node getCode() { result = this.getReceiver() }
3438
}
39+
40+
/**
41+
* Flow summary for methods which transform the receiver in some way, possibly preserving taint.
42+
*/
43+
private class StringTransformSummary extends SummarizedCallable {
44+
// We're modelling a lot of different methods, so we make up a name for this summary.
45+
StringTransformSummary() { this = "ActiveSupportStringTransform" }
46+
47+
override MethodCall getACall() {
48+
result.getMethodName() =
49+
[
50+
"camelize", "camelcase", "classify", "dasherize", "deconstantize", "demodulize",
51+
"foreign_key", "humanize", "indent", "parameterize", "pluralize", "singularize",
52+
"squish", "strip_heredoc", "tableize", "titlecase", "titleize", "underscore",
53+
"upcase_first"
54+
]
55+
}
56+
57+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
58+
input = "Argument[self]" and output = "ReturnValue" and preservesValue = false
59+
}
60+
}
61+
}
62+
63+
/**
64+
* Extensions to the `Enumerable` module.
65+
*/
66+
module Enumerable {
67+
private class ArrayIndex extends int {
68+
ArrayIndex() { this = any(DataFlow::Content::KnownElementContent c).getIndex().getInt() }
69+
}
70+
71+
private class CompactBlankSummary extends SimpleSummarizedCallable {
72+
CompactBlankSummary() { this = "compact_blank" }
73+
74+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
75+
input = "Argument[self].Element[any]" and
76+
output = "ReturnValue.Element[?]" and
77+
preservesValue = true
78+
}
79+
}
80+
81+
private class ExcludingSummary extends SimpleSummarizedCallable {
82+
ExcludingSummary() { this = ["excluding", "without"] }
83+
84+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
85+
input = "Argument[self].Element[any]" and
86+
output = "ReturnValue.Element[?]" and
87+
preservesValue = true
88+
}
89+
}
90+
91+
private class InOrderOfSummary extends SimpleSummarizedCallable {
92+
InOrderOfSummary() { this = "in_order_of" }
93+
94+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
95+
input = "Argument[self].Element[any]" and
96+
output = "ReturnValue.Element[?]" and
97+
preservesValue = true
98+
}
99+
}
100+
101+
/**
102+
* Like `Array#push` but doesn't update the receiver.
103+
*/
104+
private class IncludingSummary extends SimpleSummarizedCallable {
105+
IncludingSummary() { this = "including" }
106+
107+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
108+
(
109+
exists(ArrayIndex i |
110+
input = "Argument[self].Element[" + i + "]" and
111+
output = "ReturnValue.Element[" + i + "]"
112+
)
113+
or
114+
input = "Argument[self].Element[?]" and
115+
output = "ReturnValue.Element[?]"
116+
or
117+
exists(int i | i in [0 .. (mc.getNumberOfArguments() - 1)] |
118+
input = "Argument[" + i + "]" and
119+
output = "ReturnValue.Element[?]"
120+
)
121+
) and
122+
preservesValue = true
123+
}
124+
}
125+
// TODO: index_by, index_with, pick, pluck (they require Hash dataflow)
126+
}
127+
}
128+
129+
/**
130+
* `ActiveSupport::Logger`
131+
*/
132+
module Logger {
133+
private class ActiveSupportLoggerInstantiation extends StdlibLogger::LoggerInstantiation {
134+
ActiveSupportLoggerInstantiation() {
135+
this =
136+
API::getTopLevelMember("ActiveSupport")
137+
.getMember(["Logger", "TaggedLogging"])
138+
.getAnInstantiation()
139+
}
35140
}
36141
}
37142
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Provides modeling for the `posix-spawn` gem.
3+
* Version: 0.3.15
4+
*/
5+
6+
private import codeql.ruby.Concepts
7+
private import codeql.ruby.ApiGraphs
8+
private import codeql.ruby.DataFlow
9+
private import codeql.ruby.controlflow.CfgNodes
10+
11+
/**
12+
* Provides modeling for the `posix-spawn` gem.
13+
* Version: 0.3.15
14+
*/
15+
module PosixSpawn {
16+
private API::Node posixSpawnModule() {
17+
result = API::getTopLevelMember("POSIX").getMember("Spawn")
18+
}
19+
20+
/**
21+
* A call to `POSIX::Spawn::Child.new` or `POSIX::Spawn::Child.build`.
22+
*/
23+
class ChildCall extends SystemCommandExecution::Range, DataFlow::CallNode {
24+
ChildCall() {
25+
this =
26+
[
27+
posixSpawnModule().getMember("Child").getAMethodCall("build"),
28+
posixSpawnModule().getMember("Child").getAnInstantiation()
29+
]
30+
}
31+
32+
override DataFlow::Node getAnArgument() {
33+
result = this.getArgument(_) and not result.asExpr() instanceof ExprNodes::PairCfgNode
34+
}
35+
36+
override predicate isShellInterpreted(DataFlow::Node arg) { none() }
37+
}
38+
39+
/**
40+
* A call to `POSIX::Spawn.spawn` or a related method.
41+
*/
42+
class SystemCall extends SystemCommandExecution::Range, DataFlow::CallNode {
43+
SystemCall() {
44+
this =
45+
posixSpawnModule()
46+
.getAMethodCall(["spawn", "fspawn", "popen4", "pspawn", "system", "_pspawn", "`"])
47+
}
48+
49+
override DataFlow::Node getAnArgument() { this.argument(result) }
50+
51+
// From the docs:
52+
// When only command is given and includes a space character, the command
53+
// text is executed by the system shell interpreter.
54+
// This means the following signatures are shell interpreted:
55+
//
56+
// spawn(cmd)
57+
// spawn(cmd, opts)
58+
// spawn(env, cmd)
59+
// spawn(env, cmd, opts)
60+
//
61+
// env and opts will be hashes. We over-approximate by assuming the argument
62+
// is shell interpreted unless there is another argument with a string
63+
// constant value.
64+
override predicate isShellInterpreted(DataFlow::Node arg) {
65+
not exists(DataFlow::Node otherArg |
66+
otherArg != arg and
67+
this.argument(arg) and
68+
this.argument(otherArg) and
69+
otherArg.asExpr().getConstantValue().isString(_)
70+
)
71+
}
72+
73+
private predicate argument(DataFlow::Node arg) {
74+
arg = this.getArgument(_) and
75+
not arg.asExpr() instanceof ExprNodes::HashLiteralCfgNode and
76+
not arg.asExpr() instanceof ExprNodes::ArrayLiteralCfgNode and
77+
not arg.asExpr() instanceof ExprNodes::PairCfgNode
78+
}
79+
}
80+
}

ruby/ql/lib/codeql/ruby/frameworks/stdlib/Logger.qll

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ private import codeql.ruby.dataflow.internal.DataFlowDispatch
1616
module Logger {
1717
/** A reference to a `Logger` instance */
1818
private DataFlow::Node loggerInstance() {
19-
result = API::getTopLevelMember("Logger").getAnInstantiation()
19+
result instanceof LoggerInstantiation
2020
or
2121
exists(DataFlow::Node inst |
2222
inst = loggerInstance() and
@@ -33,11 +33,29 @@ module Logger {
3333
)
3434
}
3535

36+
/**
37+
* An instantiation of a logger that responds to the std lib logging methods.
38+
* This can be extended to recognize additional instances that conform to the
39+
* same interface.
40+
*/
41+
abstract class LoggerInstantiation extends DataFlow::Node { }
42+
43+
/**
44+
* An instantiation of the std lib `Logger` class.
45+
*/
46+
private class StdlibLoggerInstantiation extends LoggerInstantiation {
47+
StdlibLoggerInstantiation() { this = API::getTopLevelMember("Logger").getAnInstantiation() }
48+
}
49+
50+
private class LoggerInstance extends DataFlow::Node {
51+
LoggerInstance() { this = loggerInstance() }
52+
}
53+
3654
/**
3755
* A call to a `Logger` instance method that causes a message to be logged.
3856
*/
3957
abstract class LoggerLoggingCall extends Logging::Range, DataFlow::CallNode {
40-
LoggerLoggingCall() { this.getReceiver() = loggerInstance() }
58+
LoggerLoggingCall() { this.getReceiver() instanceof LoggerInstance }
4159
}
4260

4361
/**
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
systemCalls
2+
| PosixSpawn.rb:1:1:1:32 | call to popen4 | PosixSpawn.rb:1:22:1:25 | "ls" | false |
3+
| PosixSpawn.rb:1:1:1:32 | call to popen4 | PosixSpawn.rb:1:28:1:31 | "-l" | false |
4+
| PosixSpawn.rb:2:1:2:31 | call to popen4 | PosixSpawn.rb:2:21:2:24 | "ls" | false |
5+
| PosixSpawn.rb:2:1:2:31 | call to popen4 | PosixSpawn.rb:2:27:2:30 | "-l" | false |
6+
| PosixSpawn.rb:7:1:7:40 | call to spawn | PosixSpawn.rb:7:20:7:39 | * ... | true |
7+
| PosixSpawn.rb:8:1:8:30 | call to spawn | PosixSpawn.rb:8:21:8:29 | "sleep 5" | true |
8+
| PosixSpawn.rb:9:1:9:23 | call to spawn | PosixSpawn.rb:9:20:9:22 | call to cmd | true |
9+
| PosixSpawn.rb:10:1:10:29 | call to spawn | PosixSpawn.rb:10:20:10:22 | call to env | false |
10+
| PosixSpawn.rb:10:1:10:29 | call to spawn | PosixSpawn.rb:10:25:10:28 | "ls" | true |
11+
| PosixSpawn.rb:15:1:15:60 | call to system | PosixSpawn.rb:15:21:15:25 | "foo" | false |
12+
| PosixSpawn.rb:15:1:15:60 | call to system | PosixSpawn.rb:15:28:15:32 | "bar" | false |
13+
| PosixSpawn.rb:15:1:15:60 | call to system | PosixSpawn.rb:15:35:15:44 | "--a-flag" | false |
14+
| PosixSpawn.rb:15:1:15:60 | call to system | PosixSpawn.rb:15:47:15:52 | call to before | false |
15+
| PosixSpawn.rb:15:1:15:60 | call to system | PosixSpawn.rb:15:55:15:59 | call to after | false |
16+
| PosixSpawn.rb:17:1:17:28 | call to fspawn | PosixSpawn.rb:17:21:17:27 | call to command | true |
17+
| PosixSpawn.rb:18:1:18:28 | call to pspawn | PosixSpawn.rb:18:21:18:27 | call to command | true |
18+
| PosixSpawn.rb:19:1:19:28 | call to popen4 | PosixSpawn.rb:19:21:19:27 | call to command | true |
19+
| PosixSpawn.rb:21:1:21:28 | call to ` | PosixSpawn.rb:21:16:21:20 | "foo" | false |
20+
| PosixSpawn.rb:21:1:21:28 | call to ` | PosixSpawn.rb:21:23:21:27 | "bar" | false |
21+
childCalls
22+
| PosixSpawn.rb:4:1:4:77 | call to new | PosixSpawn.rb:4:25:4:39 | call to [] | false |
23+
| PosixSpawn.rb:4:1:4:77 | call to new | PosixSpawn.rb:4:42:4:51 | ... + ... | false |
24+
| PosixSpawn.rb:4:1:4:77 | call to new | PosixSpawn.rb:4:54:4:58 | * ... | false |
25+
| PosixSpawn.rb:5:1:5:80 | call to new | PosixSpawn.rb:5:25:5:32 | * ... | false |
26+
| PosixSpawn.rb:12:1:12:35 | call to new | PosixSpawn.rb:12:25:12:28 | "ls" | false |
27+
| PosixSpawn.rb:12:1:12:35 | call to new | PosixSpawn.rb:12:31:12:34 | "-l" | false |
28+
| PosixSpawn.rb:13:1:13:38 | call to build | PosixSpawn.rb:13:27:13:32 | "echo" | false |
29+
| PosixSpawn.rb:13:1:13:38 | call to build | PosixSpawn.rb:13:35:13:37 | call to msg | false |
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import ruby
2+
import codeql.ruby.frameworks.PosixSpawn
3+
import codeql.ruby.DataFlow
4+
5+
query predicate systemCalls(
6+
PosixSpawn::SystemCall call, DataFlow::Node arg, boolean shellInterpreted
7+
) {
8+
arg = call.getAnArgument() and
9+
if call.isShellInterpreted(arg) then shellInterpreted = true else shellInterpreted = false
10+
}
11+
12+
query predicate childCalls(PosixSpawn::ChildCall call, DataFlow::Node arg, boolean shellInterpreted) {
13+
arg = call.getAnArgument() and
14+
if call.isShellInterpreted(arg) then shellInterpreted = true else shellInterpreted = false
15+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
POSIX::Spawn::popen4("ls", "-l")
2+
POSIX::Spawn.popen4("ls", "-l")
3+
4+
POSIX::Spawn::Child.new({'ENV' => @var}, "foo/"+cmd, *argv, :chdir=>root_dir)
5+
POSIX::Spawn::Child.new(*command, input: options[:stdin].to_s, timeout: timeout)
6+
7+
POSIX::Spawn.spawn(*(argv+[{:in => f}]))
8+
POSIX::Spawn::spawn('sleep 5')
9+
POSIX::Spawn.spawn(cmd)
10+
POSIX::Spawn.spawn(env, "ls")
11+
12+
POSIX::Spawn::Child.new("ls", "-l")
13+
POSIX::Spawn::Child.build("echo", msg)
14+
15+
POSIX::Spawn.system("foo", "bar", "--a-flag", before, after)
16+
17+
POSIX::Spawn.fspawn(command)
18+
POSIX::Spawn.pspawn(command)
19+
POSIX::Spawn.popen4(command)
20+
21+
POSIX::Spawn.`("foo", "bar")

ruby/ql/test/library-tests/frameworks/active_support.rb

Lines changed: 0 additions & 3 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
1+
constantizeCalls
12
| active_support.rb:1:1:1:22 | call to constantize | active_support.rb:1:1:1:10 | "Foo::Bar" |
23
| active_support.rb:3:1:3:13 | call to constantize | active_support.rb:3:1:3:1 | call to a |
4+
loggerInstantiations
5+
| active_support.rb:5:1:5:33 | call to new |
6+
| active_support.rb:6:1:6:40 | call to new |
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import codeql.ruby.frameworks.ActiveSupport
22
import codeql.ruby.DataFlow
3+
import codeql.ruby.frameworks.stdlib.Logger
34

45
query DataFlow::Node constantizeCalls(ActiveSupport::CoreExtensions::String::Constantize c) {
56
result = c.getCode()
67
}
8+
9+
query predicate loggerInstantiations(Logger::LoggerInstantiation l) { any() }

0 commit comments

Comments
 (0)