mirror of
https://github.com/github/codeql.git
synced 2026-04-30 19:26:02 +02:00
Merge branch 'main' into aeisenberg/remove-upgrades
This commit is contained in:
@@ -2,5 +2,8 @@ name: codeql/javascript-experimental-atm-lib
|
||||
version: 0.0.2
|
||||
extractor: javascript
|
||||
library: true
|
||||
groups:
|
||||
- javascript
|
||||
- experimental
|
||||
dependencies:
|
||||
codeql/javascript-all: "*"
|
||||
|
||||
@@ -3,6 +3,9 @@ language: javascript
|
||||
version: 0.0.2
|
||||
suites: codeql-suites
|
||||
defaultSuiteFile: codeql-suites/javascript-atm-code-scanning.qls
|
||||
groups:
|
||||
- javascript
|
||||
- experimental
|
||||
dependencies:
|
||||
codeql/javascript-experimental-atm-lib: "*"
|
||||
mlModels:
|
||||
|
||||
@@ -85,6 +85,7 @@ import semmle.javascript.frameworks.CookieLibraries
|
||||
import semmle.javascript.frameworks.Credentials
|
||||
import semmle.javascript.frameworks.CryptoLibraries
|
||||
import semmle.javascript.frameworks.D3
|
||||
import semmle.javascript.frameworks.data.ModelsAsData
|
||||
import semmle.javascript.frameworks.DateFunctions
|
||||
import semmle.javascript.frameworks.DigitalOcean
|
||||
import semmle.javascript.frameworks.Electron
|
||||
|
||||
@@ -16,3 +16,13 @@ abstract class CredentialsExpr extends Expr {
|
||||
*/
|
||||
abstract string getCredentialsKind();
|
||||
}
|
||||
|
||||
private class CredentialsFromModel extends CredentialsExpr {
|
||||
string kind;
|
||||
|
||||
CredentialsFromModel() {
|
||||
this = ModelOutput::getASinkNode("credentials[" + kind + "]").getARhs().asExpr()
|
||||
}
|
||||
|
||||
override string getCredentialsKind() { result = kind }
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@ module SQL {
|
||||
/** A string-valued expression that is interpreted as a SQL command. */
|
||||
abstract class SqlString extends Expr { }
|
||||
|
||||
private class SqlStringFromModel extends SqlString {
|
||||
SqlStringFromModel() { this = ModelOutput::getASinkNode("sql-injection").getARhs().asExpr() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression that sanitizes a string to make it safe to embed into
|
||||
* a SQL command.
|
||||
@@ -474,176 +478,65 @@ private module MsSql {
|
||||
* Provides classes modeling the `sequelize` package.
|
||||
*/
|
||||
private module Sequelize {
|
||||
/** Gets an import of the `sequelize` module or one that re-exports it. */
|
||||
API::Node sequelize() { result = API::moduleImport(["sequelize", "sequelize-typescript"]) }
|
||||
|
||||
/** Gets an expression that creates an instance of the `Sequelize` class. */
|
||||
API::Node instance() {
|
||||
result = [sequelize(), sequelize().getMember("Sequelize")].getInstance()
|
||||
or
|
||||
result = API::Node::ofType(["sequelize", "sequelize-typescript"], ["Sequelize", "default"])
|
||||
}
|
||||
|
||||
/** A call to `Sequelize.query`. */
|
||||
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
|
||||
QueryCall() { this = instance().getMember("query").getACall() }
|
||||
|
||||
override DataFlow::Node getAQueryArgument() {
|
||||
result = this.getArgument(0)
|
||||
or
|
||||
result = this.getOptionArgument(0, "query")
|
||||
class SequelizeModel extends ModelInput::TypeModelCsv {
|
||||
override predicate row(string row) {
|
||||
// package1;type1;package2;type2;path
|
||||
row =
|
||||
[
|
||||
"sequelize;;sequelize-typescript;;", //
|
||||
"sequelize;Sequelize;sequelize;default;", //
|
||||
"sequelize;Sequelize;sequelize;;Instance",
|
||||
"sequelize;Sequelize;sequelize;;Member[Sequelize].Instance",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/** An expression that is passed to `Sequelize.query` method and hence interpreted as SQL. */
|
||||
class QueryString extends SQL::SqlString {
|
||||
QueryString() {
|
||||
this = any(QueryCall qc).getAQueryArgument().asExpr()
|
||||
or
|
||||
this = sequelize().getMember(["literal", "asIs"]).getParameter(0).getARhs().asExpr()
|
||||
class SequelizeSink extends ModelInput::SinkModelCsv {
|
||||
override predicate row(string row) {
|
||||
row =
|
||||
[
|
||||
"sequelize;Sequelize;Member[query].Argument[0];sql-injection",
|
||||
"sequelize;Sequelize;Member[query].Argument[0].Member[query];sql-injection",
|
||||
"sequelize;;Member[literal,asIs].Argument[0];sql-injection",
|
||||
"sequelize;;Argument[1];credentials[user name]",
|
||||
"sequelize;;Argument[2];credentials[password]",
|
||||
"sequelize;;Argument[0..].Member[username];credentials[user name]",
|
||||
"sequelize;;Argument[0..].Member[password];credentials[password]"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression that is passed as user name or password when creating an instance of the
|
||||
* `Sequelize` class.
|
||||
*/
|
||||
class Credentials extends CredentialsExpr {
|
||||
string kind;
|
||||
|
||||
Credentials() {
|
||||
exists(NewExpr ne, string prop |
|
||||
ne = sequelize().getAnInstantiation().asExpr() and
|
||||
(
|
||||
this = ne.getArgument(1) and prop = "username"
|
||||
or
|
||||
this = ne.getArgument(2) and prop = "password"
|
||||
or
|
||||
ne.hasOptionArgument(ne.getNumArgument() - 1, prop, this)
|
||||
) and
|
||||
(
|
||||
prop = "username" and kind = "user name"
|
||||
or
|
||||
prop = "password" and kind = prop
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override string getCredentialsKind() { result = kind }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides classes modeling the Google Cloud Spanner library.
|
||||
*/
|
||||
private module Spanner {
|
||||
/**
|
||||
* Gets a node that refers to the `Spanner` class
|
||||
*/
|
||||
API::Node spanner() {
|
||||
// older versions
|
||||
result = API::moduleImport("@google-cloud/spanner")
|
||||
or
|
||||
// newer versions
|
||||
result = API::moduleImport("@google-cloud/spanner").getMember("Spanner")
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that refers to an instance of the `Database` class.
|
||||
*/
|
||||
API::Node database() {
|
||||
result =
|
||||
spanner().getReturn().getMember("instance").getReturn().getMember("database").getReturn()
|
||||
or
|
||||
result = API::Node::ofType("@google-cloud/spanner", "Database")
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that refers to an instance of the `v1.SpannerClient` class.
|
||||
*/
|
||||
API::Node v1SpannerClient() {
|
||||
result = spanner().getMember("v1").getMember("SpannerClient").getInstance()
|
||||
or
|
||||
result = API::Node::ofType("@google-cloud/spanner", "v1.SpannerClient")
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that refers to a transaction object.
|
||||
*/
|
||||
API::Node transaction() {
|
||||
result =
|
||||
database()
|
||||
.getMember(["runTransaction", "runTransactionAsync"])
|
||||
.getParameter([0, 1])
|
||||
.getParameter(1)
|
||||
or
|
||||
result = API::Node::ofType("@google-cloud/spanner", "Transaction")
|
||||
}
|
||||
|
||||
/** Gets an API node referring to a `BatchTransaction` object. */
|
||||
API::Node batchTransaction() {
|
||||
result = database().getMember("batchTransaction").getReturn()
|
||||
or
|
||||
result = database().getMember("createBatchTransaction").getReturn().getPromised()
|
||||
or
|
||||
result = API::Node::ofType("@google-cloud/spanner", "BatchTransaction")
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a Spanner method that executes a SQL query.
|
||||
*/
|
||||
abstract class SqlExecution extends DatabaseAccess, DataFlow::InvokeNode { }
|
||||
|
||||
/**
|
||||
* A SQL execution that takes the input directly in the first argument or in the `sql` option.
|
||||
*/
|
||||
class SqlExecutionDirect extends SqlExecution {
|
||||
SqlExecutionDirect() {
|
||||
this = database().getMember(["run", "runPartitionedUpdate", "runStream"]).getACall()
|
||||
or
|
||||
this = transaction().getMember(["run", "runStream", "runUpdate"]).getACall()
|
||||
or
|
||||
this = batchTransaction().getMember("createQueryPartitions").getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAQueryArgument() {
|
||||
result = this.getArgument(0)
|
||||
or
|
||||
result = this.getOptionArgument(0, "sql")
|
||||
private module SpannerCsv {
|
||||
class SpannerTypes extends ModelInput::TypeModelCsv {
|
||||
override predicate row(string row) {
|
||||
// package1; type1; package2; type2; path
|
||||
row =
|
||||
[
|
||||
"@google-cloud/spanner;;@google-cloud/spanner;;Member[Spanner]",
|
||||
"@google-cloud/spanner;Database;@google-cloud/spanner;;ReturnValue.Member[instance].ReturnValue.Member[database].ReturnValue",
|
||||
"@google-cloud/spanner;v1.SpannerClient;@google-cloud/spanner;;Member[v1].Member[SpannerClient].Instance",
|
||||
"@google-cloud/spanner;Transaction;@google-cloud/spanner;Database;Member[runTransaction,runTransactionAsync].Argument[0..1].Parameter[1]",
|
||||
"@google-cloud/spanner;BatchTransaction;@google-cloud/spanner;Database;Member[batchTransaction].ReturnValue",
|
||||
"@google-cloud/spanner;BatchTransaction;@google-cloud/spanner;Database;Member[createBatchTransaction].ReturnValue.Awaited",
|
||||
"@google-cloud/spanner;~SqlExecutorDirect;@google-cloud/spanner;Database;Member[run,runPartitionedUpdate,runStream]",
|
||||
"@google-cloud/spanner;~SqlExecutorDirect;@google-cloud/spanner;Transaction;Member[run,runStream,runUpdate]",
|
||||
"@google-cloud/spanner;~SqlExecutorDirect;@google-cloud/spanner;BatchTransaction;Member[createQueryPartitions]",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A SQL execution that takes an array of SQL strings or { sql: string } objects.
|
||||
*/
|
||||
class SqlExecutionBatch extends SqlExecution, API::CallNode {
|
||||
SqlExecutionBatch() { this = transaction().getMember("batchUpdate").getACall() }
|
||||
|
||||
override DataFlow::Node getAQueryArgument() {
|
||||
// just use the whole array as the query argument, as arrays becomes tainted if one of the elements
|
||||
// are tainted
|
||||
result = this.getArgument(0)
|
||||
or
|
||||
result = this.getParameter(0).getUnknownMember().getMember("sql").getARhs()
|
||||
class SpannerSinks extends ModelInput::SinkModelCsv {
|
||||
override predicate row(string row) {
|
||||
// package; type; path; kind
|
||||
row =
|
||||
[
|
||||
"@google-cloud/spanner;~SqlExecutorDirect;Argument[0];sql-injection",
|
||||
"@google-cloud/spanner;~SqlExecutorDirect;Argument[0].Member[sql];sql-injection",
|
||||
"@google-cloud/spanner;Transaction;Member[batchUpdate].Argument[0];sql-injection",
|
||||
"@google-cloud/spanner;Transaction;Member[batchUpdate].Argument[0].ArrayElement.Member[sql];sql-injection",
|
||||
"@google-cloud/spanner;v1.SpannerClient;Member[executeSql,executeStreamingSql].Argument[0].Member[sql];sql-injection",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A SQL execution that only takes the input in the `sql` option, and do not accept query strings
|
||||
* directly.
|
||||
*/
|
||||
class SqlExecutionWithOption extends SqlExecution {
|
||||
SqlExecutionWithOption() {
|
||||
this = v1SpannerClient().getMember(["executeSql", "executeStreamingSql"]).getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAQueryArgument() { result = this.getOptionArgument(0, "sql") }
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression that is interpreted as a SQL string.
|
||||
*/
|
||||
class QueryString extends SQL::SqlString {
|
||||
QueryString() { this = any(SqlExecution se).getAQueryArgument().asExpr() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Provides classes for contributing a model, or using the interpreted results
|
||||
* of a model represented as data.
|
||||
*
|
||||
* - Use the `ModelInput` module to contribute new models.
|
||||
* - Use the `ModelOutput` module to access the model results in terms of API nodes.
|
||||
*
|
||||
* The package name refers to an NPM package name or a path within a package name such as `lodash/extend`.
|
||||
* The string `global` refers to the global object (whether it came from the `global` package or not).
|
||||
*
|
||||
* The following tokens have a language-specific interpretation:
|
||||
* - `Instance`: the value returned by a `new`-call to a function
|
||||
* - `Awaited`: the value from a resolved promise
|
||||
*
|
||||
* A `(package, type)` tuple may refer to the exported type named `type` from the NPM package `package`.
|
||||
* For example, `(express, Request)` would match a parameter below due to the type annotation:
|
||||
* ```ts
|
||||
* import * as express from 'express';
|
||||
* export function handler(req: express.Request) { ... }
|
||||
* ```
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
private import internal.Shared as Shared
|
||||
import Shared::ModelInput as ModelInput
|
||||
import Shared::ModelOutput as ModelOutput
|
||||
|
||||
/**
|
||||
* A remote flow source originating from a CSV source row.
|
||||
*/
|
||||
private class RemoteFlowSourceFromCsv extends RemoteFlowSource {
|
||||
RemoteFlowSourceFromCsv() { this = ModelOutput::getASourceNode("remote").getAnImmediateUse() }
|
||||
|
||||
override string getSourceType() { result = "Remote flow" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `ModelOutput::summaryStep` but with API nodes mapped to data-flow nodes.
|
||||
*/
|
||||
private predicate summaryStepNodes(DataFlow::Node pred, DataFlow::Node succ, string kind) {
|
||||
exists(API::Node predNode, API::Node succNode |
|
||||
ModelOutput::summaryStep(predNode, succNode, kind) and
|
||||
pred = predNode.getARhs() and
|
||||
succ = succNode.getAnImmediateUse()
|
||||
)
|
||||
}
|
||||
|
||||
/** Data flow steps induced by summary models of kind `value`. */
|
||||
private class DataFlowStepFromSummary extends DataFlow::SharedFlowStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
summaryStepNodes(pred, succ, "value")
|
||||
}
|
||||
}
|
||||
|
||||
/** Taint steps induced by summary models of kind `taint`. */
|
||||
private class TaintStepFromSummary extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
summaryStepNodes(pred, succ, "taint")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Contains the language-specific part of the models-as-data implementation found in `Shared.qll`.
|
||||
*
|
||||
* It must export the following members:
|
||||
* ```codeql
|
||||
* class Unit // a unit type
|
||||
* module API // the API graph module
|
||||
* predicate isPackageUsed(string package)
|
||||
* API::Node getExtraNodeFromPath(string package, string type, string path, int n)
|
||||
* API::Node getExtraSuccessorFromNode(API::Node node, AccessPathToken token)
|
||||
* API::Node getExtraSuccessorFromInvoke(API::InvokeNode node, AccessPathToken token)
|
||||
* predicate invocationMatchesExtraCallSiteFilter(API::InvokeNode invoke, AccessPathToken token)
|
||||
* ```
|
||||
*/
|
||||
|
||||
private import javascript as js
|
||||
private import js::DataFlow as DataFlow
|
||||
private import Shared
|
||||
|
||||
class Unit = js::Unit;
|
||||
|
||||
module API = js::API;
|
||||
|
||||
/**
|
||||
* Holds if models describing `package` may be relevant for the analysis of this database.
|
||||
*/
|
||||
predicate isPackageUsed(string package) {
|
||||
exists(DataFlow::moduleImport(package))
|
||||
or
|
||||
package = "global"
|
||||
or
|
||||
exists(API::Node::ofType(package, _))
|
||||
}
|
||||
|
||||
/** Holds if `global` is a global variable referenced via a the `global` package in a CSV row. */
|
||||
private predicate isRelevantGlobal(string global) {
|
||||
exists(AccessPath path, AccessPathToken token |
|
||||
isRelevantFullPath("global", "", path) and
|
||||
token = path.getToken(0) and
|
||||
token.getName() = "Member" and
|
||||
global = token.getAnArgument()
|
||||
)
|
||||
}
|
||||
|
||||
/** An API graph entry point for global variables mentioned in a model. */
|
||||
private class GlobalApiEntryPoint extends API::EntryPoint {
|
||||
string global;
|
||||
|
||||
GlobalApiEntryPoint() {
|
||||
isRelevantGlobal(global) and
|
||||
this = "GlobalApiEntryPoint:" + global
|
||||
}
|
||||
|
||||
override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef(global) }
|
||||
|
||||
override DataFlow::Node getARhs() { none() }
|
||||
|
||||
/** Gets the name of the global variable. */
|
||||
string getGlobal() { result = global }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an API node referring to the given global variable (if relevant).
|
||||
*/
|
||||
private API::Node getGlobalNode(string globalName) {
|
||||
result = any(GlobalApiEntryPoint e | e.getGlobal() = globalName).getNode()
|
||||
}
|
||||
|
||||
/** Gets a JavaScript-specific interpretation of the `(package, type, path)` tuple after resolving the first `n` access path tokens. */
|
||||
bindingset[package, type, path]
|
||||
API::Node getExtraNodeFromPath(string package, string type, AccessPath path, int n) {
|
||||
// Global variable accesses is via the 'global' package
|
||||
exists(AccessPathToken token |
|
||||
package = getAPackageAlias("global") and
|
||||
type = "" and
|
||||
token = path.getToken(0) and
|
||||
token.getName() = "Member" and
|
||||
result = getGlobalNode(token.getAnArgument()) and
|
||||
n = 1
|
||||
)
|
||||
or
|
||||
// Access instance of a type based on type annotations
|
||||
n = 0 and
|
||||
result = API::Node::ofType(getAPackageAlias(package), type)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a JavaScript-specific API graph successor of `node` reachable by resolving `token`.
|
||||
*/
|
||||
bindingset[token]
|
||||
API::Node getExtraSuccessorFromNode(API::Node node, AccessPathToken token) {
|
||||
token.getName() = "Instance" and
|
||||
result = node.getInstance()
|
||||
or
|
||||
token.getName() = "Awaited" and
|
||||
result = node.getPromised()
|
||||
or
|
||||
token.getName() = "ArrayElement" and
|
||||
result = node.getMember(DataFlow::PseudoProperties::arrayElement())
|
||||
or
|
||||
token.getName() = "Element" and
|
||||
result = node.getMember(DataFlow::PseudoProperties::arrayLikeElement())
|
||||
or
|
||||
// Note: MapKey not currently supported
|
||||
token.getName() = "MapValue" and
|
||||
result = node.getMember(DataFlow::PseudoProperties::mapValueAll())
|
||||
or
|
||||
// Currently we need to include the "unknown member" for ArrayElement and Element since
|
||||
// API graphs do not use store/load steps for arrays
|
||||
token.getName() = ["ArrayElement", "Element"] and
|
||||
result = node.getUnknownMember()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a JavaScript-specific API graph successor of `node` reachable by resolving `token`.
|
||||
*/
|
||||
bindingset[token]
|
||||
API::Node getExtraSuccessorFromInvoke(API::InvokeNode node, AccessPathToken token) {
|
||||
token.getName() = "Instance" and
|
||||
result = node.getInstance()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `invoke` matches the JS-specific call site filter in `token`.
|
||||
*/
|
||||
bindingset[token]
|
||||
predicate invocationMatchesExtraCallSiteFilter(API::InvokeNode invoke, AccessPathToken token) {
|
||||
token.getName() = "NewCall" and
|
||||
invoke instanceof API::NewNode
|
||||
or
|
||||
token.getName() = "Call" and
|
||||
invoke instanceof API::CallNode and
|
||||
invoke instanceof DataFlow::CallNode // Workaround compiler bug
|
||||
}
|
||||
@@ -0,0 +1,591 @@
|
||||
/**
|
||||
* INTERNAL use only. This is an experimental API subject to change without notice.
|
||||
*
|
||||
* Provides classes and predicates for dealing with flow models specified in CSV format.
|
||||
*
|
||||
* The CSV specification has the following columns:
|
||||
* - Sources:
|
||||
* `package; type; path; kind`
|
||||
* - Sinks:
|
||||
* `package; type; path; kind`
|
||||
* - Summaries:
|
||||
* `package; type; path; input; output; kind`
|
||||
* - Types:
|
||||
* `package1; type1; package2; type2; path`
|
||||
*
|
||||
* The interpretation of a row is similar to API-graphs with a left-to-right
|
||||
* reading.
|
||||
* 1. The `package` column selects a package name, as it would be referenced in the source code,
|
||||
* such as an NPM package, PIP package, or Ruby gem. (See `ModelsAsData.qll` for language-specific details).
|
||||
* It may also be a synthetic package used for a type definition (see type definitions below).
|
||||
* 2. The `type` column selects all instances of a named type originating from that package,
|
||||
* or the empty string if referring to the package itself.
|
||||
* It can also be a synthetic type name defined by a type definition (see type definitions below).
|
||||
* 3. The `path` column is a `.`-separated list of "access path tokens" to resolve, starting at the node selected by `package` and `type`.
|
||||
* The possible access path tokens are:
|
||||
* - Member[x] : a property named `x`. May be a comma-separated list of named.
|
||||
* - Argument[n]: the n-th argument to a call. May be a range of form `x..y` (inclusive) and/or a comma-separated list.
|
||||
* Additionally, `N-1` refers to the last argument, `N-2` refers to the second-last, and so on.
|
||||
* - Parameter[n]: the n-th parameter of a callback. May be a range of form `x..y` (inclusive) and/or a comma-separated list.
|
||||
* - ReturnValue: the value returned by a function call
|
||||
* - Instance: the value returned by a constructor call
|
||||
* - Awaited: the value from a resolved promise/future-like object
|
||||
* - WithArity[n]: match a call with the given arity. May be a range of form `x..y` (inclusive) and/or a comma-separated list.
|
||||
* - Other langauge-specific tokens mentioned in `ModelsAsData.qll`.
|
||||
* 4. The `input` and `output` columns specify how data enters and leaves the element selected by the
|
||||
* first `(package, type, path)` tuple. Both strings are `.`-separated access paths
|
||||
* of the same syntax as the `path` column.
|
||||
* 5. The `kind` column is a tag that can be referenced from QL to determine to
|
||||
* which classes the interpreted elements should be added. For example, for
|
||||
* sources `"remote"` indicates a default remote flow source, and for summaries
|
||||
* `"taint"` indicates a default additional taint step and `"value"` indicates a
|
||||
* globally applicable value-preserving step.
|
||||
*
|
||||
* ### Types
|
||||
*
|
||||
* A type row of form `package1; type1; package2; type2; path` indicates that `package2; type2; path`
|
||||
* should be seen as an instance of the type `package1; type1`.
|
||||
*
|
||||
* A `(package,type)` pair may refer to a static type or a synthetic type name used internally in the model.
|
||||
* Synthetic type names can be used to reuse intermediate sub-paths, when there are multiple ways to access the same
|
||||
* element.
|
||||
* See `ModelsAsData.qll` for the langauge-specific interpretation of packages and static type names.
|
||||
*
|
||||
* By convention, if one wants to avoid clashes with static types from the package, the type name
|
||||
* should be prefixed with a tilde character (`~`). For example, `(foo, ~Bar)` can be used to indicate that
|
||||
* the type is related to the `foo` package but is not intended to match a static type.
|
||||
*/
|
||||
|
||||
private import Impl as Impl
|
||||
|
||||
private class Unit = Impl::Unit;
|
||||
|
||||
private module API = Impl::API;
|
||||
|
||||
/** Module containing hooks for providing input data to be interpreted as a model. */
|
||||
module ModelInput {
|
||||
/**
|
||||
* A unit class for adding additional source model rows.
|
||||
*
|
||||
* Extend this class to add additional source definitions.
|
||||
*/
|
||||
class SourceModelCsv extends Unit {
|
||||
/**
|
||||
* Holds if `row` specifies a source definition.
|
||||
*
|
||||
* A row of form
|
||||
* ```
|
||||
* package;type;path;kind
|
||||
* ```
|
||||
* indicates that the value at `(package, type, path)` should be seen as a flow
|
||||
* source of the given `kind`.
|
||||
*
|
||||
* The kind `remote` represents a general remote flow source.
|
||||
*/
|
||||
abstract predicate row(string row);
|
||||
}
|
||||
|
||||
/**
|
||||
* A unit class for adding additional sink model rows.
|
||||
*
|
||||
* Extend this class to add additional sink definitions.
|
||||
*/
|
||||
class SinkModelCsv extends Unit {
|
||||
/**
|
||||
* Holds if `row` specifies a sink definition.
|
||||
*
|
||||
* A row of form
|
||||
* ```
|
||||
* package;type;path;kind
|
||||
* ```
|
||||
* indicates that the value at `(package, type, path)` should be seen as a sink
|
||||
* of the given `kind`.
|
||||
*/
|
||||
abstract predicate row(string row);
|
||||
}
|
||||
|
||||
/**
|
||||
* A unit class for adding additional summary model rows.
|
||||
*
|
||||
* Extend this class to add additional flow summary definitions.
|
||||
*/
|
||||
class SummaryModelCsv extends Unit {
|
||||
/**
|
||||
* Holds if `row` specifies a summary definition.
|
||||
*
|
||||
* A row of form
|
||||
* ```
|
||||
* package;type;path;input;output;kind
|
||||
* ```
|
||||
* indicates that for each call to `(package, type, path)`, the value referred to by `input`
|
||||
* can flow to the value referred to by `output`.
|
||||
*
|
||||
* `kind` should be either `value` or `taint`, for value-preserving or taint-preserving steps,
|
||||
* respectively.
|
||||
*/
|
||||
abstract predicate row(string row);
|
||||
}
|
||||
|
||||
/**
|
||||
* A unit class for adding additional type model rows.
|
||||
*
|
||||
* Extend this class to add additional type definitions.
|
||||
*/
|
||||
class TypeModelCsv extends Unit {
|
||||
/**
|
||||
* Holds if `row` specifies a type definition.
|
||||
*
|
||||
* A row of form,
|
||||
* ```
|
||||
* package1;type1;package2;type2;path
|
||||
* ```
|
||||
* indicates that `(package2, type2, path)` should be seen as an instance of `(package1, type1)`.
|
||||
*/
|
||||
abstract predicate row(string row);
|
||||
}
|
||||
}
|
||||
|
||||
private import ModelInput
|
||||
|
||||
/**
|
||||
* Append `;dummy` to the value of `s` to work around the fact that `string.split(delim,n)`
|
||||
* does not preserve empty trailing substrings.
|
||||
*/
|
||||
bindingset[result]
|
||||
private string inversePad(string s) { s = result + ";dummy" }
|
||||
|
||||
private predicate sourceModel(string row) { any(SourceModelCsv s).row(inversePad(row)) }
|
||||
|
||||
private predicate sinkModel(string row) { any(SinkModelCsv s).row(inversePad(row)) }
|
||||
|
||||
private predicate summaryModel(string row) { any(SummaryModelCsv s).row(inversePad(row)) }
|
||||
|
||||
private predicate typeModel(string row) { any(TypeModelCsv s).row(inversePad(row)) }
|
||||
|
||||
/**
|
||||
* Replaces `..` with `-->` in order to simplify subsequent parsing.
|
||||
*/
|
||||
bindingset[path]
|
||||
private string normalizePath(string path) { result = path.replaceAll("..", "-->") }
|
||||
|
||||
/** Holds if a source model exists for the given parameters. */
|
||||
predicate sourceModel(string package, string type, string path, string kind) {
|
||||
exists(string row |
|
||||
sourceModel(row) and
|
||||
row.splitAt(";", 0) = package and
|
||||
row.splitAt(";", 1) = type and
|
||||
normalizePath(row.splitAt(";", 2)) = path and
|
||||
row.splitAt(";", 3) = kind
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a sink model exists for the given parameters. */
|
||||
private predicate sinkModel(string package, string type, string path, string kind) {
|
||||
exists(string row |
|
||||
sinkModel(row) and
|
||||
row.splitAt(";", 0) = package and
|
||||
row.splitAt(";", 1) = type and
|
||||
normalizePath(row.splitAt(";", 2)) = path and
|
||||
row.splitAt(";", 3) = kind
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a summary model `row` exists for the given parameters. */
|
||||
private predicate summaryModel(
|
||||
string package, string type, string path, string input, string output, string kind
|
||||
) {
|
||||
exists(string row |
|
||||
summaryModel(row) and
|
||||
row.splitAt(";", 0) = package and
|
||||
row.splitAt(";", 1) = type and
|
||||
normalizePath(row.splitAt(";", 2)) = path and
|
||||
normalizePath(row.splitAt(";", 3)) = input and
|
||||
normalizePath(row.splitAt(";", 4)) = output and
|
||||
row.splitAt(";", 5) = kind
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if an type model exists for the given parameters. */
|
||||
private predicate typeModel(
|
||||
string package1, string type1, string package2, string type2, string path
|
||||
) {
|
||||
exists(string row |
|
||||
typeModel(row) and
|
||||
row.splitAt(";", 0) = package1 and
|
||||
row.splitAt(";", 1) = type1 and
|
||||
row.splitAt(";", 2) = package2 and
|
||||
row.splitAt(";", 3) = type2 and
|
||||
normalizePath(row.splitAt(";", 4)) = path
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a package that should be seen as an alias for the given other `package`,
|
||||
* or the `package` itself.
|
||||
*/
|
||||
bindingset[package]
|
||||
bindingset[result]
|
||||
string getAPackageAlias(string package) {
|
||||
typeModel(package, "", result, "", "")
|
||||
or
|
||||
result = package
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if CSV rows involving `package` might be relevant for the analysis of this database.
|
||||
*/
|
||||
private predicate isRelevantPackage(string package) {
|
||||
Impl::isPackageUsed(package)
|
||||
or
|
||||
exists(string other |
|
||||
isRelevantPackage(other) and
|
||||
typeModel(package, _, other, _, _)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `package,type,path` is used in some CSV row.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate isRelevantFullPath(string package, string type, string path) {
|
||||
isRelevantPackage(package) and
|
||||
(
|
||||
sourceModel(package, type, path, _) or
|
||||
sinkModel(package, type, path, _) or
|
||||
summaryModel(package, type, path, _, _, _) or
|
||||
typeModel(_, _, package, type, path)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A string that occurs as an access path (either identifying or input/output spec)
|
||||
* which might be relevant for this database.
|
||||
*/
|
||||
class AccessPath extends string {
|
||||
AccessPath() {
|
||||
isRelevantFullPath(_, _, this)
|
||||
or
|
||||
exists(string package | isRelevantPackage(package) |
|
||||
summaryModel(package, _, _, this, _, _) or
|
||||
summaryModel(package, _, _, _, this, _)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the `n`th token on the access path as a string. */
|
||||
string getRawToken(int n) {
|
||||
this != "" and // The empty path should have zero tokens, not a single empty token
|
||||
result = this.splitAt(".", n)
|
||||
}
|
||||
|
||||
/** Gets the `n`th token on the access path. */
|
||||
AccessPathToken getToken(int n) { result = this.getRawToken(n) }
|
||||
|
||||
/** Gets the number of tokens on the path. */
|
||||
int getNumToken() { result = count(int n | exists(this.getRawToken(n))) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a successor of `node` in the API graph.
|
||||
*/
|
||||
bindingset[token]
|
||||
private API::Node getSuccessorFromNode(API::Node node, AccessPathToken token) {
|
||||
// API graphs use the same label for arguments and parameters. An edge originating from a
|
||||
// use-node represents be an argument, and an edge originating from a def-node represents a parameter.
|
||||
// We just map both to the same thing.
|
||||
token.getName() = ["Argument", "Parameter"] and
|
||||
result = node.getParameter(getAnIntFromStringUnbounded(token.getAnArgument()))
|
||||
or
|
||||
token.getName() = "Member" and
|
||||
result = node.getMember(token.getAnArgument())
|
||||
or
|
||||
token.getName() = "ReturnValue" and
|
||||
result = node.getReturn()
|
||||
or
|
||||
// Language-specific tokens
|
||||
result = Impl::getExtraSuccessorFromNode(node, token)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an API-graph successor for the given invocation.
|
||||
*/
|
||||
bindingset[token]
|
||||
private API::Node getSuccessorFromInvoke(API::InvokeNode invoke, AccessPathToken token) {
|
||||
token.getName() = "Argument" and
|
||||
(
|
||||
result = invoke.getParameter(getAnIntFromStringUnbounded(token.getAnArgument()))
|
||||
or
|
||||
result =
|
||||
invoke
|
||||
.getParameter(getAnIntFromStringWithArity(token.getAnArgument(), invoke.getNumArgument()))
|
||||
)
|
||||
or
|
||||
token.getName() = "ReturnValue" and
|
||||
result = invoke.getReturn()
|
||||
or
|
||||
// Language-specific tokens
|
||||
result = Impl::getExtraSuccessorFromInvoke(invoke, token)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `invoke` invokes a call-site filter given by `token`.
|
||||
*/
|
||||
pragma[inline]
|
||||
private predicate invocationMatchesCallSiteFilter(API::InvokeNode invoke, AccessPathToken token) {
|
||||
token.getName() = "WithArity" and
|
||||
invoke.getNumArgument() = getAnIntFromStringUnbounded(token.getAnArgument())
|
||||
or
|
||||
Impl::invocationMatchesExtraCallSiteFilter(invoke, token)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the API node identified by the first `n` tokens of `path` in the given `(package, type, path)` tuple.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
API::Node getNodeFromPath(string package, string type, AccessPath path, int n) {
|
||||
isRelevantFullPath(package, type, path) and
|
||||
(
|
||||
type = "" and
|
||||
n = 0 and
|
||||
result = API::moduleImport(package)
|
||||
or
|
||||
n = 0 and
|
||||
exists(string package2, string type2, AccessPath path2 |
|
||||
typeModel(package, type, package2, type2, path2) and
|
||||
result = getNodeFromPath(package2, type2, path2, path2.getNumToken())
|
||||
)
|
||||
or
|
||||
// Language-specific cases, such as handling of global variables
|
||||
result = Impl::getExtraNodeFromPath(package, type, path, n)
|
||||
)
|
||||
or
|
||||
result = getSuccessorFromNode(getNodeFromPath(package, type, path, n - 1), path.getToken(n - 1))
|
||||
or
|
||||
// Similar to the other recursive case, but where the path may have stepped through one or more call-site filters
|
||||
result =
|
||||
getSuccessorFromInvoke(getInvocationFromPath(package, type, path, n - 1), path.getToken(n - 1))
|
||||
}
|
||||
|
||||
/** Gets the node identified by the given `(package, type, path)` tuple. */
|
||||
API::Node getNodeFromPath(string package, string type, AccessPath path) {
|
||||
result = getNodeFromPath(package, type, path, path.getNumToken())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an invocation identified by the given `(package, type, path)` tuple.
|
||||
*
|
||||
* Unlike `getNodeFromPath`, the `path` may end with one or more call-site filters.
|
||||
*/
|
||||
API::InvokeNode getInvocationFromPath(string package, string type, AccessPath path, int n) {
|
||||
result = getNodeFromPath(package, type, path, n).getAnInvocation()
|
||||
or
|
||||
result = getInvocationFromPath(package, type, path, n - 1) and
|
||||
invocationMatchesCallSiteFilter(result, path.getToken(n - 1))
|
||||
}
|
||||
|
||||
/** Gets an invocation identified by the given `(package, type, path)` tuple. */
|
||||
API::InvokeNode getInvocationFromPath(string package, string type, AccessPath path) {
|
||||
result = getInvocationFromPath(package, type, path, path.getNumToken())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a summary edge with the given `input, output, kind` columns have a `package, type, path` tuple
|
||||
* that resolves to `baseNode`.
|
||||
*/
|
||||
private predicate resolvedSummaryBase(
|
||||
API::InvokeNode baseNode, AccessPath input, AccessPath output, string kind
|
||||
) {
|
||||
exists(string package, string type, AccessPath path |
|
||||
summaryModel(package, type, path, input, output, kind) and
|
||||
baseNode = getInvocationFromPath(package, type, path)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `path` is an input or output spec for a summary with the given `base` node.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate relevantInputOutputPath(API::InvokeNode base, AccessPath path) {
|
||||
resolvedSummaryBase(base, path, _, _)
|
||||
or
|
||||
resolvedSummaryBase(base, _, path, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the API node for the first `n` tokens of the given input/output path, evaluated relative to `baseNode`.
|
||||
*/
|
||||
private API::Node getNodeFromInputOutputPath(API::InvokeNode baseNode, AccessPath path, int n) {
|
||||
relevantInputOutputPath(baseNode, path) and
|
||||
(
|
||||
n = 1 and
|
||||
result = getSuccessorFromInvoke(baseNode, path.getToken(0))
|
||||
or
|
||||
result =
|
||||
getSuccessorFromNode(getNodeFromInputOutputPath(baseNode, path, n - 1), path.getToken(n - 1))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the API node for the given input/output path, evaluated relative to `baseNode`.
|
||||
*/
|
||||
private API::Node getNodeFromInputOutputPath(API::InvokeNode baseNode, AccessPath path) {
|
||||
result = getNodeFromInputOutputPath(baseNode, path, path.getNumToken())
|
||||
}
|
||||
|
||||
/**
|
||||
* An access part token such as `Argument[1]` or `ReturnValue`, appearing in one or more access paths.
|
||||
*/
|
||||
class AccessPathToken extends string {
|
||||
AccessPathToken() { this = any(AccessPath path).getRawToken(_) }
|
||||
|
||||
private string getPart(int part) {
|
||||
result = this.regexpCapture("([^\\[]+)(?:\\[([^\\]]*)\\])?", part)
|
||||
}
|
||||
|
||||
/** Gets the name of the token, such as `Member` from `Member[x]` */
|
||||
string getName() { result = this.getPart(1) }
|
||||
|
||||
/**
|
||||
* Gets the argument list, such as `1,2` from `Member[1,2]`,
|
||||
* or has no result if there are no arguments.
|
||||
*/
|
||||
string getArgumentList() { result = this.getPart(2) }
|
||||
|
||||
/** Gets the `n`th argument to this token, such as `x` or `y` from `Member[x,y]`. */
|
||||
string getArgument(int n) { result = this.getArgumentList().splitAt(",", n) }
|
||||
|
||||
/** Gets an argument to this token, such as `x` or `y` from `Member[x,y]`. */
|
||||
string getAnArgument() { result = this.getArgument(_) }
|
||||
|
||||
/** Gets the number of arguments to this token, such as 2 for `Member[x,y]` or zero for `ReturnValue`. */
|
||||
int getNumArgument() { result = count(int n | exists(this.getArgument(n))) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience-predicate for extracting two capture groups at once.
|
||||
*/
|
||||
bindingset[input, regexp]
|
||||
private predicate regexpCaptureTwo(string input, string regexp, string capture1, string capture2) {
|
||||
capture1 = input.regexpCapture(regexp, 1) and
|
||||
capture2 = input.regexpCapture(regexp, 2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an integer constant `n` or interval `n1..n2` (inclusive) and gets the value
|
||||
* of the constant or any value contained in the interval.
|
||||
*/
|
||||
bindingset[arg]
|
||||
private int getAnIntFromString(string arg) {
|
||||
result = arg.toInt()
|
||||
or
|
||||
// Match "n1..n2", where ".." has previously been replaced with "-->" to simplify parsing
|
||||
exists(string lo, string hi |
|
||||
regexpCaptureTwo(arg, "(\\d+)-->(\\d+)", lo, hi) and
|
||||
result = [lo.toInt() .. hi.toInt()]
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a lower-bounded interval `n..` and gets the lower bound.
|
||||
*/
|
||||
bindingset[arg]
|
||||
private int getLowerBoundFromString(string arg) {
|
||||
// Match "n..", where ".." has previously been replaced with "-->" to simplify parsing
|
||||
result = arg.regexpCapture("(\\d+)-->", 1).toInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an integer constant or interval (bounded or unbounded) and gets any
|
||||
* of the integers contained within (of which there may be infinitely many).
|
||||
*
|
||||
* Has no result for arguments involving an explicit arity, such as `N-1`.
|
||||
*/
|
||||
bindingset[arg, result]
|
||||
private int getAnIntFromStringUnbounded(string arg) {
|
||||
result = getAnIntFromString(arg)
|
||||
or
|
||||
result >= getLowerBoundFromString(arg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an integer constant or interval (bounded or unbounded) that explicitly
|
||||
* references the arity, such as `N-1` or `N-3..N-1`.
|
||||
*
|
||||
* Note that such expressions will never resolve to a negative index, even if the
|
||||
* arity is zero (it will have no result in that case).
|
||||
*/
|
||||
bindingset[arg, arity]
|
||||
private int getAnIntFromStringWithArity(string arg, int arity) {
|
||||
result >= 0 and // do not allow N-1 to resolve to a negative index
|
||||
exists(string lo |
|
||||
// N-x
|
||||
lo = arg.regexpCapture("N-(\\d+)", 1) and
|
||||
result = arity - lo.toInt()
|
||||
or
|
||||
// N-x..
|
||||
lo = arg.regexpCapture("N-(\\d+)-->", 1) and
|
||||
result = [arity - lo.toInt(), arity - 1]
|
||||
)
|
||||
or
|
||||
exists(string lo, string hi |
|
||||
// x..N-y
|
||||
regexpCaptureTwo(arg, "(\\d+)-->N-(\\d+)", lo, hi) and
|
||||
result = [lo.toInt() .. arity - hi.toInt()]
|
||||
or
|
||||
// N-x..Ny
|
||||
regexpCaptureTwo(arg, "N-(\\d+)-->N-(\\d+)", lo, hi) and
|
||||
result = [arity - lo.toInt() .. arity - hi.toInt()] and
|
||||
result >= 0
|
||||
or
|
||||
// N-x..y
|
||||
regexpCaptureTwo(arg, "N-(\\d+)-->(\\d+)", lo, hi) and
|
||||
result = [arity - lo.toInt() .. hi.toInt()] and
|
||||
result >= 0
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Module providing access to the imported models in terms of API graph nodes.
|
||||
*/
|
||||
module ModelOutput {
|
||||
/**
|
||||
* Holds if a CSV source model contributed `source` with the given `kind`.
|
||||
*/
|
||||
API::Node getASourceNode(string kind) {
|
||||
exists(string package, string type, string path |
|
||||
sourceModel(package, type, path, kind) and
|
||||
result = getNodeFromPath(package, type, path)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a CSV sink model contributed `sink` with the given `kind`.
|
||||
*/
|
||||
API::Node getASinkNode(string kind) {
|
||||
exists(string package, string type, string path |
|
||||
sinkModel(package, type, path, kind) and
|
||||
result = getNodeFromPath(package, type, path)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a CSV summary contributed the step `pred -> succ` of the given `kind`.
|
||||
*/
|
||||
predicate summaryStep(API::Node pred, API::Node succ, string kind) {
|
||||
exists(API::InvokeNode base, AccessPath input, AccessPath output |
|
||||
resolvedSummaryBase(base, input, output, kind) and
|
||||
pred = getNodeFromInputOutputPath(base, input) and
|
||||
succ = getNodeFromInputOutputPath(base, output)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is seen as an instance of `(package,type)` due to a type definition
|
||||
* contributed by a CSV model.
|
||||
*/
|
||||
API::Node getATypeNode(string package, string type) {
|
||||
exists(string package2, string type2, AccessPath path |
|
||||
typeModel(package, type, package2, type2, path) and
|
||||
result = getNodeFromPath(package2, type2, path)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
consistencyIssue
|
||||
taintFlow
|
||||
| test.js:5:30:5:37 | source() | test.js:5:8:5:38 | testlib ... urce()) |
|
||||
| test.js:6:22:6:29 | source() | test.js:6:8:6:30 | preserv ... urce()) |
|
||||
| test.js:7:41:7:48 | source() | test.js:7:8:7:49 | require ... urce()) |
|
||||
| test.js:11:38:11:45 | source() | test.js:11:8:11:55 | testlib ... , 1, 1) |
|
||||
| test.js:13:44:13:51 | source() | test.js:13:8:13:55 | testlib ... e(), 1) |
|
||||
| test.js:17:47:17:54 | source() | test.js:17:8:17:61 | testlib ... , 1, 1) |
|
||||
| test.js:18:50:18:57 | source() | test.js:18:8:18:61 | testlib ... e(), 1) |
|
||||
| test.js:19:53:19:60 | source() | test.js:19:8:19:61 | testlib ... urce()) |
|
||||
| test.js:21:34:21:41 | source() | test.js:21:8:21:51 | testlib ... , 1, 1) |
|
||||
| test.js:22:37:22:44 | source() | test.js:22:8:22:51 | testlib ... , 1, 1) |
|
||||
| test.js:23:40:23:47 | source() | test.js:23:8:23:51 | testlib ... e(), 1) |
|
||||
| test.js:24:43:24:50 | source() | test.js:24:8:24:51 | testlib ... urce()) |
|
||||
| test.js:31:29:31:36 | source() | test.js:32:10:32:10 | y |
|
||||
| test.js:37:29:37:36 | source() | test.js:38:10:38:10 | y |
|
||||
| test.js:46:18:46:25 | source() | test.js:46:18:46:25 | source() |
|
||||
| test.js:47:22:47:29 | source() | test.js:47:22:47:29 | source() |
|
||||
| test.js:49:24:49:31 | source() | test.js:49:24:49:31 | source() |
|
||||
| test.js:53:27:53:34 | source() | test.js:53:27:53:34 | source() |
|
||||
| test.js:58:31:58:38 | source() | test.js:58:31:58:38 | source() |
|
||||
| test.js:62:34:62:41 | source() | test.js:62:34:62:41 | source() |
|
||||
| test.js:67:31:67:38 | source() | test.js:67:31:67:38 | source() |
|
||||
| test.js:68:34:68:41 | source() | test.js:68:34:68:41 | source() |
|
||||
| test.js:72:36:72:43 | source() | test.js:72:36:72:43 | source() |
|
||||
| test.js:73:39:73:46 | source() | test.js:73:39:73:46 | source() |
|
||||
| test.js:75:28:75:35 | source() | test.js:75:28:75:35 | source() |
|
||||
| test.js:76:31:76:38 | source() | test.js:76:31:76:38 | source() |
|
||||
| test.js:77:34:77:41 | source() | test.js:77:34:77:41 | source() |
|
||||
| test.js:81:28:81:35 | source() | test.js:81:28:81:35 | source() |
|
||||
isSink
|
||||
| test.js:46:18:46:25 | source() | test-sink |
|
||||
| test.js:47:22:47:29 | source() | test-sink |
|
||||
| test.js:49:24:49:31 | source() | test-sink |
|
||||
| test.js:53:27:53:34 | source() | test-sink |
|
||||
| test.js:55:38:55:38 | 4 | test-sink |
|
||||
| test.js:56:38:56:38 | 4 | test-sink |
|
||||
| test.js:57:38:57:38 | 4 | test-sink |
|
||||
| test.js:58:31:58:38 | source() | test-sink |
|
||||
| test.js:60:41:60:41 | 3 | test-sink |
|
||||
| test.js:61:41:61:41 | 3 | test-sink |
|
||||
| test.js:62:34:62:41 | source() | test-sink |
|
||||
| test.js:63:34:63:34 | 3 | test-sink |
|
||||
| test.js:65:38:65:38 | 3 | test-sink |
|
||||
| test.js:65:41:65:41 | 4 | test-sink |
|
||||
| test.js:66:38:66:38 | 3 | test-sink |
|
||||
| test.js:66:41:66:41 | 4 | test-sink |
|
||||
| test.js:67:31:67:38 | source() | test-sink |
|
||||
| test.js:67:41:67:41 | 4 | test-sink |
|
||||
| test.js:68:31:68:31 | 3 | test-sink |
|
||||
| test.js:68:34:68:41 | source() | test-sink |
|
||||
| test.js:70:43:70:43 | 3 | test-sink |
|
||||
| test.js:70:46:70:46 | 4 | test-sink |
|
||||
| test.js:71:43:71:43 | 3 | test-sink |
|
||||
| test.js:71:46:71:46 | 4 | test-sink |
|
||||
| test.js:72:36:72:43 | source() | test-sink |
|
||||
| test.js:72:46:72:46 | 4 | test-sink |
|
||||
| test.js:73:36:73:36 | 3 | test-sink |
|
||||
| test.js:73:39:73:46 | source() | test-sink |
|
||||
| test.js:75:28:75:35 | source() | test-sink |
|
||||
| test.js:75:38:75:38 | 2 | test-sink |
|
||||
| test.js:75:41:75:41 | 3 | test-sink |
|
||||
| test.js:76:28:76:28 | 1 | test-sink |
|
||||
| test.js:76:31:76:38 | source() | test-sink |
|
||||
| test.js:76:41:76:41 | 3 | test-sink |
|
||||
| test.js:77:28:77:28 | 1 | test-sink |
|
||||
| test.js:77:31:77:31 | 2 | test-sink |
|
||||
| test.js:77:34:77:41 | source() | test-sink |
|
||||
| test.js:78:28:78:28 | 1 | test-sink |
|
||||
| test.js:78:31:78:31 | 2 | test-sink |
|
||||
| test.js:78:34:78:34 | 3 | test-sink |
|
||||
| test.js:81:28:81:35 | source() | test-sink |
|
||||
| test.js:82:28:82:28 | 1 | test-sink |
|
||||
86
javascript/ql/test/library-tests/frameworks/data/test.js
Normal file
86
javascript/ql/test/library-tests/frameworks/data/test.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import * as testlib from 'testlib';
|
||||
import { preserveTaint } from 'testlib';
|
||||
|
||||
function testPreserveTaint() {
|
||||
sink(testlib.preserveTaint(source())); // NOT OK
|
||||
sink(preserveTaint(source())); // NOT OK
|
||||
sink(require('testlib').preserveTaint(source())); // NOT OK
|
||||
sink(require('testlib').preserveTaint('safe')); // OK
|
||||
sink(require('testlib').preserveTaint(1, source())); // OK
|
||||
|
||||
sink(testlib.preserveArgZeroAndTwo(source(), 1, 1, 1)); // NOT OK
|
||||
sink(testlib.preserveArgZeroAndTwo(1, source(), 1, 1)); // OK
|
||||
sink(testlib.preserveArgZeroAndTwo(1, 1, source(), 1)); // NOT OK
|
||||
sink(testlib.preserveArgZeroAndTwo(1, 1, 1, source())); // OK
|
||||
|
||||
sink(testlib.preserveAllButFirstArgument(source(), 1, 1, 1)); // OK
|
||||
sink(testlib.preserveAllButFirstArgument(1, source(), 1, 1)); // NOT OK
|
||||
sink(testlib.preserveAllButFirstArgument(1, 1, source(), 1)); // NOT OK
|
||||
sink(testlib.preserveAllButFirstArgument(1, 1, 1, source())); // NOT OK
|
||||
|
||||
sink(testlib.preserveAllIfCall(source(), 1, 1, 1)); // NOT OK
|
||||
sink(testlib.preserveAllIfCall(1, source(), 1, 1)); // NOT OK
|
||||
sink(testlib.preserveAllIfCall(1, 1, source(), 1)); // NOT OK
|
||||
sink(testlib.preserveAllIfCall(1, 1, 1, source())); // NOT OK
|
||||
|
||||
sink(new testlib.preserveAllIfCall(source(), 1, 1, 1)); // OK
|
||||
sink(new testlib.preserveAllIfCall(1, source(), 1, 1)); // OK
|
||||
sink(new testlib.preserveAllIfCall(1, 1, source(), 1)); // OK
|
||||
sink(new testlib.preserveAllIfCall(1, 1, 1, source())); // OK
|
||||
|
||||
testlib.taintIntoCallback(source(), y => {
|
||||
sink(y); // NOT OK
|
||||
});
|
||||
testlib.taintIntoCallback('safe', y => {
|
||||
sink(y); // OK
|
||||
});
|
||||
testlib.taintIntoCallback(source(), undefined, y => {
|
||||
sink(y); // NOT OK
|
||||
});
|
||||
testlib.taintIntoCallback(source(), undefined, undefined, y => {
|
||||
sink(y); // OK - only callback 1-2 receive taint
|
||||
});
|
||||
}
|
||||
|
||||
function testSinks() {
|
||||
testlib.mySink(source()); // NOT OK
|
||||
new testlib.mySink(source()); // NOT OK
|
||||
|
||||
testlib.mySinkIfCall(source()); // NOT OK
|
||||
new testlib.mySinkIfCall(source()); // OK
|
||||
|
||||
testlib.mySinkIfNew(source()); // OK
|
||||
new testlib.mySinkIfNew(source()); // NOT OK
|
||||
|
||||
testlib.mySinkLast(source(), 2, 3, 4); // OK
|
||||
testlib.mySinkLast(1, source(), 3, 4); // OK
|
||||
testlib.mySinkLast(1, 2, source(), 4); // OK
|
||||
testlib.mySinkLast(1, 2, 3, source()); // NOT OK
|
||||
|
||||
testlib.mySinkSecondLast(source(), 2, 3, 4); // OK
|
||||
testlib.mySinkSecondLast(1, source(), 3, 4); // OK
|
||||
testlib.mySinkSecondLast(1, 2, source(), 4); // NOT OK
|
||||
testlib.mySinkSecondLast(1, 2, 3, source()); // OK
|
||||
|
||||
testlib.mySinkTwoLast(source(), 2, 3, 4); // OK
|
||||
testlib.mySinkTwoLast(1, source(), 3, 4); // OK
|
||||
testlib.mySinkTwoLast(1, 2, source(), 4); // NOT OK
|
||||
testlib.mySinkTwoLast(1, 2, 3, source()); // NOT OK
|
||||
|
||||
testlib.mySinkTwoLastRange(source(), 2, 3, 4); // OK
|
||||
testlib.mySinkTwoLastRange(1, source(), 3, 4); // OK
|
||||
testlib.mySinkTwoLastRange(1, 2, source(), 4); // NOT OK
|
||||
testlib.mySinkTwoLastRange(1, 2, 3, source()); // NOT OK
|
||||
|
||||
testlib.mySinkExceptLast(source(), 2, 3, 4); // NOT OK
|
||||
testlib.mySinkExceptLast(1, source(), 3, 4); // NOT OK
|
||||
testlib.mySinkExceptLast(1, 2, source(), 4); // NOT OK
|
||||
testlib.mySinkExceptLast(1, 2, 3, source()); // OK
|
||||
|
||||
testlib.mySinkIfArityTwo(source()); // OK
|
||||
testlib.mySinkIfArityTwo(source(), 2); // NOT OK
|
||||
testlib.mySinkIfArityTwo(1, source()); // OK
|
||||
testlib.mySinkIfArityTwo(source(), 2, 3); // OK
|
||||
testlib.mySinkIfArityTwo(1, source(), 3); // OK
|
||||
testlib.mySinkIfArityTwo(1, 2, source()); // OK
|
||||
}
|
||||
56
javascript/ql/test/library-tests/frameworks/data/test.ql
Normal file
56
javascript/ql/test/library-tests/frameworks/data/test.ql
Normal file
@@ -0,0 +1,56 @@
|
||||
import javascript
|
||||
import testUtilities.ConsistencyChecking
|
||||
|
||||
class Steps extends ModelInput::SummaryModelCsv {
|
||||
override predicate row(string row) {
|
||||
// package;type;path;input;output;kind
|
||||
row =
|
||||
[
|
||||
"testlib;;Member[preserveTaint];Argument[0];ReturnValue;taint",
|
||||
"testlib;;Member[taintIntoCallback];Argument[0];Argument[1..2].Parameter[0];taint",
|
||||
"testlib;;Member[preserveArgZeroAndTwo];Argument[0,2];ReturnValue;taint",
|
||||
"testlib;;Member[preserveAllButFirstArgument];Argument[1..];ReturnValue;taint",
|
||||
"testlib;;Member[preserveAllIfCall].Call;Argument[0..];ReturnValue;taint"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
class Sinks extends ModelInput::SinkModelCsv {
|
||||
override predicate row(string row) {
|
||||
// package;type;path;kind
|
||||
row =
|
||||
[
|
||||
"testlib;;Member[mySink].Argument[0];test-sink",
|
||||
"testlib;;Member[mySinkIfCall].Call.Argument[0];test-sink",
|
||||
"testlib;;Member[mySinkIfNew].NewCall.Argument[0];test-sink",
|
||||
"testlib;;Member[mySinkLast].Argument[N-1];test-sink",
|
||||
"testlib;;Member[mySinkSecondLast].Argument[N-2];test-sink",
|
||||
"testlib;;Member[mySinkTwoLast].Argument[N-1,N-2];test-sink",
|
||||
"testlib;;Member[mySinkTwoLastRange].Argument[N-2..N-1];test-sink",
|
||||
"testlib;;Member[mySinkExceptLast].Argument[0..N-2];test-sink",
|
||||
"testlib;;Member[mySinkIfArityTwo].WithArity[2].Argument[0];test-sink",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
class BasicTaintTracking extends TaintTracking::Configuration {
|
||||
BasicTaintTracking() { this = "BasicTaintTracking" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source.(DataFlow::CallNode).getCalleeName() = "source"
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink = any(DataFlow::CallNode call | call.getCalleeName() = "sink").getAnArgument()
|
||||
or
|
||||
sink = ModelOutput::getASinkNode("test-sink").getARhs()
|
||||
}
|
||||
}
|
||||
|
||||
query predicate taintFlow(DataFlow::Node source, DataFlow::Node sink) {
|
||||
any(BasicTaintTracking tr).hasFlow(source, sink)
|
||||
}
|
||||
|
||||
query predicate isSink(DataFlow::Node node, string kind) {
|
||||
node = ModelOutput::getASinkNode(kind).getARhs()
|
||||
}
|
||||
Reference in New Issue
Block a user