mirror of
https://github.com/github/codeql.git
synced 2025-12-24 04:36:35 +01:00
JS: add model of ShellJS
This commit is contained in:
@@ -80,6 +80,7 @@ import semmle.javascript.frameworks.PropertyProjection
|
|||||||
import semmle.javascript.frameworks.React
|
import semmle.javascript.frameworks.React
|
||||||
import semmle.javascript.frameworks.ReactNative
|
import semmle.javascript.frameworks.ReactNative
|
||||||
import semmle.javascript.frameworks.Request
|
import semmle.javascript.frameworks.Request
|
||||||
|
import semmle.javascript.frameworks.ShellJS
|
||||||
import semmle.javascript.frameworks.SQL
|
import semmle.javascript.frameworks.SQL
|
||||||
import semmle.javascript.frameworks.SocketIO
|
import semmle.javascript.frameworks.SocketIO
|
||||||
import semmle.javascript.frameworks.StringFormatters
|
import semmle.javascript.frameworks.StringFormatters
|
||||||
|
|||||||
196
javascript/ql/src/semmle/javascript/frameworks/ShellJS.qll
Normal file
196
javascript/ql/src/semmle/javascript/frameworks/ShellJS.qll
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
/**
|
||||||
|
* Models the `shelljs` library in terms of `FileSystemAccess` and `SystemCommandExecution`.
|
||||||
|
*/
|
||||||
|
import javascript
|
||||||
|
|
||||||
|
module ShellJS {
|
||||||
|
/** A reference to the `shelljs` library or a library that mimics its API. */
|
||||||
|
class Instance extends DataFlow::SourceNode {
|
||||||
|
Instance::Range range;
|
||||||
|
|
||||||
|
Instance() { this = range }
|
||||||
|
}
|
||||||
|
|
||||||
|
module Instance {
|
||||||
|
/**
|
||||||
|
* A reference to the `shelljs` library.
|
||||||
|
*
|
||||||
|
* Can be subclassed to recognize aliases for the `shelljs` library.
|
||||||
|
*/
|
||||||
|
abstract class Range extends DataFlow::SourceNode { }
|
||||||
|
|
||||||
|
private class DefaultRange extends Range {
|
||||||
|
DefaultRange() {
|
||||||
|
this = DataFlow::moduleImport("shelljs") or
|
||||||
|
this = DataFlow::moduleImport("async-shelljs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A member of the `shelljs` library. */
|
||||||
|
class Member extends DataFlow::SourceNode {
|
||||||
|
Member::Range range;
|
||||||
|
|
||||||
|
Member() { this = range }
|
||||||
|
|
||||||
|
string getName() { result = range.getName() }
|
||||||
|
}
|
||||||
|
|
||||||
|
module Member {
|
||||||
|
/**
|
||||||
|
* A member of the `shelljs` library.
|
||||||
|
*
|
||||||
|
* Can be subclassed to recognize additional values as `shelljs` member functions.
|
||||||
|
*/
|
||||||
|
abstract class Range extends DataFlow::SourceNode {
|
||||||
|
abstract string getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DefaultRange extends Range {
|
||||||
|
string name;
|
||||||
|
|
||||||
|
DefaultRange() { this = any(Instance inst).getAPropertyRead(name) }
|
||||||
|
|
||||||
|
override string getName() { result = name }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The `shelljs.exec` library modelled as a `shelljs` member. */
|
||||||
|
private class ShellJsExec extends Range {
|
||||||
|
ShellJsExec() { this = DataFlow::moduleImport("shelljs.exec") }
|
||||||
|
|
||||||
|
override string getName() { result = "exec" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A call to one of the functions exported from the `shelljs` library.
|
||||||
|
*/
|
||||||
|
class ShellJSCall extends DataFlow::CallNode {
|
||||||
|
string name;
|
||||||
|
|
||||||
|
ShellJSCall() {
|
||||||
|
exists(Member member |
|
||||||
|
this = member.getACall() and
|
||||||
|
name = member.getName()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the name of the exported function, such as `rm` in `shelljs.rm()`. */
|
||||||
|
string getName() { result = name }
|
||||||
|
|
||||||
|
/** Holds if the first argument starts with a `-`, indicating it is an option. */
|
||||||
|
predicate hasOptionsArg() {
|
||||||
|
exists(string val |
|
||||||
|
getArgument(0).mayHaveStringValue(val) and
|
||||||
|
val.matches("-%")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the `n`th argument after the initial options argument, if any. */
|
||||||
|
DataFlow::Node getTranslatedArgument(int n) {
|
||||||
|
if hasOptionsArg() then result = getArgument(n + 1) else result = getArgument(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A file system access that can't be modelled as a read or a write.
|
||||||
|
*/
|
||||||
|
private class ShellJSGenericFileAccess extends FileSystemAccess, ShellJSCall {
|
||||||
|
ShellJSGenericFileAccess() {
|
||||||
|
name = "cd" or
|
||||||
|
name = "cp" or
|
||||||
|
name = "chmod" or
|
||||||
|
name = "pushd" or
|
||||||
|
name = "find" or
|
||||||
|
name = "ls" or
|
||||||
|
name = "ln" or
|
||||||
|
name = "mkdir" or
|
||||||
|
name = "mv" or
|
||||||
|
name = "rm" or
|
||||||
|
name = "touch"
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getAPathArgument() { result = getAnArgument() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `shelljs` call that returns names of existing files.
|
||||||
|
*/
|
||||||
|
private class ShellJSFilenameSource extends FileNameSource, ShellJSCall {
|
||||||
|
ShellJSFilenameSource() {
|
||||||
|
name = "find" or
|
||||||
|
name = "ls"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A file system access that returns the contents of a file.
|
||||||
|
*/
|
||||||
|
private class ShellJSRead extends FileSystemReadAccess, ShellJSCall {
|
||||||
|
ShellJSRead() {
|
||||||
|
name = "cat" or
|
||||||
|
name = "head" or
|
||||||
|
name = "sort" or
|
||||||
|
name = "tail" or
|
||||||
|
name = "uniq"
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getAPathArgument() { result = getAnArgument() }
|
||||||
|
|
||||||
|
override DataFlow::Node getADataNode() { result = this }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A file system access that returns the contents of a file, but where certain arguemnts
|
||||||
|
* should be treated as patterns, not filenames.
|
||||||
|
*/
|
||||||
|
private class ShellJSPatternRead extends FileSystemReadAccess, ShellJSCall {
|
||||||
|
int offset;
|
||||||
|
|
||||||
|
ShellJSPatternRead() {
|
||||||
|
name = "grep" and offset = 1
|
||||||
|
or
|
||||||
|
name = "sed" and offset = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getAPathArgument() {
|
||||||
|
// Do not treat regex patterns as filenames.
|
||||||
|
exists(int arg |
|
||||||
|
arg >= offset and
|
||||||
|
result = getTranslatedArgument(arg)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getADataNode() { result = this }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A call to `shelljs.exec()` modelled as command execution.
|
||||||
|
*/
|
||||||
|
private class ShellJSExec extends SystemCommandExecution, ShellJSCall {
|
||||||
|
ShellJSExec() { name = "exec" }
|
||||||
|
|
||||||
|
override DataFlow::Node getACommandArgument() { result = getArgument(0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A call to `to()` or `toEnd()` on the `ShellString` object returned from another `shelljs` call,
|
||||||
|
* such as `shelljs.cat(file1).to(file2)`.
|
||||||
|
*/
|
||||||
|
private class ShellJSPipe extends FileSystemWriteAccess, DataFlow::CallNode {
|
||||||
|
ShellJSPipe() {
|
||||||
|
exists(string name | this = any(ShellJSCall inner).getAMethodCall(name) |
|
||||||
|
name = "to" or
|
||||||
|
name = "toEnd"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getAPathArgument() {
|
||||||
|
result = getArgument(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getADataNode() {
|
||||||
|
result = getReceiver()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import javascript
|
||||||
|
|
||||||
|
query predicate test_FileSystemAccess(FileSystemAccess access) { any() }
|
||||||
|
|
||||||
|
query predicate test_MissingFileSystemAccess(VarAccess var) {
|
||||||
|
var.getName().matches("file%") and
|
||||||
|
not exists(FileSystemAccess access | access.getAPathArgument().asExpr() = var)
|
||||||
|
}
|
||||||
58
javascript/ql/test/library-tests/frameworks/Shelljs/tst.js
Normal file
58
javascript/ql/test/library-tests/frameworks/Shelljs/tst.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import shelljs from 'shelljs';
|
||||||
|
|
||||||
|
shelljs.cat(file);
|
||||||
|
shelljs.cat(file1, file2);
|
||||||
|
shelljs.cd(file);
|
||||||
|
shelljs.chmod(mode, file);
|
||||||
|
shelljs.chmod(opts, mode, file);
|
||||||
|
shelljs.cp(file1, file2);
|
||||||
|
shelljs.cp(opts, file1, file2);
|
||||||
|
shelljs.cp(opts, file1, file2, file3);
|
||||||
|
shelljs.pushd(file);
|
||||||
|
shelljs.pushd(opts, file);
|
||||||
|
shelljs.popd(opts);
|
||||||
|
shelljs.exec(cmd, opts, cb);
|
||||||
|
shelljs.find(file1, file2);
|
||||||
|
shelljs.grep(regex, file);
|
||||||
|
shelljs.grep(opts, regex, file);
|
||||||
|
shelljs.grep(opts, regex, file1, file2);
|
||||||
|
shelljs.head(file);
|
||||||
|
shelljs.head(opts, file);
|
||||||
|
shelljs.head(opts, file1, file2);
|
||||||
|
shelljs.ln(file1, file2);
|
||||||
|
shelljs.ln(opts, file1, file2);
|
||||||
|
shelljs.ls(file);
|
||||||
|
shelljs.ls(opts, file);
|
||||||
|
shelljs.ls(opts, file1, file2);
|
||||||
|
shelljs.ls(file1, file2);
|
||||||
|
shelljs.mkdir(file);
|
||||||
|
shelljs.mkdir(opts, file);
|
||||||
|
shelljs.mkdir(opts, file1, file2);
|
||||||
|
shelljs.mv(file1, file2);
|
||||||
|
shelljs.mv(opts, file1, file2);
|
||||||
|
shelljs.rm(file1);
|
||||||
|
shelljs.rm(file1, file2);
|
||||||
|
shelljs.rm(opts, file1, file2);
|
||||||
|
shelljs.sed(regex, replacement, file);
|
||||||
|
shelljs.sed(regex, replacement, file1, file2);
|
||||||
|
shelljs.sed(opts, regex, replacement, file);
|
||||||
|
shelljs.sed(opts, regex, replacement, file1, file2);
|
||||||
|
shelljs.sort(file);
|
||||||
|
shelljs.sort(opts, file);
|
||||||
|
shelljs.sort(opts, file1, file2);
|
||||||
|
shelljs.tail(file);
|
||||||
|
shelljs.tail(opts, file);
|
||||||
|
shelljs.tail(opts, file1, file2);
|
||||||
|
shelljs.tail(file1, file2);
|
||||||
|
|
||||||
|
shelljs.cat(file1).to(file2);
|
||||||
|
shelljs.cat(file1).toEnd(file2);
|
||||||
|
|
||||||
|
shelljs.touch(file);
|
||||||
|
shelljs.touch(opts, file);
|
||||||
|
shelljs.touch(opts, file1, file2);
|
||||||
|
shelljs.touch(file1, file2);
|
||||||
|
|
||||||
|
shelljs.uniq(file);
|
||||||
|
shelljs.uniq(file1, file2);
|
||||||
|
shelljs.uniq(opts, file1, file2);
|
||||||
Reference in New Issue
Block a user