Skip to content

Commit eebba36

Browse files
authored
Merge pull request #9708 from github/nickrolfe/pathname
Ruby: model the standard library's `Pathname` class
2 parents cc5f59f + dbd6607 commit eebba36

File tree

8 files changed

+756
-0
lines changed

8 files changed

+756
-0
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44

55
import stdlib.Open3
66
import stdlib.Logger
7+
import stdlib.Pathname
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/** Modeling of the `Pathname` class from the Ruby standard library. */
2+
3+
private import codeql.ruby.AST
4+
private import codeql.ruby.ApiGraphs
5+
private import codeql.ruby.Concepts
6+
private import codeql.ruby.DataFlow
7+
private import codeql.ruby.dataflow.FlowSummary
8+
private import codeql.ruby.dataflow.internal.DataFlowDispatch
9+
private import codeql.ruby.frameworks.data.ModelsAsData
10+
11+
/**
12+
* Modeling of the `Pathname` class from the Ruby standard library.
13+
*
14+
* https://docs.ruby-lang.org/en/3.1/Pathname.html
15+
*/
16+
module Pathname {
17+
/**
18+
* An instance of the `Pathname` class. For example, in
19+
*
20+
* ```rb
21+
* pn = Pathname.new "foo.txt'"
22+
* puts pn.read
23+
* ```
24+
*
25+
* there are three `PathnameInstance`s - the call to `Pathname.new`, the
26+
* assignment `pn = ...`, and the read access to `pn` on the second line.
27+
*
28+
* Every `PathnameInstance` is considered to be a `FileNameSource`.
29+
*/
30+
class PathnameInstance extends FileNameSource, DataFlow::Node {
31+
PathnameInstance() { this = pathnameInstance() }
32+
}
33+
34+
private DataFlow::Node pathnameInstance() {
35+
// A call to `Pathname.new`.
36+
result = API::getTopLevelMember("Pathname").getAnInstantiation()
37+
or
38+
// Class methods on `Pathname` that return a new `Pathname`.
39+
result = API::getTopLevelMember("Pathname").getAMethodCall(["getwd", "pwd",])
40+
or
41+
// Instance methods on `Pathname` that return a new `Pathname`.
42+
exists(DataFlow::CallNode c | result = c |
43+
c.getReceiver() = pathnameInstance() and
44+
c.getMethodName() =
45+
[
46+
"+", "/", "basename", "cleanpath", "expand_path", "join", "realpath",
47+
"relative_path_from", "sub", "sub_ext", "to_path"
48+
]
49+
)
50+
or
51+
exists(DataFlow::Node inst |
52+
inst = pathnameInstance() and
53+
inst.(DataFlow::LocalSourceNode).flowsTo(result)
54+
)
55+
}
56+
57+
/** A call where the receiver is a `Pathname`. */
58+
class PathnameCall extends DataFlow::CallNode {
59+
PathnameCall() { this.getReceiver() instanceof PathnameInstance }
60+
}
61+
62+
/**
63+
* A call to `Pathname#open` or `Pathname#opendir`, considered as a
64+
* `FileSystemAccess`.
65+
*/
66+
class PathnameOpen extends FileSystemAccess::Range, PathnameCall {
67+
PathnameOpen() { this.getMethodName() = ["open", "opendir"] }
68+
69+
override DataFlow::Node getAPathArgument() { result = this.getReceiver() }
70+
}
71+
72+
/** A call to `Pathname#read`, considered as a `FileSystemReadAccess`. */
73+
class PathnameRead extends FileSystemReadAccess::Range, PathnameCall {
74+
PathnameRead() { this.getMethodName() = "read" }
75+
76+
// The path is the receiver (the `Pathname` object).
77+
override DataFlow::Node getAPathArgument() { result = this.getReceiver() }
78+
79+
// The read data is the return value of the call.
80+
override DataFlow::Node getADataNode() { result = this }
81+
}
82+
83+
/** A call to `Pathname#write`, considered as a `FileSystemWriteAccess`. */
84+
class PathnameWrite extends FileSystemWriteAccess::Range, PathnameCall {
85+
PathnameWrite() { this.getMethodName() = "write" }
86+
87+
// The path is the receiver (the `Pathname` object).
88+
override DataFlow::Node getAPathArgument() { result = this.getReceiver() }
89+
90+
// The data to write is the 0th argument.
91+
override DataFlow::Node getADataNode() { result = this.getArgument(0) }
92+
}
93+
94+
/** A call to `Pathname#to_s`, considered as a `FileNameSource`. */
95+
class PathnameToSFilenameSource extends FileNameSource, PathnameCall {
96+
PathnameToSFilenameSource() { this.getMethodName() = "to_s" }
97+
}
98+
99+
private class PathnamePermissionModification extends FileSystemPermissionModification::Range,
100+
PathnameCall {
101+
private DataFlow::Node permissionArg;
102+
103+
PathnamePermissionModification() {
104+
exists(string methodName | this.getMethodName() = methodName |
105+
methodName = ["chmod", "mkdir"] and permissionArg = this.getArgument(0)
106+
or
107+
methodName = "mkpath" and permissionArg = this.getKeywordArgument("mode")
108+
or
109+
methodName = "open" and permissionArg = this.getArgument(1)
110+
// TODO: defaults for optional args? This may depend on the umask
111+
)
112+
}
113+
114+
override DataFlow::Node getAPermissionNode() { result = permissionArg }
115+
}
116+
117+
/**
118+
* Type summaries for the `Pathname` class, i.e. method calls that produce new
119+
* `Pathname` instances.
120+
*/
121+
private class PathnameTypeSummary extends ModelInput::TypeModelCsv {
122+
override predicate row(string row) {
123+
// package1;type1;package2;type2;path
124+
row =
125+
[
126+
// Pathname.new : Pathname
127+
";Pathname;;;Member[Pathname].Instance",
128+
// Pathname#+(path) : Pathname
129+
";Pathname;;Pathname;Method[+].ReturnValue",
130+
// Pathname#/(path) : Pathname
131+
";Pathname;;Pathname;Method[/].ReturnValue",
132+
// Pathname#basename(path) : Pathname
133+
";Pathname;;Pathname;Method[basename].ReturnValue",
134+
// Pathname#cleanpath(path) : Pathname
135+
";Pathname;;Pathname;Method[cleanpath].ReturnValue",
136+
// Pathname#expand_path(path) : Pathname
137+
";Pathname;;Pathname;Method[expand_path].ReturnValue",
138+
// Pathname#join(path) : Pathname
139+
";Pathname;;Pathname;Method[join].ReturnValue",
140+
// Pathname#realpath(path) : Pathname
141+
";Pathname;;Pathname;Method[realpath].ReturnValue",
142+
// Pathname#relative_path_from(path) : Pathname
143+
";Pathname;;Pathname;Method[relative_path_from].ReturnValue",
144+
// Pathname#sub(path) : Pathname
145+
";Pathname;;Pathname;Method[sub].ReturnValue",
146+
// Pathname#sub_ext(path) : Pathname
147+
";Pathname;;Pathname;Method[sub_ext].ReturnValue",
148+
// Pathname#to_path(path) : Pathname
149+
";Pathname;;Pathname;Method[to_path].ReturnValue",
150+
]
151+
}
152+
}
153+
154+
/** Taint flow summaries for the `Pathname` class. */
155+
private class PathnameTaintSummary extends ModelInput::SummaryModelCsv {
156+
override predicate row(string row) {
157+
row =
158+
[
159+
// Pathname.new(path)
160+
";;Member[Pathname].Method[new];Argument[0];ReturnValue;taint",
161+
// Pathname#dirname
162+
";Pathname;Method[dirname];Argument[self];ReturnValue;taint",
163+
// Pathname#each_filename
164+
";Pathname;Method[each_filename];Argument[self];Argument[block].Parameter[0];taint",
165+
// Pathname#expand_path
166+
";Pathname;Method[expand_path];Argument[self];ReturnValue;taint",
167+
// Pathname#join
168+
";Pathname;Method[join];Argument[self,any];ReturnValue;taint",
169+
// Pathname#parent
170+
";Pathname;Method[parent];Argument[self];ReturnValue;taint",
171+
// Pathname#realpath
172+
";Pathname;Method[realpath];Argument[self];ReturnValue;taint",
173+
// Pathname#relative_path_from
174+
";Pathname;Method[relative_path_from];Argument[self];ReturnValue;taint",
175+
// Pathname#to_path
176+
";Pathname;Method[to_path];Argument[self];ReturnValue;taint",
177+
// Pathname#basename
178+
";Pathname;Method[basename];Argument[self];ReturnValue;taint",
179+
// Pathname#cleanpath
180+
";Pathname;Method[cleanpath];Argument[self];ReturnValue;taint",
181+
// Pathname#sub
182+
";Pathname;Method[sub];Argument[self];ReturnValue;taint",
183+
// Pathname#sub_ext
184+
";Pathname;Method[sub_ext];Argument[self];ReturnValue;taint",
185+
]
186+
}
187+
}
188+
}

0 commit comments

Comments
 (0)