mirror of
https://github.com/github/codeql.git
synced 2026-06-12 00:11:07 +02:00
Compare commits
8 Commits
python/cla
...
yoff/pytho
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4aee0b3c87 | ||
|
|
a2295e7216 | ||
|
|
0623acc7f5 | ||
|
|
dc5aa8e0f5 | ||
|
|
db1e5035b4 | ||
|
|
7a3f546587 | ||
|
|
821325b7e5 | ||
|
|
4d2296d4f0 |
208
.github/workflows/go-version-update.yml
vendored
208
.github/workflows/go-version-update.yml
vendored
@@ -1,208 +0,0 @@
|
|||||||
name: Update Go version
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 3 * * 1" # Run weekly on Mondays at 3 AM UTC (1 = Monday)
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update-go-version:
|
|
||||||
name: Check and update Go version
|
|
||||||
if: github.repository == 'github/codeql'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Set up Git
|
|
||||||
run: |
|
|
||||||
git config user.name "github-actions[bot]"
|
|
||||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
||||||
|
|
||||||
- name: Fetch latest Go version
|
|
||||||
id: fetch-version
|
|
||||||
run: |
|
|
||||||
LATEST_GO_VERSION=$(curl -s https://go.dev/dl/?mode=json | jq -r '.[0].version')
|
|
||||||
|
|
||||||
if [ -z "$LATEST_GO_VERSION" ] || [ "$LATEST_GO_VERSION" = "null" ]; then
|
|
||||||
echo "Error: Failed to fetch latest Go version from go.dev"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Latest Go version from go.dev: $LATEST_GO_VERSION"
|
|
||||||
echo "version=$LATEST_GO_VERSION" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Extract version numbers (e.g., go1.26.0 -> 1.26.0)
|
|
||||||
LATEST_VERSION_NUM=$(echo $LATEST_GO_VERSION | sed 's/^go//')
|
|
||||||
echo "version_num=$LATEST_VERSION_NUM" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Extract major.minor version (e.g., 1.26.0 -> 1.26)
|
|
||||||
LATEST_MAJOR_MINOR=$(echo $LATEST_VERSION_NUM | sed -E 's/^([0-9]+\.[0-9]+).*/\1/')
|
|
||||||
echo "major_minor=$LATEST_MAJOR_MINOR" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Check current Go version
|
|
||||||
id: current-version
|
|
||||||
run: |
|
|
||||||
CURRENT_VERSION=$(sed -n 's/.*go_sdk\.download(version = \"\([^\"]*\)\".*/\1/p' MODULE.bazel)
|
|
||||||
|
|
||||||
if [ -z "$CURRENT_VERSION" ]; then
|
|
||||||
echo "Error: Could not extract Go version from MODULE.bazel"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Current Go version in MODULE.bazel: $CURRENT_VERSION"
|
|
||||||
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Extract major.minor version
|
|
||||||
CURRENT_MAJOR_MINOR=$(echo $CURRENT_VERSION | sed -E 's/^([0-9]+\.[0-9]+).*/\1/')
|
|
||||||
echo "major_minor=$CURRENT_MAJOR_MINOR" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Compare versions
|
|
||||||
id: compare
|
|
||||||
run: |
|
|
||||||
LATEST="${{ steps.fetch-version.outputs.version_num }}"
|
|
||||||
CURRENT="${{ steps.current-version.outputs.version }}"
|
|
||||||
|
|
||||||
echo "Latest: $LATEST"
|
|
||||||
echo "Current: $CURRENT"
|
|
||||||
|
|
||||||
if [ "$LATEST" = "$CURRENT" ]; then
|
|
||||||
echo "Go version is up to date"
|
|
||||||
echo "needs_update=false" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "Go version needs update from $CURRENT to $LATEST"
|
|
||||||
echo "needs_update=true" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Update Go version in files
|
|
||||||
if: steps.compare.outputs.needs_update == 'true'
|
|
||||||
run: |
|
|
||||||
LATEST_VERSION_NUM="${{ steps.fetch-version.outputs.version_num }}"
|
|
||||||
LATEST_MAJOR_MINOR="${{ steps.fetch-version.outputs.major_minor }}"
|
|
||||||
CURRENT_VERSION="${{ steps.current-version.outputs.version }}"
|
|
||||||
CURRENT_MAJOR_MINOR="${{ steps.current-version.outputs.major_minor }}"
|
|
||||||
|
|
||||||
echo "Updating from $CURRENT_VERSION to $LATEST_VERSION_NUM"
|
|
||||||
|
|
||||||
# Escape dots in current version strings for use in sed patterns
|
|
||||||
CURRENT_VERSION_ESCAPED=$(echo "$CURRENT_VERSION" | sed 's/\./\\./g')
|
|
||||||
CURRENT_MAJOR_MINOR_ESCAPED=$(echo "$CURRENT_MAJOR_MINOR" | sed 's/\./\\./g')
|
|
||||||
|
|
||||||
# Update MODULE.bazel
|
|
||||||
sed -i "s/go_sdk\.download(version = \"$CURRENT_VERSION_ESCAPED\")/go_sdk.download(version = \"$LATEST_VERSION_NUM\")/" MODULE.bazel
|
|
||||||
if ! grep -q "go_sdk.download(version = \"$LATEST_VERSION_NUM\")" MODULE.bazel; then
|
|
||||||
echo "Error: Failed to update MODULE.bazel"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Update go/extractor/go.mod
|
|
||||||
if ! sed -i "s/^go $CURRENT_MAJOR_MINOR_ESCAPED\$/go $LATEST_MAJOR_MINOR/" go/extractor/go.mod; then
|
|
||||||
echo "Warning: Failed to update go directive in go.mod"
|
|
||||||
fi
|
|
||||||
if ! sed -i "s/^toolchain go$CURRENT_VERSION_ESCAPED\$/toolchain go$LATEST_VERSION_NUM/" go/extractor/go.mod; then
|
|
||||||
echo "Warning: Failed to update toolchain in go.mod"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Update go/extractor/autobuilder/build-environment.go
|
|
||||||
if ! sed -i "s/var maxGoVersion = util\.NewSemVer(\"$CURRENT_MAJOR_MINOR_ESCAPED\")/var maxGoVersion = util.NewSemVer(\"$LATEST_MAJOR_MINOR\")/" go/extractor/autobuilder/build-environment.go; then
|
|
||||||
echo "Warning: Failed to update build-environment.go"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Update go/actions/test/action.yml
|
|
||||||
if ! sed -i "s/default: \"~$CURRENT_VERSION_ESCAPED\"/default: \"~$LATEST_VERSION_NUM\"/" go/actions/test/action.yml; then
|
|
||||||
echo "Warning: Failed to update action.yml"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Show what changed
|
|
||||||
git diff
|
|
||||||
|
|
||||||
- name: Check for changes
|
|
||||||
id: check-changes
|
|
||||||
if: steps.compare.outputs.needs_update == 'true'
|
|
||||||
run: |
|
|
||||||
if git diff --quiet; then
|
|
||||||
echo "No changes detected"
|
|
||||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "Changes detected"
|
|
||||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Check for existing PR
|
|
||||||
if: steps.check-changes.outputs.has_changes == 'true'
|
|
||||||
id: check-pr
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
BRANCH_NAME="workflow/go-version-update"
|
|
||||||
PR_NUMBER=$(gh pr list --head "$BRANCH_NAME" --state open --json number --jq '.[0].number')
|
|
||||||
|
|
||||||
if [ -n "$PR_NUMBER" ]; then
|
|
||||||
echo "Existing PR found: #$PR_NUMBER"
|
|
||||||
echo "pr_exists=true" >> $GITHUB_OUTPUT
|
|
||||||
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "No existing PR found"
|
|
||||||
echo "pr_exists=false" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Commit and push changes
|
|
||||||
if: steps.check-changes.outputs.has_changes == 'true'
|
|
||||||
run: |
|
|
||||||
BRANCH_NAME="workflow/go-version-update"
|
|
||||||
LATEST_VERSION_NUM="${{ steps.fetch-version.outputs.version_num }}"
|
|
||||||
LATEST_MAJOR_MINOR="${{ steps.fetch-version.outputs.major_minor }}"
|
|
||||||
|
|
||||||
# Create or switch to branch
|
|
||||||
git checkout -B "$BRANCH_NAME"
|
|
||||||
|
|
||||||
# Stage and commit changes
|
|
||||||
git add MODULE.bazel go/extractor/go.mod go/extractor/autobuilder/build-environment.go go/actions/test/action.yml
|
|
||||||
git commit -m "Go: Update to $LATEST_VERSION_NUM"
|
|
||||||
|
|
||||||
# Push changes
|
|
||||||
git push --force-with-lease origin "$BRANCH_NAME"
|
|
||||||
|
|
||||||
- name: Create or update PR
|
|
||||||
if: steps.check-changes.outputs.has_changes == 'true'
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
BRANCH_NAME="workflow/go-version-update"
|
|
||||||
LATEST_VERSION_NUM="${{ steps.fetch-version.outputs.version_num }}"
|
|
||||||
CURRENT_VERSION="${{ steps.current-version.outputs.version }}"
|
|
||||||
|
|
||||||
PR_TITLE="Go: Update to $LATEST_VERSION_NUM"
|
|
||||||
|
|
||||||
PR_BODY=$(cat <<EOF
|
|
||||||
This PR updates Go from $CURRENT_VERSION to $LATEST_VERSION_NUM.
|
|
||||||
|
|
||||||
Updated files:
|
|
||||||
- \`MODULE.bazel\` - go_sdk.download version
|
|
||||||
- \`go/extractor/go.mod\` - go directive and toolchain
|
|
||||||
- \`go/extractor/autobuilder/build-environment.go\` - maxGoVersion (only if MAJOR.MINOR changes)
|
|
||||||
- \`go/actions/test/action.yml\` - default go-test-version
|
|
||||||
|
|
||||||
This PR was automatically created by the [Go version update workflow](https://github.com/${{ github.repository }}/blob/main/.github/workflows/go-version-update.yml).
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
|
|
||||||
if [ "${{ steps.check-pr.outputs.pr_exists }}" = "true" ]; then
|
|
||||||
echo "Updating existing PR #${{ steps.check-pr.outputs.pr_number }}"
|
|
||||||
gh pr edit "${{ steps.check-pr.outputs.pr_number }}" --title "$PR_TITLE" --body "$PR_BODY"
|
|
||||||
else
|
|
||||||
echo "Creating new PR"
|
|
||||||
gh pr create \
|
|
||||||
--title "$PR_TITLE" \
|
|
||||||
--body "$PR_BODY" \
|
|
||||||
--base main \
|
|
||||||
--head "$BRANCH_NAME" \
|
|
||||||
--label "Go"
|
|
||||||
fi
|
|
||||||
@@ -273,7 +273,7 @@ use_repo(
|
|||||||
)
|
)
|
||||||
|
|
||||||
go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk")
|
go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk")
|
||||||
go_sdk.download(version = "1.26.4")
|
go_sdk.download(version = "1.26.0")
|
||||||
|
|
||||||
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
|
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
|
||||||
go_deps.from_file(go_mod = "//go/extractor:go.mod")
|
go_deps.from_file(go_mod = "//go/extractor:go.mod")
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
"java/ql/lib/semmle/code/java/dataflow/internal/rangeanalysis/SignAnalysisCommon.qll",
|
"java/ql/lib/semmle/code/java/dataflow/internal/rangeanalysis/SignAnalysisCommon.qll",
|
||||||
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/rangeanalysis/SignAnalysisCommon.qll"
|
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/rangeanalysis/SignAnalysisCommon.qll"
|
||||||
],
|
],
|
||||||
|
"Bound Java/C#": [
|
||||||
|
"java/ql/lib/semmle/code/java/dataflow/Bound.qll",
|
||||||
|
"csharp/ql/lib/semmle/code/csharp/dataflow/Bound.qll"
|
||||||
|
],
|
||||||
"ModulusAnalysis Java/C#": [
|
"ModulusAnalysis Java/C#": [
|
||||||
"java/ql/lib/semmle/code/java/dataflow/ModulusAnalysis.qll",
|
"java/ql/lib/semmle/code/java/dataflow/ModulusAnalysis.qll",
|
||||||
"csharp/ql/lib/semmle/code/csharp/dataflow/ModulusAnalysis.qll"
|
"csharp/ql/lib/semmle/code/csharp/dataflow/ModulusAnalysis.qll"
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ dependencies:
|
|||||||
codeql/controlflow: ${workspace}
|
codeql/controlflow: ${workspace}
|
||||||
codeql/dataflow: ${workspace}
|
codeql/dataflow: ${workspace}
|
||||||
codeql/mad: ${workspace}
|
codeql/mad: ${workspace}
|
||||||
codeql/rangeanalysis: ${workspace}
|
|
||||||
codeql/ssa: ${workspace}
|
codeql/ssa: ${workspace}
|
||||||
codeql/threat-models: ${workspace}
|
codeql/threat-models: ${workspace}
|
||||||
codeql/tutorial: ${workspace}
|
codeql/tutorial: ${workspace}
|
||||||
|
|||||||
@@ -4,31 +4,67 @@
|
|||||||
overlay[local?]
|
overlay[local?]
|
||||||
module;
|
module;
|
||||||
|
|
||||||
private import csharp as CS
|
private import internal.rangeanalysis.BoundSpecific
|
||||||
private import semmle.code.csharp.dataflow.SSA::Ssa
|
|
||||||
private import semmle.code.csharp.dataflow.internal.rangeanalysis.ConstantUtils as CU
|
|
||||||
private import semmle.code.csharp.dataflow.internal.rangeanalysis.RangeUtils as RU
|
|
||||||
private import semmle.code.csharp.dataflow.internal.rangeanalysis.SsaUtils as SU
|
|
||||||
private import codeql.rangeanalysis.Bound as SharedBound
|
|
||||||
|
|
||||||
/** Provides C#-specific definitions for bounds. */
|
private newtype TBound =
|
||||||
private module BoundDefs implements SharedBound::BoundDefinitions<CS::Location> {
|
TBoundZero() or
|
||||||
class Type = CS::Type;
|
TBoundSsa(SsaVariable v) { v.getSourceVariable().getType() instanceof IntegralType } or
|
||||||
|
TBoundExpr(Expr e) {
|
||||||
class SsaVariable = SU::SsaVariable;
|
interestingExprBound(e) and
|
||||||
|
not exists(SsaVariable v | e = v.getAUse())
|
||||||
class SsaSourceVariable = SourceVariable;
|
|
||||||
|
|
||||||
class Expr = CS::ControlFlowNodes::ExprNode;
|
|
||||||
|
|
||||||
class IntegralType = CS::IntegralType;
|
|
||||||
|
|
||||||
class ConstantIntegerExpr = CU::ConstantIntegerExpr;
|
|
||||||
|
|
||||||
/** Holds if `e` is a bound expression and it is not an SSA variable read. */
|
|
||||||
predicate interestingExprBound(Expr e) { CU::systemArrayLengthAccess(e.getExpr()) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module BoundImpl = SharedBound::Bound<CS::Location, BoundDefs>;
|
/**
|
||||||
|
* A bound that may be inferred for an expression plus/minus an integer delta.
|
||||||
|
*/
|
||||||
|
abstract class Bound extends TBound {
|
||||||
|
/** Gets a textual representation of this bound. */
|
||||||
|
abstract string toString();
|
||||||
|
|
||||||
import BoundImpl
|
/** Gets an expression that equals this bound plus `delta`. */
|
||||||
|
abstract Expr getExpr(int delta);
|
||||||
|
|
||||||
|
/** Gets an expression that equals this bound. */
|
||||||
|
Expr getExpr() { result = this.getExpr(0) }
|
||||||
|
|
||||||
|
/** Gets the location of this bound. */
|
||||||
|
abstract Location getLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The bound that corresponds to the integer 0. This is used to represent all
|
||||||
|
* integer bounds as bounds are always accompanied by an added integer delta.
|
||||||
|
*/
|
||||||
|
class ZeroBound extends Bound, TBoundZero {
|
||||||
|
override string toString() { result = "0" }
|
||||||
|
|
||||||
|
override Expr getExpr(int delta) { result.(ConstantIntegerExpr).getIntValue() = delta }
|
||||||
|
|
||||||
|
override Location getLocation() { result.hasLocationInfo("", 0, 0, 0, 0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A bound corresponding to the value of an SSA variable.
|
||||||
|
*/
|
||||||
|
class SsaBound extends Bound, TBoundSsa {
|
||||||
|
/** Gets the SSA variable that equals this bound. */
|
||||||
|
SsaVariable getSsa() { this = TBoundSsa(result) }
|
||||||
|
|
||||||
|
override string toString() { result = this.getSsa().toString() }
|
||||||
|
|
||||||
|
override Expr getExpr(int delta) { result = this.getSsa().getAUse() and delta = 0 }
|
||||||
|
|
||||||
|
override Location getLocation() { result = this.getSsa().getLocation() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A bound that corresponds to the value of a specific expression that might be
|
||||||
|
* interesting, but isn't otherwise represented by the value of an SSA variable.
|
||||||
|
*/
|
||||||
|
class ExprBound extends Bound, TBoundExpr {
|
||||||
|
override string toString() { result = this.getExpr().toString() }
|
||||||
|
|
||||||
|
override Expr getExpr(int delta) { this = TBoundExpr(result) and delta = 0 }
|
||||||
|
|
||||||
|
override Location getLocation() { result = this.getExpr().getLocation() }
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Provides C#-specific definitions for bounds.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private import csharp as CS
|
||||||
|
private import semmle.code.csharp.dataflow.SSA::Ssa as Ssa
|
||||||
|
private import semmle.code.csharp.dataflow.internal.rangeanalysis.ConstantUtils as CU
|
||||||
|
private import semmle.code.csharp.dataflow.internal.rangeanalysis.RangeUtils as RU
|
||||||
|
private import semmle.code.csharp.dataflow.internal.rangeanalysis.SsaUtils as SU
|
||||||
|
|
||||||
|
class SsaVariable = SU::SsaVariable;
|
||||||
|
|
||||||
|
class Expr = CS::ControlFlowNodes::ExprNode;
|
||||||
|
|
||||||
|
class Location = CS::Location;
|
||||||
|
|
||||||
|
class IntegralType = CS::IntegralType;
|
||||||
|
|
||||||
|
class ConstantIntegerExpr = CU::ConstantIntegerExpr;
|
||||||
|
|
||||||
|
/** Holds if `e` is a bound expression and it is not an SSA variable read. */
|
||||||
|
predicate interestingExprBound(Expr e) { CU::systemArrayLengthAccess(e.getExpr()) }
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
.. _codeql-cli-2.25.6:
|
|
||||||
|
|
||||||
==========================
|
|
||||||
CodeQL 2.25.6 (2026-06-04)
|
|
||||||
==========================
|
|
||||||
|
|
||||||
.. contents:: Contents
|
|
||||||
:depth: 2
|
|
||||||
:local:
|
|
||||||
:backlinks: none
|
|
||||||
|
|
||||||
This is an overview of changes in the CodeQL CLI and relevant CodeQL query and library packs. For additional updates on changes to the CodeQL code scanning experience, check out the `code scanning section on the GitHub blog <https://github.blog/tag/code-scanning/>`__, `relevant GitHub Changelog updates <https://github.blog/changelog/label/application-security/>`__, `changes in the CodeQL extension for Visual Studio Code <https://marketplace.visualstudio.com/items/GitHub.vscode-codeql/changelog>`__, and the `CodeQL Action changelog <https://github.com/github/codeql-action/blob/main/CHANGELOG.md>`__.
|
|
||||||
|
|
||||||
Security Coverage
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
CodeQL 2.25.6 runs a total of 496 security queries when configured with the Default suite (covering 169 CWE). The Extended suite enables an additional 131 queries (covering 32 more CWE).
|
|
||||||
|
|
||||||
CodeQL CLI
|
|
||||||
----------
|
|
||||||
|
|
||||||
Improvements
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
* When the :code:`git` executable is available, CodeQL can now obtain configuration and queries from SHA-256 Git repositories, and infer Git metadata about them.
|
|
||||||
|
|
||||||
Miscellaneous
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
* The build of Eclipse Temurin OpenJDK that is used to run the CodeQL CLI has been updated to version 21.0.11.
|
|
||||||
|
|
||||||
Query Packs
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Bug Fixes
|
|
||||||
~~~~~~~~~
|
|
||||||
|
|
||||||
GitHub Actions
|
|
||||||
""""""""""""""
|
|
||||||
|
|
||||||
* Adjusted (minor) help file descriptions for queries: :code:`actions/untrusted-checkout/critical`, :code:`actions/untrusted-checkout/high`, :code:`actions/untrusted-checkout/medium`. Clarified wording on a minor point, added one more listed resource and added one more recommendation for things to check.
|
|
||||||
|
|
||||||
Major Analysis Improvements
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
GitHub Actions
|
|
||||||
""""""""""""""
|
|
||||||
|
|
||||||
* Adjusted :code:`actions/untrusted-checkout/critical` to align more with other untrusted resource queries, where the alert location is the location where the artifact is obtained from (the checkout point). This aligns with the other 2 related queries. This will cause the same alerts to re-open for closed alerts of this query.
|
|
||||||
|
|
||||||
Minor Analysis Improvements
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
GitHub Actions
|
|
||||||
""""""""""""""
|
|
||||||
|
|
||||||
* Altered the alert message for clarity for queries: :code:`actions/untrusted-checkout/critical`, :code:`actions/untrusted-checkout/high`.
|
|
||||||
* The :code:`actions/unpinned-tag` query now recognizes 64-character SHA-256 commit hashes as properly pinned references, in addition to 40-character SHA-1 hashes.
|
|
||||||
|
|
||||||
Query Metadata Changes
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
GitHub Actions
|
|
||||||
""""""""""""""
|
|
||||||
|
|
||||||
* Reversed adjustment of the name of :code:`actions/untrusted-checkout/high`, but kept the portion of the previous change for the word "trusted" to "privileged". Added a missing "a" to phrasing in :code:`actions/untrusted-checkout/high` and :code:`actions/untrusted-checkout/medium`.
|
|
||||||
|
|
||||||
Language Libraries
|
|
||||||
------------------
|
|
||||||
|
|
||||||
Major Analysis Improvements
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Swift
|
|
||||||
"""""
|
|
||||||
|
|
||||||
* Upgraded to allow analysis of Swift 6.3.2.
|
|
||||||
|
|
||||||
Minor Analysis Improvements
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
C/C++
|
|
||||||
"""""
|
|
||||||
|
|
||||||
* Added flow source models for :code:`scanf_s` and related functions.
|
|
||||||
* Added a :code:`Call` column to :code:`LocalFlowSourceFunction::hasLocalFlowSource` and :code:`RemoteFlowSourceFunction::hasRemoteFlowSource`. The old predicates without a :code:`Call` column continue to be supported.
|
|
||||||
|
|
||||||
C#
|
|
||||||
""
|
|
||||||
|
|
||||||
* Full support for C# 14 / .NET 10. All new language features are now supported by the extractor. The QL library and data flow analysis now support the new C# 14 language constructs and include generated Models as Data (MaD) models for the .NET 10 runtime.
|
|
||||||
* C# 14: Added support for user-defined instance increment/decrement operators.
|
|
||||||
|
|
||||||
Java/Kotlin
|
|
||||||
"""""""""""
|
|
||||||
|
|
||||||
* Added LLM-generated source and sink models for :code:`org.apache.avro`.
|
|
||||||
|
|
||||||
JavaScript/TypeScript
|
|
||||||
"""""""""""""""""""""
|
|
||||||
|
|
||||||
* The sensitive data heuristics used to identify code that handles passwords and private data have been improved. Most of the changes permit more variations of established patterns, thereby finding more sensitive data. Queries that use the sensitive data library (for example :code:`js/clear-text-logging`) may find more correct results and fewer false positive results after these changes.
|
|
||||||
|
|
||||||
Python
|
|
||||||
""""""
|
|
||||||
|
|
||||||
* The sensitive data heuristics used to identify code that handles passwords and private data have been improved. Most of the changes permit more variations of established patterns, thereby finding more sensitive data. Queries that use the sensitive data library (for example :code:`py/clear-text-logging-sensitive-data`) may find more correct results and fewer false positive results after these changes.
|
|
||||||
|
|
||||||
Swift
|
|
||||||
"""""
|
|
||||||
|
|
||||||
* The sensitive data heuristics used to identify code that handles passwords and private data have been improved. Most of the changes permit more variations of established patterns, thereby finding more sensitive data. Queries that use the sensitive data library (for example :code:`swift/cleartext-logging`) may find more correct results and fewer false positive results after these changes.
|
|
||||||
|
|
||||||
GitHub Actions
|
|
||||||
""""""""""""""
|
|
||||||
|
|
||||||
* The GitHub Actions analysis now recognizes more Bash regex checks that restrict a value to alphanumeric characters, including regexes like :code:`^[0-9a-zA-Z]{40}([0-9a-zA-Z]{24})?$` which check for a SHA-1 or SHA-256 hash. This may reduce false positive results where command output is validated with grouped or optional alphanumeric patterns before being used.
|
|
||||||
|
|
||||||
Rust
|
|
||||||
""""
|
|
||||||
|
|
||||||
* The sensitive data heuristics used to identify code that handles passwords and private data have been improved. Most of the changes permit more variations of established patterns, thereby finding more sensitive data. Queries that use the sensitive data library (for example :code:`rust/cleartext-logging`) may find more correct results and fewer false positive results after these changes.
|
|
||||||
|
|
||||||
Deprecated APIs
|
|
||||||
~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
C/C++
|
|
||||||
"""""
|
|
||||||
|
|
||||||
* The :code:`UsingAliasTypedefType` class has been deprecated. Use :code:`TypeAliasType` instead.
|
|
||||||
|
|
||||||
New Features
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
C/C++
|
|
||||||
"""""
|
|
||||||
|
|
||||||
* Added a :code:`getOriginalTemplate` predicate to :code:`TemplateClass`, :code:`TemplateFunction`, :code:`TemplateVariable`, and :code:`AliasTemplateType`, which yields the class member template the template was generated from. The predicates only have results for templates that are members of class template instantiations.
|
|
||||||
* Added :code:`AliasTemplateType` and :code:`AliasTemplateInstantiationType` classes, representing C++ alias templates and their instantiations.
|
|
||||||
@@ -11,7 +11,6 @@ A list of queries for each suite and language `is available here <https://docs.g
|
|||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
codeql-cli-2.25.6
|
|
||||||
codeql-cli-2.25.5
|
codeql-cli-2.25.5
|
||||||
codeql-cli-2.25.4
|
codeql-cli-2.25.4
|
||||||
codeql-cli-2.25.3
|
codeql-cli-2.25.3
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ inputs:
|
|||||||
go-test-version:
|
go-test-version:
|
||||||
description: Which Go version to use for running the tests
|
description: Which Go version to use for running the tests
|
||||||
required: false
|
required: false
|
||||||
default: "~1.26.4"
|
default: "~1.26.0"
|
||||||
run-code-checks:
|
run-code-checks:
|
||||||
description: Whether to run formatting, code and qhelp generation checks
|
description: Whether to run formatting, code and qhelp generation checks
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ module github.com/github/codeql-go/extractor
|
|||||||
|
|
||||||
go 1.26
|
go 1.26
|
||||||
|
|
||||||
toolchain go1.26.4
|
toolchain go1.26.0
|
||||||
|
|
||||||
// when updating this, run
|
// when updating this, run
|
||||||
// bazel run @rules_go//go -- mod tidy
|
// bazel run @rules_go//go -- mod tidy
|
||||||
// when adding or removing dependencies, run
|
// when adding or removing dependencies, run
|
||||||
// bazel mod tidy
|
// bazel mod tidy
|
||||||
require (
|
require (
|
||||||
golang.org/x/mod v0.37.0
|
golang.org/x/mod v0.36.0
|
||||||
golang.org/x/tools v0.45.0
|
golang.org/x/tools v0.45.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||||
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import semmle.go.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
|
|||||||
import utils.test.InlineFlowTest
|
import utils.test.InlineFlowTest
|
||||||
|
|
||||||
module Config implements DataFlow::ConfigSig {
|
module Config implements DataFlow::ConfigSig {
|
||||||
predicate isSource(DataFlow::Node source) { sourceNode(source, "qltest") }
|
predicate isSource(DataFlow::Node src) { sourceNode(src, "qltest") }
|
||||||
|
|
||||||
predicate isSink(DataFlow::Node sink) { sinkNode(sink, "qltest") }
|
predicate isSink(DataFlow::Node src) { sinkNode(src, "qltest") }
|
||||||
}
|
}
|
||||||
|
|
||||||
import ValueFlowTest<Config>
|
import ValueFlowTest<Config>
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
reverseRead
|
|
||||||
| main.go:23:3:23:5 | out | Origin of readStep is missing a PostUpdateNode. |
|
|
||||||
@@ -4,7 +4,7 @@ func source() string {
|
|||||||
return "untrusted data"
|
return "untrusted data"
|
||||||
}
|
}
|
||||||
|
|
||||||
func sink(any) {
|
func sink(string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type A struct {
|
type A struct {
|
||||||
@@ -19,10 +19,6 @@ func functionWithVarArgsParameter(s ...string) string {
|
|||||||
return s[1]
|
return s[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func functionWithVarArgsOutParameter(in string, out ...*string) {
|
|
||||||
*out[0] = in
|
|
||||||
}
|
|
||||||
|
|
||||||
func functionWithSliceOfStructsParameter(s []A) string {
|
func functionWithSliceOfStructsParameter(s []A) string {
|
||||||
return s[1].f
|
return s[1].f
|
||||||
}
|
}
|
||||||
@@ -42,12 +38,6 @@ func main() {
|
|||||||
sink(functionWithVarArgsParameter(sSlice...)) // $ hasValueFlow="call to functionWithVarArgsParameter"
|
sink(functionWithVarArgsParameter(sSlice...)) // $ hasValueFlow="call to functionWithVarArgsParameter"
|
||||||
sink(functionWithVarArgsParameter(s0, s1)) // $ hasValueFlow="call to functionWithVarArgsParameter"
|
sink(functionWithVarArgsParameter(s0, s1)) // $ hasValueFlow="call to functionWithVarArgsParameter"
|
||||||
|
|
||||||
var out1 *string
|
|
||||||
var out2 *string
|
|
||||||
functionWithVarArgsOutParameter(source(), out1, out2)
|
|
||||||
sink(out1) // $ MISSING: hasValueFlow="out1"
|
|
||||||
sink(out2) // $ MISSING: hasValueFlow="out2"
|
|
||||||
|
|
||||||
sliceOfStructs := []A{{f: source()}}
|
sliceOfStructs := []A{{f: source()}}
|
||||||
sink(sliceOfStructs[0].f) // $ hasValueFlow="selection of f"
|
sink(sliceOfStructs[0].f) // $ hasValueFlow="selection of f"
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
invalidModelRow
|
|
||||||
testFailures
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
extensions:
|
|
||||||
- addsTo:
|
|
||||||
pack: codeql/go-all
|
|
||||||
extensible: summaryModel
|
|
||||||
data:
|
|
||||||
- ["github.com/nonexistent/test", "", False, "FunctionWithParameter", "", "", "Argument[0]", "ReturnValue", "value", "manual"]
|
|
||||||
- ["github.com/nonexistent/test", "", False, "FunctionWithSliceParameter", "", "", "Argument[0].ArrayElement", "ReturnValue", "value", "manual"]
|
|
||||||
- ["github.com/nonexistent/test", "", False, "FunctionWithVarArgsParameter", "", "", "Argument[0].ArrayElement", "ReturnValue", "value", "manual"]
|
|
||||||
- ["github.com/nonexistent/test", "", False, "FunctionWithVarArgsOutParameter", "", "", "Argument[0]", "Argument[1].ArrayElement", "value", "manual"]
|
|
||||||
- ["github.com/nonexistent/test", "", False, "FunctionWithSliceOfStructsParameter", "", "", "Argument[0].ArrayElement.Field[github.com/nonexistent/test.A.Field]", "ReturnValue", "value", "manual"]
|
|
||||||
- ["github.com/nonexistent/test", "", False, "FunctionWithVarArgsOfStructsParameter", "", "", "Argument[0].ArrayElement.Field[github.com/nonexistent/test.A.Field]", "ReturnValue", "value", "manual"]
|
|
||||||
- addsTo:
|
|
||||||
pack: codeql/go-all
|
|
||||||
extensible: sourceModel
|
|
||||||
data:
|
|
||||||
- ["github.com/nonexistent/test", "", False, "VariadicSource", "", "", "Argument[0]", "qltest", "manual"]
|
|
||||||
- addsTo:
|
|
||||||
pack: codeql/go-all
|
|
||||||
extensible: sinkModel
|
|
||||||
data:
|
|
||||||
- ["github.com/nonexistent/test", "", False, "VariadicSink", "", "", "Argument[0]", "qltest", "manual"]
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import go
|
|
||||||
import semmle.go.dataflow.ExternalFlow
|
|
||||||
import ModelValidation
|
|
||||||
import utils.test.InlineFlowTest
|
|
||||||
|
|
||||||
module Config implements DataFlow::ConfigSig {
|
|
||||||
predicate isSource(DataFlow::Node source) {
|
|
||||||
sourceNode(source, "qltest")
|
|
||||||
or
|
|
||||||
exists(Function fn | fn.hasQualifiedName(_, ["source", "taint"]) |
|
|
||||||
source = fn.getACall().getResult()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
predicate isSink(DataFlow::Node sink) {
|
|
||||||
sinkNode(sink, "qltest")
|
|
||||||
or
|
|
||||||
exists(Function fn | fn.hasQualifiedName(_, "sink") | sink = fn.getACall().getAnArgument())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
import FlowTest<Config, Config>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
module semmle.go.Packages
|
|
||||||
|
|
||||||
go 1.25
|
|
||||||
|
|
||||||
require github.com/nonexistent/test v0.0.0-20200203000000-0000000000000
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/nonexistent/test"
|
|
||||||
)
|
|
||||||
|
|
||||||
func source() string {
|
|
||||||
return "untrusted data"
|
|
||||||
}
|
|
||||||
|
|
||||||
func sink(any) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
s := source()
|
|
||||||
sink(test.FunctionWithParameter(s)) // $ hasValueFlow="call to FunctionWithParameter"
|
|
||||||
|
|
||||||
stringSlice := []string{source()}
|
|
||||||
sink(stringSlice[0]) // $ hasValueFlow="index expression"
|
|
||||||
|
|
||||||
s0 := ""
|
|
||||||
s1 := source()
|
|
||||||
sSlice := []string{s0, s1}
|
|
||||||
sink(test.FunctionWithParameter(sSlice[1])) // $ hasValueFlow="call to FunctionWithParameter"
|
|
||||||
sink(test.FunctionWithSliceParameter(sSlice)) // $ hasValueFlow="call to FunctionWithSliceParameter"
|
|
||||||
sink(test.FunctionWithVarArgsParameter(sSlice...)) // $ hasValueFlow="call to FunctionWithVarArgsParameter"
|
|
||||||
sink(test.FunctionWithVarArgsParameter(s0, s1)) // $ hasValueFlow="call to FunctionWithVarArgsParameter"
|
|
||||||
|
|
||||||
var out1 *string
|
|
||||||
var out2 *string
|
|
||||||
test.FunctionWithVarArgsOutParameter(source(), out1, out2)
|
|
||||||
sink(out1) // $ MISSING: hasValueFlow="out1"
|
|
||||||
sink(out2) // $ MISSING: hasValueFlow="out2"
|
|
||||||
|
|
||||||
sliceOfStructs := []test.A{{Field: source()}}
|
|
||||||
sink(sliceOfStructs[0].Field) // $ hasValueFlow="selection of Field"
|
|
||||||
|
|
||||||
a0 := test.A{Field: ""}
|
|
||||||
a1 := test.A{Field: source()}
|
|
||||||
aSlice := []test.A{a0, a1}
|
|
||||||
sink(test.FunctionWithSliceOfStructsParameter(aSlice)) // $ hasValueFlow="call to FunctionWithSliceOfStructsParameter"
|
|
||||||
sink(test.FunctionWithVarArgsOfStructsParameter(aSlice...)) // $ hasValueFlow="call to FunctionWithVarArgsOfStructsParameter"
|
|
||||||
sink(test.FunctionWithVarArgsOfStructsParameter(a0, a1)) // $ hasValueFlow="call to FunctionWithVarArgsOfStructsParameter"
|
|
||||||
|
|
||||||
var variadicSource string
|
|
||||||
test.VariadicSource(&variadicSource)
|
|
||||||
sink(variadicSource) // $ MISSING: hasTaintFlow="variadicSource"
|
|
||||||
sink(&variadicSource) // $ MISSING: hasTaintFlow="&..."
|
|
||||||
|
|
||||||
var variadicSourcePtr *string
|
|
||||||
test.VariadicSource(variadicSourcePtr)
|
|
||||||
sink(variadicSourcePtr) // $ MISSING: hasTaintFlow="variadicSourcePtr"
|
|
||||||
sink(*variadicSourcePtr) // $ MISSING: hasTaintFlow="star expression"
|
|
||||||
|
|
||||||
test.VariadicSink(source()) // $ hasTaintFlow="[]type{args}"
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package test
|
|
||||||
|
|
||||||
type A struct {
|
|
||||||
Field string
|
|
||||||
}
|
|
||||||
|
|
||||||
func FunctionWithParameter(s string) string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func FunctionWithSliceParameter(s []string) string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func FunctionWithVarArgsParameter(s ...string) string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func FunctionWithVarArgsOutParameter(in string, out ...*string) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func FunctionWithSliceOfStructsParameter(s []A) string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func FunctionWithVarArgsOfStructsParameter(s ...A) string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func VariadicSource(s ...*string) {}
|
|
||||||
|
|
||||||
func VariadicSink(s ...string) {}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# github.com/nonexistent/test v0.0.0-20200203000000-0000000000000
|
|
||||||
## explicit
|
|
||||||
github.com/nonexistent/test
|
|
||||||
@@ -20,9 +20,6 @@ class SummaryModelTest extends DataFlow::FunctionModel {
|
|||||||
this.hasQualifiedName("github.com/nonexistent/test", "FunctionWithVarArgsParameter") and
|
this.hasQualifiedName("github.com/nonexistent/test", "FunctionWithVarArgsParameter") and
|
||||||
(inp.isParameter(_) and outp.isResult())
|
(inp.isParameter(_) and outp.isResult())
|
||||||
or
|
or
|
||||||
this.hasQualifiedName("github.com/nonexistent/test", "FunctionWithVarArgsOutParameter") and
|
|
||||||
(inp.isParameter(0) and outp.isParameter(any(int i | i >= 1)))
|
|
||||||
or
|
|
||||||
this.hasQualifiedName("github.com/nonexistent/test", "FunctionWithSliceOfStructsParameter") and
|
this.hasQualifiedName("github.com/nonexistent/test", "FunctionWithSliceOfStructsParameter") and
|
||||||
(inp.isParameter(0) and outp.isResult())
|
(inp.isParameter(0) and outp.isResult())
|
||||||
or
|
or
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
module semmle.go.Packages
|
module semmle.go.Packages
|
||||||
|
|
||||||
go 1.25
|
go 1.17
|
||||||
|
|
||||||
require github.com/nonexistent/test v0.0.0-20200203000000-0000000000000
|
require github.com/nonexistent/test v0.0.0-20200203000000-0000000000000
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ func source() string {
|
|||||||
return "untrusted data"
|
return "untrusted data"
|
||||||
}
|
}
|
||||||
|
|
||||||
func sink(any) {
|
func sink(string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -24,14 +24,7 @@ func main() {
|
|||||||
sink(test.FunctionWithParameter(sSlice[1])) // $ hasValueFlow="call to FunctionWithParameter"
|
sink(test.FunctionWithParameter(sSlice[1])) // $ hasValueFlow="call to FunctionWithParameter"
|
||||||
sink(test.FunctionWithSliceParameter(sSlice)) // $ hasTaintFlow="call to FunctionWithSliceParameter" MISSING: hasValueFlow="call to FunctionWithSliceParameter"
|
sink(test.FunctionWithSliceParameter(sSlice)) // $ hasTaintFlow="call to FunctionWithSliceParameter" MISSING: hasValueFlow="call to FunctionWithSliceParameter"
|
||||||
sink(test.FunctionWithVarArgsParameter(sSlice...)) // $ hasTaintFlow="call to FunctionWithVarArgsParameter" MISSING: hasValueFlow="call to FunctionWithVarArgsParameter"
|
sink(test.FunctionWithVarArgsParameter(sSlice...)) // $ hasTaintFlow="call to FunctionWithVarArgsParameter" MISSING: hasValueFlow="call to FunctionWithVarArgsParameter"
|
||||||
randomFunctionWithMoreThanOneParameter(1, 2, 3, 4, 5) // This is needed to make the next line pass, because we need to have seen a call to a function with at least 2 parameters for ParameterInput to exist with index 1.
|
sink(test.FunctionWithVarArgsParameter(s0, s1)) // $ MISSING: hasValueFlow="call to FunctionWithVarArgsParameter"
|
||||||
sink(test.FunctionWithVarArgsParameter(s0, s1)) // $ hasValueFlow="call to FunctionWithVarArgsParameter"
|
|
||||||
|
|
||||||
var out1 *string
|
|
||||||
var out2 *string
|
|
||||||
test.FunctionWithVarArgsOutParameter(source(), out1, out2)
|
|
||||||
sink(out1) // $ hasValueFlow="out1"
|
|
||||||
sink(out2) // $ hasValueFlow="out2"
|
|
||||||
|
|
||||||
sliceOfStructs := []test.A{{Field: source()}}
|
sliceOfStructs := []test.A{{Field: source()}}
|
||||||
sink(sliceOfStructs[0].Field) // $ hasValueFlow="selection of Field"
|
sink(sliceOfStructs[0].Field) // $ hasValueFlow="selection of Field"
|
||||||
@@ -44,6 +37,3 @@ func main() {
|
|||||||
sink(test.FunctionWithVarArgsOfStructsParameter(aSlice...)) // $ MISSING: hasValueFlow="call to FunctionWithVarArgsOfStructsParameter"
|
sink(test.FunctionWithVarArgsOfStructsParameter(aSlice...)) // $ MISSING: hasValueFlow="call to FunctionWithVarArgsOfStructsParameter"
|
||||||
sink(test.FunctionWithVarArgsOfStructsParameter(a0, a1)) // $ MISSING: hasValueFlow="call to FunctionWithVarArgsOfStructsParameter"
|
sink(test.FunctionWithVarArgsOfStructsParameter(a0, a1)) // $ MISSING: hasValueFlow="call to FunctionWithVarArgsOfStructsParameter"
|
||||||
}
|
}
|
||||||
|
|
||||||
func randomFunctionWithMoreThanOneParameter(i1, i2, i3, i4, i5 int) {
|
|
||||||
}
|
|
||||||
|
|||||||
Binary file not shown.
@@ -16,9 +16,6 @@ func FunctionWithVarArgsParameter(s ...string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func FunctionWithVarArgsOutParameter(in string, out ...*string) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func FunctionWithSliceOfStructsParameter(s []A) string {
|
func FunctionWithSliceOfStructsParameter(s []A) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ org.apache.hc.core5.http,73,2,45,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,72,,,,,,,,,,,
|
|||||||
org.apache.hc.core5.net,,,18,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,18,
|
org.apache.hc.core5.net,,,18,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,18,
|
||||||
org.apache.hc.core5.util,,,24,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,18,6
|
org.apache.hc.core5.util,,,24,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,18,6
|
||||||
org.apache.hive.hcatalog.templeton,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,
|
org.apache.hive.hcatalog.templeton,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,
|
||||||
org.apache.http,53,3,117,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,51,,,,,,,,,,,,,,,,3,108,9
|
org.apache.http,48,3,95,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,46,,,,,,,,,,,,,,,,3,86,9
|
||||||
org.apache.ibatis.jdbc,6,,57,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,57,
|
org.apache.ibatis.jdbc,6,,57,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,57,
|
||||||
org.apache.ibatis.mapping,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,
|
org.apache.ibatis.mapping,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,
|
||||||
org.apache.log4j,11,,,,,,,,,,,,,,,,,,,,,,11,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
org.apache.log4j,11,,,,,,,,,,,,,,,,,,,,,,11,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||||
|
|||||||
|
@@ -13,7 +13,7 @@ Java framework & library support
|
|||||||
`Apache Commons IO <https://commons.apache.org/proper/commons-io/>`_,``org.apache.commons.io``,,570,124,105,,,,,15
|
`Apache Commons IO <https://commons.apache.org/proper/commons-io/>`_,``org.apache.commons.io``,,570,124,105,,,,,15
|
||||||
`Apache Commons Lang <https://commons.apache.org/proper/commons-lang/>`_,``org.apache.commons.lang3``,,425,7,,,,,,
|
`Apache Commons Lang <https://commons.apache.org/proper/commons-lang/>`_,``org.apache.commons.lang3``,,425,7,,,,,,
|
||||||
`Apache Commons Text <https://commons.apache.org/proper/commons-text/>`_,``org.apache.commons.text``,,272,,,,,,,
|
`Apache Commons Text <https://commons.apache.org/proper/commons-text/>`_,``org.apache.commons.text``,,272,,,,,,,
|
||||||
`Apache HttpComponents <https://hc.apache.org/>`_,"``org.apache.hc.core5.*``, ``org.apache.http``",5,205,127,,3,,,,124
|
`Apache HttpComponents <https://hc.apache.org/>`_,"``org.apache.hc.core5.*``, ``org.apache.http``",5,183,122,,3,,,,119
|
||||||
`Apache Log4j 2 <https://logging.apache.org/log4j/2.0/>`_,``org.apache.logging.log4j``,,8,359,,,,,,
|
`Apache Log4j 2 <https://logging.apache.org/log4j/2.0/>`_,``org.apache.logging.log4j``,,8,359,,,,,,
|
||||||
`Apache Struts <https://struts.apache.org/>`_,"``org.apache.struts2``, ``org.apache.struts.beanvalidation.validation.interceptor``",,3877,14,,,,,,
|
`Apache Struts <https://struts.apache.org/>`_,"``org.apache.struts2``, ``org.apache.struts.beanvalidation.validation.interceptor``",,3877,14,,,,,,
|
||||||
`Apache Velocity <https://velocity.apache.org/>`_,"``org.apache.velocity.app``, ``org.apache.velocity.runtime``",,,8,,,,,,
|
`Apache Velocity <https://velocity.apache.org/>`_,"``org.apache.velocity.app``, ``org.apache.velocity.runtime``",,,8,,,,,,
|
||||||
@@ -41,5 +41,5 @@ Java framework & library support
|
|||||||
`Thymeleaf <https://www.thymeleaf.org/>`_,``org.thymeleaf``,,2,2,,,,,,
|
`Thymeleaf <https://www.thymeleaf.org/>`_,``org.thymeleaf``,,2,2,,,,,,
|
||||||
`jOOQ <https://www.jooq.org/>`_,``org.jooq``,,,1,,,1,,,
|
`jOOQ <https://www.jooq.org/>`_,``org.jooq``,,,1,,,1,,,
|
||||||
Others,"``actions.osgi``, ``antlr``, ``ch.ethz.ssh2``, ``cn.hutool.core.codec``, ``com.alibaba.com.caucho.hessian.io``, ``com.alibaba.druid.sql``, ``com.alibaba.fastjson2``, ``com.amazonaws.auth``, ``com.auth0.jwt.algorithms``, ``com.azure.identity``, ``com.caucho.burlap.io``, ``com.caucho.hessian.io``, ``com.cedarsoftware.util.io``, ``com.esotericsoftware.kryo.io``, ``com.esotericsoftware.kryo5.io``, ``com.esotericsoftware.yamlbeans``, ``com.hubspot.jinjava``, ``com.jcraft.jsch``, ``com.microsoft.sqlserver.jdbc``, ``com.mitchellbosecke.pebble``, ``com.opensymphony.xwork2``, ``com.sshtools.j2ssh.authentication``, ``com.sun.crypto.provider``, ``com.sun.jndi.ldap``, ``com.sun.net.httpserver``, ``com.sun.net.ssl``, ``com.sun.rowset``, ``com.sun.security.auth.module``, ``com.sun.security.ntlm``, ``com.sun.security.sasl.digest``, ``com.thoughtworks.xstream``, ``com.trilead.ssh2``, ``com.unboundid.ldap.sdk``, ``com.zaxxer.hikari``, ``flexjson``, ``hudson``, ``io.jsonwebtoken``, ``io.undertow.server.handlers.resource``, ``javafx.scene.web``, ``jenkins``, ``jodd.json``, ``liquibase.database.jvm``, ``liquibase.statement.core``, ``net.lingala.zip4j``, ``net.schmizz.sshj``, ``net.sf.json``, ``net.sf.saxon.s9api``, ``ognl``, ``org.acegisecurity``, ``org.antlr.runtime``, ``org.apache.avro``, ``org.apache.commons.codec``, ``org.apache.commons.compress.archivers.tar``, ``org.apache.commons.exec``, ``org.apache.commons.fileupload``, ``org.apache.commons.httpclient.util``, ``org.apache.commons.jelly``, ``org.apache.commons.jexl2``, ``org.apache.commons.jexl3``, ``org.apache.commons.lang``, ``org.apache.commons.logging``, ``org.apache.commons.net``, ``org.apache.commons.ognl``, ``org.apache.cxf.catalog``, ``org.apache.cxf.common.classloader``, ``org.apache.cxf.common.jaxb``, ``org.apache.cxf.common.logging``, ``org.apache.cxf.configuration.jsse``, ``org.apache.cxf.helpers``, ``org.apache.cxf.resource``, ``org.apache.cxf.staxutils``, ``org.apache.cxf.tools.corba.utils``, ``org.apache.cxf.tools.util``, ``org.apache.cxf.transform``, ``org.apache.directory.ldap.client.api``, ``org.apache.hadoop.fs``, ``org.apache.hadoop.hive.metastore``, ``org.apache.hadoop.hive.ql.exec``, ``org.apache.hadoop.hive.ql.metadata``, ``org.apache.hc.client5.http.async.methods``, ``org.apache.hc.client5.http.classic.methods``, ``org.apache.hc.client5.http.fluent``, ``org.apache.hive.hcatalog.templeton``, ``org.apache.ibatis.jdbc``, ``org.apache.ibatis.mapping``, ``org.apache.log4j``, ``org.apache.shiro.authc``, ``org.apache.shiro.codec``, ``org.apache.shiro.jndi``, ``org.apache.shiro.mgt``, ``org.apache.sshd.client.session``, ``org.apache.tools.ant``, ``org.apache.tools.zip``, ``org.codehaus.cargo.container.installer``, ``org.dom4j``, ``org.exolab.castor.xml``, ``org.fusesource.leveldbjni``, ``org.geogebra.web.full.main``, ``org.gradle.api.file``, ``org.ho.yaml``, ``org.influxdb``, ``org.jabsorb``, ``org.jboss.vfs``, ``org.jdbi.v3.core``, ``org.jenkins.ui.icon``, ``org.jenkins.ui.symbol``, ``org.keycloak.models.map.storage``, ``org.kohsuke.stapler``, ``org.lastaflute.web``, ``org.mvel2``, ``org.openjdk.jmh.runner.options``, ``org.owasp.esapi``, ``org.pac4j.jwt.config.encryption``, ``org.pac4j.jwt.config.signature``, ``org.scijava.log``, ``org.xml.sax``, ``org.xmlpull.v1``, ``play.libs.ws``, ``play.mvc``, ``ratpack.core.form``, ``ratpack.core.handling``, ``ratpack.core.http``, ``ratpack.exec``, ``ratpack.form``, ``ratpack.func``, ``ratpack.handling``, ``ratpack.http``, ``ratpack.util``, ``software.amazon.awssdk.transfer.s3.model``, ``sun.jvmstat.perfdata.monitor.protocol.local``, ``sun.jvmstat.perfdata.monitor.protocol.rmi``, ``sun.misc``, ``sun.net.ftp``, ``sun.net.www.protocol.http``, ``sun.security.acl``, ``sun.security.jgss.krb5``, ``sun.security.krb5``, ``sun.security.pkcs``, ``sun.security.pkcs11``, ``sun.security.provider``, ``sun.security.ssl``, ``sun.security.x509``, ``sun.tools.jconsole``",127,6034,775,148,6,14,18,,186
|
Others,"``actions.osgi``, ``antlr``, ``ch.ethz.ssh2``, ``cn.hutool.core.codec``, ``com.alibaba.com.caucho.hessian.io``, ``com.alibaba.druid.sql``, ``com.alibaba.fastjson2``, ``com.amazonaws.auth``, ``com.auth0.jwt.algorithms``, ``com.azure.identity``, ``com.caucho.burlap.io``, ``com.caucho.hessian.io``, ``com.cedarsoftware.util.io``, ``com.esotericsoftware.kryo.io``, ``com.esotericsoftware.kryo5.io``, ``com.esotericsoftware.yamlbeans``, ``com.hubspot.jinjava``, ``com.jcraft.jsch``, ``com.microsoft.sqlserver.jdbc``, ``com.mitchellbosecke.pebble``, ``com.opensymphony.xwork2``, ``com.sshtools.j2ssh.authentication``, ``com.sun.crypto.provider``, ``com.sun.jndi.ldap``, ``com.sun.net.httpserver``, ``com.sun.net.ssl``, ``com.sun.rowset``, ``com.sun.security.auth.module``, ``com.sun.security.ntlm``, ``com.sun.security.sasl.digest``, ``com.thoughtworks.xstream``, ``com.trilead.ssh2``, ``com.unboundid.ldap.sdk``, ``com.zaxxer.hikari``, ``flexjson``, ``hudson``, ``io.jsonwebtoken``, ``io.undertow.server.handlers.resource``, ``javafx.scene.web``, ``jenkins``, ``jodd.json``, ``liquibase.database.jvm``, ``liquibase.statement.core``, ``net.lingala.zip4j``, ``net.schmizz.sshj``, ``net.sf.json``, ``net.sf.saxon.s9api``, ``ognl``, ``org.acegisecurity``, ``org.antlr.runtime``, ``org.apache.avro``, ``org.apache.commons.codec``, ``org.apache.commons.compress.archivers.tar``, ``org.apache.commons.exec``, ``org.apache.commons.fileupload``, ``org.apache.commons.httpclient.util``, ``org.apache.commons.jelly``, ``org.apache.commons.jexl2``, ``org.apache.commons.jexl3``, ``org.apache.commons.lang``, ``org.apache.commons.logging``, ``org.apache.commons.net``, ``org.apache.commons.ognl``, ``org.apache.cxf.catalog``, ``org.apache.cxf.common.classloader``, ``org.apache.cxf.common.jaxb``, ``org.apache.cxf.common.logging``, ``org.apache.cxf.configuration.jsse``, ``org.apache.cxf.helpers``, ``org.apache.cxf.resource``, ``org.apache.cxf.staxutils``, ``org.apache.cxf.tools.corba.utils``, ``org.apache.cxf.tools.util``, ``org.apache.cxf.transform``, ``org.apache.directory.ldap.client.api``, ``org.apache.hadoop.fs``, ``org.apache.hadoop.hive.metastore``, ``org.apache.hadoop.hive.ql.exec``, ``org.apache.hadoop.hive.ql.metadata``, ``org.apache.hc.client5.http.async.methods``, ``org.apache.hc.client5.http.classic.methods``, ``org.apache.hc.client5.http.fluent``, ``org.apache.hive.hcatalog.templeton``, ``org.apache.ibatis.jdbc``, ``org.apache.ibatis.mapping``, ``org.apache.log4j``, ``org.apache.shiro.authc``, ``org.apache.shiro.codec``, ``org.apache.shiro.jndi``, ``org.apache.shiro.mgt``, ``org.apache.sshd.client.session``, ``org.apache.tools.ant``, ``org.apache.tools.zip``, ``org.codehaus.cargo.container.installer``, ``org.dom4j``, ``org.exolab.castor.xml``, ``org.fusesource.leveldbjni``, ``org.geogebra.web.full.main``, ``org.gradle.api.file``, ``org.ho.yaml``, ``org.influxdb``, ``org.jabsorb``, ``org.jboss.vfs``, ``org.jdbi.v3.core``, ``org.jenkins.ui.icon``, ``org.jenkins.ui.symbol``, ``org.keycloak.models.map.storage``, ``org.kohsuke.stapler``, ``org.lastaflute.web``, ``org.mvel2``, ``org.openjdk.jmh.runner.options``, ``org.owasp.esapi``, ``org.pac4j.jwt.config.encryption``, ``org.pac4j.jwt.config.signature``, ``org.scijava.log``, ``org.xml.sax``, ``org.xmlpull.v1``, ``play.libs.ws``, ``play.mvc``, ``ratpack.core.form``, ``ratpack.core.handling``, ``ratpack.core.http``, ``ratpack.exec``, ``ratpack.form``, ``ratpack.func``, ``ratpack.handling``, ``ratpack.http``, ``ratpack.util``, ``software.amazon.awssdk.transfer.s3.model``, ``sun.jvmstat.perfdata.monitor.protocol.local``, ``sun.jvmstat.perfdata.monitor.protocol.rmi``, ``sun.misc``, ``sun.net.ftp``, ``sun.net.www.protocol.http``, ``sun.security.acl``, ``sun.security.jgss.krb5``, ``sun.security.krb5``, ``sun.security.pkcs``, ``sun.security.pkcs11``, ``sun.security.provider``, ``sun.security.ssl``, ``sun.security.x509``, ``sun.tools.jconsole``",127,6034,775,148,6,14,18,,186
|
||||||
Totals,,382,26403,2707,421,16,137,33,1,415
|
Totals,,382,26381,2702,421,16,137,33,1,410
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
category: minorAnalysis
|
|
||||||
---
|
|
||||||
* Improved modeling of Apache HttpClient `execute` method sinks for `java/ssrf` and `java/non-https-url`.
|
|
||||||
@@ -11,7 +11,7 @@ extensions:
|
|||||||
- ["org.apache.http.client.methods", "HttpPost", False, "HttpPost", "", "", "Argument[0]", "request-forgery", "manual"]
|
- ["org.apache.http.client.methods", "HttpPost", False, "HttpPost", "", "", "Argument[0]", "request-forgery", "manual"]
|
||||||
- ["org.apache.http.client.methods", "HttpPut", False, "HttpPut", "", "", "Argument[0]", "request-forgery", "manual"]
|
- ["org.apache.http.client.methods", "HttpPut", False, "HttpPut", "", "", "Argument[0]", "request-forgery", "manual"]
|
||||||
- ["org.apache.http.client.methods", "HttpRequestBase", True, "setURI", "", "", "Argument[0]", "request-forgery", "manual"]
|
- ["org.apache.http.client.methods", "HttpRequestBase", True, "setURI", "", "", "Argument[0]", "request-forgery", "manual"]
|
||||||
- ["org.apache.http.client.methods", "HttpRequestWrapper", True, "setURI", "(URI)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
- ["org.apache.http.client.methods", "HttpRequestWrapper", True, "setURI", "(URI)", "", "Argument[0]", "request-forgery", "hq-manual"]
|
||||||
- ["org.apache.http.client.methods", "HttpTrace", False, "HttpTrace", "", "", "Argument[0]", "request-forgery", "manual"]
|
- ["org.apache.http.client.methods", "HttpTrace", False, "HttpTrace", "", "", "Argument[0]", "request-forgery", "manual"]
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", False, "delete", "", "", "Argument[0]", "request-forgery", "manual"]
|
- ["org.apache.http.client.methods", "RequestBuilder", False, "delete", "", "", "Argument[0]", "request-forgery", "manual"]
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", False, "get", "", "", "Argument[0]", "request-forgery", "manual"]
|
- ["org.apache.http.client.methods", "RequestBuilder", False, "get", "", "", "Argument[0]", "request-forgery", "manual"]
|
||||||
@@ -22,29 +22,3 @@ extensions:
|
|||||||
- ["org.apache.http.client.methods", "RequestBuilder", False, "put", "", "", "Argument[0]", "request-forgery", "manual"]
|
- ["org.apache.http.client.methods", "RequestBuilder", False, "put", "", "", "Argument[0]", "request-forgery", "manual"]
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", False, "setUri", "", "", "Argument[0]", "request-forgery", "manual"]
|
- ["org.apache.http.client.methods", "RequestBuilder", False, "setUri", "", "", "Argument[0]", "request-forgery", "manual"]
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", False, "trace", "", "", "Argument[0]", "request-forgery", "manual"]
|
- ["org.apache.http.client.methods", "RequestBuilder", False, "trace", "", "", "Argument[0]", "request-forgery", "manual"]
|
||||||
- addsTo:
|
|
||||||
pack: codeql/java-all
|
|
||||||
extensible: summaryModel
|
|
||||||
data:
|
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "build", "()", "", "Argument[this]", "ReturnValue", "taint", "ai-manual"]
|
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "delete", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "delete", "(URI)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "get", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "get", "(URI)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "getUri", "()", "", "Argument[this]", "ReturnValue", "taint", "ai-manual"]
|
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "head", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "head", "(URI)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "options", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "options", "(URI)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "patch", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "patch", "(URI)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "post", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "post", "(URI)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "put", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "put", "(URI)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "setUri", "(String)", "", "Argument[0]", "Argument[this]", "taint", "ai-manual"]
|
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "setUri", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "setUri", "(URI)", "", "Argument[0]", "Argument[this]", "taint", "ai-manual"]
|
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "setUri", "(URI)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "trace", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
|
||||||
- ["org.apache.http.client.methods", "RequestBuilder", True, "trace", "(URI)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
|
|
||||||
|
|||||||
@@ -3,11 +3,6 @@ extensions:
|
|||||||
pack: codeql/java-all
|
pack: codeql/java-all
|
||||||
extensible: sinkModel
|
extensible: sinkModel
|
||||||
data:
|
data:
|
||||||
- ["org.apache.http.client", "HttpClient", True, "execute", "(HttpHost,HttpRequest)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
|
||||||
- ["org.apache.http.client", "HttpClient", True, "execute", "(HttpHost,HttpRequest,HttpContext)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
|
||||||
- ["org.apache.http.client", "HttpClient", True, "execute", "(HttpHost,HttpRequest,ResponseHandler)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
|
||||||
- ["org.apache.http.client", "HttpClient", True, "execute", "(HttpHost,HttpRequest,ResponseHandler,HttpContext)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
|
||||||
- ["org.apache.http.client", "HttpClient", True, "execute", "(HttpUriRequest)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
|
||||||
- ["org.apache.http.client", "HttpClient", True, "execute", "(HttpUriRequest,HttpContext)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
- ["org.apache.http.client", "HttpClient", True, "execute", "(HttpUriRequest,HttpContext)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
||||||
- ["org.apache.http.client", "HttpClient", True, "execute", "(HttpUriRequest,ResponseHandler)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
|
||||||
- ["org.apache.http.client", "HttpClient", True, "execute", "(HttpUriRequest,ResponseHandler,HttpContext)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
- ["org.apache.http.client", "HttpClient", True, "execute", "(HttpUriRequest,ResponseHandler,HttpContext)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
||||||
|
- ["org.apache.http.client", "HttpClient", True, "execute", "(HttpUriRequest)", "", "Argument[0]", "request-forgery", "ai-manual"]
|
||||||
|
|||||||
@@ -4,33 +4,67 @@
|
|||||||
overlay[local?]
|
overlay[local?]
|
||||||
module;
|
module;
|
||||||
|
|
||||||
private import java as J
|
private import internal.rangeanalysis.BoundSpecific
|
||||||
private import semmle.code.java.dataflow.SSA
|
|
||||||
private import semmle.code.java.dataflow.RangeUtils as RU
|
|
||||||
private import codeql.rangeanalysis.Bound as SharedBound
|
|
||||||
|
|
||||||
private module BoundDefs implements SharedBound::BoundDefinitions<J::Location> {
|
private newtype TBound =
|
||||||
class SsaVariable extends Ssa::SsaDefinition {
|
TBoundZero() or
|
||||||
/** Gets a use of this variable. */
|
TBoundSsa(SsaVariable v) { v.getSourceVariable().getType() instanceof IntegralType } or
|
||||||
Expr getAUse() { result = super.getARead() }
|
TBoundExpr(Expr e) {
|
||||||
|
interestingExprBound(e) and
|
||||||
|
not exists(SsaVariable v | e = v.getAUse())
|
||||||
}
|
}
|
||||||
|
|
||||||
class SsaSourceVariable = Ssa::SourceVariable;
|
/**
|
||||||
|
* A bound that may be inferred for an expression plus/minus an integer delta.
|
||||||
|
*/
|
||||||
|
abstract class Bound extends TBound {
|
||||||
|
/** Gets a textual representation of this bound. */
|
||||||
|
abstract string toString();
|
||||||
|
|
||||||
class Type = J::Type;
|
/** Gets an expression that equals this bound plus `delta`. */
|
||||||
|
abstract Expr getExpr(int delta);
|
||||||
|
|
||||||
class Expr = J::Expr;
|
/** Gets an expression that equals this bound. */
|
||||||
|
Expr getExpr() { result = this.getExpr(0) }
|
||||||
|
|
||||||
class IntegralType = J::IntegralType;
|
/** Gets the location of this bound. */
|
||||||
|
abstract Location getLocation();
|
||||||
class ConstantIntegerExpr = RU::ConstantIntegerExpr;
|
|
||||||
|
|
||||||
/** Holds if `e` is a bound expression and it is not an SSA variable read. */
|
|
||||||
predicate interestingExprBound(Expr e) {
|
|
||||||
e.(J::FieldRead).getField() instanceof J::ArrayLengthField
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module BoundImpl = SharedBound::Bound<J::Location, BoundDefs>;
|
/**
|
||||||
|
* The bound that corresponds to the integer 0. This is used to represent all
|
||||||
|
* integer bounds as bounds are always accompanied by an added integer delta.
|
||||||
|
*/
|
||||||
|
class ZeroBound extends Bound, TBoundZero {
|
||||||
|
override string toString() { result = "0" }
|
||||||
|
|
||||||
import BoundImpl
|
override Expr getExpr(int delta) { result.(ConstantIntegerExpr).getIntValue() = delta }
|
||||||
|
|
||||||
|
override Location getLocation() { result.hasLocationInfo("", 0, 0, 0, 0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A bound corresponding to the value of an SSA variable.
|
||||||
|
*/
|
||||||
|
class SsaBound extends Bound, TBoundSsa {
|
||||||
|
/** Gets the SSA variable that equals this bound. */
|
||||||
|
SsaVariable getSsa() { this = TBoundSsa(result) }
|
||||||
|
|
||||||
|
override string toString() { result = this.getSsa().toString() }
|
||||||
|
|
||||||
|
override Expr getExpr(int delta) { result = this.getSsa().getAUse() and delta = 0 }
|
||||||
|
|
||||||
|
override Location getLocation() { result = this.getSsa().getLocation() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A bound that corresponds to the value of a specific expression that might be
|
||||||
|
* interesting, but isn't otherwise represented by the value of an SSA variable.
|
||||||
|
*/
|
||||||
|
class ExprBound extends Bound, TBoundExpr {
|
||||||
|
override string toString() { result = this.getExpr().toString() }
|
||||||
|
|
||||||
|
override Expr getExpr(int delta) { this = TBoundExpr(result) and delta = 0 }
|
||||||
|
|
||||||
|
override Location getLocation() { result = this.getExpr().getLocation() }
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Provides Java-specific definitions for bounds.
|
||||||
|
*/
|
||||||
|
overlay[local?]
|
||||||
|
module;
|
||||||
|
|
||||||
|
private import java as J
|
||||||
|
private import semmle.code.java.dataflow.SSA as Ssa
|
||||||
|
private import semmle.code.java.dataflow.RangeUtils as RU
|
||||||
|
|
||||||
|
class SsaVariable extends Ssa::SsaDefinition {
|
||||||
|
/** Gets a use of this variable. */
|
||||||
|
Expr getAUse() { result = super.getARead() }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Expr = J::Expr;
|
||||||
|
|
||||||
|
class Location = J::Location;
|
||||||
|
|
||||||
|
class IntegralType = J::IntegralType;
|
||||||
|
|
||||||
|
class ConstantIntegerExpr = RU::ConstantIntegerExpr;
|
||||||
|
|
||||||
|
/** Holds if `e` is a bound expression and it is not an SSA variable read. */
|
||||||
|
predicate interestingExprBound(Expr e) {
|
||||||
|
e.(J::FieldRead).getField() instanceof J::ArrayLengthField
|
||||||
|
}
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.apache.http.HttpHost;
|
|
||||||
import org.apache.http.HttpRequest;
|
|
||||||
import org.apache.http.client.HttpClient;
|
|
||||||
import org.apache.http.client.ResponseHandler;
|
|
||||||
import org.apache.http.client.methods.HttpUriRequest;
|
|
||||||
import org.apache.http.client.methods.RequestBuilder;
|
|
||||||
import org.apache.http.impl.client.HttpClients;
|
|
||||||
import org.apache.http.message.BasicHttpRequest;
|
|
||||||
import org.apache.http.protocol.HttpContext;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServlet;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
public class ApacheHttpClientExecuteSSRF extends HttpServlet {
|
|
||||||
|
|
||||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
|
||||||
throws ServletException, IOException {
|
|
||||||
try {
|
|
||||||
|
|
||||||
String source = request.getParameter("host"); // $ Source
|
|
||||||
|
|
||||||
HttpHost host = new HttpHost(source);
|
|
||||||
HttpRequest req = new BasicHttpRequest("GET", "/");
|
|
||||||
HttpUriRequest uriReq = RequestBuilder.get(source).build(); // $ Alert
|
|
||||||
HttpContext context = null;
|
|
||||||
HttpClient client = HttpClients.createDefault();
|
|
||||||
ResponseHandler<Object> handler = null;
|
|
||||||
|
|
||||||
client.execute(host, req); // $ Alert
|
|
||||||
client.execute(host, req, context); // $ Alert
|
|
||||||
client.execute(host, req, handler); // $ Alert
|
|
||||||
client.execute(host, req, handler, context); // $ Alert
|
|
||||||
client.execute(uriReq); // $ Alert
|
|
||||||
client.execute(uriReq, context); // $ Alert
|
|
||||||
client.execute(uriReq, handler); // $ Alert
|
|
||||||
client.execute(uriReq, handler, context); // $ Alert
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
// TODO: handle exception
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
|||||||
//semmle-extractor-options: --javac-args -source 11 -target 11 -cp ${testdir}/../../../stubs/javax-validation-constraints:${testdir}/../../../stubs/springframework-5.8.x:${testdir}/../../../stubs/javax-ws-rs-api-2.1.1:${testdir}/../../../stubs/javax-ws-rs-api-3.0.0:${testdir}/../../../stubs/apache-http-4.4.13/:${testdir}/../../../stubs/apache-http-client-4.4.13:${testdir}/../../../stubs/projectreactor-3.4.3/:${testdir}/../../../stubs/postgresql-42.3.3/:${testdir}/../../../stubs/HikariCP-3.4.5/:${testdir}/../../../stubs/spring-jdbc-5.3.8/:${testdir}/../../../stubs/jdbi3-core-3.27.2/:${testdir}/../../../stubs/cargo:${testdir}/../../../stubs/javafx-web:${testdir}/../../../stubs/apache-commons-jelly-1.0.1:${testdir}/../../../stubs/dom4j-2.1.1:${testdir}/../../../stubs/jaxen-1.2.0:${testdir}/../../../stubs/stapler-1.263:${testdir}/../../../stubs/javax-servlet-2.5:${testdir}/../../../stubs/apache-commons-fileupload-1.4:${testdir}/../../../stubs/saxon-xqj-9.x:${testdir}/../../../stubs/apache-commons-beanutils:${testdir}/../../../stubs/apache-commons-lang:${testdir}/../../../stubs/apache-http-5:${testdir}/../../../stubs/playframework-2.6.x:${testdir}/../../../stubs/jaxws-api-2.0:${testdir}/../../../stubs/apache-cxf
|
//semmle-extractor-options: --javac-args -source 11 -target 11 -cp ${testdir}/../../../stubs/javax-validation-constraints:${testdir}/../../../stubs/springframework-5.8.x:${testdir}/../../../stubs/javax-ws-rs-api-2.1.1:${testdir}/../../../stubs/javax-ws-rs-api-3.0.0:${testdir}/../../../stubs/apache-http-4.4.13/:${testdir}/../../../stubs/projectreactor-3.4.3/:${testdir}/../../../stubs/postgresql-42.3.3/:${testdir}/../../../stubs/HikariCP-3.4.5/:${testdir}/../../../stubs/spring-jdbc-5.3.8/:${testdir}/../../../stubs/jdbi3-core-3.27.2/:${testdir}/../../../stubs/cargo:${testdir}/../../../stubs/javafx-web:${testdir}/../../../stubs/apache-commons-jelly-1.0.1:${testdir}/../../../stubs/dom4j-2.1.1:${testdir}/../../../stubs/jaxen-1.2.0:${testdir}/../../../stubs/stapler-1.263:${testdir}/../../../stubs/javax-servlet-2.5:${testdir}/../../../stubs/apache-commons-fileupload-1.4:${testdir}/../../../stubs/saxon-xqj-9.x:${testdir}/../../../stubs/apache-commons-beanutils:${testdir}/../../../stubs/apache-commons-lang:${testdir}/../../../stubs/apache-http-5:${testdir}/../../../stubs/playframework-2.6.x:${testdir}/../../../stubs/jaxws-api-2.0:${testdir}/../../../stubs/apache-cxf
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
// Generated automatically from org.apache.http.client.HttpClient for testing purposes
|
|
||||||
|
|
||||||
package org.apache.http.client;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import org.apache.http.HttpHost;
|
|
||||||
import org.apache.http.HttpRequest;
|
|
||||||
import org.apache.http.HttpResponse;
|
|
||||||
import org.apache.http.client.methods.HttpUriRequest;
|
|
||||||
import org.apache.http.protocol.HttpContext;
|
|
||||||
|
|
||||||
public interface HttpClient {
|
|
||||||
HttpResponse execute(HttpHost target, HttpRequest request) throws IOException;
|
|
||||||
HttpResponse execute(HttpHost target, HttpRequest request, HttpContext context) throws IOException;
|
|
||||||
<T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler) throws IOException;
|
|
||||||
<T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler, HttpContext context)
|
|
||||||
throws IOException;
|
|
||||||
HttpResponse execute(HttpUriRequest request) throws IOException;
|
|
||||||
HttpResponse execute(HttpUriRequest request, HttpContext context) throws IOException;
|
|
||||||
<T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler) throws IOException;
|
|
||||||
<T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler, HttpContext context)
|
|
||||||
throws IOException;
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
// Generated automatically from org.apache.http.client.ResponseHandler for testing purposes
|
|
||||||
|
|
||||||
package org.apache.http.client;
|
|
||||||
|
|
||||||
import org.apache.http.HttpResponse;
|
|
||||||
|
|
||||||
public interface ResponseHandler<T> {
|
|
||||||
T handleResponse(HttpResponse response);
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package org.apache.http.impl.client;
|
|
||||||
|
|
||||||
import org.apache.http.client.HttpClient;
|
|
||||||
|
|
||||||
public abstract class CloseableHttpClient implements HttpClient {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
// Generated automatically from org.apache.http.client.HttpClient for testing purposes
|
|
||||||
|
|
||||||
package org.apache.http.impl.client;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
|
||||||
|
|
||||||
public final class HttpClients {
|
|
||||||
public static CloseableHttpClient createDefault() { return null; }
|
|
||||||
}
|
|
||||||
@@ -63,7 +63,6 @@ ql/javascript/ql/src/experimental/Security/CWE-347/decodeJwtWithoutVerificationL
|
|||||||
ql/javascript/ql/src/experimental/Security/CWE-444/InsecureHttpParser.ql
|
ql/javascript/ql/src/experimental/Security/CWE-444/InsecureHttpParser.ql
|
||||||
ql/javascript/ql/src/experimental/Security/CWE-522-DecompressionBombs/DecompressionBombs.ql
|
ql/javascript/ql/src/experimental/Security/CWE-522-DecompressionBombs/DecompressionBombs.ql
|
||||||
ql/javascript/ql/src/experimental/Security/CWE-918/SSRF.ql
|
ql/javascript/ql/src/experimental/Security/CWE-918/SSRF.ql
|
||||||
ql/javascript/ql/src/experimental/Security/CWE-918/SsrfIpv6TransitionIncompleteGuard.ql
|
|
||||||
ql/javascript/ql/src/experimental/StandardLibrary/MultipleArgumentsToSetConstructor.ql
|
ql/javascript/ql/src/experimental/StandardLibrary/MultipleArgumentsToSetConstructor.ql
|
||||||
ql/javascript/ql/src/experimental/heuristics/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql
|
ql/javascript/ql/src/experimental/heuristics/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql
|
||||||
ql/javascript/ql/src/experimental/heuristics/ql/src/Security/CWE-078/CommandInjection.ql
|
ql/javascript/ql/src/experimental/heuristics/ql/src/Security/CWE-078/CommandInjection.ql
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
category: newQuery
|
|
||||||
---
|
|
||||||
* Added a new experimental query, `javascript/ssrf-ipv6-transition-incomplete-guard`, to detect SSRF host-validation guards that reject private IPv4 ranges but fail to unwrap IPv6-transition forms (IPv4-mapped `::ffff:`, NAT64 `64:ff9b::`, 6to4 `2002::`), allowing the guard to be bypassed by wrapping an internal IPv4 address in a transition literal.
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
<!DOCTYPE qhelp PUBLIC
|
|
||||||
"-//Semmle//qhelp//EN"
|
|
||||||
"qhelp.dtd">
|
|
||||||
<qhelp>
|
|
||||||
|
|
||||||
<overview>
|
|
||||||
<p>
|
|
||||||
Server-side request forgery (SSRF) guards frequently reject requests to internal
|
|
||||||
addresses by checking the request host against a denylist of private, loopback and
|
|
||||||
cloud-metadata IPv4 ranges. When such a guard inspects only the dotted-quad IPv4 form
|
|
||||||
and never unwraps IPv6-transition representations, it can be bypassed: the host
|
|
||||||
validator classifies the address as public, but the operating system routes the
|
|
||||||
connection to the embedded internal IPv4 endpoint.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
The affected forms include IPv4-mapped IPv6 (<code>::ffff:169.254.169.254</code>),
|
|
||||||
NAT64 (<code>64:ff9b::a9fe:a9fe</code>) and 6to4 (<code>2002::</code>). A URL such as
|
|
||||||
<code>http://[::ffff:169.254.169.254]/</code> passes a dotted-quad denylist unchanged
|
|
||||||
while still reaching the internal address.
|
|
||||||
</p>
|
|
||||||
</overview>
|
|
||||||
|
|
||||||
<recommendation>
|
|
||||||
<p>
|
|
||||||
Normalize the host before validating it: parse the address with a transition-aware
|
|
||||||
library and unwrap IPv4-mapped, NAT64 and 6to4 forms to their embedded IPv4 address,
|
|
||||||
then apply the private-range check to the normalized value. Libraries such as
|
|
||||||
<code>ipaddr.js</code> classify these forms correctly via their range API, and
|
|
||||||
SSRF-protection libraries such as <code>request-filtering-agent</code> apply the check
|
|
||||||
after DNS resolution. Validate the resolved address rather than the textual host.
|
|
||||||
</p>
|
|
||||||
</recommendation>
|
|
||||||
|
|
||||||
<example>
|
|
||||||
<p>
|
|
||||||
The following guard rejects private IPv4 ranges using the <code>private-ip</code>
|
|
||||||
package, which inspects the textual IPv4 form only. An attacker supplies
|
|
||||||
<code>::ffff:169.254.169.254</code>, which the guard classifies as public, but the
|
|
||||||
request still reaches the internal metadata endpoint.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<sample src="examples/SsrfIpv6TransitionIncompleteGuardBad.js"/>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The following guard parses the host with a transition-aware classifier, so the
|
|
||||||
embedded internal IPv4 address is detected regardless of the transition form used.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<sample src="examples/SsrfIpv6TransitionIncompleteGuardGood.js"/>
|
|
||||||
</example>
|
|
||||||
|
|
||||||
<references>
|
|
||||||
|
|
||||||
<li>OWASP: <a href="https://owasp.org/www-community/attacks/Server_Side_Request_Forgery">Server-Side Request Forgery</a>.</li>
|
|
||||||
<li>Common Weakness Enumeration: <a href="https://cwe.mitre.org/data/definitions/918.html">CWE-918</a>.</li>
|
|
||||||
<li>Common Weakness Enumeration: <a href="https://cwe.mitre.org/data/definitions/1389.html">CWE-1389</a>.</li>
|
|
||||||
|
|
||||||
</references>
|
|
||||||
</qhelp>
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
/**
|
|
||||||
* @name SSRF host guard does not reject IPv6-transition forms
|
|
||||||
* @description An SSRF host guard that rejects private or loopback IPv4 ranges but never
|
|
||||||
* unwraps IPv6-transition forms (IPv4-mapped `::ffff:`, NAT64 `64:ff9b::`,
|
|
||||||
* 6to4 `2002::`) can be bypassed by wrapping an internal IPv4 address in a
|
|
||||||
* transition literal, allowing requests to reach internal endpoints.
|
|
||||||
* @kind problem
|
|
||||||
* @problem.severity warning
|
|
||||||
* @id javascript/ssrf-ipv6-transition-incomplete-guard
|
|
||||||
* @tags security
|
|
||||||
* experimental
|
|
||||||
* external/cwe/cwe-918
|
|
||||||
* external/cwe/cwe-1389
|
|
||||||
*/
|
|
||||||
|
|
||||||
import javascript
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds if `f` imports a dotted-quad-oriented private-IP guard package whose
|
|
||||||
* classification is performed on the textual IPv4 form and therefore returns
|
|
||||||
* `false` for an internal address wrapped in an IPv6-transition literal.
|
|
||||||
*/
|
|
||||||
predicate importsHandRolledIpGuard(File f) {
|
|
||||||
exists(DataFlow::SourceNode mod |
|
|
||||||
mod.getFile() = f and
|
|
||||||
mod = DataFlow::moduleImport(["private-ip", "is-ip", "ip", "ip-range-check"])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds if `f` contains a call to an `isPrivate`-style host classifier, the
|
|
||||||
* common name for a hand-rolled SSRF guard.
|
|
||||||
*/
|
|
||||||
predicate hasIsPrivateCall(File f) {
|
|
||||||
exists(DataFlow::CallNode c |
|
|
||||||
c.getFile() = f and
|
|
||||||
c.getCalleeName().regexpMatch("(?i)^is_?private(ip|address|host)?$")
|
|
||||||
)
|
|
||||||
or
|
|
||||||
exists(DataFlow::MethodCallNode m |
|
|
||||||
m.getFile() = f and
|
|
||||||
m.getMethodName().regexpMatch("(?i)^is_?private(ip|address|host)?$")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds if `f` contains a hand-written RFC 1918, loopback or cloud-metadata IPv4
|
|
||||||
* literal used as a denylist entry.
|
|
||||||
*/
|
|
||||||
predicate hasRfc1918Literal(File f) {
|
|
||||||
exists(StringLiteral s |
|
|
||||||
s.getFile() = f and
|
|
||||||
s.getValue()
|
|
||||||
.regexpMatch("(?i).*(127\\.0\\.0\\.1|169\\.254\\.169\\.254|10\\.|192\\.168|172\\.1[6-9]|::1|fc00|fd00|metadata\\.google).*")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Holds if `f` carries any hand-rolled, dotted-quad-oriented SSRF guard signal. */
|
|
||||||
predicate hasUnsafeGuardSignal(File f) {
|
|
||||||
importsHandRolledIpGuard(f) or
|
|
||||||
hasIsPrivateCall(f) or
|
|
||||||
hasRfc1918Literal(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Holds if `func` has a name that reads as an SSRF host or URL validator. */
|
|
||||||
predicate isSsrfValidatorFunction(Function func) {
|
|
||||||
func.getName()
|
|
||||||
.regexpMatch("(?i).*(validate|check|guard|reject|deny|block|allow|is_?safe|sanitiz)e?_?.*(url|host|ip|address|target|endpoint|webhook|origin).*")
|
|
||||||
or
|
|
||||||
func.getName()
|
|
||||||
.regexpMatch("(?i).*(is_?)?(private|internal|loopback|reserved|external)_?(ip|address|host|url).*")
|
|
||||||
or
|
|
||||||
func.getName().regexpMatch("(?i).*(ssrf|metadata).*")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds if `f` imports a maturity-hardened, transition-aware address classifier
|
|
||||||
* or SSRF-protection library that does unwrap IPv6-transition forms.
|
|
||||||
*/
|
|
||||||
predicate importsSafeClassifier(File f) {
|
|
||||||
exists(DataFlow::SourceNode mod |
|
|
||||||
mod.getFile() = f and
|
|
||||||
mod =
|
|
||||||
DataFlow::moduleImport([
|
|
||||||
"ipaddr.js", "ssrf-req-filter", "request-filtering-agent", "ssrf-agent", "netmask",
|
|
||||||
"ip-cidr", "cidr-matcher", "blocked-at"
|
|
||||||
])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds if `f` already performs an explicit IPv6-transition unwrap or
|
|
||||||
* canonicalization, so the guard does see the embedded IPv4 address.
|
|
||||||
*/
|
|
||||||
predicate hasTransitionUnwrap(File f) {
|
|
||||||
exists(StringLiteral s |
|
|
||||||
s.getFile() = f and
|
|
||||||
(
|
|
||||||
s.getValue().matches("%64:ff9b%") or
|
|
||||||
s.getValue().matches("%::ffff%") or
|
|
||||||
s.getValue().matches("%2002:%") or
|
|
||||||
s.getValue().matches("%2001:%")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
or
|
|
||||||
exists(Identifier id |
|
|
||||||
id.getFile() = f and
|
|
||||||
id.getName()
|
|
||||||
.regexpMatch("(?i).*(ipv4mapped|v4mapped|mappedipv4|ipv4inipv6|embeddedipv4|unwrap.*ip|toipv4|canonicaliz|isipv4compat).*")
|
|
||||||
)
|
|
||||||
or
|
|
||||||
exists(DataFlow::MethodCallNode m | m.getFile() = f and m.getMethodName() = ["range", "kind"])
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Holds if `f` is treated as safe (transition-aware), suppressing the alert. */
|
|
||||||
predicate isSafe(File f) { importsSafeClassifier(f) or hasTransitionUnwrap(f) }
|
|
||||||
|
|
||||||
from Function guard, File f
|
|
||||||
where
|
|
||||||
guard.getFile() = f and
|
|
||||||
isSsrfValidatorFunction(guard) and
|
|
||||||
hasUnsafeGuardSignal(f) and
|
|
||||||
not isSafe(f) and
|
|
||||||
not f.getRelativePath()
|
|
||||||
.regexpMatch("(?i).*/(tests?|specs?|examples?|__tests__|e2e|node_modules)/.*")
|
|
||||||
select guard,
|
|
||||||
"This SSRF host guard rejects private IPv4 ranges but never unwraps IPv6-transition forms " +
|
|
||||||
"(IPv4-mapped '::ffff:', NAT64 '64:ff9b::', 6to4 '2002::'); an attacker can wrap an internal " +
|
|
||||||
"IPv4 address in a transition literal to bypass it and reach internal endpoints."
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
const isPrivate = require('private-ip');
|
|
||||||
const fetch = require('node-fetch');
|
|
||||||
|
|
||||||
// BAD: `private-ip` classifies the textual IPv4 form only, so it returns false
|
|
||||||
// for `::ffff:169.254.169.254`. The guard treats the wrapped internal address as
|
|
||||||
// public, but the request still reaches the metadata endpoint.
|
|
||||||
async function validateUrlHost(host) {
|
|
||||||
if (isPrivate(host)) {
|
|
||||||
throw new Error('blocked private host');
|
|
||||||
}
|
|
||||||
return fetch('http://' + host + '/');
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { validateUrlHost };
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
const ipaddr = require('ipaddr.js');
|
|
||||||
const fetch = require('node-fetch');
|
|
||||||
|
|
||||||
// GOOD: ipaddr.js parses the host and classifies it with `.range()`, which is
|
|
||||||
// transition-aware. `::ffff:169.254.169.254` parses as an IPv4-mapped address and
|
|
||||||
// is reported in the `linkLocal` range, so the guard is complete.
|
|
||||||
async function validateTargetHost(host) {
|
|
||||||
const addr = ipaddr.parse(host);
|
|
||||||
const range = addr.range();
|
|
||||||
if (range === 'private' || range === 'loopback' || range === 'linkLocal') {
|
|
||||||
throw new Error('blocked internal host');
|
|
||||||
}
|
|
||||||
return fetch('http://' + host + '/');
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { validateTargetHost };
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
| bad-private-ip-pkg.js:6:1:11:1 | async f ... '/');\\n} | This SSRF host guard rejects private IPv4 ranges but never unwraps IPv6-transition forms (IPv4-mapped '::ffff:', NAT64 '64:ff9b::', 6to4 '2002::'); an attacker can wrap an internal IPv4 address in a transition literal to bypass it and reach internal endpoints. |
|
|
||||||
| bad-rfc1918-regex.js:5:1:16:1 | functio ... '/');\\n} | This SSRF host guard rejects private IPv4 ranges but never unwraps IPv6-transition forms (IPv4-mapped '::ffff:', NAT64 '64:ff9b::', 6to4 '2002::'); an attacker can wrap an internal IPv4 address in a transition literal to bypass it and reach internal endpoints. |
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
experimental/Security/CWE-918/SsrfIpv6TransitionIncompleteGuard.ql
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
const isPrivate = require('private-ip');
|
|
||||||
const fetch = require('node-fetch');
|
|
||||||
|
|
||||||
// BAD: `private-ip` classifies the textual IPv4 form only. It returns false for
|
|
||||||
// `::ffff:169.254.169.254`, so a transition-wrapped internal address slips past.
|
|
||||||
async function validateUrlHost(host) { // NOT OK
|
|
||||||
if (isPrivate(host)) {
|
|
||||||
throw new Error('blocked private host');
|
|
||||||
}
|
|
||||||
return fetch('http://' + host + '/');
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { validateUrlHost };
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
const http = require('http');
|
|
||||||
|
|
||||||
// BAD: a hand-written RFC 1918 / loopback / metadata denylist matched against the
|
|
||||||
// host string. The embedded IPv4 inside `::ffff:10.0.0.1` is never seen.
|
|
||||||
function checkTargetHost(host) { // NOT OK
|
|
||||||
if (
|
|
||||||
host === '127.0.0.1' ||
|
|
||||||
host === '169.254.169.254' ||
|
|
||||||
host.startsWith('10.') ||
|
|
||||||
host.startsWith('192.168') ||
|
|
||||||
host.startsWith('172.16')
|
|
||||||
) {
|
|
||||||
throw new Error('blocked internal host');
|
|
||||||
}
|
|
||||||
return http.get('http://' + host + '/');
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { checkTargetHost };
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
const http = require('http');
|
|
||||||
|
|
||||||
const IPV4_MAPPED_PREFIX = '::ffff:';
|
|
||||||
|
|
||||||
// OK: this guard uses a hand-rolled denylist, but it first unwraps the
|
|
||||||
// IPv6-transition form, so the embedded IPv4 is normalized before the check.
|
|
||||||
function unwrapMapped(host) {
|
|
||||||
// strip an IPv4-mapped `::ffff:` prefix down to the embedded dotted quad
|
|
||||||
if (host.toLowerCase().startsWith(IPV4_MAPPED_PREFIX)) {
|
|
||||||
return host.slice(IPV4_MAPPED_PREFIX.length);
|
|
||||||
}
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPrivateAddress(host) { // OK
|
|
||||||
const h = unwrapMapped(host);
|
|
||||||
return (
|
|
||||||
h === '127.0.0.1' ||
|
|
||||||
h === '169.254.169.254' ||
|
|
||||||
h.startsWith('10.') ||
|
|
||||||
h.startsWith('192.168')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateHost(host) { // OK
|
|
||||||
if (isPrivateAddress(host)) {
|
|
||||||
throw new Error('blocked internal host');
|
|
||||||
}
|
|
||||||
return http.get('http://' + host + '/');
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { validateHost };
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
const ipaddr = require('ipaddr.js');
|
|
||||||
const fetch = require('node-fetch');
|
|
||||||
|
|
||||||
// OK: ipaddr.js parses the address and classifies it with `.range()`, which is
|
|
||||||
// transition-aware. `::ffff:10.0.0.1` parses as an IPv4-mapped address and is
|
|
||||||
// reported in the `private` range, so the guard is complete.
|
|
||||||
async function validateTargetHost(host) { // OK
|
|
||||||
const addr = ipaddr.parse(host);
|
|
||||||
const range = addr.range();
|
|
||||||
if (range === 'private' || range === 'loopback' || range === 'linkLocal') {
|
|
||||||
throw new Error('blocked internal host');
|
|
||||||
}
|
|
||||||
return fetch('http://' + host + '/');
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { validateTargetHost };
|
|
||||||
@@ -21,19 +21,13 @@ file_coverage_languages:
|
|||||||
scc_languages:
|
scc_languages:
|
||||||
- TypeScript
|
- TypeScript
|
||||||
- TypeScript Typings
|
- TypeScript Typings
|
||||||
- name: vue
|
|
||||||
display_name: Vue.js component
|
|
||||||
scc_languages:
|
|
||||||
- Vue
|
|
||||||
github_api_languages:
|
github_api_languages:
|
||||||
- JavaScript
|
- JavaScript
|
||||||
- TypeScript
|
- TypeScript
|
||||||
- Vue
|
|
||||||
scc_languages:
|
scc_languages:
|
||||||
- JavaScript
|
- JavaScript
|
||||||
- TypeScript
|
- TypeScript
|
||||||
- TypeScript Typings
|
- TypeScript Typings
|
||||||
- Vue
|
|
||||||
file_types:
|
file_types:
|
||||||
- name: javascript
|
- name: javascript
|
||||||
display_name: JavaScript
|
display_name: JavaScript
|
||||||
|
|||||||
2
python/ql/consistency-queries/CfgConsistency.ql
Normal file
2
python/ql/consistency-queries/CfgConsistency.ql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import semmle.python.controlflow.internal.AstNodeImpl
|
||||||
|
import ControlFlow::Consistency
|
||||||
@@ -36,8 +36,6 @@ private module Input implements InputSig<Location, PythonDataFlow> {
|
|||||||
// parameter, but dataflow-consistency queries should _not_ complain about there not
|
// parameter, but dataflow-consistency queries should _not_ complain about there not
|
||||||
// being a post-update node for the synthetic `**kwargs` parameter.
|
// being a post-update node for the synthetic `**kwargs` parameter.
|
||||||
n instanceof SynthDictSplatParameterNode
|
n instanceof SynthDictSplatParameterNode
|
||||||
or
|
|
||||||
Private::Conversions::readStep(n, _, _)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
predicate uniqueParameterNodePositionExclude(DataFlowCallable c, ParameterPosition pos, Node p) {
|
predicate uniqueParameterNodePositionExclude(DataFlowCallable c, ParameterPosition pos, Node p) {
|
||||||
|
|||||||
@@ -213,9 +213,11 @@ class ExprWithPointsTo extends Expr {
|
|||||||
* Gets what this expression might "refer-to" in the given `context`.
|
* Gets what this expression might "refer-to" in the given `context`.
|
||||||
*/
|
*/
|
||||||
predicate refersTo(Context context, Object obj, ClassObject cls, AstNode origin) {
|
predicate refersTo(Context context, Object obj, ClassObject cls, AstNode origin) {
|
||||||
this.getAFlowNode()
|
exists(ControlFlowNode this_, ControlFlowNode origin_ |
|
||||||
.(ControlFlowNodeWithPointsTo)
|
this_.getNode() = this and origin_.getNode() = origin
|
||||||
.refersTo(context, obj, cls, origin.getAFlowNode())
|
|
|
||||||
|
this_.(ControlFlowNodeWithPointsTo).refersTo(context, obj, cls, origin_)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -226,7 +228,11 @@ class ExprWithPointsTo extends Expr {
|
|||||||
*/
|
*/
|
||||||
pragma[nomagic]
|
pragma[nomagic]
|
||||||
predicate refersTo(Object obj, AstNode origin) {
|
predicate refersTo(Object obj, AstNode origin) {
|
||||||
this.getAFlowNode().(ControlFlowNodeWithPointsTo).refersTo(obj, origin.getAFlowNode())
|
exists(ControlFlowNode this_, ControlFlowNode origin_ |
|
||||||
|
this_.getNode() = this and origin_.getNode() = origin
|
||||||
|
|
|
||||||
|
this_.(ControlFlowNodeWithPointsTo).refersTo(obj, origin_)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -240,16 +246,22 @@ class ExprWithPointsTo extends Expr {
|
|||||||
* in the given `context`.
|
* in the given `context`.
|
||||||
*/
|
*/
|
||||||
predicate pointsTo(Context context, Value value, AstNode origin) {
|
predicate pointsTo(Context context, Value value, AstNode origin) {
|
||||||
this.getAFlowNode()
|
exists(ControlFlowNode this_, ControlFlowNode origin_ |
|
||||||
.(ControlFlowNodeWithPointsTo)
|
this_.getNode() = this and origin_.getNode() = origin
|
||||||
.pointsTo(context, value, origin.getAFlowNode())
|
|
|
||||||
|
this_.(ControlFlowNodeWithPointsTo).pointsTo(context, value, origin_)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds if this expression might "point-to" to `value` which is from `origin`.
|
* Holds if this expression might "point-to" to `value` which is from `origin`.
|
||||||
*/
|
*/
|
||||||
predicate pointsTo(Value value, AstNode origin) {
|
predicate pointsTo(Value value, AstNode origin) {
|
||||||
this.getAFlowNode().(ControlFlowNodeWithPointsTo).pointsTo(value, origin.getAFlowNode())
|
exists(ControlFlowNode this_, ControlFlowNode origin_ |
|
||||||
|
this_.getNode() = this and origin_.getNode() = origin
|
||||||
|
|
|
||||||
|
this_.(ControlFlowNodeWithPointsTo).pointsTo(value, origin_)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -475,7 +487,10 @@ class FunctionMetricsWithPointsTo extends FunctionMetrics {
|
|||||||
not non_coupling_method(result) and
|
not non_coupling_method(result) and
|
||||||
exists(Call call | call.getScope() = this |
|
exists(Call call | call.getScope() = this |
|
||||||
exists(FunctionObject callee | callee.getFunction() = result |
|
exists(FunctionObject callee | callee.getFunction() = result |
|
||||||
call.getAFlowNode().getFunction().(ControlFlowNodeWithPointsTo).refersTo(callee)
|
exists(CallNode call_ |
|
||||||
|
call_.getNode() = call and
|
||||||
|
call_.getFunction().(ControlFlowNodeWithPointsTo).refersTo(callee)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
or
|
or
|
||||||
exists(Attribute a | call.getFunc() = a |
|
exists(Attribute a | call.getFunc() = a |
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ private predicate jump_to_defn(ControlFlowNode use, Definition defn) {
|
|||||||
private predicate preferred_jump_to_defn(Expr use, Definition def) {
|
private predicate preferred_jump_to_defn(Expr use, Definition def) {
|
||||||
not use instanceof ClassExpr and
|
not use instanceof ClassExpr and
|
||||||
not use instanceof FunctionExpr and
|
not use instanceof FunctionExpr and
|
||||||
jump_to_defn(use.getAFlowNode(), def)
|
exists(ControlFlowNode useNode | useNode.getNode() = use | jump_to_defn(useNode, def))
|
||||||
}
|
}
|
||||||
|
|
||||||
private predicate unique_jump_to_defn(Expr use, Definition def) {
|
private predicate unique_jump_to_defn(Expr use, Definition def) {
|
||||||
@@ -452,7 +452,7 @@ private predicate self_parameter_jump_to_defn_attribute(
|
|||||||
* This exists primarily for testing use `getPreferredDefinition()` instead.
|
* This exists primarily for testing use `getPreferredDefinition()` instead.
|
||||||
*/
|
*/
|
||||||
Definition getADefinition(Expr use) {
|
Definition getADefinition(Expr use) {
|
||||||
jump_to_defn(use.getAFlowNode(), result) and
|
exists(ControlFlowNode useNode | useNode.getNode() = use | jump_to_defn(useNode, result)) and
|
||||||
not use instanceof Call and
|
not use instanceof Call and
|
||||||
not use.isArtificial() and
|
not use.isArtificial() and
|
||||||
// Not the use itself
|
// Not the use itself
|
||||||
|
|||||||
4
python/ql/lib/change-notes/2026-05-19-add-shared-cfg.md
Normal file
4
python/ql/lib/change-notes/2026-05-19-add-shared-cfg.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
category: minorAnalysis
|
||||||
|
---
|
||||||
|
* A new Python control flow graph implementation has been added under `semmle.python.controlflow.internal.Cfg` (backed by `AstNodeImpl.qll`), built on the shared `codeql.controlflow.ControlFlowGraph` library. It is not yet used by the dataflow library or any production query; the legacy CFG in `semmle/python/Flow.qll` remains the default. The new library is exposed for tests and for upcoming migrations.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
category: deprecated
|
||||||
|
---
|
||||||
|
* The `AstNode.getAFlowNode()` predicate has been deprecated. Use `ControlFlowNode.getNode()` from the other direction instead: replace `e.getAFlowNode() = n` with `n.getNode() = e`. This is a preparatory step towards migrating the dataflow library off the legacy CFG; it has no semantic effect.
|
||||||
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
category: minorAnalysis
|
|
||||||
---
|
|
||||||
* Python taint tracking is now more precise for values flowing through container contents, such as list, set, tuple, and dictionary elements. This may remove some false positive alerts.
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
category: deprecated
|
||||||
|
---
|
||||||
|
* The `Function.getAReturnValueFlowNode()` predicate has been deprecated. Bind a `Return` node explicitly instead — `exists(Return ret | ret.getScope() = f and n.getNode() = ret.getValue())`. This is a preparatory step towards migrating the dataflow library off the legacy CFG; it has no semantic effect.
|
||||||
45
python/ql/lib/printCfgNew.ql
Normal file
45
python/ql/lib/printCfgNew.ql
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* @name Print CFG (New)
|
||||||
|
* @description Produces a representation of a file's Control Flow Graph
|
||||||
|
* using the new shared control flow library.
|
||||||
|
* This query is used by the VS Code extension.
|
||||||
|
* @id python/print-cfg
|
||||||
|
* @kind graph
|
||||||
|
* @tags ide-contextual-queries/print-cfg
|
||||||
|
*/
|
||||||
|
|
||||||
|
private import python as Py
|
||||||
|
import semmle.python.controlflow.internal.AstNodeImpl
|
||||||
|
|
||||||
|
external string selectedSourceFile();
|
||||||
|
|
||||||
|
private predicate selectedSourceFileAlias = selectedSourceFile/0;
|
||||||
|
|
||||||
|
external int selectedSourceLine();
|
||||||
|
|
||||||
|
private predicate selectedSourceLineAlias = selectedSourceLine/0;
|
||||||
|
|
||||||
|
external int selectedSourceColumn();
|
||||||
|
|
||||||
|
private predicate selectedSourceColumnAlias = selectedSourceColumn/0;
|
||||||
|
|
||||||
|
module ViewCfgQueryInput implements ControlFlow::ViewCfgQueryInputSig<Py::File> {
|
||||||
|
predicate selectedSourceFile = selectedSourceFileAlias/0;
|
||||||
|
|
||||||
|
predicate selectedSourceLine = selectedSourceLineAlias/0;
|
||||||
|
|
||||||
|
predicate selectedSourceColumn = selectedSourceColumnAlias/0;
|
||||||
|
|
||||||
|
predicate cfgScopeSpan(
|
||||||
|
Ast::Callable callable, Py::File file, int startLine, int startColumn, int endLine,
|
||||||
|
int endColumn
|
||||||
|
) {
|
||||||
|
exists(Py::Scope scope |
|
||||||
|
scope = callable.asScope() and
|
||||||
|
file = scope.getLocation().getFile() and
|
||||||
|
scope.getLocation().hasLocationInfo(_, startLine, startColumn, endLine, endColumn)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
import ControlFlow::ViewCfgQuery<Py::File, ViewCfgQueryInput>
|
||||||
@@ -16,21 +16,26 @@ abstract class AstNode extends AstNode_ {
|
|||||||
/** Gets the scope that this node occurs in */
|
/** Gets the scope that this node occurs in */
|
||||||
abstract Scope getScope();
|
abstract Scope getScope();
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a flow node corresponding directly to this node.
|
|
||||||
* NOTE: For some statements and other purely syntactic elements,
|
|
||||||
* there may not be a `ControlFlowNode`
|
|
||||||
*/
|
|
||||||
cached
|
|
||||||
ControlFlowNode getAFlowNode() {
|
|
||||||
Stages::AST::ref() and
|
|
||||||
py_flow_bb_node(result, this, _, _)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Gets the location for this AST node */
|
/** Gets the location for this AST node */
|
||||||
cached
|
cached
|
||||||
Location getLocation() { none() }
|
Location getLocation() { none() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DEPRECATED: use `ControlFlowNode.getNode()` from the other direction instead;
|
||||||
|
* that is, replace `e.getAFlowNode() = n` with `n.getNode() = e`. This API is
|
||||||
|
* being removed to untangle the AST and CFG hierarchies in preparation for
|
||||||
|
* migrating the dataflow library off the legacy CFG.
|
||||||
|
*
|
||||||
|
* Gets a flow node corresponding directly to this node.
|
||||||
|
* NOTE: For some statements and other purely syntactic elements,
|
||||||
|
* there may not be a `ControlFlowNode`.
|
||||||
|
*/
|
||||||
|
cached
|
||||||
|
deprecated ControlFlowNode getAFlowNode() {
|
||||||
|
Stages::AST::ref() and
|
||||||
|
py_flow_bb_node(result, this, _, _)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether this syntactic element is artificial, that is it is generated
|
* Whether this syntactic element is artificial, that is it is generated
|
||||||
* by the compiler and is not present in the source
|
* by the compiler and is not present in the source
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ class Expr extends Expr_, AstNode {
|
|||||||
/** Whether this expression may have a side effect (as determined purely from its syntax) */
|
/** Whether this expression may have a side effect (as determined purely from its syntax) */
|
||||||
predicate hasSideEffects() {
|
predicate hasSideEffects() {
|
||||||
/* If an exception raised by this expression handled, count that as a side effect */
|
/* If an exception raised by this expression handled, count that as a side effect */
|
||||||
this.getAFlowNode().getASuccessor().getNode() instanceof ExceptStmt
|
exists(ControlFlowNode n | n.getNode() = this |
|
||||||
|
n.getASuccessor().getNode() instanceof ExceptStmt
|
||||||
|
)
|
||||||
or
|
or
|
||||||
this.getASubExpression().hasSideEffects()
|
this.getASubExpression().hasSideEffects()
|
||||||
}
|
}
|
||||||
@@ -68,8 +70,6 @@ class Attribute extends Attribute_ {
|
|||||||
/* syntax: Expr.name */
|
/* syntax: Expr.name */
|
||||||
override Expr getASubExpression() { result = this.getObject() }
|
override Expr getASubExpression() { result = this.getObject() }
|
||||||
|
|
||||||
override AttrNode getAFlowNode() { result = super.getAFlowNode() }
|
|
||||||
|
|
||||||
/** Gets the name of this attribute. That is the `name` in `obj.name` */
|
/** Gets the name of this attribute. That is the `name` in `obj.name` */
|
||||||
string getName() { result = Attribute_.super.getAttr() }
|
string getName() { result = Attribute_.super.getAttr() }
|
||||||
|
|
||||||
@@ -96,8 +96,6 @@ class Subscript extends Subscript_ {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Expr getObject() { result = Subscript_.super.getValue() }
|
Expr getObject() { result = Subscript_.super.getValue() }
|
||||||
|
|
||||||
override SubscriptNode getAFlowNode() { result = super.getAFlowNode() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A call expression, such as `func(...)` */
|
/** A call expression, such as `func(...)` */
|
||||||
@@ -113,8 +111,6 @@ class Call extends Call_ {
|
|||||||
|
|
||||||
override string toString() { result = this.getFunc().toString() + "()" }
|
override string toString() { result = this.getFunc().toString() + "()" }
|
||||||
|
|
||||||
override CallNode getAFlowNode() { result = super.getAFlowNode() }
|
|
||||||
|
|
||||||
/** Gets a tuple (*) argument of this call. */
|
/** Gets a tuple (*) argument of this call. */
|
||||||
Expr getStarargs() { result = this.getAPositionalArg().(Starred).getValue() }
|
Expr getStarargs() { result = this.getAPositionalArg().(Starred).getValue() }
|
||||||
|
|
||||||
@@ -200,8 +196,6 @@ class IfExp extends IfExp_ {
|
|||||||
override Expr getASubExpression() {
|
override Expr getASubExpression() {
|
||||||
result = this.getTest() or result = this.getBody() or result = this.getOrelse()
|
result = this.getTest() or result = this.getBody() or result = this.getOrelse()
|
||||||
}
|
}
|
||||||
|
|
||||||
override IfExprNode getAFlowNode() { result = super.getAFlowNode() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A starred expression, such as the `*rest` in the assignment `first, *rest = seq` */
|
/** A starred expression, such as the `*rest` in the assignment `first, *rest = seq` */
|
||||||
@@ -410,8 +404,6 @@ class PlaceHolder extends PlaceHolder_ {
|
|||||||
override Expr getASubExpression() { none() }
|
override Expr getASubExpression() { none() }
|
||||||
|
|
||||||
override string toString() { result = "$" + this.getId() }
|
override string toString() { result = "$" + this.getId() }
|
||||||
|
|
||||||
override NameNode getAFlowNode() { result = super.getAFlowNode() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A tuple expression such as `( 1, 3, 5, 7, 9 )` */
|
/** A tuple expression such as `( 1, 3, 5, 7, 9 )` */
|
||||||
@@ -478,8 +470,6 @@ class Name extends Name_ {
|
|||||||
|
|
||||||
override string toString() { result = this.getId() }
|
override string toString() { result = this.getId() }
|
||||||
|
|
||||||
override NameNode getAFlowNode() { result = super.getAFlowNode() }
|
|
||||||
|
|
||||||
override predicate isArtificial() {
|
override predicate isArtificial() {
|
||||||
/* Artificial variable names in comprehensions all start with "." */
|
/* Artificial variable names in comprehensions all start with "." */
|
||||||
this.getId().charAt(0) = "."
|
this.getId().charAt(0) = "."
|
||||||
@@ -585,8 +575,6 @@ abstract class NameConstant extends Name, ImmutableLiteral {
|
|||||||
|
|
||||||
override predicate isConstant() { any() }
|
override predicate isConstant() { any() }
|
||||||
|
|
||||||
override NameConstantNode getAFlowNode() { result = Name.super.getAFlowNode() }
|
|
||||||
|
|
||||||
override predicate isArtificial() { none() }
|
override predicate isArtificial() { none() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
overlay[local]
|
overlay[local]
|
||||||
module;
|
module;
|
||||||
|
|
||||||
import python
|
import python as Py
|
||||||
private import semmle.python.internal.CachedStages
|
private import semmle.python.internal.CachedStages
|
||||||
private import codeql.controlflow.BasicBlock as BB
|
private import codeql.controlflow.BasicBlock as BB
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ private import codeql.controlflow.BasicBlock as BB
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
private predicate augstore(ControlFlowNode load, ControlFlowNode store) {
|
private predicate augstore(ControlFlowNode load, ControlFlowNode store) {
|
||||||
exists(Expr load_store | exists(AugAssign aa | aa.getTarget() = load_store) |
|
exists(Py::Expr load_store | exists(Py::AugAssign aa | aa.getTarget() = load_store) |
|
||||||
toAst(load) = load_store and
|
toAst(load) = load_store and
|
||||||
toAst(store) = load_store and
|
toAst(store) = load_store and
|
||||||
load.strictlyDominates(store)
|
load.strictlyDominates(store)
|
||||||
@@ -25,7 +25,7 @@ private predicate augstore(ControlFlowNode load, ControlFlowNode store) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** A non-dispatched getNode() to avoid negative recursion issues */
|
/** A non-dispatched getNode() to avoid negative recursion issues */
|
||||||
private AstNode toAst(ControlFlowNode n) { py_flow_bb_node(n, result, _, _) }
|
private Py::AstNode toAst(ControlFlowNode n) { py_flow_bb_node(n, result, _, _) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A control flow node. Control flow nodes have a many-to-one relation with syntactic nodes,
|
* A control flow node. Control flow nodes have a many-to-one relation with syntactic nodes,
|
||||||
@@ -35,19 +35,19 @@ private AstNode toAst(ControlFlowNode n) { py_flow_bb_node(n, result, _, _) }
|
|||||||
class ControlFlowNode extends @py_flow_node {
|
class ControlFlowNode extends @py_flow_node {
|
||||||
/** Whether this control flow node is a load (including those in augmented assignments) */
|
/** Whether this control flow node is a load (including those in augmented assignments) */
|
||||||
predicate isLoad() {
|
predicate isLoad() {
|
||||||
exists(Expr e | e = toAst(this) | py_expr_contexts(_, 3, e) and not augstore(_, this))
|
exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 3, e) and not augstore(_, this))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Whether this control flow node is a store (including those in augmented assignments) */
|
/** Whether this control flow node is a store (including those in augmented assignments) */
|
||||||
predicate isStore() {
|
predicate isStore() {
|
||||||
exists(Expr e | e = toAst(this) | py_expr_contexts(_, 5, e) or augstore(_, this))
|
exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 5, e) or augstore(_, this))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Whether this control flow node is a delete */
|
/** Whether this control flow node is a delete */
|
||||||
predicate isDelete() { exists(Expr e | e = toAst(this) | py_expr_contexts(_, 2, e)) }
|
predicate isDelete() { exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 2, e)) }
|
||||||
|
|
||||||
/** Whether this control flow node is a parameter */
|
/** Whether this control flow node is a parameter */
|
||||||
predicate isParameter() { exists(Expr e | e = toAst(this) | py_expr_contexts(_, 4, e)) }
|
predicate isParameter() { exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 4, e)) }
|
||||||
|
|
||||||
/** Whether this control flow node is a store in an augmented assignment */
|
/** Whether this control flow node is a store in an augmented assignment */
|
||||||
predicate isAugStore() { augstore(_, this) }
|
predicate isAugStore() { augstore(_, this) }
|
||||||
@@ -57,61 +57,61 @@ class ControlFlowNode extends @py_flow_node {
|
|||||||
|
|
||||||
/** Whether this flow node corresponds to a literal */
|
/** Whether this flow node corresponds to a literal */
|
||||||
predicate isLiteral() {
|
predicate isLiteral() {
|
||||||
toAst(this) instanceof Bytes
|
toAst(this) instanceof Py::Bytes
|
||||||
or
|
or
|
||||||
toAst(this) instanceof Dict
|
toAst(this) instanceof Py::Dict
|
||||||
or
|
or
|
||||||
toAst(this) instanceof DictComp
|
toAst(this) instanceof Py::DictComp
|
||||||
or
|
or
|
||||||
toAst(this) instanceof Set
|
toAst(this) instanceof Py::Set
|
||||||
or
|
or
|
||||||
toAst(this) instanceof SetComp
|
toAst(this) instanceof Py::SetComp
|
||||||
or
|
or
|
||||||
toAst(this) instanceof Ellipsis
|
toAst(this) instanceof Py::Ellipsis
|
||||||
or
|
or
|
||||||
toAst(this) instanceof GeneratorExp
|
toAst(this) instanceof Py::GeneratorExp
|
||||||
or
|
or
|
||||||
toAst(this) instanceof Lambda
|
toAst(this) instanceof Py::Lambda
|
||||||
or
|
or
|
||||||
toAst(this) instanceof ListComp
|
toAst(this) instanceof Py::ListComp
|
||||||
or
|
or
|
||||||
toAst(this) instanceof List
|
toAst(this) instanceof Py::List
|
||||||
or
|
or
|
||||||
toAst(this) instanceof Num
|
toAst(this) instanceof Py::Num
|
||||||
or
|
or
|
||||||
toAst(this) instanceof Tuple
|
toAst(this) instanceof Py::Tuple
|
||||||
or
|
or
|
||||||
toAst(this) instanceof Unicode
|
toAst(this) instanceof Py::Unicode
|
||||||
or
|
or
|
||||||
toAst(this) instanceof NameConstant
|
toAst(this) instanceof Py::NameConstant
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Whether this flow node corresponds to an attribute expression */
|
/** Whether this flow node corresponds to an attribute expression */
|
||||||
predicate isAttribute() { toAst(this) instanceof Attribute }
|
predicate isAttribute() { toAst(this) instanceof Py::Attribute }
|
||||||
|
|
||||||
/** Whether this flow node corresponds to an subscript expression */
|
/** Whether this flow node corresponds to an subscript expression */
|
||||||
predicate isSubscript() { toAst(this) instanceof Subscript }
|
predicate isSubscript() { toAst(this) instanceof Py::Subscript }
|
||||||
|
|
||||||
/** Whether this flow node corresponds to an import member */
|
/** Whether this flow node corresponds to an import member */
|
||||||
predicate isImportMember() { toAst(this) instanceof ImportMember }
|
predicate isImportMember() { toAst(this) instanceof Py::ImportMember }
|
||||||
|
|
||||||
/** Whether this flow node corresponds to a call */
|
/** Whether this flow node corresponds to a call */
|
||||||
predicate isCall() { toAst(this) instanceof Call }
|
predicate isCall() { toAst(this) instanceof Py::Call }
|
||||||
|
|
||||||
/** Whether this flow node is the first in a module */
|
/** Whether this flow node is the first in a module */
|
||||||
predicate isModuleEntry() { this.isEntryNode() and toAst(this) instanceof Module }
|
predicate isModuleEntry() { this.isEntryNode() and toAst(this) instanceof Py::Module }
|
||||||
|
|
||||||
/** Whether this flow node corresponds to an import */
|
/** Whether this flow node corresponds to an import */
|
||||||
predicate isImport() { toAst(this) instanceof ImportExpr }
|
predicate isImport() { toAst(this) instanceof Py::ImportExpr }
|
||||||
|
|
||||||
/** Whether this flow node corresponds to a conditional expression */
|
/** Whether this flow node corresponds to a conditional expression */
|
||||||
predicate isIfExp() { toAst(this) instanceof IfExp }
|
predicate isIfExp() { toAst(this) instanceof Py::IfExp }
|
||||||
|
|
||||||
/** Whether this flow node corresponds to a function definition expression */
|
/** Whether this flow node corresponds to a function definition expression */
|
||||||
predicate isFunction() { toAst(this) instanceof FunctionExpr }
|
predicate isFunction() { toAst(this) instanceof Py::FunctionExpr }
|
||||||
|
|
||||||
/** Whether this flow node corresponds to a class definition expression */
|
/** Whether this flow node corresponds to a class definition expression */
|
||||||
predicate isClass() { toAst(this) instanceof ClassExpr }
|
predicate isClass() { toAst(this) instanceof Py::ClassExpr }
|
||||||
|
|
||||||
/** Gets a predecessor of this flow node */
|
/** Gets a predecessor of this flow node */
|
||||||
ControlFlowNode getAPredecessor() { this = result.getASuccessor() }
|
ControlFlowNode getAPredecessor() { this = result.getASuccessor() }
|
||||||
@@ -123,25 +123,25 @@ class ControlFlowNode extends @py_flow_node {
|
|||||||
ControlFlowNode getImmediateDominator() { py_idoms(this, result) }
|
ControlFlowNode getImmediateDominator() { py_idoms(this, result) }
|
||||||
|
|
||||||
/** Gets the syntactic element corresponding to this flow node */
|
/** Gets the syntactic element corresponding to this flow node */
|
||||||
AstNode getNode() { py_flow_bb_node(this, result, _, _) }
|
Py::AstNode getNode() { py_flow_bb_node(this, result, _, _) }
|
||||||
|
|
||||||
/** Gets a textual representation of this element. */
|
/** Gets a textual representation of this element. */
|
||||||
cached
|
cached
|
||||||
string toString() {
|
string toString() {
|
||||||
Stages::AST::ref() and
|
Stages::AST::ref() and
|
||||||
// Since modules can have ambigous names, entry nodes can too, if we do not collate them.
|
// Since modules can have ambigous names, entry nodes can too, if we do not collate them.
|
||||||
exists(Scope s | s.getEntryNode() = this |
|
exists(Py::Scope s | s.getEntryNode() = this |
|
||||||
result = "Entry node for " + concat( | | s.toString(), ",")
|
result = "Entry node for " + concat( | | s.toString(), ",")
|
||||||
)
|
)
|
||||||
or
|
or
|
||||||
exists(Scope s | s.getANormalExit() = this | result = "Exit node for " + s.toString())
|
exists(Py::Scope s | s.getANormalExit() = this | result = "Exit node for " + s.toString())
|
||||||
or
|
or
|
||||||
not exists(Scope s | s.getEntryNode() = this or s.getANormalExit() = this) and
|
not exists(Py::Scope s | s.getEntryNode() = this or s.getANormalExit() = this) and
|
||||||
result = "ControlFlowNode for " + this.getNode().toString()
|
result = "ControlFlowNode for " + this.getNode().toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gets the location of this ControlFlowNode */
|
/** Gets the location of this ControlFlowNode */
|
||||||
Location getLocation() { result = this.getNode().getLocation() }
|
Py::Location getLocation() { result = this.getNode().getLocation() }
|
||||||
|
|
||||||
/** Whether this flow node is the first in its scope */
|
/** Whether this flow node is the first in its scope */
|
||||||
predicate isEntryNode() { py_scope_flow(this, _, -1) }
|
predicate isEntryNode() { py_scope_flow(this, _, -1) }
|
||||||
@@ -151,9 +151,9 @@ class ControlFlowNode extends @py_flow_node {
|
|||||||
|
|
||||||
/** Gets the scope containing this flow node */
|
/** Gets the scope containing this flow node */
|
||||||
cached
|
cached
|
||||||
Scope getScope() {
|
Py::Scope getScope() {
|
||||||
Stages::AST::ref() and
|
Stages::AST::ref() and
|
||||||
if this.getNode() instanceof Scope
|
if this.getNode() instanceof Py::Scope
|
||||||
then
|
then
|
||||||
/* Entry or exit node */
|
/* Entry or exit node */
|
||||||
result = this.getNode()
|
result = this.getNode()
|
||||||
@@ -161,7 +161,7 @@ class ControlFlowNode extends @py_flow_node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Gets the enclosing module */
|
/** Gets the enclosing module */
|
||||||
Module getEnclosingModule() { result = this.getScope().getEnclosingModule() }
|
Py::Module getEnclosingModule() { result = this.getScope().getEnclosingModule() }
|
||||||
|
|
||||||
/** Gets a successor for this node if the relevant condition is True. */
|
/** Gets a successor for this node if the relevant condition is True. */
|
||||||
ControlFlowNode getATrueSuccessor() {
|
ControlFlowNode getATrueSuccessor() {
|
||||||
@@ -188,7 +188,7 @@ class ControlFlowNode extends @py_flow_node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Whether the scope may be exited as a result of this node raising an exception */
|
/** Whether the scope may be exited as a result of this node raising an exception */
|
||||||
predicate isExceptionalExit(Scope s) { py_scope_flow(this, s, 1) }
|
predicate isExceptionalExit(Py::Scope s) { py_scope_flow(this, s, 1) }
|
||||||
|
|
||||||
/** Whether this node is a normal (non-exceptional) exit */
|
/** Whether this node is a normal (non-exceptional) exit */
|
||||||
predicate isNormalExit() { py_scope_flow(this, _, 0) or py_scope_flow(this, _, 2) }
|
predicate isNormalExit() { py_scope_flow(this, _, 0) or py_scope_flow(this, _, 2) }
|
||||||
@@ -236,7 +236,7 @@ class ControlFlowNode extends @py_flow_node {
|
|||||||
/* join-ordering helper for `getAChild() */
|
/* join-ordering helper for `getAChild() */
|
||||||
pragma[noinline]
|
pragma[noinline]
|
||||||
private ControlFlowNode getExprChild(BasicBlock dom) {
|
private ControlFlowNode getExprChild(BasicBlock dom) {
|
||||||
this.getNode().(Expr).getAChildNode() = result.getNode() and
|
this.getNode().(Py::Expr).getAChildNode() = result.getNode() and
|
||||||
result.getBasicBlock().dominates(dom) and
|
result.getBasicBlock().dominates(dom) and
|
||||||
not this instanceof UnaryExprNode
|
not this instanceof UnaryExprNode
|
||||||
}
|
}
|
||||||
@@ -249,16 +249,16 @@ class ControlFlowNode extends @py_flow_node {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
private class AnyNode extends ControlFlowNode {
|
private class AnyNode extends ControlFlowNode {
|
||||||
override AstNode getNode() { result = super.getNode() }
|
override Py::AstNode getNode() { result = super.getNode() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A control flow node corresponding to a call expression, such as `func(...)` */
|
/** A control flow node corresponding to a call expression, such as `func(...)` */
|
||||||
class CallNode extends ControlFlowNode {
|
class CallNode extends ControlFlowNode {
|
||||||
CallNode() { toAst(this) instanceof Call }
|
CallNode() { toAst(this) instanceof Py::Call }
|
||||||
|
|
||||||
/** Gets the flow node corresponding to the function expression for the call corresponding to this flow node */
|
/** Gets the flow node corresponding to the function expression for the call corresponding to this flow node */
|
||||||
ControlFlowNode getFunction() {
|
ControlFlowNode getFunction() {
|
||||||
exists(Call c |
|
exists(Py::Call c |
|
||||||
this.getNode() = c and
|
this.getNode() = c and
|
||||||
c.getFunc() = result.getNode() and
|
c.getFunc() = result.getNode() and
|
||||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||||
@@ -267,7 +267,7 @@ class CallNode extends ControlFlowNode {
|
|||||||
|
|
||||||
/** Gets the flow node corresponding to the n'th positional argument of the call corresponding to this flow node */
|
/** Gets the flow node corresponding to the n'th positional argument of the call corresponding to this flow node */
|
||||||
ControlFlowNode getArg(int n) {
|
ControlFlowNode getArg(int n) {
|
||||||
exists(Call c |
|
exists(Py::Call c |
|
||||||
this.getNode() = c and
|
this.getNode() = c and
|
||||||
c.getArg(n) = result.getNode() and
|
c.getArg(n) = result.getNode() and
|
||||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||||
@@ -276,7 +276,7 @@ class CallNode extends ControlFlowNode {
|
|||||||
|
|
||||||
/** Gets the flow node corresponding to the named argument of the call corresponding to this flow node */
|
/** Gets the flow node corresponding to the named argument of the call corresponding to this flow node */
|
||||||
ControlFlowNode getArgByName(string name) {
|
ControlFlowNode getArgByName(string name) {
|
||||||
exists(Call c, Keyword k |
|
exists(Py::Call c, Py::Keyword k |
|
||||||
this.getNode() = c and
|
this.getNode() = c and
|
||||||
k = c.getANamedArg() and
|
k = c.getANamedArg() and
|
||||||
k.getValue() = result.getNode() and
|
k.getValue() = result.getNode() and
|
||||||
@@ -292,7 +292,7 @@ class CallNode extends ControlFlowNode {
|
|||||||
result = this.getArgByName(_)
|
result = this.getArgByName(_)
|
||||||
}
|
}
|
||||||
|
|
||||||
override Call getNode() { result = super.getNode() }
|
override Py::Call getNode() { result = super.getNode() }
|
||||||
|
|
||||||
predicate isDecoratorCall() {
|
predicate isDecoratorCall() {
|
||||||
this.isClassDecoratorCall()
|
this.isClassDecoratorCall()
|
||||||
@@ -301,11 +301,11 @@ class CallNode extends ControlFlowNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
predicate isClassDecoratorCall() {
|
predicate isClassDecoratorCall() {
|
||||||
exists(ClassExpr cls | this.getNode() = cls.getADecoratorCall())
|
exists(Py::ClassExpr cls | this.getNode() = cls.getADecoratorCall())
|
||||||
}
|
}
|
||||||
|
|
||||||
predicate isFunctionDecoratorCall() {
|
predicate isFunctionDecoratorCall() {
|
||||||
exists(FunctionExpr func | this.getNode() = func.getADecoratorCall())
|
exists(Py::FunctionExpr func | this.getNode() = func.getADecoratorCall())
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gets the first tuple (*) argument of this call, if any. */
|
/** Gets the first tuple (*) argument of this call, if any. */
|
||||||
@@ -323,11 +323,11 @@ class CallNode extends ControlFlowNode {
|
|||||||
|
|
||||||
/** A control flow corresponding to an attribute expression, such as `value.attr` */
|
/** A control flow corresponding to an attribute expression, such as `value.attr` */
|
||||||
class AttrNode extends ControlFlowNode {
|
class AttrNode extends ControlFlowNode {
|
||||||
AttrNode() { toAst(this) instanceof Attribute }
|
AttrNode() { toAst(this) instanceof Py::Attribute }
|
||||||
|
|
||||||
/** Gets the flow node corresponding to the object of the attribute expression corresponding to this flow node */
|
/** Gets the flow node corresponding to the object of the attribute expression corresponding to this flow node */
|
||||||
ControlFlowNode getObject() {
|
ControlFlowNode getObject() {
|
||||||
exists(Attribute a |
|
exists(Py::Attribute a |
|
||||||
this.getNode() = a and
|
this.getNode() = a and
|
||||||
a.getObject() = result.getNode() and
|
a.getObject() = result.getNode() and
|
||||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||||
@@ -339,7 +339,7 @@ class AttrNode extends ControlFlowNode {
|
|||||||
* with the matching name
|
* with the matching name
|
||||||
*/
|
*/
|
||||||
ControlFlowNode getObject(string name) {
|
ControlFlowNode getObject(string name) {
|
||||||
exists(Attribute a |
|
exists(Py::Attribute a |
|
||||||
this.getNode() = a and
|
this.getNode() = a and
|
||||||
a.getObject() = result.getNode() and
|
a.getObject() = result.getNode() and
|
||||||
a.getName() = name and
|
a.getName() = name and
|
||||||
@@ -348,57 +348,57 @@ class AttrNode extends ControlFlowNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Gets the attribute name of the attribute expression corresponding to this flow node */
|
/** Gets the attribute name of the attribute expression corresponding to this flow node */
|
||||||
string getName() { exists(Attribute a | this.getNode() = a and a.getName() = result) }
|
string getName() { exists(Py::Attribute a | this.getNode() = a and a.getName() = result) }
|
||||||
|
|
||||||
override Attribute getNode() { result = super.getNode() }
|
override Py::Attribute getNode() { result = super.getNode() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A control flow node corresponding to a `from ... import ...` expression */
|
/** A control flow node corresponding to a `from ... import ...` expression */
|
||||||
class ImportMemberNode extends ControlFlowNode {
|
class ImportMemberNode extends ControlFlowNode {
|
||||||
ImportMemberNode() { toAst(this) instanceof ImportMember }
|
ImportMemberNode() { toAst(this) instanceof Py::ImportMember }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the flow node corresponding to the module in the import-member expression corresponding to this flow node,
|
* Gets the flow node corresponding to the module in the import-member expression corresponding to this flow node,
|
||||||
* with the matching name
|
* with the matching name
|
||||||
*/
|
*/
|
||||||
ControlFlowNode getModule(string name) {
|
ControlFlowNode getModule(string name) {
|
||||||
exists(ImportMember i | this.getNode() = i and i.getModule() = result.getNode() |
|
exists(Py::ImportMember i | this.getNode() = i and i.getModule() = result.getNode() |
|
||||||
i.getName() = name and
|
i.getName() = name and
|
||||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override ImportMember getNode() { result = super.getNode() }
|
override Py::ImportMember getNode() { result = super.getNode() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A control flow node corresponding to an artificial expression representing an import */
|
/** A control flow node corresponding to an artificial expression representing an import */
|
||||||
class ImportExprNode extends ControlFlowNode {
|
class ImportExprNode extends ControlFlowNode {
|
||||||
ImportExprNode() { toAst(this) instanceof ImportExpr }
|
ImportExprNode() { toAst(this) instanceof Py::ImportExpr }
|
||||||
|
|
||||||
override ImportExpr getNode() { result = super.getNode() }
|
override Py::ImportExpr getNode() { result = super.getNode() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A control flow node corresponding to a `from ... import *` statement */
|
/** A control flow node corresponding to a `from ... import *` statement */
|
||||||
class ImportStarNode extends ControlFlowNode {
|
class ImportStarNode extends ControlFlowNode {
|
||||||
ImportStarNode() { toAst(this) instanceof ImportStar }
|
ImportStarNode() { toAst(this) instanceof Py::ImportStar }
|
||||||
|
|
||||||
/** Gets the flow node corresponding to the module in the import-star corresponding to this flow node */
|
/** Gets the flow node corresponding to the module in the import-star corresponding to this flow node */
|
||||||
ControlFlowNode getModule() {
|
ControlFlowNode getModule() {
|
||||||
exists(ImportStar i | this.getNode() = i and i.getModuleExpr() = result.getNode() |
|
exists(Py::ImportStar i | this.getNode() = i and i.getModuleExpr() = result.getNode() |
|
||||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override ImportStar getNode() { result = super.getNode() }
|
override Py::ImportStar getNode() { result = super.getNode() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A control flow node corresponding to a subscript expression, such as `value[slice]` */
|
/** A control flow node corresponding to a subscript expression, such as `value[slice]` */
|
||||||
class SubscriptNode extends ControlFlowNode {
|
class SubscriptNode extends ControlFlowNode {
|
||||||
SubscriptNode() { toAst(this) instanceof Subscript }
|
SubscriptNode() { toAst(this) instanceof Py::Subscript }
|
||||||
|
|
||||||
/** flow node corresponding to the value of the sequence in a subscript operation */
|
/** flow node corresponding to the value of the sequence in a subscript operation */
|
||||||
ControlFlowNode getObject() {
|
ControlFlowNode getObject() {
|
||||||
exists(Subscript s |
|
exists(Py::Subscript s |
|
||||||
this.getNode() = s and
|
this.getNode() = s and
|
||||||
s.getObject() = result.getNode() and
|
s.getObject() = result.getNode() and
|
||||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||||
@@ -407,23 +407,23 @@ class SubscriptNode extends ControlFlowNode {
|
|||||||
|
|
||||||
/** flow node corresponding to the index in a subscript operation */
|
/** flow node corresponding to the index in a subscript operation */
|
||||||
ControlFlowNode getIndex() {
|
ControlFlowNode getIndex() {
|
||||||
exists(Subscript s |
|
exists(Py::Subscript s |
|
||||||
this.getNode() = s and
|
this.getNode() = s and
|
||||||
s.getIndex() = result.getNode() and
|
s.getIndex() = result.getNode() and
|
||||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override Subscript getNode() { result = super.getNode() }
|
override Py::Subscript getNode() { result = super.getNode() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A control flow node corresponding to a comparison operation, such as `x<y` */
|
/** A control flow node corresponding to a comparison operation, such as `x<y` */
|
||||||
class CompareNode extends ControlFlowNode {
|
class CompareNode extends ControlFlowNode {
|
||||||
CompareNode() { toAst(this) instanceof Compare }
|
CompareNode() { toAst(this) instanceof Py::Compare }
|
||||||
|
|
||||||
/** Whether left and right are a pair of operands for this comparison */
|
/** Whether left and right are a pair of operands for this comparison */
|
||||||
predicate operands(ControlFlowNode left, Cmpop op, ControlFlowNode right) {
|
predicate operands(ControlFlowNode left, Py::Cmpop op, ControlFlowNode right) {
|
||||||
exists(Compare c, Expr eleft, Expr eright |
|
exists(Py::Compare c, Py::Expr eleft, Py::Expr eright |
|
||||||
this.getNode() = c and left.getNode() = eleft and right.getNode() = eright
|
this.getNode() = c and left.getNode() = eleft and right.getNode() = eright
|
||||||
|
|
|
|
||||||
eleft = c.getLeft() and eright = c.getComparator(0) and op = c.getOp(0)
|
eleft = c.getLeft() and eright = c.getComparator(0) and op = c.getOp(0)
|
||||||
@@ -436,26 +436,26 @@ class CompareNode extends ControlFlowNode {
|
|||||||
right.getBasicBlock().dominates(this.getBasicBlock())
|
right.getBasicBlock().dominates(this.getBasicBlock())
|
||||||
}
|
}
|
||||||
|
|
||||||
override Compare getNode() { result = super.getNode() }
|
override Py::Compare getNode() { result = super.getNode() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A control flow node corresponding to a conditional expression such as, `body if test else orelse` */
|
/** A control flow node corresponding to a conditional expression such as, `body if test else orelse` */
|
||||||
class IfExprNode extends ControlFlowNode {
|
class IfExprNode extends ControlFlowNode {
|
||||||
IfExprNode() { toAst(this) instanceof IfExp }
|
IfExprNode() { toAst(this) instanceof Py::IfExp }
|
||||||
|
|
||||||
/** flow node corresponding to one of the operands of an if-expression */
|
/** flow node corresponding to one of the operands of an if-expression */
|
||||||
ControlFlowNode getAnOperand() { result = this.getAPredecessor() }
|
ControlFlowNode getAnOperand() { result = this.getAPredecessor() }
|
||||||
|
|
||||||
override IfExp getNode() { result = super.getNode() }
|
override Py::IfExp getNode() { result = super.getNode() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A control flow node corresponding to an assignment expression such as `lhs := rhs`. */
|
/** A control flow node corresponding to an assignment expression such as `lhs := rhs`. */
|
||||||
class AssignmentExprNode extends ControlFlowNode {
|
class AssignmentExprNode extends ControlFlowNode {
|
||||||
AssignmentExprNode() { toAst(this) instanceof AssignExpr }
|
AssignmentExprNode() { toAst(this) instanceof Py::AssignExpr }
|
||||||
|
|
||||||
/** Gets the flow node corresponding to the left-hand side of the assignment expression */
|
/** Gets the flow node corresponding to the left-hand side of the assignment expression */
|
||||||
ControlFlowNode getTarget() {
|
ControlFlowNode getTarget() {
|
||||||
exists(AssignExpr a |
|
exists(Py::AssignExpr a |
|
||||||
this.getNode() = a and
|
this.getNode() = a and
|
||||||
a.getTarget() = result.getNode() and
|
a.getTarget() = result.getNode() and
|
||||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||||
@@ -464,27 +464,27 @@ class AssignmentExprNode extends ControlFlowNode {
|
|||||||
|
|
||||||
/** Gets the flow node corresponding to the right-hand side of the assignment expression */
|
/** Gets the flow node corresponding to the right-hand side of the assignment expression */
|
||||||
ControlFlowNode getValue() {
|
ControlFlowNode getValue() {
|
||||||
exists(AssignExpr a |
|
exists(Py::AssignExpr a |
|
||||||
this.getNode() = a and
|
this.getNode() = a and
|
||||||
a.getValue() = result.getNode() and
|
a.getValue() = result.getNode() and
|
||||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override AssignExpr getNode() { result = super.getNode() }
|
override Py::AssignExpr getNode() { result = super.getNode() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A control flow node corresponding to a binary expression, such as `x + y` */
|
/** A control flow node corresponding to a binary expression, such as `x + y` */
|
||||||
class BinaryExprNode extends ControlFlowNode {
|
class BinaryExprNode extends ControlFlowNode {
|
||||||
BinaryExprNode() { toAst(this) instanceof BinaryExpr }
|
BinaryExprNode() { toAst(this) instanceof Py::BinaryExpr }
|
||||||
|
|
||||||
/** flow node corresponding to one of the operands of a binary expression */
|
/** flow node corresponding to one of the operands of a binary expression */
|
||||||
ControlFlowNode getAnOperand() { result = this.getLeft() or result = this.getRight() }
|
ControlFlowNode getAnOperand() { result = this.getLeft() or result = this.getRight() }
|
||||||
|
|
||||||
override BinaryExpr getNode() { result = super.getNode() }
|
override Py::BinaryExpr getNode() { result = super.getNode() }
|
||||||
|
|
||||||
ControlFlowNode getLeft() {
|
ControlFlowNode getLeft() {
|
||||||
exists(BinaryExpr b |
|
exists(Py::BinaryExpr b |
|
||||||
this.getNode() = b and
|
this.getNode() = b and
|
||||||
result.getNode() = b.getLeft() and
|
result.getNode() = b.getLeft() and
|
||||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||||
@@ -492,7 +492,7 @@ class BinaryExprNode extends ControlFlowNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ControlFlowNode getRight() {
|
ControlFlowNode getRight() {
|
||||||
exists(BinaryExpr b |
|
exists(Py::BinaryExpr b |
|
||||||
this.getNode() = b and
|
this.getNode() = b and
|
||||||
result.getNode() = b.getRight() and
|
result.getNode() = b.getRight() and
|
||||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||||
@@ -500,11 +500,11 @@ class BinaryExprNode extends ControlFlowNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Gets the operator of this binary expression node. */
|
/** Gets the operator of this binary expression node. */
|
||||||
Operator getOp() { result = this.getNode().getOp() }
|
Py::Operator getOp() { result = this.getNode().getOp() }
|
||||||
|
|
||||||
/** Whether left and right are a pair of operands for this binary expression */
|
/** Whether left and right are a pair of operands for this binary expression */
|
||||||
predicate operands(ControlFlowNode left, Operator op, ControlFlowNode right) {
|
predicate operands(ControlFlowNode left, Py::Operator op, ControlFlowNode right) {
|
||||||
exists(BinaryExpr b, Expr eleft, Expr eright |
|
exists(Py::BinaryExpr b, Py::Expr eleft, Py::Expr eright |
|
||||||
this.getNode() = b and left.getNode() = eleft and right.getNode() = eright
|
this.getNode() = b and left.getNode() = eleft and right.getNode() = eright
|
||||||
|
|
|
|
||||||
eleft = b.getLeft() and eright = b.getRight() and op = b.getOp()
|
eleft = b.getLeft() and eright = b.getRight() and op = b.getOp()
|
||||||
@@ -516,20 +516,20 @@ class BinaryExprNode extends ControlFlowNode {
|
|||||||
|
|
||||||
/** A control flow node corresponding to a boolean shortcut (and/or) operation */
|
/** A control flow node corresponding to a boolean shortcut (and/or) operation */
|
||||||
class BoolExprNode extends ControlFlowNode {
|
class BoolExprNode extends ControlFlowNode {
|
||||||
BoolExprNode() { toAst(this) instanceof BoolExpr }
|
BoolExprNode() { toAst(this) instanceof Py::BoolExpr }
|
||||||
|
|
||||||
/** flow node corresponding to one of the operands of a boolean expression */
|
/** flow node corresponding to one of the operands of a boolean expression */
|
||||||
ControlFlowNode getAnOperand() {
|
ControlFlowNode getAnOperand() {
|
||||||
exists(BoolExpr b | this.getNode() = b and result.getNode() = b.getAValue()) and
|
exists(Py::BoolExpr b | this.getNode() = b and result.getNode() = b.getAValue()) and
|
||||||
this.getBasicBlock().dominates(result.getBasicBlock())
|
this.getBasicBlock().dominates(result.getBasicBlock())
|
||||||
}
|
}
|
||||||
|
|
||||||
override BoolExpr getNode() { result = super.getNode() }
|
override Py::BoolExpr getNode() { result = super.getNode() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A control flow node corresponding to a unary expression: (`+x`), (`-x`) or (`~x`) */
|
/** A control flow node corresponding to a unary expression: (`+x`), (`-x`) or (`~x`) */
|
||||||
class UnaryExprNode extends ControlFlowNode {
|
class UnaryExprNode extends ControlFlowNode {
|
||||||
UnaryExprNode() { toAst(this) instanceof UnaryExpr }
|
UnaryExprNode() { toAst(this) instanceof Py::UnaryExpr }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets flow node corresponding to the operand of a unary expression.
|
* Gets flow node corresponding to the operand of a unary expression.
|
||||||
@@ -540,7 +540,7 @@ class UnaryExprNode extends ControlFlowNode {
|
|||||||
*/
|
*/
|
||||||
ControlFlowNode getOperand() { result = this.getAPredecessor() }
|
ControlFlowNode getOperand() { result = this.getAPredecessor() }
|
||||||
|
|
||||||
override UnaryExpr getNode() { result = super.getNode() }
|
override Py::UnaryExpr getNode() { result = super.getNode() }
|
||||||
|
|
||||||
override ControlFlowNode getAChild() { result = this.getAPredecessor() }
|
override ControlFlowNode getAChild() { result = this.getAPredecessor() }
|
||||||
}
|
}
|
||||||
@@ -555,27 +555,27 @@ class DefinitionNode extends ControlFlowNode {
|
|||||||
cached
|
cached
|
||||||
DefinitionNode() {
|
DefinitionNode() {
|
||||||
Stages::AST::ref() and
|
Stages::AST::ref() and
|
||||||
exists(Assign a | a.getATarget().getAFlowNode() = this)
|
exists(Py::Assign a | this.getNode() = a.getATarget())
|
||||||
or
|
or
|
||||||
exists(AssignExpr a | a.getTarget().getAFlowNode() = this)
|
exists(Py::AssignExpr a | this.getNode() = a.getTarget())
|
||||||
or
|
or
|
||||||
exists(AnnAssign a | a.getTarget().getAFlowNode() = this and exists(a.getValue()))
|
exists(Py::AnnAssign a | this.getNode() = a.getTarget() and exists(a.getValue()))
|
||||||
or
|
or
|
||||||
exists(Alias a | a.getAsname().getAFlowNode() = this)
|
exists(Py::Alias a | this.getNode() = a.getAsname())
|
||||||
or
|
or
|
||||||
augstore(_, this)
|
augstore(_, this)
|
||||||
or
|
or
|
||||||
// `x, y = 1, 2` where LHS is a combination of list or tuples
|
// `x, y = 1, 2` where LHS is a combination of list or tuples
|
||||||
exists(Assign a | list_or_tuple_nested_element(a.getATarget()).getAFlowNode() = this)
|
exists(Py::Assign a | this.getNode() = list_or_tuple_nested_element(a.getATarget()))
|
||||||
or
|
or
|
||||||
exists(For for | for.getTarget().getAFlowNode() = this)
|
exists(Py::For for | this.getNode() = for.getTarget())
|
||||||
or
|
or
|
||||||
exists(Parameter param | this = param.asName().getAFlowNode() and exists(param.getDefault()))
|
exists(Py::Parameter param | this.getNode() = param.asName() and exists(param.getDefault()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** flow node corresponding to the value assigned for the definition corresponding to this flow node */
|
/** flow node corresponding to the value assigned for the definition corresponding to this flow node */
|
||||||
ControlFlowNode getValue() {
|
ControlFlowNode getValue() {
|
||||||
result = assigned_value(this.getNode()).getAFlowNode() and
|
result.getNode() = assigned_value(this.getNode()) and
|
||||||
(
|
(
|
||||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||||
or
|
or
|
||||||
@@ -584,16 +584,16 @@ class DefinitionNode extends ControlFlowNode {
|
|||||||
// since the default value for a parameter is evaluated in the same basic block as
|
// since the default value for a parameter is evaluated in the same basic block as
|
||||||
// the function definition, but the parameter belongs to the basic block of the function,
|
// the function definition, but the parameter belongs to the basic block of the function,
|
||||||
// there is no dominance relationship between the two.
|
// there is no dominance relationship between the two.
|
||||||
exists(Parameter param | this = param.asName().getAFlowNode())
|
exists(Py::Parameter param | this.getNode() = param.asName())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expr list_or_tuple_nested_element(Expr list_or_tuple) {
|
private Py::Expr list_or_tuple_nested_element(Py::Expr list_or_tuple) {
|
||||||
exists(Expr elt |
|
exists(Py::Expr elt |
|
||||||
elt = list_or_tuple.(Tuple).getAnElt()
|
elt = list_or_tuple.(Py::Tuple).getAnElt()
|
||||||
or
|
or
|
||||||
elt = list_or_tuple.(List).getAnElt()
|
elt = list_or_tuple.(Py::List).getAnElt()
|
||||||
|
|
|
|
||||||
result = elt
|
result = elt
|
||||||
or
|
or
|
||||||
@@ -603,12 +603,12 @@ private Expr list_or_tuple_nested_element(Expr list_or_tuple) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A control flow node corresponding to a deletion statement, such as `del x`.
|
* A control flow node corresponding to a deletion statement, such as `del x`.
|
||||||
* There can be multiple `DeletionNode`s for each `Delete` such that each
|
* There can be multiple `DeletionNode`s for each `Py::Delete` such that each
|
||||||
* target has own `DeletionNode`. The CFG for `del a, x.y` looks like:
|
* target has own `DeletionNode`. The CFG for `del a, x.y` looks like:
|
||||||
* `NameNode('a') -> DeletionNode -> NameNode('b') -> AttrNode('y') -> DeletionNode`.
|
* `NameNode('a') -> DeletionNode -> NameNode('b') -> AttrNode('y') -> DeletionNode`.
|
||||||
*/
|
*/
|
||||||
class DeletionNode extends ControlFlowNode {
|
class DeletionNode extends ControlFlowNode {
|
||||||
DeletionNode() { toAst(this) instanceof Delete }
|
DeletionNode() { toAst(this) instanceof Py::Delete }
|
||||||
|
|
||||||
/** Gets the unique target of this deletion node. */
|
/** Gets the unique target of this deletion node. */
|
||||||
ControlFlowNode getTarget() { result.getASuccessor() = this }
|
ControlFlowNode getTarget() { result.getASuccessor() = this }
|
||||||
@@ -617,9 +617,9 @@ class DeletionNode extends ControlFlowNode {
|
|||||||
/** A control flow node corresponding to a sequence (tuple or list) literal */
|
/** A control flow node corresponding to a sequence (tuple or list) literal */
|
||||||
abstract class SequenceNode extends ControlFlowNode {
|
abstract class SequenceNode extends ControlFlowNode {
|
||||||
SequenceNode() {
|
SequenceNode() {
|
||||||
toAst(this) instanceof Tuple
|
toAst(this) instanceof Py::Tuple
|
||||||
or
|
or
|
||||||
toAst(this) instanceof List
|
toAst(this) instanceof Py::List
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gets the control flow node for an element of this sequence */
|
/** Gets the control flow node for an element of this sequence */
|
||||||
@@ -632,11 +632,11 @@ abstract class SequenceNode extends ControlFlowNode {
|
|||||||
|
|
||||||
/** A control flow node corresponding to a tuple expression such as `( 1, 3, 5, 7, 9 )` */
|
/** A control flow node corresponding to a tuple expression such as `( 1, 3, 5, 7, 9 )` */
|
||||||
class TupleNode extends SequenceNode {
|
class TupleNode extends SequenceNode {
|
||||||
TupleNode() { toAst(this) instanceof Tuple }
|
TupleNode() { toAst(this) instanceof Py::Tuple }
|
||||||
|
|
||||||
override ControlFlowNode getElement(int n) {
|
override ControlFlowNode getElement(int n) {
|
||||||
Stages::AST::ref() and
|
Stages::AST::ref() and
|
||||||
exists(Tuple t | this.getNode() = t and result.getNode() = t.getElt(n)) and
|
exists(Py::Tuple t | this.getNode() = t and result.getNode() = t.getElt(n)) and
|
||||||
(
|
(
|
||||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||||
or
|
or
|
||||||
@@ -647,10 +647,10 @@ class TupleNode extends SequenceNode {
|
|||||||
|
|
||||||
/** A control flow node corresponding to a list expression, such as `[ 1, 3, 5, 7, 9 ]` */
|
/** A control flow node corresponding to a list expression, such as `[ 1, 3, 5, 7, 9 ]` */
|
||||||
class ListNode extends SequenceNode {
|
class ListNode extends SequenceNode {
|
||||||
ListNode() { toAst(this) instanceof List }
|
ListNode() { toAst(this) instanceof Py::List }
|
||||||
|
|
||||||
override ControlFlowNode getElement(int n) {
|
override ControlFlowNode getElement(int n) {
|
||||||
exists(List l | this.getNode() = l and result.getNode() = l.getElt(n)) and
|
exists(Py::List l | this.getNode() = l and result.getNode() = l.getElt(n)) and
|
||||||
(
|
(
|
||||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||||
or
|
or
|
||||||
@@ -661,10 +661,10 @@ class ListNode extends SequenceNode {
|
|||||||
|
|
||||||
/** A control flow node corresponding to a set expression, such as `{ 1, 3, 5, 7, 9 }` */
|
/** A control flow node corresponding to a set expression, such as `{ 1, 3, 5, 7, 9 }` */
|
||||||
class SetNode extends ControlFlowNode {
|
class SetNode extends ControlFlowNode {
|
||||||
SetNode() { toAst(this) instanceof Set }
|
SetNode() { toAst(this) instanceof Py::Set }
|
||||||
|
|
||||||
ControlFlowNode getAnElement() {
|
ControlFlowNode getAnElement() {
|
||||||
exists(Set s | this.getNode() = s and result.getNode() = s.getElt(_)) and
|
exists(Py::Set s | this.getNode() = s and result.getNode() = s.getElt(_)) and
|
||||||
(
|
(
|
||||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||||
or
|
or
|
||||||
@@ -675,20 +675,20 @@ class SetNode extends ControlFlowNode {
|
|||||||
|
|
||||||
/** A control flow node corresponding to a dictionary literal, such as `{ 'a': 1, 'b': 2 }` */
|
/** A control flow node corresponding to a dictionary literal, such as `{ 'a': 1, 'b': 2 }` */
|
||||||
class DictNode extends ControlFlowNode {
|
class DictNode extends ControlFlowNode {
|
||||||
DictNode() { toAst(this) instanceof Dict }
|
DictNode() { toAst(this) instanceof Py::Dict }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a key of this dictionary literal node, for those items that have keys
|
* Gets a key of this dictionary literal node, for those items that have keys
|
||||||
* E.g, in {'a':1, **b} this returns only 'a'
|
* E.g, in {'a':1, **b} this returns only 'a'
|
||||||
*/
|
*/
|
||||||
ControlFlowNode getAKey() {
|
ControlFlowNode getAKey() {
|
||||||
exists(Dict d | this.getNode() = d and result.getNode() = d.getAKey()) and
|
exists(Py::Dict d | this.getNode() = d and result.getNode() = d.getAKey()) and
|
||||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gets a value of this dictionary literal node */
|
/** Gets a value of this dictionary literal node */
|
||||||
ControlFlowNode getAValue() {
|
ControlFlowNode getAValue() {
|
||||||
exists(Dict d | this.getNode() = d and result.getNode() = d.getAValue()) and
|
exists(Py::Dict d | this.getNode() = d and result.getNode() = d.getAValue()) and
|
||||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -712,21 +712,23 @@ class IterableNode extends ControlFlowNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private AstNode assigned_value(Expr lhs) {
|
private Py::AstNode assigned_value(Py::Expr lhs) {
|
||||||
/* lhs = result */
|
/* lhs = result */
|
||||||
exists(Assign a | a.getATarget() = lhs and result = a.getValue())
|
exists(Py::Assign a | a.getATarget() = lhs and result = a.getValue())
|
||||||
or
|
or
|
||||||
/* lhs := result */
|
/* lhs := result */
|
||||||
exists(AssignExpr a | a.getTarget() = lhs and result = a.getValue())
|
exists(Py::AssignExpr a | a.getTarget() = lhs and result = a.getValue())
|
||||||
or
|
or
|
||||||
/* lhs : annotation = result */
|
/* lhs : annotation = result */
|
||||||
exists(AnnAssign a | a.getTarget() = lhs and result = a.getValue())
|
exists(Py::AnnAssign a | a.getTarget() = lhs and result = a.getValue())
|
||||||
or
|
or
|
||||||
/* import result as lhs */
|
/* import result as lhs */
|
||||||
exists(Alias a | a.getAsname() = lhs and result = a.getValue())
|
exists(Py::Alias a | a.getAsname() = lhs and result = a.getValue())
|
||||||
or
|
or
|
||||||
/* lhs += x => result = (lhs + x) */
|
/* lhs += x => result = (lhs + x) */
|
||||||
exists(AugAssign a, BinaryExpr b | b = a.getOperation() and result = b and lhs = b.getLeft())
|
exists(Py::AugAssign a, Py::BinaryExpr b |
|
||||||
|
b = a.getOperation() and result = b and lhs = b.getLeft()
|
||||||
|
)
|
||||||
or
|
or
|
||||||
/*
|
/*
|
||||||
* ..., lhs, ... = ..., result, ...
|
* ..., lhs, ... = ..., result, ...
|
||||||
@@ -734,31 +736,31 @@ private AstNode assigned_value(Expr lhs) {
|
|||||||
* ..., (..., lhs, ...), ... = ..., (..., result, ...), ...
|
* ..., (..., lhs, ...), ... = ..., (..., result, ...), ...
|
||||||
*/
|
*/
|
||||||
|
|
||||||
exists(Assign a | nested_sequence_assign(a.getATarget(), a.getValue(), lhs, result))
|
exists(Py::Assign a | nested_sequence_assign(a.getATarget(), a.getValue(), lhs, result))
|
||||||
or
|
or
|
||||||
/* for lhs in seq: => `result` is the `for` node, representing the `iter(next(seq))` operation. */
|
/* for lhs in seq: => `result` is the `for` node, representing the `iter(next(seq))` operation. */
|
||||||
result.(For).getTarget() = lhs
|
result.(Py::For).getTarget() = lhs
|
||||||
or
|
or
|
||||||
exists(Parameter param | lhs = param.asName() and result = param.getDefault())
|
exists(Py::Parameter param | lhs = param.asName() and result = param.getDefault())
|
||||||
}
|
}
|
||||||
|
|
||||||
predicate nested_sequence_assign(
|
predicate nested_sequence_assign(
|
||||||
Expr left_parent, Expr right_parent, Expr left_result, Expr right_result
|
Py::Expr left_parent, Py::Expr right_parent, Py::Expr left_result, Py::Expr right_result
|
||||||
) {
|
) {
|
||||||
exists(Assign a |
|
exists(Py::Assign a |
|
||||||
a.getATarget().getASubExpression*() = left_parent and
|
a.getATarget().getASubExpression*() = left_parent and
|
||||||
a.getValue().getASubExpression*() = right_parent
|
a.getValue().getASubExpression*() = right_parent
|
||||||
) and
|
) and
|
||||||
exists(int i, Expr left_elem, Expr right_elem |
|
exists(int i, Py::Expr left_elem, Py::Expr right_elem |
|
||||||
(
|
(
|
||||||
left_elem = left_parent.(Tuple).getElt(i)
|
left_elem = left_parent.(Py::Tuple).getElt(i)
|
||||||
or
|
or
|
||||||
left_elem = left_parent.(List).getElt(i)
|
left_elem = left_parent.(Py::List).getElt(i)
|
||||||
) and
|
) and
|
||||||
(
|
(
|
||||||
right_elem = right_parent.(Tuple).getElt(i)
|
right_elem = right_parent.(Py::Tuple).getElt(i)
|
||||||
or
|
or
|
||||||
right_elem = right_parent.(List).getElt(i)
|
right_elem = right_parent.(Py::List).getElt(i)
|
||||||
)
|
)
|
||||||
|
|
|
|
||||||
left_result = left_elem and right_result = right_elem
|
left_result = left_elem and right_result = right_elem
|
||||||
@@ -769,9 +771,9 @@ predicate nested_sequence_assign(
|
|||||||
|
|
||||||
/** A flow node for a `for` statement. */
|
/** A flow node for a `for` statement. */
|
||||||
class ForNode extends ControlFlowNode {
|
class ForNode extends ControlFlowNode {
|
||||||
ForNode() { toAst(this) instanceof For }
|
ForNode() { toAst(this) instanceof Py::For }
|
||||||
|
|
||||||
override For getNode() { result = super.getNode() }
|
override Py::For getNode() { result = super.getNode() }
|
||||||
|
|
||||||
/** Holds if this `for` statement causes iteration over `sequence` storing each step of the iteration in `target` */
|
/** Holds if this `for` statement causes iteration over `sequence` storing each step of the iteration in `target` */
|
||||||
predicate iterates(ControlFlowNode target, ControlFlowNode sequence) {
|
predicate iterates(ControlFlowNode target, ControlFlowNode sequence) {
|
||||||
@@ -782,7 +784,7 @@ class ForNode extends ControlFlowNode {
|
|||||||
|
|
||||||
/** Gets the sequence node for this `for` statement. */
|
/** Gets the sequence node for this `for` statement. */
|
||||||
ControlFlowNode getSequence() {
|
ControlFlowNode getSequence() {
|
||||||
exists(For for |
|
exists(Py::For for |
|
||||||
toAst(this) = for and
|
toAst(this) = for and
|
||||||
for.getIter() = result.getNode()
|
for.getIter() = result.getNode()
|
||||||
|
|
|
|
||||||
@@ -792,7 +794,7 @@ class ForNode extends ControlFlowNode {
|
|||||||
|
|
||||||
/** A possible `target` for this `for` statement, not accounting for loop unrolling */
|
/** A possible `target` for this `for` statement, not accounting for loop unrolling */
|
||||||
private ControlFlowNode possibleTarget() {
|
private ControlFlowNode possibleTarget() {
|
||||||
exists(For for |
|
exists(Py::For for |
|
||||||
toAst(this) = for and
|
toAst(this) = for and
|
||||||
for.getTarget() = result.getNode() and
|
for.getTarget() = result.getNode() and
|
||||||
this.getBasicBlock().dominates(result.getBasicBlock())
|
this.getBasicBlock().dominates(result.getBasicBlock())
|
||||||
@@ -809,11 +811,11 @@ class ForNode extends ControlFlowNode {
|
|||||||
|
|
||||||
/** A flow node for a `raise` statement */
|
/** A flow node for a `raise` statement */
|
||||||
class RaiseStmtNode extends ControlFlowNode {
|
class RaiseStmtNode extends ControlFlowNode {
|
||||||
RaiseStmtNode() { toAst(this) instanceof Raise }
|
RaiseStmtNode() { toAst(this) instanceof Py::Raise }
|
||||||
|
|
||||||
/** Gets the control flow node for the exception raised by this raise statement */
|
/** Gets the control flow node for the exception raised by this raise statement */
|
||||||
ControlFlowNode getException() {
|
ControlFlowNode getException() {
|
||||||
exists(Raise r |
|
exists(Py::Raise r |
|
||||||
r = toAst(this) and
|
r = toAst(this) and
|
||||||
r.getException() = toAst(result) and
|
r.getException() = toAst(result) and
|
||||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||||
@@ -827,36 +829,36 @@ class RaiseStmtNode extends ControlFlowNode {
|
|||||||
*/
|
*/
|
||||||
class NameNode extends ControlFlowNode {
|
class NameNode extends ControlFlowNode {
|
||||||
NameNode() {
|
NameNode() {
|
||||||
exists(Name n | py_flow_bb_node(this, n, _, _))
|
exists(Py::Name n | py_flow_bb_node(this, n, _, _))
|
||||||
or
|
or
|
||||||
exists(PlaceHolder p | py_flow_bb_node(this, p, _, _))
|
exists(Py::PlaceHolder p | py_flow_bb_node(this, p, _, _))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Whether this flow node defines the variable `v`. */
|
/** Whether this flow node defines the variable `v`. */
|
||||||
predicate defines(Variable v) {
|
predicate defines(Py::Variable v) {
|
||||||
exists(Name d | this.getNode() = d and d.defines(v)) and
|
exists(Py::Name d | this.getNode() = d and d.defines(v)) and
|
||||||
not this.isLoad()
|
not this.isLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Whether this flow node deletes the variable `v`. */
|
/** Whether this flow node deletes the variable `v`. */
|
||||||
predicate deletes(Variable v) { exists(Name d | this.getNode() = d and d.deletes(v)) }
|
predicate deletes(Py::Variable v) { exists(Py::Name d | this.getNode() = d and d.deletes(v)) }
|
||||||
|
|
||||||
/** Whether this flow node uses the variable `v`. */
|
/** Whether this flow node uses the variable `v`. */
|
||||||
predicate uses(Variable v) {
|
predicate uses(Py::Variable v) {
|
||||||
this.isLoad() and
|
this.isLoad() and
|
||||||
exists(Name u | this.getNode() = u and u.uses(v))
|
exists(Py::Name u | this.getNode() = u and u.uses(v))
|
||||||
or
|
or
|
||||||
exists(PlaceHolder u |
|
exists(Py::PlaceHolder u |
|
||||||
this.getNode() = u and u.getVariable() = v and u.getCtx() instanceof Load
|
this.getNode() = u and u.getVariable() = v and u.getCtx() instanceof Py::Load
|
||||||
)
|
)
|
||||||
or
|
or
|
||||||
Scopes::use_of_global_variable(this, v.getScope(), v.getId())
|
Scopes::use_of_global_variable(this, v.getScope(), v.getId())
|
||||||
}
|
}
|
||||||
|
|
||||||
string getId() {
|
string getId() {
|
||||||
result = this.getNode().(Name).getId()
|
result = this.getNode().(Py::Name).getId()
|
||||||
or
|
or
|
||||||
result = this.getNode().(PlaceHolder).getId()
|
result = this.getNode().(Py::PlaceHolder).getId()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Whether this is a use of a local variable. */
|
/** Whether this is a use of a local variable. */
|
||||||
@@ -868,82 +870,84 @@ class NameNode extends ControlFlowNode {
|
|||||||
/** Whether this is a use of a global (including builtin) variable. */
|
/** Whether this is a use of a global (including builtin) variable. */
|
||||||
predicate isGlobal() { Scopes::use_of_global_variable(this, _, _) }
|
predicate isGlobal() { Scopes::use_of_global_variable(this, _, _) }
|
||||||
|
|
||||||
predicate isSelf() { exists(SsaVariable selfvar | selfvar.isSelf() and selfvar.getAUse() = this) }
|
predicate isSelf() {
|
||||||
|
exists(Py::SsaVariable selfvar | selfvar.isSelf() and selfvar.getAUse() = this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A control flow node corresponding to a named constant, one of `None`, `True` or `False`. */
|
/** A control flow node corresponding to a named constant, one of `None`, `True` or `False`. */
|
||||||
class NameConstantNode extends NameNode {
|
class NameConstantNode extends NameNode {
|
||||||
NameConstantNode() { exists(NameConstant n | py_flow_bb_node(this, n, _, _)) }
|
NameConstantNode() { exists(Py::NameConstant n | py_flow_bb_node(this, n, _, _)) }
|
||||||
/*
|
/*
|
||||||
* We ought to override uses as well, but that has
|
* We ought to override uses as well, but that has
|
||||||
* a serious performance impact.
|
* a serious performance impact.
|
||||||
* deprecated predicate uses(Variable v) { none() }
|
* deprecated predicate uses(Py::Variable v) { none() }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A control flow node corresponding to a starred expression, `*a`. */
|
/** A control flow node corresponding to a starred expression, `*a`. */
|
||||||
class StarredNode extends ControlFlowNode {
|
class StarredNode extends ControlFlowNode {
|
||||||
StarredNode() { toAst(this) instanceof Starred }
|
StarredNode() { toAst(this) instanceof Py::Starred }
|
||||||
|
|
||||||
ControlFlowNode getValue() { toAst(result) = toAst(this).(Starred).getValue() }
|
ControlFlowNode getValue() { toAst(result) = toAst(this).(Py::Starred).getValue() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The ControlFlowNode for an 'except' statement. */
|
/** The ControlFlowNode for an 'except' statement. */
|
||||||
class ExceptFlowNode extends ControlFlowNode {
|
class ExceptFlowNode extends ControlFlowNode {
|
||||||
ExceptFlowNode() { this.getNode() instanceof ExceptStmt }
|
ExceptFlowNode() { this.getNode() instanceof Py::ExceptStmt }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the type handled by this exception handler.
|
* Gets the type handled by this exception handler.
|
||||||
* `ExceptionType` in `except ExceptionType as e:`
|
* `Py::ExceptionType` in `except Py::ExceptionType as e:`
|
||||||
*/
|
*/
|
||||||
ControlFlowNode getType() {
|
ControlFlowNode getType() {
|
||||||
exists(ExceptStmt ex |
|
exists(Py::ExceptStmt ex |
|
||||||
this.getBasicBlock().dominates(result.getBasicBlock()) and
|
this.getBasicBlock().dominates(result.getBasicBlock()) and
|
||||||
ex = this.getNode() and
|
ex = this.getNode() and
|
||||||
result = ex.getType().getAFlowNode()
|
result.getNode() = ex.getType()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the name assigned to the handled exception, if any.
|
* Gets the name assigned to the handled exception, if any.
|
||||||
* `e` in `except ExceptionType as e:`
|
* `e` in `except Py::ExceptionType as e:`
|
||||||
*/
|
*/
|
||||||
ControlFlowNode getName() {
|
ControlFlowNode getName() {
|
||||||
exists(ExceptStmt ex |
|
exists(Py::ExceptStmt ex |
|
||||||
this.getBasicBlock().dominates(result.getBasicBlock()) and
|
this.getBasicBlock().dominates(result.getBasicBlock()) and
|
||||||
ex = this.getNode() and
|
ex = this.getNode() and
|
||||||
result = ex.getName().getAFlowNode()
|
result.getNode() = ex.getName()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The ControlFlowNode for an 'except*' statement. */
|
/** The ControlFlowNode for an 'except*' statement. */
|
||||||
class ExceptGroupFlowNode extends ControlFlowNode {
|
class ExceptGroupFlowNode extends ControlFlowNode {
|
||||||
ExceptGroupFlowNode() { this.getNode() instanceof ExceptGroupStmt }
|
ExceptGroupFlowNode() { this.getNode() instanceof Py::ExceptGroupStmt }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the type handled by this exception handler.
|
* Gets the type handled by this exception handler.
|
||||||
* `ExceptionType` in `except* ExceptionType as e:`
|
* `Py::ExceptionType` in `except* Py::ExceptionType as e:`
|
||||||
*/
|
*/
|
||||||
ControlFlowNode getType() {
|
ControlFlowNode getType() {
|
||||||
this.getBasicBlock().dominates(result.getBasicBlock()) and
|
this.getBasicBlock().dominates(result.getBasicBlock()) and
|
||||||
result = this.getNode().(ExceptGroupStmt).getType().getAFlowNode()
|
result.getNode() = this.getNode().(Py::ExceptGroupStmt).getType()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the name assigned to the handled exception, if any.
|
* Gets the name assigned to the handled exception, if any.
|
||||||
* `e` in `except* ExceptionType as e:`
|
* `e` in `except* Py::ExceptionType as e:`
|
||||||
*/
|
*/
|
||||||
ControlFlowNode getName() {
|
ControlFlowNode getName() {
|
||||||
this.getBasicBlock().dominates(result.getBasicBlock()) and
|
this.getBasicBlock().dominates(result.getBasicBlock()) and
|
||||||
result = this.getNode().(ExceptGroupStmt).getName().getAFlowNode()
|
result.getNode() = this.getNode().(Py::ExceptGroupStmt).getName()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private module Scopes {
|
private module Scopes {
|
||||||
private predicate fast_local(NameNode n) {
|
private predicate fast_local(NameNode n) {
|
||||||
exists(FastLocalVariable v |
|
exists(Py::FastLocalVariable v |
|
||||||
n.uses(v) and
|
n.uses(v) and
|
||||||
v.getScope() = n.getScope()
|
v.getScope() = n.getScope()
|
||||||
)
|
)
|
||||||
@@ -952,15 +956,15 @@ private module Scopes {
|
|||||||
predicate local(NameNode n) {
|
predicate local(NameNode n) {
|
||||||
fast_local(n)
|
fast_local(n)
|
||||||
or
|
or
|
||||||
exists(SsaVariable var |
|
exists(Py::SsaVariable var |
|
||||||
var.getAUse() = n and
|
var.getAUse() = n and
|
||||||
n.getScope() instanceof Class and
|
n.getScope() instanceof Py::Class and
|
||||||
exists(var.getDefinition())
|
exists(var.getDefinition())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
predicate non_local(NameNode n) {
|
predicate non_local(NameNode n) {
|
||||||
exists(FastLocalVariable flv |
|
exists(Py::FastLocalVariable flv |
|
||||||
flv.getALoad() = n.getNode() and
|
flv.getALoad() = n.getNode() and
|
||||||
not flv.getScope() = n.getScope()
|
not flv.getScope() = n.getScope()
|
||||||
)
|
)
|
||||||
@@ -968,20 +972,20 @@ private module Scopes {
|
|||||||
|
|
||||||
// magic is fine, but we get questionable join-ordering of it
|
// magic is fine, but we get questionable join-ordering of it
|
||||||
pragma[nomagic]
|
pragma[nomagic]
|
||||||
predicate use_of_global_variable(NameNode n, Module scope, string name) {
|
predicate use_of_global_variable(NameNode n, Py::Module scope, string name) {
|
||||||
n.isLoad() and
|
n.isLoad() and
|
||||||
not non_local(n) and
|
not non_local(n) and
|
||||||
not exists(SsaVariable var | var.getAUse() = n |
|
not exists(Py::SsaVariable var | var.getAUse() = n |
|
||||||
var.getVariable() instanceof FastLocalVariable
|
var.getVariable() instanceof Py::FastLocalVariable
|
||||||
or
|
or
|
||||||
n.getScope() instanceof Class and
|
n.getScope() instanceof Py::Class and
|
||||||
not maybe_undefined(var)
|
not maybe_undefined(var)
|
||||||
) and
|
) and
|
||||||
name = n.getId() and
|
name = n.getId() and
|
||||||
scope = n.getEnclosingModule()
|
scope = n.getEnclosingModule()
|
||||||
}
|
}
|
||||||
|
|
||||||
private predicate maybe_undefined(SsaVariable var) {
|
private predicate maybe_undefined(Py::SsaVariable var) {
|
||||||
not exists(var.getDefinition()) and not py_ssa_phi(var, _)
|
not exists(var.getDefinition()) and not py_ssa_phi(var, _)
|
||||||
or
|
or
|
||||||
var.getDefinition().isDelete()
|
var.getDefinition().isDelete()
|
||||||
@@ -1058,13 +1062,13 @@ class BasicBlock extends @py_flow_node {
|
|||||||
private predicate oneNodeBlock() { this.firstNode() = this.getLastNode() }
|
private predicate oneNodeBlock() { this.firstNode() = this.getLastNode() }
|
||||||
|
|
||||||
private predicate startLocationInfo(string file, int line, int col) {
|
private predicate startLocationInfo(string file, int line, int col) {
|
||||||
if this.firstNode().getNode() instanceof Scope
|
if this.firstNode().getNode() instanceof Py::Scope
|
||||||
then this.firstNode().getASuccessor().getLocation().hasLocationInfo(file, line, col, _, _)
|
then this.firstNode().getASuccessor().getLocation().hasLocationInfo(file, line, col, _, _)
|
||||||
else this.firstNode().getLocation().hasLocationInfo(file, line, col, _, _)
|
else this.firstNode().getLocation().hasLocationInfo(file, line, col, _, _)
|
||||||
}
|
}
|
||||||
|
|
||||||
private predicate endLocationInfo(int endl, int endc) {
|
private predicate endLocationInfo(int endl, int endc) {
|
||||||
if this.getLastNode().getNode() instanceof Scope and not this.oneNodeBlock()
|
if this.getLastNode().getNode() instanceof Py::Scope and not this.oneNodeBlock()
|
||||||
then this.getLastNode().getAPredecessor().getLocation().hasLocationInfo(_, _, _, endl, endc)
|
then this.getLastNode().getAPredecessor().getLocation().hasLocationInfo(_, _, _, endl, endc)
|
||||||
else this.getLastNode().getLocation().hasLocationInfo(_, _, _, endl, endc)
|
else this.getLastNode().getLocation().hasLocationInfo(_, _, _, endl, endc)
|
||||||
}
|
}
|
||||||
@@ -1081,7 +1085,7 @@ class BasicBlock extends @py_flow_node {
|
|||||||
|
|
||||||
/** Whether flow from this basic block reaches a normal exit from its scope */
|
/** Whether flow from this basic block reaches a normal exit from its scope */
|
||||||
predicate reachesExit() {
|
predicate reachesExit() {
|
||||||
exists(Scope s | s.getANormalExit().getBasicBlock() = this)
|
exists(Py::Scope s | s.getANormalExit().getBasicBlock() = this)
|
||||||
or
|
or
|
||||||
this.getASuccessor().reachesExit()
|
this.getASuccessor().reachesExit()
|
||||||
}
|
}
|
||||||
@@ -1122,7 +1126,7 @@ class BasicBlock extends @py_flow_node {
|
|||||||
|
|
||||||
/** Gets the scope of this block */
|
/** Gets the scope of this block */
|
||||||
pragma[nomagic]
|
pragma[nomagic]
|
||||||
Scope getScope() {
|
Py::Scope getScope() {
|
||||||
exists(ControlFlowNode n | n.getBasicBlock() = this |
|
exists(ControlFlowNode n | n.getBasicBlock() = this |
|
||||||
/* Take care not to use an entry or exit node as that node's scope will be the outer scope */
|
/* Take care not to use an entry or exit node as that node's scope will be the outer scope */
|
||||||
not py_scope_flow(n, _, -1) and
|
not py_scope_flow(n, _, -1) and
|
||||||
@@ -1145,17 +1149,17 @@ class BasicBlock extends @py_flow_node {
|
|||||||
predicate reaches(BasicBlock other) { this = other or this.strictlyReaches(other) }
|
predicate reaches(BasicBlock other) { this = other or this.strictlyReaches(other) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the `ConditionBlock`, if any, that controls this block and
|
* Gets the `Py::ConditionBlock`, if any, that controls this block and
|
||||||
* does not control any other `ConditionBlock`s that control this block.
|
* does not control any other `Py::ConditionBlock`s that control this block.
|
||||||
* That is the `ConditionBlock` that is closest dominator.
|
* That is the `Py::ConditionBlock` that is closest dominator.
|
||||||
*/
|
*/
|
||||||
ConditionBlock getImmediatelyControllingBlock() {
|
Py::ConditionBlock getImmediatelyControllingBlock() {
|
||||||
result = this.nonControllingImmediateDominator*().getImmediateDominator()
|
result = this.nonControllingImmediateDominator*().getImmediateDominator()
|
||||||
}
|
}
|
||||||
|
|
||||||
private BasicBlock nonControllingImmediateDominator() {
|
private BasicBlock nonControllingImmediateDominator() {
|
||||||
result = this.getImmediateDominator() and
|
result = this.getImmediateDominator() and
|
||||||
not result.(ConditionBlock).controls(this, _)
|
not result.(Py::ConditionBlock).controls(this, _)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1175,7 +1179,7 @@ private class ControlFlowNodeAlias = ControlFlowNode;
|
|||||||
|
|
||||||
final private class FinalBasicBlock = BasicBlock;
|
final private class FinalBasicBlock = BasicBlock;
|
||||||
|
|
||||||
module Cfg implements BB::CfgSig<Location> {
|
module Cfg implements BB::CfgSig<Py::Location> {
|
||||||
private import codeql.controlflow.SuccessorType
|
private import codeql.controlflow.SuccessorType
|
||||||
|
|
||||||
class ControlFlowNode = ControlFlowNodeAlias;
|
class ControlFlowNode = ControlFlowNodeAlias;
|
||||||
@@ -1186,7 +1190,7 @@ module Cfg implements BB::CfgSig<Location> {
|
|||||||
// Using the location of the first node is simple
|
// Using the location of the first node is simple
|
||||||
// and we just need a way to identify the basic block
|
// and we just need a way to identify the basic block
|
||||||
// during debugging, so this will be serviceable.
|
// during debugging, so this will be serviceable.
|
||||||
Location getLocation() { result = super.getNode(0).getLocation() }
|
Py::Location getLocation() { result = super.getNode(0).getLocation() }
|
||||||
|
|
||||||
int length() { result = count(int i | exists(this.getNode(i))) }
|
int length() { result = count(int i | exists(this.getNode(i))) }
|
||||||
|
|
||||||
|
|||||||
@@ -153,8 +153,16 @@ class Function extends Function_, Scope, AstNode {
|
|||||||
|
|
||||||
override predicate contains(AstNode inner) { Scope.super.contains(inner) }
|
override predicate contains(AstNode inner) { Scope.super.contains(inner) }
|
||||||
|
|
||||||
/** Gets a control flow node for a return value of this function */
|
/**
|
||||||
ControlFlowNode getAReturnValueFlowNode() {
|
* DEPRECATED: bind a `Return` node explicitly instead, e.g.
|
||||||
|
* `exists(Return ret | ret.getScope() = this and n.getNode() = ret.getValue())`.
|
||||||
|
* This API is being phased out together with `AstNode.getAFlowNode()` to
|
||||||
|
* untangle the AST and CFG hierarchies in preparation for migrating the
|
||||||
|
* dataflow library off the legacy CFG.
|
||||||
|
*
|
||||||
|
* Gets a control flow node for a return value of this function.
|
||||||
|
*/
|
||||||
|
deprecated ControlFlowNode getAReturnValueFlowNode() {
|
||||||
exists(Return ret |
|
exists(Return ret |
|
||||||
ret.getScope() = this and
|
ret.getScope() = this and
|
||||||
ret.getValue() = result.getNode()
|
ret.getValue() = result.getNode()
|
||||||
|
|||||||
@@ -162,8 +162,6 @@ class ImportMember extends ImportMember_ {
|
|||||||
string getImportedModuleName() {
|
string getImportedModuleName() {
|
||||||
result = this.getModule().(ImportExpr).getImportedModuleName() + "." + this.getName()
|
result = this.getModule().(ImportExpr).getImportedModuleName() + "." + this.getName()
|
||||||
}
|
}
|
||||||
|
|
||||||
override ImportMemberNode getAFlowNode() { result = super.getAFlowNode() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An import statement */
|
/** An import statement */
|
||||||
|
|||||||
@@ -46,20 +46,23 @@ class SelfAttributeRead extends SelfAttribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
predicate guardedByHasattr() {
|
predicate guardedByHasattr() {
|
||||||
exists(Variable var, ControlFlowNode n |
|
exists(Variable var, ControlFlowNode n, ControlFlowNode this_, ControlFlowNode obj_ |
|
||||||
var.getAUse() = this.getObject().getAFlowNode() and
|
this_.getNode() = this and obj_.getNode() = this.getObject()
|
||||||
|
|
|
||||||
|
var.getAUse() = obj_ and
|
||||||
hasattr(n, var.getAUse(), this.getName()) and
|
hasattr(n, var.getAUse(), this.getName()) and
|
||||||
n.strictlyDominates(this.getAFlowNode())
|
n.strictlyDominates(this_)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pragma[noinline]
|
pragma[noinline]
|
||||||
predicate locallyDefined() {
|
predicate locallyDefined() {
|
||||||
exists(SelfAttributeStore store |
|
exists(SelfAttributeStore store, ControlFlowNode store_, ControlFlowNode this_ |
|
||||||
this.getName() = store.getName() and
|
store_.getNode() = store and this_.getNode() = this
|
||||||
this.getScope() = store.getScope()
|
|
||||||
|
|
|
|
||||||
store.getAFlowNode().strictlyDominates(this.getAFlowNode())
|
this.getName() = store.getName() and
|
||||||
|
this.getScope() = store.getScope() and
|
||||||
|
store_.strictlyDominates(this_)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1600
python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll
Normal file
1600
python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll
Normal file
File diff suppressed because it is too large
Load Diff
1163
python/ql/lib/semmle/python/controlflow/internal/Cfg.qll
Normal file
1163
python/ql/lib/semmle/python/controlflow/internal/Cfg.qll
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,24 +5,30 @@ private import semmle.python.dataflow.new.DataFlow
|
|||||||
|
|
||||||
private predicate constCompare(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) {
|
private predicate constCompare(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) {
|
||||||
exists(CompareNode cn | cn = g |
|
exists(CompareNode cn | cn = g |
|
||||||
exists(ImmutableLiteral const, Cmpop op |
|
exists(ImmutableLiteral const, Cmpop op, ControlFlowNode c |
|
||||||
|
c.getNode() = const and
|
||||||
|
(
|
||||||
op = any(Eq eq) and branch = true
|
op = any(Eq eq) and branch = true
|
||||||
or
|
or
|
||||||
op = any(NotEq ne) and branch = false
|
op = any(NotEq ne) and branch = false
|
||||||
|
)
|
||||||
|
|
|
|
||||||
cn.operands(const.getAFlowNode(), op, node)
|
cn.operands(c, op, node)
|
||||||
or
|
or
|
||||||
cn.operands(node, op, const.getAFlowNode())
|
cn.operands(node, op, c)
|
||||||
)
|
)
|
||||||
or
|
or
|
||||||
exists(NameConstant const, Cmpop op |
|
exists(NameConstant const, Cmpop op, ControlFlowNode c |
|
||||||
|
c.getNode() = const and
|
||||||
|
(
|
||||||
op = any(Is is_) and branch = true
|
op = any(Is is_) and branch = true
|
||||||
or
|
or
|
||||||
op = any(IsNot isn) and branch = false
|
op = any(IsNot isn) and branch = false
|
||||||
|
)
|
||||||
|
|
|
|
||||||
cn.operands(const.getAFlowNode(), op, node)
|
cn.operands(c, op, node)
|
||||||
or
|
or
|
||||||
cn.operands(node, op, const.getAFlowNode())
|
cn.operands(node, op, c)
|
||||||
)
|
)
|
||||||
or
|
or
|
||||||
exists(IterableNode const_iterable, Cmpop op |
|
exists(IterableNode const_iterable, Cmpop op |
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ private class ClassDefinitionAsAttrWrite extends AttrWrite, CfgNode {
|
|||||||
|
|
||||||
override Node getValue() { result.asCfgNode() = node.getValue() }
|
override Node getValue() { result.asCfgNode() = node.getValue() }
|
||||||
|
|
||||||
override Node getObject() { result.asCfgNode() = cls.getAFlowNode() }
|
override Node getObject() { result.asCfgNode().getNode() = cls }
|
||||||
|
|
||||||
override ExprNode getAttributeNameExpr() { none() }
|
override ExprNode getAttributeNameExpr() { none() }
|
||||||
|
|
||||||
|
|||||||
@@ -1913,8 +1913,8 @@ abstract class ReturnNode extends Node {
|
|||||||
class ExtractedReturnNode extends ReturnNode, CfgNode {
|
class ExtractedReturnNode extends ReturnNode, CfgNode {
|
||||||
// See `TaintTrackingImplementation::returnFlowStep`
|
// See `TaintTrackingImplementation::returnFlowStep`
|
||||||
ExtractedReturnNode() {
|
ExtractedReturnNode() {
|
||||||
node = any(Return ret).getValue().getAFlowNode() or
|
node.getNode() = any(Return ret).getValue() or
|
||||||
node = any(Yield yield).getAFlowNode()
|
node.getNode() = any(Yield yield)
|
||||||
}
|
}
|
||||||
|
|
||||||
override ReturnKind getKind() { any() }
|
override ReturnKind getKind() { any() }
|
||||||
@@ -1932,7 +1932,7 @@ class ExtractedReturnNode extends ReturnNode, CfgNode {
|
|||||||
class YieldNodeInContextManagerFunction extends ReturnNode, CfgNode {
|
class YieldNodeInContextManagerFunction extends ReturnNode, CfgNode {
|
||||||
YieldNodeInContextManagerFunction() {
|
YieldNodeInContextManagerFunction() {
|
||||||
hasContextmanagerDecorator(node.getScope()) and
|
hasContextmanagerDecorator(node.getScope()) and
|
||||||
node = any(Yield yield).getValue().getAFlowNode()
|
node.getNode() = any(Yield yield).getValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
override ReturnKind getKind() { any() }
|
override ReturnKind getKind() { any() }
|
||||||
|
|||||||
@@ -185,8 +185,8 @@ private predicate synthDictSplatArgumentNodeStoreStep(
|
|||||||
*/
|
*/
|
||||||
predicate yieldStoreStep(Node nodeFrom, Content c, Node nodeTo) {
|
predicate yieldStoreStep(Node nodeFrom, Content c, Node nodeTo) {
|
||||||
exists(Yield yield |
|
exists(Yield yield |
|
||||||
nodeTo.asCfgNode() = yield.getAFlowNode() and
|
nodeTo.asCfgNode().getNode() = yield and
|
||||||
nodeFrom.asCfgNode() = yield.getValue().getAFlowNode() and
|
nodeFrom.asCfgNode().getNode() = yield.getValue() and
|
||||||
// TODO: Consider if this will also need to transfer dictionary content
|
// TODO: Consider if this will also need to transfer dictionary content
|
||||||
// once dictionary comprehensions are supported.
|
// once dictionary comprehensions are supported.
|
||||||
c instanceof ListElementContent
|
c instanceof ListElementContent
|
||||||
@@ -753,7 +753,7 @@ predicate jumpStepNotSharedWithTypeTracker(Node nodeFrom, Node nodeTo) {
|
|||||||
* As of 2024-04-02 the type-tracking library only supports precise content, so there is
|
* As of 2024-04-02 the type-tracking library only supports precise content, so there is
|
||||||
* no reason to include steps for list content right now.
|
* no reason to include steps for list content right now.
|
||||||
*/
|
*/
|
||||||
predicate storeStepCommon(Node nodeFrom, Content c, Node nodeTo) {
|
predicate storeStepCommon(Node nodeFrom, ContentSet c, Node nodeTo) {
|
||||||
tupleStoreStep(nodeFrom, c, nodeTo)
|
tupleStoreStep(nodeFrom, c, nodeTo)
|
||||||
or
|
or
|
||||||
dictStoreStep(nodeFrom, c, nodeTo)
|
dictStoreStep(nodeFrom, c, nodeTo)
|
||||||
@@ -767,8 +767,7 @@ predicate storeStepCommon(Node nodeFrom, Content c, Node nodeTo) {
|
|||||||
* Holds if data can flow from `nodeFrom` to `nodeTo` via an assignment to
|
* Holds if data can flow from `nodeFrom` to `nodeTo` via an assignment to
|
||||||
* content `c`.
|
* content `c`.
|
||||||
*/
|
*/
|
||||||
predicate storeStep(Node nodeFrom, ContentSet cs, Node nodeTo) {
|
predicate storeStep(Node nodeFrom, ContentSet c, Node nodeTo) {
|
||||||
exists(Content c | cs = singleton(c) |
|
|
||||||
storeStepCommon(nodeFrom, c, nodeTo)
|
storeStepCommon(nodeFrom, c, nodeTo)
|
||||||
or
|
or
|
||||||
listStoreStep(nodeFrom, c, nodeTo)
|
listStoreStep(nodeFrom, c, nodeTo)
|
||||||
@@ -781,6 +780,9 @@ predicate storeStep(Node nodeFrom, ContentSet cs, Node nodeTo) {
|
|||||||
or
|
or
|
||||||
any(Orm::AdditionalOrmSteps es).storeStep(nodeFrom, c, nodeTo)
|
any(Orm::AdditionalOrmSteps es).storeStep(nodeFrom, c, nodeTo)
|
||||||
or
|
or
|
||||||
|
FlowSummaryImpl::Private::Steps::summaryStoreStep(nodeFrom.(FlowSummaryNode).getSummaryNode(), c,
|
||||||
|
nodeTo.(FlowSummaryNode).getSummaryNode())
|
||||||
|
or
|
||||||
synthStarArgsElementParameterNodeStoreStep(nodeFrom, c, nodeTo)
|
synthStarArgsElementParameterNodeStoreStep(nodeFrom, c, nodeTo)
|
||||||
or
|
or
|
||||||
synthDictSplatArgumentNodeStoreStep(nodeFrom, c, nodeTo)
|
synthDictSplatArgumentNodeStoreStep(nodeFrom, c, nodeTo)
|
||||||
@@ -788,10 +790,6 @@ predicate storeStep(Node nodeFrom, ContentSet cs, Node nodeTo) {
|
|||||||
yieldStoreStep(nodeFrom, c, nodeTo)
|
yieldStoreStep(nodeFrom, c, nodeTo)
|
||||||
or
|
or
|
||||||
VariableCapture::storeStep(nodeFrom, c, nodeTo)
|
VariableCapture::storeStep(nodeFrom, c, nodeTo)
|
||||||
)
|
|
||||||
or
|
|
||||||
FlowSummaryImpl::Private::Steps::summaryStoreStep(nodeFrom.(FlowSummaryNode).getSummaryNode(), cs,
|
|
||||||
nodeTo.(FlowSummaryNode).getSummaryNode())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -987,7 +985,7 @@ predicate attributeStoreStep(Node nodeFrom, AttributeContent c, Node nodeTo) {
|
|||||||
/**
|
/**
|
||||||
* Subset of `readStep` that should be shared with type-tracking.
|
* Subset of `readStep` that should be shared with type-tracking.
|
||||||
*/
|
*/
|
||||||
predicate readStepCommon(Node nodeFrom, Content c, Node nodeTo) {
|
predicate readStepCommon(Node nodeFrom, ContentSet c, Node nodeTo) {
|
||||||
subscriptReadStep(nodeFrom, c, nodeTo)
|
subscriptReadStep(nodeFrom, c, nodeTo)
|
||||||
or
|
or
|
||||||
iterableUnpackingReadStep(nodeFrom, c, nodeTo)
|
iterableUnpackingReadStep(nodeFrom, c, nodeTo)
|
||||||
@@ -996,8 +994,7 @@ predicate readStepCommon(Node nodeFrom, Content c, Node nodeTo) {
|
|||||||
/**
|
/**
|
||||||
* Holds if data can flow from `nodeFrom` to `nodeTo` via a read of content `c`.
|
* Holds if data can flow from `nodeFrom` to `nodeTo` via a read of content `c`.
|
||||||
*/
|
*/
|
||||||
predicate readStep(Node nodeFrom, ContentSet cs, Node nodeTo) {
|
predicate readStep(Node nodeFrom, ContentSet c, Node nodeTo) {
|
||||||
exists(Content c | cs = singleton(c) |
|
|
||||||
readStepCommon(nodeFrom, c, nodeTo)
|
readStepCommon(nodeFrom, c, nodeTo)
|
||||||
or
|
or
|
||||||
matchReadStep(nodeFrom, c, nodeTo)
|
matchReadStep(nodeFrom, c, nodeTo)
|
||||||
@@ -1006,15 +1003,12 @@ predicate readStep(Node nodeFrom, ContentSet cs, Node nodeTo) {
|
|||||||
or
|
or
|
||||||
attributeReadStep(nodeFrom, c, nodeTo)
|
attributeReadStep(nodeFrom, c, nodeTo)
|
||||||
or
|
or
|
||||||
|
FlowSummaryImpl::Private::Steps::summaryReadStep(nodeFrom.(FlowSummaryNode).getSummaryNode(), c,
|
||||||
|
nodeTo.(FlowSummaryNode).getSummaryNode())
|
||||||
|
or
|
||||||
synthDictSplatParameterNodeReadStep(nodeFrom, c, nodeTo)
|
synthDictSplatParameterNodeReadStep(nodeFrom, c, nodeTo)
|
||||||
or
|
or
|
||||||
VariableCapture::readStep(nodeFrom, c, nodeTo)
|
VariableCapture::readStep(nodeFrom, c, nodeTo)
|
||||||
)
|
|
||||||
or
|
|
||||||
FlowSummaryImpl::Private::Steps::summaryReadStep(nodeFrom.(FlowSummaryNode).getSummaryNode(), cs,
|
|
||||||
nodeTo.(FlowSummaryNode).getSummaryNode())
|
|
||||||
or
|
|
||||||
Conversions::readStep(nodeFrom, cs, nodeTo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Data flows from a sequence to a subscript of the sequence. */
|
/** Data flows from a sequence to a subscript of the sequence. */
|
||||||
@@ -1070,68 +1064,23 @@ predicate attributeReadStep(Node nodeFrom, AttributeContent c, AttrRead nodeTo)
|
|||||||
nodeTo.accesses(nodeFrom, c.getAttribute())
|
nodeTo.accesses(nodeFrom, c.getAttribute())
|
||||||
}
|
}
|
||||||
|
|
||||||
module Conversions {
|
|
||||||
private import semmle.python.Concepts
|
|
||||||
|
|
||||||
predicate decoderReadStep(Node nodeFrom, ContentSet c, Node nodeTo) {
|
|
||||||
exists(Decoding decoding |
|
|
||||||
nodeFrom = decoding.getAnInput() and
|
|
||||||
nodeTo = decoding.getOutput()
|
|
||||||
) and
|
|
||||||
c.isAnyTupleOrDictionaryElement()
|
|
||||||
}
|
|
||||||
|
|
||||||
predicate encoderReadStep(Node nodeFrom, ContentSet c, Node nodeTo) {
|
|
||||||
exists(Encoding encoding |
|
|
||||||
nodeFrom = encoding.getAnInput() and
|
|
||||||
nodeTo = encoding.getOutput()
|
|
||||||
) and
|
|
||||||
c.isAnyTupleOrDictionaryElement()
|
|
||||||
}
|
|
||||||
|
|
||||||
predicate formatReadStep(Node nodeFrom, ContentSet c, Node nodeTo) {
|
|
||||||
// % formatting
|
|
||||||
exists(BinaryExprNode fmt | fmt = nodeTo.asCfgNode() |
|
|
||||||
fmt.getOp() instanceof Mod and
|
|
||||||
fmt.getRight() = nodeFrom.asCfgNode()
|
|
||||||
) and
|
|
||||||
c.isAnyTupleElement()
|
|
||||||
or
|
|
||||||
// format_map
|
|
||||||
// see https://docs.python.org/3/library/stdtypes.html#str.format_map
|
|
||||||
nodeTo.(MethodCallNode).calls(_, "format_map") and
|
|
||||||
nodeTo.(MethodCallNode).getArg(0) = nodeFrom and
|
|
||||||
c.isAnyDictionaryElement()
|
|
||||||
}
|
|
||||||
|
|
||||||
predicate readStep(Node nodeFrom, ContentSet c, Node nodeTo) {
|
|
||||||
decoderReadStep(nodeFrom, c, nodeTo)
|
|
||||||
or
|
|
||||||
encoderReadStep(nodeFrom, c, nodeTo)
|
|
||||||
or
|
|
||||||
formatReadStep(nodeFrom, c, nodeTo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds if values stored inside content `c` are cleared at node `n`. For example,
|
* Holds if values stored inside content `c` are cleared at node `n`. For example,
|
||||||
* any value stored inside `f` is cleared at the pre-update node associated with `x`
|
* any value stored inside `f` is cleared at the pre-update node associated with `x`
|
||||||
* in `x.f = newValue`.
|
* in `x.f = newValue`.
|
||||||
*/
|
*/
|
||||||
predicate clearsContent(Node n, ContentSet cs) {
|
predicate clearsContent(Node n, ContentSet c) {
|
||||||
exists(Content c | cs = singleton(c) |
|
|
||||||
matchClearStep(n, c)
|
matchClearStep(n, c)
|
||||||
or
|
or
|
||||||
attributeClearStep(n, c)
|
attributeClearStep(n, c)
|
||||||
or
|
or
|
||||||
dictClearStep(n, c)
|
dictClearStep(n, c)
|
||||||
or
|
or
|
||||||
|
FlowSummaryImpl::Private::Steps::summaryClearsContent(n.(FlowSummaryNode).getSummaryNode(), c)
|
||||||
|
or
|
||||||
dictSplatParameterNodeClearStep(n, c)
|
dictSplatParameterNodeClearStep(n, c)
|
||||||
or
|
or
|
||||||
VariableCapture::clearsContent(n, c)
|
VariableCapture::clearsContent(n, c)
|
||||||
)
|
|
||||||
or
|
|
||||||
FlowSummaryImpl::Private::Steps::summaryClearsContent(n.(FlowSummaryNode).getSummaryNode(), cs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1249,65 +1198,12 @@ predicate allowParameterReturnInSelf(ParameterNode p) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
bindingset[s]
|
|
||||||
private string getFirstChar(string s) {
|
|
||||||
result =
|
|
||||||
min(int i, string c |
|
|
||||||
c = s.charAt(i) and c != "_"
|
|
||||||
or
|
|
||||||
c = "" and i = s.length()
|
|
||||||
|
|
|
||||||
c order by i
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private string getAttributeContentFirstChar(AttributeContent ac) {
|
|
||||||
result = getFirstChar(ac.getAttribute())
|
|
||||||
}
|
|
||||||
|
|
||||||
private string getDictionaryElementContentKeyFirstChar(DictionaryElementContent dec) {
|
|
||||||
result = getFirstChar(dec.getKey())
|
|
||||||
}
|
|
||||||
|
|
||||||
private newtype TContentApprox =
|
|
||||||
TListElementContentApprox() or
|
|
||||||
TSetElementContentApprox() or
|
|
||||||
TTupleElementContentApprox() or
|
|
||||||
TDictionaryElementContentApprox(string first) {
|
|
||||||
first = "" // for `TDictionaryElementAnyContent`
|
|
||||||
or
|
|
||||||
first = getDictionaryElementContentKeyFirstChar(_)
|
|
||||||
} or
|
|
||||||
TAttributeContentApprox(string first) { first = getAttributeContentFirstChar(_) } or
|
|
||||||
TCapturedVariableContentApprox()
|
|
||||||
|
|
||||||
/** An approximated `Content`. */
|
/** An approximated `Content`. */
|
||||||
class ContentApprox extends TContentApprox {
|
class ContentApprox = Unit;
|
||||||
/** Gets a textual representation of this element. */
|
|
||||||
string toString() { result = "" }
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Gets an approximated value for content `c`. */
|
/** Gets an approximated value for content `c`. */
|
||||||
ContentApprox getContentApprox(Content c) {
|
pragma[inline]
|
||||||
c = TListElementContent() and
|
ContentApprox getContentApprox(Content c) { any() }
|
||||||
result = TListElementContentApprox()
|
|
||||||
or
|
|
||||||
c = TSetElementContent() and
|
|
||||||
result = TSetElementContentApprox()
|
|
||||||
or
|
|
||||||
c = TTupleElementContent(_) and
|
|
||||||
result = TTupleElementContentApprox()
|
|
||||||
or
|
|
||||||
result = TDictionaryElementContentApprox(getDictionaryElementContentKeyFirstChar(c))
|
|
||||||
or
|
|
||||||
c = TDictionaryElementAnyContent() and
|
|
||||||
result = TDictionaryElementContentApprox("")
|
|
||||||
or
|
|
||||||
result = TAttributeContentApprox(getAttributeContentFirstChar(c))
|
|
||||||
or
|
|
||||||
c = TCapturedVariableContent(_) and
|
|
||||||
result = TCapturedVariableContentApprox()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Helper for `.getEnclosingCallable`. */
|
/** Helper for `.getEnclosingCallable`. */
|
||||||
DataFlowCallable getCallableScope(Scope s) {
|
DataFlowCallable getCallableScope(Scope s) {
|
||||||
|
|||||||
@@ -485,7 +485,7 @@ class ModuleVariableNode extends Node, TModuleVariableNode {
|
|||||||
|
|
||||||
/** Gets a node that reads this variable, excluding reads that happen through `from ... import *`. */
|
/** Gets a node that reads this variable, excluding reads that happen through `from ... import *`. */
|
||||||
Node getALocalRead() {
|
Node getALocalRead() {
|
||||||
result.asCfgNode() = var.getALoad().getAFlowNode() and
|
result.asCfgNode().getNode() = var.getALoad() and
|
||||||
not result.getScope() = mod
|
not result.getScope() = mod
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -898,78 +898,19 @@ class CapturedVariableContent extends Content, TCapturedVariableContent {
|
|||||||
override string getMaDRepresentation() { none() }
|
override string getMaDRepresentation() { none() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* An entity that represents a set of `Content`s.
|
|
||||||
*
|
|
||||||
* Most `ContentSet`s are singletons (i.e. they consist of a single `Content`),
|
|
||||||
* but `AnyDictionaryElement` and `AnyTupleElement` act as wildcards on the
|
|
||||||
* read side: a read at such a `ContentSet` matches any specific dictionary
|
|
||||||
* key / tuple index store, as well as (for dictionaries) the
|
|
||||||
* "unknown-bucket" Content `DictionaryElementAnyContent`.
|
|
||||||
*
|
|
||||||
* Keeping these as wildcard `ContentSet`s (rather than enumerating one
|
|
||||||
* `ContentSet` per key/index) keeps the dataflow `readSetEx` relation small
|
|
||||||
* when implicit reads are used (e.g. at sinks via `defaultImplicitTaintRead`).
|
|
||||||
*/
|
|
||||||
private newtype TContentSet =
|
|
||||||
TSingletonContent(Content c) or
|
|
||||||
TAnyTupleElement() or
|
|
||||||
TAnyDictionaryElement() or
|
|
||||||
TAnyTupleOrDictionaryElement()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An entity that represents a set of `Content`s.
|
* An entity that represents a set of `Content`s.
|
||||||
*
|
*
|
||||||
* The set may be interpreted differently depending on whether it is
|
* The set may be interpreted differently depending on whether it is
|
||||||
* stored into (`getAStoreContent`) or read from (`getAReadContent`).
|
* stored into (`getAStoreContent`) or read from (`getAReadContent`).
|
||||||
*/
|
*/
|
||||||
class ContentSet extends TContentSet {
|
class ContentSet instanceof Content {
|
||||||
/** Holds if this content set is the singleton `{c}`. */
|
|
||||||
predicate isSingleton(Content c) { this = TSingletonContent(c) }
|
|
||||||
|
|
||||||
/** Holds if this content set is the wildcard for all tuple elements. */
|
|
||||||
predicate isAnyTupleElement() { this = TAnyTupleElement() }
|
|
||||||
|
|
||||||
/** Holds if this content set is the wildcard for all dictionary elements. */
|
|
||||||
predicate isAnyDictionaryElement() { this = TAnyDictionaryElement() }
|
|
||||||
|
|
||||||
/** Holds if this content set is the wildcard for all tuple elements or dictionary elements. */
|
|
||||||
predicate isAnyTupleOrDictionaryElement() { this = TAnyTupleOrDictionaryElement() }
|
|
||||||
|
|
||||||
/** Gets a content that may be stored into when storing into this set. */
|
/** Gets a content that may be stored into when storing into this set. */
|
||||||
Content getAStoreContent() { this = TSingletonContent(result) }
|
Content getAStoreContent() { result = this }
|
||||||
|
|
||||||
/** Gets a content that may be read from when reading from this set. */
|
/** Gets a content that may be read from when reading from this set. */
|
||||||
Content getAReadContent() {
|
Content getAReadContent() { result = this }
|
||||||
this = TSingletonContent(result)
|
|
||||||
or
|
|
||||||
// Wildcard expansion: a read at "any tuple element" matches a store at any
|
|
||||||
// specific tuple index. (Stores always target a specific index, so we don't
|
|
||||||
// need a `TupleElementAnyContent` Content kind here.)
|
|
||||||
this = TAnyTupleElement() and result instanceof TupleElementContent
|
|
||||||
or
|
|
||||||
this = TAnyDictionaryElement() and
|
|
||||||
(result instanceof DictionaryElementContent or result instanceof DictionaryElementAnyContent)
|
|
||||||
or
|
|
||||||
this = TAnyTupleOrDictionaryElement() and
|
|
||||||
(
|
|
||||||
result instanceof TupleElementContent or
|
|
||||||
result instanceof DictionaryElementContent or
|
|
||||||
result instanceof DictionaryElementAnyContent
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Gets a textual representation of this content set. */
|
/** Gets a textual representation of this content set. */
|
||||||
string toString() {
|
string toString() { result = super.toString() }
|
||||||
exists(Content c | this = TSingletonContent(c) | result = c.toString())
|
|
||||||
or
|
|
||||||
this = TAnyTupleElement() and result = "Any tuple element"
|
|
||||||
or
|
|
||||||
this = TAnyDictionaryElement() and result = "Any dictionary element"
|
|
||||||
or
|
|
||||||
this = TAnyTupleOrDictionaryElement() and result = "Any tuple or dictionary element"
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** Gets the singleton `ContentSet` wrapping the `Content` `c`. */
|
|
||||||
ContentSet singleton(Content c) { result = TSingletonContent(c) }
|
|
||||||
|
|||||||
@@ -66,29 +66,21 @@ module Input implements InputSig<Location, DataFlowImplSpecific::PythonDataFlow>
|
|||||||
}
|
}
|
||||||
|
|
||||||
string encodeContent(ContentSet cs, string arg) {
|
string encodeContent(ContentSet cs, string arg) {
|
||||||
exists(Content c | cs.isSingleton(c) |
|
cs = TListElementContent() and result = "ListElement" and arg = ""
|
||||||
c = TListElementContent() and result = "ListElement" and arg = ""
|
|
||||||
or
|
or
|
||||||
c = TSetElementContent() and result = "SetElement" and arg = ""
|
cs = TSetElementContent() and result = "SetElement" and arg = ""
|
||||||
or
|
or
|
||||||
exists(int index |
|
exists(int index |
|
||||||
c = TTupleElementContent(index) and result = "TupleElement" and arg = index.toString()
|
cs = TTupleElementContent(index) and result = "TupleElement" and arg = index.toString()
|
||||||
)
|
)
|
||||||
or
|
or
|
||||||
exists(string key |
|
exists(string key |
|
||||||
c = TDictionaryElementContent(key) and result = "DictionaryElement" and arg = key
|
cs = TDictionaryElementContent(key) and result = "DictionaryElement" and arg = key
|
||||||
)
|
)
|
||||||
or
|
or
|
||||||
c = TDictionaryElementAnyContent() and result = "DictionaryElementAny" and arg = ""
|
cs = TDictionaryElementAnyContent() and result = "DictionaryElementAny" and arg = ""
|
||||||
or
|
or
|
||||||
exists(string attr | c = TAttributeContent(attr) and result = "Attribute" and arg = attr)
|
exists(string attr | cs = TAttributeContent(attr) and result = "Attribute" and arg = attr)
|
||||||
)
|
|
||||||
or
|
|
||||||
cs.isAnyTupleElement() and result = "AnyTupleElement" and arg = ""
|
|
||||||
or
|
|
||||||
cs.isAnyDictionaryElement() and result = "AnyDictionaryElement" and arg = ""
|
|
||||||
or
|
|
||||||
cs.isAnyTupleOrDictionaryElement() and result = "AnyTupleOrDictionaryElement" and arg = ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bindingset[token]
|
bindingset[token]
|
||||||
@@ -147,29 +139,27 @@ module Private {
|
|||||||
predicate withContent = SC::withContent/1;
|
predicate withContent = SC::withContent/1;
|
||||||
|
|
||||||
/** Gets a summary component that represents a list element. */
|
/** Gets a summary component that represents a list element. */
|
||||||
SummaryComponent listElement() { result = content(singleton(any(ListElementContent c))) }
|
SummaryComponent listElement() { result = content(any(ListElementContent c)) }
|
||||||
|
|
||||||
/** Gets a summary component that represents a set element. */
|
/** Gets a summary component that represents a set element. */
|
||||||
SummaryComponent setElement() { result = content(singleton(any(SetElementContent c))) }
|
SummaryComponent setElement() { result = content(any(SetElementContent c)) }
|
||||||
|
|
||||||
/** Gets a summary component that represents a tuple element. */
|
/** Gets a summary component that represents a tuple element. */
|
||||||
SummaryComponent tupleElement(int index) {
|
SummaryComponent tupleElement(int index) {
|
||||||
exists(TupleElementContent c | c.getIndex() = index and result = content(singleton(c)))
|
exists(TupleElementContent c | c.getIndex() = index and result = content(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gets a summary component that represents a dictionary element. */
|
/** Gets a summary component that represents a dictionary element. */
|
||||||
SummaryComponent dictionaryElement(string key) {
|
SummaryComponent dictionaryElement(string key) {
|
||||||
exists(DictionaryElementContent c | c.getKey() = key and result = content(singleton(c)))
|
exists(DictionaryElementContent c | c.getKey() = key and result = content(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gets a summary component that represents a dictionary element at any key. */
|
/** Gets a summary component that represents a dictionary element at any key. */
|
||||||
SummaryComponent dictionaryElementAny() {
|
SummaryComponent dictionaryElementAny() { result = content(any(DictionaryElementAnyContent c)) }
|
||||||
result = content(singleton(any(DictionaryElementAnyContent c)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Gets a summary component that represents an attribute element. */
|
/** Gets a summary component that represents an attribute element. */
|
||||||
SummaryComponent attribute(string attr) {
|
SummaryComponent attribute(string attr) {
|
||||||
exists(AttributeContent c | c.getAttribute() = attr and result = content(singleton(c)))
|
exists(AttributeContent c | c.getAttribute() = attr and result = content(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gets a summary component that represents the return value of a call. */
|
/** Gets a summary component that represents the return value of a call. */
|
||||||
|
|||||||
@@ -9,7 +9,19 @@ private import semmle.python.dataflow.new.DataFlow
|
|||||||
private import semmle.python.dataflow.new.internal.ImportStar
|
private import semmle.python.dataflow.new.internal.ImportStar
|
||||||
private import semmle.python.dataflow.new.TypeTracking
|
private import semmle.python.dataflow.new.TypeTracking
|
||||||
private import semmle.python.dataflow.new.internal.DataFlowPrivate
|
private import semmle.python.dataflow.new.internal.DataFlowPrivate
|
||||||
private import semmle.python.essa.SsaDefinitions
|
|
||||||
|
/**
|
||||||
|
* Holds if `init` is a package's `__init__.py` and `var` is a global variable in
|
||||||
|
* `init` whose name matches a submodule of the package.
|
||||||
|
*
|
||||||
|
* Inlined from `SsaSource::init_module_submodule_defn` to avoid pulling
|
||||||
|
* `semmle.python.essa.SsaDefinitions` into the new dataflow stack.
|
||||||
|
*/
|
||||||
|
private predicate initModuleSubmoduleDefn(GlobalVariable var, Module init) {
|
||||||
|
init.isPackageInit() and
|
||||||
|
exists(init.getPackage().getSubModule(var.getId())) and
|
||||||
|
var.getScope() = init
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Python modules and the way imports are resolved are... complicated. Here's a crash course in how
|
* Python modules and the way imports are resolved are... complicated. Here's a crash course in how
|
||||||
@@ -326,7 +338,7 @@ module ImportResolution {
|
|||||||
// imported yet.
|
// imported yet.
|
||||||
exists(string submodule, Module package, EssaVariable var |
|
exists(string submodule, Module package, EssaVariable var |
|
||||||
submodule = var.getName() and
|
submodule = var.getName() and
|
||||||
SsaSource::init_module_submodule_defn(var.getSourceVariable(), package.getEntryNode()) and
|
initModuleSubmoduleDefn(var.getSourceVariable(), package) and
|
||||||
m = getModuleFromName(package.getPackageName() + "." + submodule) and
|
m = getModuleFromName(package.getPackageName() + "." + submodule) and
|
||||||
result.asCfgNode() = var.getDefinition().(EssaNodeDefinition).getDefiningNode()
|
result.asCfgNode() = var.getDefinition().(EssaNodeDefinition).getDefiningNode()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,34 +11,12 @@ private import semmle.python.ApiGraphs
|
|||||||
*/
|
*/
|
||||||
predicate defaultTaintSanitizer(DataFlow::Node node) { none() }
|
predicate defaultTaintSanitizer(DataFlow::Node node) { none() }
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds if default taint tracking should read content `contentSet` implicitly and
|
|
||||||
* propagate taint from a container to reads of that content.
|
|
||||||
*/
|
|
||||||
private predicate defaultTaintReadContent(DataFlow::ContentSet contentSet) {
|
|
||||||
// Tuple and dictionary content is precise, so use wildcard content sets to avoid
|
|
||||||
// blowing up the size of `Stage1::readSetEx` (otherwise this predicate would
|
|
||||||
// expand to one row per (node, distinct key or index) and the framework's
|
|
||||||
// read-set relation grows quadratically). `ContentSet.getAReadContent` expands
|
|
||||||
// these wildcards back to the specific contents when matching against stores.
|
|
||||||
contentSet.isAnyTupleOrDictionaryElement()
|
|
||||||
or
|
|
||||||
// List and set element content is already imprecise, so no wildcard expansion is
|
|
||||||
// needed.
|
|
||||||
contentSet.getAStoreContent() instanceof DataFlow::ListElementContent
|
|
||||||
or
|
|
||||||
contentSet.getAStoreContent() instanceof DataFlow::SetElementContent
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds if default `TaintTracking::Configuration`s should allow implicit reads
|
* Holds if default `TaintTracking::Configuration`s should allow implicit reads
|
||||||
* of `c` at sinks and inputs to additional taint steps.
|
* of `c` at sinks and inputs to additional taint steps.
|
||||||
*/
|
*/
|
||||||
bindingset[node]
|
bindingset[node]
|
||||||
predicate defaultImplicitTaintRead(DataFlow::Node node, DataFlow::ContentSet c) {
|
predicate defaultImplicitTaintRead(DataFlow::Node node, DataFlow::ContentSet c) { none() }
|
||||||
exists(node) and
|
|
||||||
defaultTaintReadContent(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
private module Cached {
|
private module Cached {
|
||||||
/**
|
/**
|
||||||
@@ -150,6 +128,11 @@ predicate stringManipulation(DataFlow::CfgNode nodeFrom, DataFlow::CfgNode nodeT
|
|||||||
nodeFrom.getNode() = object and
|
nodeFrom.getNode() = object and
|
||||||
method_name in ["partition", "rpartition", "rsplit", "split", "splitlines"]
|
method_name in ["partition", "rpartition", "rsplit", "split", "splitlines"]
|
||||||
or
|
or
|
||||||
|
// Iterable[str] -> str
|
||||||
|
// TODO: check if these should be handled differently in regards to content
|
||||||
|
method_name = "join" and
|
||||||
|
nodeFrom.getNode() = call.getArg(0)
|
||||||
|
or
|
||||||
// Mapping[str, Any] -> str
|
// Mapping[str, Any] -> str
|
||||||
method_name = "format_map" and
|
method_name = "format_map" and
|
||||||
nodeFrom.getNode() = call.getArg(0)
|
nodeFrom.getNode() = call.getArg(0)
|
||||||
@@ -178,21 +161,32 @@ predicate stringManipulation(DataFlow::CfgNode nodeFrom, DataFlow::CfgNode nodeT
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds if taint can flow from `nodeFrom` to `nodeTo` with a step related to reading
|
* Holds if taint can flow from `nodeFrom` to `nodeTo` with a step related to containers
|
||||||
* content from containers (lists/sets/dictionaries/tuples): subscripts, iteration,
|
* (lists/sets/dictionaries): literals, constructor invocation, methods. Note that this
|
||||||
* constructor invocation, methods.
|
* is currently very imprecise, as an example, since we model `dict.get`, we treat any
|
||||||
|
* `<tainted object>.get(<arg>)` will be tainted, whether it's true or not.
|
||||||
*/
|
*/
|
||||||
predicate containerStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
predicate containerStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||||
exists(DataFlow::ContentSet contentSet |
|
// construction by literal
|
||||||
DataFlowPrivate::readStep(nodeFrom, contentSet, nodeTo) and
|
//
|
||||||
exists(DataFlow::Content c | c = contentSet.getAReadContent() |
|
// TODO: once we have proper flow-summary modeling, we might not need this step any
|
||||||
c instanceof DataFlow::TupleElementContent or
|
// longer -- but there needs to be a matching read-step for the store-step, and we
|
||||||
c instanceof DataFlow::DictionaryElementContent or
|
// don't provide that right now.
|
||||||
c instanceof DataFlow::DictionaryElementAnyContent or
|
DataFlowPrivate::listStoreStep(nodeFrom, _, nodeTo)
|
||||||
c instanceof DataFlow::ListElementContent or
|
or
|
||||||
c instanceof DataFlow::SetElementContent
|
DataFlowPrivate::setStoreStep(nodeFrom, _, nodeTo)
|
||||||
)
|
or
|
||||||
)
|
DataFlowPrivate::tupleStoreStep(nodeFrom, _, nodeTo)
|
||||||
|
or
|
||||||
|
DataFlowPrivate::dictStoreStep(nodeFrom, _, nodeTo)
|
||||||
|
or
|
||||||
|
// comprehension, so there is taint-flow from `x` in `[x for x in xs]` to the
|
||||||
|
// resulting list of the list-comprehension.
|
||||||
|
//
|
||||||
|
// TODO: once we have proper flow-summary modeling, we might not need this step any
|
||||||
|
// longer -- but there needs to be a matching read-step for the store-step, and we
|
||||||
|
// don't provide that right now.
|
||||||
|
DataFlowPrivate::yieldStoreStep(nodeFrom, _, nodeTo)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -94,8 +94,10 @@ private module SummaryTypeTrackerInput implements SummaryTypeTracker::Input {
|
|||||||
Node returnOf(Node callable, SummaryComponent return) {
|
Node returnOf(Node callable, SummaryComponent return) {
|
||||||
return = FlowSummaryImpl::Private::SummaryComponent::return() and
|
return = FlowSummaryImpl::Private::SummaryComponent::return() and
|
||||||
// `result` should be the return value of a callable expression (lambda or function) referenced by `callable`
|
// `result` should be the return value of a callable expression (lambda or function) referenced by `callable`
|
||||||
result.asCfgNode() =
|
exists(Return ret |
|
||||||
callable.getALocalSource().asExpr().(CallableExpr).getInnerScope().getAReturnValueFlowNode()
|
ret.getScope() = callable.getALocalSource().asExpr().(CallableExpr).getInnerScope() and
|
||||||
|
result.asCfgNode().getNode() = ret.getValue()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Relating callables to nodes
|
// Relating callables to nodes
|
||||||
@@ -241,7 +243,7 @@ module TypeTrackingInput implements Shared::TypeTrackingInput<Location> {
|
|||||||
// is only fed set/list content)
|
// is only fed set/list content)
|
||||||
not nodeFrom instanceof DataFlowPublic::IterableElementNode
|
not nodeFrom instanceof DataFlowPublic::IterableElementNode
|
||||||
or
|
or
|
||||||
TypeTrackerSummaryFlow::basicStoreStep(nodeFrom, nodeTo, DataFlowPublic::singleton(content))
|
TypeTrackerSummaryFlow::basicStoreStep(nodeFrom, nodeTo, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -272,15 +274,14 @@ module TypeTrackingInput implements Shared::TypeTrackingInput<Location> {
|
|||||||
nodeFrom.asCfgNode() instanceof SequenceNode
|
nodeFrom.asCfgNode() instanceof SequenceNode
|
||||||
)
|
)
|
||||||
or
|
or
|
||||||
TypeTrackerSummaryFlow::basicLoadStep(nodeFrom, nodeTo, DataFlowPublic::singleton(content))
|
TypeTrackerSummaryFlow::basicLoadStep(nodeFrom, nodeTo, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds if the `loadContent` of `nodeFrom` is stored in the `storeContent` of `nodeTo`.
|
* Holds if the `loadContent` of `nodeFrom` is stored in the `storeContent` of `nodeTo`.
|
||||||
*/
|
*/
|
||||||
predicate loadStoreStep(Node nodeFrom, Node nodeTo, Content loadContent, Content storeContent) {
|
predicate loadStoreStep(Node nodeFrom, Node nodeTo, Content loadContent, Content storeContent) {
|
||||||
TypeTrackerSummaryFlow::basicLoadStoreStep(nodeFrom, nodeTo,
|
TypeTrackerSummaryFlow::basicLoadStoreStep(nodeFrom, nodeTo, loadContent, storeContent)
|
||||||
DataFlowPublic::singleton(loadContent), DataFlowPublic::singleton(storeContent))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ private module CaptureInput implements Shared::InputSig<Location, Cfg::BasicBloc
|
|||||||
class VariableWrite extends ControlFlowNode {
|
class VariableWrite extends ControlFlowNode {
|
||||||
CapturedVariable v;
|
CapturedVariable v;
|
||||||
|
|
||||||
VariableWrite() { this = v.getAStore().getAFlowNode().(DefinitionNode).getValue() }
|
VariableWrite() { exists(DefinitionNode d | d.getNode() = v.getAStore() | this = d.getValue()) }
|
||||||
|
|
||||||
CapturedVariable getVariable() { result = v }
|
CapturedVariable getVariable() { result = v }
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ private module CaptureInput implements Shared::InputSig<Location, Cfg::BasicBloc
|
|||||||
class VariableRead extends Expr {
|
class VariableRead extends Expr {
|
||||||
CapturedVariable v;
|
CapturedVariable v;
|
||||||
|
|
||||||
VariableRead() { this = v.getALoad().getAFlowNode() }
|
VariableRead() { this.getNode() = v.getALoad() }
|
||||||
|
|
||||||
CapturedVariable getVariable() { result = v }
|
CapturedVariable getVariable() { result = v }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -448,8 +448,7 @@ class TaintTrackingImplementation extends string instanceof TaintTracking::Confi
|
|||||||
context = TNoParam() and
|
context = TNoParam() and
|
||||||
src = TTaintTrackingNode_(retval, TNoParam(), path, kind, this) and
|
src = TTaintTrackingNode_(retval, TNoParam(), path, kind, this) and
|
||||||
node.asCfgNode() = call and
|
node.asCfgNode() = call and
|
||||||
retval.asCfgNode() =
|
retval.asCfgNode().getNode() = any(Return ret | ret.getScope() = pyfunc.getScope()).getValue()
|
||||||
any(Return ret | ret.getScope() = pyfunc.getScope()).getValue().getAFlowNode()
|
|
||||||
) and
|
) and
|
||||||
edgeLabel = "return"
|
edgeLabel = "return"
|
||||||
}
|
}
|
||||||
@@ -471,8 +470,7 @@ class TaintTrackingImplementation extends string instanceof TaintTracking::Confi
|
|||||||
this.callContexts(call, src, pyfunc, context, callee) and
|
this.callContexts(call, src, pyfunc, context, callee) and
|
||||||
retnode = TTaintTrackingNode_(retval, callee, path, kind, this) and
|
retnode = TTaintTrackingNode_(retval, callee, path, kind, this) and
|
||||||
node.asCfgNode() = call and
|
node.asCfgNode() = call and
|
||||||
retval.asCfgNode() =
|
retval.asCfgNode().getNode() = any(Return ret | ret.getScope() = pyfunc.getScope()).getValue()
|
||||||
any(Return ret | ret.getScope() = pyfunc.getScope()).getValue().getAFlowNode()
|
|
||||||
) and
|
) and
|
||||||
edgeLabel = "call"
|
edgeLabel = "call"
|
||||||
}
|
}
|
||||||
@@ -716,8 +714,10 @@ private class EssaTaintTracking extends string instanceof TaintTracking::Configu
|
|||||||
src = TTaintTrackingNode_(srcnode, context, path, srckind, this) and
|
src = TTaintTrackingNode_(srcnode, context, path, srckind, this) and
|
||||||
path.noAttribute()
|
path.noAttribute()
|
||||||
|
|
|
|
||||||
assign.getValue().getAFlowNode() = srcnode.asCfgNode() and
|
srcnode.asCfgNode().getNode() = assign.getValue() and
|
||||||
depth = iterable_unpacking_descent(assign.getATarget().getAFlowNode(), defn.getDefiningNode()) and
|
exists(SequenceNode left_parent | left_parent.getNode() = assign.getATarget() |
|
||||||
|
depth = iterable_unpacking_descent(left_parent, defn.getDefiningNode())
|
||||||
|
) and
|
||||||
kind = taint_at_depth(srckind, depth)
|
kind = taint_at_depth(srckind, depth)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -964,7 +964,7 @@ private TaintKind taint_at_depth(SequenceKind parent_kind, int depth) {
|
|||||||
* - with `left_defn` = `*y`, `left_parent` = `((x, *y), ...)`, result = 1
|
* - with `left_defn` = `*y`, `left_parent` = `((x, *y), ...)`, result = 1
|
||||||
*/
|
*/
|
||||||
int iterable_unpacking_descent(SequenceNode left_parent, ControlFlowNode left_defn) {
|
int iterable_unpacking_descent(SequenceNode left_parent, ControlFlowNode left_defn) {
|
||||||
exists(Assign a | a.getATarget().getASubExpression*().getAFlowNode() = left_parent) and
|
exists(Assign a | left_parent.getNode() = a.getATarget().getASubExpression*()) and
|
||||||
left_parent.getAnElement() = left_defn and
|
left_parent.getAnElement() = left_defn and
|
||||||
// Handle `a, *b = some_iterable`
|
// Handle `a, *b = some_iterable`
|
||||||
if left_defn instanceof StarredNode then result = 0 else result = 1
|
if left_defn instanceof StarredNode then result = 0 else result = 1
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ module SsaSource {
|
|||||||
predicate with_definition(Variable v, ControlFlowNode defn) {
|
predicate with_definition(Variable v, ControlFlowNode defn) {
|
||||||
exists(With with, Name var |
|
exists(With with, Name var |
|
||||||
with.getOptionalVars() = var and
|
with.getOptionalVars() = var and
|
||||||
var.getAFlowNode() = defn
|
defn.getNode() = var
|
||||||
|
|
|
|
||||||
var = v.getAStore()
|
var = v.getAStore()
|
||||||
)
|
)
|
||||||
@@ -67,7 +67,7 @@ module SsaSource {
|
|||||||
predicate pattern_capture_definition(Variable v, ControlFlowNode defn) {
|
predicate pattern_capture_definition(Variable v, ControlFlowNode defn) {
|
||||||
exists(MatchCapturePattern capture, Name var |
|
exists(MatchCapturePattern capture, Name var |
|
||||||
capture.getVariable() = var and
|
capture.getVariable() = var and
|
||||||
var.getAFlowNode() = defn
|
defn.getNode() = var
|
||||||
|
|
|
|
||||||
var = v.getAStore()
|
var = v.getAStore()
|
||||||
)
|
)
|
||||||
@@ -78,7 +78,7 @@ module SsaSource {
|
|||||||
predicate pattern_alias_definition(Variable v, ControlFlowNode defn) {
|
predicate pattern_alias_definition(Variable v, ControlFlowNode defn) {
|
||||||
exists(MatchAsPattern pattern, Name var |
|
exists(MatchAsPattern pattern, Name var |
|
||||||
pattern.getAlias() = var and
|
pattern.getAlias() = var and
|
||||||
var.getAFlowNode() = defn
|
defn.getNode() = var
|
||||||
|
|
|
|
||||||
var = v.getAStore()
|
var = v.getAStore()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ module Bottle {
|
|||||||
|
|
||||||
override Parameter getARoutedParameter() { none() }
|
override Parameter getARoutedParameter() { none() }
|
||||||
|
|
||||||
override Function getARequestHandler() { result.getADecorator().getAFlowNode() = node }
|
override Function getARequestHandler() { node.getNode() = result.getADecorator() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +73,10 @@ module Bottle {
|
|||||||
/** A response returned by a view callable. */
|
/** A response returned by a view callable. */
|
||||||
class BottleReturnResponse extends Http::Server::HttpResponse::Range {
|
class BottleReturnResponse extends Http::Server::HttpResponse::Range {
|
||||||
BottleReturnResponse() {
|
BottleReturnResponse() {
|
||||||
this.asCfgNode() = any(View::ViewCallable vc).getAReturnValueFlowNode()
|
exists(Return ret |
|
||||||
|
ret.getScope() = any(View::ViewCallable vc) and
|
||||||
|
this.asCfgNode().getNode() = ret.getValue()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override DataFlow::Node getBody() { result = this }
|
override DataFlow::Node getBody() { result = this }
|
||||||
|
|||||||
@@ -2872,7 +2872,10 @@ module PrivateDjango {
|
|||||||
DataFlow::CfgNode
|
DataFlow::CfgNode
|
||||||
{
|
{
|
||||||
DjangoRedirectViewGetRedirectUrlReturn() {
|
DjangoRedirectViewGetRedirectUrlReturn() {
|
||||||
node = any(GetRedirectUrlFunction f).getAReturnValueFlowNode()
|
exists(Return ret |
|
||||||
|
ret.getScope() = any(GetRedirectUrlFunction f) and
|
||||||
|
node.getNode() = ret.getValue()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override DataFlow::Node getRedirectLocation() { result = this }
|
override DataFlow::Node getRedirectLocation() { result = this }
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ module FastApi {
|
|||||||
result in [this.getArg(0), this.getArgByName("path")]
|
result in [this.getArg(0), this.getArgByName("path")]
|
||||||
}
|
}
|
||||||
|
|
||||||
override Function getARequestHandler() { result.getADecorator().getAFlowNode() = node }
|
override Function getARequestHandler() { node.getNode() = result.getADecorator() }
|
||||||
|
|
||||||
override string getFramework() { result = "FastAPI" }
|
override string getFramework() { result = "FastAPI" }
|
||||||
|
|
||||||
@@ -309,7 +309,10 @@ module FastApi {
|
|||||||
FastApiRouteSetup routeSetup;
|
FastApiRouteSetup routeSetup;
|
||||||
|
|
||||||
FastApiRequestHandlerReturn() {
|
FastApiRequestHandlerReturn() {
|
||||||
node = routeSetup.getARequestHandler().getAReturnValueFlowNode()
|
exists(Return ret |
|
||||||
|
ret.getScope() = routeSetup.getARequestHandler() and
|
||||||
|
node.getNode() = ret.getValue()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override DataFlow::Node getBody() { result = this }
|
override DataFlow::Node getBody() { result = this }
|
||||||
|
|||||||
@@ -371,7 +371,7 @@ module Flask {
|
|||||||
result in [this.getArg(0), this.getArgByName("rule")]
|
result in [this.getArg(0), this.getArgByName("rule")]
|
||||||
}
|
}
|
||||||
|
|
||||||
override Function getARequestHandler() { result.getADecorator().getAFlowNode() = node }
|
override Function getARequestHandler() { node.getNode() = result.getADecorator() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -536,7 +536,7 @@ module Flask {
|
|||||||
FlaskRouteHandlerReturn() {
|
FlaskRouteHandlerReturn() {
|
||||||
exists(Function routeHandler |
|
exists(Function routeHandler |
|
||||||
routeHandler = any(FlaskRouteSetup rs).getARequestHandler() and
|
routeHandler = any(FlaskRouteSetup rs).getARequestHandler() and
|
||||||
node = routeHandler.getAReturnValueFlowNode() and
|
exists(Return ret | ret.getScope() = routeHandler and node.getNode() = ret.getValue()) and
|
||||||
not this instanceof Flask::Response::InstanceSource
|
not this instanceof Flask::Response::InstanceSource
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ private module FlaskAdmin {
|
|||||||
result in [this.getArg(0), this.getArgByName("url")]
|
result in [this.getArg(0), this.getArgByName("url")]
|
||||||
}
|
}
|
||||||
|
|
||||||
override Function getARequestHandler() { result.getADecorator().getAFlowNode() = node }
|
override Function getARequestHandler() { node.getNode() = result.getADecorator() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,7 +71,7 @@ private module FlaskAdmin {
|
|||||||
|
|
||||||
override Function getARequestHandler() {
|
override Function getARequestHandler() {
|
||||||
exists(Flask::FlaskViewClass cls |
|
exists(Flask::FlaskViewClass cls |
|
||||||
cls.getADecorator().getAFlowNode() = node and
|
node.getNode() = cls.getADecorator() and
|
||||||
result = cls.getARequestHandler()
|
result = cls.getARequestHandler()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,7 +166,10 @@ module Pyramid {
|
|||||||
/** A response returned by a view callable. */
|
/** A response returned by a view callable. */
|
||||||
private class PyramidReturnResponse extends Http::Server::HttpResponse::Range {
|
private class PyramidReturnResponse extends Http::Server::HttpResponse::Range {
|
||||||
PyramidReturnResponse() {
|
PyramidReturnResponse() {
|
||||||
this.asCfgNode() = any(View::ViewCallable vc).getAReturnValueFlowNode() and
|
exists(Return ret |
|
||||||
|
ret.getScope() = any(View::ViewCallable vc) and
|
||||||
|
this.asCfgNode().getNode() = ret.getValue()
|
||||||
|
) and
|
||||||
not this = instance()
|
not this = instance()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2254,8 +2254,9 @@ module StdlibPrivate {
|
|||||||
DataFlow::CfgNode
|
DataFlow::CfgNode
|
||||||
{
|
{
|
||||||
WsgirefSimpleServerApplicationReturn() {
|
WsgirefSimpleServerApplicationReturn() {
|
||||||
exists(WsgirefSimpleServerApplication requestHandler |
|
exists(WsgirefSimpleServerApplication requestHandler, Return ret |
|
||||||
node = requestHandler.getAReturnValueFlowNode()
|
ret.getScope() = requestHandler and
|
||||||
|
node.getNode() = ret.getValue()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4244,7 +4245,6 @@ module StdlibPrivate {
|
|||||||
)
|
)
|
||||||
// TODO: Once we have DictKeyContent, we need to transform that into ListElementContent
|
// TODO: Once we have DictKeyContent, we need to transform that into ListElementContent
|
||||||
) and
|
) and
|
||||||
// Element content is mutated into list element content
|
|
||||||
output = "ReturnValue.ListElement" and
|
output = "ReturnValue.ListElement" and
|
||||||
preservesValue = true
|
preservesValue = true
|
||||||
or
|
or
|
||||||
@@ -4271,9 +4271,11 @@ module StdlibPrivate {
|
|||||||
preservesValue = true
|
preservesValue = true
|
||||||
)
|
)
|
||||||
or
|
or
|
||||||
input = "Argument[0].ListElement" and
|
// TODO: We need to also translate iterable content such as list element
|
||||||
|
// but we currently lack TupleElementAny
|
||||||
|
input = "Argument[0]" and
|
||||||
output = "ReturnValue" and
|
output = "ReturnValue" and
|
||||||
preservesValue = true
|
preservesValue = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4968,26 +4970,6 @@ module StdlibPrivate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A flow summary for `str.join`. */
|
|
||||||
class StrJoinSummary extends SummarizedCallable::Range {
|
|
||||||
StrJoinSummary() { this = "str.join" }
|
|
||||||
|
|
||||||
override DataFlow::CallCfgNode getACall() { result.(DataFlow::MethodCallNode).calls(_, "join") }
|
|
||||||
|
|
||||||
override DataFlow::ArgumentNode getACallback() {
|
|
||||||
result.(DataFlow::AttrRead).getAttributeName() = "join"
|
|
||||||
}
|
|
||||||
|
|
||||||
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
|
|
||||||
(
|
|
||||||
// For code like `" ".join([name])`
|
|
||||||
input = "Argument[0,iterable:].ListElement" and
|
|
||||||
preservesValue = true
|
|
||||||
) and
|
|
||||||
output = "ReturnValue"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// asyncio
|
// asyncio
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -182,7 +182,10 @@ private module Twisted {
|
|||||||
DataFlow::CfgNode
|
DataFlow::CfgNode
|
||||||
{
|
{
|
||||||
TwistedResourceRenderMethodReturn() {
|
TwistedResourceRenderMethodReturn() {
|
||||||
this.asCfgNode() = any(TwistedResourceRenderMethod meth).getAReturnValueFlowNode()
|
exists(Return ret |
|
||||||
|
ret.getScope() = any(TwistedResourceRenderMethod meth) and
|
||||||
|
this.asCfgNode().getNode() = ret.getValue()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override DataFlow::Node getBody() { result = this }
|
override DataFlow::Node getBody() { result = this }
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
extensions:
|
|
||||||
- addsTo:
|
|
||||||
pack: codeql/python-all
|
|
||||||
extensible: summaryModel
|
|
||||||
data:
|
|
||||||
- ['lxml', 'Member[etree].Member[fromstringlist]', 'Argument[0,strings:].ListElement', 'ReturnValue', 'taint']
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
extensions:
|
|
||||||
- addsTo:
|
|
||||||
pack: codeql/python-all
|
|
||||||
extensible: summaryModel
|
|
||||||
data:
|
|
||||||
- ['xml', 'Member[etree].Member[fromstringlist]', 'Argument[0,strings:].ListElement', 'ReturnValue', 'taint']
|
|
||||||
@@ -77,7 +77,7 @@ module Stages {
|
|||||||
or
|
or
|
||||||
exists(any(AstExtended::AstNode n).getParentNode())
|
exists(any(AstExtended::AstNode n).getParentNode())
|
||||||
or
|
or
|
||||||
exists(any(AstExtended::AstNode n).getAFlowNode())
|
exists(PyFlow::ControlFlowNode cfg, AstExtended::AstNode n | cfg.getNode() = n)
|
||||||
or
|
or
|
||||||
exists(any(PyFlow::BasicBlock b).getImmediateDominator())
|
exists(any(PyFlow::BasicBlock b).getImmediateDominator())
|
||||||
or
|
or
|
||||||
|
|||||||
@@ -56,8 +56,9 @@ abstract class CallableObjectInternal extends ObjectInternal {
|
|||||||
/** A Python function. */
|
/** A Python function. */
|
||||||
class PythonFunctionObjectInternal extends CallableObjectInternal, TPythonFunctionObject {
|
class PythonFunctionObjectInternal extends CallableObjectInternal, TPythonFunctionObject {
|
||||||
override Function getScope() {
|
override Function getScope() {
|
||||||
exists(CallableExpr expr |
|
exists(CallableExpr expr, ControlFlowNode exprCfg |
|
||||||
this = TPythonFunctionObject(expr.getAFlowNode()) and
|
exprCfg.getNode() = expr and
|
||||||
|
this = TPythonFunctionObject(exprCfg) and
|
||||||
result = expr.getInnerScope()
|
result = expr.getInnerScope()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -80,11 +81,12 @@ class PythonFunctionObjectInternal extends CallableObjectInternal, TPythonFuncti
|
|||||||
|
|
||||||
pragma[nomagic]
|
pragma[nomagic]
|
||||||
override predicate callResult(PointsToContext callee, ObjectInternal obj, CfgOrigin origin) {
|
override predicate callResult(PointsToContext callee, ObjectInternal obj, CfgOrigin origin) {
|
||||||
exists(Function func, ControlFlowNode rval, ControlFlowNode forigin |
|
exists(Function func, Return ret, ControlFlowNode rval, ControlFlowNode forigin |
|
||||||
func = this.getScope() and
|
func = this.getScope() and
|
||||||
callee.appliesToScope(func)
|
callee.appliesToScope(func)
|
||||||
|
|
|
|
||||||
rval = func.getAReturnValueFlowNode() and
|
ret.getScope() = func and
|
||||||
|
rval.getNode() = ret.getValue() and
|
||||||
PointsToInternal::pointsTo(rval, callee, obj, forigin) and
|
PointsToInternal::pointsTo(rval, callee, obj, forigin) and
|
||||||
origin = CfgOrigin::fromCfgNode(forigin)
|
origin = CfgOrigin::fromCfgNode(forigin)
|
||||||
)
|
)
|
||||||
@@ -160,10 +162,11 @@ class PythonFunctionObjectInternal extends CallableObjectInternal, TPythonFuncti
|
|||||||
}
|
}
|
||||||
|
|
||||||
private BasicBlock blockReturningNone(Function func) {
|
private BasicBlock blockReturningNone(Function func) {
|
||||||
exists(Return ret |
|
exists(Return ret, ControlFlowNode ret_ |
|
||||||
not exists(ret.getValue()) and
|
not exists(ret.getValue()) and
|
||||||
ret.getScope() = func and
|
ret.getScope() = func and
|
||||||
result = ret.getAFlowNode().getBasicBlock()
|
ret_.getNode() = ret and
|
||||||
|
result = ret_.getBasicBlock()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -113,8 +113,9 @@ abstract class ClassObjectInternal extends ObjectInternal {
|
|||||||
class PythonClassObjectInternal extends ClassObjectInternal, TPythonClassObject {
|
class PythonClassObjectInternal extends ClassObjectInternal, TPythonClassObject {
|
||||||
/** Gets the scope for this Python class */
|
/** Gets the scope for this Python class */
|
||||||
Class getScope() {
|
Class getScope() {
|
||||||
exists(ClassExpr expr |
|
exists(ClassExpr expr, ControlFlowNode exprCfg |
|
||||||
this = TPythonClassObject(expr.getAFlowNode()) and
|
exprCfg.getNode() = expr and
|
||||||
|
this = TPythonClassObject(exprCfg) and
|
||||||
result = expr.getInnerScope()
|
result = expr.getInnerScope()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -745,7 +745,12 @@ class PythonFunctionValue extends FunctionValue {
|
|||||||
override int maxParameters() { result = this.getScope().getMaxPositionalArguments() }
|
override int maxParameters() { result = this.getScope().getMaxPositionalArguments() }
|
||||||
|
|
||||||
/** Gets a control flow node corresponding to a return statement in this function */
|
/** Gets a control flow node corresponding to a return statement in this function */
|
||||||
ControlFlowNode getAReturnedNode() { result = this.getScope().getAReturnValueFlowNode() }
|
ControlFlowNode getAReturnedNode() {
|
||||||
|
exists(Return ret |
|
||||||
|
ret.getScope() = this.getScope() and
|
||||||
|
result.getNode() = ret.getValue()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override ClassValue getARaisedType() { scope_raises(result, this.getScope()) }
|
override ClassValue getARaisedType() { scope_raises(result, this.getScope()) }
|
||||||
|
|
||||||
|
|||||||
@@ -387,7 +387,7 @@ private PythonClassObjectInternal abcMetaClassObject() {
|
|||||||
private predicate neither_class_nor_static_method(Function f) {
|
private predicate neither_class_nor_static_method(Function f) {
|
||||||
not exists(f.getADecorator())
|
not exists(f.getADecorator())
|
||||||
or
|
or
|
||||||
exists(ControlFlowNode deco | deco = f.getADecorator().getAFlowNode() |
|
exists(ControlFlowNode deco | deco.getNode() = f.getADecorator() |
|
||||||
exists(ObjectInternal o | PointsToInternal::pointsTo(deco, _, o, _) |
|
exists(ObjectInternal o | PointsToInternal::pointsTo(deco, _, o, _) |
|
||||||
o != ObjectInternal::staticMethod() and
|
o != ObjectInternal::staticMethod() and
|
||||||
o != ObjectInternal::classMethod()
|
o != ObjectInternal::classMethod()
|
||||||
|
|||||||
@@ -711,7 +711,7 @@ private module InterModulePointsTo {
|
|||||||
ControlFlowNode f, PointsToContext context, ObjectInternal value, ControlFlowNode origin
|
ControlFlowNode f, PointsToContext context, ObjectInternal value, ControlFlowNode origin
|
||||||
) {
|
) {
|
||||||
exists(string name, ImportExpr i |
|
exists(string name, ImportExpr i |
|
||||||
i.getAFlowNode() = f and
|
f.getNode() = i and
|
||||||
i.getImportedModuleName() = name and
|
i.getImportedModuleName() = name and
|
||||||
PointsToInternal::module_imported_as(value, name) and
|
PointsToInternal::module_imported_as(value, name) and
|
||||||
origin = f and
|
origin = f and
|
||||||
@@ -2118,8 +2118,9 @@ module Types {
|
|||||||
result.getBuiltin() = cls.getBuiltin().getBaseClass() and n = 0
|
result.getBuiltin() = cls.getBuiltin().getBaseClass() and n = 0
|
||||||
or
|
or
|
||||||
exists(Class pycls | pycls = cls.(PythonClassObjectInternal).getScope() |
|
exists(Class pycls | pycls = cls.(PythonClassObjectInternal).getScope() |
|
||||||
exists(ObjectInternal base |
|
exists(ObjectInternal base, ControlFlowNode baseNode |
|
||||||
PointsToInternal::pointsTo(pycls.getBase(n).getAFlowNode(), _, base, _)
|
baseNode.getNode() = pycls.getBase(n) and
|
||||||
|
PointsToInternal::pointsTo(baseNode, _, base, _)
|
||||||
|
|
|
|
||||||
result = base and base != ObjectInternal::unknown()
|
result = base and base != ObjectInternal::unknown()
|
||||||
or
|
or
|
||||||
@@ -2223,7 +2224,10 @@ module Types {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ControlFlowNode decorator_call_callee(PythonClassObjectInternal cls) {
|
private ControlFlowNode decorator_call_callee(PythonClassObjectInternal cls) {
|
||||||
result = cls.getScope().getADecorator().getAFlowNode().(CallNode).getFunction()
|
exists(CallNode deco |
|
||||||
|
deco.getNode() = cls.getScope().getADecorator() and
|
||||||
|
result = deco.getFunction()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean has_six_add_metaclass(PythonClassObjectInternal cls) {
|
private boolean has_six_add_metaclass(PythonClassObjectInternal cls) {
|
||||||
@@ -2262,7 +2266,7 @@ module Types {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private EssaVariable metaclass_var(Class cls) {
|
private EssaVariable metaclass_var(Class cls) {
|
||||||
result.getASourceUse() = cls.getMetaClass().getAFlowNode()
|
result.getASourceUse().getNode() = cls.getMetaClass()
|
||||||
or
|
or
|
||||||
major_version() = 2 and
|
major_version() = 2 and
|
||||||
not exists(cls.getMetaClass()) and
|
not exists(cls.getMetaClass()) and
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ class ClassObject extends Object {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ControlFlowNode declaredMetaClass() { result = this.getPyClass().getMetaClass().getAFlowNode() }
|
ControlFlowNode declaredMetaClass() { result.getNode() = this.getPyClass().getMetaClass() }
|
||||||
|
|
||||||
/** Has type inference failed to compute the full class hierarchy for this class for the reason given. */
|
/** Has type inference failed to compute the full class hierarchy for this class for the reason given. */
|
||||||
predicate failedInference(string reason) { Types::failedInference(this.theClass(), reason) }
|
predicate failedInference(string reason) { Types::failedInference(this.theClass(), reason) }
|
||||||
@@ -195,8 +195,9 @@ class ClassObject extends Object {
|
|||||||
* It is guaranteed that getProbableSingletonInstance() returns at most one Object for each ClassObject.
|
* It is guaranteed that getProbableSingletonInstance() returns at most one Object for each ClassObject.
|
||||||
*/
|
*/
|
||||||
Object getProbableSingletonInstance() {
|
Object getProbableSingletonInstance() {
|
||||||
exists(ControlFlowNodeWithPointsTo use, Expr origin |
|
exists(ControlFlowNodeWithPointsTo use, Expr origin, ControlFlowNode origin_ |
|
||||||
use.refersTo(result, this, origin.getAFlowNode())
|
origin_.getNode() = origin and
|
||||||
|
use.refersTo(result, this, origin_)
|
||||||
|
|
|
|
||||||
this.hasStaticallyUniqueInstance() and
|
this.hasStaticallyUniqueInstance() and
|
||||||
/* Ensure that original expression will be executed only one. */
|
/* Ensure that original expression will be executed only one. */
|
||||||
|
|||||||
@@ -427,7 +427,7 @@ class ExceptFlowNodeWithPointsTo extends ExceptFlowNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ControlFlowNodeWithPointsTo element_from_tuple_objectapi(Object tuple) {
|
private ControlFlowNodeWithPointsTo element_from_tuple_objectapi(Object tuple) {
|
||||||
exists(Tuple t | t = tuple.getOrigin() and result = t.getAnElt().getAFlowNode())
|
exists(Tuple t | t = tuple.getOrigin() and result.getNode() = t.getAnElt())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user