mirror of
https://github.com/github/codeql.git
synced 2026-05-03 04:39:29 +02:00
Merge branch 'main' into topPack
This commit is contained in:
5
javascript/change-notes/2021-03-17-koa-route.md
Normal file
5
javascript/change-notes/2021-03-17-koa-route.md
Normal 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)
|
||||
@@ -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.
|
||||
4
javascript/change-notes/2021-03-17-puppeteer.md
Normal file
4
javascript/change-notes/2021-03-17-puppeteer.md
Normal 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)
|
||||
4
javascript/change-notes/2021-03-19-async-execute.md
Normal file
4
javascript/change-notes/2021-03-19-async-execute.md
Normal 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)
|
||||
3
javascript/change-notes/2021-03-23-accessor-calls.md
Normal file
3
javascript/change-notes/2021-03-23-accessor-calls.md
Normal 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.
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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() |
|
||||
|
||||
@@ -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>
|
||||
@@ -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."
|
||||
@@ -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>
|
||||
@@ -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`"
|
||||
@@ -0,0 +1,8 @@
|
||||
on: issue_comment
|
||||
|
||||
jobs:
|
||||
echo-body:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: |
|
||||
echo '${{ github.event.comment.body }}'
|
||||
@@ -0,0 +1,10 @@
|
||||
on: issue_comment
|
||||
|
||||
jobs:
|
||||
echo-body:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- env:
|
||||
BODY: ${{ github.event.issue.body }}
|
||||
run: |
|
||||
echo '$BODY'
|
||||
@@ -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!'
|
||||
});
|
||||
@@ -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!
|
||||
@@ -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/
|
||||
316
javascript/ql/src/experimental/semmle/javascript/Actions.qll
Normal file
316
javascript/ql/src/experimental/semmle/javascript/Actions.qll
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() |
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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` → `succ` should be considered a data flow edge.
|
||||
*/
|
||||
cached
|
||||
predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `pred` → `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` → `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` → `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)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
95
javascript/ql/src/semmle/javascript/frameworks/Puppeteer.qll
Normal file
95
javascript/ql/src/semmle/javascript/frameworks/Puppeteer.qll
Normal 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() }
|
||||
}
|
||||
}
|
||||
@@ -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()))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -219,7 +219,7 @@ module Stages {
|
||||
or
|
||||
AccessPath::DominatingPaths::hasDominatingWrite(_)
|
||||
or
|
||||
any(DataFlow::AdditionalFlowStep s).step(_, _)
|
||||
DataFlow::SharedFlowStep::step(_, _)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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, _, _)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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(), ["<", "'", "\""])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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)))) */
|
||||
}
|
||||
}
|
||||
8
javascript/ql/test/experimental/Security/CWE-094/.github/workflows/comment_issue.yml
vendored
Normal file
8
javascript/ql/test/experimental/Security/CWE-094/.github/workflows/comment_issue.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
on: issue_comment
|
||||
|
||||
jobs:
|
||||
echo-chamber:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: |
|
||||
echo '${{ github.event.comment.body }}'
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -0,0 +1,10 @@
|
||||
on:
|
||||
pull_request_target:
|
||||
|
||||
jobs:
|
||||
echo-chamber:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: master
|
||||
11
javascript/ql/test/experimental/Security/CWE-094/.github/workflows/pull_request_target_run.yml
vendored
Normal file
11
javascript/ql/test/experimental/Security/CWE-094/.github/workflows/pull_request_target_run.yml
vendored
Normal 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
|
||||
@@ -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 }}
|
||||
@@ -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. |
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE-094/ExpressionInjection.ql
|
||||
@@ -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` |
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE-094/UntrustedCheckout.ql
|
||||
@@ -0,0 +1 @@
|
||||
console.log('test')
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
var foo1 = require('./lib/index.js');
|
||||
var foo2 = require('./src/index.ts');
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "foo",
|
||||
"main": "./lib/index.js"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export default class Foo {}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib"
|
||||
}
|
||||
}
|
||||
@@ -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> |
|
||||
|
||||
@@ -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() }
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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()) |
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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).
|
||||
})();
|
||||
|
||||
@@ -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() }
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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() }
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user