Merge 'main' into redsun82/swift-extraction

This commit is contained in:
Paolo Tranquilli
2022-07-18 11:55:21 +02:00
182 changed files with 6965 additions and 2686 deletions

View File

@@ -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

View File

@@ -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 }}

View File

@@ -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

View File

@@ -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.

View File

@@ -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()
)
}
/**

View File

@@ -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)) }

View File

@@ -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 |

View File

@@ -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")

View File

@@ -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) {}
}

View File

@@ -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
}
});

View File

@@ -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>

View File

@@ -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."

View File

@@ -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>`."

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
1 package sink source summary sink:bean-validation sink:create-file sink:groovy sink:header-splitting sink:information-leak sink:intent-start sink:jdbc-url sink:jexl sink:jndi-injection sink:ldap sink:logging sink:mvel sink:ognl-injection sink:open-url sink:pending-intent-sent sink:regex-use[-1] sink:regex-use[0] sink:regex-use[] sink:regex-use[f-1] sink:regex-use[f1] sink:regex-use[f] sink:set-hostname-verifier sink:sql sink:url-open-stream sink:url-redirect sink:write-file sink:xpath sink:xslt sink:xss source:android-external-storage-dir source:android-widget source:contentprovider source:remote summary:taint summary:value
36 java.net 10 3 7 10 3 7
37 java.nio 15 6 13 2 6
38 java.sql 11 4 7
39 java.util 44 438 441 34 5 2 1 2 24 414 417
40 javax.faces.context 2 7 2 7
41 javax.jms 9 57 9 57
42 javax.json 123 100 23

View File

@@ -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

View File

@@ -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

View File

@@ -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 =

View File

@@ -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)
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -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)
}
}
}

View 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.

View 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.

View File

@@ -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

View File

@@ -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>"
}
/**

View File

@@ -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() }

View File

@@ -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",

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
description: Add errortype
compatibility: full

View File

@@ -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);

View File

@@ -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>

View File

@@ -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."

View File

@@ -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
}
}

View File

@@ -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(...) |

View File

@@ -0,0 +1,2 @@
public class Test {
}

View 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<?,?> |

View File

@@ -0,0 +1,5 @@
import java
from RefType t
where t = t.getAStrictAncestor()
select t.toString()

View File

@@ -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) {

View File

@@ -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

View File

@@ -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) }
/**

View File

@@ -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`.
*/

View File

@@ -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, _) |

View File

@@ -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.

View File

@@ -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

View File

@@ -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. */

View File

@@ -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

View File

@@ -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) {

View File

@@ -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, _) }

View File

@@ -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.

View File

@@ -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
}

View File

@@ -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.

View File

@@ -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()
}

View File

@@ -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`.

View File

@@ -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))
* )
* }
*

View File

@@ -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
);
/**

View File

@@ -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]

View File

@@ -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() |

View File

@@ -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

View File

@@ -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() }
}
}

View File

@@ -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 |

View File

@@ -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)
}
}

View File

@@ -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"]
)
}

View File

@@ -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

View File

@@ -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"
))
}
/**

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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"

View File

@@ -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
}

View File

@@ -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]

View File

@@ -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())
}
/**

View File

@@ -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
}
/**

View File

@@ -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.

View File

@@ -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

View File

@@ -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() |

View File

@@ -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)))
}
/**

View File

@@ -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>

View File

@@ -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"

View File

@@ -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]);
});

View File

@@ -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]);
});

View File

@@ -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

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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