mirror of
https://github.com/github/codeql.git
synced 2026-04-27 17:55:19 +02:00
Merge 'main' into redsun82/swift-extraction
This commit is contained in:
63
.github/workflows/ql-for-ql-build.yml
vendored
63
.github/workflows/ql-for-ql-build.yml
vendored
@@ -10,16 +10,16 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
queries:
|
||||
runs-on: ubuntu-latest
|
||||
analyze:
|
||||
runs-on: ubuntu-latest-xl
|
||||
steps:
|
||||
### Build the queries ###
|
||||
- uses: actions/checkout@v3
|
||||
- name: Find codeql
|
||||
id: find-codeql
|
||||
uses: github/codeql-action/init@aa93aea877e5fb8841bcb1193f672abf6e9f2980
|
||||
with:
|
||||
languages: javascript # does not matter
|
||||
tools: latest
|
||||
- name: Get CodeQL version
|
||||
id: get-codeql-version
|
||||
run: |
|
||||
@@ -49,14 +49,7 @@ jobs:
|
||||
name: query-pack-zip
|
||||
path: ${{ runner.temp }}/query-pack.zip
|
||||
|
||||
extractors:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
### Build the extractor ###
|
||||
- name: Cache entire extractor
|
||||
id: cache-extractor
|
||||
uses: actions/cache@v3
|
||||
@@ -100,15 +93,8 @@ jobs:
|
||||
ql/target/release/ql-extractor
|
||||
ql/target/release/ql-extractor.exe
|
||||
retention-days: 1
|
||||
package:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
needs:
|
||||
- extractors
|
||||
- queries
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
### Package the queries and extractor ###
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: query-pack-zip
|
||||
@@ -136,16 +122,8 @@ jobs:
|
||||
name: codeql-ql-pack
|
||||
path: codeql-ql.zip
|
||||
retention-days: 1
|
||||
analyze:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
folder: [cpp, csharp, java, javascript, python, ql, ruby, swift, go]
|
||||
|
||||
needs:
|
||||
- package
|
||||
|
||||
steps:
|
||||
### Run the analysis ###
|
||||
- name: Download pack
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
@@ -165,14 +143,11 @@ jobs:
|
||||
env:
|
||||
PACK: ${{ runner.temp }}/pack
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Create CodeQL config file
|
||||
run: |
|
||||
echo "paths:" > ${CONF}
|
||||
echo " - ${FOLDER}" >> ${CONF}
|
||||
echo "paths-ignore:" >> ${CONF}
|
||||
echo " - ql/ql/test" >> ${CONF}
|
||||
echo " - \"*/ql/lib/upgrades/\"" >> ${CONF}
|
||||
echo "disable-default-queries: true" >> ${CONF}
|
||||
echo "packs:" >> ${CONF}
|
||||
echo " - codeql/ql" >> ${CONF}
|
||||
@@ -180,24 +155,34 @@ jobs:
|
||||
cat ${CONF}
|
||||
env:
|
||||
CONF: ./ql-for-ql-config.yml
|
||||
FOLDER: ${{ matrix.folder }}
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@aa93aea877e5fb8841bcb1193f672abf6e9f2980
|
||||
with:
|
||||
languages: ql
|
||||
db-location: ${{ runner.temp }}/db
|
||||
config-file: ./ql-for-ql-config.yml
|
||||
tools: latest
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@aa93aea877e5fb8841bcb1193f672abf6e9f2980
|
||||
with:
|
||||
category: "ql-for-ql-${{ matrix.folder }}"
|
||||
category: "ql-for-ql"
|
||||
- name: Copy sarif file to CWD
|
||||
run: cp ../results/ql.sarif ./${{ matrix.folder }}.sarif
|
||||
run: cp ../results/ql.sarif ./ql-for-ql.sarif
|
||||
- name: Fixup the $scema in sarif # Until https://github.com/microsoft/sarif-vscode-extension/pull/436/ is part in a stable release
|
||||
run: |
|
||||
sed -i 's/\$schema.*/\$schema": "https:\/\/raw.githubusercontent.com\/oasis-tcs\/sarif-spec\/master\/Schemata\/sarif-schema-2.1.0",/' ql-for-ql.sarif
|
||||
- name: Sarif as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.folder }}.sarif
|
||||
path: ${{ matrix.folder }}.sarif
|
||||
|
||||
name: ql-for-ql.sarif
|
||||
path: ql-for-ql.sarif
|
||||
- name: Split out the sarif file into langs
|
||||
run: |
|
||||
mkdir split-sarif
|
||||
node ./ql/scripts/split-sarif.js ql-for-ql.sarif split-sarif
|
||||
- name: Upload langs as artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ql-for-ql-langs
|
||||
path: split-sarif
|
||||
retention-days: 1
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
ql/target
|
||||
key: ${{ runner.os }}-qltest-cargo-${{ hashFiles('ql/**/Cargo.lock') }}
|
||||
- name: Build Extractor
|
||||
run: cd ql; env "PATH=$PATH:`dirname ${CODEQL}`" ./create-extractor-pack.sh
|
||||
run: cd ql; env "PATH=$PATH:`dirname ${CODEQL}`" ./scripts/create-extractor-pack.sh
|
||||
env:
|
||||
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
|
||||
- name: Checkout ${{ matrix.repo }}
|
||||
|
||||
2
.github/workflows/ql-for-ql-tests.yml
vendored
2
.github/workflows/ql-for-ql-tests.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
run: |
|
||||
cd ql;
|
||||
codeqlpath=$(dirname ${{ steps.find-codeql.outputs.codeql-path }});
|
||||
env "PATH=$PATH:$codeqlpath" ./create-extractor-pack.sh
|
||||
env "PATH=$PATH:$codeqlpath" ./scripts/create-extractor-pack.sh
|
||||
- name: Run QL tests
|
||||
run: |
|
||||
"${CODEQL}" test run --check-databases --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition --search-path "${{ github.workspace }}/ql/extractor-pack" --consistency-queries ql/ql/consistency-queries ql/ql/test
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* `AnalysedExpr::isNullCheck` and `AnalysedExpr::isValidCheck` have been updated to handle variable accesses on the left-hand side of the the C++ logical and variable declarations in conditions.
|
||||
@@ -46,7 +46,7 @@ predicate nullCheckExpr(Expr checkExpr, Variable var) {
|
||||
or
|
||||
exists(LogicalAndExpr op, AnalysedExpr child |
|
||||
expr = op and
|
||||
op.getRightOperand() = child and
|
||||
op.getAnOperand() = child and
|
||||
nullCheckExpr(child, v)
|
||||
)
|
||||
or
|
||||
@@ -99,7 +99,7 @@ predicate validCheckExpr(Expr checkExpr, Variable var) {
|
||||
or
|
||||
exists(LogicalAndExpr op, AnalysedExpr child |
|
||||
expr = op and
|
||||
op.getRightOperand() = child and
|
||||
op.getAnOperand() = child and
|
||||
validCheckExpr(child, v)
|
||||
)
|
||||
or
|
||||
@@ -169,7 +169,10 @@ class AnalysedExpr extends Expr {
|
||||
*/
|
||||
predicate isDef(LocalScopeVariable v) {
|
||||
this.inCondition() and
|
||||
this.(Assignment).getLValue() = v.getAnAccess()
|
||||
(
|
||||
this.(Assignment).getLValue() = v.getAnAccess() or
|
||||
this.(ConditionDeclExpr).getVariableAccess() = v.getAnAccess()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -255,8 +255,10 @@ class FunctionCall extends Call, @funbindexpr {
|
||||
/**
|
||||
* Gets the function called by this call.
|
||||
*
|
||||
* In the case of virtual function calls, the result is the most-specific function in the override tree (as
|
||||
* determined by the compiler) such that the target at runtime will be one of `result.getAnOverridingFunction*()`.
|
||||
* In the case of virtual function calls, the result is the most-specific function in the override tree
|
||||
* such that the target at runtime will be one of `result.getAnOverridingFunction*()`. The most-specific
|
||||
* function is determined by the compiler based on the compile time type of the object the function is a
|
||||
* member of.
|
||||
*/
|
||||
override Function getTarget() { funbind(underlyingElement(this), unresolveElement(result)) }
|
||||
|
||||
|
||||
@@ -7,9 +7,14 @@
|
||||
| test.cpp:15:8:15:23 | call to __builtin_expect | test.cpp:5:13:5:13 | v | is not null | is valid |
|
||||
| test.cpp:16:8:16:23 | call to __builtin_expect | test.cpp:5:13:5:13 | v | is null | is not valid |
|
||||
| test.cpp:17:9:17:17 | ... && ... | test.cpp:5:13:5:13 | v | is not null | is valid |
|
||||
| test.cpp:18:9:18:17 | ... && ... | test.cpp:5:13:5:13 | v | is not null | is not valid |
|
||||
| test.cpp:18:9:18:17 | ... && ... | test.cpp:5:13:5:13 | v | is not null | is valid |
|
||||
| test.cpp:19:9:19:18 | ... && ... | test.cpp:5:13:5:13 | v | is null | is not valid |
|
||||
| test.cpp:20:9:20:18 | ... && ... | test.cpp:5:13:5:13 | v | is not null | is not valid |
|
||||
| test.cpp:20:9:20:18 | ... && ... | test.cpp:5:13:5:13 | v | is null | is not valid |
|
||||
| test.cpp:21:9:21:14 | ... = ... | test.cpp:5:13:5:13 | v | is null | is not valid |
|
||||
| test.cpp:21:9:21:14 | ... = ... | test.cpp:7:10:7:10 | b | is not null | is valid |
|
||||
| test.cpp:22:17:22:17 | b | test.cpp:7:10:7:10 | b | is not null | is valid |
|
||||
| test.cpp:22:9:22:14 | ... = ... | test.cpp:5:13:5:13 | v | is not null | is not valid |
|
||||
| test.cpp:22:9:22:14 | ... = ... | test.cpp:7:13:7:13 | c | is not null | is not valid |
|
||||
| test.cpp:22:17:22:17 | c | test.cpp:7:13:7:13 | c | is not null | is valid |
|
||||
| test.cpp:23:21:23:21 | x | test.cpp:23:14:23:14 | x | is not null | is valid |
|
||||
| test.cpp:24:9:24:18 | (condition decl) | test.cpp:5:13:5:13 | v | is not null | is not valid |
|
||||
| test.cpp:24:9:24:18 | (condition decl) | test.cpp:24:14:24:14 | y | is not null | is valid |
|
||||
|
||||
@@ -2,7 +2,6 @@ import cpp
|
||||
|
||||
from AnalysedExpr a, LocalScopeVariable v, string isNullCheck, string isValidCheck
|
||||
where
|
||||
a.getParent() instanceof IfStmt and
|
||||
v.getAnAccess().getEnclosingStmt() = a.getParent() and
|
||||
(if a.isNullCheck(v) then isNullCheck = "is null" else isNullCheck = "is not null") and
|
||||
(if a.isValidCheck(v) then isValidCheck = "is valid" else isValidCheck = "is not valid")
|
||||
|
||||
@@ -4,7 +4,7 @@ long __builtin_expect(long);
|
||||
|
||||
void f(int *v) {
|
||||
int *w;
|
||||
bool b;
|
||||
bool b, c;
|
||||
|
||||
if (v) {}
|
||||
if (!v) {}
|
||||
@@ -19,5 +19,7 @@ void f(int *v) {
|
||||
if (true && !v) {}
|
||||
if (!v && true) {}
|
||||
if (b = !v) {}
|
||||
if (b = !v; b) {}
|
||||
if (c = !v; c) {}
|
||||
if (int *x = v; x) {}
|
||||
if (int *y = v) {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
|
||||
{
|
||||
SymmetricKey aesKey = new SymmetricKey(kid: "symencryptionkey");
|
||||
|
||||
// BAD: Using the outdated client side encryption version V1_0
|
||||
BlobEncryptionPolicy uploadPolicy = new BlobEncryptionPolicy(key: aesKey, keyResolver: null);
|
||||
BlobRequestOptions uploadOptions = new BlobRequestOptions() { EncryptionPolicy = uploadPolicy };
|
||||
|
||||
MemoryStream stream = new MemoryStream(buffer);
|
||||
blob.UploadFromStream(stream, length: size, accessCondition: null, options: uploadOptions);
|
||||
}
|
||||
|
||||
var client = new BlobClient(myConnectionString, new SpecializedBlobClientOptions()
|
||||
{
|
||||
// BAD: Using an outdated SDK that does not support client side encryption version V2_0
|
||||
ClientSideEncryption = new ClientSideEncryptionOptions()
|
||||
{
|
||||
KeyEncryptionKey = myKey,
|
||||
KeyResolver = myKeyResolver,
|
||||
KeyWrapAlgorihm = myKeyWrapAlgorithm
|
||||
}
|
||||
});
|
||||
|
||||
var client = new BlobClient(myConnectionString, new SpecializedBlobClientOptions()
|
||||
{
|
||||
// BAD: Using the outdated client side encryption version V1_0
|
||||
ClientSideEncryption = new ClientSideEncryptionOptions(ClientSideEncryptionVersion.V1_0)
|
||||
{
|
||||
KeyEncryptionKey = myKey,
|
||||
KeyResolver = myKeyResolver,
|
||||
KeyWrapAlgorihm = myKeyWrapAlgorithm
|
||||
}
|
||||
});
|
||||
|
||||
var client = new BlobClient(myConnectionString, new SpecializedBlobClientOptions()
|
||||
{
|
||||
// GOOD: Using client side encryption version V2_0
|
||||
ClientSideEncryption = new ClientSideEncryptionOptions(ClientSideEncryptionVersion.V2_0)
|
||||
{
|
||||
KeyEncryptionKey = myKey,
|
||||
KeyResolver = myKeyResolver,
|
||||
KeyWrapAlgorihm = myKeyWrapAlgorithm
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
|
||||
<overview>
|
||||
<p>Azure Storage .NET, Java, and Python SDKs support encryption on the client with a customer-managed key that is maintained in Azure Key Vault or another key store.</p>
|
||||
<p>Current release versions of the Azure Storage SDKs use cipher block chaining (CBC mode) for client-side encryption (referred to as <code>v1</code>).</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Consider switching to <code>v2</code> client-side encryption.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<sample src="UnsafeUsageOfClientSideEncryptionVersion.cs" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
<li>
|
||||
<a href="http://aka.ms/azstorageclientencryptionblog">Azure Storage Client Encryption Blog.</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-30187">CVE-2022-30187</a>
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* @name Unsafe usage of v1 version of Azure Storage client-side encryption (CVE-2022-30187).
|
||||
* @description Unsafe usage of v1 version of Azure Storage client-side encryption, please refer to http://aka.ms/azstorageclientencryptionblog
|
||||
* @kind problem
|
||||
* @tags security
|
||||
* cryptography
|
||||
* external/cwe/cwe-327
|
||||
* @id cs/azure-storage/unsafe-usage-of-client-side-encryption-version
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
*/
|
||||
|
||||
import csharp
|
||||
|
||||
/**
|
||||
* Holds if `oc` is creating an object of type `c` = `Azure.Storage.ClientSideEncryptionOptions`
|
||||
* and `e` is the `version` argument to the constructor
|
||||
*/
|
||||
predicate isCreatingAzureClientSideEncryptionObject(ObjectCreation oc, Class c, Expr e) {
|
||||
exists(Parameter p | p.hasName("version") |
|
||||
c.hasQualifiedName("Azure.Storage.ClientSideEncryptionOptions") and
|
||||
oc.getTarget() = c.getAConstructor() and
|
||||
e = oc.getArgumentForParameter(p)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `oc` is an object creation of the outdated type `c` = `Microsoft.Azure.Storage.Blob.BlobEncryptionPolicy`
|
||||
*/
|
||||
predicate isCreatingOutdatedAzureClientSideEncryptionObject(ObjectCreation oc, Class c) {
|
||||
c.hasQualifiedName("Microsoft.Azure.Storage.Blob.BlobEncryptionPolicy") and
|
||||
oc.getTarget() = c.getAConstructor()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the Azure.Storage assembly for `c` is a version known to support
|
||||
* version 2+ for client-side encryption
|
||||
*/
|
||||
predicate doesAzureStorageAssemblySupportSafeClientSideEncryption(Assembly asm) {
|
||||
exists(int versionCompare |
|
||||
versionCompare = asm.getVersion().compareTo("12.12.0.0") and
|
||||
versionCompare >= 0
|
||||
) and
|
||||
asm.getName() = "Azure.Storage.Common"
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the Azure.Storage assembly for `c` is a version known to support
|
||||
* version 2+ for client-side encryption and if the argument for the constructor `version`
|
||||
* is set to a secure value.
|
||||
*/
|
||||
predicate isObjectCreationArgumentSafeAndUsingSafeVersionOfAssembly(Expr versionExpr, Assembly asm) {
|
||||
// Check if the Azure.Storage assembly version has the fix
|
||||
doesAzureStorageAssemblySupportSafeClientSideEncryption(asm) and
|
||||
// and that the version argument for the constructor is guaranteed to be Version2
|
||||
isExprAnAccessToSafeClientSideEncryptionVersionValue(versionExpr)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the expression `e` is an access to a safe version of the enum `ClientSideEncryptionVersion`
|
||||
* or an equivalent numeric value
|
||||
*/
|
||||
predicate isExprAnAccessToSafeClientSideEncryptionVersionValue(Expr e) {
|
||||
exists(EnumConstant ec |
|
||||
ec.hasQualifiedName("Azure.Storage.ClientSideEncryptionVersion.V2_0") and
|
||||
ec.getAnAccess() = e
|
||||
)
|
||||
}
|
||||
|
||||
from Expr e, Class c, Assembly asm
|
||||
where
|
||||
asm = c.getLocation() and
|
||||
(
|
||||
exists(Expr e2 |
|
||||
isCreatingAzureClientSideEncryptionObject(e, c, e2) and
|
||||
not isObjectCreationArgumentSafeAndUsingSafeVersionOfAssembly(e2, asm)
|
||||
)
|
||||
or
|
||||
isCreatingOutdatedAzureClientSideEncryptionObject(e, c)
|
||||
)
|
||||
select e, "Unsafe usage of v1 version of Azure Storage client-side encryption."
|
||||
@@ -68,3 +68,11 @@ This command downloads all dependencies to the shared cache on the local disk.
|
||||
Note
|
||||
|
||||
Running the ``codeql pack add`` and ``codeql pack install`` commands will generate or update the ``qlpack.lock.yml`` file. This file should be checked-in to version control. The ``qlpack.lock.yml`` file contains the precise version numbers used by the pack.
|
||||
|
||||
.. pull-quote::
|
||||
|
||||
Note
|
||||
|
||||
By default ``codeql pack install`` will install dependencies from the Container registry on GitHub.com.
|
||||
You can install dependencies from a GitHub Enterprise Server Container registry by creating a ``qlconfig.yml`` file.
|
||||
For more information, see ":doc:`Publishing and using CodeQL packs <publishing-and-using-codeql-packs>`."
|
||||
|
||||
@@ -72,3 +72,53 @@ The ``analyze`` command will run the default suite of any specified CodeQL packs
|
||||
::
|
||||
|
||||
codeql <database> analyze <scope>/<pack> <scope>/<other-pack>
|
||||
|
||||
Working with CodeQL packs on GitHub Enterprise Server
|
||||
-----------------------------------------------------
|
||||
|
||||
.. pull-quote::
|
||||
|
||||
Note
|
||||
|
||||
The Container registry for GitHub Enterprise Server supports CodeQL query packs from GitHub Enterprise Server 3.6 onward.
|
||||
|
||||
By default, the CodeQL CLI expects to download CodeQL packs from and publish packs to the Container registry on GitHub.com. However, you can also work with CodeQL packs in a Container registry on GitHub Enterprise Server 3.6, and later, by creating a ``qlconfig.yml`` file to tell the CLI which Container registry to use for each pack.
|
||||
|
||||
Create a ``~/.codeql/qlconfig.yml`` file using your preferred text editor, and add entries to specify which registry to use for one or more package name patterns.
|
||||
For example, the following ``qlconfig.yml`` file associates all packs with the Container registry for the GitHub Enterprise Server at ``GHE_HOSTNAME``, except packs matching ``codeql/*``, which are associated with the Container registry on GitHub.com:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
registries:
|
||||
- packages: 'codeql/*'
|
||||
url: https://ghcr.io/v2/
|
||||
- packages: '*'
|
||||
url: https://containers.GHE_HOSTNAME/v2/
|
||||
|
||||
The CodeQL CLI will determine which registry to use for a given package name by finding the first item in the ``registries`` list with a ``packages`` property that matches that package name.
|
||||
This means that you'll generally want to define the most specific package name patterns first.
|
||||
|
||||
You can now use ``codeql pack publish``, ``codeql pack download``, and ``codeql database analyze`` to manage packs on GitHub Enterprise Server.
|
||||
|
||||
Authenticating to GitHub Container registries
|
||||
---------------------------------------------
|
||||
|
||||
You can publish packs and download private packs by authenticating to the appropriate GitHub Container registry.
|
||||
|
||||
You can authenticate to the Container registry on GitHub.com in two ways:
|
||||
|
||||
1. Pass the ``--github-auth-stdin`` option to the CodeQL CLI, then supply a GitHub Apps token or personal access token via standard input.
|
||||
2. Set the ``GITHUB_TOKEN`` environment variable to a GitHub Apps token or personal access token.
|
||||
|
||||
Similarly, you can authenticate to a GHES Container registry, or authenticate to multiple registries simultaneously (for example, to download or run private packs from multiple registries) in two ways:
|
||||
|
||||
1. Pass the ``--registries-auth-stdin`` option to the CodeQL CLI, then supply a registry authentication string via standard input.
|
||||
2. Set the ``CODEQL_REGISTRIES_AUTH`` environment variable to a registry authentication string.
|
||||
|
||||
A registry authentication string is a comma-separated list of ``<registry-url>=<token>`` pairs, where ``registry-url`` is a GitHub Container registry URL, such as ``https://containers.GHE_HOSTNAME/v2/``, and ``token`` is a GitHub Apps token or personal access token for that GitHub Container registry.
|
||||
This ensures that each token is only passed to the Container registry you specify.
|
||||
For instance, the following registry authentication string specifies that the CodeQL CLI should authenticate to the Container registry on GitHub.com using the token ``<token1>`` and to the Container registry for the GHES instance at ``GHE_HOSTNAME`` using the token ``<token2>``:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
https://ghcr.io/v2/=<token1>,https://containers.GHE_HOSTNAME/v2/=<token2>
|
||||
|
||||
@@ -3,7 +3,9 @@ CodeQL query help for C and C++
|
||||
|
||||
.. include:: ../reusables/query-help-overview.rst
|
||||
|
||||
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/main/cpp/ql/examples>`__.
|
||||
These queries are published in the CodeQL query pack ``codeql/cpp-queries`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/cpp/ql/src/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/cpp/ql/src>`__).
|
||||
|
||||
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/codeql-cli/latest/cpp/ql/examples>`__.
|
||||
|
||||
.. include:: toc-cpp.rst
|
||||
|
||||
@@ -3,6 +3,8 @@ CodeQL query help for C#
|
||||
|
||||
.. include:: ../reusables/query-help-overview.rst
|
||||
|
||||
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/main/csharp/ql/examples>`__.
|
||||
These queries are published in the CodeQL query pack ``codeql/csharp-queries`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/csharp/ql/src/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/csharp/ql/src>`__).
|
||||
|
||||
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/codeql-cli/latest/csharp/ql/examples>`__.
|
||||
|
||||
.. include:: toc-csharp.rst
|
||||
@@ -3,6 +3,8 @@ CodeQL query help for Go
|
||||
|
||||
.. include:: ../reusables/query-help-overview.rst
|
||||
|
||||
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/main/go/ql/examples>`__.
|
||||
These queries are published in the CodeQL query pack ``codeql/go-queries`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/go/ql/src/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/go/ql/src>`__).
|
||||
|
||||
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/codeql-cli/latest/go/ql/examples>`__.
|
||||
|
||||
.. include:: toc-go.rst
|
||||
|
||||
@@ -3,6 +3,8 @@ CodeQL query help for Java
|
||||
|
||||
.. include:: ../reusables/query-help-overview.rst
|
||||
|
||||
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/main/java/ql/examples>`__.
|
||||
These queries are published in the CodeQL query pack ``codeql/java-queries`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/java/ql/src/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/java/ql/src>`__).
|
||||
|
||||
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/codeql-cli/latest/java/ql/examples>`__.
|
||||
|
||||
.. include:: toc-java.rst
|
||||
|
||||
@@ -3,6 +3,8 @@ CodeQL query help for JavaScript
|
||||
|
||||
.. include:: ../reusables/query-help-overview.rst
|
||||
|
||||
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/main/javascript/ql/examples>`__.
|
||||
These queries are published in the CodeQL query pack ``codeql/javascript-queries`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/javascript/ql/src/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/javascript/ql/src>`__).
|
||||
|
||||
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/codeql-cli/latest/javascript/ql/examples>`__.
|
||||
|
||||
.. include:: toc-javascript.rst
|
||||
@@ -3,6 +3,8 @@ CodeQL query help for Python
|
||||
|
||||
.. include:: ../reusables/query-help-overview.rst
|
||||
|
||||
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/main/python/ql/examples>`__.
|
||||
These queries are published in the CodeQL query pack ``codeql/python-queries`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/python/ql/src/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/python/ql/src>`__).
|
||||
|
||||
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/codeql-cli/latest/python/ql/examples>`__.
|
||||
|
||||
.. include:: toc-python.rst
|
||||
@@ -3,6 +3,8 @@ CodeQL query help for Ruby
|
||||
|
||||
.. include:: ../reusables/query-help-overview.rst
|
||||
|
||||
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/main/ruby/ql/examples>`__.
|
||||
These queries are published in the CodeQL query pack ``codeql/ruby-queries`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/ruby/ql/src/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/ruby/ql/src>`__).
|
||||
|
||||
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/codeql-cli/latest/ruby/ql/examples>`__.
|
||||
|
||||
.. include:: toc-ruby.rst
|
||||
|
||||
@@ -36,7 +36,7 @@ java.lang,13,,58,,,,,,,,,,,8,,,,,4,,,1,,,,,,,,,,,,,,,46,12
|
||||
java.net,10,3,7,,,,,,,,,,,,,,10,,,,,,,,,,,,,,,,,,,3,7,
|
||||
java.nio,15,,6,,13,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,6,
|
||||
java.sql,11,,,,,,,,,4,,,,,,,,,,,,,,,,7,,,,,,,,,,,,
|
||||
java.util,44,,438,,,,,,,,,,,34,,,,,,5,2,,1,2,,,,,,,,,,,,,24,414
|
||||
java.util,44,,441,,,,,,,,,,,34,,,,,,5,2,,1,2,,,,,,,,,,,,,24,417
|
||||
javax.faces.context,2,7,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,7,,
|
||||
javax.jms,,9,57,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9,57,
|
||||
javax.json,,,123,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,100,23
|
||||
|
||||
|
@@ -15,9 +15,9 @@ Java framework & library support
|
||||
`Apache HttpComponents <https://hc.apache.org/>`_,"``org.apache.hc.core5.*``, ``org.apache.http``",5,136,28,,,3,,,,25
|
||||
`Google Guava <https://guava.dev/>`_,``com.google.common.*``,,728,39,,6,,,,,
|
||||
`JSON-java <https://github.com/stleary/JSON-java>`_,``org.json``,,236,,,,,,,,
|
||||
Java Standard Library,``java.*``,3,549,130,28,,,7,,,10
|
||||
Java Standard Library,``java.*``,3,552,130,28,,,7,,,10
|
||||
Java extensions,"``javax.*``, ``jakarta.*``",63,609,32,,,4,,1,1,2
|
||||
`Spring <https://spring.io/>`_,``org.springframework.*``,29,476,101,,,,19,14,,29
|
||||
Others,"``androidx.slice``, ``cn.hutool.core.codec``, ``com.esotericsoftware.kryo.io``, ``com.esotericsoftware.kryo5.io``, ``com.fasterxml.jackson.core``, ``com.fasterxml.jackson.databind``, ``com.opensymphony.xwork2.ognl``, ``com.rabbitmq.client``, ``com.unboundid.ldap.sdk``, ``com.zaxxer.hikari``, ``flexjson``, ``groovy.lang``, ``groovy.util``, ``jodd.json``, ``kotlin.jvm.internal``, ``net.sf.saxon.s9api``, ``ognl``, ``okhttp3``, ``org.apache.commons.codec``, ``org.apache.commons.jexl2``, ``org.apache.commons.jexl3``, ``org.apache.commons.logging``, ``org.apache.commons.ognl``, ``org.apache.directory.ldap.client.api``, ``org.apache.ibatis.jdbc``, ``org.apache.log4j``, ``org.apache.logging.log4j``, ``org.apache.shiro.codec``, ``org.apache.shiro.jndi``, ``org.codehaus.groovy.control``, ``org.dom4j``, ``org.hibernate``, ``org.jboss.logging``, ``org.jdbi.v3.core``, ``org.jooq``, ``org.mvel2``, ``org.scijava.log``, ``org.slf4j``, ``org.xml.sax``, ``org.xmlpull.v1``, ``play.mvc``, ``ratpack.core.form``, ``ratpack.core.handling``, ``ratpack.core.http``, ``ratpack.exec``, ``ratpack.form``, ``ratpack.func``, ``ratpack.handling``, ``ratpack.http``, ``ratpack.util``, ``retrofit2``",65,395,932,,,,14,18,,3
|
||||
Totals,,217,6410,1474,117,6,10,107,33,1,84
|
||||
Totals,,217,6413,1474,117,6,10,107,33,1,84
|
||||
|
||||
|
||||
@@ -111,14 +111,26 @@ open class KotlinUsesExtractor(
|
||||
}
|
||||
data class TypeResults(val javaResult: TypeResult<DbType>, val kotlinResult: TypeResult<DbKt_type>)
|
||||
|
||||
fun useType(t: IrType, context: TypeContext = TypeContext.OTHER) =
|
||||
fun useType(t: IrType, context: TypeContext = TypeContext.OTHER): TypeResults {
|
||||
when(t) {
|
||||
is IrSimpleType -> useSimpleType(t, context)
|
||||
is IrSimpleType -> return useSimpleType(t, context)
|
||||
else -> {
|
||||
logger.error("Unrecognised IrType: " + t.javaClass)
|
||||
TypeResults(TypeResult(fakeLabel(), "unknown", "unknown"), TypeResult(fakeLabel(), "unknown", "unknown"))
|
||||
return extractErrorType()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractErrorType(): TypeResults {
|
||||
val typeId = tw.getLabelFor<DbErrortype>("@\"errorType\"") {
|
||||
tw.writeError_type(it)
|
||||
}
|
||||
val kotlinTypeId = tw.getLabelFor<DbKt_nullable_type>("@\"errorKotlinType\"") {
|
||||
tw.writeKt_nullable_types(it, typeId)
|
||||
}
|
||||
return TypeResults(TypeResult(typeId, null, "<CodeQL error type>"),
|
||||
TypeResult(kotlinTypeId, null, "<CodeQL error type>"))
|
||||
}
|
||||
|
||||
fun getJavaEquivalentClass(c: IrClass) =
|
||||
getJavaEquivalentClassId(c)?.let { pluginContext.referenceClass(it.asSingleFqName()) }?.owner
|
||||
|
||||
@@ -16,7 +16,8 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
class CommentExtractor(private val fileExtractor: KotlinFileExtractor, private val file: IrFile, private val fileLabel: Label<out DbFile>) {
|
||||
private val tw = fileExtractor.tw
|
||||
private val logger = fileExtractor.logger
|
||||
private val ktFile = Psi2Ir().getKtFile(file)
|
||||
private val psi2Ir = Psi2Ir(logger)
|
||||
private val ktFile = psi2Ir.getKtFile(file)
|
||||
|
||||
fun extract() {
|
||||
if (ktFile == null) {
|
||||
@@ -85,7 +86,7 @@ class CommentExtractor(private val fileExtractor: KotlinFileExtractor, private v
|
||||
val ownerPsi = getKDocOwner(comment) ?: return
|
||||
|
||||
val owners = mutableListOf<IrElement>()
|
||||
file.accept(IrVisitorLookup(ownerPsi, file), owners)
|
||||
file.accept(IrVisitorLookup(psi2Ir, ownerPsi, file), owners)
|
||||
|
||||
for (ownerIr in owners) {
|
||||
val ownerLabel =
|
||||
|
||||
@@ -8,7 +8,7 @@ import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.util.isFakeOverride
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
|
||||
|
||||
class IrVisitorLookup(private val psi: PsiElement, private val file: IrFile) :
|
||||
class IrVisitorLookup(private val psi2Ir: Psi2Ir, private val psi: PsiElement, private val file: IrFile) :
|
||||
IrElementVisitor<Unit, MutableCollection<IrElement>> {
|
||||
private val location = psi.getLocation()
|
||||
|
||||
@@ -27,7 +27,7 @@ class IrVisitorLookup(private val psi: PsiElement, private val file: IrFile) :
|
||||
}
|
||||
|
||||
if (location.contains(elementLocation)) {
|
||||
val psiElement = Psi2Ir().findPsiElement(element, file)
|
||||
val psiElement = psi2Ir.findPsiElement(element, file)
|
||||
if (psiElement == psi) {
|
||||
// There can be multiple IrElements that match the same PSI element.
|
||||
data.add(element)
|
||||
@@ -35,4 +35,4 @@ class IrVisitorLookup(private val psi: PsiElement, private val file: IrFile) :
|
||||
}
|
||||
element.acceptChildren(this, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
package com.github.codeql.utils.versions
|
||||
|
||||
import com.github.codeql.FileLogger
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi2ir.PsiSourceManager
|
||||
|
||||
class Psi2Ir : Psi2IrFacade {
|
||||
companion object {
|
||||
val psiManager = PsiSourceManager()
|
||||
}
|
||||
|
||||
class Psi2Ir(private val logger: FileLogger) : Psi2IrFacade {
|
||||
override fun getKtFile(irFile: IrFile): KtFile? {
|
||||
return psiManager.getKtFile(irFile)
|
||||
logger.warn("Comment extraction is not supported for Kotlin < 1.5.20")
|
||||
return null
|
||||
}
|
||||
|
||||
override fun findPsiElement(irElement: IrElement, irFile: IrFile): PsiElement? {
|
||||
return psiManager.findPsiElement(irElement, irFile)
|
||||
logger.error("Attempted comment extraction for Kotlin < 1.5.20")
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
package com.github.codeql.utils.versions
|
||||
|
||||
import com.github.codeql.FileLogger
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi2ir.PsiSourceManager
|
||||
|
||||
class Psi2Ir : Psi2IrFacade {
|
||||
companion object {
|
||||
val psiManager = PsiSourceManager()
|
||||
}
|
||||
|
||||
class Psi2Ir(private val logger: FileLogger) : Psi2IrFacade {
|
||||
override fun getKtFile(irFile: IrFile): KtFile? {
|
||||
return psiManager.getKtFile(irFile)
|
||||
logger.warn("Comment extraction is not supported for Kotlin < 1.5.20")
|
||||
return null
|
||||
}
|
||||
|
||||
override fun findPsiElement(irElement: IrElement, irFile: IrFile): PsiElement? {
|
||||
return psiManager.findPsiElement(irElement, irFile)
|
||||
logger.error("Attempted comment extraction for Kotlin < 1.5.20")
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
package com.github.codeql.utils.versions
|
||||
|
||||
import com.github.codeql.FileLogger
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi2ir.PsiSourceManager
|
||||
|
||||
class Psi2Ir : Psi2IrFacade {
|
||||
companion object {
|
||||
val psiManager = PsiSourceManager()
|
||||
}
|
||||
|
||||
class Psi2Ir(private val logger: FileLogger) : Psi2IrFacade {
|
||||
override fun getKtFile(irFile: IrFile): KtFile? {
|
||||
return psiManager.getKtFile(irFile)
|
||||
logger.warn("Comment extraction is not supported for Kotlin < 1.5.20")
|
||||
return null
|
||||
}
|
||||
|
||||
override fun findPsiElement(irElement: IrElement, irFile: IrFile): PsiElement? {
|
||||
return psiManager.findPsiElement(irElement, irFile)
|
||||
logger.error("Attempted comment extraction for Kotlin < 1.5.20")
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.github.codeql.utils.versions
|
||||
|
||||
import com.github.codeql.FileLogger
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.backend.common.psi.PsiSourceManager
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.getKtFile
|
||||
@@ -7,7 +8,7 @@ import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
class Psi2Ir: Psi2IrFacade {
|
||||
class Psi2Ir(private val logger: FileLogger): Psi2IrFacade {
|
||||
override fun getKtFile(irFile: IrFile): KtFile? {
|
||||
return irFile.getKtFile()
|
||||
}
|
||||
@@ -15,4 +16,4 @@ class Psi2Ir: Psi2IrFacade {
|
||||
override fun findPsiElement(irElement: IrElement, irFile: IrFile): PsiElement? {
|
||||
return PsiSourceManager.findPsiElement(irElement, irFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.github.codeql.utils.versions
|
||||
|
||||
import com.github.codeql.FileLogger
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.backend.common.psi.PsiSourceManager
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.getKtFile
|
||||
@@ -7,7 +8,7 @@ import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
class Psi2Ir: Psi2IrFacade {
|
||||
class Psi2Ir(private val logger: FileLogger): Psi2IrFacade {
|
||||
override fun getKtFile(irFile: IrFile): KtFile? {
|
||||
return irFile.getKtFile()
|
||||
}
|
||||
@@ -15,4 +16,4 @@ class Psi2Ir: Psi2IrFacade {
|
||||
override fun findPsiElement(irElement: IrElement, irFile: IrFile): PsiElement? {
|
||||
return PsiSourceManager.findPsiElement(irElement, irFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.github.codeql.utils.versions
|
||||
|
||||
import com.github.codeql.FileLogger
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.backend.common.psi.PsiSourceManager
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.getKtFile
|
||||
@@ -7,7 +8,7 @@ import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
class Psi2Ir: Psi2IrFacade {
|
||||
class Psi2Ir(private val logger: FileLogger): Psi2IrFacade {
|
||||
override fun getKtFile(irFile: IrFile): KtFile? {
|
||||
return irFile.getKtFile()
|
||||
}
|
||||
@@ -15,4 +16,4 @@ class Psi2Ir: Psi2IrFacade {
|
||||
override fun findPsiElement(irElement: IrElement, irFile: IrFile): PsiElement? {
|
||||
return PsiSourceManager.findPsiElement(irElement, irFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.github.codeql.utils.versions
|
||||
|
||||
import com.github.codeql.FileLogger
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.backend.common.psi.PsiSourceManager
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.getKtFile
|
||||
@@ -7,7 +8,7 @@ import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
class Psi2Ir: Psi2IrFacade {
|
||||
class Psi2Ir(private val logger: FileLogger): Psi2IrFacade {
|
||||
override fun getKtFile(irFile: IrFile): KtFile? {
|
||||
return irFile.getKtFile()
|
||||
}
|
||||
@@ -15,4 +16,4 @@ class Psi2Ir: Psi2IrFacade {
|
||||
override fun findPsiElement(irElement: IrElement, irFile: IrFile): PsiElement? {
|
||||
return PsiSourceManager.findPsiElement(irElement, irFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.github.codeql.utils.versions
|
||||
|
||||
import com.github.codeql.FileLogger
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.backend.common.psi.PsiSourceManager
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.getKtFile
|
||||
@@ -7,7 +8,7 @@ import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
class Psi2Ir: Psi2IrFacade {
|
||||
class Psi2Ir(private val logger: FileLogger): Psi2IrFacade {
|
||||
override fun getKtFile(irFile: IrFile): KtFile? {
|
||||
return irFile.getKtFile()
|
||||
}
|
||||
@@ -15,4 +16,4 @@ class Psi2Ir: Psi2IrFacade {
|
||||
override fun findPsiElement(irElement: IrElement, irFile: IrFile): PsiElement? {
|
||||
return PsiSourceManager.findPsiElement(irElement, irFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4
java/ql/lib/change-notes/2022-07-12-errortype.md
Normal file
4
java/ql/lib/change-notes/2022-07-12-errortype.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: feature
|
||||
---
|
||||
* Added an `ErrorType` class. An instance of this class will be used if an extractor is unable to extract a type, or if an up/downgrade script is unable to provide a type.
|
||||
4
java/ql/lib/change-notes/2022-07-13-properites.md
Normal file
4
java/ql/lib/change-notes/2022-07-13-properites.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added data-flow models for `java.util.Properites`. Additional results may be found where relevant data is stored in and then retrieved from a `Properties` instance.
|
||||
@@ -332,6 +332,14 @@ modifiers(
|
||||
string nodeName: string ref
|
||||
);
|
||||
|
||||
/**
|
||||
* An errortype is used when the extractor is unable to extract a type
|
||||
* correctly for some reason.
|
||||
*/
|
||||
error_type(
|
||||
unique int id: @errortype
|
||||
);
|
||||
|
||||
classes(
|
||||
unique int id: @class,
|
||||
string nodeName: string ref,
|
||||
@@ -1012,13 +1020,13 @@ javadocText(
|
||||
@classorinterfaceorpackage = @classorinterface | @package;
|
||||
@classorinterfaceorcallable = @classorinterface | @callable;
|
||||
@boundedtype = @typevariable | @wildcard;
|
||||
@reftype = @classorinterface | @array | @boundedtype;
|
||||
@reftype = @classorinterface | @array | @boundedtype | @errortype;
|
||||
@classorarray = @class | @array;
|
||||
@type = @primitive | @reftype;
|
||||
@callable = @method | @constructor;
|
||||
|
||||
/** A program element that has a name. */
|
||||
@element = @package | @modifier | @annotation |
|
||||
@element = @package | @modifier | @annotation | @errortype |
|
||||
@locatableElement;
|
||||
|
||||
@locatableElement = @file | @primitive | @class | @interface | @method | @constructor | @param | @exception | @field |
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -47,6 +47,8 @@ predicate hasName(Element e, string name) {
|
||||
kt_type_alias(e, name, _)
|
||||
or
|
||||
ktProperties(e, name)
|
||||
or
|
||||
e instanceof ErrorType and name = "<CodeQL error type>"
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -413,8 +413,12 @@ class RefType extends Type, Annotatable, Modifiable, @reftype {
|
||||
/** Gets a direct or indirect supertype of this type, including itself. */
|
||||
RefType getAnAncestor() { hasDescendant(result, this) }
|
||||
|
||||
/** Gets a direct or indirect supertype of this type, not including itself. */
|
||||
RefType getAStrictAncestor() { result = this.getAnAncestor() and result != this }
|
||||
/**
|
||||
* Gets a direct or indirect supertype of this type.
|
||||
* This does not including itself, unless this type is part of a cycle
|
||||
* in the type hierarchy.
|
||||
*/
|
||||
RefType getAStrictAncestor() { result = this.getASupertype().getAnAncestor() }
|
||||
|
||||
/**
|
||||
* Gets the source declaration of a direct supertype of this type, excluding itself.
|
||||
@@ -666,6 +670,14 @@ class RefType extends Type, Annotatable, Modifiable, @reftype {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An `ErrorType` is generated when CodeQL is unable to correctly
|
||||
* extract a type.
|
||||
*/
|
||||
class ErrorType extends RefType, @errortype {
|
||||
override string getAPrimaryQlClass() { result = "ErrorType" }
|
||||
}
|
||||
|
||||
/** A type that is the same as its source declaration. */
|
||||
class SrcRefType extends RefType {
|
||||
SrcRefType() { this.isSourceDeclaration() }
|
||||
|
||||
@@ -241,6 +241,9 @@ private class ContainerFlowSummaries extends SummaryModelCsv {
|
||||
"java.util;NavigableSet;true;pollLast;();;Argument[-1].Element;ReturnValue;value;manual",
|
||||
"java.util;NavigableSet;true;subSet;(Object,boolean,Object,boolean);;Argument[-1].Element;ReturnValue.Element;value;manual",
|
||||
"java.util;NavigableSet;true;tailSet;(Object,boolean);;Argument[-1].Element;ReturnValue.Element;value;manual",
|
||||
"java.util;Properties;true;getProperty;(String);;Argument[-1].MapValue;ReturnValue;value;manual",
|
||||
"java.util;Properties;true;getProperty;(String,String);;Argument[-1].MapValue;ReturnValue;value;manual",
|
||||
"java.util;Properties;true;getProperty;(String,String);;Argument[1];ReturnValue;value;manual",
|
||||
"java.util;Scanner;true;next;(Pattern);;Argument[-1];ReturnValue;taint;manual",
|
||||
"java.util;Scanner;true;next;(String);;Argument[-1];ReturnValue;taint;manual",
|
||||
"java.util;SortedMap;true;headMap;(Object);;Argument[-1].MapKey;ReturnValue.MapKey;value;manual",
|
||||
|
||||
@@ -10,13 +10,11 @@ class TypeProperty extends Class {
|
||||
}
|
||||
|
||||
/** The `getProperty` method of the class `java.util.Properties`. */
|
||||
class PropertiesGetPropertyMethod extends ValuePreservingMethod {
|
||||
class PropertiesGetPropertyMethod extends Method {
|
||||
PropertiesGetPropertyMethod() {
|
||||
getDeclaringType() instanceof TypeProperty and
|
||||
hasName("getProperty")
|
||||
}
|
||||
|
||||
override predicate returnsValue(int arg) { arg = 1 }
|
||||
}
|
||||
|
||||
/** The `get` method of the class `java.util.Properties`. */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1236
java/ql/lib/upgrades/cf58c7d9b1fa1eae9cdc20ce8f157c140ac0c3de/semmlecode.dbscheme
Executable file
1236
java/ql/lib/upgrades/cf58c7d9b1fa1eae9cdc20ce8f157c140ac0c3de/semmlecode.dbscheme
Executable file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
description: Add errortype
|
||||
compatibility: full
|
||||
@@ -0,0 +1,46 @@
|
||||
|
||||
// BAD: Using an outdated SDK that does not support client side encryption version V2_0
|
||||
new EncryptedBlobClientBuilder()
|
||||
.blobClient(blobClient)
|
||||
.key(resolver.buildAsyncKeyEncryptionKey(keyid).block(), keyWrapAlgorithm)
|
||||
.buildEncryptedBlobClient()
|
||||
.uploadWithResponse(new BlobParallelUploadOptions(data)
|
||||
.setMetadata(metadata)
|
||||
.setHeaders(headers)
|
||||
.setTags(tags)
|
||||
.setTier(tier)
|
||||
.setRequestConditions(requestConditions)
|
||||
.setComputeMd5(computeMd5)
|
||||
.setParallelTransferOptions(parallelTransferOptions),
|
||||
timeout, context);
|
||||
|
||||
// BAD: Using the deprecatedd client side encryption version V1_0
|
||||
new EncryptedBlobClientBuilder(EncryptionVersion.V1)
|
||||
.blobClient(blobClient)
|
||||
.key(resolver.buildAsyncKeyEncryptionKey(keyid).block(), keyWrapAlgorithm)
|
||||
.buildEncryptedBlobClient()
|
||||
.uploadWithResponse(new BlobParallelUploadOptions(data)
|
||||
.setMetadata(metadata)
|
||||
.setHeaders(headers)
|
||||
.setTags(tags)
|
||||
.setTier(tier)
|
||||
.setRequestConditions(requestConditions)
|
||||
.setComputeMd5(computeMd5)
|
||||
.setParallelTransferOptions(parallelTransferOptions),
|
||||
timeout, context);
|
||||
|
||||
|
||||
// GOOD: Using client side encryption version V2_0
|
||||
new EncryptedBlobClientBuilder(EncryptionVersion.V2)
|
||||
.blobClient(blobClient)
|
||||
.key(resolver.buildAsyncKeyEncryptionKey(keyid).block(), keyWrapAlgorithm)
|
||||
.buildEncryptedBlobClient()
|
||||
.uploadWithResponse(new BlobParallelUploadOptions(data)
|
||||
.setMetadata(metadata)
|
||||
.setHeaders(headers)
|
||||
.setTags(tags)
|
||||
.setTier(tier)
|
||||
.setRequestConditions(requestConditions)
|
||||
.setComputeMd5(computeMd5)
|
||||
.setParallelTransferOptions(parallelTransferOptions),
|
||||
timeout, context);
|
||||
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
|
||||
<overview>
|
||||
<p>Azure Storage .NET, Java, and Python SDKs support encryption on the client with a customer-managed key that is maintained in Azure Key Vault or another key store.</p>
|
||||
<p>The Azure Storage SDK version 12.18.0 or later supports version <code>V2</code> for client-side encryption. All previous versions of Azure Storage SDK only support client-side encryption <code>V1</code> which is unsafe.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Consider switching to <code>V2</code> client-side encryption.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<sample src="UnsafeUsageOfClientSideEncryptionVersion.java" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
<li>
|
||||
<a href="http://aka.ms/azstorageclientencryptionblog">Azure Storage Client Encryption Blog.</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-30187">CVE-2022-30187</a>
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* @name Unsafe usage of v1 version of Azure Storage client-side encryption (CVE-2022-30187).
|
||||
* @description Unsafe usage of v1 version of Azure Storage client-side encryption, please refer to http://aka.ms/azstorageclientencryptionblog
|
||||
* @kind problem
|
||||
* @tags security
|
||||
* cryptography
|
||||
* external/cwe/cwe-327
|
||||
* @id java/azure-storage/unsafe-client-side-encryption-in-use
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.dataflow.DataFlow
|
||||
|
||||
/**
|
||||
* Holds if `call` is an object creation for a class `EncryptedBlobClientBuilder`
|
||||
* that takes no arguments, which means that it is using V1 encryption.
|
||||
*/
|
||||
predicate isCreatingOutdatedAzureClientSideEncryptionObject(Call call, Class c) {
|
||||
exists(string package, string type, Constructor constructor |
|
||||
c.hasQualifiedName(package, type) and
|
||||
c.getAConstructor() = constructor and
|
||||
call.getCallee() = constructor and
|
||||
(
|
||||
type = "EncryptedBlobClientBuilder" and
|
||||
package = "com.azure.storage.blob.specialized.cryptography" and
|
||||
constructor.hasNoParameters()
|
||||
or
|
||||
type = "BlobEncryptionPolicy" and package = "com.microsoft.azure.storage.blob"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `call` is an object creation for a class `EncryptedBlobClientBuilder`
|
||||
* that takes `versionArg` as the argument specifying the encryption version.
|
||||
*/
|
||||
predicate isCreatingAzureClientSideEncryptionObjectNewVersion(Call call, Class c, Expr versionArg) {
|
||||
exists(string package, string type, Constructor constructor |
|
||||
c.hasQualifiedName(package, type) and
|
||||
c.getAConstructor() = constructor and
|
||||
call.getCallee() = constructor and
|
||||
type = "EncryptedBlobClientBuilder" and
|
||||
package = "com.azure.storage.blob.specialized.cryptography" and
|
||||
versionArg = call.getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A dataflow config that tracks `EncryptedBlobClientBuilder.version` argument initialization.
|
||||
*/
|
||||
private class EncryptedBlobClientBuilderSafeEncryptionVersionConfig extends DataFlow::Configuration {
|
||||
EncryptedBlobClientBuilderSafeEncryptionVersionConfig() {
|
||||
this = "EncryptedBlobClientBuilderSafeEncryptionVersionConfig"
|
||||
}
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
exists(FieldRead fr, Field f | fr = source.asExpr() |
|
||||
f.getAnAccess() = fr and
|
||||
f.hasQualifiedName("com.azure.storage.blob.specialized.cryptography", "EncryptionVersion",
|
||||
"V2")
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
isCreatingAzureClientSideEncryptionObjectNewVersion(_, _, sink.asExpr())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `call` is an object creation for a class `EncryptedBlobClientBuilder`
|
||||
* that takes `versionArg` as the argument specifying the encryption version, and that version is safe.
|
||||
*/
|
||||
predicate isCreatingSafeAzureClientSideEncryptionObject(Call call, Class c, Expr versionArg) {
|
||||
isCreatingAzureClientSideEncryptionObjectNewVersion(call, c, versionArg) and
|
||||
exists(EncryptedBlobClientBuilderSafeEncryptionVersionConfig config, DataFlow::Node sink |
|
||||
sink.asExpr() = versionArg
|
||||
|
|
||||
config.hasFlow(_, sink)
|
||||
)
|
||||
}
|
||||
|
||||
from Expr e, Class c
|
||||
where
|
||||
exists(Expr argVersion |
|
||||
isCreatingAzureClientSideEncryptionObjectNewVersion(e, c, argVersion) and
|
||||
not isCreatingSafeAzureClientSideEncryptionObject(e, c, argVersion)
|
||||
)
|
||||
or
|
||||
isCreatingOutdatedAzureClientSideEncryptionObject(e, c)
|
||||
select e, "Unsafe usage of v1 version of Azure Storage client-side encryption."
|
||||
@@ -78,4 +78,14 @@ public class Test {
|
||||
sink(x18); // Flow
|
||||
});
|
||||
}
|
||||
|
||||
public void run4() {
|
||||
Properties p = new Properties();
|
||||
p.put("key", tainted);
|
||||
sink(p.getProperty("key")); // Flow
|
||||
sink(p.getProperty("key", "defaultValue")); // Flow
|
||||
|
||||
Properties clean = new Properties();
|
||||
sink(clean.getProperty("key", tainted)); // Flow
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,3 +11,6 @@
|
||||
| Test.java:49:20:49:26 | tainted | Test.java:60:12:60:14 | x14 |
|
||||
| Test.java:73:11:73:17 | tainted | Test.java:75:10:75:12 | x17 |
|
||||
| Test.java:73:11:73:17 | tainted | Test.java:78:12:78:14 | x18 |
|
||||
| Test.java:84:18:84:24 | tainted | Test.java:85:10:85:29 | getProperty(...) |
|
||||
| Test.java:84:18:84:24 | tainted | Test.java:86:10:86:45 | getProperty(...) |
|
||||
| Test.java:89:35:89:41 | tainted | Test.java:89:10:89:42 | getProperty(...) |
|
||||
|
||||
2
java/ql/test/library-tests/types/cycles/Test.java
Normal file
2
java/ql/test/library-tests/types/cycles/Test.java
Normal file
@@ -0,0 +1,2 @@
|
||||
public class Test {
|
||||
}
|
||||
6
java/ql/test/library-tests/types/cycles/cycles.expected
Normal file
6
java/ql/test/library-tests/types/cycles/cycles.expected
Normal file
@@ -0,0 +1,6 @@
|
||||
| BiFunction<? super Object,? super Object,? extends Object> |
|
||||
| BiFunction<? super Object,? super Object,?> |
|
||||
| Function<? super Object,? extends Object> |
|
||||
| Function<? super Object,?> |
|
||||
| Map<? extends Object,? extends Object> |
|
||||
| Map<?,?> |
|
||||
5
java/ql/test/library-tests/types/cycles/cycles.ql
Normal file
5
java/ql/test/library-tests/types/cycles/cycles.ql
Normal file
@@ -0,0 +1,5 @@
|
||||
import java
|
||||
|
||||
from RefType t
|
||||
where t = t.getAStrictAncestor()
|
||||
select t.toString()
|
||||
@@ -14,7 +14,7 @@ import DataFlow::PathGraph
|
||||
/**
|
||||
* Gets the name of an unescaped placeholder in a lodash template.
|
||||
*
|
||||
* For example, the string `<h1><%= title %></h1>` contains the placeholder `title`.
|
||||
* For example, the string `"<h1><%= title %></h1>"` contains the placeholder "title".
|
||||
*/
|
||||
bindingset[s]
|
||||
string getAPlaceholderInString(string s) {
|
||||
|
||||
@@ -45,7 +45,7 @@ private predicate variableDefLookup(VarAccess va, AstNode def, string kind) {
|
||||
|
||||
/**
|
||||
* Holds if variable access `va` is of kind `kind` and refers to the
|
||||
* variable declaration.
|
||||
* variable declaration `decl`.
|
||||
*
|
||||
* For example, in the statement `var x = 42, y = x;`, the initializing
|
||||
* expression of `y` is a variable access `x` of kind `"V"` that refers to
|
||||
|
||||
@@ -146,7 +146,7 @@ class BasicBlock extends @cfg_node, NodeInStmtContainer {
|
||||
/** Holds if this basic block uses variable `v` in its `i`th node `u`. */
|
||||
predicate useAt(int i, Variable v, VarUse u) { useAt(this, i, v, u) }
|
||||
|
||||
/** Holds if this basic block defines variable `v` in its `i`th node `u`. */
|
||||
/** Holds if this basic block defines variable `v` in its `i`th node `d`. */
|
||||
predicate defAt(int i, Variable v, VarDef d) { defAt(this, i, v, d) }
|
||||
|
||||
/**
|
||||
|
||||
@@ -75,7 +75,7 @@ module CharacterEscapes {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a character in `n` that is preceded by a single useless backslash, resulting in a likely regular expression mistake explained by `mistake`.
|
||||
* Gets a character in `src` that is preceded by a single useless backslash, resulting in a likely regular expression mistake explained by `mistake`.
|
||||
*
|
||||
* The character is the `i`th character of the raw string value of `rawStringNode`.
|
||||
*/
|
||||
|
||||
@@ -172,7 +172,7 @@ class ClassDefinition extends @class_definition, ClassOrInterface, AST::ValueNod
|
||||
/** Gets the expression denoting the super class of the defined class, if any. */
|
||||
override Expr getSuperClass() { result = this.getChildExpr(1) }
|
||||
|
||||
/** Gets the `n`th type from the `implements` clause of this class, starting at 0. */
|
||||
/** Gets the `i`th type from the `implements` clause of this class, starting at 0. */
|
||||
override TypeExpr getSuperInterface(int i) {
|
||||
// AST indices for super interfaces: -1, -4, -7, ...
|
||||
exists(int astIndex | typeexprs(result, _, this, astIndex, _) |
|
||||
|
||||
@@ -54,7 +54,7 @@ private predicate hasNamedExports(ES2015Module mod) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this module contains a `default` export.
|
||||
* Holds if this module contains a default export.
|
||||
*/
|
||||
private predicate hasDefaultExport(ES2015Module mod) {
|
||||
// export default foo;
|
||||
@@ -337,7 +337,7 @@ class BulkReExportDeclaration extends ReExportDeclaration, @export_all_declarati
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the given bulk export should not re-export `name` because there is an explicit export
|
||||
* Holds if the given bulk export `reExport` should not re-export `name` because there is an explicit export
|
||||
* of that name in the same module.
|
||||
*
|
||||
* At compile time, shadowing works across declaration spaces.
|
||||
|
||||
@@ -180,7 +180,7 @@ private Path resolveUpTo(PathString p, int n, Folder root, boolean inTS) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `i`th component of the path `str`, where `base` is the resolved path one level up.
|
||||
* Gets the `n`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`.
|
||||
*/
|
||||
@@ -227,7 +227,7 @@ private module TypeScriptOutDir {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `outDir` option from a tsconfig file from the folder `parent`.
|
||||
* 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
|
||||
|
||||
@@ -195,7 +195,7 @@ private module PrintJavaScript {
|
||||
* Gets the `i`th child of `element`.
|
||||
* Can be overridden in subclasses to get more specific behavior for `getChild()`.
|
||||
*/
|
||||
AstNode getChildNode(int childIndex) { result = getLocationSortedChild(element, childIndex) }
|
||||
AstNode getChildNode(int i) { result = getLocationSortedChild(element, i) }
|
||||
}
|
||||
|
||||
/** Provides predicates for pretty printing `AstNode`s. */
|
||||
|
||||
@@ -260,7 +260,7 @@ module RangeAnalysis {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the given comparison can be modeled as `A <op> B + bias` where `<op>` is the comparison operator,
|
||||
* Holds if the given `comparison` can be modeled as `A <op> B + bias` where `<op>` is the comparison operator,
|
||||
* and `A` is `a * asign` and likewise `B` is `b * bsign`.
|
||||
*/
|
||||
predicate linearComparison(
|
||||
@@ -310,18 +310,18 @@ module RangeAnalysis {
|
||||
* Holds if `guard` asserts that the outcome of `A <op> B + bias` is true, where `<op>` is a comparison operator.
|
||||
*/
|
||||
predicate linearComparisonGuard(
|
||||
ConditionGuardNode guard, DataFlow::Node a, int asign, string operator, DataFlow::Node b,
|
||||
int bsign, Bias bias
|
||||
ConditionGuardNode guard, DataFlow::Node a, int asign, string op, DataFlow::Node b, int bsign,
|
||||
Bias bias
|
||||
) {
|
||||
exists(Comparison compare |
|
||||
compare = guard.getTest().flow().getImmediatePredecessor*().asExpr() and
|
||||
linearComparison(compare, a, asign, b, bsign, bias) and
|
||||
(
|
||||
guard.getOutcome() = true and operator = compare.getOperator()
|
||||
guard.getOutcome() = true and op = compare.getOperator()
|
||||
or
|
||||
not hasNaNIndicator(guard.getContainer()) and
|
||||
guard.getOutcome() = false and
|
||||
operator = negateOperator(compare.getOperator())
|
||||
op = negateOperator(compare.getOperator())
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -657,13 +657,13 @@ module RangeAnalysis {
|
||||
*/
|
||||
pragma[noopt]
|
||||
private predicate reachableByNegativeEdges(
|
||||
DataFlow::Node a, int asign, DataFlow::Node b, int bsign, ControlFlowNode cfg
|
||||
DataFlow::Node src, int asign, DataFlow::Node dst, int bsign, ControlFlowNode cfg
|
||||
) {
|
||||
negativeEdge(a, asign, b, bsign, cfg)
|
||||
negativeEdge(src, asign, dst, bsign, cfg)
|
||||
or
|
||||
exists(DataFlow::Node mid, int midx, ControlFlowNode midcfg |
|
||||
reachableByNegativeEdges(a, asign, mid, midx, cfg) and
|
||||
negativeEdge(mid, midx, b, bsign, midcfg) and
|
||||
reachableByNegativeEdges(src, asign, mid, midx, cfg) and
|
||||
negativeEdge(mid, midx, dst, bsign, midcfg) and
|
||||
exists(BasicBlock bb, int i, int j |
|
||||
bb.getNode(i) = midcfg and
|
||||
bb.getNode(j) = cfg and
|
||||
@@ -676,8 +676,8 @@ module RangeAnalysis {
|
||||
DataFlow::Node mid, int midx, ControlFlowNode midcfg, BasicBlock midBB,
|
||||
ReachableBasicBlock midRBB, BasicBlock cfgBB
|
||||
|
|
||||
reachableByNegativeEdges(a, asign, mid, midx, cfg) and
|
||||
negativeEdge(mid, midx, b, bsign, midcfg) and
|
||||
reachableByNegativeEdges(src, asign, mid, midx, cfg) and
|
||||
negativeEdge(mid, midx, dst, bsign, midcfg) and
|
||||
midBB = midcfg.getBasicBlock() and
|
||||
midRBB = midBB.(ReachableBasicBlock) and
|
||||
cfgBB = cfg.getBasicBlock() and
|
||||
|
||||
@@ -148,6 +148,18 @@ module Routing {
|
||||
this instanceof MkRouter
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `mayResumeDispatch` but without the assumption that functions with an unknown
|
||||
* implementation invoke their continuation.
|
||||
*/
|
||||
predicate definitelyResumesDispatch() {
|
||||
this.getLastChild().definitelyResumesDispatch()
|
||||
or
|
||||
exists(this.(RouteHandler).getAContinuationInvocation())
|
||||
or
|
||||
this instanceof MkRouter
|
||||
}
|
||||
|
||||
/** Gets the parent of this node, provided that this node may invoke its continuation. */
|
||||
private Node getContinuationParent() {
|
||||
result = this.getParent() and
|
||||
@@ -229,7 +241,7 @@ module Routing {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` has processed the incoming request strictly prior to this node.
|
||||
* Holds if `guard` has processed the incoming request strictly prior to this node.
|
||||
*/
|
||||
pragma[inline]
|
||||
private predicate isGuardedByNodeInternal(Node guard) {
|
||||
|
||||
@@ -501,7 +501,7 @@ class SsaExplicitDefinition extends SsaDefinition, TExplicitDef {
|
||||
}
|
||||
|
||||
/** This SSA definition corresponds to the definition of `v` at `def`. */
|
||||
predicate defines(VarDef d, SsaSourceVariable v) { this = TExplicitDef(_, _, d, v) }
|
||||
predicate defines(VarDef def, SsaSourceVariable v) { this = TExplicitDef(_, _, def, v) }
|
||||
|
||||
/** Gets the variable definition wrapped by this SSA definition. */
|
||||
VarDef getDef() { this = TExplicitDef(_, _, result, _) }
|
||||
|
||||
@@ -751,7 +751,7 @@ class TypeAccess extends @typeaccess, TypeExpr, TypeRef {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a suitable name for the library imported by `import`.
|
||||
* Gets a suitable name for the library imported by `imprt`.
|
||||
*
|
||||
* For relative imports, this is the snapshot-relative path to the imported module.
|
||||
* For non-relative imports, it is the import path itself.
|
||||
|
||||
@@ -353,7 +353,7 @@ abstract class BarrierGuardNode extends DataFlow::Node {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data flow node `nd` acts as a barrier for data flow.
|
||||
* Holds if data flow node `guard` acts as a barrier for data flow.
|
||||
*
|
||||
* `label` is bound to the blocked label, or the empty string if all labels should be blocked.
|
||||
*/
|
||||
@@ -382,7 +382,7 @@ private predicate barrierGuardIsRelevant(BarrierGuardNode guard) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data flow node `nd` acts as a barrier for data flow due to aliasing through
|
||||
* Holds if data flow node `guard` acts as a barrier for data flow due to aliasing through
|
||||
* an access path.
|
||||
*
|
||||
* `label` is bound to the blocked label, or the empty string if all labels should be blocked.
|
||||
@@ -1155,7 +1155,7 @@ private predicate appendStep(
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a function invoked at `invk` may return an expression into which `input`,
|
||||
* Holds if a function invoked at `output` may return an expression into which `input`,
|
||||
* which is either an argument or a definition captured by the function, flows under
|
||||
* configuration `cfg`, possibly through callees.
|
||||
*/
|
||||
@@ -1395,7 +1395,7 @@ private predicate reachableFromStoreBase(
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `base` is the base of a write to property `prop`, and `nd` is reachable
|
||||
* Holds if `base` is the base of a write to property `endProp`, and `nd` is reachable
|
||||
* from `base` under configuration `cfg` (possibly through callees) along a path whose
|
||||
* last step is summarized by `newSummary`, and the previous steps are summarized
|
||||
* by `oldSummary`.
|
||||
@@ -1758,7 +1758,7 @@ class PathNode extends TPathNode {
|
||||
this = MkSinkNode(nd, cfg)
|
||||
}
|
||||
|
||||
/** Holds if this path node wraps data-flow node `nd` and configuration `c`. */
|
||||
/** Holds if this path node wraps data-flow node `n` and configuration `c`. */
|
||||
predicate wraps(DataFlow::Node n, DataFlow::Configuration c) { nd = n and cfg = c }
|
||||
|
||||
/** Gets the underlying configuration of this path node. */
|
||||
@@ -1873,7 +1873,7 @@ class MidPathNode extends PathNode, MkMidNode {
|
||||
|
||||
MidPathNode() { this = MkMidNode(nd, cfg, summary) }
|
||||
|
||||
/** Holds if this path node wraps data-flow node `nd`, configuration `c` and summary `s`. */
|
||||
/** Holds if this path node wraps data-flow node `n`, configuration `c` and summary `s`. */
|
||||
predicate wraps(DataFlow::Node n, DataFlow::Configuration c, PathSummary s) {
|
||||
nd = n and cfg = c and summary = s
|
||||
}
|
||||
|
||||
@@ -1653,7 +1653,7 @@ module DataFlow {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the flow information for this node is incomplete.
|
||||
* Holds if the flow information for the node `nd`.
|
||||
*
|
||||
* This predicate holds if there may be a source flow node from which data flows into
|
||||
* this node, but that node is not a result of `getALocalSource()` due to analysis incompleteness.
|
||||
|
||||
@@ -498,7 +498,7 @@ private module ReturnPortal {
|
||||
invk = callee.getAnExitNode(isRemote).getAnInvocation()
|
||||
}
|
||||
|
||||
/** Holds if `ret` is a return node of a function flowing through `callee`. */
|
||||
/** Holds if `ret` is a return node of a function flowing through `base`. */
|
||||
predicate returns(Portal base, DataFlow::Node ret, boolean escapes) {
|
||||
ret = base.getAnEntryNode(escapes).getALocalSource().(DataFlow::FunctionNode).getAReturn()
|
||||
}
|
||||
|
||||
@@ -831,7 +831,7 @@ module TaintTracking {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the property `loadStep` should be copied from the object `pred` to the property `storeStep` of object `succ`.
|
||||
* Holds if the property `loadProp` should be copied from the object `pred` to the property `storeProp` of object `succ`.
|
||||
*
|
||||
* This step is used to copy the value of our pseudo-property that can later be accessed using a `get` or `getAll` call.
|
||||
* For an expression `url.searchParams`, the property `hiddenUrlPseudoProperty()` from the `url` object is stored in the property `getableUrlPseudoProperty()` on `url.searchParams`.
|
||||
|
||||
@@ -312,7 +312,7 @@ class TypeBackTracker extends TTypeBackTracker {
|
||||
* result = < some API call >.getArgument(< n >)
|
||||
* or
|
||||
* exists (DataFlow::TypeBackTracker t2 |
|
||||
* t = t2.smallstep(result, myType(t2))
|
||||
* t2 = t.smallstep(result, myType(t2))
|
||||
* )
|
||||
* }
|
||||
*
|
||||
|
||||
@@ -15,11 +15,11 @@ import javascript
|
||||
abstract class NgSourceProvider extends Locatable {
|
||||
/**
|
||||
* Holds if this element provides the source as `src` for an AngularJS expression at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* The location spans column `startColumn` of line `startLine` to
|
||||
* column `endColumn` of line `endLine` in file `filepath`.
|
||||
*/
|
||||
abstract predicate providesSourceAt(
|
||||
string src, string path, int startLine, int startColumn, int endLine, int endColumn
|
||||
string src, string filepath, int startLine, int startColumn, int endLine, int endColumn
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -278,11 +278,11 @@ abstract private class CustomSpecialServiceDefinition extends CustomServiceDefin
|
||||
bindingset[moduleMethodName]
|
||||
private predicate isCustomServiceDefinitionOnModule(
|
||||
DataFlow::CallNode mce, string moduleMethodName, string serviceName,
|
||||
DataFlow::Node factoryArgument
|
||||
DataFlow::Node factoryFunction
|
||||
) {
|
||||
mce = moduleRef(_).getAMethodCall(moduleMethodName) and
|
||||
mce.getArgument(0).asExpr().mayHaveStringValue(serviceName) and
|
||||
factoryArgument = mce.getArgument(1)
|
||||
factoryFunction = mce.getArgument(1)
|
||||
}
|
||||
|
||||
pragma[inline]
|
||||
|
||||
@@ -102,9 +102,9 @@ private predicate isBrowserifyDependencyMap(ObjectExpr deps) {
|
||||
* Holds if `m` is a function that looks like a bundled module created
|
||||
* by Webpack.
|
||||
*
|
||||
* Parameters must be named either `module` or `exports`,
|
||||
* or their name must contain the substring `webpack_require`
|
||||
* or `webpack_module_template_argument`.
|
||||
* Parameters must be named either "module" or "exports",
|
||||
* or their name must contain the substring "webpack_require"
|
||||
* or "webpack_module_template_argument".
|
||||
*/
|
||||
private predicate isWebpackModule(FunctionExpr m) {
|
||||
forex(Parameter parm | parm = m.getAParameter() |
|
||||
|
||||
@@ -50,7 +50,7 @@ module ConnectExpressShared {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `fun` appears to match the given signature based on parameter naming.
|
||||
* Holds if `function` appears to match the given signature based on parameter naming.
|
||||
*/
|
||||
private predicate matchesSignature(Function function, RouteHandlerSignature sig) {
|
||||
function.getNumParameter() = sig.getArity() and
|
||||
|
||||
@@ -33,6 +33,11 @@ module Express {
|
||||
or
|
||||
// `app = [new] express.Router()`
|
||||
result = DataFlow::moduleMember("express", "Router").getAnInvocation()
|
||||
or
|
||||
exists(DataFlow::SourceNode app |
|
||||
app.hasUnderlyingType("probot/lib/application", "Application") and
|
||||
result = app.getAMethodCall("route")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1043,4 +1048,22 @@ module Express {
|
||||
|
||||
override DataFlow::SourceNode getOutput() { result = this.getCallback(2).getParameter(1) }
|
||||
}
|
||||
|
||||
private class ResumeDispatchRefinement extends Routing::RouteHandler {
|
||||
ResumeDispatchRefinement() { this.getFunction() instanceof RouteHandler }
|
||||
|
||||
override predicate mayResumeDispatch() { this.getAParameter().getName() = "next" }
|
||||
|
||||
override predicate definitelyResumesDispatch() { this.getAParameter().getName() = "next" }
|
||||
}
|
||||
|
||||
private class ExpressStaticResumeDispatchRefinement extends Routing::Node {
|
||||
ExpressStaticResumeDispatchRefinement() {
|
||||
this = Routing::getNode(DataFlow::moduleMember("express", "static").getACall())
|
||||
}
|
||||
|
||||
override predicate mayResumeDispatch() { none() }
|
||||
|
||||
override predicate definitelyResumesDispatch() { none() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,7 +299,7 @@ module Fastify {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `rh` uses `plugin`.
|
||||
* Holds if `rh` uses `middleware`.
|
||||
*/
|
||||
private predicate usesMiddleware(RouteHandler rh, DataFlow::SourceNode middleware) {
|
||||
exists(RouteSetup setup |
|
||||
|
||||
@@ -583,11 +583,11 @@ private module Minimongo {
|
||||
*/
|
||||
module CollectionMethodSignatures {
|
||||
/**
|
||||
* Holds if Collection method `name` interprets parameter `n` as a query.
|
||||
* Holds if Collection method `name` interprets parameter `queryArgIdx` as a query.
|
||||
*/
|
||||
predicate interpretsArgumentAsQuery(string m, int queryArgIdx) {
|
||||
predicate interpretsArgumentAsQuery(string name, int queryArgIdx) {
|
||||
// implements most of the MongoDB interface
|
||||
MongoDB::CollectionMethodSignatures::interpretsArgumentAsQuery(m, queryArgIdx)
|
||||
MongoDB::CollectionMethodSignatures::interpretsArgumentAsQuery(name, queryArgIdx)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -474,17 +474,17 @@ module NodeJSLib {
|
||||
* that receives the data.
|
||||
*
|
||||
* We determine this by looking for an externs declaration for
|
||||
* `fs.methodName` where the `i`th parameter's name is `data` or
|
||||
* `fs.methodName` where the `i`th parameter's name (`paramName`) is `data` or
|
||||
* `buffer` or a `callback`.
|
||||
*/
|
||||
private predicate fsDataParam(string methodName, int i, string n) {
|
||||
private predicate fsDataParam(string methodName, int i, string paramName) {
|
||||
exists(ExternalMemberDecl decl, Function f, JSDocParamTag p |
|
||||
decl.hasQualifiedName("fs", methodName) and
|
||||
f = decl.getInit() and
|
||||
p.getDocumentedParameter() = f.getParameter(i).getAVariable() and
|
||||
n = p.getName().toLowerCase()
|
||||
paramName = p.getName().toLowerCase()
|
||||
|
|
||||
n = "data" or n = "buffer" or n = "callback"
|
||||
paramName = ["data", "buffer", "callback"]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ module SocketIO {
|
||||
/** Gets the namespace with the given path of this server. */
|
||||
NamespaceObject getNamespace(string path) { result = MkNamespace(this, path) }
|
||||
|
||||
/** Gets a api node that may refer to the socket.io server created at `srv`. */
|
||||
/** Gets a api node that may refer to a socket.io server. */
|
||||
private API::Node server() {
|
||||
result = node
|
||||
or
|
||||
@@ -144,7 +144,7 @@ module SocketIO {
|
||||
override NamespaceObject getNamespace() { result = ns }
|
||||
|
||||
/**
|
||||
* Gets a data flow node that may refer to the socket.io namespace created at `ns`.
|
||||
* Gets a data flow node that may refer a the socket.io namespace.
|
||||
*/
|
||||
private API::Node namespace() {
|
||||
result = node
|
||||
|
||||
@@ -309,12 +309,13 @@ private module JQueryClientRequest {
|
||||
/**
|
||||
* Gets a node referring to the response contained in an `jqXHR` object.
|
||||
*/
|
||||
private DataFlow::SourceNode getAResponseNodeFromAnXHRObject(DataFlow::SourceNode obj) {
|
||||
private DataFlow::SourceNode getAResponseNodeFromAnXHRObject(DataFlow::SourceNode jqXHR) {
|
||||
result =
|
||||
obj.getAPropertyRead(any(string s |
|
||||
s = "responseText" or
|
||||
s = "responseXML"
|
||||
))
|
||||
jqXHR
|
||||
.getAPropertyRead(any(string s |
|
||||
s = "responseText" or
|
||||
s = "responseXML"
|
||||
))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -80,7 +80,7 @@ module UnsafeHtmlConstruction {
|
||||
t.start() and
|
||||
result = sink
|
||||
or
|
||||
exists(DataFlow::TypeBackTracker t2 | t = t2.smallstep(result, isUsedInXssSink(t2, sink)))
|
||||
exists(DataFlow::TypeBackTracker t2 | t2 = t.smallstep(result, isUsedInXssSink(t2, sink)))
|
||||
or
|
||||
exists(DataFlow::TypeBackTracker t2 |
|
||||
t.continue() = t2 and
|
||||
|
||||
@@ -154,7 +154,7 @@ predicate maybeAssignsAccessedPropInBlock(DataFlow::PropWrite assign, boolean af
|
||||
*/
|
||||
private module PurityCheck {
|
||||
/**
|
||||
* Holds if a ControlFlowNode `c` is before an impure expression inside `bb`.
|
||||
* Holds if `write` is before an impure expression inside `bb`.
|
||||
*/
|
||||
predicate isBeforeImpure(DataFlow::PropWrite write, ReachableBasicBlock bb) {
|
||||
getANodeAfterWrite(write, bb).(Expr).isImpure()
|
||||
@@ -181,7 +181,7 @@ private module PurityCheck {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a ControlFlowNode `c` is after an impure expression inside `bb`.
|
||||
* Holds if `write` is after an impure expression inside `bb`.
|
||||
*/
|
||||
predicate isAfterImpure(DataFlow::PropWrite write, ReachableBasicBlock bb) {
|
||||
getANodeBeforeWrite(write, bb).(Expr).isImpure()
|
||||
|
||||
@@ -144,6 +144,9 @@ predicate whitelisted(UnusedLocal v) {
|
||||
// exclude variables mentioned in JSDoc comments in externs
|
||||
mentionedInJSDocComment(v)
|
||||
or
|
||||
// the attributes in .vue files are not extracted, so we can get false positives in those.
|
||||
v.getADeclaration().getFile().getExtension() = "vue"
|
||||
or
|
||||
// exclude variables used to filter out unwanted properties
|
||||
isPropertyFilter(v)
|
||||
or
|
||||
|
||||
@@ -5793,6 +5793,8 @@ predicate typos(string wrong, string right) {
|
||||
or
|
||||
wrong = "paramters" and right = "parameters"
|
||||
or
|
||||
wrong = "parametarized" and right = "parameterized"
|
||||
or
|
||||
wrong = "paranthesis" and right = "parenthesis"
|
||||
or
|
||||
wrong = "paraphenalia" and right = "paraphernalia"
|
||||
|
||||
@@ -84,10 +84,10 @@ predicate hasObjectProvidingTemplateVariables(CandidateStringLiteral lit) {
|
||||
* Gets a declaration of variable `v` in `tl`, where `v` has the given `name` and
|
||||
* belongs to `scope`.
|
||||
*/
|
||||
VarDecl getDeclIn(Variable v, Scope s, string name, CandidateTopLevel tl) {
|
||||
VarDecl getDeclIn(Variable v, Scope scope, string name, CandidateTopLevel tl) {
|
||||
v.getName() = name and
|
||||
v.getADeclaration() = result and
|
||||
v.getScope() = s and
|
||||
v.getScope() = scope and
|
||||
result.getTopLevel() = tl
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Holds if `nd` is a use of a feature introduced in ECMAScript version `ver`
|
||||
* Holds if `nd` is a use of a feature introduced in ECMAScript `version`
|
||||
* from the given category.
|
||||
*
|
||||
* Categories are taken from Kangax' [ECMAScript 6 compatibility table]
|
||||
|
||||
@@ -16,14 +16,14 @@ import javascript
|
||||
/**
|
||||
* Holds if `assign` assigns the value of `nd` to `exportsVar`, which is an `exports` variable
|
||||
*/
|
||||
predicate exportsAssign(Assignment assgn, Variable exportsVar, DataFlow::Node nd) {
|
||||
predicate exportsAssign(Assignment assign, Variable exportsVar, DataFlow::Node nd) {
|
||||
exists(NodeModule m |
|
||||
exportsVar = m.getScope().getVariable("exports") and
|
||||
assgn.getLhs() = exportsVar.getAnAccess() and
|
||||
nd = assgn.getRhs().flow()
|
||||
assign.getLhs() = exportsVar.getAnAccess() and
|
||||
nd = assign.getRhs().flow()
|
||||
)
|
||||
or
|
||||
exportsAssign(assgn, exportsVar, nd.getASuccessor())
|
||||
exportsAssign(assign, exportsVar, nd.getASuccessor())
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,10 +39,10 @@ RegExpTerm getEffectiveRoot(RegExpTerm actualRoot) {
|
||||
/**
|
||||
* Holds if `term` contains an anchor on both ends.
|
||||
*/
|
||||
predicate isPossiblyAnchoredOnBothEnds(RegExpSequence node) {
|
||||
node.getAChild*() instanceof RegExpCaret and
|
||||
node.getAChild*() instanceof RegExpDollar and
|
||||
node.getNumChild() >= 2
|
||||
predicate isPossiblyAnchoredOnBothEnds(RegExpSequence term) {
|
||||
term.getAChild*() instanceof RegExpCaret and
|
||||
term.getAChild*() instanceof RegExpDollar and
|
||||
term.getNumChild() >= 2
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -53,7 +53,7 @@ predicate matchesBeginningOfString(RegExpTerm term) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the given sequence contains top-level domain preceded by a dot, such as `.com`,
|
||||
* Holds if the given sequence `seq` contains top-level domain preceded by a dot, such as `.com`,
|
||||
* excluding cases where this is at the very beginning of the regexp.
|
||||
*
|
||||
* `i` is bound to the index of the last child in the top-level domain part.
|
||||
|
||||
@@ -109,8 +109,8 @@ DataFlow::Node schemeCheck(DataFlow::Node nd, DangerousScheme scheme) {
|
||||
}
|
||||
|
||||
/** Gets a data-flow node that checks an instance of `ap` against the given `scheme`. */
|
||||
DataFlow::Node schemeCheckOn(DataFlow::SourceNode root, string path, DangerousScheme scheme) {
|
||||
result = schemeCheck(AccessPath::getAReferenceTo(root, path), scheme)
|
||||
DataFlow::Node schemeCheckOn(DataFlow::SourceNode root, string ap, DangerousScheme scheme) {
|
||||
result = schemeCheck(AccessPath::getAReferenceTo(root, ap), scheme)
|
||||
}
|
||||
|
||||
from DataFlow::SourceNode root, string path, int n
|
||||
|
||||
@@ -84,7 +84,7 @@ class LiteralLengthExpr extends DotExpr {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `length` is derived from the length of the given `indexOf`-operand.
|
||||
* Holds if `length` is derived from the length of the given indexOf `operand`.
|
||||
*/
|
||||
predicate isDerivedFromLength(DataFlow::Node length, DataFlow::Node operand) {
|
||||
exists(IndexOfCall call | operand = call.getAnOperand() |
|
||||
|
||||
@@ -50,7 +50,7 @@ private DataFlow::Node endsInCodeInjectionSink(DataFlow::TypeBackTracker t) {
|
||||
not result instanceof StringOps::ConcatenationRoot // the heuristic CodeInjection sink looks for string-concats, we are not interrested in those here.
|
||||
)
|
||||
or
|
||||
exists(DataFlow::TypeBackTracker t2 | t = t2.smallstep(result, endsInCodeInjectionSink(t2)))
|
||||
exists(DataFlow::TypeBackTracker t2 | t2 = t.smallstep(result, endsInCodeInjectionSink(t2)))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Using a case-sensitive regular expression path in a middleware route enables an attacker to bypass that middleware
|
||||
when accessing an endpoint with a case-insensitive path.
|
||||
Paths specified using a string are case-insensitive, whereas regular expressions are case-sensitive by default.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
When using a regular expression as a middleware path, make sure the regular expression is
|
||||
case-insensitive by adding the <code>i</code> flag.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The following example restricts access to paths in the <code>/admin</code> path to users logged in as
|
||||
administrators:
|
||||
</p>
|
||||
<sample src="examples/CaseSensitiveMiddlewarePath.js" />
|
||||
<p>
|
||||
A path such as <code>/admin/users/45</code> can only be accessed by an administrator. However, the path
|
||||
<code>/ADMIN/USERS/45</code> can be accessed by anyone because the upper-case path doesn't match the case-sensitive regular expression, whereas
|
||||
Express considers it to match the path string <code>/admin/users</code>.
|
||||
</p>
|
||||
<p>
|
||||
The issue can be fixed by adding the <code>i</code> flag to the regular expression:
|
||||
</p>
|
||||
<sample src="examples/CaseSensitiveMiddlewarePathGood.js" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
MDN
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#advanced_searching_with_flags">Regular Expression Flags</a>.
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* @name Case-sensitive middleware path
|
||||
* @description Middleware with case-sensitive paths do not protect endpoints with case-insensitive paths.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 7.3
|
||||
* @precision high
|
||||
* @id js/case-sensitive-middleware-path
|
||||
* @tags security
|
||||
* external/cwe/cwe-178
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Converts `s` to upper case, or to lower-case if it was already upper case.
|
||||
*/
|
||||
bindingset[s]
|
||||
string toOtherCase(string s) {
|
||||
if s.regexpMatch(".*[a-z].*") then result = s.toUpperCase() else result = s.toLowerCase()
|
||||
}
|
||||
|
||||
RegExpCharacterClass getEnclosingClass(RegExpTerm term) {
|
||||
term = result.getAChild()
|
||||
or
|
||||
term = result.getAChild().(RegExpRange).getAChild()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `term` seems to distinguish between upper and lower case letters, assuming the `i` flag is not present.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate isLikelyCaseSensitiveRegExp(RegExpTerm term) {
|
||||
exists(RegExpConstant const |
|
||||
const = term.getAChild*() and
|
||||
const.getValue().regexpMatch(".*[a-zA-Z].*") and
|
||||
not getEnclosingClass(const).getAChild().(RegExpConstant).getValue() =
|
||||
toOtherCase(const.getValue()) and
|
||||
not const.getParent*() instanceof RegExpNegativeLookahead and
|
||||
not const.getParent*() instanceof RegExpNegativeLookbehind
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string matched by `term`, or part of such a string.
|
||||
*/
|
||||
string getExampleString(RegExpTerm term) {
|
||||
result = term.getAMatchedString()
|
||||
or
|
||||
// getAMatchedString does not recurse into sequences. Perform one step manually.
|
||||
exists(RegExpSequence seq | seq = term |
|
||||
result =
|
||||
strictconcat(RegExpTerm child, int i, string text |
|
||||
child = seq.getChild(i) and
|
||||
(
|
||||
text = child.getAMatchedString()
|
||||
or
|
||||
not exists(child.getAMatchedString()) and
|
||||
text = ""
|
||||
)
|
||||
|
|
||||
text order by i
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
string getCaseSensitiveBypassExample(RegExpTerm term) {
|
||||
exists(string example |
|
||||
example = getExampleString(term) and
|
||||
result = toOtherCase(example) and
|
||||
result != example // getting an example string is approximate; ensure we got a proper case-change example
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `setup` has a path-argument `arg` referring to the given case-sensitive `regexp`.
|
||||
*/
|
||||
predicate isCaseSensitiveMiddleware(
|
||||
Routing::RouteSetup setup, DataFlow::RegExpCreationNode regexp, DataFlow::Node arg
|
||||
) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
setup = Routing::getRouteSetupNode(call) and
|
||||
(
|
||||
setup.definitelyResumesDispatch()
|
||||
or
|
||||
// If applied to all HTTP methods, be a bit more lenient in detecting middleware
|
||||
setup.mayResumeDispatch() and
|
||||
not exists(setup.getOwnHttpMethod())
|
||||
) and
|
||||
arg = call.getArgument(0) and
|
||||
regexp.getAReference().flowsTo(arg) and
|
||||
isLikelyCaseSensitiveRegExp(regexp.getRoot()) and
|
||||
exists(string flags |
|
||||
flags = regexp.getFlags() and
|
||||
not RegExp::isIgnoreCase(flags)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
predicate isGuardedCaseInsensitiveEndpoint(
|
||||
Routing::RouteSetup endpoint, Routing::RouteSetup middleware
|
||||
) {
|
||||
isCaseSensitiveMiddleware(middleware, _, _) and
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
endpoint = Routing::getRouteSetupNode(call) and
|
||||
endpoint.isGuardedByNode(middleware) and
|
||||
call.getArgument(0).mayHaveStringValue(_)
|
||||
)
|
||||
}
|
||||
|
||||
from
|
||||
DataFlow::RegExpCreationNode regexp, Routing::RouteSetup middleware, Routing::RouteSetup endpoint,
|
||||
DataFlow::Node arg, string example
|
||||
where
|
||||
isCaseSensitiveMiddleware(middleware, regexp, arg) and
|
||||
example = getCaseSensitiveBypassExample(regexp.getRoot()) and
|
||||
isGuardedCaseInsensitiveEndpoint(endpoint, middleware) and
|
||||
exists(endpoint.getRelativePath().toLowerCase().indexOf(example.toLowerCase()))
|
||||
select arg,
|
||||
"This route uses a case-sensitive path $@, but is guarding a case-insensitive path $@. A path such as '"
|
||||
+ example + "' will bypass the middleware.", regexp, "pattern", endpoint, "here"
|
||||
@@ -0,0 +1,13 @@
|
||||
const app = require('express')();
|
||||
|
||||
app.use(/\/admin\/.*/, (req, res, next) => {
|
||||
if (!req.user.isAdmin) {
|
||||
res.status(401).send('Unauthorized');
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/admin/users/:id', (req, res) => {
|
||||
res.send(app.database.users[req.params.id]);
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
const app = require('express')();
|
||||
|
||||
app.use(/\/admin\/.*/i, (req, res, next) => {
|
||||
if (!req.user.isAdmin) {
|
||||
res.status(401).send('Unauthorized');
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/admin/users/:id', (req, res) => {
|
||||
res.send(app.database.users[req.params.id]);
|
||||
});
|
||||
@@ -72,11 +72,11 @@ pragma[noinline]
|
||||
Folder getAPackageJsonFolder() { result = any(PackageJson json).getFile().getParentContainer() }
|
||||
|
||||
/**
|
||||
* Gets a reference to `dirname`, the home folder, the current working folder, or the root folder.
|
||||
* Gets a reference to a directory that has a `package.json` in the same folder, the home folder,
|
||||
* the current working folder, or the root folder.
|
||||
* All of these might cause information to be leaked.
|
||||
*
|
||||
* For `dirname` that can happen if there is a `package.json` file in the same folder.
|
||||
* It is assumed that the presence of a `package.json` file means that a `node_modules` folder can also exist.
|
||||
* For the first case it is assumed that the presence of a `package.json` file means that a `node_modules` folder can also exist.
|
||||
*
|
||||
* For the root/home/working folder, they contain so much information that they must leak information somehow (e.g. ssh keys in the `~/.ssh` folder).
|
||||
*/
|
||||
@@ -108,7 +108,7 @@ DataFlow::Node getALeakingFolder(string description) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data-flow node that represents a path to the private folder `path`.
|
||||
* Gets a data-flow node that represents the private folder descriped by `description`.
|
||||
*/
|
||||
DataFlow::Node getAPrivateFolderPath(string description) {
|
||||
exists(string path |
|
||||
@@ -119,7 +119,7 @@ DataFlow::Node getAPrivateFolderPath(string description) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gest a call that serves the folder `path` to the public.
|
||||
* Gest a call that serves the folder descriped by `description` to the public.
|
||||
*/
|
||||
DataFlow::CallNode servesAPrivateFolder(string description) {
|
||||
result = DataFlow::moduleMember(["express", "connect"], "static").getACall() and
|
||||
|
||||
@@ -34,7 +34,7 @@ predicate isLoginSetup(Express::RouteSetup setup) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `handler` regenerates its session using `req.session.regenerate`.
|
||||
* Holds if `setup` regenerates its session using `req.session.regenerate`.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate regeneratesSession(Express::RouteSetup setup) {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: newQuery
|
||||
---
|
||||
|
||||
- A new query "Case-sensitive middleware path" (`js/case-sensitive-middleware-path`) has been added.
|
||||
It highlights middleware routes that can be bypassed due to having a case-sensitive regular expression path.
|
||||
4
javascript/ql/src/external/DefectFilter.qll
vendored
4
javascript/ql/src/external/DefectFilter.qll
vendored
@@ -5,8 +5,8 @@ import semmle.javascript.Files
|
||||
/**
|
||||
* Holds if `id` in the opaque identifier of a result reported by query `queryPath`,
|
||||
* such that `message` is the associated message and the location of the result spans
|
||||
* column `startcolumn` of line `startline` to column `endcolumn` of line `endline`
|
||||
* in file `filepath`.
|
||||
* column `startcol` of line `startline` to column `endcol` of line `endline`
|
||||
* in `file`.
|
||||
*
|
||||
* For more information, see [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user