mirror of
https://github.com/github/codeql.git
synced 2026-04-27 09:45:15 +02:00
Merge pull request #283 from github/file-system-sources
Start modelling some file system access concepts
This commit is contained in:
@@ -36,6 +36,98 @@ module SqlExecution {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node that performs a file system access, including reading and writing data,
|
||||
* creating and deleting files and folders, checking and updating permissions, and so on.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `FileSystemAccess::Range` instead.
|
||||
*/
|
||||
class FileSystemAccess extends DataFlow::Node instanceof FileSystemAccess::Range {
|
||||
/** Gets an argument to this file system access that is interpreted as a path. */
|
||||
DataFlow::Node getAPathArgument() { result = super.getAPathArgument() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new file system access APIs. */
|
||||
module FileSystemAccess {
|
||||
/**
|
||||
* A data-flow node that performs a file system access, including reading and writing data,
|
||||
* creating and deleting files and folders, checking and updating permissions, and so on.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `FileSystemAccess` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets an argument to this file system access that is interpreted as a path. */
|
||||
abstract DataFlow::Node getAPathArgument();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node that reads data from the file system.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `FileSystemReadAccess::Range` instead.
|
||||
*/
|
||||
class FileSystemReadAccess extends FileSystemAccess instanceof FileSystemReadAccess::Range {
|
||||
/**
|
||||
* Gets a node that represents data read from the file system access.
|
||||
*/
|
||||
DataFlow::Node getADataNode() { result = FileSystemReadAccess::Range.super.getADataNode() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new file system reads. */
|
||||
module FileSystemReadAccess {
|
||||
/**
|
||||
* A data flow node that reads data from the file system.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `FileSystemReadAccess` instead.
|
||||
*/
|
||||
abstract class Range extends FileSystemAccess::Range {
|
||||
/**
|
||||
* Gets a node that represents data read from the file system.
|
||||
*/
|
||||
abstract DataFlow::Node getADataNode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node that sets the permissions for one or more files.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `FileSystemPermissionModification::Range` instead.
|
||||
*/
|
||||
class FileSystemPermissionModification extends DataFlow::Node instanceof FileSystemPermissionModification::Range {
|
||||
/**
|
||||
* Gets an argument to this permission modification that is interpreted as a
|
||||
* set of permissions.
|
||||
*/
|
||||
DataFlow::Node getAPermissionNode() { result = super.getAPermissionNode() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new file system permission modifications. */
|
||||
module FileSystemPermissionModification {
|
||||
/**
|
||||
* A data-flow node that sets permissions for a one or more files.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `FileSystemPermissionModification` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/**
|
||||
* Gets an argument to this permission modification that is interpreted as a
|
||||
* set of permissions.
|
||||
*/
|
||||
abstract DataFlow::Node getAPermissionNode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node that contains a file name or an array of file names from the local file system.
|
||||
*/
|
||||
abstract class FileNameSource extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A data-flow node that escapes meta-characters, which could be used to prevent
|
||||
* injection attacks.
|
||||
|
||||
@@ -6,3 +6,4 @@ private import codeql.ruby.frameworks.ActionController
|
||||
private import codeql.ruby.frameworks.ActiveRecord
|
||||
private import codeql.ruby.frameworks.ActionView
|
||||
private import codeql.ruby.frameworks.StandardLibrary
|
||||
private import codeql.ruby.frameworks.Files
|
||||
|
||||
302
ql/lib/codeql/ruby/frameworks/Files.qll
Normal file
302
ql/lib/codeql/ruby/frameworks/Files.qll
Normal file
@@ -0,0 +1,302 @@
|
||||
/**
|
||||
* Provides classes for working with file system libraries.
|
||||
*/
|
||||
|
||||
private import ruby
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.frameworks.StandardLibrary
|
||||
|
||||
private DataFlow::Node ioInstanceInstantiation() {
|
||||
result = API::getTopLevelMember("IO").getAnInstantiation() or
|
||||
result = API::getTopLevelMember("IO").getAMethodCall(["for_fd", "open", "try_convert"])
|
||||
}
|
||||
|
||||
private DataFlow::Node ioInstance() {
|
||||
result = ioInstanceInstantiation()
|
||||
or
|
||||
exists(DataFlow::Node inst |
|
||||
inst = ioInstance() and
|
||||
inst.(DataFlow::LocalSourceNode).flowsTo(result)
|
||||
)
|
||||
}
|
||||
|
||||
// Match some simple cases where a path argument specifies a shell command to
|
||||
// be executed. For example, the `"|date"` argument in `IO.read("|date")`, which
|
||||
// will execute a shell command and read its output rather than reading from the
|
||||
// filesystem.
|
||||
private predicate pathArgSpawnsSubprocess(Expr arg) {
|
||||
arg.(StringlikeLiteral).getValueText().charAt(0) = "|"
|
||||
}
|
||||
|
||||
private DataFlow::Node fileInstanceInstantiation() {
|
||||
result = API::getTopLevelMember("File").getAnInstantiation()
|
||||
or
|
||||
result = API::getTopLevelMember("File").getAMethodCall("open")
|
||||
or
|
||||
// Calls to `Kernel.open` can yield `File` instances
|
||||
exists(KernelMethodCall c |
|
||||
c = result.asExpr().getExpr() and
|
||||
c.getMethodName() = "open" and
|
||||
// Assume that calls that don't invoke shell commands will instead open
|
||||
// a file.
|
||||
not pathArgSpawnsSubprocess(c.getArgument(0))
|
||||
)
|
||||
}
|
||||
|
||||
private DataFlow::Node fileInstance() {
|
||||
result = fileInstanceInstantiation()
|
||||
or
|
||||
exists(DataFlow::Node inst |
|
||||
inst = fileInstance() and
|
||||
inst.(DataFlow::LocalSourceNode).flowsTo(result)
|
||||
)
|
||||
}
|
||||
|
||||
private string ioFileReaderClassMethodName() {
|
||||
result = ["binread", "foreach", "read", "readlines", "try_convert"]
|
||||
}
|
||||
|
||||
private string ioFileReaderInstanceMethodName() {
|
||||
result =
|
||||
[
|
||||
"getbyte", "getc", "gets", "pread", "read", "read_nonblock", "readbyte", "readchar",
|
||||
"readline", "readlines", "readpartial", "sysread"
|
||||
]
|
||||
}
|
||||
|
||||
private string ioFileReaderMethodName(boolean classMethodCall) {
|
||||
classMethodCall = true and result = ioFileReaderClassMethodName()
|
||||
or
|
||||
classMethodCall = false and result = ioFileReaderInstanceMethodName()
|
||||
}
|
||||
|
||||
/**
|
||||
* Classes and predicates for modeling the core `IO` module.
|
||||
*/
|
||||
module IO {
|
||||
/**
|
||||
* An instance of the `IO` class, for example in
|
||||
*
|
||||
* ```rb
|
||||
* rand = IO.new(IO.sysopen("/dev/random", "r"), "r")
|
||||
* rand_data = rand.read(32)
|
||||
* ```
|
||||
*
|
||||
* there are 3 `IOInstance`s - the call to `IO.new`, the assignment
|
||||
* `rand = ...`, and the read access to `rand` on the second line.
|
||||
*/
|
||||
class IOInstance extends DataFlow::Node {
|
||||
IOInstance() {
|
||||
this = ioInstance() or
|
||||
this = fileInstance()
|
||||
}
|
||||
}
|
||||
|
||||
// "Direct" `IO` instances, i.e. cases where there is no more specific
|
||||
// subtype such as `File`
|
||||
private class IOInstanceStrict extends IOInstance {
|
||||
IOInstanceStrict() { this = ioInstance() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `DataFlow::CallNode` that reads data using the `IO` class. For example,
|
||||
* the `IO.read call in:
|
||||
*
|
||||
* ```rb
|
||||
* IO.read("|date")
|
||||
* ```
|
||||
*
|
||||
* returns the output of the `date` shell command, invoked as a subprocess.
|
||||
*
|
||||
* This class includes reads both from shell commands and reads from the
|
||||
* filesystem. For working with filesystem accesses specifically, see
|
||||
* `IOFileReader` or the `FileSystemReadAccess` concept.
|
||||
*/
|
||||
class IOReader extends DataFlow::CallNode {
|
||||
private boolean classMethodCall;
|
||||
private string api;
|
||||
|
||||
IOReader() {
|
||||
// Class methods
|
||||
api = ["File", "IO"] and
|
||||
classMethodCall = true and
|
||||
this = API::getTopLevelMember(api).getAMethodCall(ioFileReaderMethodName(classMethodCall))
|
||||
or
|
||||
// IO instance methods
|
||||
classMethodCall = false and
|
||||
api = "IO" and
|
||||
exists(IOInstanceStrict ii |
|
||||
this.getReceiver() = ii and
|
||||
this.asExpr().getExpr().(MethodCall).getMethodName() =
|
||||
ioFileReaderMethodName(classMethodCall)
|
||||
)
|
||||
or
|
||||
// File instance methods
|
||||
classMethodCall = false and
|
||||
api = "File" and
|
||||
exists(File::FileInstance fi |
|
||||
this.getReceiver() = fi and
|
||||
this.asExpr().getExpr().(MethodCall).getMethodName() =
|
||||
ioFileReaderMethodName(classMethodCall)
|
||||
)
|
||||
// TODO: enumeration style methods such as `each`, `foreach`, etc.
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the most specific core class used for this read, `IO` or `File`
|
||||
*/
|
||||
string getAPI() { result = api }
|
||||
|
||||
predicate isClassMethodCall() { classMethodCall = true }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `DataFlow::CallNode` that reads data from the filesystem using the `IO`
|
||||
* class. For example, the `IO.read call in:
|
||||
*
|
||||
* ```rb
|
||||
* IO.read("foo.txt")
|
||||
* ```
|
||||
*
|
||||
* reads the file `foo.txt` and returns its contents as a string.
|
||||
*/
|
||||
class IOFileReader extends IOReader, FileSystemReadAccess::Range {
|
||||
IOFileReader() {
|
||||
this.getAPI() = "File"
|
||||
or
|
||||
this.isClassMethodCall() and
|
||||
// Assume that calls that don't invoke shell commands will instead
|
||||
// read from a file.
|
||||
not pathArgSpawnsSubprocess(this.getArgument(0).asExpr().getExpr())
|
||||
}
|
||||
|
||||
// TODO: can we infer a path argument for instance method calls?
|
||||
// e.g. by tracing back to the instantiation of that instance
|
||||
override DataFlow::Node getAPathArgument() {
|
||||
result = this.getArgument(0) and this.isClassMethodCall()
|
||||
}
|
||||
|
||||
// This class represents calls that return data
|
||||
override DataFlow::Node getADataNode() { result = this }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Classes and predicates for modeling the core `File` module.
|
||||
*
|
||||
* Because `File` is a subclass of `IO`, all `FileInstance`s and
|
||||
* `FileModuleReader`s are also `IOInstance`s and `IOModuleReader`s
|
||||
* respectively.
|
||||
*/
|
||||
module File {
|
||||
/**
|
||||
* An instance of the `File` class, for example in
|
||||
*
|
||||
* ```rb
|
||||
* f = File.new("foo.txt")
|
||||
* puts f.read()
|
||||
* ```
|
||||
*
|
||||
* there are 3 `FileInstance`s - the call to `File.new`, the assignment
|
||||
* `f = ...`, and the read access to `f` on the second line.
|
||||
*/
|
||||
class FileInstance extends IO::IOInstance {
|
||||
FileInstance() { this = fileInstance() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A read using the `File` module, e.g. the `f.read` call in
|
||||
*
|
||||
* ```rb
|
||||
* f = File.new("foo.txt")
|
||||
* puts f.read()
|
||||
* ```
|
||||
*/
|
||||
class FileModuleReader extends IO::IOFileReader {
|
||||
FileModuleReader() { this.getAPI() = "File" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a `File` method that may return one or more filenames.
|
||||
*/
|
||||
class FileModuleFilenameSource extends FileNameSource, DataFlow::CallNode {
|
||||
FileModuleFilenameSource() {
|
||||
// Class methods
|
||||
this =
|
||||
API::getTopLevelMember("File")
|
||||
.getAMethodCall([
|
||||
"absolute_path", "basename", "expand_path", "join", "path", "readlink",
|
||||
"realdirpath", "realpath"
|
||||
])
|
||||
or
|
||||
// Instance methods
|
||||
exists(FileInstance fi |
|
||||
this.getReceiver() = fi and
|
||||
this.asExpr().getExpr().(MethodCall).getMethodName() = ["path", "to_path"]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class FileModulePermissionModification extends FileSystemPermissionModification::Range,
|
||||
DataFlow::CallNode {
|
||||
private DataFlow::Node permissionArg;
|
||||
|
||||
FileModulePermissionModification() {
|
||||
exists(string methodName | this = API::getTopLevelMember("File").getAMethodCall(methodName) |
|
||||
methodName in ["chmod", "lchmod"] and permissionArg = this.getArgument(0)
|
||||
or
|
||||
methodName = "mkfifo" and permissionArg = this.getArgument(1)
|
||||
or
|
||||
methodName in ["new", "open"] and permissionArg = this.getArgument(2)
|
||||
// TODO: defaults for optional args? This may depend on the umask
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAPermissionNode() { result = permissionArg }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Classes and predicates for modeling the `FileUtils` module from the standard
|
||||
* library.
|
||||
*/
|
||||
module FileUtils {
|
||||
/**
|
||||
* A call to a FileUtils method that may return one or more filenames.
|
||||
*/
|
||||
class FileUtilsFilenameSource extends FileNameSource {
|
||||
FileUtilsFilenameSource() {
|
||||
// Note that many methods in FileUtils accept a `noop` option that will
|
||||
// perform a dry run of the command. This means that, for instance, `rm`
|
||||
// and similar methods may not actually delete/unlink a file when called.
|
||||
this =
|
||||
API::getTopLevelMember("FileUtils")
|
||||
.getAMethodCall([
|
||||
"chmod", "chmod_R", "chown", "chown_R", "getwd", "makedirs", "mkdir", "mkdir_p",
|
||||
"mkpath", "remove", "remove_dir", "remove_entry", "rm", "rm_f", "rm_r", "rm_rf",
|
||||
"rmdir", "rmtree", "safe_unlink", "touch"
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
private class FileUtilsPermissionModification extends FileSystemPermissionModification::Range,
|
||||
DataFlow::CallNode {
|
||||
private DataFlow::Node permissionArg;
|
||||
|
||||
FileUtilsPermissionModification() {
|
||||
exists(string methodName |
|
||||
this = API::getTopLevelMember("FileUtils").getAMethodCall(methodName)
|
||||
|
|
||||
methodName in ["chmod", "chmod_R"] and permissionArg = this.getArgument(0)
|
||||
or
|
||||
methodName in ["install", "makedirs", "mkdir", "mkdir_p", "mkpath"] and
|
||||
permissionArg = this.getKeywordArgument("mode")
|
||||
// TODO: defaults for optional args? This may depend on the umask
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAPermissionNode() { result = permissionArg }
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
*/
|
||||
|
||||
import ruby
|
||||
import codeql.ruby.Concepts
|
||||
import codeql.ruby.DataFlow
|
||||
import DataFlow::PathGraph
|
||||
import codeql.ruby.ApiGraphs
|
||||
@@ -43,29 +44,6 @@ class PermissivePermissionsExpr extends Expr {
|
||||
}
|
||||
}
|
||||
|
||||
/** A permissions argument of a call to a File/FileUtils method that may modify file permissions */
|
||||
class PermissionArgument extends DataFlow::Node {
|
||||
private DataFlow::CallNode call;
|
||||
|
||||
PermissionArgument() {
|
||||
exists(string methodName |
|
||||
call = API::getTopLevelMember(["File", "FileUtils"]).getAMethodCall(methodName)
|
||||
|
|
||||
methodName in ["chmod", "chmod_R", "lchmod"] and this = call.getArgument(0)
|
||||
or
|
||||
methodName = "mkfifo" and this = call.getArgument(1)
|
||||
or
|
||||
methodName in ["new", "open"] and this = call.getArgument(2)
|
||||
or
|
||||
methodName in ["install", "makedirs", "mkdir", "mkdir_p", "mkpath"] and
|
||||
this = call.getKeywordArgument("mode")
|
||||
// TODO: defaults for optional args? This may depend on the umask
|
||||
)
|
||||
}
|
||||
|
||||
MethodCall getCall() { result = call.asExpr().getExpr() }
|
||||
}
|
||||
|
||||
class PermissivePermissionsConfig extends DataFlow::Configuration {
|
||||
PermissivePermissionsConfig() { this = "PermissivePermissionsConfig" }
|
||||
|
||||
@@ -73,12 +51,14 @@ class PermissivePermissionsConfig extends DataFlow::Configuration {
|
||||
exists(PermissivePermissionsExpr ppe | source.asExpr().getExpr() = ppe)
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof PermissionArgument }
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(FileSystemPermissionModification mod | mod.getAPermissionNode() = sink)
|
||||
}
|
||||
}
|
||||
|
||||
from
|
||||
DataFlow::PathNode source, DataFlow::PathNode sink, PermissivePermissionsConfig conf,
|
||||
PermissionArgument arg
|
||||
where conf.hasFlowPath(source, sink) and arg = sink.getNode()
|
||||
select source.getNode(), source, sink, "Overly permissive mask in $@ sets file to $@.",
|
||||
arg.getCall(), arg.getCall().toString(), source.getNode(), source.getNode().toString()
|
||||
FileSystemPermissionModification mod
|
||||
where conf.hasFlowPath(source, sink) and mod.getAPermissionNode() = sink.getNode()
|
||||
select source.getNode(), source, sink, "Overly permissive mask in $@ sets file to $@.", mod,
|
||||
mod.toString(), source.getNode(), source.getNode().toString()
|
||||
|
||||
60
ql/test/library-tests/frameworks/Files.expected
Normal file
60
ql/test/library-tests/frameworks/Files.expected
Normal file
@@ -0,0 +1,60 @@
|
||||
fileInstances
|
||||
| Files.rb:2:1:2:30 | ... = ... |
|
||||
| Files.rb:2:1:2:30 | ... = ... |
|
||||
| Files.rb:2:12:2:30 | call to new |
|
||||
| Files.rb:3:1:3:21 | ... = ... |
|
||||
| Files.rb:3:1:3:21 | ... = ... |
|
||||
| Files.rb:3:14:3:21 | foo_file |
|
||||
| Files.rb:4:1:4:8 | foo_file |
|
||||
| Files.rb:7:13:7:22 | foo_file_2 |
|
||||
| Files.rb:10:6:10:13 | foo_file |
|
||||
| Files.rb:11:6:11:13 | foo_file |
|
||||
| Files.rb:23:1:23:33 | ... = ... |
|
||||
| Files.rb:23:19:23:33 | call to open |
|
||||
| Files.rb:24:1:24:40 | ... = ... |
|
||||
| Files.rb:24:19:24:40 | call to open |
|
||||
ioInstances
|
||||
| Files.rb:2:1:2:30 | ... = ... |
|
||||
| Files.rb:2:1:2:30 | ... = ... |
|
||||
| Files.rb:2:12:2:30 | call to new |
|
||||
| Files.rb:3:1:3:21 | ... = ... |
|
||||
| Files.rb:3:1:3:21 | ... = ... |
|
||||
| Files.rb:3:14:3:21 | foo_file |
|
||||
| Files.rb:4:1:4:8 | foo_file |
|
||||
| Files.rb:7:13:7:22 | foo_file_2 |
|
||||
| Files.rb:10:6:10:13 | foo_file |
|
||||
| Files.rb:11:6:11:13 | foo_file |
|
||||
| Files.rb:17:1:17:50 | ... = ... |
|
||||
| Files.rb:17:1:17:50 | ... = ... |
|
||||
| Files.rb:17:8:17:50 | call to new |
|
||||
| Files.rb:18:1:18:13 | ... = ... |
|
||||
| Files.rb:18:10:18:13 | rand |
|
||||
| Files.rb:20:13:20:16 | rand |
|
||||
| Files.rb:23:1:23:33 | ... = ... |
|
||||
| Files.rb:23:19:23:33 | call to open |
|
||||
| Files.rb:24:1:24:40 | ... = ... |
|
||||
| Files.rb:24:19:24:40 | call to open |
|
||||
| Files.rb:35:1:35:56 | ... = ... |
|
||||
| Files.rb:35:13:35:56 | call to open |
|
||||
fileReaders
|
||||
| Files.rb:7:13:7:32 | call to readlines |
|
||||
ioReaders
|
||||
| Files.rb:7:13:7:32 | call to readlines | File |
|
||||
| Files.rb:20:13:20:25 | call to read | IO |
|
||||
| Files.rb:29:12:29:29 | call to read | IO |
|
||||
| Files.rb:32:8:32:23 | call to read | IO |
|
||||
ioFileReaders
|
||||
| Files.rb:7:13:7:32 | call to readlines | File |
|
||||
| Files.rb:29:12:29:29 | call to read | IO |
|
||||
fileModuleFilenameSources
|
||||
| Files.rb:10:6:10:18 | call to path |
|
||||
| Files.rb:11:6:11:21 | call to to_path |
|
||||
fileUtilsFilenameSources
|
||||
| Files.rb:14:8:14:43 | call to makedirs |
|
||||
fileSystemReadAccesses
|
||||
| Files.rb:7:13:7:32 | call to readlines |
|
||||
| Files.rb:29:12:29:29 | call to read |
|
||||
fileNameSources
|
||||
| Files.rb:10:6:10:18 | call to path |
|
||||
| Files.rb:11:6:11:21 | call to to_path |
|
||||
| Files.rb:14:8:14:43 | call to makedirs |
|
||||
21
ql/test/library-tests/frameworks/Files.ql
Normal file
21
ql/test/library-tests/frameworks/Files.ql
Normal file
@@ -0,0 +1,21 @@
|
||||
private import ruby
|
||||
private import codeql.ruby.frameworks.Files
|
||||
private import codeql.ruby.Concepts
|
||||
|
||||
query predicate fileInstances(File::FileInstance i) { any() }
|
||||
|
||||
query predicate ioInstances(IO::IOInstance i) { any() }
|
||||
|
||||
query predicate fileReaders(File::FileModuleReader r) { any() }
|
||||
|
||||
query predicate ioReaders(IO::IOReader r, string api) { api = r.getAPI() }
|
||||
|
||||
query predicate ioFileReaders(IO::IOFileReader r, string api) { api = r.getAPI() }
|
||||
|
||||
query predicate fileModuleFilenameSources(File::FileModuleFilenameSource s) { any() }
|
||||
|
||||
query predicate fileUtilsFilenameSources(FileUtils::FileUtilsFilenameSource s) { any() }
|
||||
|
||||
query predicate fileSystemReadAccesses(FileSystemReadAccess a) { any() }
|
||||
|
||||
query predicate fileNameSources(FileNameSource s) { any() }
|
||||
35
ql/test/library-tests/frameworks/Files.rb
Normal file
35
ql/test/library-tests/frameworks/Files.rb
Normal file
@@ -0,0 +1,35 @@
|
||||
# `foo_file` is a `File` instance
|
||||
foo_file = File.new("foo.txt")
|
||||
foo_file_2 = foo_file
|
||||
foo_file
|
||||
|
||||
# File read access
|
||||
foo_lines = foo_file_2.readlines
|
||||
|
||||
# `fp` is a file path
|
||||
fp = foo_file.path
|
||||
fp = foo_file.to_path
|
||||
|
||||
# `FileUtils.makedirs` returns an array of file names
|
||||
dirs = FileUtils.makedirs(["dir1", "dir2"])
|
||||
|
||||
# `rand` is an `IO` instance
|
||||
rand = IO.new(IO.sysopen("/dev/random", "r"), "r")
|
||||
rand_2 = rand
|
||||
|
||||
rand_data = rand.read(32)
|
||||
|
||||
# `foo_file_kernel` is a `File` instance
|
||||
foo_file_kernel = open("foo.txt")
|
||||
foo_file_kernel = Kernel.open("foo.txt")
|
||||
|
||||
foo_command_kernel = open("|ls")
|
||||
|
||||
# `IO.read("foo.txt")` reads from a file
|
||||
foo_text = IO.read("foo.txt")
|
||||
|
||||
# `IO.read("|date")` does not read from a file
|
||||
date = IO.read("|date")
|
||||
|
||||
# `rand_open` is an `IO` instance
|
||||
rand_open = IO.open(IO.sysopen("/dev/random", "r"), "r")
|
||||
@@ -1,9 +1,13 @@
|
||||
require "fileutils"
|
||||
|
||||
def run_chmod_1(filename)
|
||||
# BAD: sets file as world writable
|
||||
FileUtils.chmod 0222, filename
|
||||
# BAD: sets file as world writable
|
||||
FileUtils.chmod 0622, filename
|
||||
# BAD: sets file as world readable
|
||||
FileUtils.chmod 0755, filename
|
||||
# BAD: sets file as world readable + writable
|
||||
FileUtils.chmod 0777, filename
|
||||
end
|
||||
|
||||
@@ -14,13 +18,13 @@ module DummyModule
|
||||
end
|
||||
|
||||
def run_chmod_2(filename)
|
||||
foo = FileUtils
|
||||
foo = File
|
||||
bar = foo
|
||||
baz = Dummy
|
||||
# "safe"
|
||||
baz = DummyModule
|
||||
# GOOD: DummyModule is not a known class that performs file permission modifications
|
||||
baz.chmod 0755, filename
|
||||
baz = bar
|
||||
# unsafe
|
||||
# BAD: sets file as world readable
|
||||
baz.chmod 0755, filename
|
||||
end
|
||||
|
||||
@@ -28,31 +32,42 @@ def run_chmod_3(filename)
|
||||
# TODO: we currently miss this
|
||||
foo = FileUtils
|
||||
bar, baz = foo, 7
|
||||
# BAD: sets file as world readable
|
||||
bar.chmod 0755, filename
|
||||
end
|
||||
|
||||
def run_chmod_4(filename)
|
||||
# safe permissions
|
||||
# GOOD: no group/world access
|
||||
FileUtils.chmod 0700, filename
|
||||
# GOOD: group/world execute bit only
|
||||
FileUtils.chmod 0711, filename
|
||||
# GOOD: world execute bit only
|
||||
FileUtils.chmod 0701, filename
|
||||
# GOOD: group execute bit only
|
||||
FileUtils.chmod 0710, filename
|
||||
end
|
||||
|
||||
def run_chmod_5(filename)
|
||||
perm = 0777
|
||||
# BAD: sets world rwx
|
||||
FileUtils.chmod perm, filename
|
||||
perm2 = perm
|
||||
# BAD: sets world rwx
|
||||
FileUtils.chmod perm2, filename
|
||||
|
||||
perm = "u=wrx,g=rwx,o=x"
|
||||
perm2 = perm
|
||||
# BAD: sets group rwx
|
||||
FileUtils.chmod perm2, filename
|
||||
# BAD: sets file as world readable
|
||||
FileUtils.chmod "u=rwx,o+r", filename
|
||||
# GOOD: sets file as group/world unreadable
|
||||
FileUtils.chmod "u=rwx,go-r", filename
|
||||
# BAD: sets group/world as +rw
|
||||
FileUtils.chmod "a+rw", filename
|
||||
end
|
||||
|
||||
def run_chmod_R(filename)
|
||||
File.chmod_R 0755, filename
|
||||
# BAD: sets file as world readable
|
||||
FileUtils.chmod_R 0755, filename
|
||||
end
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
edges
|
||||
| FilePermissions.rb:43:10:43:13 | 0777 : | FilePermissions.rb:44:19:44:22 | perm |
|
||||
| FilePermissions.rb:43:10:43:13 | 0777 : | FilePermissions.rb:46:19:46:23 | perm2 |
|
||||
| FilePermissions.rb:48:10:48:26 | "u=wrx,g=rwx,o=x" : | FilePermissions.rb:50:19:50:23 | perm2 |
|
||||
| FilePermissions.rb:51:10:51:13 | 0777 : | FilePermissions.rb:53:19:53:22 | perm |
|
||||
| FilePermissions.rb:51:10:51:13 | 0777 : | FilePermissions.rb:56:19:56:23 | perm2 |
|
||||
| FilePermissions.rb:58:10:58:26 | "u=wrx,g=rwx,o=x" : | FilePermissions.rb:61:19:61:23 | perm2 |
|
||||
nodes
|
||||
| FilePermissions.rb:4:19:4:22 | 0222 | semmle.label | 0222 |
|
||||
| FilePermissions.rb:5:19:5:22 | 0622 | semmle.label | 0622 |
|
||||
| FilePermissions.rb:6:19:6:22 | 0755 | semmle.label | 0755 |
|
||||
| FilePermissions.rb:7:19:7:22 | 0777 | semmle.label | 0777 |
|
||||
| FilePermissions.rb:24:13:24:16 | 0755 | semmle.label | 0755 |
|
||||
| FilePermissions.rb:43:10:43:13 | 0777 : | semmle.label | 0777 : |
|
||||
| FilePermissions.rb:44:19:44:22 | perm | semmle.label | perm |
|
||||
| FilePermissions.rb:46:19:46:23 | perm2 | semmle.label | perm2 |
|
||||
| FilePermissions.rb:48:10:48:26 | "u=wrx,g=rwx,o=x" : | semmle.label | "u=wrx,g=rwx,o=x" : |
|
||||
| FilePermissions.rb:50:19:50:23 | perm2 | semmle.label | perm2 |
|
||||
| FilePermissions.rb:51:19:51:29 | "u=rwx,o+r" | semmle.label | "u=rwx,o+r" |
|
||||
| FilePermissions.rb:53:19:53:24 | "a+rw" | semmle.label | "a+rw" |
|
||||
| FilePermissions.rb:57:16:57:19 | 0755 | semmle.label | 0755 |
|
||||
| FilePermissions.rb:5:19:5:22 | 0222 | semmle.label | 0222 |
|
||||
| FilePermissions.rb:7:19:7:22 | 0622 | semmle.label | 0622 |
|
||||
| FilePermissions.rb:9:19:9:22 | 0755 | semmle.label | 0755 |
|
||||
| FilePermissions.rb:11:19:11:22 | 0777 | semmle.label | 0777 |
|
||||
| FilePermissions.rb:28:13:28:16 | 0755 | semmle.label | 0755 |
|
||||
| FilePermissions.rb:51:10:51:13 | 0777 : | semmle.label | 0777 : |
|
||||
| FilePermissions.rb:53:19:53:22 | perm | semmle.label | perm |
|
||||
| FilePermissions.rb:56:19:56:23 | perm2 | semmle.label | perm2 |
|
||||
| FilePermissions.rb:58:10:58:26 | "u=wrx,g=rwx,o=x" : | semmle.label | "u=wrx,g=rwx,o=x" : |
|
||||
| FilePermissions.rb:61:19:61:23 | perm2 | semmle.label | perm2 |
|
||||
| FilePermissions.rb:63:19:63:29 | "u=rwx,o+r" | semmle.label | "u=rwx,o+r" |
|
||||
| FilePermissions.rb:67:19:67:24 | "a+rw" | semmle.label | "a+rw" |
|
||||
| FilePermissions.rb:72:21:72:24 | 0755 | semmle.label | 0755 |
|
||||
subpaths
|
||||
#select
|
||||
| FilePermissions.rb:4:19:4:22 | 0222 | FilePermissions.rb:4:19:4:22 | 0222 | FilePermissions.rb:4:19:4:22 | 0222 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:4:3:4:32 | call to chmod | call to chmod | FilePermissions.rb:4:19:4:22 | 0222 | 0222 |
|
||||
| FilePermissions.rb:5:19:5:22 | 0622 | FilePermissions.rb:5:19:5:22 | 0622 | FilePermissions.rb:5:19:5:22 | 0622 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:5:3:5:32 | call to chmod | call to chmod | FilePermissions.rb:5:19:5:22 | 0622 | 0622 |
|
||||
| FilePermissions.rb:6:19:6:22 | 0755 | FilePermissions.rb:6:19:6:22 | 0755 | FilePermissions.rb:6:19:6:22 | 0755 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:6:3:6:32 | call to chmod | call to chmod | FilePermissions.rb:6:19:6:22 | 0755 | 0755 |
|
||||
| FilePermissions.rb:7:19:7:22 | 0777 | FilePermissions.rb:7:19:7:22 | 0777 | FilePermissions.rb:7:19:7:22 | 0777 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:7:3:7:32 | call to chmod | call to chmod | FilePermissions.rb:7:19:7:22 | 0777 | 0777 |
|
||||
| FilePermissions.rb:24:13:24:16 | 0755 | FilePermissions.rb:24:13:24:16 | 0755 | FilePermissions.rb:24:13:24:16 | 0755 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:24:3:24:26 | call to chmod | call to chmod | FilePermissions.rb:24:13:24:16 | 0755 | 0755 |
|
||||
| FilePermissions.rb:43:10:43:13 | 0777 | FilePermissions.rb:43:10:43:13 | 0777 : | FilePermissions.rb:44:19:44:22 | perm | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:44:3:44:32 | call to chmod | call to chmod | FilePermissions.rb:43:10:43:13 | 0777 | 0777 |
|
||||
| FilePermissions.rb:43:10:43:13 | 0777 | FilePermissions.rb:43:10:43:13 | 0777 : | FilePermissions.rb:46:19:46:23 | perm2 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:46:3:46:33 | call to chmod | call to chmod | FilePermissions.rb:43:10:43:13 | 0777 | 0777 |
|
||||
| FilePermissions.rb:48:10:48:26 | "u=wrx,g=rwx,o=x" | FilePermissions.rb:48:10:48:26 | "u=wrx,g=rwx,o=x" : | FilePermissions.rb:50:19:50:23 | perm2 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:50:3:50:33 | call to chmod | call to chmod | FilePermissions.rb:48:10:48:26 | "u=wrx,g=rwx,o=x" | "u=wrx,g=rwx,o=x" |
|
||||
| FilePermissions.rb:51:19:51:29 | "u=rwx,o+r" | FilePermissions.rb:51:19:51:29 | "u=rwx,o+r" | FilePermissions.rb:51:19:51:29 | "u=rwx,o+r" | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:51:3:51:39 | call to chmod | call to chmod | FilePermissions.rb:51:19:51:29 | "u=rwx,o+r" | "u=rwx,o+r" |
|
||||
| FilePermissions.rb:53:19:53:24 | "a+rw" | FilePermissions.rb:53:19:53:24 | "a+rw" | FilePermissions.rb:53:19:53:24 | "a+rw" | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:53:3:53:34 | call to chmod | call to chmod | FilePermissions.rb:53:19:53:24 | "a+rw" | "a+rw" |
|
||||
| FilePermissions.rb:57:16:57:19 | 0755 | FilePermissions.rb:57:16:57:19 | 0755 | FilePermissions.rb:57:16:57:19 | 0755 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:57:3:57:29 | call to chmod_R | call to chmod_R | FilePermissions.rb:57:16:57:19 | 0755 | 0755 |
|
||||
| FilePermissions.rb:5:19:5:22 | 0222 | FilePermissions.rb:5:19:5:22 | 0222 | FilePermissions.rb:5:19:5:22 | 0222 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:5:3:5:32 | call to chmod | call to chmod | FilePermissions.rb:5:19:5:22 | 0222 | 0222 |
|
||||
| FilePermissions.rb:7:19:7:22 | 0622 | FilePermissions.rb:7:19:7:22 | 0622 | FilePermissions.rb:7:19:7:22 | 0622 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:7:3:7:32 | call to chmod | call to chmod | FilePermissions.rb:7:19:7:22 | 0622 | 0622 |
|
||||
| FilePermissions.rb:9:19:9:22 | 0755 | FilePermissions.rb:9:19:9:22 | 0755 | FilePermissions.rb:9:19:9:22 | 0755 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:9:3:9:32 | call to chmod | call to chmod | FilePermissions.rb:9:19:9:22 | 0755 | 0755 |
|
||||
| FilePermissions.rb:11:19:11:22 | 0777 | FilePermissions.rb:11:19:11:22 | 0777 | FilePermissions.rb:11:19:11:22 | 0777 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:11:3:11:32 | call to chmod | call to chmod | FilePermissions.rb:11:19:11:22 | 0777 | 0777 |
|
||||
| FilePermissions.rb:28:13:28:16 | 0755 | FilePermissions.rb:28:13:28:16 | 0755 | FilePermissions.rb:28:13:28:16 | 0755 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:28:3:28:26 | call to chmod | call to chmod | FilePermissions.rb:28:13:28:16 | 0755 | 0755 |
|
||||
| FilePermissions.rb:51:10:51:13 | 0777 | FilePermissions.rb:51:10:51:13 | 0777 : | FilePermissions.rb:53:19:53:22 | perm | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:53:3:53:32 | call to chmod | call to chmod | FilePermissions.rb:51:10:51:13 | 0777 | 0777 |
|
||||
| FilePermissions.rb:51:10:51:13 | 0777 | FilePermissions.rb:51:10:51:13 | 0777 : | FilePermissions.rb:56:19:56:23 | perm2 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:56:3:56:33 | call to chmod | call to chmod | FilePermissions.rb:51:10:51:13 | 0777 | 0777 |
|
||||
| FilePermissions.rb:58:10:58:26 | "u=wrx,g=rwx,o=x" | FilePermissions.rb:58:10:58:26 | "u=wrx,g=rwx,o=x" : | FilePermissions.rb:61:19:61:23 | perm2 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:61:3:61:33 | call to chmod | call to chmod | FilePermissions.rb:58:10:58:26 | "u=wrx,g=rwx,o=x" | "u=wrx,g=rwx,o=x" |
|
||||
| FilePermissions.rb:63:19:63:29 | "u=rwx,o+r" | FilePermissions.rb:63:19:63:29 | "u=rwx,o+r" | FilePermissions.rb:63:19:63:29 | "u=rwx,o+r" | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:63:3:63:39 | call to chmod | call to chmod | FilePermissions.rb:63:19:63:29 | "u=rwx,o+r" | "u=rwx,o+r" |
|
||||
| FilePermissions.rb:67:19:67:24 | "a+rw" | FilePermissions.rb:67:19:67:24 | "a+rw" | FilePermissions.rb:67:19:67:24 | "a+rw" | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:67:3:67:34 | call to chmod | call to chmod | FilePermissions.rb:67:19:67:24 | "a+rw" | "a+rw" |
|
||||
| FilePermissions.rb:72:21:72:24 | 0755 | FilePermissions.rb:72:21:72:24 | 0755 | FilePermissions.rb:72:21:72:24 | 0755 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:72:3:72:34 | call to chmod_R | call to chmod_R | FilePermissions.rb:72:21:72:24 | 0755 | 0755 |
|
||||
|
||||
Reference in New Issue
Block a user