Merge branch 'main' into topPack

This commit is contained in:
Erik Krogh Kristensen
2021-03-25 09:58:15 +01:00
462 changed files with 8954 additions and 1322 deletions

View File

@@ -0,0 +1,5 @@
lgtm,codescanning
* Route handlers registered using koa routing libraries are recognized as a source of remote user input.
Affected packages are
[koa-route](https://www.npmjs.com/package/koa-route), and
[koa-router](https://www.npmjs.com/package/koa-router)

View File

@@ -0,0 +1,3 @@
lgtm,codescanning
* The analysis of regular expression-based sanitization patterns has improved,
leading to more true-positive results, in particular for the XSS queries.

View File

@@ -0,0 +1,4 @@
lgtm,codescanning
* URIs used in the puppeteer library are now recognized as sinks for `js/request-forgery`.
Affected packages are
[puppeteer](https://www.npmjs.com/package/puppeteer)

View File

@@ -0,0 +1,4 @@
lgtm,codescanning
* The command injection security queries now recognize additional sinks.
Affected packages are
[async-execute](https://npmjs.com/package/async-execute)

View File

@@ -0,0 +1,3 @@
lgtm,codescanning
* Calls to property accessors are now analyzed on par with regular function calls,
leading to more results from queries that rely on data flow.

View File

@@ -407,7 +407,7 @@ public class AutoBuild {
// codeql-javascript-*.json files
patterns.add("**/.eslintrc*");
patterns.add("**/package.json");
patterns.add("**/tsconfig.json");
patterns.add("**/tsconfig*.json");
patterns.add("**/codeql-javascript-*.json");
// include any explicitly specified extensions

View File

@@ -43,7 +43,7 @@ public class Main {
* A version identifier that should be updated every time the extractor changes in such a way that
* it may produce different tuples for the same file under the same {@link ExtractorConfig}.
*/
public static final String EXTRACTOR_VERSION = "2021-03-08";
public static final String EXTRACTOR_VERSION = "2021-03-19";
public static final Pattern NEWLINE = Pattern.compile("\n");

View File

@@ -87,11 +87,6 @@ private DataFlow::Node goodRandom(DataFlow::TypeTracker t, DataFlow::SourceNode
or
exists(DataFlow::TypeTracker t2 | t = t2.smallstep(goodRandom(t2, source), result))
or
// re-using the collection steps for `Set`.
exists(DataFlow::TypeTracker t2 |
result = CollectionsTypeTracking::collectionStep(goodRandom(t2, source), t, t2)
)
or
InsecureRandomness::isAdditionalTaintStep(goodRandom(t.continue(), source), result) and
// bit shifts and multiplication by powers of two are generally used for constructing larger numbers from smaller numbers.
not exists(BinaryExpr binop | binop = result.asExpr() |

View File

@@ -0,0 +1,54 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Using user-controlled input in GitHub Actions may lead to
code injection in contexts like <i>run:</i> or <i>script:</i>.
</p>
</overview>
<recommendation>
<p>
The best practice to avoid code injection vulnerabilities
in GitHub workflows is to set the untrusted input value of the expression
to an intermediate environment variable.
</p>
</recommendation>
<example>
<p>
The following example lets a user inject an arbitrary shell command:
</p>
<sample src="examples/comment_issue_bad.yml" />
<p>
The following example uses shell syntax to read
the environment variable and will prevent the attack:
</p>
<sample src="examples/comment_issue_good.yml" />
</example>
<references>
<li>GitHub Security Lab Research: <a href="https://securitylab.github.com/research/github-actions-untrusted-input">Keeping your GitHub Actions and workflows secure: Untrusted input</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,93 @@
/**
* @name Expression injection in Actions
* @description Using user-controlled GitHub Actions contexts like `run:` or `script:` may allow a malicious
* user to inject code into the GitHub action.
* @kind problem
* @problem.severity error
* @precision high
* @id js/actions/injection
* @tags actions
* security
* external/cwe/cwe-094
*/
import javascript
import experimental.semmle.javascript.Actions
bindingset[context]
private predicate isExternalUserControlledIssue(string context) {
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*issue\\s*\\.\\s*title\\b") or
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*issue\\s*\\.\\s*body\\b")
}
bindingset[context]
private predicate isExternalUserControlledPullRequest(string context) {
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*title\\b") or
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*body\\b") or
context
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*label\\b") or
context
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*repo\\s*\\.\\s*default_branch\\b") or
context
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*ref\\b")
}
bindingset[context]
private predicate isExternalUserControlledReview(string context) {
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*review\\s*\\.\\s*body\\b") or
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*review_comment\\s*\\.\\s*body\\b")
}
bindingset[context]
private predicate isExternalUserControlledComment(string context) {
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*comment\\s*\\.\\s*body\\b")
}
bindingset[context]
private predicate isExternalUserControlledGollum(string context) {
context
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pages(?:\\[[0-9]\\]|\\s*\\.\\s*\\*)+\\s*\\.\\s*page_name\\b")
}
bindingset[context]
private predicate isExternalUserControlledCommit(string context) {
context
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits(?:\\[[0-9]\\]|\\s*\\.\\s*\\*)+\\s*\\.\\s*message\\b") or
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*message\\b") or
context
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*author\\s*\\.\\s*email\\b") or
context
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*author\\s*\\.\\s*name\\b") or
context
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits(?:\\[[0-9]\\]|\\s*\\.\\s*\\*)+\\s*\\.\\s*author\\s*\\.\\s*email\\b") or
context
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits(?:\\[[0-9]\\]|\\s*\\.\\s*\\*)+\\s*\\.\\s*author\\s*\\.\\s*name\\b") or
context.regexpMatch("\\bgithub\\s*\\.\\s*head_ref\\b")
}
from Actions::Run run, string context, Actions::On on
where
run.getAReferencedExpression() = context and
run.getStep().getJob().getWorkflow().getOn() = on and
(
exists(on.getNode("issues")) and
isExternalUserControlledIssue(context)
or
exists(on.getNode("pull_request_target")) and
isExternalUserControlledPullRequest(context)
or
(exists(on.getNode("pull_request_review_comment")) or exists(on.getNode("pull_request_review"))) and
isExternalUserControlledReview(context)
or
(exists(on.getNode("issue_comment")) or exists(on.getNode("pull_request_target"))) and
isExternalUserControlledComment(context)
or
exists(on.getNode("gollum")) and
isExternalUserControlledGollum(context)
or
exists(on.getNode("pull_request_target")) and
isExternalUserControlledCommit(context)
)
select run,
"Potential injection from the " + context +
" context, which may be controlled by an external user."

View File

@@ -0,0 +1,64 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Combining <i>pull_request_target</i> workflow trigger with an explicit checkout
of an untrusted pull request is a dangerous practice
that may lead to repository compromise.
</p>
</overview>
<recommendation>
<p>
The best practice is to handle the potentially untrusted pull request
via the <i>pull_request</i> trigger so that it is isolated in
an unprivileged environment. The workflow processing the pull request
should then store any results like code coverage or failed/passed tests
in artifacts and exit. The following workflow then starts on <i>workflow_run</i>
where it is granted write permission to the target repository and access to
repository secrets, so that it can download the artifacts and make
any necessary modifications to the repository or interact with third party services
that require repository secrets (e.g. API tokens).
</p>
</recommendation>
<example>
<p>
The following example allows unauthorized repository modification
and secrets exfiltration:
</p>
<sample src="examples/pull_request_target_bad.yml" />
<p>
The following example uses two workflows to handle potentially untrusted
pull request in a secure manner. The receive_pr.yml is triggered first:
</p>
<sample src="examples/receive_pr.yml" />
<p>The comment_pr.yml is triggered after receive_pr.yml completes:</p>
<sample src="examples/comment_pr.yml" />
</example>
<references>
<li>GitHub Security Lab Research: <a href="https://securitylab.github.com/research/github-actions-preventing-pwn-requests">Keeping your GitHub Actions and workflows secure: Preventing pwn requests</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,122 @@
/**
* @name Checkout of untrusted code in trusted context
* @description Workflows triggered on `pull_request_target` have read/write access to the base repository and access to secrets.
* By explicitly checking out and running the build script from a fork the untrusted code is running in an environment
* that is able to push to the base repository and to access secrets.
* @kind problem
* @problem.severity warning
* @precision low
* @id js/actions/pull-request-target
* @tags actions
* security
* external/cwe/cwe-094
*/
import javascript
import experimental.semmle.javascript.Actions
/**
* Action step that doesn't contain `actor` or `label` check in `if:` or
* the check requires manual analysis.
*/
class ProbableStep extends Actions::Step {
// some simplistic checks to eleminate likely false positives:
ProbableStep() {
// no if at all
not exists(this.getIf().getValue())
or
// needs manual analysis if there is OR
this.getIf().getValue().matches("%||%")
or
// labels can be assigned by owners only
not exists(
this.getIf()
.getValue()
.regexpFind("\\bcontains\\s*\\(\\s*github\\s*\\.\\s*event\\s*\\.\\s*(?:issue|pull_request)\\s*\\.\\s*labels\\b",
_, _)
) and
not exists(
this.getIf()
.getValue()
.regexpFind("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*label\\s*\\.\\s*name\\s*==", _, _)
) and
// actor check means only the user is able to run it
not exists(this.getIf().getValue().regexpFind("\\bgithub\\s*\\.\\s*actor\\s*==", _, _))
}
}
/**
* Action job that doesn't contain `actor` or `label` check in `if:` or
* the check requires manual analysis.
*/
class ProbableJob extends Actions::Job {
// some simplistic checks to eleminate likely false positives:
ProbableJob() {
// no if at all
not exists(this.getIf().getValue())
or
// needs manual analysis if there is OR
this.getIf().getValue().matches("%||%")
or
// labels can be assigned by owners only
not exists(
this.getIf()
.getValue()
.regexpFind("\\bcontains\\s*\\(\\s*github\\s*\\.\\s*event\\s*\\.\\s*(?:issue|pull_request)\\s*\\.\\s*labels\\b",
_, _)
) and
not exists(
this.getIf()
.getValue()
.regexpFind("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*label\\s*\\.\\s*name\\s*==", _, _)
) and
// actor check means only the user is able to run it
not exists(this.getIf().getValue().regexpFind("\\bgithub\\s*\\.\\s*actor\\s*==", _, _))
}
}
/**
* Action step that doesn't contain `actor` or `label` check in `if:` or
*/
class ProbablePullRequestTarget extends Actions::On, Actions::MappingOrSequenceOrScalar {
ProbablePullRequestTarget() {
exists(YAMLNode prtNode |
// The `on:` is triggered on `pull_request_target`
this.getNode("pull_request_target") = prtNode and
(
// and either doesn't contain `types` filter
not exists(prtNode.getAChild())
or
// or has the filter, that is something else than just [labeled]
exists(Actions::MappingOrSequenceOrScalar prt, Actions::MappingOrSequenceOrScalar types |
types = prt.getNode("types") and
prtNode = prt and
(
not types.getElementCount() = 1 or
not exists(types.getNode("labeled"))
)
)
)
)
}
}
from
Actions::Ref ref, Actions::Uses uses, Actions::Step step, Actions::Job job,
ProbablePullRequestTarget pullRequestTarget
where
pullRequestTarget.getWorkflow() = job.getWorkflow() and
uses.getStep() = step and
ref.getWith().getStep() = step and
step.getJob() = job and
uses.getGitHubRepository() = "actions/checkout" and
(
ref.getValue().matches("%github.event.pull_request.head.ref%") or
ref.getValue().matches("%github.event.pull_request.head.sha%") or
ref.getValue().matches("%github.event.pull_request.number%") or
ref.getValue().matches("%github.event.number%") or
ref.getValue().matches("%github.head_ref%")
) and
step instanceof ProbableStep and
job instanceof ProbableJob
select step, "Potential unsafe checkout of untrusted pull request on `pull_request_target`"

View File

@@ -0,0 +1,8 @@
on: issue_comment
jobs:
echo-body:
runs-on: ubuntu-latest
steps:
- run: |
echo '${{ github.event.comment.body }}'

View File

@@ -0,0 +1,10 @@
on: issue_comment
jobs:
echo-body:
runs-on: ubuntu-latest
steps:
- env:
BODY: ${{ github.event.issue.body }}
run: |
echo '$BODY'

View File

@@ -0,0 +1,52 @@
name: Comment on the pull request
# read-write repo token
# access to secrets
on:
workflow_run:
workflows: ["Receive PR"]
types:
- completed
jobs:
upload:
runs-on: ubuntu-latest
if: >
${{ github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success' }}
steps:
- name: 'Download artifact'
uses: actions/github-script@v3.1.0
with:
script: |
var artifacts = await github.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{github.event.workflow_run.id }},
});
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "pr"
})[0];
var download = await github.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
var fs = require('fs');
fs.writeFileSync('${{github.workspace}}/pr.zip', Buffer.from(download.data));
- run: unzip pr.zip
- name: 'Comment on PR'
uses: actions/github-script@v3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
var fs = require('fs');
var issue_number = Number(fs.readFileSync('./NR'));
await github.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue_number,
body: 'Everything is OK. Thank you for the PR!'
});

View File

@@ -0,0 +1,25 @@
on:
pull_request_target
jobs:
build:
name: Build and test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-node@v1
- run: |
npm install
npm build
- uses: completely/fakeaction@v2
with:
arg1: ${{ secrets.supersecret }}
- uses: fakerepo/comment-on-pr@v1
with:
message: |
Thank you!

View File

@@ -0,0 +1,26 @@
name: Receive PR
# read-only repo token
# no access to secrets
on:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# imitation of a build process
- name: Build
run: /bin/bash ./build.sh
- name: Save PR number
run: |
mkdir -p ./pr
echo ${{ github.event.number }} > ./pr/NR
- uses: actions/upload-artifact@v2
with:
name: pr
path: pr/

View File

@@ -0,0 +1,316 @@
/**
* Libraries for modelling GitHub Actions workflow files written in YAML.
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions.
*/
import javascript
/**
* Libraries for modelling GitHub Actions workflow files written in YAML.
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions.
*/
module Actions {
/** A YAML node in a GitHub Actions workflow file. */
private class Node extends YAMLNode {
Node() {
this.getLocation()
.getFile()
.getRelativePath()
.matches(["experimental/Security/CWE-094/.github/workflows/%", ".github/workflows/%"])
}
}
/**
* Actions are quite flexible in parsing YAML.
*
* For example:
* ```
* on: pull_request
* ```
* and
* ```
* on: [pull_request]
* ```
* and
* ```
* on:
* pull_request:
* ```
*
* are equivalent.
*/
class MappingOrSequenceOrScalar extends YAMLNode {
MappingOrSequenceOrScalar() {
this instanceof YAMLMapping
or
this instanceof YAMLSequence
or
this instanceof YAMLScalar
}
YAMLNode getNode(string name) {
exists(YAMLMapping mapping |
mapping = this and
result = mapping.lookup(name)
)
or
exists(YAMLSequence sequence, YAMLNode node |
sequence = this and
sequence.getAChildNode() = node and
node.eval().toString() = name and
result = node
)
or
exists(YAMLScalar scalar |
scalar = this and
scalar.getValue() = name and
result = scalar
)
}
int getElementCount() {
exists(YAMLMapping mapping |
mapping = this and
result = mapping.getNumChild() / 2
)
or
exists(YAMLSequence sequence |
sequence = this and
result = sequence.getNumChild()
)
or
exists(YAMLScalar scalar |
scalar = this and
result = 1
)
}
}
/**
* An Actions workflow. This is a mapping at the top level of an Actions YAML workflow file.
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions.
*/
class Workflow extends Node, YAMLDocument, YAMLMapping {
/** Gets the `jobs` mapping from job IDs to job definitions in this workflow. */
YAMLMapping getJobs() { result = this.lookup("jobs") }
/** Gets the name of the workflow file. */
string getFileName() { result = this.getFile().getBaseName() }
/** Gets the `on:` in this workflow. */
On getOn() { result = this.lookup("on") }
/** Gets the job within this workflow with the given job ID. */
Job getJob(string jobId) { result.getWorkflow() = this and result.getId() = jobId }
}
/**
* An Actions On trigger within a workflow.
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#on.
*/
class On extends YAMLNode, MappingOrSequenceOrScalar {
Workflow workflow;
On() { workflow.lookup("on") = this }
Workflow getWorkflow() { result = workflow }
}
/**
* An Actions job within a workflow.
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobs.
*/
class Job extends YAMLNode, YAMLMapping {
string jobId;
Workflow workflow;
Job() { this = workflow.getJobs().lookup(jobId) }
/**
* Gets the ID of this job, as a string.
* This is the job's key within the `jobs` mapping.
*/
string getId() { result = jobId }
/**
* Gets the ID of this job, as a YAML scalar node.
* This is the job's key within the `jobs` mapping.
*/
YAMLString getIdNode() { workflow.getJobs().maps(result, this) }
/** Gets the human-readable name of this job, if any, as a string. */
string getName() { result = this.getNameNode().getValue() }
/** Gets the human-readable name of this job, if any, as a YAML scalar node. */
YAMLString getNameNode() { result = this.lookup("name") }
/** Gets the step at the given index within this job. */
Step getStep(int index) { result.getJob() = this and result.getIndex() = index }
/** Gets the sequence of `steps` within this job. */
YAMLSequence getSteps() { result = this.lookup("steps") }
/** Gets the workflow this job belongs to. */
Workflow getWorkflow() { result = workflow }
/** Gets the value of the `if` field in this job, if any. */
JobIf getIf() { result.getJob() = this }
}
/**
* An `if` within a job.
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idif.
*/
class JobIf extends YAMLNode, YAMLScalar {
Job job;
JobIf() { job.lookup("if") = this }
/** Gets the step this field belongs to. */
Job getJob() { result = job }
}
/**
* A step within an Actions job.
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idsteps.
*/
class Step extends YAMLNode, YAMLMapping {
int index;
Job job;
Step() { this = job.getSteps().getElement(index) }
/** Gets the 0-based position of this step within the sequence of `steps`. */
int getIndex() { result = index }
/** Gets the job this step belongs to. */
Job getJob() { result = job }
/** Gets the value of the `uses` field in this step, if any. */
Uses getUses() { result.getStep() = this }
/** Gets the value of the `run` field in this step, if any. */
Run getRun() { result.getStep() = this }
/** Gets the value of the `if` field in this step, if any. */
StepIf getIf() { result.getStep() = this }
}
/**
* An `if` within a step.
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsif.
*/
class StepIf extends YAMLNode, YAMLScalar {
Step step;
StepIf() { step.lookup("if") = this }
/** Gets the step this field belongs to. */
Step getStep() { result = step }
}
/**
* A `uses` field within an Actions job step, which references an action as a reusable unit of code.
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsuses.
*
* For example:
* ```
* uses: actions/checkout@v2
* ```
* TODO: Does not currently handle local repository references, e.g. `.github/actions/action-name`.
*/
class Uses extends YAMLNode, YAMLScalar {
Step step;
/** The owner of the repository where the Action comes from, e.g. `actions` in `actions/checkout@v2`. */
string repositoryOwner;
/** The name of the repository where the Action comes from, e.g. `checkout` in `actions/checkout@v2`. */
string repositoryName;
/** The version reference used when checking out the Action, e.g. `v2` in `actions/checkout@v2`. */
string version;
Uses() {
step.lookup("uses") = this and
// Simple regular expression to split up an Action reference `owner/repo@version` into its components.
exists(string regexp | regexp = "([^/]+)/([^/@]+)@(.+)" |
repositoryOwner = this.getValue().regexpCapture(regexp, 1) and
repositoryName = this.getValue().regexpCapture(regexp, 2) and
version = this.getValue().regexpCapture(regexp, 3)
)
}
/** Gets the step this field belongs to. */
Step getStep() { result = step }
/** Gets the owner and name of the repository where the Action comes from, e.g. `actions/checkout` in `actions/checkout@v2`. */
string getGitHubRepository() { result = repositoryOwner + "/" + repositoryName }
/** Gets the version reference used when checking out the Action, e.g. `v2` in `actions/checkout@v2`. */
string getVersion() { result = version }
}
/**
* A `with` field within an Actions job step, which references an action as a reusable unit of code.
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepswith.
*
* For example:
* ```
* with:
* arg1: 1
* arg2: abc
* ```
*/
class With extends YAMLNode, YAMLMapping {
Step step;
With() { step.lookup("with") = this }
/** Gets the step this field belongs to. */
Step getStep() { result = step }
}
/**
* A `ref:` field within an Actions `with:` specific to `actions/checkout` action.
*
* For example:
* ```
* uses: actions/checkout@v2
* with:
* ref: ${{ github.event.pull_request.head.sha }}
* ```
*/
class Ref extends YAMLNode, YAMLString {
With with;
Ref() { with.lookup("ref") = this }
With getWith() { result = with }
}
/**
* A `run` field within an Actions job step, which runs command-line programs using an operating system shell.
* See https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsrun.
*/
class Run extends YAMLNode, YAMLString {
Step step;
Run() { step.lookup("run") = this }
/** Gets the step that executes this `run` command. */
Step getStep() { result = step }
/**
* Holds if `${{ e }}` is a GitHub Actions expression evaluated within this `run` command.
* See https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions.
*/
string getAReferencedExpression() {
// We use `regexpFind` to obtain *all* matches of `${{...}}`,
// not just the last (greedy match) or first (reluctant match).
// TODO: This only handles expression strings that refer to contexts.
// It does not handle operators within the expression.
result =
this.getValue()
.regexpFind("\\$\\{\\{\\s*[A-Za-z0-9_\\.\\-]+\\s*\\}\\}", _, _)
.regexpCapture("\\$\\{\\{\\s*([A-Za-z0-9_\\.\\-]+)\\s*\\}\\}", 1)
}
}
}

View File

@@ -102,6 +102,7 @@ import semmle.javascript.frameworks.Next
import semmle.javascript.frameworks.NoSQL
import semmle.javascript.frameworks.PkgCloud
import semmle.javascript.frameworks.PropertyProjection
import semmle.javascript.frameworks.Puppeteer
import semmle.javascript.frameworks.React
import semmle.javascript.frameworks.ReactNative
import semmle.javascript.frameworks.Request

View File

@@ -4,7 +4,6 @@
* in the documentation, queries that rely on the contract may yield unexpected
* results.
* @kind table
* @problem.severity error
* @id js/consistency/api-contracts
* @tags consistency
*/

View File

@@ -10,6 +10,9 @@
import javascript
from DataFlow::InvokeNode invoke, Function f
where invoke.getACallee() = f
select invoke, "Call to $@", f, f.describe()
from DataFlow::Node invoke, Function f, string kind
where
invoke.(DataFlow::InvokeNode).getACallee() = f and kind = "Call"
or
invoke.(DataFlow::PropRef).getAnAccessorCallee().getFunction() = f and kind = "Accessor call"
select invoke, kind + " to $@", f, f.describe()

View File

@@ -15,17 +15,17 @@ predicate relevantStep(DataFlow::Node pred, DataFlow::Node succ) {
(
TaintTracking::sharedTaintStep(pred, succ)
or
any(DataFlow::AdditionalFlowStep cfg).step(pred, succ)
DataFlow::SharedFlowStep::step(pred, succ)
or
any(DataFlow::AdditionalFlowStep cfg).step(pred, succ, _, _)
DataFlow::SharedFlowStep::step(pred, succ, _, _)
or
any(DataFlow::AdditionalFlowStep cfg).loadStep(pred, succ, _)
DataFlow::SharedFlowStep::loadStep(pred, succ, _)
or
any(DataFlow::AdditionalFlowStep cfg).storeStep(pred, succ, _)
DataFlow::SharedFlowStep::storeStep(pred, succ, _)
or
any(DataFlow::AdditionalFlowStep cfg).loadStoreStep(pred, succ, _, _)
DataFlow::SharedFlowStep::loadStoreStep(pred, succ, _, _)
or
any(DataFlow::AdditionalFlowStep cfg).loadStoreStep(pred, succ, _)
DataFlow::SharedFlowStep::loadStoreStep(pred, succ, _)
) and
not pred.getFile() instanceof IgnoredFile and
not succ.getFile() instanceof IgnoredFile

View File

@@ -313,10 +313,7 @@ module API {
module Node {
/** Gets a node whose type has the given qualified name. */
Node ofType(string moduleName, string exportedName) {
exists(TypeName tn |
tn.hasQualifiedName(moduleName, exportedName) and
result = Impl::MkCanonicalNameUse(tn).(Node).getInstance()
)
result = Impl::MkHasUnderlyingType(moduleName, exportedName).(Node).getInstance()
}
}
@@ -384,6 +381,8 @@ module API {
imports(_, m)
or
m = any(CanonicalName n | isUsed(n)).getExternalModuleName()
or
any(TypeAnnotation n).hasQualifiedName(m, _)
} or
MkClassInstance(DataFlow::ClassNode cls) { cls = trackDefNode(_) and hasSemantics(cls) } or
MkAsyncFuncResult(DataFlow::FunctionNode f) {
@@ -392,26 +391,13 @@ module API {
MkDef(DataFlow::Node nd) { rhs(_, _, nd) } or
MkUse(DataFlow::Node nd) { use(_, _, nd) } or
/**
* A TypeScript canonical name that is defined somewhere, and that isn't a module root.
* (Module roots are represented by `MkModuleExport` nodes instead.)
*
* For most purposes, you probably want to use the `mkCanonicalNameDef` predicate instead of
* this constructor.
* A TypeScript type, identified by name of the type-annotation.
* This API node is exclusively used by `API::Node::ofType`.
*/
MkCanonicalNameDef(CanonicalName n) {
not n.isRoot() and
isDefined(n)
} or
/**
* A TypeScript canonical name that is used somewhere, and that isn't a module root.
* (Module roots are represented by `MkModuleImport` nodes instead.)
*
* For most purposes, you probably want to use the `mkCanonicalNameUse` predicate instead of
* this constructor.
*/
MkCanonicalNameUse(CanonicalName n) {
not n.isRoot() and
isUsed(n)
MkHasUnderlyingType(string moduleName, string exportName) {
any(TypeAnnotation n).hasQualifiedName(moduleName, exportName)
or
any(Type t).hasUnderlyingType(moduleName, exportName)
} or
MkSyntheticCallbackArg(DataFlow::Node src, int bound, DataFlow::InvokeNode nd) {
trackUseNode(src, true, bound).flowsTo(nd.getCalleeNode())
@@ -420,10 +406,9 @@ module API {
class TDef = MkModuleDef or TNonModuleDef;
class TNonModuleDef =
MkModuleExport or MkClassInstance or MkAsyncFuncResult or MkDef or MkCanonicalNameDef or
MkSyntheticCallbackArg;
MkModuleExport or MkClassInstance or MkAsyncFuncResult or MkDef or MkSyntheticCallbackArg;
class TUse = MkModuleUse or MkModuleImport or MkUse or MkCanonicalNameUse;
class TUse = MkModuleUse or MkModuleImport or MkUse or MkHasUnderlyingType;
private predicate hasSemantics(DataFlow::Node nd) { not nd.getTopLevel().isExterns() }
@@ -460,20 +445,6 @@ module API {
)
}
/** An API-graph node representing definitions of the canonical name `cn`. */
private TApiNode mkCanonicalNameDef(CanonicalName cn) {
if cn.isModuleRoot()
then result = MkModuleExport(cn.getExternalModuleName())
else result = MkCanonicalNameDef(cn)
}
/** An API-graph node representing uses of the canonical name `cn`. */
private TApiNode mkCanonicalNameUse(CanonicalName cn) {
if cn.isModuleRoot()
then result = MkModuleImport(cn.getExternalModuleName())
else result = MkCanonicalNameUse(cn)
}
/**
* Holds if `rhs` is the right-hand side of a definition of a node that should have an
* incoming edge from `base` labeled `lbl` in the API graph.
@@ -577,11 +548,6 @@ module API {
exists(string m | nd = MkModuleExport(m) | exports(m, rhs))
or
nd = MkDef(rhs)
or
exists(CanonicalName n | nd = MkCanonicalNameDef(n) |
rhs = n.(Namespace).getADefinition().flow() or
rhs = n.(CanonicalFunctionName).getADefinition().flow()
)
}
/**
@@ -633,10 +599,10 @@ module API {
ref = cls.getConstructor().getParameter(i)
)
or
exists(TypeName tn |
base = MkCanonicalNameUse(tn) and
exists(string moduleName, string exportName |
base = MkHasUnderlyingType(moduleName, exportName) and
lbl = Label::instance() and
ref = getANodeWithType(tn)
ref.(DataFlow::SourceNode).hasUnderlyingType(moduleName, exportName)
)
or
exists(DataFlow::InvokeNode call |
@@ -653,11 +619,11 @@ module API {
cached
predicate use(TApiNode nd, DataFlow::Node ref) {
exists(string m, Module mod | nd = MkModuleDef(m) and mod = importableModule(m) |
ref.(ModuleAsSourceNode).getModule() = mod
ref.(ModuleVarNode).getModule() = mod
)
or
exists(string m, Module mod | nd = MkModuleExport(m) and mod = importableModule(m) |
ref.(ExportsAsSourceNode).getModule() = mod
ref.(ExportsVarNode).getModule() = mod
or
exists(DataFlow::Node base | use(MkModuleDef(m), base) |
ref = trackUseNode(base).getAPropertyRead("exports")
@@ -676,8 +642,6 @@ module API {
)
or
nd = MkUse(ref)
or
exists(CanonicalName n | nd = MkCanonicalNameUse(n) | ref.asExpr() = n.getAnAccess())
}
/** Holds if module `m` exports `rhs`. */
@@ -782,9 +746,9 @@ module API {
or
// additional backwards step from `require('m')` to `exports` or `module.exports` in m
exists(Import imp | imp.getImportedModuleNode() = trackDefNode(nd, t.continue()) |
result.(ExportsAsSourceNode).getModule() = imp.getImportedModule()
result.(ExportsVarNode).getModule() = imp.getImportedModule()
or
exists(ModuleAsSourceNode mod |
exists(ModuleVarNode mod |
mod.getModule() = imp.getImportedModule() and
result = mod.(DataFlow::SourceNode).getAPropertyRead("exports")
)
@@ -832,13 +796,6 @@ module API {
result = awaited(call, DataFlow::TypeTracker::end())
}
private DataFlow::SourceNode getANodeWithType(TypeName tn) {
exists(string moduleName, string typeName |
tn.hasQualifiedName(moduleName, typeName) and
result.hasUnderlyingType(moduleName, typeName)
)
}
/**
* Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`.
*/
@@ -879,11 +836,10 @@ module API {
succ = MkClassInstance(trackDefNode(def))
)
or
exists(CanonicalName cn1, string n, CanonicalName cn2 |
pred in [mkCanonicalNameDef(cn1), mkCanonicalNameUse(cn1)] and
cn2 = cn1.getChild(n) and
lbl = Label::member(n) and
succ in [mkCanonicalNameDef(cn2), mkCanonicalNameUse(cn2)]
exists(string moduleName, string exportName |
pred = MkModuleImport(moduleName) and
lbl = Label::member(exportName) and
succ = MkHasUnderlyingType(moduleName, exportName)
)
or
exists(DataFlow::Node nd, DataFlow::FunctionNode f |
@@ -1027,31 +983,44 @@ private module Label {
string promised() { result = "promised" }
}
private class NodeModuleSourcesNodes extends DataFlow::SourceNode::Range {
Variable v;
NodeModuleSourcesNodes() {
exists(NodeModule m |
this = DataFlow::ssaDefinitionNode(SSA::implicitInit(v)) and
v = [m.getModuleVariable(), m.getExportsVariable()]
)
}
Variable getVariable() { result = v }
}
/**
* A CommonJS/AMD `module` variable, considered as a source node.
* A CommonJS/AMD `module` variable.
*/
private class ModuleAsSourceNode extends DataFlow::SourceNode::Range {
private class ModuleVarNode extends DataFlow::Node {
Module m;
ModuleAsSourceNode() {
this = DataFlow::ssaDefinitionNode(SSA::implicitInit(m.(NodeModule).getModuleVariable()))
ModuleVarNode() {
this.(NodeModuleSourcesNodes).getVariable() = m.(NodeModule).getModuleVariable()
or
this = DataFlow::parameterNode(m.(AmdModule).getDefine().getModuleParameter())
DataFlow::parameterNode(this, m.(AmdModule).getDefine().getModuleParameter())
}
Module getModule() { result = m }
}
/**
* A CommonJS/AMD `exports` variable, considered as a source node.
* A CommonJS/AMD `exports` variable.
*/
private class ExportsAsSourceNode extends DataFlow::SourceNode::Range {
private class ExportsVarNode extends DataFlow::Node {
Module m;
ExportsAsSourceNode() {
this = DataFlow::ssaDefinitionNode(SSA::implicitInit(m.(NodeModule).getExportsVariable()))
ExportsVarNode() {
this.(NodeModuleSourcesNodes).getVariable() = m.(NodeModule).getExportsVariable()
or
this = DataFlow::parameterNode(m.(AmdModule).getDefine().getExportsParameter())
DataFlow::parameterNode(this, m.(AmdModule).getDefine().getExportsParameter())
}
Module getModule() { result = m }

View File

@@ -84,16 +84,17 @@ private module ArrayDataFlow {
* A step modelling the creation of an Array using the `Array.from(x)` method.
* The step copies the elements of the argument (set, array, or iterator elements) into the resulting array.
*/
private class ArrayFrom extends DataFlow::AdditionalFlowStep, DataFlow::CallNode {
ArrayFrom() { this = DataFlow::globalVarRef("Array").getAMemberCall("from") }
private class ArrayFrom extends DataFlow::SharedFlowStep {
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string fromProp, string toProp
) {
pred = this.getArgument(0) and
succ = this and
fromProp = arrayLikeElement() and
toProp = arrayElement()
exists(DataFlow::CallNode call |
call = DataFlow::globalVarRef("Array").getAMemberCall("from") and
pred = call.getArgument(0) and
succ = call and
fromProp = arrayLikeElement() and
toProp = arrayElement()
)
}
}
@@ -103,55 +104,45 @@ private module ArrayDataFlow {
*
* Such a step can occur both with the `push` and `unshift` methods, or when creating a new array.
*/
private class ArrayCopySpread extends DataFlow::AdditionalFlowStep {
DataFlow::Node spreadArgument; // the spread argument containing the elements to be copied.
DataFlow::Node base; // the object where the elements should be copied to.
ArrayCopySpread() {
exists(DataFlow::MethodCallNode mcn | mcn = this |
mcn.getMethodName() = ["push", "unshift"] and
spreadArgument = mcn.getASpreadArgument() and
base = mcn.getReceiver().getALocalSource()
)
or
spreadArgument = this.(DataFlow::ArrayCreationNode).getASpreadArgument() and
base = this
}
private class ArrayCopySpread extends DataFlow::SharedFlowStep {
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string fromProp, string toProp
) {
pred = spreadArgument and
succ = base and
fromProp = arrayLikeElement() and
toProp = arrayElement()
toProp = arrayElement() and
(
exists(DataFlow::MethodCallNode mcn |
mcn.getMethodName() = ["push", "unshift"] and
pred = mcn.getASpreadArgument() and
succ = mcn.getReceiver().getALocalSource()
)
or
pred = succ.(DataFlow::ArrayCreationNode).getASpreadArgument()
)
}
}
/**
* A step for storing an element on an array using `arr.push(e)` or `arr.unshift(e)`.
*/
private class ArrayAppendStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
ArrayAppendStep() {
this.getMethodName() = "push" or
this.getMethodName() = "unshift"
}
private class ArrayAppendStep extends DataFlow::SharedFlowStep {
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
prop = arrayElement() and
element = this.getAnArgument() and
obj.getAMethodCall() = this
exists(DataFlow::MethodCallNode call |
call.getMethodName() = ["push", "unshift"] and
element = call.getAnArgument() and
obj.getAMethodCall() = call
)
}
}
/**
* A step for reading/writing an element from an array inside a for-loop.
* E.g. a read from `foo[i]` to `bar` in `for(var i = 0; i < arr.length; i++) {bar = foo[i]}`.
* A node that reads or writes an element from an array inside a for-loop.
*/
private class ArrayIndexingStep extends DataFlow::AdditionalFlowStep, DataFlow::Node {
private class ArrayIndexingAccess extends DataFlow::Node {
DataFlow::PropRef read;
ArrayIndexingStep() {
ArrayIndexingAccess() {
read = this and
TTNumber() =
unique(InferredType type | type = read.getPropertyNameExpr().flow().analyze().getAType()) and
@@ -162,17 +153,27 @@ private module ArrayDataFlow {
i.getVariable().getADefinition().(VariableDeclarator).getDeclStmt() = init
)
}
}
/**
* A step for reading/writing an element from an array inside a for-loop.
* E.g. a read from `foo[i]` to `bar` in `for(var i = 0; i < arr.length; i++) {bar = foo[i]}`.
*/
private class ArrayIndexingStep extends DataFlow::SharedFlowStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
prop = arrayElement() and
obj = this.(DataFlow::PropRead).getBase() and
element = this
exists(ArrayIndexingAccess access |
prop = arrayElement() and
obj = access.(DataFlow::PropRead).getBase() and
element = access
)
}
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
prop = arrayElement() and
element = this.(DataFlow::PropWrite).getRhs() and
this = obj.getAPropertyWrite()
exists(ArrayIndexingAccess access |
prop = arrayElement() and
element = access.(DataFlow::PropWrite).getRhs() and
access = obj.getAPropertyWrite()
)
}
}
@@ -180,16 +181,14 @@ private module ArrayDataFlow {
* A step for retrieving an element from an array using `.pop()` or `.shift()`.
* E.g. `array.pop()`.
*/
private class ArrayPopStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
ArrayPopStep() {
getMethodName() = "pop" or
getMethodName() = "shift"
}
private class ArrayPopStep extends DataFlow::SharedFlowStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
prop = arrayElement() and
obj = this.getReceiver() and
element = this
exists(DataFlow::MethodCallNode call |
call.getMethodName() = ["pop", "shift"] and
prop = arrayElement() and
obj = call.getReceiver() and
element = call
)
}
}
@@ -235,12 +234,12 @@ private module ArrayDataFlow {
/**
* A step for creating an array and storing the elements in the array.
*/
private class ArrayCreationStep extends DataFlow::AdditionalFlowStep, DataFlow::ArrayCreationNode {
private class ArrayCreationStep extends DataFlow::SharedFlowStep {
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
exists(int i |
element = this.getElement(i) and
obj = this and
if this = any(PromiseAllCreation c).getArrayNode()
exists(DataFlow::ArrayCreationNode array, int i |
element = array.getElement(i) and
obj = array and
if array = any(PromiseAllCreation c).getArrayNode()
then prop = arrayElement(i)
else prop = arrayElement()
)
@@ -251,13 +250,14 @@ private module ArrayDataFlow {
* A step modelling that `splice` can insert elements into an array.
* For example in `array.splice(i, del, e)`: if `e` is tainted, then so is `array
*/
private class ArraySpliceStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
ArraySpliceStep() { this.getMethodName() = "splice" }
private class ArraySpliceStep extends DataFlow::SharedFlowStep {
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
prop = arrayElement() and
element = getArgument(2) and
this = obj.getAMethodCall()
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "splice" and
prop = arrayElement() and
element = call.getArgument(2) and
call = obj.getAMethodCall()
)
}
}
@@ -265,42 +265,27 @@ private module ArrayDataFlow {
* A step for modelling `concat`.
* For example in `e = arr1.concat(arr2, arr3)`: if any of the `arr` is tainted, then so is `e`.
*/
private class ArrayConcatStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
ArrayConcatStep() { this.getMethodName() = "concat" }
private class ArrayConcatStep extends DataFlow::SharedFlowStep {
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
prop = arrayElement() and
(pred = this.getReceiver() or pred = this.getAnArgument()) and
succ = this
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "concat" and
prop = arrayElement() and
(pred = call.getReceiver() or pred = call.getAnArgument()) and
succ = call
)
}
}
/**
* A step for modelling that elements from an array `arr` also appear in the result from calling `slice`/`splice`/`filter`.
*/
private class ArraySliceStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
ArraySliceStep() {
this.getMethodName() = "slice" or
this.getMethodName() = "splice" or
this.getMethodName() = "filter"
}
private class ArraySliceStep extends DataFlow::SharedFlowStep {
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
prop = arrayElement() and
pred = this.getReceiver() and
succ = this
}
}
/**
* A step for modelling `for of` iteration on arrays.
*/
private class ForOfStep extends PreCallGraphStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node e, string prop) {
exists(ForOfStmt forOf |
obj = forOf.getIterationDomain().flow() and
e = DataFlow::lvalueNode(forOf.getLValue()) and
prop = arrayElement()
exists(DataFlow::MethodCallNode call |
call.getMethodName() = ["slice", "splice", "filter"] and
prop = arrayElement() and
pred = call.getReceiver() and
succ = call
)
}
}

View File

@@ -6,32 +6,30 @@
import javascript
private import semmle.javascript.dataflow.internal.StepSummary
private import semmle.javascript.dataflow.internal.PreCallGraphStep
private import DataFlow::PseudoProperties
/**
* A pseudo-property used in a data-flow/type-tracking step for collections.
* DEPRECATED. Exists only to support other deprecated elements.
*
* By extending `TypeTrackingPseudoProperty` the class enables the use of the collection related pseudo-properties in type-tracking predicates.
* Type-tracking now automatically determines the set of pseudo-properties to include
* ased on which properties are contributed by `SharedTaintStep`s.
*/
private class PseudoProperty extends TypeTrackingPseudoProperty {
deprecated private class PseudoProperty extends string {
PseudoProperty() {
this = [arrayLikeElement(), "1"] or // the "1" is required for the `ForOfStep`.
this = any(CollectionDataFlow::MapSet step).getAPseudoProperty()
}
override PseudoProperty getLoadStoreToProp() {
exists(CollectionFlowStep step | step.loadStore(_, _, this, result))
this =
[
mapValue(any(DataFlow::CallNode c | c.getCalleeName() = "set").getArgument(0)),
mapValueAll()
]
}
}
/**
* An `AdditionalFlowStep` used to model a data-flow step related to standard library collections.
*
* The `loadStep`/`storeStep`/`loadStoreStep` methods are overloaded such that the new predicates
* `load`/`store`/`loadStore` can be used in the `CollectionsTypeTracking` module.
* (Thereby avoiding naming conflicts with a "cousin" `AdditionalFlowStep` implementation.)
* DEPRECATED. Use `SharedFlowStep` or `SharedTaintTrackingStep` instead.
*/
abstract class CollectionFlowStep extends DataFlow::AdditionalFlowStep {
abstract deprecated class CollectionFlowStep extends DataFlow::AdditionalFlowStep {
final override predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
final override predicate step(
@@ -84,27 +82,28 @@ abstract class CollectionFlowStep extends DataFlow::AdditionalFlowStep {
}
/**
* Provides predicates and clases for type-tracking collections.
* DEPRECATED. These steps are now included in the default type tracking steps,
* in most cases one can simply use those instead.
*/
module CollectionsTypeTracking {
deprecated module CollectionsTypeTracking {
/**
* Gets the result from a single step through a collection, from `pred` to `result` summarized by `summary`.
*/
pragma[inline]
DataFlow::SourceNode collectionStep(DataFlow::Node pred, StepSummary summary) {
exists(CollectionFlowStep step, PseudoProperty field |
exists(PseudoProperty field |
summary = LoadStep(field) and
step.load(pred, result, field) and
DataFlow::SharedTypeTrackingStep::loadStep(pred, result, field) and
not field = mapValueUnknownKey() // prune unknown reads in type-tracking
or
summary = StoreStep(field) and
step.store(pred, result, field)
DataFlow::SharedTypeTrackingStep::storeStep(pred, result, field)
or
summary = CopyStep(field) and
step.loadStore(pred, result, field)
DataFlow::SharedTypeTrackingStep::loadStoreStep(pred, result, field)
or
exists(PseudoProperty toField | summary = LoadStoreStep(field, toField) |
step.loadStore(pred, result, field, toField)
DataFlow::SharedTypeTrackingStep::loadStoreStep(pred, result, field, toField)
)
)
}
@@ -129,74 +128,71 @@ private module CollectionDataFlow {
/**
* A step for `Set.add()` method, which adds an element to a Set.
*/
private class SetAdd extends CollectionFlowStep, DataFlow::MethodCallNode {
SetAdd() { this.getMethodName() = "add" }
override predicate store(DataFlow::Node element, DataFlow::SourceNode obj, PseudoProperty prop) {
this = obj.getAMethodCall() and
element = this.getArgument(0) and
prop = setElement()
private class SetAdd extends PreCallGraphStep {
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
exists(DataFlow::MethodCallNode call |
call = obj.getAMethodCall("add") and
element = call.getArgument(0) and
prop = setElement()
)
}
}
/**
* A step for the `Set` constructor, which copies any elements from the first argument into the resulting set.
*/
private class SetConstructor extends CollectionFlowStep, DataFlow::NewNode {
SetConstructor() { this = DataFlow::globalVarRef("Set").getAnInstantiation() }
override predicate loadStore(
DataFlow::Node pred, DataFlow::Node succ, PseudoProperty fromProp, PseudoProperty toProp
private class SetConstructor extends PreCallGraphStep {
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string fromProp, string toProp
) {
pred = this.getArgument(0) and
succ = this and
fromProp = arrayLikeElement() and
toProp = setElement()
exists(DataFlow::NewNode invoke |
invoke = DataFlow::globalVarRef("Set").getAnInstantiation() and
pred = invoke.getArgument(0) and
succ = invoke and
fromProp = arrayLikeElement() and
toProp = setElement()
)
}
}
/**
* A step for a `for of` statement on a Map, Set, or Iterator.
* For Sets and iterators the l-value are the elements of the set/iterator.
* For Maps the l-value is a tuple containing a key and a value.
* A step for modelling `for of` iteration on arrays, maps, sets, and iterators.
*
* For sets and iterators the l-value are the elements of the set/iterator.
* For maps the l-value is a tuple containing a key and a value.
*/
// This is partially duplicated behavior with the `for of` step for Arrays (`ArrayDataFlow::ForOfStep`).
// This duplication is required for the type-tracking steps defined in `CollectionsTypeTracking`.
private class ForOfStep extends CollectionFlowStep, DataFlow::ValueNode {
ForOfStmt forOf;
DataFlow::Node element;
ForOfStep() {
this.asExpr() = forOf.getIterationDomain() and
element = DataFlow::lvalueNode(forOf.getLValue())
private class ForOfStep extends PreCallGraphStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node e, string prop) {
exists(ForOfStmt forOf |
obj = forOf.getIterationDomain().flow() and
e = DataFlow::lvalueNode(forOf.getLValue()) and
prop = arrayLikeElement()
)
}
override predicate load(DataFlow::Node obj, DataFlow::Node e, PseudoProperty prop) {
obj = this and
e = element and
prop = arrayLikeElement()
}
override predicate loadStore(
DataFlow::Node pred, DataFlow::Node succ, PseudoProperty fromProp, PseudoProperty toProp
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string fromProp, string toProp
) {
pred = this and
succ = element and
fromProp = mapValueAll() and
toProp = "1"
exists(ForOfStmt forOf |
pred = forOf.getIterationDomain().flow() and
succ = DataFlow::lvalueNode(forOf.getLValue()) and
fromProp = mapValueAll() and
toProp = "1"
)
}
}
/**
* A step for a call to `forEach` on a Set or Map.
*/
private class SetMapForEach extends CollectionFlowStep, DataFlow::MethodCallNode {
SetMapForEach() { this.getMethodName() = "forEach" }
override predicate load(DataFlow::Node obj, DataFlow::Node element, PseudoProperty prop) {
obj = this.getReceiver() and
element = this.getCallback(0).getParameter(0) and
prop = [setElement(), mapValueAll()]
private class SetMapForEach extends PreCallGraphStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "forEach" and
obj = call.getReceiver() and
element = call.getCallback(0).getParameter(0) and
prop = [setElement(), mapValueAll()]
)
}
}
@@ -204,14 +200,15 @@ private module CollectionDataFlow {
* A call to the `get` method on a Map.
* If the key of the call to `get` has a known string value, then only the value corresponding to that key will be retrieved. (The known string value is encoded as part of the pseudo-property)
*/
private class MapGet extends CollectionFlowStep, DataFlow::MethodCallNode {
MapGet() { this.getMethodName() = "get" }
override predicate load(DataFlow::Node obj, DataFlow::Node element, PseudoProperty prop) {
obj = this.getReceiver() and
element = this and
// reading the join of known and unknown values
(prop = mapValue(this.getArgument(0)) or prop = mapValueUnknownKey())
private class MapGet extends PreCallGraphStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "get" and
obj = call.getReceiver() and
element = call and
// reading the join of known and unknown values
(prop = mapValue(call.getArgument(0)) or prop = mapValueUnknownKey())
)
}
}
@@ -223,56 +220,47 @@ private module CollectionDataFlow {
* Otherwise the value will be stored into a pseudo-property corresponding to values with unknown keys.
* The value will additionally be stored into a pseudo-property corresponding to all values.
*/
class MapSet extends CollectionFlowStep, DataFlow::MethodCallNode {
MapSet() { this.getMethodName() = "set" }
override predicate store(DataFlow::Node element, DataFlow::SourceNode obj, PseudoProperty prop) {
this = obj.getAMethodCall() and
element = this.getArgument(1) and
prop = getAPseudoProperty()
class MapSet extends PreCallGraphStep {
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
exists(DataFlow::MethodCallNode call |
call = obj.getAMethodCall("set") and
element = call.getArgument(1) and
prop = [mapValue(call.getArgument(0)), mapValueAll()]
)
}
/**
* Gets a pseudo-property used to store an element in a map.
* The pseudo-property represents both values where the key is a known string value (which is encoded in the pseudo-property),
* and values where the key is unknown.
*
* Additionally, all elements are stored into the pseudo-property `mapValueAll()`.
*
* The return-type is `string` as this predicate is used to define which pseudo-properties exist.
*/
string getAPseudoProperty() { result = [mapValue(this.getArgument(0)), mapValueAll()] }
}
/**
* A step for a call to `values` on a Map or a Set.
*/
private class MapAndSetValues extends CollectionFlowStep, DataFlow::MethodCallNode {
MapAndSetValues() { this.getMethodName() = "values" }
override predicate loadStore(
DataFlow::Node pred, DataFlow::Node succ, PseudoProperty fromProp, PseudoProperty toProp
private class MapAndSetValues extends PreCallGraphStep {
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string fromProp, string toProp
) {
pred = this.getReceiver() and
succ = this and
fromProp = [mapValueAll(), setElement()] and
toProp = iteratorElement()
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "values" and
pred = call.getReceiver() and
succ = call and
fromProp = [mapValueAll(), setElement()] and
toProp = iteratorElement()
)
}
}
/**
* A step for a call to `keys` on a Set.
*/
private class SetKeys extends CollectionFlowStep, DataFlow::MethodCallNode {
SetKeys() { this.getMethodName() = "keys" }
override predicate loadStore(
DataFlow::Node pred, DataFlow::Node succ, PseudoProperty fromProp, PseudoProperty toProp
private class SetKeys extends PreCallGraphStep {
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string fromProp, string toProp
) {
pred = this.getReceiver() and
succ = this and
fromProp = setElement() and
toProp = iteratorElement()
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "keys" and
pred = call.getReceiver() and
succ = call and
fromProp = setElement() and
toProp = iteratorElement()
)
}
}
}

View File

@@ -315,6 +315,11 @@ module DOM {
)
}
private InferredType getArgumentTypeFromJQueryMethodGet(JQuery::MethodCall call) {
call.getMethodName() = "get" and
result = call.getArgument(0).analyze().getAType()
}
private class DefaultRange extends Range {
DefaultRange() {
this.asExpr().(VarAccess).getVariable() instanceof DOMGlobalVariable
@@ -344,7 +349,7 @@ module DOM {
or
exists(JQuery::MethodCall call | this = call and call.getMethodName() = "get" |
call.getNumArgument() = 1 and
unique(InferredType t | t = call.getArgument(0).analyze().getAType()) = TTNumber()
unique(InferredType t | t = getArgumentTypeFromJQueryMethodGet(call)) = TTNumber()
)
or
// A `this` node from a callback given to a `$().each(callback)` call.

View File

@@ -305,6 +305,7 @@ module AccessPath {
* }
* ```
*/
pragma[inline]
DataFlow::Node getAReferenceTo(Root root, string path) {
path = fromReference(result, root) and
not root.isGlobal()
@@ -327,6 +328,7 @@ module AccessPath {
* })(NS = NS || {});
* ```
*/
pragma[inline]
DataFlow::Node getAReferenceTo(string path) {
path = fromReference(result, DataFlow::globalAccessPathRootPseudoNode())
}
@@ -347,6 +349,7 @@ module AccessPath {
* }
* ```
*/
pragma[inline]
DataFlow::Node getAnAssignmentTo(Root root, string path) {
path = fromRhs(result, root) and
not root.isGlobal()
@@ -367,6 +370,7 @@ module AccessPath {
* })(foo = foo || {});
* ```
*/
pragma[inline]
DataFlow::Node getAnAssignmentTo(string path) {
path = fromRhs(result, DataFlow::globalAccessPathRootPseudoNode())
}
@@ -376,6 +380,7 @@ module AccessPath {
*
* See `getAReferenceTo` and `getAnAssignmentTo` for more details.
*/
pragma[inline]
DataFlow::Node getAReferenceOrAssignmentTo(string path) {
result = getAReferenceTo(path)
or
@@ -387,6 +392,7 @@ module AccessPath {
*
* See `getAReferenceTo` and `getAnAssignmentTo` for more details.
*/
pragma[inline]
DataFlow::Node getAReferenceOrAssignmentTo(Root root, string path) {
result = getAReferenceTo(root, path)
or

View File

@@ -16,51 +16,53 @@ abstract class HtmlSanitizerCall extends DataFlow::CallNode {
abstract DataFlow::Node getInput();
}
pragma[noinline]
private DataFlow::SourceNode htmlSanitizerFunction() {
result = DataFlow::moduleMember("ent", "encode")
or
result = DataFlow::moduleMember("entities", "encodeHTML")
or
result = DataFlow::moduleMember("entities", "encodeXML")
or
result = DataFlow::moduleMember("escape-goat", "escape")
or
result = DataFlow::moduleMember("he", "encode")
or
result = DataFlow::moduleMember("he", "escape")
or
result = DataFlow::moduleImport("sanitize-html")
or
result = DataFlow::moduleMember("sanitizer", "escape")
or
result = DataFlow::moduleMember("sanitizer", "sanitize")
or
result = DataFlow::moduleMember("validator", "escape")
or
result = DataFlow::moduleImport("xss")
or
result = DataFlow::moduleMember("xss-filters", _)
or
result = LodashUnderscore::member("escape")
or
exists(DataFlow::PropRead read | read = result |
read.getPropertyName() = "sanitize" and
read.getBase().asExpr().(VarAccess).getName() = "DOMPurify"
)
or
exists(string name | name = "encode" or name = "encodeNonUTF" |
result = DataFlow::moduleMember("html-entities", _).getAnInstantiation().getAPropertyRead(name) or
result = DataFlow::moduleMember("html-entities", _).getAPropertyRead(name)
)
or
result = Closure::moduleImport("goog.string.htmlEscape")
}
/**
* Matches HTML sanitizers from known NPM packages as well as home-made sanitizers (matched by name).
*/
private class DefaultHtmlSanitizerCall extends HtmlSanitizerCall {
DefaultHtmlSanitizerCall() {
exists(DataFlow::SourceNode callee | this = callee.getACall() |
callee = DataFlow::moduleMember("ent", "encode")
or
callee = DataFlow::moduleMember("entities", "encodeHTML")
or
callee = DataFlow::moduleMember("entities", "encodeXML")
or
callee = DataFlow::moduleMember("escape-goat", "escape")
or
callee = DataFlow::moduleMember("he", "encode")
or
callee = DataFlow::moduleMember("he", "escape")
or
callee = DataFlow::moduleImport("sanitize-html")
or
callee = DataFlow::moduleMember("sanitizer", "escape")
or
callee = DataFlow::moduleMember("sanitizer", "sanitize")
or
callee = DataFlow::moduleMember("validator", "escape")
or
callee = DataFlow::moduleImport("xss")
or
callee = DataFlow::moduleMember("xss-filters", _)
or
callee = LodashUnderscore::member("escape")
or
exists(DataFlow::PropRead read | read = callee |
read.getPropertyName() = "sanitize" and
read.getBase().asExpr().(VarAccess).getName() = "DOMPurify"
)
or
exists(string name | name = "encode" or name = "encodeNonUTF" |
callee =
DataFlow::moduleMember("html-entities", _).getAnInstantiation().getAPropertyRead(name) or
callee = DataFlow::moduleMember("html-entities", _).getAPropertyRead(name)
)
or
callee = Closure::moduleImport("goog.string.htmlEscape")
)
this = htmlSanitizerFunction().getACall()
or
// Match home-made sanitizers by name.
exists(string calleeName | calleeName = getCalleeName() |

View File

@@ -49,6 +49,58 @@ private DataFlow::Node getAValueExportedByPackage() {
result = cla.getAStaticMethod() or
result = cla.getConstructor()
)
or
// *****
// Common styles of transforming exported objects.
// *****
//
// Object.defineProperties
exists(DataFlow::MethodCallNode call |
call = DataFlow::globalVarRef("Object").getAMethodCall("defineProperties") and
[call, call.getArgument(0)] = getAValueExportedByPackage() and
result = call.getArgument(any(int i | i > 0))
)
or
// Object.defineProperty
exists(CallToObjectDefineProperty call |
[call, call.getBaseObject()] = getAValueExportedByPackage()
|
result = call.getPropertyDescriptor().getALocalSource().getAPropertyReference("value")
or
result =
call.getPropertyDescriptor()
.getALocalSource()
.getAPropertyReference("get")
.(DataFlow::FunctionNode)
.getAReturn()
)
or
// Object.assign and friends
exists(ExtendCall assign |
getAValueExportedByPackage() = [assign, assign.getDestinationOperand()] and
result = assign.getASourceOperand()
)
or
// Array.prototype.{map, reduce, entries, values}
exists(DataFlow::MethodCallNode map |
map.getMethodName() = ["map", "reduce", "entries", "values"] and
map = getAValueExportedByPackage()
|
result = map.getArgument(0).getABoundFunctionValue(_).getAReturn()
or
// assuming that the receiver of the call is somehow exported
result = map.getReceiver()
)
or
// Object.{fromEntries, freeze, seal, entries, values}
exists(DataFlow::MethodCallNode freeze |
freeze =
DataFlow::globalVarRef("Object")
.getAMethodCall(["fromEntries", "freeze", "seal", "entries", "values"])
|
freeze = getAValueExportedByPackage() and
result = freeze.getArgument(0)
)
}
/**

View File

@@ -141,29 +141,7 @@ abstract class PathString extends string {
* components of this path refers to when resolved relative to the
* given `root` folder.
*/
Path resolveUpTo(int n, Folder root) {
n = 0 and result.getContainer() = root and root = getARootFolder()
or
exists(Path base, string next | next = getComponent(this, n - 1, base, root) |
// handle empty components and the special "." folder
(next = "" or next = ".") and
result = base
or
// handle the special ".." folder
next = ".." and result = base.(ConsPath).getParent()
or
// special handling for Windows drive letters when resolving absolute path:
// the extractor populates "C:/" as a folder that has path "C:/" but name ""
n = 1 and
next.regexpMatch("[A-Za-z]:") and
root.getBaseName() = "" and
root.toString() = next.toUpperCase() + "/" and
result = base
or
// default case
result = TConsPath(base, next)
)
}
Path resolveUpTo(int n, Folder root) { result = resolveUpTo(this, n, root, _) }
/**
* Gets the absolute path that this path refers to when resolved relative to
@@ -172,16 +150,57 @@ abstract class PathString extends string {
Path resolve(Folder root) { result = resolveUpTo(getNumComponent(), root) }
}
/**
* Gets the absolute path that the sub-path consisting of the first `n`
* components of this path refers to when resolved relative to the
* given `root` folder.
*/
private Path resolveUpTo(PathString p, int n, Folder root, boolean inTS) {
n = 0 and result.getContainer() = root and root = p.getARootFolder() and inTS = false
or
exists(Path base, string next | next = getComponent(p, n - 1, base, root, inTS) |
// handle empty components and the special "." folder
(next = "" or next = ".") and
result = base
or
// handle the special ".." folder
next = ".." and result = base.(ConsPath).getParent()
or
// special handling for Windows drive letters when resolving absolute path:
// the extractor populates "C:/" as a folder that has path "C:/" but name ""
n = 1 and
next.regexpMatch("[A-Za-z]:") and
root.getBaseName() = "" and
root.toString() = next.toUpperCase() + "/" and
result = base
or
// default case
result = TConsPath(base, next)
)
}
/**
* Gets the `i`th component of the path `str`, where `base` is the resolved path one level up.
* Supports that the root directory might be compiled output from TypeScript.
* `inTS` is true if the result is TypeScript that is compiled into the path specified by `str`.
*/
private string getComponent(PathString str, int n, Path base, Folder root) {
base = str.resolveUpTo(n, root) and
(
result = str.getComponent(n)
or
result = TypeScriptOutDir::getOriginalTypeScriptFolder(str.getComponent(n), base.getContainer())
private string getComponent(PathString str, int n, Path base, Folder root, boolean inTS) {
exists(boolean prevTS |
base = resolveUpTo(str, n, root, prevTS) and
(
result = str.getComponent(n) and prevTS = inTS
or
// If we are in a TypeScript source folder, try to replace file endings with ".ts" or ".tsx"
n = str.getNumComponent() - 1 and
prevTS = true and
inTS = prevTS and
result = str.getComponent(n).regexpCapture("^(.*)\\.js$", 1) + "." + ["ts", "tsx"]
or
prevTS = false and
inTS = true and
result =
TypeScriptOutDir::getOriginalTypeScriptFolder(str.getComponent(n), base.getContainer())
)
)
}
@@ -194,21 +213,35 @@ private module TypeScriptOutDir {
*/
string getOriginalTypeScriptFolder(string outdir, Folder parent) {
exists(JSONObject tsconfig |
tsconfig.getFile().getBaseName() = "tsconfig.json" and
tsconfig.isTopLevel() and
tsconfig.getFile().getParentContainer() = parent
|
outdir =
tsconfig
.getPropValue("compilerOptions")
.(JSONObject)
.getPropValue("outDir")
.(JSONString)
.getValue() and
result = getEffectiveRootDirFromTSConfig(tsconfig)
outdir = removeLeadingSlash(getOutDir(tsconfig, parent)) and
result = removeLeadingSlash(getEffectiveRootDirFromTSConfig(tsconfig))
)
}
/**
* Removes a leading dot and/or slash from `raw`.
*/
bindingset[raw]
private string removeLeadingSlash(string raw) {
result = raw.regexpCapture("^\\.?/?([\\w.\\-]+)$", 1)
}
/**
* Gets the `outDir` option from a tsconfig file from the folder `parent`.
*/
private string getOutDir(JSONObject tsconfig, Folder parent) {
tsconfig.getFile().getBaseName().regexpMatch("tsconfig.*\\.json") and
tsconfig.isTopLevel() and
tsconfig.getFile().getParentContainer() = parent and
result =
tsconfig
.getPropValue("compilerOptions")
.(JSONObject)
.getPropValue("outDir")
.(JSONString)
.getValue()
}
/**
* Gets the directory that contains the TypeScript source files.
* Based on the tsconfig.json file `tsconfig`.

View File

@@ -214,13 +214,6 @@ module PromiseTypeTracking {
result = PromiseTypeTracking::promiseStep(mid, summary)
)
}
/**
* A class enabling the use of the `resolveField` as a pseudo-property in type-tracking predicates.
*/
private class ResolveFieldAsTypeTrackingProperty extends TypeTrackingPseudoProperty {
ResolveFieldAsTypeTrackingProperty() { this = Promises::valueProp() }
}
}
private import semmle.javascript.dataflow.internal.PreCallGraphStep
@@ -596,6 +589,7 @@ private module ClosurePromise {
* A promise created by a call `goog.Promise.resolve(value)`.
*/
private class ResolvedClosurePromiseDefinition extends ResolvedPromiseDefinition {
pragma[noinline]
ResolvedClosurePromiseDefinition() {
this = Closure::moduleImport("goog.Promise.resolve").getACall()
}

View File

@@ -1114,4 +1114,64 @@ module RegExp {
or
result = node.asExpr().(StringLiteral).asRegExp()
}
/**
* A character that will be analyzed by `RegExp::alwaysMatchesMetaCharacter`.
*
* Currently only `<`, `'`, and `"` are considered to be meta-characters, but new meta-characters
* can be added by subclassing this class.
*/
abstract class MetaCharacter extends string {
bindingset[this]
MetaCharacter() { any() }
/**
* Holds if the given atomic term matches this meta-character.
*
* Does not hold for derived terms like alternatives and groups.
*
* By default, `.`, `\W`, `\S`, and `\D` are considered to match any meta-character,
* but the predicate can be overridden for meta-characters where this is not the case.
*/
predicate matchedByAtom(RegExpTerm term) {
term.(RegExpConstant).getConstantValue() = this
or
term instanceof RegExpDot
or
term.(RegExpCharacterClassEscape).getValue() = ["\\W", "\\S", "\\D"]
or
exists(string lo, string hi |
term.(RegExpCharacterRange).isRange(lo, hi) and
lo <= this and
this <= hi
)
}
}
private class DefaultMetaCharacter extends MetaCharacter {
DefaultMetaCharacter() { this = ["<", "'", "\""] }
}
/**
* Holds if `term` can match any occurence of `char` within a string (not taking into account
* the context in which `term` appears).
*
* This predicate is under-approximate and never considers sequences to guarantee a match.
*/
predicate alwaysMatchesMetaCharacter(RegExpTerm term, MetaCharacter char) {
not term.getParent() instanceof RegExpSequence and // restrict size of predicate
char.matchedByAtom(term)
or
alwaysMatchesMetaCharacter(term.(RegExpGroup).getAChild(), char)
or
alwaysMatchesMetaCharacter(term.(RegExpAlt).getAlternative(), char)
or
exists(RegExpCharacterClass class_ | term = class_ |
not class_.isInverted() and
char.matchedByAtom(class_.getAChild())
or
class_.isInverted() and
not char.matchedByAtom(class_.getAChild())
)
}
}

View File

@@ -74,23 +74,13 @@ private class ArrayIterationCallbackAsPartialInvoke extends DataFlow::PartialInv
* A flow step propagating the exception thrown from a callback to a method whose name coincides
* a built-in Array iteration method, such as `forEach` or `map`.
*/
private class IteratorExceptionStep extends DataFlow::MethodCallNode, DataFlow::AdditionalFlowStep {
IteratorExceptionStep() {
exists(string name | name = getMethodName() |
name = "forEach" or
name = "each" or
name = "map" or
name = "filter" or
name = "some" or
name = "every" or
name = "fold" or
name = "reduce"
)
}
private class IteratorExceptionStep extends DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = getAnArgument().(DataFlow::FunctionNode).getExceptionalReturn() and
succ = this.getExceptionalReturn()
exists(DataFlow::MethodCallNode call |
call.getMethodName() = ["forEach", "each", "map", "filter", "some", "every", "fold", "reduce"] and
pred = call.getAnArgument().(DataFlow::FunctionNode).getExceptionalReturn() and
succ = call.getExceptionalReturn()
)
}
}
@@ -107,7 +97,7 @@ class StringReplaceCall extends DataFlow::MethodCallNode {
}
/** Gets the regular expression passed as the first argument to `replace`, if any. */
DataFlow::RegExpLiteralNode getRegExp() { result.flowsTo(getArgument(0)) }
DataFlow::RegExpCreationNode getRegExp() { result.flowsTo(getArgument(0)) }
/** Gets a string that is being replaced by this call. */
string getAReplacedString() {

View File

@@ -55,12 +55,8 @@ module StringConcatenation {
exists(DataFlow::MethodCallNode call |
node = call and
call.getMethodName() = "concat" and
not (
exists(DataFlow::ArrayCreationNode array |
array.flowsTo(call.getAnArgument()) or array.flowsTo(call.getReceiver())
)
or
DataFlow::reflectiveCallNode(_) = call
not exists(DataFlow::ArrayCreationNode array |
array.flowsTo(call.getAnArgument()) or array.flowsTo(call.getReceiver())
) and
(
n = 0 and

View File

@@ -544,6 +544,12 @@ abstract class LabeledBarrierGuardNode extends BarrierGuardNode {
}
/**
* DEPRECATED. Subclasses should extend `SharedFlowStep` instead, unless the subclass
* is part of a query, in which case it should be moved into the `isAdditionalFlowStep` predicate
* of the relevant data-flow configuration.
* Other uses of the predicate in this class should instead reference the predicates in the
* `SharedFlowStep::` module, such as `SharedFlowStep::step`.
*
* A data flow edge that should be added to all data flow configurations in
* addition to standard data flow edges.
*
@@ -551,19 +557,19 @@ abstract class LabeledBarrierGuardNode extends BarrierGuardNode {
* of the standard library. Override `Configuration::isAdditionalFlowStep`
* for analysis-specific flow steps.
*/
cached
abstract class AdditionalFlowStep extends DataFlow::Node {
deprecated class AdditionalFlowStep = LegacyAdditionalFlowStep;
// Internal version of AdditionalFlowStep that we can reference without deprecation warnings.
abstract private class LegacyAdditionalFlowStep extends DataFlow::Node {
/**
* Holds if `pred` &rarr; `succ` should be considered a data flow edge.
*/
cached
predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
/**
* Holds if `pred` &rarr; `succ` should be considered a data flow edge
* transforming values with label `predlbl` to have label `succlbl`.
*/
cached
predicate step(
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predlbl,
DataFlow::FlowLabel succlbl
@@ -577,7 +583,6 @@ abstract class AdditionalFlowStep extends DataFlow::Node {
* Holds if `pred` should be stored in the object `succ` under the property `prop`.
* The object `succ` must be a `DataFlow::SourceNode` for the object wherein the value is stored.
*/
cached
predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
/**
@@ -585,7 +590,6 @@ abstract class AdditionalFlowStep extends DataFlow::Node {
*
* Holds if the property `prop` of the object `pred` should be loaded into `succ`.
*/
cached
predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
/**
@@ -593,7 +597,6 @@ abstract class AdditionalFlowStep extends DataFlow::Node {
*
* Holds if the property `prop` should be copied from the object `pred` to the object `succ`.
*/
cached
predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
/**
@@ -601,12 +604,10 @@ abstract class AdditionalFlowStep extends DataFlow::Node {
*
* Holds if the property `loadProp` should be copied from the object `pred` to the property `storeProp` of object `succ`.
*/
cached
predicate loadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
) {
loadProp = storeProp and
loadStoreStep(pred, succ, loadProp)
none()
}
}
@@ -636,28 +637,133 @@ class SharedFlowStep extends Unit {
) {
none()
}
/**
* Holds if `pred` should be stored in the object `succ` under the property `prop`.
* The object `succ` must be a `DataFlow::SourceNode` for the object wherein the value is stored.
*/
predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
/**
* Holds if the property `prop` of the object `pred` should be loaded into `succ`.
*/
predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
/**
* Holds if the property `prop` should be copied from the object `pred` to the object `succ`.
*/
predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
/**
* Holds if the property `loadProp` should be copied from the object `pred` to the property `storeProp` of object `succ`.
*/
predicate loadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
) {
none()
}
}
/**
* Contributes subclasses of `SharedFlowStep` to `AdditionalFlowStep`.
*
* This is a placeholder until we migrate to the `SharedFlowStep` class and deprecate `AdditionalFlowStep`.
* Contains predicates for accessing the steps contributed by `SharedFlowStep` subclasses.
*/
private class SharedStepAsAdditionalFlowStep extends AdditionalFlowStep {
SharedStepAsAdditionalFlowStep() {
any(SharedFlowStep st).step(_, this) or
any(SharedFlowStep st).step(_, this, _, _)
cached
module SharedFlowStep {
cached
private module Internal {
// Forces this to be part of the `FlowSteps` stage.
// We use a public predicate in a private module to avoid warnings about this being unused.
cached
predicate forceStage() { Stages::FlowSteps::ref() }
}
/**
* Holds if `pred` &rarr; `succ` should be considered a data flow edge.
*/
cached
predicate step(DataFlow::Node pred, DataFlow::Node succ) {
any(SharedFlowStep s).step(pred, succ)
}
/**
* Holds if `pred` &rarr; `succ` should be considered a data flow edge
* transforming values with label `predlbl` to have label `succlbl`.
*/
cached
predicate step(
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predlbl,
DataFlow::FlowLabel succlbl
) {
any(SharedFlowStep s).step(pred, succ, predlbl, succlbl)
}
/**
* Holds if `pred` should be stored in the object `succ` under the property `prop`.
* The object `succ` must be a `DataFlow::SourceNode` for the object wherein the value is stored.
*/
cached
predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
any(SharedFlowStep s).storeStep(pred, succ, prop)
}
/**
* Holds if the property `prop` of the object `pred` should be loaded into `succ`.
*/
cached
predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
any(SharedFlowStep s).loadStep(pred, succ, prop)
}
/**
* Holds if the property `prop` should be copied from the object `pred` to the object `succ`.
*/
cached
predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
any(SharedFlowStep s).loadStoreStep(pred, succ, prop)
}
/**
* Holds if the property `loadProp` should be copied from the object `pred` to the property `storeProp` of object `succ`.
*/
cached
predicate loadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
) {
any(SharedFlowStep s).loadStoreStep(pred, succ, loadProp, storeProp)
}
}
/**
* Contributes subclasses of `AdditionalFlowStep` to `SharedFlowStep`.
*/
private class AdditionalFlowStepAsSharedStep extends SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
any(SharedFlowStep st).step(pred, succ) and succ = this
any(LegacyAdditionalFlowStep s).step(pred, succ)
}
override predicate step(
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predlbl,
DataFlow::FlowLabel succlbl
) {
any(SharedFlowStep st).step(pred, succ, predlbl, succlbl) and succ = this
any(LegacyAdditionalFlowStep s).step(pred, succ, predlbl, succlbl)
}
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
any(LegacyAdditionalFlowStep s).storeStep(pred, succ, prop)
}
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
any(LegacyAdditionalFlowStep s).loadStep(pred, succ, prop)
}
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
any(LegacyAdditionalFlowStep s).loadStoreStep(pred, succ, prop)
}
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
) {
any(LegacyAdditionalFlowStep s).loadStoreStep(pred, succ, loadProp, storeProp)
}
}
@@ -666,7 +772,7 @@ private class SharedStepAsAdditionalFlowStep extends AdditionalFlowStep {
*
* A pseudo-property represents the location where some value is stored in an object.
*
* For use with load/store steps in `DataFlow::AdditionalFlowStep` and TypeTracking.
* For use with load/store steps in `DataFlow::SharedFlowStep` and TypeTracking.
*/
module PseudoProperties {
bindingset[s]
@@ -782,13 +888,12 @@ abstract class AdditionalSink extends DataFlow::Node {
* Additional flow step to model flow from import specifiers into the SSA variable
* corresponding to the imported variable.
*/
private class FlowStepThroughImport extends AdditionalFlowStep, DataFlow::ValueNode {
override ImportSpecifier astNode;
private class FlowStepThroughImport extends SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
Stages::FlowSteps::ref() and
pred = this and
succ = DataFlow::ssaDefinitionNode(SSA::definition(astNode))
exists(ImportSpecifier specifier |
pred = DataFlow::valueNode(specifier) and
succ = DataFlow::ssaDefinitionNode(SSA::definition(specifier))
)
}
}
@@ -1202,7 +1307,7 @@ private predicate reachesReturn(
private predicate isAdditionalLoadStep(
DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg
) {
any(AdditionalFlowStep s).loadStep(pred, succ, prop)
SharedFlowStep::loadStep(pred, succ, prop)
or
cfg.isAdditionalLoadStep(pred, succ, prop)
}
@@ -1213,7 +1318,7 @@ private predicate isAdditionalLoadStep(
private predicate isAdditionalStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg
) {
any(AdditionalFlowStep s).storeStep(pred, succ, prop)
SharedFlowStep::storeStep(pred, succ, prop)
or
cfg.isAdditionalStoreStep(pred, succ, prop)
}
@@ -1225,13 +1330,13 @@ private predicate isAdditionalLoadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp,
DataFlow::Configuration cfg
) {
any(AdditionalFlowStep s).loadStoreStep(pred, succ, loadProp, storeProp)
SharedFlowStep::loadStoreStep(pred, succ, loadProp, storeProp)
or
cfg.isAdditionalLoadStoreStep(pred, succ, loadProp, storeProp)
or
loadProp = storeProp and
(
any(AdditionalFlowStep s).loadStoreStep(pred, succ, loadProp)
SharedFlowStep::loadStoreStep(pred, succ, loadProp)
or
cfg.isAdditionalLoadStoreStep(pred, succ, loadProp)
)
@@ -1356,19 +1461,20 @@ private predicate summarizedHigherOrderCall(
DataFlow::Node arg, DataFlow::Node cb, int i, DataFlow::Configuration cfg, PathSummary summary
) {
exists(
Function f, DataFlow::InvokeNode outer, DataFlow::InvokeNode inner, int j,
DataFlow::Node innerArg, DataFlow::SourceNode cbParm, PathSummary oldSummary
Function f, DataFlow::InvokeNode inner, int j, DataFlow::Node innerArg,
DataFlow::SourceNode cbParm, PathSummary oldSummary
|
// Captured flow does not need to be summarized - it is handled by the local case in `higherOrderCall`.
not arg = DataFlow::capturedVariableNode(_) and
summarizedHigherOrderCallAux(f, outer, arg, innerArg, cfg, oldSummary, cbParm, inner, j, cb)
not arg = DataFlow::capturedVariableNode(_)
|
// direct higher-order call
cbParm.flowsTo(inner.getCalleeNode()) and
summarizedHigherOrderCallAux(f, arg, innerArg, cfg, oldSummary, cbParm, inner, j, cb) and
inner = cbParm.getAnInvocation() and
i = j and
summary = oldSummary
or
// indirect higher-order call
summarizedHigherOrderCallAux(f, arg, innerArg, cfg, oldSummary, cbParm, inner, j, cb) and
exists(DataFlow::Node cbArg, PathSummary newSummary |
cbParm.flowsTo(cbArg) and
summarizedHigherOrderCall(innerArg, cbArg, i, cfg, newSummary) and
@@ -1382,14 +1488,17 @@ private predicate summarizedHigherOrderCall(
*/
pragma[noinline]
private predicate summarizedHigherOrderCallAux(
Function f, DataFlow::InvokeNode outer, DataFlow::Node arg, DataFlow::Node innerArg,
DataFlow::Configuration cfg, PathSummary oldSummary, DataFlow::SourceNode cbParm,
DataFlow::InvokeNode inner, int j, DataFlow::Node cb
Function f, DataFlow::Node arg, DataFlow::Node innerArg, DataFlow::Configuration cfg,
PathSummary oldSummary, DataFlow::SourceNode cbParm, DataFlow::InvokeNode inner, int j,
DataFlow::Node cb
) {
reachableFromInput(f, outer, arg, innerArg, cfg, oldSummary) and
// Only track actual parameter flow.
argumentPassing(outer, cb, f, cbParm) and
innerArg = inner.getArgument(j)
exists(DataFlow::Node outer1, DataFlow::Node outer2 |
reachableFromInput(f, outer1, arg, innerArg, cfg, oldSummary) and
outer1 = pragma[only_bind_into](outer2) and
// Only track actual parameter flow.
argumentPassing(outer2, cb, f, cbParm) and
innerArg = inner.getArgument(j)
)
}
/**

View File

@@ -531,6 +531,13 @@ module DataFlow {
predicate isPrivateField() {
getPropertyName().charAt(0) = "#" and getPropertyNameExpr() instanceof Label
}
/**
* Gets an accessor (`get` or `set` method) that may be invoked by this property reference.
*/
final DataFlow::FunctionNode getAnAccessorCallee() {
result = CallGraph::getAnAccessorCallee(this)
}
}
/**

View File

@@ -59,18 +59,16 @@ class ParameterNode extends DataFlow::SourceNode {
* ```
*/
class InvokeNode extends DataFlow::SourceNode {
DataFlow::Impl::InvokeNodeDef impl;
InvokeNode() { this = impl }
InvokeNode() { this instanceof DataFlow::Impl::InvokeNodeDef }
/** Gets the syntactic invoke expression underlying this function invocation. */
InvokeExpr getInvokeExpr() { result = impl.getInvokeExpr() }
InvokeExpr getInvokeExpr() { result = this.(DataFlow::Impl::InvokeNodeDef).getInvokeExpr() }
/** Gets the name of the function or method being invoked, if it can be determined. */
string getCalleeName() { result = impl.getCalleeName() }
string getCalleeName() { result = this.(DataFlow::Impl::InvokeNodeDef).getCalleeName() }
/** Gets the data flow node specifying the function to be called. */
DataFlow::Node getCalleeNode() { result = impl.getCalleeNode() }
DataFlow::Node getCalleeNode() { result = this.(DataFlow::Impl::InvokeNodeDef).getCalleeNode() }
/**
* Gets the data flow node corresponding to the `i`th argument of this invocation.
@@ -91,10 +89,10 @@ class InvokeNode extends DataFlow::SourceNode {
* but the position of `z` cannot be determined, hence there are no first and second
* argument nodes.
*/
DataFlow::Node getArgument(int i) { result = impl.getArgument(i) }
DataFlow::Node getArgument(int i) { result = this.(DataFlow::Impl::InvokeNodeDef).getArgument(i) }
/** Gets the data flow node corresponding to an argument of this invocation. */
DataFlow::Node getAnArgument() { result = impl.getAnArgument() }
DataFlow::Node getAnArgument() { result = this.(DataFlow::Impl::InvokeNodeDef).getAnArgument() }
/** Gets the data flow node corresponding to the last argument of this invocation. */
DataFlow::Node getLastArgument() { result = getArgument(getNumArgument() - 1) }
@@ -111,10 +109,12 @@ class InvokeNode extends DataFlow::SourceNode {
* ```
* .
*/
DataFlow::Node getASpreadArgument() { result = impl.getASpreadArgument() }
DataFlow::Node getASpreadArgument() {
result = this.(DataFlow::Impl::InvokeNodeDef).getASpreadArgument()
}
/** Gets the number of arguments of this invocation, if it can be determined. */
int getNumArgument() { result = impl.getNumArgument() }
int getNumArgument() { result = this.(DataFlow::Impl::InvokeNodeDef).getNumArgument() }
Function getEnclosingFunction() { result = getBasicBlock().getContainer() }
@@ -256,14 +256,14 @@ class InvokeNode extends DataFlow::SourceNode {
* ```
*/
class CallNode extends InvokeNode {
override DataFlow::Impl::CallNodeDef impl;
CallNode() { this instanceof DataFlow::Impl::CallNodeDef }
/**
* Gets the data flow node corresponding to the receiver expression of this method call.
*
* For example, the receiver of `x.m()` is `x`.
*/
DataFlow::Node getReceiver() { result = impl.getReceiver() }
DataFlow::Node getReceiver() { result = this.(DataFlow::Impl::CallNodeDef).getReceiver() }
}
/**
@@ -277,10 +277,10 @@ class CallNode extends InvokeNode {
* ```
*/
class MethodCallNode extends CallNode {
override DataFlow::Impl::MethodCallNodeDef impl;
MethodCallNode() { this instanceof DataFlow::Impl::MethodCallNodeDef }
/** Gets the name of the invoked method, if it can be determined. */
string getMethodName() { result = impl.getMethodName() }
string getMethodName() { result = this.(DataFlow::Impl::MethodCallNodeDef).getMethodName() }
/**
* Holds if this data flow node calls method `methodName` on receiver node `receiver`.
@@ -300,7 +300,7 @@ class MethodCallNode extends CallNode {
* ```
*/
class NewNode extends InvokeNode {
override DataFlow::Impl::NewNodeDef impl;
NewNode() { this instanceof DataFlow::Impl::NewNodeDef }
}
/**
@@ -546,6 +546,16 @@ class ObjectLiteralNode extends DataFlow::ValueNode, DataFlow::SourceNode {
DataFlow::Node getASpreadProperty() {
result = astNode.getAProperty().(SpreadProperty).getInit().(SpreadElement).getOperand().flow()
}
/** Gets the property getter of the given name, installed on this object literal. */
DataFlow::FunctionNode getPropertyGetter(string name) {
result = astNode.getPropertyByName(name).(PropertyGetter).getInit().flow()
}
/** Gets the property setter of the given name, installed on this object literal. */
DataFlow::FunctionNode getPropertySetter(string name) {
result = astNode.getPropertyByName(name).(PropertySetter).getInit().flow()
}
}
/**
@@ -1624,6 +1634,9 @@ class RegExpCreationNode extends DataFlow::SourceNode {
result = this.(RegExpLiteralNode).getFlags()
}
/** Holds if the constructed predicate has the `g` flag. */
predicate isGlobal() { RegExp::isGlobal(getFlags()) }
/** Gets a data flow node referring to this regular expression. */
private DataFlow::SourceNode getAReference(DataFlow::TypeTracker t) {
t.start() and

View File

@@ -697,10 +697,28 @@ module TaintTracking {
name = "encodeURIComponent" or
name = "decodeURIComponent"
)
or
// In and out of .replace callbacks
exists(StringReplaceCall call |
// Into the callback if the regexp does not sanitize matches
hasWildcardReplaceRegExp(call) and
pred = call.getReceiver() and
succ = call.getReplacementCallback().getParameter(0)
or
// Out of the callback
pred = call.getReplacementCallback().getReturnNode() and
succ = call
)
)
}
}
/** Holds if the given call takes a regexp containing a wildcard. */
pragma[noinline]
private predicate hasWildcardReplaceRegExp(StringReplaceCall call) {
RegExp::isWildcardLike(call.getRegExp().getRoot().getAChild*())
}
/**
* A taint propagating data flow edge arising from string formatting.
*/
@@ -810,13 +828,13 @@ module TaintTracking {
/**
* A taint propagating data flow edge arising from URL parameter parsing.
*/
private class UrlSearchParamsTaintStep extends DataFlow::AdditionalFlowStep, DataFlow::ValueNode {
private class UrlSearchParamsTaintStep extends DataFlow::SharedFlowStep {
/**
* Holds if `succ` is a `URLSearchParams` providing access to the
* parameters encoded in `pred`.
*/
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
isUrlSearchParams(succ, pred) and succ = this
isUrlSearchParams(succ, pred)
}
/**
@@ -829,17 +847,14 @@ module TaintTracking {
* which can be accessed using a `get` or `getAll` call. (See getableUrlPseudoProperty())
*/
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
succ = this and
(
prop = ["searchParams", "hash", "search", hiddenUrlPseudoProperty()] and
exists(DataFlow::NewNode newUrl | succ = newUrl |
newUrl = DataFlow::globalVarRef("URL").getAnInstantiation() and
pred = newUrl.getArgument(0)
)
or
prop = getableUrlPseudoProperty() and
isUrlSearchParams(succ, pred)
prop = ["searchParams", "hash", "search", hiddenUrlPseudoProperty()] and
exists(DataFlow::NewNode newUrl | succ = newUrl |
newUrl = DataFlow::globalVarRef("URL").getAnInstantiation() and
pred = newUrl.getArgument(0)
)
or
prop = getableUrlPseudoProperty() and
isUrlSearchParams(succ, pred)
}
/**
@@ -851,7 +866,6 @@ module TaintTracking {
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
) {
succ = this and
loadProp = hiddenUrlPseudoProperty() and
storeProp = getableUrlPseudoProperty() and
exists(DataFlow::PropRead read | read = succ |
@@ -866,7 +880,6 @@ module TaintTracking {
* This step is used to load the value stored in the pseudo-property `getableUrlPseudoProperty()`.
*/
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
succ = this and
prop = getableUrlPseudoProperty() and
// this is a call to `get` or `getAll` on a `URLSearchParams` object
exists(string m, DataFlow::MethodCallNode call | call = succ |

View File

@@ -102,7 +102,7 @@ private module NodeTracking {
predicate localFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
pred = succ.getAPredecessor()
or
any(DataFlow::AdditionalFlowStep afs).step(pred, succ)
DataFlow::SharedFlowStep::step(pred, succ)
or
localExceptionStep(pred, succ)
}

View File

@@ -9,6 +9,7 @@
private import javascript
private import internal.FlowSteps
private import internal.StepSummary
private import internal.Unit
private import semmle.javascript.internal.CachedStages
private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalPropertyName prop)
@@ -252,6 +253,12 @@ class TypeBackTracker extends TTypeBackTracker {
*/
predicate start() { hasReturn = false and prop = "" }
/**
* Holds if this is the starting point of type backtracking, and the value is in the property named `propName`.
* The type tracking only ends after the property has been stored.
*/
predicate isInProp(PropertyName propName) { hasReturn = false and prop = propName }
/**
* Holds if this is the end point of type tracking.
*/
@@ -328,6 +335,89 @@ module TypeBackTracker {
}
/**
* A data flow edge that should be followed by type tracking.
*
* Unlike `SharedFlowStep`, this type of edge does not affect
* the local data flow graph, and is not used by data-flow configurations.
*
* Note: For performance reasons, all subclasses of this class should be part
* of the standard library. For query-specific steps, consider including the
* custom steps in the type-tracking predicate itself.
*/
class SharedTypeTrackingStep extends Unit {
/**
* Holds if type-tracking should step from `pred` to `succ`.
*/
predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
/**
* Holds if type-tracking should step from `pred` into the `prop` property of `succ`.
*/
predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
/**
* Holds if type-tracking should step from the `prop` property of `pred` to `succ`.
*/
predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
/**
* Holds if type-tracking should step from the `prop` property of `pred` to the same property in `succ`.
*/
predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
/**
* Holds if type-tracking should step from the `loadProp` property of `pred` to the `storeProp` property in `succ`.
*/
predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string loadProp, string storeProp
) {
none()
}
}
/** Provides access to the steps contributed by subclasses of `SharedTypeTrackingStep`. */
module SharedTypeTrackingStep {
/**
* Holds if type-tracking should step from `pred` to `succ`.
*/
predicate step(DataFlow::Node pred, DataFlow::Node succ) {
any(SharedTypeTrackingStep s).step(pred, succ)
}
/**
* Holds if type-tracking should step from `pred` into the `prop` property of `succ`.
*/
predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
any(SharedTypeTrackingStep s).storeStep(pred, succ, prop)
}
/**
* Holds if type-tracking should step from the `prop` property of `pred` to `succ`.
*/
predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
any(SharedTypeTrackingStep s).loadStep(pred, succ, prop)
}
/**
* Holds if type-tracking should step from the `prop` property of `pred` to the same property in `succ`.
*/
predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
any(SharedTypeTrackingStep s).loadStoreStep(pred, succ, prop)
}
/**
* Holds if type-tracking should step from the `loadProp` property of `pred` to the `storeProp` property in `succ`.
*/
predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string loadProp, string storeProp
) {
any(SharedTypeTrackingStep s).loadStoreStep(pred, succ, loadProp, storeProp)
}
}
/**
* DEPRECATED. Use `SharedTypeTrackingStep` instead.
*
* A data flow edge that should be followed by type tracking.
*
* Unlike `AdditionalFlowStep`, this type of edge does not affect
@@ -337,7 +427,10 @@ module TypeBackTracker {
* of the standard library. For query-specific steps, consider including the
* custom steps in the type-tracking predicate itself.
*/
abstract class AdditionalTypeTrackingStep extends DataFlow::Node {
deprecated class AdditionalTypeTrackingStep = LegacyTypeTrackingStep;
// Internal version of AdditionalTypeTrackingStep that we can reference without deprecation warnings.
abstract private class LegacyTypeTrackingStep extends DataFlow::Node {
/**
* Holds if type-tracking should step from `pred` to `succ`.
*/
@@ -358,3 +451,21 @@ abstract class AdditionalTypeTrackingStep extends DataFlow::Node {
*/
predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
}
private class LegacyStepAsSharedTypeTrackingStep extends SharedTypeTrackingStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
any(LegacyTypeTrackingStep s).step(pred, succ)
}
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
any(LegacyTypeTrackingStep s).storeStep(pred, succ, prop)
}
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
any(LegacyTypeTrackingStep s).loadStep(pred, succ, prop)
}
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
any(LegacyTypeTrackingStep s).loadStoreStep(pred, succ, prop)
}
}

View File

@@ -162,4 +162,46 @@ module CallGraph {
)
)
}
/** Holds if a property setter named `name` exists in a class. */
private predicate isSetterName(string name) {
exists(any(DataFlow::ClassNode cls).getInstanceMember(name, DataFlow::MemberKind::setter()))
}
/**
* Gets a property write that assigns to the property `name` on an instance of this class,
* and `name` is the name of a property setter.
*/
private DataFlow::PropWrite getAnInstanceMemberAssignment(DataFlow::ClassNode cls, string name) {
isSetterName(name) and // restrict size of predicate
result = cls.getAnInstanceReference().getAPropertyWrite(name)
or
exists(DataFlow::ClassNode subclass |
result = getAnInstanceMemberAssignment(subclass, name) and
not exists(subclass.getInstanceMember(name, DataFlow::MemberKind::setter())) and
cls = subclass.getADirectSuperClass()
)
}
/**
* Gets a getter or setter invoked as a result of the given property access.
*/
cached
DataFlow::FunctionNode getAnAccessorCallee(DataFlow::PropRef ref) {
exists(DataFlow::ClassNode cls, string name |
ref = cls.getAnInstanceMemberAccess(name) and
result = cls.getInstanceMember(name, DataFlow::MemberKind::getter())
or
ref = getAnInstanceMemberAssignment(cls, name) and
result = cls.getInstanceMember(name, DataFlow::MemberKind::setter())
)
or
exists(DataFlow::ObjectLiteralNode object, string name |
ref = object.getAPropertyRead(name) and
result = object.getPropertyGetter(name)
or
ref = object.getAPropertyWrite(name) and
result = object.getPropertySetter(name)
)
}
}

View File

@@ -25,7 +25,9 @@ predicate shouldTrackProperties(AbstractValue obj) {
*/
pragma[noinline]
predicate returnExpr(Function f, DataFlow::Node source, DataFlow::Node sink) {
sink.asExpr() = f.getAReturnedExpr() and source = sink
sink.asExpr() = f.getAReturnedExpr() and
source = sink and
not f = any(SetterMethodDeclaration decl).getBody()
}
/**
@@ -39,9 +41,9 @@ predicate localFlowStep(
) {
pred = succ.getAPredecessor() and predlbl = succlbl
or
any(DataFlow::AdditionalFlowStep afs).step(pred, succ) and predlbl = succlbl
DataFlow::SharedFlowStep::step(pred, succ) and predlbl = succlbl
or
any(DataFlow::AdditionalFlowStep afs).step(pred, succ, predlbl, succlbl)
DataFlow::SharedFlowStep::step(pred, succ, predlbl, succlbl)
or
exists(boolean vp | configuration.isAdditionalFlowStep(pred, succ, vp) |
vp = true and
@@ -120,7 +122,11 @@ private module CachedSteps {
* Holds if `invk` may invoke `f`.
*/
cached
predicate calls(DataFlow::InvokeNode invk, Function f) { f = invk.getACallee(0) }
predicate calls(DataFlow::SourceNode invk, Function f) {
f = invk.(DataFlow::InvokeNode).getACallee(0)
or
f = invk.(DataFlow::PropRef).getAnAccessorCallee().getFunction()
}
private predicate callsBoundInternal(
DataFlow::InvokeNode invk, Function f, int boundArgs, boolean contextDependent
@@ -177,11 +183,11 @@ private module CachedSteps {
*/
cached
predicate argumentPassing(
DataFlow::InvokeNode invk, DataFlow::ValueNode arg, Function f, DataFlow::SourceNode parm
DataFlow::SourceNode invk, DataFlow::Node arg, Function f, DataFlow::SourceNode parm
) {
calls(invk, f) and
(
exists(int i | arg = invk.getArgument(i) |
exists(int i | arg = invk.(DataFlow::InvokeNode).getArgument(i) |
exists(Parameter p |
f.getParameter(i) = p and
not p.isRestParameter() and
@@ -193,12 +199,19 @@ private module CachedSteps {
or
arg = invk.(DataFlow::CallNode).getReceiver() and
parm = DataFlow::thisNode(f)
or
arg = invk.(DataFlow::PropRef).getBase() and
parm = DataFlow::thisNode(f)
or
arg = invk.(DataFlow::PropWrite).getRhs() and
parm = DataFlow::parameterNode(f.getParameter(0))
)
or
exists(DataFlow::Node callback, int i, Parameter p |
exists(DataFlow::Node callback, int i, Parameter p, Function target |
invk.(DataFlow::PartialInvokeNode).isPartialArgument(callback, arg, i) and
partiallyCalls(invk, callback, f) and
f.getParameter(i) = p and
f = pragma[only_bind_into](target) and
target.getParameter(i) = p and
not p.isRestParameter() and
parm = DataFlow::parameterNode(p)
)
@@ -213,7 +226,7 @@ private module CachedSteps {
callsBound(invk, f, boundArgs) and
f.getParameter(boundArgs + i) = p and
not p.isRestParameter() and
arg = invk.getArgument(i) and
arg = invk.(DataFlow::InvokeNode).getArgument(i) and
parm = DataFlow::parameterNode(p)
)
}

View File

@@ -16,7 +16,7 @@ private class Unit extends TUnit {
* Internal extension point for adding flow edges prior to call graph construction
* and type tracking.
*
* Steps added here will be added to both `AdditionalFlowStep` and `AdditionalTypeTrackingStep`.
* Steps added here will be added to both `SharedFlowStep` and `SharedTypeTrackingStep`.
*
* Contributing steps that rely on type tracking will lead to negative recursion.
*/
@@ -40,6 +40,15 @@ class PreCallGraphStep extends Unit {
* Holds if there is a step from the `prop` property of `pred` to the same property in `succ`.
*/
predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
/**
* Holds if there is a step from the `loadProp` property of `pred` to the `storeProp` property in `succ`.
*/
predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string loadProp, string storeProp
) {
none()
}
}
module PreCallGraphStep {
@@ -75,62 +84,61 @@ module PreCallGraphStep {
predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
any(PreCallGraphStep s).loadStoreStep(pred, succ, prop)
}
}
private class NodeWithPreCallGraphStep extends DataFlow::Node {
NodeWithPreCallGraphStep() {
PreCallGraphStep::step(this, _)
or
PreCallGraphStep::storeStep(this, _, _)
or
PreCallGraphStep::loadStep(this, _, _)
or
PreCallGraphStep::loadStoreStep(this, _, _)
/**
* Holds if there is a step from the `loadProp` property of `pred` to the `storeProp` property in `succ`.
*/
predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string loadProp, string storeProp
) {
any(PreCallGraphStep s).loadStoreStep(pred, succ, loadProp, storeProp)
}
}
private class AdditionalFlowStepFromPreCallGraph extends NodeWithPreCallGraphStep,
DataFlow::AdditionalFlowStep {
private class SharedFlowStepFromPreCallGraph extends DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = this and
PreCallGraphStep::step(this, succ)
PreCallGraphStep::step(pred, succ)
}
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
pred = this and
PreCallGraphStep::storeStep(this, succ, prop)
PreCallGraphStep::storeStep(pred, succ, prop)
}
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
pred = this and
PreCallGraphStep::loadStep(this, succ, prop)
PreCallGraphStep::loadStep(pred, succ, prop)
}
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
pred = this and
PreCallGraphStep::loadStoreStep(this, succ, prop)
PreCallGraphStep::loadStoreStep(pred, succ, prop)
}
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
) {
PreCallGraphStep::loadStoreStep(pred, succ, loadProp, storeProp)
}
}
private class AdditionalTypeTrackingStepFromPreCallGraph extends NodeWithPreCallGraphStep,
DataFlow::AdditionalTypeTrackingStep {
private class SharedTypeTrackingStepFromPreCallGraph extends DataFlow::SharedTypeTrackingStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = this and
PreCallGraphStep::step(this, succ)
PreCallGraphStep::step(pred, succ)
}
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
pred = this and
PreCallGraphStep::storeStep(this, succ, prop)
PreCallGraphStep::storeStep(pred, succ, prop)
}
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
pred = this and
PreCallGraphStep::loadStep(this, succ, prop)
PreCallGraphStep::loadStep(pred, succ, prop)
}
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
pred = this and
PreCallGraphStep::loadStoreStep(this, succ, prop)
PreCallGraphStep::loadStoreStep(pred, succ, prop)
}
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string loadProp, string storeProp
) {
PreCallGraphStep::loadStoreStep(pred, succ, loadProp, storeProp)
}
}

View File

@@ -1,50 +1,66 @@
import javascript
private import semmle.javascript.dataflow.TypeTracking
private import semmle.javascript.internal.CachedStages
private import FlowSteps
class PropertyName extends string {
PropertyName() {
this = any(DataFlow::PropRef pr).getPropertyName()
or
AccessPath::isAssignedInUniqueFile(this)
or
exists(AccessPath::getAnAssignmentTo(_, this))
or
this instanceof TypeTrackingPseudoProperty
cached
private module Cached {
cached
module Public {
cached
predicate forceStage() { Stages::TypeTracking::ref() }
cached
class PropertyName extends string {
cached
PropertyName() {
this = any(DataFlow::PropRef pr).getPropertyName()
or
AccessPath::isAssignedInUniqueFile(this)
or
exists(AccessPath::getAnAssignmentTo(_, this))
or
SharedTypeTrackingStep::loadStep(_, _, this)
or
SharedTypeTrackingStep::storeStep(_, _, this)
or
SharedTypeTrackingStep::loadStoreStep(_, _, this, _)
or
SharedTypeTrackingStep::loadStoreStep(_, _, _, this)
}
}
/**
* A description of a step on an inter-procedural data flow path.
*/
cached
newtype TStepSummary =
LevelStep() or
CallStep() or
ReturnStep() or
StoreStep(PropertyName prop) or
LoadStep(PropertyName prop) or
CopyStep(PropertyName prop) or
LoadStoreStep(PropertyName fromProp, PropertyName toProp) {
SharedTypeTrackingStep::loadStoreStep(_, _, fromProp, toProp)
}
}
/**
* INTERNAL: Use `SourceNode.track()` or `SourceNode.backtrack()` instead.
*/
cached
predicate step(DataFlow::SourceNode pred, DataFlow::SourceNode succ, StepSummary summary) {
exists(DataFlow::Node mid | pred.flowsTo(mid) | StepSummary::smallstep(mid, succ, summary))
}
}
import Cached::Public
class OptionalPropertyName extends string {
OptionalPropertyName() { this instanceof PropertyName or this = "" }
}
/**
* A pseudo-property that can be used in type-tracking.
*/
abstract class TypeTrackingPseudoProperty extends string {
bindingset[this]
TypeTrackingPseudoProperty() { any() }
/**
* Gets a property name that `this` can be copied to in a `LoadStoreStep(this, result)`.
*/
string getLoadStoreToProp() { none() }
}
/**
* A description of a step on an inter-procedural data flow path.
*/
newtype TStepSummary =
LevelStep() or
CallStep() or
ReturnStep() or
StoreStep(PropertyName prop) or
LoadStep(PropertyName prop) or
CopyStep(PropertyName prop) or
LoadStoreStep(PropertyName fromProp, PropertyName toProp) {
exists(TypeTrackingPseudoProperty prop | fromProp = prop and toProp = prop.getLoadStoreToProp())
}
/**
* INTERNAL: Use `TypeTracker` or `TypeBackTracker` instead.
*
@@ -75,10 +91,7 @@ module StepSummary {
/**
* INTERNAL: Use `SourceNode.track()` or `SourceNode.backtrack()` instead.
*/
cached
predicate step(DataFlow::SourceNode pred, DataFlow::SourceNode succ, StepSummary summary) {
exists(DataFlow::Node mid | pred.flowsTo(mid) | smallstep(mid, succ, summary))
}
predicate step = Cached::step/3;
/**
* INTERNAL: Use `TypeBackTracker.smallstep()` instead.
@@ -111,17 +124,22 @@ module StepSummary {
basicLoadStep(pred, succ, prop) and
summary = LoadStep(prop)
or
any(AdditionalTypeTrackingStep st).storeStep(pred, succ, prop) and
SharedTypeTrackingStep::storeStep(pred, succ, prop) and
summary = StoreStep(prop)
or
any(AdditionalTypeTrackingStep st).loadStep(pred, succ, prop) and
SharedTypeTrackingStep::loadStep(pred, succ, prop) and
summary = LoadStep(prop)
or
any(AdditionalTypeTrackingStep st).loadStoreStep(pred, succ, prop) and
SharedTypeTrackingStep::loadStoreStep(pred, succ, prop) and
summary = CopyStep(prop)
)
or
any(AdditionalTypeTrackingStep st).step(pred, succ) and
exists(string fromProp, string toProp |
SharedTypeTrackingStep::loadStoreStep(pred, succ, fromProp, toProp) and
summary = LoadStoreStep(fromProp, toProp)
)
or
SharedTypeTrackingStep::step(pred, succ) and
summary = LevelStep()
or
// Store to global access path

View File

@@ -194,26 +194,22 @@ module EventDispatch {
}
/**
* A taint-step that models data-flow between event handlers and event dispatchers.
* A flow-step that models data-flow between event handlers and event dispatchers.
*/
private class EventEmitterTaintStep extends DataFlow::AdditionalFlowStep {
EventRegistration reg;
EventDispatch dispatch;
EventEmitterTaintStep() {
this = dispatch and
reg = dispatch.getAReceiver() and
not dispatch.getChannel() != reg.getChannel()
}
private class EventEmitterFlowStep extends DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(int i | i >= 0 |
pred = dispatch.getSentItem(i) and
succ = reg.getReceivedItem(i)
exists(EventRegistration reg, EventDispatch dispatch |
reg = dispatch.getAReceiver() and
not dispatch.getChannel() != reg.getChannel()
|
exists(int i | i >= 0 |
pred = dispatch.getSentItem(i) and
succ = reg.getReceivedItem(i)
)
or
dispatch = reg.getAReturnDispatch() and
pred = reg.getAReturnedValue() and
succ = dispatch
)
or
dispatch = reg.getAReturnDispatch() and
pred = reg.getAReturnedValue() and
succ = dispatch
}
}

View File

@@ -6,6 +6,7 @@ import javascript
private import semmle.javascript.DynamicPropertyAccess
private import semmle.javascript.dataflow.internal.StepSummary
private import semmle.javascript.dataflow.internal.CallGraphs
private import DataFlow::PseudoProperties as PseudoProperties
module HTTP {
/**
@@ -689,33 +690,30 @@ module HTTP {
isDecoratedCall(result, candidate)
}
private string mapValueProp() {
result = [PseudoProperties::mapValueAll(), PseudoProperties::mapValueUnknownKey()]
}
/**
* A collection that contains one or more route potential handlers.
*/
private class ContainerCollection extends HTTP::RouteHandlerCandidateContainer::Range {
private class ContainerCollection extends HTTP::RouteHandlerCandidateContainer::Range,
DataFlow::NewNode {
ContainerCollection() {
this = DataFlow::globalVarRef("Map").getAnInstantiation() and // restrict to Map for now
exists(
CollectionFlowStep store, DataFlow::Node storeTo, DataFlow::Node input,
RouteHandlerCandidate candidate
|
this.flowsTo(storeTo) and
store.store(input, storeTo, _) and
candidate.flowsTo(input)
exists(DataFlow::Node use |
DataFlow::SharedTypeTrackingStep::storeStep(use, this, mapValueProp()) and
use.getALocalSource() instanceof RouteHandlerCandidate
)
}
override DataFlow::SourceNode getRouteHandler(DataFlow::SourceNode access) {
result instanceof RouteHandlerCandidate and
exists(
DataFlow::Node input, TypeTrackingPseudoProperty key, CollectionFlowStep store,
CollectionFlowStep load, DataFlow::Node storeTo, DataFlow::Node loadFrom
|
this.flowsTo(storeTo) and
store.store(input, storeTo, key) and
exists(DataFlow::Node input, string key, DataFlow::Node loadFrom |
getAPossiblyDecoratedHandler(result).flowsTo(input) and
DataFlow::SharedTypeTrackingStep::storeStep(input, this, key) and
ref(this).flowsTo(loadFrom) and
load.load(loadFrom, access, key)
DataFlow::SharedTypeTrackingStep::loadStep(loadFrom, access,
[key, PseudoProperties::mapValueAll()])
)
}
}

View File

@@ -164,22 +164,15 @@ private module Immutable {
/**
* A dataflow step for an immutable collection.
*/
class ImmutableConstructionStep extends DataFlow::AdditionalFlowStep {
ImmutableConstructionStep() { this = [loadStep(_, _), storeStep(_, _), step(_)] }
class ImmutableConstructionStep extends DataFlow::SharedFlowStep {
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
this = loadStep(pred, prop) and
succ = this
succ = loadStep(pred, prop)
}
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
this = storeStep(pred, prop) and
succ = this
succ = storeStep(pred, prop)
}
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
this = step(pred) and
succ = this
}
override predicate step(DataFlow::Node pred, DataFlow::Node succ) { succ = step(pred) }
}
}

View File

@@ -36,18 +36,11 @@ module Koa {
/**
* A Koa route handler.
*/
class RouteHandler extends HTTP::Servers::StandardRouteHandler, DataFlow::ValueNode {
Function function;
RouteHandler() {
function = astNode and
any(RouteSetup setup).getARouteHandler() = this
}
abstract class RouteHandler extends HTTP::Servers::StandardRouteHandler, DataFlow::SourceNode {
/**
* Gets the parameter of the route handler that contains the context object.
*/
Parameter getContextParameter() { result = function.getParameter(0) }
Parameter getContextParameter() { result = getAFunctionValue().getFunction().getParameter(0) }
/**
* Gets an expression that contains the "context" object of
@@ -70,6 +63,35 @@ module Koa {
* object of a route handler invocation.
*/
Expr getARequestOrContextExpr() { result = getARequestExpr() or result = getAContextExpr() }
/**
* Gets a reference to a request parameter defined by this route handler.
*/
DataFlow::Node getARequestParameterAccess() {
none() // overriden in subclasses.
}
/**
* Gets a dataflow node that can be given to a `RouteSetup` to register the handler.
*/
abstract DataFlow::SourceNode getARouteHandlerRegistrationObject();
}
/**
* A koa route handler registered directly with a route-setup.
* Like:
* ```JavaScript
* var route = require('koa-route');
* var app = new Koa();
* app.use((context, next) => {
* ...
* });
* ```
*/
private class StandardRouteHandler extends RouteHandler {
StandardRouteHandler() { any(RouteSetup setup).getARouteHandler() = this }
override DataFlow::SourceNode getARouteHandlerRegistrationObject() { result = this }
}
/**
@@ -100,6 +122,77 @@ module Koa {
}
}
/**
* A Koa route handler registered using a routing library.
*
* Example of what that could look like:
* ```JavaScript
* const router = require('koa-router')();
* const Koa = require('koa');
* const app = new Koa();
* router.get('/', async (ctx, next) => {
* // route handler stuff
* });
* app.use(router.routes());
* ```
*/
private class RoutedRouteHandler extends RouteHandler {
DataFlow::InvokeNode router;
DataFlow::MethodCallNode call;
RoutedRouteHandler() {
router = DataFlow::moduleImport(["@koa/router", "koa-router"]).getAnInvocation() and
call =
router
.getAChainedMethodCall([
"use", "get", "post", "put", "link", "unlink", "delete", "del", "head", "options",
"patch", "all"
]) and
this.flowsTo(call.getArgument(any(int i | i >= 1)))
}
override DataFlow::SourceNode getARouteHandlerRegistrationObject() {
result = call
or
result = router.getAMethodCall("routes")
}
}
/**
* A route handler registered using the `koa-route` library.
*
* Example of how `koa-route` can be used:
* ```JavaScript
* var route = require('koa-route');
* var Koa = require('koa');
* var app = new Koa();
*
* app.use(route.get('/pets', (context, param1, param2, param3, ...params) => {
* // route handler stuff
* }));
*/
class KoaRouteHandler extends RouteHandler {
DataFlow::CallNode call;
KoaRouteHandler() {
call =
DataFlow::moduleMember("koa-route",
[
"all", "acl", "bind", "checkout", "connect", "copy", "delete", "del", "get", "head",
"link", "lock", "msearch", "merge", "mkactivity", "mkcalendar", "mkcol", "move",
"notify", "options", "patch", "post", "propfind", "proppatch", "purge", "put", "rebind",
"report", "search", "subscribe", "trace", "unbind", "unlink", "unlock", "unsubscribe"
]).getACall() and
this.flowsTo(call.getArgument(1))
}
override DataFlow::Node getARequestParameterAccess() {
result = call.getABoundCallbackParameter(1, any(int i | i >= 1))
}
override DataFlow::SourceNode getARouteHandlerRegistrationObject() { result = call }
}
/**
* A Koa request source, that is, an access to the `request` property
* of a context object.
@@ -189,6 +282,9 @@ module Koa {
kind = "parameter" and
this = getAQueryParameterAccess(rh)
or
kind = "parameter" and
this = rh.getARequestParameterAccess()
or
exists(Expr e | rh.getARequestOrContextExpr() = e |
// `ctx.request.url`, `ctx.request.originalUrl`, or `ctx.request.href`
exists(string propName |
@@ -202,6 +298,10 @@ module Koa {
propName = "href"
)
or
// params, when handler is registered by `koa-router` or similar.
kind = "parameter" and
this.asExpr().(PropAccess).accesses(e, "params")
or
// `ctx.request.body`
e instanceof RequestExpr and
kind = "body" and
@@ -285,7 +385,13 @@ module Koa {
getMethodName() = "use"
}
override DataFlow::SourceNode getARouteHandler() { result.flowsToExpr(getArgument(0)) }
override DataFlow::SourceNode getARouteHandler() {
// `StandardRouteHandler` uses this predicate in it's charpred, so making this predicate return a `RouteHandler` would give an empty recursion.
result.flowsToExpr(getArgument(0))
or
// For the route-handlers that does not depend on this predicate in their charpred.
result.(RouteHandler).getARouteHandlerRegistrationObject().flowsToExpr(getArgument(0))
}
override Expr getServer() { result = server }
}

View File

@@ -360,9 +360,9 @@ module LodashUnderscore {
/**
* A data flow step propagating an exception thrown from a callback to a Lodash/Underscore function.
*/
private class ExceptionStep extends DataFlow::CallNode, DataFlow::AdditionalFlowStep {
ExceptionStep() {
exists(string name | this = member(name).getACall() |
private class ExceptionStep extends DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::CallNode call, string name |
// Members ending with By, With, or While indicate that they are a variant of
// another function that takes a callback.
name.matches("%By") or
@@ -386,13 +386,12 @@ module LodashUnderscore {
name = "replace" or
name = "some" or
name = "transform"
|
call = member(name).getACall() and
pred = call.getAnArgument().(DataFlow::FunctionNode).getExceptionalReturn() and
succ = call.getExceptionalReturn()
)
}
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = getAnArgument().(DataFlow::FunctionNode).getExceptionalReturn() and
succ = this.getExceptionalReturn()
}
}
/**

View File

@@ -126,37 +126,28 @@ module NextJS {
/**
* A step modelling the flow from the server-computed props object to the default exported function that renders the page.
*/
class NextJSStaticPropsStep extends DataFlow::AdditionalFlowStep, DataFlow::FunctionNode {
Module pageModule;
NextJSStaticPropsStep() {
pageModule = getAPagesModule() and
this = pageModule.getAnExportedValue("default").getAFunctionValue()
}
class NextJSStaticPropsStep extends DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = getAPropsSource(pageModule) and
succ = this.getParameter(0)
exists(Module pageModule, DataFlow::FunctionNode function |
pageModule = getAPagesModule() and
function = pageModule.getAnExportedValue("default").getAFunctionValue() and
pred = getAPropsSource(pageModule) and
succ = function.getParameter(0)
)
}
}
/**
* A step modelling the flow from the server-computed props object to the default exported React component that renders the page.
*/
class NextJSStaticReactComponentPropsStep extends DataFlow::AdditionalFlowStep,
DataFlow::ValueNode {
Module pageModule;
ReactComponent component;
NextJSStaticReactComponentPropsStep() {
pageModule = getAPagesModule() and
this.getAstNode() = component and
this = pageModule.getAnExportedValue("default").getALocalSource()
}
class NextJSStaticReactComponentPropsStep extends DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = getAPropsSource(pageModule) and
succ = component.getADirectPropsAccess()
exists(Module pageModule, ReactComponent component |
pageModule = getAPagesModule() and
pageModule.getAnExportedValue("default").getALocalSource() = DataFlow::valueNode(component) and
pred = getAPropsSource(pageModule) and
succ = component.getADirectPropsAccess()
)
}
}

View File

@@ -0,0 +1,95 @@
/**
* Provides classes and predicates for reasoning about [puppeteer](https://www.npmjs.com/package/puppeteer).
*/
import javascript
/**
* Classes and predicates modelling the [puppeteer](https://www.npmjs.com/package/puppeteer) library.
*/
module Puppeteer {
/**
* A reference to a module import of puppeteer.
*/
private API::Node puppeteer() { result = API::moduleImport(["puppeteer", "puppeteer-core"]) }
/**
* A reference to a `Browser` from puppeteer.
*/
private API::Node browser() {
result = API::Node::ofType("puppeteer", "Browser")
or
result = puppeteer().getMember(["launch", "connect"]).getReturn().getPromised()
or
result = [page(), context(), target()].getMember("browser").getReturn()
}
/**
* A reference to a `Page` from puppeteer.
*/
API::Node page() {
result = API::Node::ofType("puppeteer", "Page")
or
result = [browser(), context()].getMember("newPage").getReturn().getPromised()
or
result = [browser(), context()].getMember("pages").getReturn().getPromised().getUnknownMember()
or
result = target().getMember("page").getReturn().getPromised()
}
/**
* A reference to a `Target` from puppeteer.
*/
private API::Node target() {
result = API::Node::ofType("puppeteer", "Target")
or
result = [page(), browser()].getMember("target").getReturn()
or
result = context().getMember("targets").getReturn().getUnknownMember()
or
result = target().getMember("opener").getReturn()
}
/**
* A reference to a `BrowserContext` from puppeteer.
*/
private API::Node context() {
result = API::Node::ofType("puppeteer", "BrowserContext")
or
result = [page(), target()].getMember("browserContext").getReturn()
or
result = browser().getMember("browserContexts").getReturn().getUnknownMember()
or
result = browser().getMember("createIncognitoBrowserContext").getReturn().getPromised()
or
result = browser().getMember("defaultBrowserContext").getReturn()
}
/**
* A call requesting a `Page` to navigate to some url, seen as a `ClientRequest`.
*/
private class PuppeteerGotoCall extends ClientRequest::Range, API::InvokeNode {
PuppeteerGotoCall() { this = page().getMember("goto").getACall() }
override DataFlow::Node getUrl() { result = getArgument(0) }
override DataFlow::Node getHost() { none() }
override DataFlow::Node getADataNode() { none() }
}
/**
* A call requesting a `Page` to load a stylesheet or script, seen as a `ClientRequest`.
*/
private class PuppeteerLoadResourceCall extends ClientRequest::Range, API::InvokeNode {
PuppeteerLoadResourceCall() {
this = page().getMember(["addStyleTag", "addScriptTag"]).getACall()
}
override DataFlow::Node getUrl() { result = getParameter(0).getMember("url").getARhs() }
override DataFlow::Node getHost() { none() }
override DataFlow::Node getADataNode() { none() }
}
}

View File

@@ -268,9 +268,11 @@ module SocketIO {
/** Gets the `i`th parameter through which data is received from a client. */
override DataFlow::SourceNode getReceivedItem(int i) {
exists(DataFlow::FunctionNode cb | cb = getListener() and result = cb.getParameter(i) |
exists(DataFlow::FunctionNode cb |
cb = getListener() and
result = cb.getParameter(i) and
// exclude last parameter if it looks like a callback
result != cb.getLastParameter() or not exists(result.getAnInvocation())
not (result = cb.getLastParameter() and exists(result.getAnInvocation()))
)
}

View File

@@ -43,9 +43,15 @@ private predicate execApi(string mod, int cmdArg, int optionsArg, boolean shell)
)
or
shell = true and
mod = "exec" and
optionsArg = -2 and
cmdArg = 0
(
mod = "exec" and
optionsArg = -2 and
cmdArg = 0
or
mod = "async-execute" and
optionsArg = 1 and
cmdArg = 0
)
}
private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::InvokeNode {

View File

@@ -81,7 +81,7 @@ module ClientWebSocket {
/**
* A client WebSocket instance.
*/
class ClientSocket extends EventEmitter::Range, DataFlow::SourceNode {
class ClientSocket extends EventEmitter::Range, DataFlow::NewNode, ClientRequest::Range {
SocketClass socketClass;
ClientSocket() { this = socketClass.getAnInstantiation() }
@@ -90,6 +90,26 @@ module ClientWebSocket {
* Gets the WebSocket library name.
*/
LibraryName getLibrary() { result = socketClass.getLibrary() }
override DataFlow::Node getUrl() { result = getArgument(0) }
override DataFlow::Node getHost() { none() }
override DataFlow::Node getADataNode() {
exists(SendNode send |
send.getEmitter() = this and
result = send.getSentItem(_)
)
}
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
responseType = "json" and
promise = false and
exists(WebSocketReceiveNode receiver |
receiver.getEmitter() = this and
result = receiver.getReceivedItem(_)
)
}
}
/**

View File

@@ -219,7 +219,7 @@ module Stages {
or
AccessPath::DominatingPaths::hasDominatingWrite(_)
or
any(DataFlow::AdditionalFlowStep s).step(_, _)
DataFlow::SharedFlowStep::step(_, _)
}
}

View File

@@ -156,6 +156,12 @@ private module PersistentWebStorage {
result = DataFlow::globalVarRef(kind)
}
pragma[noinline]
WriteAccess getAWriteByName(string name, string kind) {
result.getKey() = name and
result.getKind() = kind
}
/**
* A read access.
*/
@@ -165,8 +171,10 @@ private module PersistentWebStorage {
ReadAccess() { this = webStorage(kind).getAMethodCall("getItem") }
override PersistentWriteAccess getAWrite() {
getArgument(0).mayHaveStringValue(result.(WriteAccess).getKey()) and
result.(WriteAccess).getKind() = kind
exists(string name |
getArgument(0).mayHaveStringValue(name) and
result = getAWriteByName(name, kind)
)
}
}

View File

@@ -255,15 +255,12 @@ module ExternalAPIUsedWithUntrustedData {
not exists(DataFlow::Node arg |
arg = this.getAnArgument() and not arg instanceof DeepObjectSink
|
TaintTracking::sharedTaintStep(arg, _)
or
exists(DataFlow::AdditionalFlowStep s |
s.step(arg, _) or
s.step(arg, _, _, _) or
s.loadStep(arg, _, _) or
s.storeStep(arg, _, _) or
s.loadStoreStep(arg, _, _)
)
TaintTracking::sharedTaintStep(arg, _) or
DataFlow::SharedFlowStep::step(arg, _) or
DataFlow::SharedFlowStep::step(arg, _, _, _) or
DataFlow::SharedFlowStep::loadStep(arg, _, _) or
DataFlow::SharedFlowStep::storeStep(arg, _, _) or
DataFlow::SharedFlowStep::loadStoreStep(arg, _, _)
)
}

View File

@@ -100,10 +100,10 @@ module PrototypePollutingAssignment {
// users wouldn't bother to call Object.create in that case.
result = DataFlow::globalVarRef("Object").getAMemberCall("create")
or
// Allow use of AdditionalFlowSteps to track a bit further
// Allow use of SharedFlowSteps to track a bit further
exists(DataFlow::Node mid |
prototypeLessObject(t.continue()).flowsTo(mid) and
any(DataFlow::AdditionalFlowStep s).step(mid, result)
DataFlow::SharedFlowStep::step(mid, result)
)
or
exists(DataFlow::TypeTracker t2 | result = prototypeLessObject(t2).track(t2, t))

View File

@@ -636,6 +636,20 @@ module TaintedPath {
SendPathSink() { this = DataFlow::moduleImport("send").getACall().getArgument(1) }
}
/**
* A path argument given to a `Page` in puppeteer, specifying where a pdf/screenshot should be saved.
*/
private class PuppeteerPath extends TaintedPath::Sink {
PuppeteerPath() {
this =
Puppeteer::page()
.getMember(["pdf", "screenshot"])
.getParameter(0)
.getMember("path")
.getARhs()
}
}
/**
* Holds if there is a step `src -> dst` mapping `srclabel` to `dstlabel` relevant for path traversal vulnerabilities.
*/

View File

@@ -28,19 +28,13 @@ module Shared {
abstract class SanitizerGuard extends TaintTracking::SanitizerGuardNode { }
/**
* A global regexp replacement involving an HTML meta-character, viewed as a sanitizer for
* A global regexp replacement involving the `<`, `'`, or `"` meta-character, viewed as a sanitizer for
* XSS vulnerabilities.
*
* The XSS queries do not attempt to reason about correctness or completeness of sanitizers,
* so any such replacement stops taint propagation.
*/
class MetacharEscapeSanitizer extends Sanitizer, StringReplaceCall {
MetacharEscapeSanitizer() {
this.isGlobal() and
exists(RegExpConstant c |
c.getLiteral() = getRegExp().asExpr() and
c.getValue().regexpMatch("['\"&<>]")
)
isGlobal() and
RegExp::alwaysMatchesMetaCharacter(getRegExp().getRoot(), ["<", "'", "\""])
}
}

View File

@@ -1,3 +1,4 @@
| mongodb | Collection | index.ts:14:3:14:17 | getCollection() |
| mongoose | Model | index.ts:22:3:22:20 | getMongooseModel() |
| mongoose | Query | index.ts:23:3:23:20 | getMongooseQuery() |
| puppeteer | Browser | index.ts:30:22:30:33 | this.browser |

View File

@@ -22,3 +22,11 @@ app.post("/find", (req, res) => {
getMongooseModel().find({ id: v }); /* def (parameter 0 (member find (instance (member Model (member exports (module mongoose)))))) */
getMongooseQuery().find({ id: v }); /* def (parameter 0 (member find (instance (member Query (member exports (module mongoose)))))) */
});
import * as puppeteer from 'puppeteer';
class Renderer {
private browser: puppeteer.Browser;
foo(): void {
const page = this.browser.newPage(); /* use (instance (member Browser (member exports (module puppeteer)))) */
}
}

View File

@@ -0,0 +1,8 @@
on: issue_comment
jobs:
echo-chamber:
runs-on: ubuntu-latest
steps:
- run: |
echo '${{ github.event.comment.body }}'

View File

@@ -0,0 +1,38 @@
on:
pull_request_target:
jobs:
job1:
if: contains(github.event.issue.labels.*.name, 'ok')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}
job2:
if: github.event.label.name == 'ok'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}
job3:
if: github.actor == 'ok'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}
job4:
if: github.actor == 'ok' || true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}
job5:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}

View File

@@ -0,0 +1,31 @@
on:
pull_request_target:
jobs:
job1:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
if: contains(github.event.issue.labels.*.name, 'ok')
with:
ref: ${{ github.event.pull_request.head.ref }}
- uses: actions/checkout@v2
if: github.event.label.name == 'ok'
with:
ref: ${{ github.event.pull_request.head.ref }}
- uses: actions/checkout@v2
if: github.actor == 'ok'
with:
ref: ${{ github.event.pull_request.head.ref }}
- uses: actions/checkout@v2
if: github.actor == 'ok' || true
with:
ref: ${{ github.event.pull_request.head.ref }}
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}

View File

@@ -0,0 +1,12 @@
on:
pull_request_target:
types: [labeled]
push:
jobs:
echo-chamber:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}

View File

@@ -0,0 +1,13 @@
on:
pull_request_target:
types:
labeled:
push:
jobs:
echo-chamber:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}

View File

@@ -0,0 +1,15 @@
on:
pull_request_target:
types:
labeled:
opened:
closed:
push:
jobs:
echo-chamber:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}

View File

@@ -0,0 +1,12 @@
on:
pull_request_target:
types: [labeled, opened]
push:
jobs:
echo-chamber:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}

View File

@@ -0,0 +1,10 @@
on:
pull_request_target:
jobs:
echo-chamber:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}

View File

@@ -0,0 +1,10 @@
on:
pull_request_target:
jobs:
echo-chamber:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: master

View File

@@ -0,0 +1,11 @@
on: pull_request_target
jobs:
echo-chamber:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}
- run: make

View File

@@ -0,0 +1,9 @@
on: [pull_request_target, push]
jobs:
echo-chamber:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}

View File

@@ -0,0 +1 @@
| .github/workflows/comment_issue.yml:7:12:8:47 | \| | Potential injection from the github.event.comment.body context, which may be controlled by an external user. |

View File

@@ -0,0 +1 @@
experimental/Security/CWE-094/ExpressionInjection.ql

View File

@@ -0,0 +1,9 @@
| .github/workflows/pull_request_target_if_job.yml:30:7:33:2 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on `pull_request_target` |
| .github/workflows/pull_request_target_if_job.yml:36:7:38:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on `pull_request_target` |
| .github/workflows/pull_request_target_if_step.yml:24:7:29:4 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on `pull_request_target` |
| .github/workflows/pull_request_target_if_step.yml:29:7:31:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on `pull_request_target` |
| .github/workflows/pull_request_target_labels_mapping.yml:13:7:15:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on `pull_request_target` |
| .github/workflows/pull_request_target_labels_sequence.yml:10:7:12:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on `pull_request_target` |
| .github/workflows/pull_request_target_mapping.yml:8:7:10:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on `pull_request_target` |
| .github/workflows/pull_request_target_run.yml:7:7:11:4 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on `pull_request_target` |
| .github/workflows/pull_request_target_sequence.yml:7:7:9:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on `pull_request_target` |

View File

@@ -0,0 +1 @@
experimental/Security/CWE-094/UntrustedCheckout.ql

View File

@@ -0,0 +1 @@
console.log('test')

View File

@@ -66,6 +66,16 @@ typeInferenceMismatch
| exceptions.js:144:9:144:16 | source() | exceptions.js:132:8:132:27 | returnThrownSource() |
| exceptions.js:150:13:150:20 | source() | exceptions.js:153:10:153:10 | e |
| exceptions.js:158:13:158:20 | source() | exceptions.js:161:10:161:10 | e |
| getters-and-setters.js:6:20:6:27 | source() | getters-and-setters.js:9:10:9:18 | new C().x |
| getters-and-setters.js:6:20:6:27 | source() | getters-and-setters.js:13:18:13:20 | c.x |
| getters-and-setters.js:27:15:27:22 | source() | getters-and-setters.js:23:18:23:18 | v |
| getters-and-setters.js:47:23:47:30 | source() | getters-and-setters.js:45:14:45:16 | c.x |
| getters-and-setters.js:60:20:60:27 | source() | getters-and-setters.js:66:10:66:14 | obj.x |
| getters-and-setters.js:67:13:67:20 | source() | getters-and-setters.js:63:18:63:22 | value |
| getters-and-setters.js:79:20:79:27 | source() | getters-and-setters.js:88:10:88:18 | new C().x |
| getters-and-setters.js:79:20:79:27 | source() | getters-and-setters.js:92:14:92:16 | c.x |
| getters-and-setters.js:79:20:79:27 | source() | getters-and-setters.js:100:10:100:22 | getX(new C()) |
| getters-and-setters.js:89:17:89:24 | source() | getters-and-setters.js:82:18:82:22 | value |
| importedReactComponent.jsx:4:40:4:47 | source() | exportedReactComponent.jsx:2:10:2:19 | props.text |
| indexOf.js:4:11:4:18 | source() | indexOf.js:9:10:9:10 | x |
| json-stringify.js:2:16:2:23 | source() | json-stringify.js:5:8:5:29 | JSON.st ... source) |
@@ -124,6 +134,11 @@ typeInferenceMismatch
| static-capture-groups.js:2:17:2:24 | source() | static-capture-groups.js:27:14:27:22 | RegExp.$1 |
| static-capture-groups.js:32:17:32:24 | source() | static-capture-groups.js:38:10:38:18 | RegExp.$1 |
| static-capture-groups.js:42:12:42:19 | source() | static-capture-groups.js:43:14:43:22 | RegExp.$1 |
| string-replace.js:3:13:3:20 | source() | string-replace.js:14:10:14:13 | data |
| string-replace.js:3:13:3:20 | source() | string-replace.js:18:10:18:13 | data |
| string-replace.js:3:13:3:20 | source() | string-replace.js:21:6:21:41 | safe(). ... taint) |
| string-replace.js:3:13:3:20 | source() | string-replace.js:22:6:22:48 | safe(). ... taint) |
| string-replace.js:3:13:3:20 | source() | string-replace.js:24:6:24:45 | taint.r ... + '!') |
| thisAssignments.js:4:17:4:24 | source() | thisAssignments.js:5:10:5:18 | obj.field |
| thisAssignments.js:7:19:7:26 | source() | thisAssignments.js:8:10:8:20 | this.field2 |
| tst.js:2:13:2:20 | source() | tst.js:4:10:4:10 | x |

View File

@@ -41,6 +41,16 @@
| exceptions.js:144:9:144:16 | source() | exceptions.js:132:8:132:27 | returnThrownSource() |
| exceptions.js:150:13:150:20 | source() | exceptions.js:153:10:153:10 | e |
| exceptions.js:158:13:158:20 | source() | exceptions.js:161:10:161:10 | e |
| getters-and-setters.js:6:20:6:27 | source() | getters-and-setters.js:9:10:9:18 | new C().x |
| getters-and-setters.js:6:20:6:27 | source() | getters-and-setters.js:13:18:13:20 | c.x |
| getters-and-setters.js:27:15:27:22 | source() | getters-and-setters.js:23:18:23:18 | v |
| getters-and-setters.js:47:23:47:30 | source() | getters-and-setters.js:45:14:45:16 | c.x |
| getters-and-setters.js:60:20:60:27 | source() | getters-and-setters.js:66:10:66:14 | obj.x |
| getters-and-setters.js:67:13:67:20 | source() | getters-and-setters.js:63:18:63:22 | value |
| getters-and-setters.js:79:20:79:27 | source() | getters-and-setters.js:88:10:88:18 | new C().x |
| getters-and-setters.js:79:20:79:27 | source() | getters-and-setters.js:92:14:92:16 | c.x |
| getters-and-setters.js:79:20:79:27 | source() | getters-and-setters.js:100:10:100:22 | getX(new C()) |
| getters-and-setters.js:89:17:89:24 | source() | getters-and-setters.js:82:18:82:22 | value |
| indexOf.js:4:11:4:18 | source() | indexOf.js:9:10:9:10 | x |
| indexOf.js:4:11:4:18 | source() | indexOf.js:13:10:13:10 | x |
| nested-props.js:4:13:4:20 | source() | nested-props.js:5:10:5:14 | obj.x |

View File

@@ -0,0 +1,102 @@
import * as dummy from 'dummy';
function testGetterSource() {
class C {
get x() {
return source();
}
};
sink(new C().x); // NOT OK
function indirection(c) {
if (c) {
sink(c.x); // NOT OK
}
}
indirection(new C());
indirection(null);
}
function testSetterSink() {
class C {
set x(v) {
sink(v); // NOT OK
}
};
function indirection(c) {
c.x = source();
}
indirection(new C());
indirection(null);
}
function testFlowThroughGetter() {
class C {
constructor(x) {
this._x = x;
}
get x() {
return this._x;
}
};
function indirection(c) {
sink(c.x); // NOT OK
}
indirection(new C(source()));
indirection(null);
function getX(c) {
return c.x;
}
sink(getX(new C(source()))); // NOT OK - but not flagged
getX(null);
}
function testFlowThroughObjectLiteralAccessors() {
let obj = {
get x() {
return source();
},
set y(value) {
sink(value); // NOT OK
}
};
sink(obj.x); // NOT OK
obj.y = source();
function indirection(c) {
sink(c.x); // NOT OK - but not currently flagged
}
indirection(obj);
indirection(null);
}
function testFlowThroughSubclass() {
class Base {
get x() {
return source();
}
set y(value) {
sink(value); // NOT OK
}
};
class C extends Base {
}
sink(new C().x); // NOT OK
new C().y = source();
function indirection(c) {
sink(c.x); // NOT OK
}
indirection(new C());
indirection(null);
function getX(c) {
return c.x;
}
sink(getX(new C())); // NOT OK - but not flagged
getX(null);
}

View File

@@ -0,0 +1,24 @@
import 'dummy';
let taint = source();
taint.replace('foo', data => {
sink(data); // OK - can only be the value 'foo'
});
taint.replace(/\d+/, data => {
sink(data); // OK - can only be digits
});
taint.replace(/[^a-z]+/, data => {
sink(data); // NOT OK
});
taint.replace(/&[^&]+;/, data => {
sink(data); // NOT OK
});
sink(safe().replace('foo', data => taint)); // NOT OK
sink(safe().replace('foo', data => data + taint)); // NOT OK
sink(taint.replace('foo', data => data + '!')); // NOT OK -- propagates through replace call

View File

@@ -0,0 +1,2 @@
var foo1 = require('./lib/index.js');
var foo2 = require('./src/index.ts');

View File

@@ -0,0 +1,4 @@
{
"name": "foo",
"main": "./lib/index.js"
}

View File

@@ -0,0 +1 @@
export default class Foo {}

View File

@@ -0,0 +1,8 @@
{
"extends": "./tsconfig",
"compilerOptions": {
"module": "commonjs",
"rootDir": "./src",
"outDir": "./lib"
}
}

View File

@@ -1,3 +1,4 @@
resolveableImport
| nonUniqueInclude/index.js:1:12:1:38 | require ... oo.js') | nonUniqueInclude/src/foo.ts:1:1:1:27 | <toplevel> |
| nonUniqueInclude/index.js:2:12:2:39 | require ... oo.js') | nonUniqueInclude/src2/foo.ts:1:1:1:27 | <toplevel> |
| nonUniqueInclude/index.js:3:12:3:34 | require ... oo.ts') | nonUniqueInclude/src/foo.ts:1:1:1:27 | <toplevel> |
@@ -10,3 +11,7 @@
| simpleOutDir/index.js:2:12:2:36 | require ... ex.ts') | simpleOutDir/src/index.ts:1:1:1:27 | <toplevel> |
| simpleOutDir/index.js:4:12:4:33 | require ... index') | simpleOutDir/src/index.ts:1:1:1:27 | <toplevel> |
| simpleOutDir/index.js:5:12:5:33 | require ... index') | simpleOutDir/src/index.ts:1:1:1:27 | <toplevel> |
| slashAndExtension/index.js:1:12:1:36 | require ... ex.js') | slashAndExtension/src/index.ts:1:1:1:27 | <toplevel> |
| slashAndExtension/index.js:2:12:2:36 | require ... ex.ts') | slashAndExtension/src/index.ts:1:1:1:27 | <toplevel> |
getMain
| slashAndExtension/package.json:1:1:4:1 | {\\n " ... x.js"\\n} | slashAndExtension/src/index.ts:1:1:1:27 | <toplevel> |

View File

@@ -5,3 +5,5 @@ query predicate resolveableImport(Import imp, Module mod) {
not imp.getTopLevel().isExterns() and
not mod.getTopLevel().isExterns()
}
query Module getMain(PackageJSON json) { result = json.getMainModule() }

View File

@@ -5,6 +5,9 @@ test_ClientRequest
| apollo.js:17:1:17:34 | new Pre ... yurl"}) |
| apollo.js:20:1:20:77 | createN ... phql'}) |
| apollo.js:23:1:23:31 | new Web ... wsUri}) |
| puppeteer.ts:6:11:6:42 | page.go ... e.com') |
| puppeteer.ts:8:5:8:61 | page.ad ... css" }) |
| puppeteer.ts:18:30:18:50 | page.go ... estUrl) |
| tst.js:11:5:11:16 | request(url) |
| tst.js:13:5:13:20 | request.get(url) |
| tst.js:15:5:15:23 | request.delete(url) |
@@ -83,6 +86,7 @@ test_ClientRequest
| tst.js:269:13:269:48 | httpPro ... ptions) |
| tst.js:271:3:271:61 | proxy.w ... 080' }) |
| tst.js:274:1:283:2 | httpPro ... true\\n}) |
| tst.js:286:20:286:55 | new Web ... :8080') |
test_getADataNode
| tst.js:53:5:53:23 | axios({data: data}) | tst.js:53:18:53:21 | data |
| tst.js:57:5:57:39 | axios.p ... data2}) | tst.js:57:19:57:23 | data1 |
@@ -121,6 +125,7 @@ test_getADataNode
| tst.js:249:1:251:2 | form.su ... e();\\n}) | tst.js:246:26:246:43 | Buffer.from("foo") |
| tst.js:249:1:251:2 | form.su ... e();\\n}) | tst.js:247:24:247:68 | request ... o.png') |
| tst.js:257:1:262:2 | form.su ... rs()\\n}) | tst.js:255:25:255:35 | 'new_value' |
| tst.js:286:20:286:55 | new Web ... :8080') | tst.js:288:21:288:35 | 'Hello Server!' |
test_getHost
| tst.js:87:5:87:39 | http.ge ... host}) | tst.js:87:34:87:37 | host |
| tst.js:89:5:89:23 | axios({host: host}) | tst.js:89:18:89:21 | host |
@@ -136,6 +141,9 @@ test_getUrl
| apollo.js:17:1:17:34 | new Pre ... yurl"}) | apollo.js:17:26:17:32 | "myurl" |
| apollo.js:20:1:20:77 | createN ... phql'}) | apollo.js:20:30:20:75 | 'https: ... raphql' |
| apollo.js:23:1:23:31 | new Web ... wsUri}) | apollo.js:23:25:23:29 | wsUri |
| puppeteer.ts:6:11:6:42 | page.go ... e.com') | puppeteer.ts:6:21:6:41 | 'https: ... le.com' |
| puppeteer.ts:8:5:8:61 | page.ad ... css" }) | puppeteer.ts:8:29:8:58 | "http:/ ... le.css" |
| puppeteer.ts:18:30:18:50 | page.go ... estUrl) | puppeteer.ts:18:40:18:49 | requestUrl |
| tst.js:11:5:11:16 | request(url) | tst.js:11:13:11:15 | url |
| tst.js:13:5:13:20 | request.get(url) | tst.js:13:17:13:19 | url |
| tst.js:15:5:15:23 | request.delete(url) | tst.js:15:20:15:22 | url |
@@ -218,6 +226,7 @@ test_getUrl
| tst.js:267:1:267:61 | httpPro ... 9000'}) | tst.js:267:37:267:59 | 'http:/ ... t:9000' |
| tst.js:271:3:271:61 | proxy.w ... 080' }) | tst.js:271:33:271:58 | 'http:/ ... m:8080' |
| tst.js:274:1:283:2 | httpPro ... true\\n}) | tst.js:275:13:281:5 | {\\n ... ,\\n } |
| tst.js:286:20:286:55 | new Web ... :8080') | tst.js:286:34:286:54 | 'ws://l ... t:8080' |
test_getAResponseDataNode
| tst.js:19:5:19:23 | requestPromise(url) | tst.js:19:5:19:23 | requestPromise(url) | text | true |
| tst.js:21:5:21:23 | superagent.get(url) | tst.js:21:5:21:23 | superagent.get(url) | stream | true |
@@ -284,3 +293,4 @@ test_getAResponseDataNode
| tst.js:231:5:233:6 | needle. ... \\n }) | tst.js:231:50:231:53 | body | json | false |
| tst.js:235:5:237:6 | needle. ... \\n }) | tst.js:235:67:235:70 | resp | fetch.response | false |
| tst.js:235:5:237:6 | needle. ... \\n }) | tst.js:235:73:235:76 | body | json | false |
| tst.js:286:20:286:55 | new Web ... :8080') | tst.js:291:44:291:53 | event.data | json | false |

View File

@@ -0,0 +1,20 @@
import * as puppeteer from 'puppeteer';
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
page.addStyleTag({ url: "http://example.org/style.css" })
})();
class Renderer {
private browser: puppeteer.Browser;
constructor(browser: puppeteer.Browser) {
this.browser = browser;
}
async foo(requestUrl: string): Promise<void> {
const page = await this.browser.newPage();
let response = await page.goto(requestUrl);
}
}

View File

@@ -280,4 +280,14 @@ httpProxy.createProxyServer({
passphrase: 'password',
},
changeOrigin: true
}).listen(8000);
}).listen(8000);
function webSocket() {
const socket = new WebSocket('ws://localhost:8080');
socket.addEventListener('open', function (event) {
socket.send('Hello Server!');
});
socket.addEventListener('message', function (event) {
console.log("Data from server: " + event.data);
});
}

View File

@@ -26,3 +26,6 @@ typeTracking
| tst.js:2:16:2:23 | source() | tst.js:37:14:37:14 | e |
| tst.js:2:16:2:23 | source() | tst.js:45:14:45:14 | e |
| tst.js:2:16:2:23 | source() | tst.js:53:8:53:21 | map.get("key") |
| tst.js:2:16:2:23 | source() | tst.js:59:8:59:22 | map2.get("foo") |
| tst.js:2:16:2:23 | source() | tst.js:64:8:64:26 | map3.get(unknown()) |
| tst.js:2:16:2:23 | source() | tst.js:69:8:69:26 | map3.get(unknown()) |

View File

@@ -22,10 +22,6 @@ DataFlow::SourceNode trackSource(DataFlow::TypeTracker t, DataFlow::SourceNode s
start = result
or
exists(DataFlow::TypeTracker t2 | t = t2.step(trackSource(t2, start), result))
or
exists(DataFlow::TypeTracker t2 |
result = CollectionsTypeTracking::collectionStep(trackSource(t2, start), t, t2)
)
}
query DataFlow::SourceNode typeTracking(DataFlow::Node start) {

View File

@@ -54,17 +54,17 @@
sink(map.get("nonExistingKey")); // OK.
// unknown write, known read
var map2 = new map();
var map2 = new Map();
map2.set(unknown(), source);
sink(map2.get("foo")); // NOT OK (for data-flow). OK for type-tracking.
sink(map2.get("foo")); // NOT OK (for data-flow).
// unknown write, unknown read
var map3 = new map();
var map3 = new Map();
map3.set(unknown(), source);
sink(map3.get(unknown())); // NOT OK (for data-flow). OK for type-tracking.
sink(map3.get(unknown())); // NOT OK (for data-flow).
// known write, unknown read
var map4 = new map();
var map4 = new Map();
map4.set("foo", source);
sink(map3.get(unknown())); // NOT OK (for data-flow). OK for type-tracking.
sink(map3.get(unknown())); // NOT OK (for data-flow).
})();

View File

@@ -9,7 +9,7 @@ query predicate clientRequest_getADataNode(Electron::ElectronClientRequest cr, D
query predicate clientRequest(Electron::ElectronClientRequest cr) { any() }
query predicate ipcFlow(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::AdditionalFlowStep afs | afs.step(pred, succ))
DataFlow::SharedFlowStep::step(pred, succ)
}
query predicate remoteFlowSources(RemoteFlowSource source) { any() }

View File

@@ -1,4 +1,4 @@
taintSteps
flowSteps
| customEmitter.js:5:20:5:24 | "bar" | customEmitter.js:6:19:6:22 | data |
| customEmitter.js:12:21:12:25 | "baz" | customEmitter.js:13:23:13:26 | data |
| customEmitter.js:12:21:12:25 | "baz" | customEmitter.js:22:14:22:18 | yData |

View File

@@ -1,7 +1,7 @@
import javascript
query predicate taintSteps(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::AdditionalFlowStep step | step.step(pred, succ))
query predicate flowSteps(DataFlow::Node pred, DataFlow::Node succ) {
DataFlow::SharedFlowStep::step(pred, succ)
}
query predicate eventEmitter(EventEmitter e) { any() }

View File

@@ -1,5 +1,5 @@
import javascript
query predicate test_AdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::AdditionalFlowStep step | step.step(pred, succ) | any())
DataFlow::SharedFlowStep::step(pred, succ)
}

View File

@@ -21,7 +21,7 @@ serverSend
serverReceive
| server.js:7:3:9:4 | ws.on(' ... );\\n\\t\\t}) |
| sockjs.js:9:5:12:6 | conn.on ... \\n }) |
taintStep
flowSteps
| browser.js:5:15:5:32 | 'Hi from browser!' | server.js:7:38:7:44 | message |
| browser.js:21:13:21:18 | 'test' | sockjs.js:9:31:9:37 | message |
| client.js:7:11:7:27 | 'Hi from client!' | server.js:7:38:7:44 | message |

Some files were not shown because too many files have changed in this diff Show More