mirror of
https://github.com/github/codeql.git
synced 2026-05-01 03:35:13 +02:00
Ruby: modeling of some file-related concepts for the Pathname class
This commit is contained in:
@@ -2,10 +2,10 @@
|
||||
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.dataflow.internal.DataFlowDispatch
|
||||
private import codeql.ruby.controlflow.CfgNodes
|
||||
|
||||
/**
|
||||
* Modeling of the `Pathname` class from the Ruby standard library.
|
||||
@@ -13,7 +13,107 @@ private import codeql.ruby.controlflow.CfgNodes
|
||||
* https://docs.ruby-lang.org/en/3.1/Pathname.html
|
||||
*/
|
||||
module Pathname {
|
||||
/// Flow summary for `Pathname.new`.
|
||||
/**
|
||||
* An instance of the `Pathname` class. For example, in
|
||||
*
|
||||
* ```rb
|
||||
* pn = Pathname.new "foo.txt'"
|
||||
* puts pn.read
|
||||
* ```
|
||||
*
|
||||
* there are three `PathnameInstance`s – the call to `Pathname.new`, the
|
||||
* assignment `pn = ...`, and the read access to `pn` on the second line.
|
||||
*
|
||||
* Every `PathnameInstance` is considered to be a `FileNameSource`.
|
||||
*/
|
||||
class PathnameInstance extends FileNameSource, DataFlow::Node {
|
||||
PathnameInstance() { this = pathnameInstance() }
|
||||
}
|
||||
|
||||
private DataFlow::Node pathnameInstance() {
|
||||
// A call to `Pathname.new`.
|
||||
result = API::getTopLevelMember("Pathname").getAnInstantiation()
|
||||
or
|
||||
// Class methods on `Pathname` that return a new `Pathname`.
|
||||
result = API::getTopLevelMember("Pathname").getAMethodCall(["getwd", "pwd",])
|
||||
or
|
||||
// Instance methods on `Pathname` that return a new `Pathname`.
|
||||
exists(DataFlow::CallNode c | result = c |
|
||||
c.getReceiver() = pathnameInstance() and
|
||||
c.getMethodName() =
|
||||
[
|
||||
"+", "/", "basename", "cleanpath", "expand_path", "join", "realpath",
|
||||
"relative_path_from", "sub", "sub_ext", "to_path"
|
||||
]
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node inst |
|
||||
inst = pathnameInstance() and
|
||||
inst.(DataFlow::LocalSourceNode).flowsTo(result)
|
||||
)
|
||||
}
|
||||
|
||||
/** A call where the receiver is a `Pathname`. */
|
||||
class PathnameCall extends DataFlow::CallNode {
|
||||
PathnameCall() { this.getReceiver() instanceof PathnameInstance }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `Pathname#open` or `Pathname#opendir`, considered as a
|
||||
* `FileSystemAccess`.
|
||||
*/
|
||||
class PathnameOpen extends FileSystemAccess::Range, PathnameCall {
|
||||
PathnameOpen() { this.getMethodName() = ["open", "opendir"] }
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getReceiver() }
|
||||
}
|
||||
|
||||
/** A call to `Pathname#read`, considered as a `FileSystemReadAccess`. */
|
||||
class PathnameRead extends FileSystemReadAccess::Range, PathnameCall {
|
||||
PathnameRead() { this.getMethodName() = "read" }
|
||||
|
||||
// The path is the receiver (the `Pathname` object).
|
||||
override DataFlow::Node getAPathArgument() { result = this.getReceiver() }
|
||||
|
||||
// The read data is the return value of the call.
|
||||
override DataFlow::Node getADataNode() { result = this }
|
||||
}
|
||||
|
||||
/** A call to `Pathname#write`, considered as a `FileSystemWriteAccess`. */
|
||||
class PathnameWrite extends FileSystemWriteAccess::Range, PathnameCall {
|
||||
PathnameWrite() { this.getMethodName() = "write" }
|
||||
|
||||
// The path is the receiver (the `Pathname` object).
|
||||
override DataFlow::Node getAPathArgument() { result = this.getReceiver() }
|
||||
|
||||
// The data to write is the 0th argument.
|
||||
override DataFlow::Node getADataNode() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/** A call to `Pathname#to_s`, considered as a `FileNameSource`. */
|
||||
class PathnameToSFilenameSource extends FileNameSource, PathnameCall {
|
||||
PathnameToSFilenameSource() { this.getMethodName() = "to_s" }
|
||||
}
|
||||
|
||||
private class PathnamePermissionModification extends FileSystemPermissionModification::Range,
|
||||
PathnameCall {
|
||||
private DataFlow::Node permissionArg;
|
||||
|
||||
PathnamePermissionModification() {
|
||||
exists(string methodName | this.getMethodName() = methodName |
|
||||
methodName = ["chmod", "mkdir"] and permissionArg = this.getArgument(0)
|
||||
or
|
||||
methodName = "mkpath" and permissionArg = this.getKeywordArgument("mode")
|
||||
or
|
||||
methodName = "open" and permissionArg = this.getArgument(1)
|
||||
// TODO: defaults for optional args? This may depend on the umask
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAPermissionNode() { result = permissionArg }
|
||||
}
|
||||
|
||||
/** Flow summary for `Pathname.new`. */
|
||||
private class NewSummary extends SummarizedCallable {
|
||||
NewSummary() { this = "Pathname.new" }
|
||||
|
||||
@@ -28,7 +128,7 @@ module Pathname {
|
||||
}
|
||||
}
|
||||
|
||||
/// Flow summary for `Pathname#dirname`.
|
||||
/** Flow summary for `Pathname#dirname`. */
|
||||
private class DirnameSummary extends SimpleSummarizedCallable {
|
||||
DirnameSummary() { this = "dirname" }
|
||||
|
||||
@@ -39,7 +139,7 @@ module Pathname {
|
||||
}
|
||||
}
|
||||
|
||||
/// Flow summary for `Pathname#each_filename`.
|
||||
/** Flow summary for `Pathname#each_filename`. */
|
||||
private class EachFilenameSummary extends SimpleSummarizedCallable {
|
||||
EachFilenameSummary() { this = "each_filename" }
|
||||
|
||||
@@ -50,7 +150,7 @@ module Pathname {
|
||||
}
|
||||
}
|
||||
|
||||
/// Flow summary for `Pathname#expand_path`.
|
||||
/** Flow summary for `Pathname#expand_path`. */
|
||||
private class ExpandPathSummary extends SimpleSummarizedCallable {
|
||||
ExpandPathSummary() { this = "expand_path" }
|
||||
|
||||
@@ -61,7 +161,7 @@ module Pathname {
|
||||
}
|
||||
}
|
||||
|
||||
/// Flow summary for `Pathname#join`.
|
||||
/** Flow summary for `Pathname#join`. */
|
||||
private class JoinSummary extends SimpleSummarizedCallable {
|
||||
JoinSummary() { this = "join" }
|
||||
|
||||
@@ -72,7 +172,7 @@ module Pathname {
|
||||
}
|
||||
}
|
||||
|
||||
/// Flow summary for `Pathname#parent`.
|
||||
/** Flow summary for `Pathname#parent`. */
|
||||
private class ParentSummary extends SimpleSummarizedCallable {
|
||||
ParentSummary() { this = "parent" }
|
||||
|
||||
@@ -83,7 +183,7 @@ module Pathname {
|
||||
}
|
||||
}
|
||||
|
||||
/// Flow summary for `Pathname#realpath`.
|
||||
/** Flow summary for `Pathname#realpath`. */
|
||||
private class RealpathSummary extends SimpleSummarizedCallable {
|
||||
RealpathSummary() { this = "realpath" }
|
||||
|
||||
@@ -94,7 +194,7 @@ module Pathname {
|
||||
}
|
||||
}
|
||||
|
||||
/// Flow summary for `Pathname#relative_path_from`.
|
||||
/** Flow summary for `Pathname#relative_path_from`. */
|
||||
private class RelativePathFromSummary extends SimpleSummarizedCallable {
|
||||
RelativePathFromSummary() { this = "relative_path_from" }
|
||||
|
||||
@@ -105,7 +205,7 @@ module Pathname {
|
||||
}
|
||||
}
|
||||
|
||||
/// Flow summary for `Pathname#to_path`.
|
||||
/** Flow summary for `Pathname#to_path`. */
|
||||
private class ToPathSummary extends SimpleSummarizedCallable {
|
||||
ToPathSummary() { this = "to_path" }
|
||||
|
||||
|
||||
134
ruby/ql/test/library-tests/frameworks/pathname/Pathname.expected
Normal file
134
ruby/ql/test/library-tests/frameworks/pathname/Pathname.expected
Normal file
@@ -0,0 +1,134 @@
|
||||
pathnameInstances
|
||||
| Pathname.rb:2:1:2:33 | ... = ... |
|
||||
| Pathname.rb:2:1:2:33 | ... = ... |
|
||||
| Pathname.rb:2:12:2:33 | call to new |
|
||||
| Pathname.rb:3:1:3:20 | ... = ... |
|
||||
| Pathname.rb:3:13:3:20 | foo_path |
|
||||
| Pathname.rb:4:1:4:8 | foo_path |
|
||||
| Pathname.rb:6:1:6:29 | ... = ... |
|
||||
| Pathname.rb:6:1:6:29 | ... = ... |
|
||||
| Pathname.rb:6:12:6:29 | call to new |
|
||||
| Pathname.rb:9:1:9:21 | ... = ... |
|
||||
| Pathname.rb:9:1:9:21 | ... = ... |
|
||||
| Pathname.rb:9:8:9:21 | call to getwd |
|
||||
| Pathname.rb:10:1:10:21 | ... = ... |
|
||||
| Pathname.rb:10:7:10:10 | pwd1 |
|
||||
| Pathname.rb:10:7:10:21 | ... + ... |
|
||||
| Pathname.rb:10:14:10:21 | foo_path |
|
||||
| Pathname.rb:11:1:11:21 | ... = ... |
|
||||
| Pathname.rb:11:1:11:21 | ... = ... |
|
||||
| Pathname.rb:11:7:11:10 | pwd1 |
|
||||
| Pathname.rb:11:7:11:21 | ... / ... |
|
||||
| Pathname.rb:11:14:11:21 | bar_path |
|
||||
| Pathname.rb:12:1:12:19 | ... = ... |
|
||||
| Pathname.rb:12:7:12:10 | pwd1 |
|
||||
| Pathname.rb:12:7:12:19 | call to basename |
|
||||
| Pathname.rb:13:1:13:46 | ... = ... |
|
||||
| Pathname.rb:13:7:13:36 | call to new |
|
||||
| Pathname.rb:13:7:13:46 | call to cleanpath |
|
||||
| Pathname.rb:14:1:14:26 | ... = ... |
|
||||
| Pathname.rb:14:7:14:14 | foo_path |
|
||||
| Pathname.rb:14:7:14:26 | call to expand_path |
|
||||
| Pathname.rb:15:1:15:39 | ... = ... |
|
||||
| Pathname.rb:15:7:15:10 | pwd1 |
|
||||
| Pathname.rb:15:7:15:39 | call to join |
|
||||
| Pathname.rb:16:1:16:23 | ... = ... |
|
||||
| Pathname.rb:16:7:16:14 | foo_path |
|
||||
| Pathname.rb:16:7:16:23 | call to realpath |
|
||||
| Pathname.rb:17:1:17:59 | ... = ... |
|
||||
| Pathname.rb:17:7:17:33 | call to new |
|
||||
| Pathname.rb:17:7:17:59 | call to relative_path_from |
|
||||
| Pathname.rb:18:1:18:33 | ... = ... |
|
||||
| Pathname.rb:18:1:18:33 | ... = ... |
|
||||
| Pathname.rb:18:7:18:10 | pwd1 |
|
||||
| Pathname.rb:18:7:18:33 | call to sub |
|
||||
| Pathname.rb:19:1:19:29 | ... = ... |
|
||||
| Pathname.rb:19:7:19:14 | foo_path |
|
||||
| Pathname.rb:19:7:19:29 | call to sub_ext |
|
||||
| Pathname.rb:20:1:20:22 | ... = ... |
|
||||
| Pathname.rb:20:7:20:14 | foo_path |
|
||||
| Pathname.rb:20:7:20:22 | call to to_path |
|
||||
| Pathname.rb:23:14:23:21 | foo_path |
|
||||
| Pathname.rb:26:12:26:19 | foo_path |
|
||||
| Pathname.rb:28:11:28:14 | pwd1 |
|
||||
| Pathname.rb:32:12:32:19 | foo_path |
|
||||
| Pathname.rb:35:1:35:8 | foo_path |
|
||||
| Pathname.rb:38:1:38:8 | foo_path |
|
||||
| Pathname.rb:39:12:39:19 | foo_path |
|
||||
| Pathname.rb:41:1:41:3 | p08 |
|
||||
| Pathname.rb:42:1:42:3 | p01 |
|
||||
fileSystemAccesses
|
||||
| Pathname.rb:26:12:26:24 | call to open | Pathname.rb:26:12:26:19 | foo_path |
|
||||
| Pathname.rb:28:11:28:22 | call to opendir | Pathname.rb:28:11:28:14 | pwd1 |
|
||||
| Pathname.rb:32:12:32:24 | call to read | Pathname.rb:32:12:32:19 | foo_path |
|
||||
| Pathname.rb:35:1:35:23 | call to write | Pathname.rb:35:1:35:8 | foo_path |
|
||||
| Pathname.rb:39:12:39:34 | call to open | Pathname.rb:39:12:39:19 | foo_path |
|
||||
fileNameSources
|
||||
| Pathname.rb:2:1:2:33 | ... = ... |
|
||||
| Pathname.rb:2:1:2:33 | ... = ... |
|
||||
| Pathname.rb:2:12:2:33 | call to new |
|
||||
| Pathname.rb:3:1:3:20 | ... = ... |
|
||||
| Pathname.rb:3:13:3:20 | foo_path |
|
||||
| Pathname.rb:4:1:4:8 | foo_path |
|
||||
| Pathname.rb:6:1:6:29 | ... = ... |
|
||||
| Pathname.rb:6:1:6:29 | ... = ... |
|
||||
| Pathname.rb:6:12:6:29 | call to new |
|
||||
| Pathname.rb:9:1:9:21 | ... = ... |
|
||||
| Pathname.rb:9:1:9:21 | ... = ... |
|
||||
| Pathname.rb:9:8:9:21 | call to getwd |
|
||||
| Pathname.rb:10:1:10:21 | ... = ... |
|
||||
| Pathname.rb:10:7:10:10 | pwd1 |
|
||||
| Pathname.rb:10:7:10:21 | ... + ... |
|
||||
| Pathname.rb:10:14:10:21 | foo_path |
|
||||
| Pathname.rb:11:1:11:21 | ... = ... |
|
||||
| Pathname.rb:11:1:11:21 | ... = ... |
|
||||
| Pathname.rb:11:7:11:10 | pwd1 |
|
||||
| Pathname.rb:11:7:11:21 | ... / ... |
|
||||
| Pathname.rb:11:14:11:21 | bar_path |
|
||||
| Pathname.rb:12:1:12:19 | ... = ... |
|
||||
| Pathname.rb:12:7:12:10 | pwd1 |
|
||||
| Pathname.rb:12:7:12:19 | call to basename |
|
||||
| Pathname.rb:13:1:13:46 | ... = ... |
|
||||
| Pathname.rb:13:7:13:36 | call to new |
|
||||
| Pathname.rb:13:7:13:46 | call to cleanpath |
|
||||
| Pathname.rb:14:1:14:26 | ... = ... |
|
||||
| Pathname.rb:14:7:14:14 | foo_path |
|
||||
| Pathname.rb:14:7:14:26 | call to expand_path |
|
||||
| Pathname.rb:15:1:15:39 | ... = ... |
|
||||
| Pathname.rb:15:7:15:10 | pwd1 |
|
||||
| Pathname.rb:15:7:15:39 | call to join |
|
||||
| Pathname.rb:16:1:16:23 | ... = ... |
|
||||
| Pathname.rb:16:7:16:14 | foo_path |
|
||||
| Pathname.rb:16:7:16:23 | call to realpath |
|
||||
| Pathname.rb:17:1:17:59 | ... = ... |
|
||||
| Pathname.rb:17:7:17:33 | call to new |
|
||||
| Pathname.rb:17:7:17:59 | call to relative_path_from |
|
||||
| Pathname.rb:18:1:18:33 | ... = ... |
|
||||
| Pathname.rb:18:1:18:33 | ... = ... |
|
||||
| Pathname.rb:18:7:18:10 | pwd1 |
|
||||
| Pathname.rb:18:7:18:33 | call to sub |
|
||||
| Pathname.rb:19:1:19:29 | ... = ... |
|
||||
| Pathname.rb:19:7:19:14 | foo_path |
|
||||
| Pathname.rb:19:7:19:29 | call to sub_ext |
|
||||
| Pathname.rb:20:1:20:22 | ... = ... |
|
||||
| Pathname.rb:20:7:20:14 | foo_path |
|
||||
| Pathname.rb:20:7:20:22 | call to to_path |
|
||||
| Pathname.rb:23:14:23:21 | foo_path |
|
||||
| Pathname.rb:23:14:23:26 | call to to_s |
|
||||
| Pathname.rb:26:12:26:19 | foo_path |
|
||||
| Pathname.rb:28:11:28:14 | pwd1 |
|
||||
| Pathname.rb:32:12:32:19 | foo_path |
|
||||
| Pathname.rb:35:1:35:8 | foo_path |
|
||||
| Pathname.rb:38:1:38:8 | foo_path |
|
||||
| Pathname.rb:39:12:39:19 | foo_path |
|
||||
| Pathname.rb:41:1:41:3 | p08 |
|
||||
| Pathname.rb:42:1:42:3 | p01 |
|
||||
fileSystemReadAccesses
|
||||
| Pathname.rb:32:12:32:24 | call to read | Pathname.rb:32:12:32:24 | call to read |
|
||||
fileSystemWriteAccesses
|
||||
| Pathname.rb:35:1:35:23 | call to write | Pathname.rb:35:16:35:23 | "output" |
|
||||
fileSystemPermissionModifications
|
||||
| Pathname.rb:38:1:38:19 | call to chmod | Pathname.rb:38:16:38:19 | 0644 |
|
||||
| Pathname.rb:39:12:39:34 | call to open | Pathname.rb:39:31:39:34 | 0666 |
|
||||
| Pathname.rb:41:1:41:14 | call to mkdir | Pathname.rb:41:11:41:14 | 0755 |
|
||||
| Pathname.rb:42:1:42:22 | call to mkpath | Pathname.rb:42:18:42:21 | 0644 |
|
||||
26
ruby/ql/test/library-tests/frameworks/pathname/Pathname.ql
Normal file
26
ruby/ql/test/library-tests/frameworks/pathname/Pathname.ql
Normal file
@@ -0,0 +1,26 @@
|
||||
private import ruby
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.frameworks.core.Pathname
|
||||
|
||||
query predicate pathnameInstances(Pathname::PathnameInstance i) { any() }
|
||||
|
||||
query predicate fileSystemAccesses(FileSystemAccess a, DataFlow::Node p) {
|
||||
p = a.getAPathArgument()
|
||||
}
|
||||
|
||||
query predicate fileNameSources(FileNameSource s) { any() }
|
||||
|
||||
query predicate fileSystemReadAccesses(FileSystemReadAccess a, DataFlow::Node d) {
|
||||
d = a.getADataNode()
|
||||
}
|
||||
|
||||
query predicate fileSystemWriteAccesses(FileSystemWriteAccess a, DataFlow::Node d) {
|
||||
d = a.getADataNode()
|
||||
}
|
||||
|
||||
query predicate fileSystemPermissionModifications(
|
||||
FileSystemPermissionModification m, DataFlow::Node p
|
||||
) {
|
||||
p = m.getAPermissionNode()
|
||||
}
|
||||
42
ruby/ql/test/library-tests/frameworks/pathname/Pathname.rb
Normal file
42
ruby/ql/test/library-tests/frameworks/pathname/Pathname.rb
Normal file
@@ -0,0 +1,42 @@
|
||||
|
||||
foo_path = Pathname.new "foo.txt"
|
||||
foo_path2 = foo_path
|
||||
foo_path
|
||||
|
||||
bar_path = Pathname.new 'bar'
|
||||
|
||||
# All these calls return new `Pathname` instances
|
||||
pwd1 = Pathname.getwd
|
||||
p00 = pwd1 + foo_path
|
||||
p01 = pwd1 / bar_path
|
||||
p02 = pwd1.basename
|
||||
p03 = Pathname.new('bar/../baz.txt').cleanpath
|
||||
p04 = foo_path.expand_path
|
||||
p05 = pwd1.join 'bar', 'baz', 'qux.txt'
|
||||
p06 = foo_path.realpath
|
||||
p07 = Pathname.new('foo/bar.txt').relative_path_from('foo')
|
||||
p08 = pwd1.sub 'wibble', 'wobble'
|
||||
p09 = foo_path.sub_ext '.log'
|
||||
p10 = foo_path.to_path
|
||||
|
||||
# `Pathname#to_s` returns a string that we consider to be a filename source.
|
||||
foo_string = foo_path.to_s
|
||||
|
||||
# File-system accesses
|
||||
foo_file = foo_path.open
|
||||
foo_file.close
|
||||
pwd_dir = pwd1.opendir
|
||||
pwd_dir.close
|
||||
|
||||
# Read from a file
|
||||
foo_data = foo_path.read
|
||||
|
||||
# Write to a file
|
||||
foo_path.write 'output'
|
||||
|
||||
# Permission modifications
|
||||
foo_path.chmod 0644
|
||||
foo_file = foo_path.open 'w', 0666
|
||||
foo_file.close
|
||||
p08.mkdir 0755
|
||||
p01.mkpath(mode: 0644)
|
||||
Reference in New Issue
Block a user