mirror of
https://github.com/github/codeql.git
synced 2026-05-25 08:37:20 +02:00
Compare commits
7 Commits
esbena/pr-
...
nickrolfe/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8146a1089 | ||
|
|
ea5d696d55 | ||
|
|
6908a0dc12 | ||
|
|
189e75bfe2 | ||
|
|
b502e68783 | ||
|
|
6d28e87f57 | ||
|
|
5cada400f1 |
@@ -4,10 +4,8 @@
|
||||
"*/ql/lib/qlpack.yml",
|
||||
"*/ql/test/qlpack.yml",
|
||||
"*/ql/examples/qlpack.yml",
|
||||
"*/ql/consistency-queries/qlpack.yml",
|
||||
"cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/tainted/qlpack.yml",
|
||||
"javascript/ql/experimental/adaptivethreatmodeling/lib/qlpack.yml",
|
||||
"javascript/ql/experimental/adaptivethreatmodeling/modelbuilding/qlpack.yml",
|
||||
"javascript/ql/experimental/adaptivethreatmodeling/src/qlpack.yml",
|
||||
"csharp/ql/campaigns/Solorigate/lib/qlpack.yml",
|
||||
"csharp/ql/campaigns/Solorigate/src/qlpack.yml",
|
||||
@@ -15,6 +13,8 @@
|
||||
"misc/legacy-support/*/qlpack.yml",
|
||||
"misc/suite-helpers/qlpack.yml",
|
||||
"ruby/extractor-pack/codeql-extractor.yml",
|
||||
"ruby/ql/consistency-queries/qlpack.yml",
|
||||
"ql/ql/consistency-queries/qlpack.yml",
|
||||
"ql/extractor-pack/codeql-extractor.yml"
|
||||
],
|
||||
"versionPolicies": {
|
||||
|
||||
13
.gitattributes
vendored
13
.gitattributes
vendored
@@ -50,15 +50,4 @@
|
||||
*.pdb -text
|
||||
|
||||
java/ql/test/stubs/**/*.java linguist-generated=true
|
||||
java/ql/test/experimental/stubs/**/*.java linguist-generated=true
|
||||
|
||||
# For some languages, upgrade script testing references really old dbscheme
|
||||
# files from legacy upgrades that have CRLF line endings. Since upgrade
|
||||
# resolution relies on object hashes, we must suppress line ending conversion
|
||||
# for those testing dbscheme files.
|
||||
*/ql/lib/upgrades/initial/*.dbscheme -text
|
||||
|
||||
# Generated test files - these are synced from the standard JavaScript libraries using
|
||||
# `javascript/ql/experimental/adaptivethreatmodeling/test/update_endpoint_test_files.py`.
|
||||
javascript/ql/experimental/adaptivethreatmodeling/test/endpoint_large_scale/autogenerated/**/*.js linguist-generated=true -merge
|
||||
javascript/ql/experimental/adaptivethreatmodeling/test/endpoint_large_scale/autogenerated/**/*.ts linguist-generated=true -merge
|
||||
java/ql/test/experimental/stubs/**/*.java linguist-generated=true
|
||||
3
.github/workflows/check-change-note.yml
vendored
3
.github/workflows/check-change-note.yml
vendored
@@ -6,11 +6,8 @@ on:
|
||||
paths:
|
||||
- "*/ql/src/**/*.ql"
|
||||
- "*/ql/src/**/*.qll"
|
||||
- "*/ql/lib/**/*.ql"
|
||||
- "*/ql/lib/**/*.qll"
|
||||
- "!**/experimental/**"
|
||||
- "!ql/**"
|
||||
- ".github/workflows/check-change-note.yml"
|
||||
|
||||
jobs:
|
||||
check-change-note:
|
||||
|
||||
7
.github/workflows/codeql-analysis.yml
vendored
7
.github/workflows/codeql-analysis.yml
vendored
@@ -27,11 +27,6 @@ jobs:
|
||||
pull-requests: read
|
||||
|
||||
steps:
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: 6.0.101
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
@@ -56,7 +51,7 @@ jobs:
|
||||
# uses a compiled language
|
||||
|
||||
- run: |
|
||||
dotnet build csharp /p:UseSharedCompilation=false
|
||||
dotnet build csharp
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@main
|
||||
|
||||
43
.github/workflows/csv-coverage-metrics.yml
vendored
43
.github/workflows/csv-coverage-metrics.yml
vendored
@@ -1,43 +0,0 @@
|
||||
name: "Publish framework coverage as metrics"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '5 0 * * *'
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- ".github/workflows/csv-coverage-metrics.yml"
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup CodeQL
|
||||
uses: ./.github/actions/fetch-codeql
|
||||
- name: Create empty database
|
||||
run: |
|
||||
DATABASE="${{ runner.temp }}/java-database"
|
||||
PROJECT="${{ runner.temp }}/java-project"
|
||||
mkdir -p "$PROJECT/src/tmp/empty"
|
||||
echo "class Empty {}" >> "$PROJECT/src/tmp/empty/Empty.java"
|
||||
codeql database create "$DATABASE" --language=java --source-root="$PROJECT" --command 'javac src/tmp/empty/Empty.java'
|
||||
- name: Capture coverage information
|
||||
run: |
|
||||
DATABASE="${{ runner.temp }}/java-database"
|
||||
codeql database analyze --format=sarif-latest --output=metrics.sarif -- "$DATABASE" ./java/ql/src/Metrics/Summaries/FrameworkCoverage.ql
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: metrics.sarif
|
||||
path: metrics.sarif
|
||||
retention-days: 20
|
||||
- name: Upload SARIF file
|
||||
uses: github/codeql-action/upload-sarif@v1
|
||||
with:
|
||||
sarif_file: metrics.sarif
|
||||
76
.github/workflows/js-ml-tests.yml
vendored
76
.github/workflows/js-ml-tests.yml
vendored
@@ -1,76 +0,0 @@
|
||||
name: JS ML-powered queries tests
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "javascript/ql/experimental/adaptivethreatmodeling/**"
|
||||
- .github/workflows/js-ml-tests.yml
|
||||
branches:
|
||||
- main
|
||||
- "rc/*"
|
||||
pull_request:
|
||||
paths:
|
||||
- "javascript/ql/experimental/adaptivethreatmodeling/**"
|
||||
- .github/workflows/js-ml-tests.yml
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: javascript/ql/experimental/adaptivethreatmodeling
|
||||
|
||||
jobs:
|
||||
qlformat:
|
||||
name: Check QL formatting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: ./.github/actions/fetch-codeql
|
||||
|
||||
- name: Check QL formatting
|
||||
run: |
|
||||
find . "(" -name "*.ql" -or -name "*.qll" ")" -print0 | \
|
||||
xargs -0 codeql query format --check-only
|
||||
|
||||
qlcompile:
|
||||
name: Check QL compilation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: ./.github/actions/fetch-codeql
|
||||
|
||||
- name: Install pack dependencies
|
||||
run: |
|
||||
for pack in modelbuilding src; do
|
||||
codeql pack install --mode verify -- "${pack}"
|
||||
done
|
||||
|
||||
- name: Check QL compilation
|
||||
run: |
|
||||
codeql query compile \
|
||||
--check-only \
|
||||
--ram 5120 \
|
||||
--additional-packs "${{ github.workspace }}" \
|
||||
--threads=0 \
|
||||
-- \
|
||||
lib modelbuilding src
|
||||
|
||||
qltest:
|
||||
name: Run QL tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: ./.github/actions/fetch-codeql
|
||||
|
||||
- name: Install pack dependencies
|
||||
run: codeql pack install -- test
|
||||
|
||||
- name: Run QL tests
|
||||
run: |
|
||||
codeql test run \
|
||||
--threads=0 \
|
||||
--ram 5120 \
|
||||
--additional-packs "${{ github.workspace }}" \
|
||||
-- \
|
||||
test
|
||||
103
.github/workflows/mad_modelDiff.yml
vendored
103
.github/workflows/mad_modelDiff.yml
vendored
@@ -1,103 +0,0 @@
|
||||
name: Models as Data - Diff
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
projects:
|
||||
description: "The projects to generate models for"
|
||||
required: true
|
||||
default: '["netty/netty"]'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "java/ql/src/utils/model-generator/**/*.*"
|
||||
- ".github/workflows/mad_modelDiff.yml"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
model-diff:
|
||||
name: Model Difference
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'github/codeql'
|
||||
strategy:
|
||||
matrix:
|
||||
slug: ${{fromJson(github.event.inputs.projects || '["apache/commons-codec", "apache/commons-io", "apache/commons-beanutils", "apache/commons-logging", "apache/commons-fileupload", "apache/commons-lang", "apache/commons-validator", "apache/commons-csv", "apache/dubbo"]' )}}
|
||||
steps:
|
||||
- name: Clone github/codeql from PR
|
||||
uses: actions/checkout@v2
|
||||
if: github.event.pull_request
|
||||
with:
|
||||
path: codeql-pr
|
||||
- name: Clone github/codeql from main
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: codeql-main
|
||||
ref: main
|
||||
- uses: ./codeql-main/.github/actions/fetch-codeql
|
||||
- name: Download database
|
||||
env:
|
||||
SLUG: ${{ matrix.slug }}
|
||||
run: |
|
||||
set -x
|
||||
mkdir lib-dbs
|
||||
SHORTNAME=${SLUG//[^a-zA-Z0-9_]/}
|
||||
projectId=`curl -s https://lgtm.com/api/v1.0/projects/g/${SLUG} | jq .id`
|
||||
curl -L "https://lgtm.com/api/v1.0/snapshots/$projectId/java" -o "$SHORTNAME.zip"
|
||||
unzip -q -d "${SHORTNAME}-db" "${SHORTNAME}.zip"
|
||||
mkdir "lib-dbs/$SHORTNAME/"
|
||||
mv "${SHORTNAME}-db/"$(ls -1 "${SHORTNAME}"-db)/* "lib-dbs/${SHORTNAME}/"
|
||||
- name: Generate Models (PR and main)
|
||||
run: |
|
||||
set -x
|
||||
mkdir tmp-models
|
||||
MODELS=`pwd`/tmp-models
|
||||
DATABASES=`pwd`/lib-dbs
|
||||
|
||||
analyzeDatabaseWithCheckout() {
|
||||
QL_VARIANT=$1
|
||||
DATABASE=$2
|
||||
cd codeql-$QL_VARIANT
|
||||
SHORTNAME=`basename $DATABASE`
|
||||
python java/ql/src/utils/model-generator/GenerateFlowModel.py $DATABASE $MODELS/${SHORTNAME}.qll
|
||||
mv $MODELS/${SHORTNAME}.qll $MODELS/${SHORTNAME}Generated_${QL_VARIANT}.qll
|
||||
cd ..
|
||||
}
|
||||
|
||||
for d in $DATABASES/*/ ; do
|
||||
ls -1 "$d"
|
||||
|
||||
analyzeDatabaseWithCheckout "main" $d
|
||||
if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]
|
||||
then
|
||||
analyzeDatabaseWithCheckout "pr" $d
|
||||
fi
|
||||
done
|
||||
- name: Install diff2html
|
||||
if: github.event.pull_request
|
||||
run: |
|
||||
npm install -g diff2html-cli
|
||||
- name: Generate Model Diff
|
||||
if: github.event.pull_request
|
||||
run: |
|
||||
set -x
|
||||
MODELS=`pwd`/tmp-models
|
||||
ls -1 tmp-models/
|
||||
for m in $MODELS/*_main.qll ; do
|
||||
t="${m/main/"pr"}"
|
||||
basename=`basename $m`
|
||||
name="diff_${basename/_main.qll/""}"
|
||||
(diff -w -u $m $t | diff2html -i stdin -F $MODELS/$name.html) || true
|
||||
done
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: models
|
||||
path: tmp-models/*.qll
|
||||
retention-days: 20
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: diffs
|
||||
path: tmp-models/*.html
|
||||
retention-days: 20
|
||||
62
.github/workflows/mad_regenerate-models.yml
vendored
62
.github/workflows/mad_regenerate-models.yml
vendored
@@ -1,62 +0,0 @@
|
||||
name: Regenerate framework models
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "30 2 * * *"
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- ".github/workflows/mad_regenerate-models.yml"
|
||||
|
||||
jobs:
|
||||
regenerate-models:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
# placeholder required for each axis, excluded below, replaced by the actual combinations (see include)
|
||||
slug: ["placeholder"]
|
||||
ref: ["placeholder"]
|
||||
include:
|
||||
- slug: "apache/commons-io"
|
||||
ref: "8985de8fe74f6622a419b37a6eed0dbc484dc128"
|
||||
exclude:
|
||||
- slug: "placeholder"
|
||||
ref: "placeholder"
|
||||
steps:
|
||||
- name: Clone self (github/codeql)
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup CodeQL binaries
|
||||
uses: ./.github/actions/fetch-codeql
|
||||
- name: Clone repositories
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: repos/${{ matrix.ref }}
|
||||
ref: ${{ matrix.ref }}
|
||||
repository: ${{ matrix.slug }}
|
||||
- name: Build database
|
||||
env:
|
||||
SLUG: ${{ matrix.slug }}
|
||||
REF: ${{ matrix.ref }}
|
||||
run: |
|
||||
mkdir dbs
|
||||
cd repos/${REF}
|
||||
SHORTNAME=${SLUG//[^a-zA-Z0-9_]/}
|
||||
codeql database create --language=java ../../dbs/${SHORTNAME}
|
||||
- name: Regenerate models in-place
|
||||
env:
|
||||
SLUG: ${{ matrix.slug }}
|
||||
run: |
|
||||
SHORTNAME=${SLUG//[^a-zA-Z0-9_]/}
|
||||
java/ql/src/utils/model-generator/RegenerateModels.py "${SLUG}" dbs/${SHORTNAME}
|
||||
- name: Stage changes
|
||||
run: |
|
||||
find java -name "*.qll" -print0 | xargs -0 git add
|
||||
git status
|
||||
git diff --cached > models.patch
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: patch
|
||||
path: models.patch
|
||||
retention-days: 7
|
||||
11
.github/workflows/ql-for-ql-build.yml
vendored
11
.github/workflows/ql-for-ql-build.yml
vendored
@@ -31,13 +31,13 @@ jobs:
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ runner.temp }}/query-pack.zip
|
||||
key: queries-${{ hashFiles('ql/**/*.ql*') }}-${{ hashFiles('ql/**/qlpack.yml') }}-${{ hashFiles('ql/ql/src/ql.dbscheme*') }}-${{ steps.get-codeql-version.outputs.version }}
|
||||
key: queries-${{ hashFiles('ql/**/*.ql*') }}-${{ hashFiles('ql/ql/src/ql.dbscheme*') }}-${{ steps.get-codeql-version.outputs.version }}
|
||||
- name: Build query pack
|
||||
if: steps.cache-queries.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd ql/ql/src
|
||||
"${CODEQL}" pack create
|
||||
cd .codeql/pack/codeql/ql/0.0.0
|
||||
cd .codeql/pack/codeql/ql-all/0.0.0
|
||||
zip "${PACKZIP}" -r .
|
||||
env:
|
||||
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
|
||||
@@ -189,11 +189,4 @@ jobs:
|
||||
uses: github/codeql-action/analyze@erik-krogh/ql
|
||||
with:
|
||||
category: "ql-for-ql-${{ matrix.folder }}"
|
||||
- name: Copy sarif file to CWD
|
||||
run: cp ../results/ql.sarif ./${{ matrix.folder }}.sarif
|
||||
- name: Sarif as artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ matrix.folder }}.sarif
|
||||
path: ${{ matrix.folder }}.sarif
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
CODEQL_THREADS: 4 # TODO: remove this once it's set by the CLI
|
||||
strategy:
|
||||
matrix:
|
||||
repo:
|
||||
repo:
|
||||
- github/codeql
|
||||
- github/codeql-go
|
||||
runs-on: ubuntu-latest
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
ql/target
|
||||
key: ${{ runner.os }}-qltest-cargo-${{ hashFiles('ql/**/Cargo.lock') }}
|
||||
key: ${{ runner.os }}-qltest-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: Build Extractor
|
||||
run: cd ql; env "PATH=$PATH:`dirname ${CODEQL}`" ./create-extractor-pack.sh
|
||||
env:
|
||||
|
||||
8
.github/workflows/ql-for-ql-tests.yml
vendored
8
.github/workflows/ql-for-ql-tests.yml
vendored
@@ -29,24 +29,24 @@ jobs:
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
ql/target
|
||||
key: ${{ runner.os }}-qltest-cargo-${{ hashFiles('ql/**/Cargo.lock') }}
|
||||
key: ${{ runner.os }}-qltest-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: Build extractor
|
||||
run: |
|
||||
cd ql;
|
||||
codeqlpath=$(dirname ${{ steps.find-codeql.outputs.codeql-path }});
|
||||
env "PATH=$PATH:$codeqlpath" ./create-extractor-pack.sh
|
||||
- name: Run QL tests
|
||||
run: |
|
||||
run: |
|
||||
"${CODEQL}" test run --check-databases --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition --search-path "${{ github.workspace }}/ql/extractor-pack" --consistency-queries ql/ql/consistency-queries ql/ql/test
|
||||
env:
|
||||
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
|
||||
- name: Check QL formatting
|
||||
run: |
|
||||
run: |
|
||||
find ql/ql "(" -name "*.ql" -or -name "*.qll" ")" -print0 | xargs -0 "${CODEQL}" query format --check-only
|
||||
env:
|
||||
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
|
||||
- name: Check QL compilation
|
||||
run: |
|
||||
run: |
|
||||
"${CODEQL}" query compile --check-only --threads=4 --warnings=error --search-path "${{ github.workspace }}/ql/extractor-pack" "ql/ql/src" "ql/ql/examples"
|
||||
env:
|
||||
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
|
||||
|
||||
2
.github/workflows/ruby-build.yml
vendored
2
.github/workflows/ruby-build.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
ruby/target
|
||||
key: ${{ runner.os }}-ruby-rust-cargo-${{ hashFiles('ruby/rust-toolchain.toml', 'ruby/**/Cargo.lock') }}
|
||||
key: ${{ runner.os }}-rust-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: Check formatting
|
||||
run: cargo fmt --all -- --check
|
||||
- name: Build
|
||||
|
||||
55
.github/workflows/ruby-qltest.yml
vendored
55
.github/workflows/ruby-qltest.yml
vendored
@@ -24,54 +24,27 @@ defaults:
|
||||
working-directory: ruby
|
||||
|
||||
jobs:
|
||||
qlformat:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: ./.github/actions/fetch-codeql
|
||||
- name: Check QL formatting
|
||||
run: find ql "(" -name "*.ql" -or -name "*.qll" ")" -print0 | xargs -0 codeql query format --check-only
|
||||
qlcompile:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: ./.github/actions/fetch-codeql
|
||||
- name: Check QL compilation
|
||||
run: |
|
||||
codeql query compile --check-only --threads=0 --ram 5000 --warnings=error "ql/src" "ql/examples"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
qlupgrade:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: ./.github/actions/fetch-codeql
|
||||
- name: Check DB upgrade scripts
|
||||
run: |
|
||||
echo >empty.trap
|
||||
codeql dataset import -S ql/lib/upgrades/initial/ruby.dbscheme testdb empty.trap
|
||||
codeql dataset upgrade testdb --additional-packs ql/lib
|
||||
diff -q testdb/ruby.dbscheme ql/lib/ruby.dbscheme
|
||||
- name: Check DB downgrade scripts
|
||||
run: |
|
||||
echo >empty.trap
|
||||
rm -rf testdb; codeql dataset import -S ql/lib/ruby.dbscheme testdb empty.trap
|
||||
codeql resolve upgrades --format=lines --allow-downgrades --additional-packs downgrades \
|
||||
--dbscheme=ql/lib/ruby.dbscheme --target-dbscheme=downgrades/initial/ruby.dbscheme |
|
||||
xargs codeql execute upgrades testdb
|
||||
diff -q testdb/ruby.dbscheme downgrades/initial/ruby.dbscheme
|
||||
qltest:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
slice: ["1/2", "2/2"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: ./.github/actions/fetch-codeql
|
||||
- uses: ./ruby/actions/create-extractor-pack
|
||||
- name: Run QL tests
|
||||
run: |
|
||||
codeql test run --threads=0 --ram 5000 --slice ${{ matrix.slice }} --search-path "${{ github.workspace }}/ruby/extractor-pack" --check-databases --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition --consistency-queries ql/consistency-queries ql/test
|
||||
codeql test run --threads=0 --ram 5000 --search-path "${{ github.workspace }}/ruby/extractor-pack" --check-databases --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition --consistency-queries ql/consistency-queries ql/test
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
- name: Check QL formatting
|
||||
run: find ql "(" -name "*.ql" -or -name "*.qll" ")" -print0 | xargs -0 codeql query format --check-only
|
||||
- name: Check QL compilation
|
||||
run: |
|
||||
codeql query compile --check-only --threads=0 --ram 5000 --warnings=error "ql/src" "ql/examples"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
- name: Check DB upgrade scripts
|
||||
run: |
|
||||
echo >empty.trap
|
||||
codeql dataset import -S ql/lib/upgrades/initial/ruby.dbscheme testdb empty.trap
|
||||
codeql dataset upgrade testdb --additional-packs ql/lib
|
||||
diff -q testdb/ruby.dbscheme ql/lib/ruby.dbscheme
|
||||
|
||||
29
.github/workflows/validate-change-notes.yml
vendored
29
.github/workflows/validate-change-notes.yml
vendored
@@ -1,29 +0,0 @@
|
||||
name: Validate change notes
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "*/ql/*/change-notes/**/*"
|
||||
- ".github/workflows/validate-change-notes.yml"
|
||||
branches:
|
||||
- main
|
||||
- "rc/*"
|
||||
pull_request:
|
||||
paths:
|
||||
- "*/ql/*/change-notes/**/*"
|
||||
- ".github/workflows/validate-change-notes.yml"
|
||||
|
||||
jobs:
|
||||
check-change-note:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup CodeQL
|
||||
uses: ./.github/actions/fetch-codeql
|
||||
|
||||
- name: Fail if there are any errors with existing change notes
|
||||
|
||||
run: |
|
||||
codeql pack release --groups cpp,csharp,java,javascript,python,ruby,-examples,-test,-experimental
|
||||
@@ -1,29 +0,0 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
exclude: /test/.*$(?<!\.ql)(?<!\.qll)(?<!\.qlref)
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v3.2.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: codeql-format
|
||||
name: Fix QL file formatting
|
||||
files: \.qll?$
|
||||
language: system
|
||||
entry: codeql query format --in-place
|
||||
|
||||
- id: sync-files
|
||||
name: Fix files required to be identical
|
||||
language: system
|
||||
entry: python3 config/sync-files.py --latest
|
||||
pass_filenames: false
|
||||
|
||||
- id: qhelp
|
||||
name: Check query help generation
|
||||
files: \.qhelp$
|
||||
language: system
|
||||
entry: python3 misc/scripts/check-qhelp.py
|
||||
@@ -13,9 +13,6 @@
|
||||
/python/**/experimental/**/* @github/codeql-python @xcorail
|
||||
/ruby/**/experimental/**/* @github/codeql-ruby @xcorail
|
||||
|
||||
# ML-powered queries
|
||||
/javascript/ql/experimental/adaptivethreatmodeling/ @github/codeql-ml-powered-queries-reviewers
|
||||
|
||||
# Notify members of codeql-go about PRs to the shared data-flow library files
|
||||
/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @github/codeql-java @github/codeql-go
|
||||
/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll @github/codeql-java @github/codeql-go
|
||||
@@ -30,4 +27,4 @@
|
||||
/docs/query-*-style-guide.md @github/codeql-analysis-reviewers
|
||||
|
||||
# QL for QL reviewers
|
||||
/ql/ @github/codeql-ql-for-ql-reviewers
|
||||
/ql/ @github/codeql-ql-for-ql-reviewers
|
||||
@@ -42,11 +42,7 @@ If you have an idea for a query that you would like to share with other CodeQL u
|
||||
|
||||
- The queries and libraries must be autoformatted, for example using the "Format Document" command in [CodeQL for Visual Studio Code](https://help.semmle.com/codeql/codeql-for-vscode/procedures/about-codeql-for-vscode.html).
|
||||
|
||||
If you prefer, you can either:
|
||||
1. install the [pre-commit framework](https://pre-commit.com/) and install the configured hooks on this repo via `pre-commit install`, or
|
||||
2. use this [pre-commit hook](misc/scripts/pre-commit) that automatically checks whether your files are correctly formatted.
|
||||
|
||||
See the [pre-commit hook installation guide](docs/pre-commit-hook-setup.md) for instructions on the two approaches.
|
||||
If you prefer, you can use this [pre-commit hook](misc/scripts/pre-commit) that automatically checks whether your files are correctly formatted. See the [pre-commit hook installation guide](docs/pre-commit-hook-setup.md) for instructions on how to install the hook.
|
||||
|
||||
4. **Compilation**
|
||||
|
||||
@@ -67,6 +63,6 @@ After the experimental query is merged, we welcome pull requests to improve it.
|
||||
|
||||
## Using your personal data
|
||||
|
||||
If you contribute to this project, we will record your name and email address (as provided by you with your contributions) as part of the code repositories, which are public. We might also use this information to contact you in relation to your contributions, as well as in the normal course of software development. We also store records of CLA agreements signed in the past, but no longer require contributors to sign a CLA. Under GDPR legislation, we do this on the basis of our legitimate interest in creating the CodeQL product.
|
||||
If you contribute to this project, we will record your name and email address (as provided by you with your contributions) as part of the code repositories, which are public. We might also use this information to contact you in relation to your contributions, as well as in the normal course of software development. We also store records of CLA agreements signed in the past, but no longer require contributors to sign a CLA. Under GDPR legislation, we do this on the basis of our legitimate interest in creating the CodeQL product.
|
||||
|
||||
Please do get in touch (privacy@github.com) if you have any questions about this or our data protection policies.
|
||||
|
||||
@@ -426,6 +426,7 @@
|
||||
"python/ql/src/Lexical/CommentedOutCodeMetricOverview.inc.qhelp"
|
||||
],
|
||||
"FLinesOfDuplicatedCodeCommon.inc.qhelp": [
|
||||
"cpp/ql/src/Metrics/Files/FLinesOfDuplicatedCodeCommon.inc.qhelp",
|
||||
"java/ql/src/Metrics/Files/FLinesOfDuplicatedCodeCommon.inc.qhelp",
|
||||
"javascript/ql/src/Metrics/FLinesOfDuplicatedCodeCommon.inc.qhelp",
|
||||
"python/ql/src/Metrics/FLinesOfDuplicatedCodeCommon.inc.qhelp"
|
||||
@@ -464,8 +465,7 @@
|
||||
],
|
||||
"SensitiveDataHeuristics Python/JS": [
|
||||
"javascript/ql/lib/semmle/javascript/security/internal/SensitiveDataHeuristics.qll",
|
||||
"python/ql/lib/semmle/python/security/internal/SensitiveDataHeuristics.qll",
|
||||
"ruby/ql/lib/codeql/ruby/security/internal/SensitiveDataHeuristics.qll"
|
||||
"python/ql/lib/semmle/python/security/internal/SensitiveDataHeuristics.qll"
|
||||
],
|
||||
"ReDoS Util Python/JS/Ruby": [
|
||||
"javascript/ql/lib/semmle/javascript/security/performance/ReDoSUtil.qll",
|
||||
@@ -501,11 +501,5 @@
|
||||
"javascript/ql/lib/tutorial.qll",
|
||||
"python/ql/lib/tutorial.qll",
|
||||
"ruby/ql/lib/tutorial.qll"
|
||||
],
|
||||
"AccessPathSyntax": [
|
||||
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/AccessPathSyntax.qll",
|
||||
"java/ql/lib/semmle/code/java/dataflow/internal/AccessPathSyntax.qll",
|
||||
"javascript/ql/lib/semmle/javascript/frameworks/data/internal/AccessPathSyntax.qll",
|
||||
"ruby/ql/lib/codeql/ruby/dataflow/internal/AccessPathSyntax.qll"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<RuntimeIdentifiers>win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<AssemblyName>Semmle.Autobuild.Cpp</AssemblyName>
|
||||
<RootNamespace>Semmle.Autobuild.Cpp</RootNamespace>
|
||||
<ApplicationIcon />
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
+ semmlecode-cpp-queries/Critical/NewArrayDeleteMismatch.ql: /Correctness/Common Errors
|
||||
+ semmlecode-cpp-queries/Critical/NewDeleteArrayMismatch.ql: /Correctness/Common Errors
|
||||
+ semmlecode-cpp-queries/Critical/NewFreeMismatch.ql: /Correctness/Common Errors
|
||||
+ semmlecode-cpp-queries/Likely Bugs/Memory Management/UsingExpiredStackAddress.ql: /Correctness/Common Errors
|
||||
# Use of Libraries
|
||||
+ semmlecode-cpp-queries/Likely Bugs/Memory Management/SuspiciousCallToMemset.ql: /Correctness/Use of Libraries
|
||||
+ semmlecode-cpp-queries/Likely Bugs/Memory Management/SuspiciousSizeof.ql: /Correctness/Use of Libraries
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
+ semmlecode-cpp-queries/Critical/NewArrayDeleteMismatch.ql: /Correctness/Common Errors
|
||||
+ semmlecode-cpp-queries/Critical/NewDeleteArrayMismatch.ql: /Correctness/Common Errors
|
||||
+ semmlecode-cpp-queries/Critical/NewFreeMismatch.ql: /Correctness/Common Errors
|
||||
+ semmlecode-cpp-queries/Likely Bugs/Memory Management/UsingExpiredStackAddress.ql: /Correctness/Common Errors
|
||||
# Exceptions
|
||||
+ semmlecode-cpp-queries/Best Practices/Exceptions/AccidentalRethrow.ql: /Correctness/Exceptions
|
||||
+ semmlecode-cpp-queries/Best Practices/Exceptions/CatchingByValue.ql: /Correctness/Exceptions
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,2 +0,0 @@
|
||||
description: Remove unused legacy relations
|
||||
compatibility: backwards
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,3 +0,0 @@
|
||||
description: Add relation for tracking variables from structured binding declarations
|
||||
compatibility: full
|
||||
is_structured_binding.rel: delete
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
||||
name: codeql/cpp-downgrades
|
||||
groups: cpp
|
||||
downgrades: .
|
||||
library: true
|
||||
@@ -1,6 +1,4 @@
|
||||
name: codeql/cpp-examples
|
||||
groups:
|
||||
- cpp
|
||||
- examples
|
||||
version: 0.0.2
|
||||
dependencies:
|
||||
codeql/cpp-all: "*"
|
||||
|
||||
@@ -1,22 +1,3 @@
|
||||
## 0.0.10
|
||||
|
||||
### New Features
|
||||
|
||||
* Added a `isStructuredBinding` predicate to the `Variable` class which holds when the variable is declared as part of a structured binding declaration.
|
||||
|
||||
## 0.0.9
|
||||
|
||||
## 0.0.8
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* The `codeql/cpp-upgrades` CodeQL pack has been removed. All upgrades scripts have been merged into the `codeql/cpp-all` CodeQL pack.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* `FormatLiteral::getMaxConvertedLength` now uses range analysis to provide a
|
||||
more accurate length for integers formatted with `%x`
|
||||
|
||||
## 0.0.7
|
||||
|
||||
## 0.0.6
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: deprecated
|
||||
---
|
||||
* The `codeql/cpp-upgrades` CodeQL pack has been removed. All upgrades scripts have been merged into the `codeql/cpp-all` CodeQL pack.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* `FormatLiteral::getMaxConvertedLength` now uses range analysis to provide a
|
||||
more accurate length for integers formatted with `%x`
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Many queries now support structured bindings, as structured bindings are now handled in the IR translation.
|
||||
@@ -1,5 +0,0 @@
|
||||
## 0.0.10
|
||||
|
||||
### New Features
|
||||
|
||||
* Added a `isStructuredBinding` predicate to the `Variable` class which holds when the variable is declared as part of a structured binding declaration.
|
||||
@@ -1,10 +0,0 @@
|
||||
## 0.0.8
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* The `codeql/cpp-upgrades` CodeQL pack has been removed. All upgrades scripts have been merged into the `codeql/cpp-all` CodeQL pack.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* `FormatLiteral::getMaxConvertedLength` now uses range analysis to provide a
|
||||
more accurate length for integers formatted with `%x`
|
||||
@@ -1,2 +0,0 @@
|
||||
## 0.0.9
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.0.10
|
||||
lastReleaseVersion: 0.0.7
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/cpp-all
|
||||
version: 0.0.11-dev
|
||||
version: 0.0.8-dev
|
||||
groups: cpp
|
||||
dbscheme: semmlecode.cpp.dbscheme
|
||||
extractor: cpp
|
||||
|
||||
@@ -169,12 +169,6 @@ class Variable extends Declaration, @variable {
|
||||
variable_instantiation(underlyingElement(this), unresolveElement(v))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this variable is declated as part of a structured binding
|
||||
* declaration. For example, `x` in `auto [x, y] = ...`.
|
||||
*/
|
||||
predicate isStructuredBinding() { is_structured_binding(underlyingElement(this)) }
|
||||
|
||||
/**
|
||||
* Holds if this is a compiler-generated variable. For example, a
|
||||
* [range-based for loop](http://en.cppreference.com/w/cpp/language/range-for)
|
||||
|
||||
@@ -11,10 +11,10 @@ import cpp
|
||||
*/
|
||||
bindingset[input]
|
||||
int parseOctal(string input) {
|
||||
input.regexpMatch("0[0-7]+") and
|
||||
input.charAt(0) = "0" and
|
||||
result =
|
||||
strictsum(int ix |
|
||||
ix in [1 .. input.length()]
|
||||
ix in [0 .. input.length()]
|
||||
|
|
||||
8.pow(input.length() - (ix + 1)) * input.charAt(ix).toInt()
|
||||
)
|
||||
|
||||
@@ -1290,7 +1290,7 @@ class DataFlowCallOption extends TDataFlowCallOption {
|
||||
}
|
||||
}
|
||||
|
||||
/** A `Content` tagged with the type of a containing object. */
|
||||
/** Content tagged with the type of a containing object. */
|
||||
class TypedContent extends MkTypedContent {
|
||||
private Content c;
|
||||
private DataFlowType t;
|
||||
|
||||
@@ -592,14 +592,12 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
* Holds if data flows from `source` to `sink` in zero or more local
|
||||
* (intra-procedural) steps.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate localFlow(Node source, Node sink) { localFlowStep*(source, sink) }
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `e1` to `e2` in zero or more
|
||||
* local (intra-procedural) steps.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate localExprFlow(Expr e1, Expr e2) { localFlow(exprNode(e1), exprNode(e2)) }
|
||||
|
||||
/**
|
||||
|
||||
@@ -353,9 +353,9 @@ module FlowVar_internal {
|
||||
// indirection.
|
||||
result = def.getAUse(v)
|
||||
or
|
||||
exists(SsaDefinition descendantDef |
|
||||
this.getASuccessorSsaVar+() = TSsaVar(descendantDef, _) and
|
||||
result = descendantDef.getAUse(v)
|
||||
exists(SsaDefinition descendentDef |
|
||||
this.getASuccessorSsaVar+() = TSsaVar(descendentDef, _) and
|
||||
result = descendentDef.getAUse(v)
|
||||
)
|
||||
)
|
||||
or
|
||||
|
||||
@@ -124,14 +124,12 @@ predicate localAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeT
|
||||
* Holds if taint may propagate from `source` to `sink` in zero or more local
|
||||
* (intra-procedural) steps.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate localTaint(DataFlow::Node source, DataFlow::Node sink) { localTaintStep*(source, sink) }
|
||||
|
||||
/**
|
||||
* Holds if taint can flow from `e1` to `e2` in zero or more
|
||||
* local (intra-procedural) steps.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate localExprTaint(Expr e1, Expr e2) {
|
||||
localTaint(DataFlow::exprNode(e1), DataFlow::exprNode(e2))
|
||||
}
|
||||
|
||||
@@ -241,8 +241,8 @@ private module Cached {
|
||||
// For compatibility, send flow from arguments to parameters, even for
|
||||
// functions with no body.
|
||||
exists(FunctionCall call, int i |
|
||||
sink.asExpr() = call.getArgument(pragma[only_bind_into](i)) and
|
||||
result = resolveCall(call).getParameter(pragma[only_bind_into](i))
|
||||
sink.asExpr() = call.getArgument(i) and
|
||||
result = resolveCall(call).getParameter(i)
|
||||
)
|
||||
or
|
||||
// For compatibility, send flow into a `Variable` if there is flow to any
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
/**
|
||||
* This file provides a library for inter-procedural must-flow data flow analysis.
|
||||
* Unlike `DataFlow.qll`, the analysis provided by this file checks whether data _must_ flow
|
||||
* from a source to a _sink_.
|
||||
*/
|
||||
|
||||
private import cpp
|
||||
import semmle.code.cpp.ir.dataflow.DataFlow
|
||||
private import semmle.code.cpp.ir.IR
|
||||
|
||||
/**
|
||||
* A configuration of a data flow analysis that performs must-flow analysis. This is different
|
||||
* from `DataFlow.qll` which performs may-flow analysis (i.e., it finds paths where the source _may_
|
||||
* flow to the sink).
|
||||
*
|
||||
* Like in `DataFlow.qll`, each use of the `MustFlow.qll` library must define its own unique extension
|
||||
* of this abstract class. To create a configuration, extend this class with a subclass whose
|
||||
* characteristic predicate is a unique singleton string and override `isSource`, `isSink` (and
|
||||
* `isAdditionalFlowStep` if additional steps are required).
|
||||
*/
|
||||
abstract class MustFlowConfiguration extends string {
|
||||
bindingset[this]
|
||||
MustFlowConfiguration() { any() }
|
||||
|
||||
/**
|
||||
* Holds if `source` is a relevant data flow source.
|
||||
*/
|
||||
abstract predicate isSource(DataFlow::Node source);
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant data flow sink.
|
||||
*/
|
||||
abstract predicate isSink(DataFlow::Node sink);
|
||||
|
||||
/**
|
||||
* Holds if the additional flow step from `node1` to `node2` must be taken
|
||||
* into account in the analysis.
|
||||
*/
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data must flow from `source` to `sink` for this configuration.
|
||||
*
|
||||
* The corresponding paths are generated from the end-points and the graph
|
||||
* included in the module `PathGraph`.
|
||||
*/
|
||||
final predicate hasFlowPath(MustFlowPathNode source, MustFlowPathSink sink) {
|
||||
this.isSource(source.getNode()) and
|
||||
source.getASuccessor+() = sink
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if `node` flows from a source. */
|
||||
pragma[nomagic]
|
||||
private predicate flowsFromSource(DataFlow::Node node, MustFlowConfiguration config) {
|
||||
config.isSource(node)
|
||||
or
|
||||
exists(DataFlow::Node mid |
|
||||
step(mid, node, config) and
|
||||
flowsFromSource(mid, pragma[only_bind_into](config))
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `node` flows to a sink. */
|
||||
pragma[nomagic]
|
||||
private predicate flowsToSink(DataFlow::Node node, MustFlowConfiguration config) {
|
||||
flowsFromSource(node, pragma[only_bind_into](config)) and
|
||||
(
|
||||
config.isSink(node)
|
||||
or
|
||||
exists(DataFlow::Node mid |
|
||||
step(node, mid, config) and
|
||||
flowsToSink(mid, pragma[only_bind_into](config))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
private module Cached {
|
||||
/** Holds if `p` is the `n`'th parameter of the non-virtual function `f`. */
|
||||
private predicate parameterOf(Parameter p, Function f, int n) {
|
||||
not f.isVirtual() and f.getParameter(n) = p
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `instr` is the `n`'th argument to a call to the non-virtual function `f`, and
|
||||
* `init` is the corresponding initialization instruction that receives the value of `instr` in `f`.
|
||||
*/
|
||||
private predicate flowIntoParameter(
|
||||
Function f, int n, CallInstruction call, Instruction instr, InitializeParameterInstruction init
|
||||
) {
|
||||
not f.isVirtual() and
|
||||
call.getPositionalArgument(n) = instr and
|
||||
f = call.getStaticCallTarget() and
|
||||
getEnclosingNonVirtualFunctionInitializeParameter(init, f) and
|
||||
init.getParameter().getIndex() = pragma[only_bind_into](pragma[only_bind_out](n))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `instr` is an argument to a call to the function `f`, and `init` is the
|
||||
* corresponding initialization instruction that receives the value of `instr` in `f`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate getPositionalArgumentInitParam(
|
||||
CallInstruction call, Instruction instr, InitializeParameterInstruction init, Function f
|
||||
) {
|
||||
exists(int n |
|
||||
parameterOf(_, f, n) and
|
||||
flowIntoParameter(f, pragma[only_bind_into](pragma[only_bind_out](n)), call, instr, init)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `instr` is the qualifier to a call to the non-virtual function `f`, and
|
||||
* `init` is the corresponding initialization instruction that receives the value of
|
||||
* `instr` in `f`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate getThisArgumentInitParam(
|
||||
CallInstruction call, Instruction instr, InitializeParameterInstruction init, Function f
|
||||
) {
|
||||
not f.isVirtual() and
|
||||
call.getStaticCallTarget() = f and
|
||||
getEnclosingNonVirtualFunctionInitializeParameter(init, f) and
|
||||
call.getThisArgument() = instr and
|
||||
init.getIRVariable() instanceof IRThisVariable
|
||||
}
|
||||
|
||||
/** Holds if `f` is the enclosing non-virtual function of `init`. */
|
||||
private predicate getEnclosingNonVirtualFunctionInitializeParameter(
|
||||
InitializeParameterInstruction init, Function f
|
||||
) {
|
||||
not f.isVirtual() and
|
||||
init.getEnclosingFunction() = f
|
||||
}
|
||||
|
||||
/** Holds if `f` is the enclosing non-virtual function of `init`. */
|
||||
private predicate getEnclosingNonVirtualFunctionInitializeIndirection(
|
||||
InitializeIndirectionInstruction init, Function f
|
||||
) {
|
||||
not f.isVirtual() and
|
||||
init.getEnclosingFunction() = f
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `instr` is an argument (or argument indirection) to a call, and
|
||||
* `succ` is the corresponding initialization instruction in the call target.
|
||||
*/
|
||||
private predicate flowThroughCallable(Instruction argument, Instruction parameter) {
|
||||
// Flow from an argument to a parameter
|
||||
exists(CallInstruction call, InitializeParameterInstruction init | init = parameter |
|
||||
getPositionalArgumentInitParam(call, argument, init, call.getStaticCallTarget())
|
||||
or
|
||||
getThisArgumentInitParam(call, argument, init, call.getStaticCallTarget())
|
||||
)
|
||||
or
|
||||
// Flow from argument indirection to parameter indirection
|
||||
exists(
|
||||
CallInstruction call, ReadSideEffectInstruction read, InitializeIndirectionInstruction init
|
||||
|
|
||||
init = parameter and
|
||||
read.getPrimaryInstruction() = call and
|
||||
getEnclosingNonVirtualFunctionInitializeIndirection(init, call.getStaticCallTarget())
|
||||
|
|
||||
exists(int n |
|
||||
read.getSideEffectOperand().getAnyDef() = argument and
|
||||
read.getIndex() = pragma[only_bind_into](n) and
|
||||
init.getParameter().getIndex() = pragma[only_bind_into](n)
|
||||
)
|
||||
or
|
||||
call.getThisArgument() = argument and
|
||||
init.getIRVariable() instanceof IRThisVariable
|
||||
)
|
||||
}
|
||||
|
||||
private predicate instructionToOperandStep(Instruction instr, Operand operand) {
|
||||
operand.getDef() = instr
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data flows from `operand` to `instr`.
|
||||
*
|
||||
* This predicate ignores flow through `PhiInstruction`s to create a 'must flow' relation.
|
||||
*/
|
||||
private predicate operandToInstructionStep(Operand operand, Instruction instr) {
|
||||
instr.(CopyInstruction).getSourceValueOperand() = operand
|
||||
or
|
||||
instr.(ConvertInstruction).getUnaryOperand() = operand
|
||||
or
|
||||
instr.(CheckedConvertOrNullInstruction).getUnaryOperand() = operand
|
||||
or
|
||||
instr.(InheritanceConversionInstruction).getUnaryOperand() = operand
|
||||
or
|
||||
instr.(ChiInstruction).getTotalOperand() = operand
|
||||
}
|
||||
|
||||
cached
|
||||
predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
instructionToOperandStep(nodeFrom.asInstruction(), nodeTo.asOperand())
|
||||
or
|
||||
flowThroughCallable(nodeFrom.asInstruction(), nodeTo.asInstruction())
|
||||
or
|
||||
operandToInstructionStep(nodeFrom.asOperand(), nodeTo.asInstruction())
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if `nodeFrom` flows to `nodeTo`. */
|
||||
private predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, MustFlowConfiguration config) {
|
||||
exists(config) and
|
||||
Cached::step(nodeFrom, nodeTo)
|
||||
or
|
||||
config.isAdditionalFlowStep(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
private newtype TLocalPathNode =
|
||||
MkLocalPathNode(DataFlow::Node n, MustFlowConfiguration config) {
|
||||
flowsToSink(n, config) and
|
||||
(
|
||||
config.isSource(n)
|
||||
or
|
||||
exists(MustFlowPathNode mid | step(mid.getNode(), n, config))
|
||||
)
|
||||
}
|
||||
|
||||
/** A `Node` that is in a path from a source to a sink. */
|
||||
class MustFlowPathNode extends TLocalPathNode {
|
||||
DataFlow::Node n;
|
||||
|
||||
MustFlowPathNode() { this = MkLocalPathNode(n, _) }
|
||||
|
||||
/** Gets the underlying node. */
|
||||
DataFlow::Node getNode() { result = n }
|
||||
|
||||
/** Gets a textual representation of this node. */
|
||||
string toString() { result = n.toString() }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
Location getLocation() { result = n.getLocation() }
|
||||
|
||||
/** Gets a successor node, if any. */
|
||||
MustFlowPathNode getASuccessor() {
|
||||
step(this.getNode(), result.getNode(), this.getConfiguration())
|
||||
}
|
||||
|
||||
/** Gets the associated configuration. */
|
||||
MustFlowConfiguration getConfiguration() { this = MkLocalPathNode(_, result) }
|
||||
}
|
||||
|
||||
private class MustFlowPathSink extends MustFlowPathNode {
|
||||
MustFlowPathSink() { this.getConfiguration().isSink(this.getNode()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the query predicates needed to include a graph in a path-problem query.
|
||||
*/
|
||||
module PathGraph {
|
||||
private predicate reach(MustFlowPathNode n) {
|
||||
n instanceof MustFlowPathSink or reach(n.getASuccessor())
|
||||
}
|
||||
|
||||
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
|
||||
query predicate edges(MustFlowPathNode a, MustFlowPathNode b) {
|
||||
a.getASuccessor() = b and reach(b)
|
||||
}
|
||||
|
||||
/** Holds if `n` is a node in the graph of data flow path explanations. */
|
||||
query predicate nodes(MustFlowPathNode n, string key, string val) {
|
||||
reach(n) and key = "semmle.label" and val = n.toString()
|
||||
}
|
||||
}
|
||||
@@ -1290,7 +1290,7 @@ class DataFlowCallOption extends TDataFlowCallOption {
|
||||
}
|
||||
}
|
||||
|
||||
/** A `Content` tagged with the type of a containing object. */
|
||||
/** Content tagged with the type of a containing object. */
|
||||
class TypedContent extends MkTypedContent {
|
||||
private Content c;
|
||||
private DataFlowType t;
|
||||
|
||||
@@ -1032,14 +1032,12 @@ SideEffectInstruction getSideEffectFor(CallInstruction call, int argument) {
|
||||
* Holds if data flows from `source` to `sink` in zero or more local
|
||||
* (intra-procedural) steps.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate localFlow(Node source, Node sink) { localFlowStep*(source, sink) }
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `i1` to `i2` in zero or more
|
||||
* local (intra-procedural) steps.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate localInstructionFlow(Instruction e1, Instruction e2) {
|
||||
localFlow(instructionNode(e1), instructionNode(e2))
|
||||
}
|
||||
@@ -1048,7 +1046,6 @@ predicate localInstructionFlow(Instruction e1, Instruction e2) {
|
||||
* Holds if data can flow from `e1` to `e2` in zero or more
|
||||
* local (intra-procedural) steps.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate localExprFlow(Expr e1, Expr e2) { localFlow(exprNode(e1), exprNode(e2)) }
|
||||
|
||||
private newtype TContent =
|
||||
|
||||
@@ -121,14 +121,12 @@ private predicate operandToInstructionTaintStep(Operand opFrom, Instruction inst
|
||||
* Holds if taint may propagate from `source` to `sink` in zero or more local
|
||||
* (intra-procedural) steps.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate localTaint(DataFlow::Node source, DataFlow::Node sink) { localTaintStep*(source, sink) }
|
||||
|
||||
/**
|
||||
* Holds if taint can flow from `i1` to `i2` in zero or more
|
||||
* local (intra-procedural) steps.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate localInstructionTaint(Instruction i1, Instruction i2) {
|
||||
localTaint(DataFlow::instructionNode(i1), DataFlow::instructionNode(i2))
|
||||
}
|
||||
@@ -137,7 +135,6 @@ predicate localInstructionTaint(Instruction i1, Instruction i2) {
|
||||
* Holds if taint can flow from `e1` to `e2` in zero or more
|
||||
* local (intra-procedural) steps.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate localExprTaint(Expr e1, Expr e2) {
|
||||
localTaint(DataFlow::exprNode(e1), DataFlow::exprNode(e2))
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ class IRBlock extends IRBlockBase {
|
||||
* post-dominate block `B`, but block `A` does post-dominate an immediate successor of block `B`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
final IRBlock postDominanceFrontier() {
|
||||
final IRBlock postPominanceFrontier() {
|
||||
this.postDominates(result.getASuccessor()) and
|
||||
not this.strictlyPostDominates(result)
|
||||
}
|
||||
|
||||
@@ -106,12 +106,6 @@ private predicate filteredNumberableInstruction(Instruction instr) {
|
||||
or
|
||||
instr instanceof FieldAddressInstruction and
|
||||
count(instr.(FieldAddressInstruction).getField()) != 1
|
||||
or
|
||||
instr instanceof InheritanceConversionInstruction and
|
||||
(
|
||||
count(instr.(InheritanceConversionInstruction).getBaseClass()) != 1 or
|
||||
count(instr.(InheritanceConversionInstruction).getDerivedClass()) != 1
|
||||
)
|
||||
}
|
||||
|
||||
private predicate variableAddressValueNumber(
|
||||
@@ -121,7 +115,8 @@ private predicate variableAddressValueNumber(
|
||||
// The underlying AST element is used as value-numbering key instead of the
|
||||
// `IRVariable` to work around a problem where a variable or expression with
|
||||
// multiple types gives rise to multiple `IRVariable`s.
|
||||
unique( | | instr.getIRVariable().getAST()) = ast
|
||||
instr.getIRVariable().getAST() = ast and
|
||||
strictcount(instr.getIRVariable().getAST()) = 1
|
||||
}
|
||||
|
||||
private predicate initializeParameterValueNumber(
|
||||
@@ -138,7 +133,8 @@ private predicate constantValueNumber(
|
||||
ConstantInstruction instr, IRFunction irFunc, IRType type, string value
|
||||
) {
|
||||
instr.getEnclosingIRFunction() = irFunc and
|
||||
unique( | | instr.getResultIRType()) = type and
|
||||
strictcount(instr.getResultIRType()) = 1 and
|
||||
instr.getResultIRType() = type and
|
||||
instr.getValue() = value
|
||||
}
|
||||
|
||||
@@ -155,7 +151,8 @@ private predicate fieldAddressValueNumber(
|
||||
TValueNumber objectAddress
|
||||
) {
|
||||
instr.getEnclosingIRFunction() = irFunc and
|
||||
unique( | | instr.getField()) = field and
|
||||
instr.getField() = field and
|
||||
strictcount(instr.getField()) = 1 and
|
||||
tvalueNumber(instr.getObjectAddress()) = objectAddress
|
||||
}
|
||||
|
||||
@@ -198,9 +195,9 @@ private predicate inheritanceConversionValueNumber(
|
||||
) {
|
||||
instr.getEnclosingIRFunction() = irFunc and
|
||||
instr.getOpcode() = opcode and
|
||||
tvalueNumber(instr.getUnary()) = operand and
|
||||
unique( | | instr.getBaseClass()) = baseClass and
|
||||
unique( | | instr.getDerivedClass()) = derivedClass
|
||||
instr.getBaseClass() = baseClass and
|
||||
instr.getDerivedClass() = derivedClass and
|
||||
tvalueNumber(instr.getUnary()) = operand
|
||||
}
|
||||
|
||||
private predicate loadTotalOverlapValueNumber(
|
||||
|
||||
@@ -200,7 +200,7 @@ class IRBlock extends IRBlockBase {
|
||||
* post-dominate block `B`, but block `A` does post-dominate an immediate successor of block `B`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
final IRBlock postDominanceFrontier() {
|
||||
final IRBlock postPominanceFrontier() {
|
||||
this.postDominates(result.getASuccessor()) and
|
||||
not this.strictlyPostDominates(result)
|
||||
}
|
||||
|
||||
@@ -106,12 +106,6 @@ private predicate filteredNumberableInstruction(Instruction instr) {
|
||||
or
|
||||
instr instanceof FieldAddressInstruction and
|
||||
count(instr.(FieldAddressInstruction).getField()) != 1
|
||||
or
|
||||
instr instanceof InheritanceConversionInstruction and
|
||||
(
|
||||
count(instr.(InheritanceConversionInstruction).getBaseClass()) != 1 or
|
||||
count(instr.(InheritanceConversionInstruction).getDerivedClass()) != 1
|
||||
)
|
||||
}
|
||||
|
||||
private predicate variableAddressValueNumber(
|
||||
@@ -121,7 +115,8 @@ private predicate variableAddressValueNumber(
|
||||
// The underlying AST element is used as value-numbering key instead of the
|
||||
// `IRVariable` to work around a problem where a variable or expression with
|
||||
// multiple types gives rise to multiple `IRVariable`s.
|
||||
unique( | | instr.getIRVariable().getAST()) = ast
|
||||
instr.getIRVariable().getAST() = ast and
|
||||
strictcount(instr.getIRVariable().getAST()) = 1
|
||||
}
|
||||
|
||||
private predicate initializeParameterValueNumber(
|
||||
@@ -138,7 +133,8 @@ private predicate constantValueNumber(
|
||||
ConstantInstruction instr, IRFunction irFunc, IRType type, string value
|
||||
) {
|
||||
instr.getEnclosingIRFunction() = irFunc and
|
||||
unique( | | instr.getResultIRType()) = type and
|
||||
strictcount(instr.getResultIRType()) = 1 and
|
||||
instr.getResultIRType() = type and
|
||||
instr.getValue() = value
|
||||
}
|
||||
|
||||
@@ -155,7 +151,8 @@ private predicate fieldAddressValueNumber(
|
||||
TValueNumber objectAddress
|
||||
) {
|
||||
instr.getEnclosingIRFunction() = irFunc and
|
||||
unique( | | instr.getField()) = field and
|
||||
instr.getField() = field and
|
||||
strictcount(instr.getField()) = 1 and
|
||||
tvalueNumber(instr.getObjectAddress()) = objectAddress
|
||||
}
|
||||
|
||||
@@ -198,9 +195,9 @@ private predicate inheritanceConversionValueNumber(
|
||||
) {
|
||||
instr.getEnclosingIRFunction() = irFunc and
|
||||
instr.getOpcode() = opcode and
|
||||
tvalueNumber(instr.getUnary()) = operand and
|
||||
unique( | | instr.getBaseClass()) = baseClass and
|
||||
unique( | | instr.getDerivedClass()) = derivedClass
|
||||
instr.getBaseClass() = baseClass and
|
||||
instr.getDerivedClass() = derivedClass and
|
||||
tvalueNumber(instr.getUnary()) = operand
|
||||
}
|
||||
|
||||
private predicate loadTotalOverlapValueNumber(
|
||||
|
||||
@@ -71,8 +71,7 @@ newtype TInstructionTag =
|
||||
AsmTag() or
|
||||
AsmInputTag(int elementIndex) { exists(AsmStmt asm | exists(asm.getChild(elementIndex))) } or
|
||||
ThisAddressTag() or
|
||||
ThisLoadTag() or
|
||||
StructuredBindingAccessTag()
|
||||
ThisLoadTag()
|
||||
|
||||
class InstructionTag extends TInstructionTag {
|
||||
final string toString() { result = "Tag" }
|
||||
@@ -222,6 +221,4 @@ string getInstructionTagId(TInstructionTag tag) {
|
||||
tag = ThisAddressTag() and result = "ThisAddress"
|
||||
or
|
||||
tag = ThisLoadTag() and result = "ThisLoad"
|
||||
or
|
||||
tag = StructuredBindingAccessTag() and result = "StructuredBindingAccess"
|
||||
}
|
||||
|
||||
@@ -567,13 +567,6 @@ newtype TTranslatedElement =
|
||||
} or
|
||||
// The initialization of a base class from within a constructor.
|
||||
TTranslatedConstructorBaseInit(ConstructorBaseInit init) { not ignoreExpr(init) } or
|
||||
// Workaround for a case where no base constructor is generated but a targetless base
|
||||
// constructor call is present.
|
||||
TTranslatedConstructorBareInit(ConstructorInit init) {
|
||||
not ignoreExpr(init) and
|
||||
not init instanceof ConstructorBaseInit and
|
||||
not init instanceof ConstructorFieldInit
|
||||
} or
|
||||
// The destruction of a base class from within a destructor.
|
||||
TTranslatedDestructorBaseDestruction(DestructorBaseDestruction destruction) {
|
||||
not ignoreExpr(destruction)
|
||||
|
||||
@@ -3,7 +3,6 @@ private import semmle.code.cpp.ir.implementation.IRType
|
||||
private import semmle.code.cpp.ir.implementation.Opcode
|
||||
private import semmle.code.cpp.ir.implementation.internal.OperandTag
|
||||
private import semmle.code.cpp.ir.internal.CppType
|
||||
private import semmle.code.cpp.ir.internal.IRUtilities
|
||||
private import semmle.code.cpp.ir.internal.TempVariableTag
|
||||
private import InstructionTag
|
||||
private import TranslatedCondition
|
||||
@@ -814,9 +813,7 @@ abstract class TranslatedVariableAccess extends TranslatedNonConstantExpr {
|
||||
}
|
||||
|
||||
class TranslatedNonFieldVariableAccess extends TranslatedVariableAccess {
|
||||
TranslatedNonFieldVariableAccess() {
|
||||
not expr instanceof FieldAccess and not isNonReferenceStructuredBinding(expr.getTarget())
|
||||
}
|
||||
TranslatedNonFieldVariableAccess() { not expr instanceof FieldAccess }
|
||||
|
||||
override Instruction getFirstInstruction() {
|
||||
if exists(this.getQualifier())
|
||||
@@ -863,71 +860,6 @@ class TranslatedFieldAccess extends TranslatedVariableAccess {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The IR translation of a variable access of a structured binding, where the type
|
||||
* of the structured binding is not of a reference type, e.g., `x0` and `x1`
|
||||
* in `auto [x0, x1] = xs` where `xs` is an array. Although the type of the
|
||||
* structured binding is a non-reference type, the structured binding behaves
|
||||
* like a reference. Hence, the translation requires a `VariableAddress` followed
|
||||
* by a `Load` instead of only a `VariableAddress` as produced by
|
||||
* `TranslatedVariableAccess`.
|
||||
*/
|
||||
class TranslatedStructuredBindingVariableAccess extends TranslatedNonConstantExpr {
|
||||
override VariableAccess expr;
|
||||
|
||||
TranslatedStructuredBindingVariableAccess() { isNonReferenceStructuredBinding(expr.getTarget()) }
|
||||
|
||||
override Instruction getFirstInstruction() {
|
||||
// Structured bindings cannot be qualified.
|
||||
result = this.getInstruction(StructuredBindingAccessTag())
|
||||
}
|
||||
|
||||
override TranslatedElement getChild(int id) {
|
||||
// Structured bindings cannot be qualified.
|
||||
none()
|
||||
}
|
||||
|
||||
override Instruction getResult() { result = this.getInstruction(LoadTag()) }
|
||||
|
||||
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
|
||||
tag = StructuredBindingAccessTag() and
|
||||
kind instanceof GotoEdge and
|
||||
result = this.getInstruction(LoadTag())
|
||||
or
|
||||
tag = LoadTag() and
|
||||
kind instanceof GotoEdge and
|
||||
result = this.getParent().getChildSuccessor(this)
|
||||
}
|
||||
|
||||
override Instruction getChildSuccessor(TranslatedElement child) { none() }
|
||||
|
||||
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
|
||||
tag = StructuredBindingAccessTag() and
|
||||
opcode instanceof Opcode::VariableAddress and
|
||||
resultType = getTypeForGLValue(this.getLValueReferenceType())
|
||||
or
|
||||
tag = LoadTag() and
|
||||
opcode instanceof Opcode::Load and
|
||||
resultType = getTypeForPRValue(this.getLValueReferenceType())
|
||||
}
|
||||
|
||||
private LValueReferenceType getLValueReferenceType() {
|
||||
// The extractor ensures `result` exists when `isNonReferenceStructuredBinding(expr.getTarget())` holds.
|
||||
result.getBaseType() = expr.getUnspecifiedType()
|
||||
}
|
||||
|
||||
override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
|
||||
tag = LoadTag() and
|
||||
operandTag instanceof AddressOperandTag and
|
||||
result = this.getInstruction(StructuredBindingAccessTag())
|
||||
}
|
||||
|
||||
override IRVariable getInstructionVariable(InstructionTag tag) {
|
||||
tag = StructuredBindingAccessTag() and
|
||||
result = getIRUserVariable(expr.getEnclosingFunction(), expr.getTarget())
|
||||
}
|
||||
}
|
||||
|
||||
class TranslatedFunctionAccess extends TranslatedNonConstantExpr {
|
||||
override FunctionAccess expr;
|
||||
|
||||
|
||||
@@ -573,11 +573,6 @@ class TranslatedConstructorInitList extends TranslatedElement, InitializationCon
|
||||
baseInit = func.(Constructor).getInitializer(id) and
|
||||
result = getTranslatedConstructorBaseInit(baseInit)
|
||||
)
|
||||
or
|
||||
exists(ConstructorInit bareInit |
|
||||
bareInit = func.(Constructor).getInitializer(id) and
|
||||
result = getTranslatedConstructorBareInit(bareInit)
|
||||
)
|
||||
}
|
||||
|
||||
override Instruction getFirstInstruction() {
|
||||
|
||||
@@ -917,36 +917,3 @@ class TranslatedDestructorBaseDestruction extends TranslatedBaseStructorCall,
|
||||
|
||||
final override string toString() { result = "destroy base: " + call.toString() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A constructor base init call where no base constructor has been generated.
|
||||
*
|
||||
* Workaround for an extractor issue.
|
||||
*/
|
||||
class TranslatedConstructorBareInit extends TranslatedElement, TTranslatedConstructorBareInit {
|
||||
ConstructorInit init;
|
||||
|
||||
TranslatedConstructorBareInit() { this = TTranslatedConstructorBareInit(init) }
|
||||
|
||||
override Locatable getAST() { result = init }
|
||||
|
||||
final override string toString() { result = "construct base (no constructor)" }
|
||||
|
||||
override Instruction getFirstInstruction() { result = getParent().getChildSuccessor(this) }
|
||||
|
||||
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
|
||||
none()
|
||||
}
|
||||
|
||||
override TranslatedElement getChild(int id) { none() }
|
||||
|
||||
override Function getFunction() { result = getParent().getFunction() }
|
||||
|
||||
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) { none() }
|
||||
|
||||
override Instruction getChildSuccessor(TranslatedElement child) { none() }
|
||||
}
|
||||
|
||||
TranslatedConstructorBareInit getTranslatedConstructorBareInit(ConstructorInit init) {
|
||||
result.getAST() = init
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ class IRBlock extends IRBlockBase {
|
||||
* post-dominate block `B`, but block `A` does post-dominate an immediate successor of block `B`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
final IRBlock postDominanceFrontier() {
|
||||
final IRBlock postPominanceFrontier() {
|
||||
this.postDominates(result.getASuccessor()) and
|
||||
not this.strictlyPostDominates(result)
|
||||
}
|
||||
|
||||
@@ -106,12 +106,6 @@ private predicate filteredNumberableInstruction(Instruction instr) {
|
||||
or
|
||||
instr instanceof FieldAddressInstruction and
|
||||
count(instr.(FieldAddressInstruction).getField()) != 1
|
||||
or
|
||||
instr instanceof InheritanceConversionInstruction and
|
||||
(
|
||||
count(instr.(InheritanceConversionInstruction).getBaseClass()) != 1 or
|
||||
count(instr.(InheritanceConversionInstruction).getDerivedClass()) != 1
|
||||
)
|
||||
}
|
||||
|
||||
private predicate variableAddressValueNumber(
|
||||
@@ -121,7 +115,8 @@ private predicate variableAddressValueNumber(
|
||||
// The underlying AST element is used as value-numbering key instead of the
|
||||
// `IRVariable` to work around a problem where a variable or expression with
|
||||
// multiple types gives rise to multiple `IRVariable`s.
|
||||
unique( | | instr.getIRVariable().getAST()) = ast
|
||||
instr.getIRVariable().getAST() = ast and
|
||||
strictcount(instr.getIRVariable().getAST()) = 1
|
||||
}
|
||||
|
||||
private predicate initializeParameterValueNumber(
|
||||
@@ -138,7 +133,8 @@ private predicate constantValueNumber(
|
||||
ConstantInstruction instr, IRFunction irFunc, IRType type, string value
|
||||
) {
|
||||
instr.getEnclosingIRFunction() = irFunc and
|
||||
unique( | | instr.getResultIRType()) = type and
|
||||
strictcount(instr.getResultIRType()) = 1 and
|
||||
instr.getResultIRType() = type and
|
||||
instr.getValue() = value
|
||||
}
|
||||
|
||||
@@ -155,7 +151,8 @@ private predicate fieldAddressValueNumber(
|
||||
TValueNumber objectAddress
|
||||
) {
|
||||
instr.getEnclosingIRFunction() = irFunc and
|
||||
unique( | | instr.getField()) = field and
|
||||
instr.getField() = field and
|
||||
strictcount(instr.getField()) = 1 and
|
||||
tvalueNumber(instr.getObjectAddress()) = objectAddress
|
||||
}
|
||||
|
||||
@@ -198,9 +195,9 @@ private predicate inheritanceConversionValueNumber(
|
||||
) {
|
||||
instr.getEnclosingIRFunction() = irFunc and
|
||||
instr.getOpcode() = opcode and
|
||||
tvalueNumber(instr.getUnary()) = operand and
|
||||
unique( | | instr.getBaseClass()) = baseClass and
|
||||
unique( | | instr.getDerivedClass()) = derivedClass
|
||||
instr.getBaseClass() = baseClass and
|
||||
instr.getDerivedClass() = derivedClass and
|
||||
tvalueNumber(instr.getUnary()) = operand
|
||||
}
|
||||
|
||||
private predicate loadTotalOverlapValueNumber(
|
||||
|
||||
@@ -11,15 +11,6 @@ private Type getDecayedType(Type type) {
|
||||
result.(PointerType).getBaseType() = type.(ArrayType).getBaseType()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the sepcified variable is a structured binding with a non-reference
|
||||
* type.
|
||||
*/
|
||||
predicate isNonReferenceStructuredBinding(Variable v) {
|
||||
v.isStructuredBinding() and
|
||||
not v.getUnspecifiedType() instanceof ReferenceType
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actual type of the specified variable, as opposed to the declared type.
|
||||
* This returns the type of the variable after any pointer decay is applied, and
|
||||
@@ -39,12 +30,7 @@ Type getVariableType(Variable v) {
|
||||
result = v.getInitializer().getExpr().getType()
|
||||
or
|
||||
not exists(v.getInitializer()) and result = v.getType()
|
||||
else
|
||||
if isNonReferenceStructuredBinding(v)
|
||||
then
|
||||
// The extractor ensures `r` exists when `isNonReferenceStructuredBinding(v)` holds.
|
||||
exists(LValueReferenceType r | r.getBaseType() = v.getUnspecifiedType() | result = r)
|
||||
else result = v.getType()
|
||||
else result = v.getType()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,14 +11,15 @@ import semmle.code.cpp.models.interfaces.SideEffect
|
||||
import semmle.code.cpp.models.interfaces.FlowSource
|
||||
|
||||
/**
|
||||
* The standard functions `fgets` and `fgetws`.
|
||||
* The standard functions `gets` and `fgets`.
|
||||
*/
|
||||
private class FgetsFunction extends DataFlowFunction, TaintFunction, ArrayFunction, AliasFunction,
|
||||
private class GetsFunction extends DataFlowFunction, TaintFunction, ArrayFunction, AliasFunction,
|
||||
SideEffectFunction, RemoteFlowSourceFunction {
|
||||
FgetsFunction() {
|
||||
GetsFunction() {
|
||||
// gets(str)
|
||||
// fgets(str, num, stream)
|
||||
// fgetws(wstr, num, stream)
|
||||
this.hasGlobalOrStdOrBslName(["fgets", "fgetws"])
|
||||
this.hasGlobalOrStdOrBslName(["gets", "fgets", "fgetws"])
|
||||
}
|
||||
|
||||
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
|
||||
@@ -50,61 +51,20 @@ private class FgetsFunction extends DataFlowFunction, TaintFunction, ArrayFuncti
|
||||
override predicate hasRemoteFlowSource(FunctionOutput output, string description) {
|
||||
output.isParameterDeref(0) and
|
||||
description = "String read by " + this.getName()
|
||||
or
|
||||
output.isReturnValue() and
|
||||
description = "String read by " + this.getName()
|
||||
}
|
||||
|
||||
override predicate hasArrayWithVariableSize(int bufParam, int countParam) {
|
||||
not this.hasName("gets") and
|
||||
bufParam = 0 and
|
||||
countParam = 1
|
||||
}
|
||||
|
||||
override predicate hasArrayWithUnknownSize(int bufParam) {
|
||||
this.hasName("gets") and
|
||||
bufParam = 0
|
||||
}
|
||||
|
||||
override predicate hasArrayOutput(int bufParam) { bufParam = 0 }
|
||||
|
||||
override predicate hasSocketInput(FunctionInput input) { input.isParameterDeref(2) }
|
||||
}
|
||||
|
||||
/**
|
||||
* The standard functions `gets`.
|
||||
*/
|
||||
private class GetsFunction extends DataFlowFunction, ArrayFunction, AliasFunction,
|
||||
SideEffectFunction, LocalFlowSourceFunction {
|
||||
GetsFunction() {
|
||||
// gets(str)
|
||||
this.hasGlobalOrStdOrBslName("gets")
|
||||
}
|
||||
|
||||
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
|
||||
input.isParameter(0) and
|
||||
output.isReturnValue()
|
||||
}
|
||||
|
||||
override predicate parameterNeverEscapes(int index) { none() }
|
||||
|
||||
override predicate parameterEscapesOnlyViaReturn(int index) { index = 0 }
|
||||
|
||||
override predicate parameterIsAlwaysReturned(int index) { index = 0 }
|
||||
|
||||
override predicate hasOnlySpecificReadSideEffects() { any() }
|
||||
|
||||
override predicate hasOnlySpecificWriteSideEffects() { any() }
|
||||
|
||||
override predicate hasSpecificWriteSideEffect(ParameterIndex i, boolean buffer, boolean mustWrite) {
|
||||
i = 0 and
|
||||
buffer = true and
|
||||
mustWrite = true
|
||||
}
|
||||
|
||||
override predicate hasLocalFlowSource(FunctionOutput output, string description) {
|
||||
output.isParameterDeref(0) and
|
||||
description = "String read by " + this.getName()
|
||||
or
|
||||
output.isReturnValue() and
|
||||
description = "String read by " + this.getName()
|
||||
}
|
||||
|
||||
override predicate hasArrayWithUnknownSize(int bufParam) { bufParam = 0 }
|
||||
|
||||
override predicate hasArrayOutput(int bufParam) { bufParam = 0 }
|
||||
override predicate hasSocketInput(FunctionInput input) { input.isParameter(2) }
|
||||
}
|
||||
|
||||
@@ -135,11 +135,52 @@ externalData(
|
||||
string value : string ref
|
||||
);
|
||||
|
||||
/**
|
||||
* The date of the snapshot.
|
||||
*/
|
||||
snapshotDate(unique date snapshotDate : date ref);
|
||||
|
||||
/**
|
||||
* The source location of the snapshot.
|
||||
*/
|
||||
sourceLocationPrefix(string prefix : string ref);
|
||||
|
||||
/**
|
||||
* Data used by the 'duplicate code' detection.
|
||||
*/
|
||||
duplicateCode(
|
||||
unique int id : @duplication,
|
||||
string relativePath : string ref,
|
||||
int equivClass : int ref
|
||||
);
|
||||
|
||||
/**
|
||||
* Data used by the 'similar code' detection.
|
||||
*/
|
||||
similarCode(
|
||||
unique int id : @similarity,
|
||||
string relativePath : string ref,
|
||||
int equivClass : int ref
|
||||
);
|
||||
|
||||
/**
|
||||
* Data used by the 'duplicate code' and 'similar code' detection.
|
||||
*/
|
||||
@duplication_or_similarity = @duplication | @similarity
|
||||
|
||||
/**
|
||||
* Data used by the 'duplicate code' and 'similar code' detection.
|
||||
*/
|
||||
#keyset[id, offset]
|
||||
tokens(
|
||||
int id : @duplication_or_similarity ref,
|
||||
int offset : int ref,
|
||||
int beginLine : int ref,
|
||||
int beginColumn : int ref,
|
||||
int endLine : int ref,
|
||||
int endColumn : int ref
|
||||
);
|
||||
|
||||
/**
|
||||
* Information about packages that provide code used during compilation.
|
||||
* The `id` is just a unique identifier.
|
||||
@@ -446,7 +487,6 @@ var_decl_specifiers(
|
||||
int id: @var_decl ref,
|
||||
string name: string ref
|
||||
)
|
||||
is_structured_binding(unique int id: @variable ref);
|
||||
|
||||
type_decls(
|
||||
unique int id: @type_decl,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,22 +6,122 @@
|
||||
*/
|
||||
class Person extends string {
|
||||
Person() {
|
||||
this =
|
||||
[
|
||||
"Ronil", "Dina", "Ravi", "Bruce", "Jo", "Aida", "Esme", "Charlie", "Fred", "Meera", "Maya",
|
||||
"Chad", "Tiana", "Laura", "George", "Will", "Mary", "Almira", "Susannah", "Rhoda",
|
||||
"Cynthia", "Eunice", "Olive", "Virginia", "Angeline", "Helen", "Cornelia", "Harriet",
|
||||
"Mahala", "Abby", "Margaret", "Deb", "Minerva", "Severus", "Lavina", "Adeline", "Cath",
|
||||
"Elisa", "Lucretia", "Anne", "Eleanor", "Joanna", "Adam", "Agnes", "Rosanna", "Clara",
|
||||
"Melissa", "Amy", "Isabel", "Jemima", "Cordelia", "Melinda", "Delila", "Jeremiah", "Elijah",
|
||||
"Hester", "Walter", "Oliver", "Hugh", "Aaron", "Reuben", "Eli", "Amos", "Augustus",
|
||||
"Theodore", "Ira", "Timothy", "Cyrus", "Horace", "Simon", "Asa", "Frank", "Nelson",
|
||||
"Leonard", "Harrison", "Anthony", "Louis", "Milton", "Noah", "Cornelius", "Abdul", "Warren",
|
||||
"Harvey", "Dennis", "Wesley", "Sylvester", "Gilbert", "Sullivan", "Edmund", "Wilson",
|
||||
"Perry", "Matthew", "Simba", "Nala", "Rafiki", "Shenzi", "Ernest", "Gertrude", "Oscar",
|
||||
"Lilian", "Raymond", "Elgar", "Elmer", "Herbert", "Maude", "Mae", "Otto", "Edwin",
|
||||
"Ophelia", "Parsley", "Sage", "Rosemary", "Thyme", "Garfunkel", "King Basil", "Stephen"
|
||||
]
|
||||
this = "Ronil" or
|
||||
this = "Dina" or
|
||||
this = "Ravi" or
|
||||
this = "Bruce" or
|
||||
this = "Jo" or
|
||||
this = "Aida" or
|
||||
this = "Esme" or
|
||||
this = "Charlie" or
|
||||
this = "Fred" or
|
||||
this = "Meera" or
|
||||
this = "Maya" or
|
||||
this = "Chad" or
|
||||
this = "Tiana" or
|
||||
this = "Laura" or
|
||||
this = "George" or
|
||||
this = "Will" or
|
||||
this = "Mary" or
|
||||
this = "Almira" or
|
||||
this = "Susannah" or
|
||||
this = "Rhoda" or
|
||||
this = "Cynthia" or
|
||||
this = "Eunice" or
|
||||
this = "Olive" or
|
||||
this = "Virginia" or
|
||||
this = "Angeline" or
|
||||
this = "Helen" or
|
||||
this = "Cornelia" or
|
||||
this = "Harriet" or
|
||||
this = "Mahala" or
|
||||
this = "Abby" or
|
||||
this = "Margaret" or
|
||||
this = "Deb" or
|
||||
this = "Minerva" or
|
||||
this = "Severus" or
|
||||
this = "Lavina" or
|
||||
this = "Adeline" or
|
||||
this = "Cath" or
|
||||
this = "Elisa" or
|
||||
this = "Lucretia" or
|
||||
this = "Anne" or
|
||||
this = "Eleanor" or
|
||||
this = "Joanna" or
|
||||
this = "Adam" or
|
||||
this = "Agnes" or
|
||||
this = "Rosanna" or
|
||||
this = "Clara" or
|
||||
this = "Melissa" or
|
||||
this = "Amy" or
|
||||
this = "Isabel" or
|
||||
this = "Jemima" or
|
||||
this = "Cordelia" or
|
||||
this = "Melinda" or
|
||||
this = "Delila" or
|
||||
this = "Jeremiah" or
|
||||
this = "Elijah" or
|
||||
this = "Hester" or
|
||||
this = "Walter" or
|
||||
this = "Oliver" or
|
||||
this = "Hugh" or
|
||||
this = "Aaron" or
|
||||
this = "Reuben" or
|
||||
this = "Eli" or
|
||||
this = "Amos" or
|
||||
this = "Augustus" or
|
||||
this = "Theodore" or
|
||||
this = "Ira" or
|
||||
this = "Timothy" or
|
||||
this = "Cyrus" or
|
||||
this = "Horace" or
|
||||
this = "Simon" or
|
||||
this = "Asa" or
|
||||
this = "Frank" or
|
||||
this = "Nelson" or
|
||||
this = "Leonard" or
|
||||
this = "Harrison" or
|
||||
this = "Anthony" or
|
||||
this = "Louis" or
|
||||
this = "Milton" or
|
||||
this = "Noah" or
|
||||
this = "Cornelius" or
|
||||
this = "Abdul" or
|
||||
this = "Warren" or
|
||||
this = "Harvey" or
|
||||
this = "Dennis" or
|
||||
this = "Wesley" or
|
||||
this = "Sylvester" or
|
||||
this = "Gilbert" or
|
||||
this = "Sullivan" or
|
||||
this = "Edmund" or
|
||||
this = "Wilson" or
|
||||
this = "Perry" or
|
||||
this = "Matthew" or
|
||||
this = "Simba" or
|
||||
this = "Nala" or
|
||||
this = "Rafiki" or
|
||||
this = "Shenzi" or
|
||||
this = "Ernest" or
|
||||
this = "Gertrude" or
|
||||
this = "Oscar" or
|
||||
this = "Lilian" or
|
||||
this = "Raymond" or
|
||||
this = "Elgar" or
|
||||
this = "Elmer" or
|
||||
this = "Herbert" or
|
||||
this = "Maude" or
|
||||
this = "Mae" or
|
||||
this = "Otto" or
|
||||
this = "Edwin" or
|
||||
this = "Ophelia" or
|
||||
this = "Parsley" or
|
||||
this = "Sage" or
|
||||
this = "Rosemary" or
|
||||
this = "Thyme" or
|
||||
this = "Garfunkel" or
|
||||
this = "King Basil" or
|
||||
this = "Stephen"
|
||||
}
|
||||
|
||||
/** Gets the hair color of the person. If the person is bald, there is no result. */
|
||||
@@ -836,12 +936,25 @@ class Person extends string {
|
||||
|
||||
/** Holds if the person is deceased. */
|
||||
predicate isDeceased() {
|
||||
this =
|
||||
[
|
||||
"Ernest", "Gertrude", "Oscar", "Lilian", "Edwin", "Raymond", "Elgar", "Elmer", "Herbert",
|
||||
"Maude", "Mae", "Otto", "Ophelia", "Parsley", "Sage", "Rosemary", "Thyme", "Garfunkel",
|
||||
"King Basil"
|
||||
]
|
||||
this = "Ernest" or
|
||||
this = "Gertrude" or
|
||||
this = "Oscar" or
|
||||
this = "Lilian" or
|
||||
this = "Edwin" or
|
||||
this = "Raymond" or
|
||||
this = "Elgar" or
|
||||
this = "Elmer" or
|
||||
this = "Herbert" or
|
||||
this = "Maude" or
|
||||
this = "Mae" or
|
||||
this = "Otto" or
|
||||
this = "Ophelia" or
|
||||
this = "Parsley" or
|
||||
this = "Sage" or
|
||||
this = "Rosemary" or
|
||||
this = "Thyme" or
|
||||
this = "Garfunkel" or
|
||||
this = "King Basil"
|
||||
}
|
||||
|
||||
/** Gets a parent of the person (alive or deceased). */
|
||||
@@ -1082,7 +1195,12 @@ class Person extends string {
|
||||
}
|
||||
|
||||
/** Holds if the person is allowed in the region. Initially, all villagers are allowed in every region. */
|
||||
predicate isAllowedIn(string region) { region = ["north", "south", "east", "west"] }
|
||||
predicate isAllowedIn(string region) {
|
||||
region = "north" or
|
||||
region = "south" or
|
||||
region = "east" or
|
||||
region = "west"
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a parent of the person. */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,6 +0,0 @@
|
||||
description: Remove unused legacy relations
|
||||
compatibility: full
|
||||
snapshotDate.rel: delete
|
||||
duplicateCode.rel: delete
|
||||
similarCode.rel: delete
|
||||
tokens.rel: delete
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,2 +0,0 @@
|
||||
description: Add relation for tracking variables from structured binding declarations
|
||||
compatibility: backwards
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,7 @@ where
|
||||
not lv1.isCompilerGenerated() and
|
||||
not lv2.isCompilerGenerated() and
|
||||
not lv1.getParentScope().(BlockStmt).isInMacroExpansion() and
|
||||
not lv2.getParentScope().(BlockStmt).isInMacroExpansion()
|
||||
not lv2.getParentScope().(BlockStmt).isInMacroExpansion() and
|
||||
not lv1.getName() = "(unnamed local variable)"
|
||||
select lv1, "Variable " + lv1.getName() + " hides another variable of the same name (on $@).", lv2,
|
||||
"line " + lv2.getLocation().getStartLine().toString()
|
||||
|
||||
@@ -1,42 +1,3 @@
|
||||
## 0.0.10
|
||||
|
||||
### Deprecated Classes
|
||||
|
||||
* The `CodeDuplication.Copy`, `CodeDuplication.DuplicateBlock`, and `CodeDuplication.SimilarBlock` classes have been deprecated.
|
||||
|
||||
## 0.0.9
|
||||
|
||||
### New Queries
|
||||
|
||||
* Added a new query, `cpp/open-call-with-mode-argument`, to detect when `open` or `openat` is called with the `O_CREAT` or `O_TMPFILE` flag but when the `mode` argument is omitted.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The "Cleartext transmission of sensitive information" (`cpp/cleartext-transmission`) query has been further improved to reduce false positive results, and upgraded from `medium` to `high` precision.
|
||||
* The "Cleartext transmission of sensitive information" (`cpp/cleartext-transmission`) query now finds more results, where a password is stored in a struct field or class member variable.
|
||||
* The `cpp/cleartext-storage-file` query has been improved, removing false positives where data is written to a standard output stream.
|
||||
* The `cpp/cleartext-storage-buffer` query has been updated to use the `semmle.code.cpp.dataflow.TaintTracking` library.
|
||||
* The `cpp/world-writable-file-creation` query now only detects `open` and `openat` calls with the `O_CREAT` or `O_TMPFILE` flag.
|
||||
|
||||
## 0.0.8
|
||||
|
||||
### New Queries
|
||||
|
||||
* The `security` tag has been added to the `cpp/return-stack-allocated-memory` query. As a result, its results will now appear by default.
|
||||
* The "Uncontrolled data in arithmetic expression" (cpp/uncontrolled-arithmetic) query has been enhanced to reduce false positive results and its @precision increased to high.
|
||||
* A new `cpp/very-likely-overruning-write` query has been added to the default query suite for C/C++. The query reports some results that were formerly flagged by `cpp/overruning-write`.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Fix an issue with the `cpp/declaration-hides-variable` query where it would report variables that are unnamed in a database.
|
||||
* The `cpp/cleartext-storage-file` query has been upgraded with non-local taint flow and has been converted to a `path-problem` query.
|
||||
* The `cpp/return-stack-allocated-memory` query has been improved to produce fewer false positives. The
|
||||
query has also been converted to a `path-problem` query.
|
||||
* The "Cleartext transmission of sensitive information" (`cpp/cleartext-transmission`) query has been improved in several ways to reduce false positive results.
|
||||
* The "Potential improper null termination" (`cpp/improper-null-termination`) query now produces fewer false positive results around control flow branches and loops.
|
||||
* Added exception for GLib's gboolean to cpp/ambiguously-signed-bit-field.
|
||||
This change reduces the number of false positives in the query.
|
||||
|
||||
## 0.0.7
|
||||
|
||||
## 0.0.6
|
||||
|
||||
@@ -14,75 +14,158 @@
|
||||
*/
|
||||
|
||||
import cpp
|
||||
// We don't actually use the global value numbering library in this query, but without it we end up
|
||||
// recomputing the IR.
|
||||
private import semmle.code.cpp.valuenumbering.GlobalValueNumbering
|
||||
import semmle.code.cpp.ir.IR
|
||||
import semmle.code.cpp.ir.dataflow.MustFlow
|
||||
import PathGraph
|
||||
import semmle.code.cpp.ir.dataflow.DataFlow::DataFlow
|
||||
|
||||
/** Holds if `f` has a name that we intrepret as evidence of intentionally returning the value of the stack pointer. */
|
||||
predicate intentionallyReturnsStackPointer(Function f) {
|
||||
f.getName().toLowerCase().matches(["%stack%", "%sp%"])
|
||||
}
|
||||
|
||||
class ReturnStackAllocatedMemoryConfig extends MustFlowConfiguration {
|
||||
ReturnStackAllocatedMemoryConfig() { this = "ReturnStackAllocatedMemoryConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
// Holds if `source` is a node that represents the use of a stack variable
|
||||
exists(VariableAddressInstruction var, Function func |
|
||||
var = source.asInstruction() and
|
||||
func = var.getEnclosingFunction() and
|
||||
var.getASTVariable() instanceof StackVariable and
|
||||
// Pointer-to-member types aren't properly handled in the dbscheme.
|
||||
not var.getResultType() instanceof PointerToMemberType and
|
||||
// Rule out FPs caused by extraction errors.
|
||||
not any(ErrorExpr e).getEnclosingFunction() = func and
|
||||
not intentionallyReturnsStackPointer(func)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
// Holds if `sink` is a node that represents the `StoreInstruction` that is subsequently used in
|
||||
// a `ReturnValueInstruction`.
|
||||
// We use the `StoreInstruction` instead of the instruction that defines the
|
||||
// `ReturnValueInstruction`'s source value oprand because the former has better location information.
|
||||
exists(StoreInstruction store |
|
||||
store.getDestinationAddress().(VariableAddressInstruction).getIRVariable() instanceof
|
||||
IRReturnVariable and
|
||||
sink.asOperand() = store.getSourceValueOperand()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* This configuration intentionally conflates addresses of fields and their object, and pointer offsets
|
||||
* with their base pointer as this allows us to detect cases where an object's address flows to a
|
||||
* return statement via a field. For example:
|
||||
*
|
||||
* ```cpp
|
||||
* struct S { int x, y };
|
||||
* int* test() {
|
||||
* S s;
|
||||
* return &s.x; // BAD: &s.x is an address of a variable on the stack.
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
node2.asInstruction().(FieldAddressInstruction).getObjectAddressOperand() = node1.asOperand()
|
||||
or
|
||||
node2.asInstruction().(PointerOffsetInstruction).getLeftOperand() = node1.asOperand()
|
||||
}
|
||||
/**
|
||||
* Holds if `source` is a node that represents the use of a stack variable
|
||||
*/
|
||||
predicate isSource(Node source) {
|
||||
exists(VariableAddressInstruction var, Function func |
|
||||
var = source.asInstruction() and
|
||||
func = var.getEnclosingFunction() and
|
||||
var.getASTVariable() instanceof StackVariable and
|
||||
// Pointer-to-member types aren't properly handled in the dbscheme.
|
||||
not var.getResultType() instanceof PointerToMemberType and
|
||||
// Rule out FPs caused by extraction errors.
|
||||
not any(ErrorExpr e).getEnclosingFunction() = func and
|
||||
not intentionallyReturnsStackPointer(func)
|
||||
)
|
||||
}
|
||||
|
||||
from
|
||||
MustFlowPathNode source, MustFlowPathNode sink, VariableAddressInstruction var,
|
||||
ReturnStackAllocatedMemoryConfig conf
|
||||
/**
|
||||
* Holds if `sink` is a node that represents the `StoreInstruction` that is subsequently used in
|
||||
* a `ReturnValueInstruction`. We use the `StoreInstruction` instead of the instruction that defines the
|
||||
* `ReturnValueInstruction`'s source value oprand because the former has better location information.
|
||||
*/
|
||||
predicate isSink(Node sink) {
|
||||
exists(StoreInstruction store |
|
||||
store.getDestinationAddress().(VariableAddressInstruction).getIRVariable() instanceof
|
||||
IRReturnVariable and
|
||||
sink.asOperand() = store.getSourceValueOperand()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `node1` _must_ flow to `node2`. */
|
||||
predicate step(Node node1, Node node2) {
|
||||
instructionToOperandStep(node1.asInstruction(), node2.asOperand())
|
||||
or
|
||||
operandToInstructionStep(node1.asOperand(), node2.asInstruction())
|
||||
}
|
||||
|
||||
predicate instructionToOperandStep(Instruction instr, Operand operand) { operand.getDef() = instr }
|
||||
|
||||
/**
|
||||
* Holds if `operand` flows to the result of `instr`.
|
||||
*
|
||||
* This predicate ignores flow through `PhiInstruction`s to create a 'must flow' relation. It also
|
||||
* intentionally conflates addresses of fields and their object, and pointer offsets with their
|
||||
* base pointer as this allows us to detect cases where an object's address flows to a return statement
|
||||
* via a field. For example:
|
||||
*
|
||||
* ```cpp
|
||||
* struct S { int x, y };
|
||||
* int* test() {
|
||||
* S s;
|
||||
* return &s.x; // BAD: &s.x is an address of a variable on the stack.
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
predicate operandToInstructionStep(Operand operand, Instruction instr) {
|
||||
instr.(CopyInstruction).getSourceValueOperand() = operand
|
||||
or
|
||||
instr.(ConvertInstruction).getUnaryOperand() = operand
|
||||
or
|
||||
instr.(CheckedConvertOrNullInstruction).getUnaryOperand() = operand
|
||||
or
|
||||
instr.(InheritanceConversionInstruction).getUnaryOperand() = operand
|
||||
or
|
||||
instr.(FieldAddressInstruction).getObjectAddressOperand() = operand
|
||||
or
|
||||
instr.(PointerOffsetInstruction).getLeftOperand() = operand
|
||||
}
|
||||
|
||||
/** Holds if a source node flows to `n`. */
|
||||
predicate branchlessLocalFlow0(Node n) {
|
||||
isSource(n)
|
||||
or
|
||||
exists(Node mid |
|
||||
branchlessLocalFlow0(mid) and
|
||||
step(mid, n)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `n` is reachable through some source node, and `n` also eventually reaches a sink. */
|
||||
predicate branchlessLocalFlow1(Node n) {
|
||||
branchlessLocalFlow0(n) and
|
||||
(
|
||||
isSink(n)
|
||||
or
|
||||
exists(Node mid |
|
||||
branchlessLocalFlow1(mid) and
|
||||
step(n, mid)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
newtype TLocalPathNode =
|
||||
TLocalPathNodeMid(Node n) {
|
||||
branchlessLocalFlow1(n) and
|
||||
(
|
||||
isSource(n) or
|
||||
exists(LocalPathNodeMid mid | step(mid.getNode(), n))
|
||||
)
|
||||
}
|
||||
|
||||
abstract class LocalPathNode extends TLocalPathNode {
|
||||
Node n;
|
||||
|
||||
/** Gets the underlying node. */
|
||||
Node getNode() { result = n }
|
||||
|
||||
/** Gets a textual representation of this node. */
|
||||
string toString() { result = n.toString() }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
Location getLocation() { result = n.getLocation() }
|
||||
|
||||
/** Gets a successor `LocalPathNode`, if any. */
|
||||
LocalPathNode getASuccessor() { step(this.getNode(), result.getNode()) }
|
||||
}
|
||||
|
||||
class LocalPathNodeMid extends LocalPathNode, TLocalPathNodeMid {
|
||||
LocalPathNodeMid() { this = TLocalPathNodeMid(n) }
|
||||
}
|
||||
|
||||
class LocalPathNodeSink extends LocalPathNodeMid {
|
||||
LocalPathNodeSink() { isSink(this.getNode()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `source` is a source node, `sink` is a sink node, and there's flow
|
||||
* from `source` to `sink` using `step` relation.
|
||||
*/
|
||||
predicate hasFlow(LocalPathNode source, LocalPathNodeSink sink) {
|
||||
isSource(source.getNode()) and
|
||||
source.getASuccessor+() = sink
|
||||
}
|
||||
|
||||
predicate reach(LocalPathNode n) { n instanceof LocalPathNodeSink or reach(n.getASuccessor()) }
|
||||
|
||||
query predicate edges(LocalPathNode a, LocalPathNode b) { a.getASuccessor() = b and reach(b) }
|
||||
|
||||
query predicate nodes(LocalPathNode n, string key, string val) {
|
||||
reach(n) and key = "semmle.label" and val = n.toString()
|
||||
}
|
||||
|
||||
from LocalPathNode source, LocalPathNodeSink sink, VariableAddressInstruction var
|
||||
where
|
||||
conf.hasFlowPath(source, sink) and
|
||||
source.getNode().asInstruction() = var and
|
||||
// Only raise an alert if we're returning from the _same_ callable as the on that
|
||||
// declared the stack variable.
|
||||
var.getEnclosingFunction() = sink.getNode().getEnclosingCallable()
|
||||
hasFlow(source, sink) and
|
||||
source.getNode().asInstruction() = var
|
||||
select sink.getNode(), source, sink, "May return stack-allocated memory from $@.", var.getAST(),
|
||||
var.getAST().toString()
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
static const int* xptr;
|
||||
|
||||
void localAddressEscapes() {
|
||||
int x = 0;
|
||||
xptr = &x;
|
||||
}
|
||||
|
||||
void example1() {
|
||||
localAddressEscapes();
|
||||
const int* x = xptr; // BAD: This pointer points to expired stack allocated memory.
|
||||
}
|
||||
|
||||
void localAddressDoesNotEscape() {
|
||||
int x = 0;
|
||||
xptr = &x;
|
||||
// ...
|
||||
// use `xptr`
|
||||
// ...
|
||||
xptr = nullptr;
|
||||
}
|
||||
|
||||
void example2() {
|
||||
localAddressDoesNotEscape();
|
||||
const int* x = xptr; // GOOD: This pointer does not point to expired memory.
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
This rule finds uses of pointers that likely point to local variables in
|
||||
expired stack frames. A pointer to a local variable is only valid
|
||||
until the function returns, after which it becomes a dangling pointer.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<ol>
|
||||
|
||||
<li>
|
||||
If it is necessary to take the address of a local variable, then make
|
||||
sure that the address is only stored in memory that does not outlive
|
||||
the local variable. For example, it is safe to store the address in
|
||||
another local variable. Similarly, it is also safe to pass the address
|
||||
of a local variable to another function provided that the other
|
||||
function only uses it locally and does not store it in non-local
|
||||
memory.
|
||||
</li>
|
||||
<li>
|
||||
If it is necessary to store an address which will outlive the
|
||||
current function scope, then it should be allocated on the heap. Care
|
||||
should be taken to make sure that the memory is deallocated when it is
|
||||
no longer needed, particularly when using low-level memory management
|
||||
routines such as <tt>malloc</tt>/<tt>free</tt> or
|
||||
<tt>new</tt>/<tt>delete</tt>. Modern C++ applications often use smart
|
||||
pointers, such as <tt>std::shared_ptr</tt>, to reduce the chance of
|
||||
a memory leak.
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<sample src="UsingExpiredStackAddress.cpp" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Dangling_pointer">Dangling pointer</a>.</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,348 +0,0 @@
|
||||
/**
|
||||
* @name Use of expired stack-address
|
||||
* @description Accessing the stack-allocated memory of a function
|
||||
* after it has returned can lead to memory corruption.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 9.3
|
||||
* @precision high
|
||||
* @id cpp/using-expired-stack-address
|
||||
* @tags reliability
|
||||
* security
|
||||
* external/cwe/cwe-825
|
||||
*/
|
||||
|
||||
import cpp
|
||||
// We don't actually use the global value numbering library in this query, but without it we end up
|
||||
// recomputing the IR.
|
||||
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
|
||||
import semmle.code.cpp.ir.IR
|
||||
|
||||
predicate instructionHasVariable(VariableAddressInstruction vai, StackVariable var, Function f) {
|
||||
var = vai.getASTVariable() and
|
||||
f = vai.getEnclosingFunction() and
|
||||
// Pointer-to-member types aren't properly handled in the dbscheme.
|
||||
not vai.getResultType() instanceof PointerToMemberType and
|
||||
// Rule out FPs caused by extraction errors.
|
||||
not any(ErrorExpr e).getEnclosingFunction() = f
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `source` is the base address of an address computation whose
|
||||
* result is stored in `address`.
|
||||
*/
|
||||
predicate stackPointerFlowsToUse(Instruction address, VariableAddressInstruction source) {
|
||||
address = source and
|
||||
instructionHasVariable(source, _, _)
|
||||
or
|
||||
stackPointerFlowsToUse(address.(CopyInstruction).getSourceValue(), source)
|
||||
or
|
||||
stackPointerFlowsToUse(address.(ConvertInstruction).getUnary(), source)
|
||||
or
|
||||
stackPointerFlowsToUse(address.(CheckedConvertOrNullInstruction).getUnary(), source)
|
||||
or
|
||||
stackPointerFlowsToUse(address.(InheritanceConversionInstruction).getUnary(), source)
|
||||
or
|
||||
stackPointerFlowsToUse(address.(FieldAddressInstruction).getObjectAddress(), source)
|
||||
or
|
||||
stackPointerFlowsToUse(address.(PointerOffsetInstruction).getLeft(), source)
|
||||
}
|
||||
|
||||
/**
|
||||
* A HashCons-like table for comparing addresses that are
|
||||
* computed relative to some global variable.
|
||||
*/
|
||||
newtype TGlobalAddress =
|
||||
TGlobalVariable(GlobalOrNamespaceVariable v) {
|
||||
// Pointer-to-member types aren't properly handled in the dbscheme.
|
||||
not v.getUnspecifiedType() instanceof PointerToMemberType
|
||||
} or
|
||||
TLoad(TGlobalAddress address) {
|
||||
address = globalAddress(any(LoadInstruction load).getSourceAddress())
|
||||
} or
|
||||
TConversion(string kind, TGlobalAddress address, Type fromType, Type toType) {
|
||||
kind = "unchecked" and
|
||||
exists(ConvertInstruction convert |
|
||||
uncheckedConversionTypes(convert, fromType, toType) and
|
||||
address = globalAddress(convert.getUnary())
|
||||
)
|
||||
or
|
||||
kind = "checked" and
|
||||
exists(CheckedConvertOrNullInstruction convert |
|
||||
checkedConversionTypes(convert, fromType, toType) and
|
||||
address = globalAddress(convert.getUnary())
|
||||
)
|
||||
or
|
||||
kind = "inheritance" and
|
||||
exists(InheritanceConversionInstruction convert |
|
||||
inheritanceConversionTypes(convert, fromType, toType) and
|
||||
address = globalAddress(convert.getUnary())
|
||||
)
|
||||
} or
|
||||
TFieldAddress(TGlobalAddress address, Field f) {
|
||||
exists(FieldAddressInstruction fai |
|
||||
fai.getField() = f and
|
||||
address = globalAddress(fai.getObjectAddress())
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
predicate uncheckedConversionTypes(ConvertInstruction convert, Type fromType, Type toType) {
|
||||
fromType = convert.getUnary().getResultType() and
|
||||
toType = convert.getResultType()
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
predicate checkedConversionTypes(CheckedConvertOrNullInstruction convert, Type fromType, Type toType) {
|
||||
fromType = convert.getUnary().getResultType() and
|
||||
toType = convert.getResultType()
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
predicate inheritanceConversionTypes(
|
||||
InheritanceConversionInstruction convert, Type fromType, Type toType
|
||||
) {
|
||||
fromType = convert.getUnary().getResultType() and
|
||||
toType = convert.getResultType()
|
||||
}
|
||||
|
||||
/** Gets the HashCons value of an address computed by `instr`, if any. */
|
||||
TGlobalAddress globalAddress(Instruction instr) {
|
||||
result = TGlobalVariable(instr.(VariableAddressInstruction).getASTVariable())
|
||||
or
|
||||
not instr instanceof LoadInstruction and
|
||||
result = globalAddress(instr.(CopyInstruction).getSourceValue())
|
||||
or
|
||||
exists(LoadInstruction load | instr = load |
|
||||
result = TLoad(globalAddress(load.getSourceAddress()))
|
||||
)
|
||||
or
|
||||
exists(ConvertInstruction convert, Type fromType, Type toType | instr = convert |
|
||||
uncheckedConversionTypes(convert, fromType, toType) and
|
||||
result = TConversion("unchecked", globalAddress(convert.getUnary()), fromType, toType)
|
||||
)
|
||||
or
|
||||
exists(CheckedConvertOrNullInstruction convert, Type fromType, Type toType | instr = convert |
|
||||
checkedConversionTypes(convert, fromType, toType) and
|
||||
result = TConversion("checked", globalAddress(convert.getUnary()), fromType, toType)
|
||||
)
|
||||
or
|
||||
exists(InheritanceConversionInstruction convert, Type fromType, Type toType | instr = convert |
|
||||
inheritanceConversionTypes(convert, fromType, toType) and
|
||||
result = TConversion("inheritance", globalAddress(convert.getUnary()), fromType, toType)
|
||||
)
|
||||
or
|
||||
exists(FieldAddressInstruction fai | instr = fai |
|
||||
result = TFieldAddress(globalAddress(fai.getObjectAddress()), fai.getField())
|
||||
)
|
||||
or
|
||||
result = globalAddress(instr.(PointerOffsetInstruction).getLeft())
|
||||
}
|
||||
|
||||
/** Gets a `StoreInstruction` that may be executed after executing `store`. */
|
||||
pragma[inline]
|
||||
StoreInstruction getAStoreStrictlyAfter(StoreInstruction store) {
|
||||
exists(IRBlock block, int index1, int index2 |
|
||||
block.getInstruction(index1) = store and
|
||||
block.getInstruction(index2) = result and
|
||||
index2 > index1
|
||||
)
|
||||
or
|
||||
exists(IRBlock block1, IRBlock block2 |
|
||||
store.getBlock() = block1 and
|
||||
result.getBlock() = block2 and
|
||||
block1.getASuccessor+() = block2
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `store` copies the address of `f`'s local variable `var`
|
||||
* into the address `globalAddress`.
|
||||
*/
|
||||
predicate stackAddressEscapes(
|
||||
StoreInstruction store, StackVariable var, TGlobalAddress globalAddress, Function f
|
||||
) {
|
||||
globalAddress = globalAddress(store.getDestinationAddress()) and
|
||||
exists(VariableAddressInstruction vai |
|
||||
instructionHasVariable(pragma[only_bind_into](vai), var, f) and
|
||||
stackPointerFlowsToUse(store.getSourceValue(), vai)
|
||||
) and
|
||||
// Ensure there's no subsequent store that overrides the global address.
|
||||
not globalAddress = globalAddress(getAStoreStrictlyAfter(store).getDestinationAddress())
|
||||
}
|
||||
|
||||
predicate blockStoresToAddress(
|
||||
IRBlock block, int index, StoreInstruction store, TGlobalAddress globalAddress
|
||||
) {
|
||||
block.getInstruction(index) = store and
|
||||
globalAddress = globalAddress(store.getDestinationAddress())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `globalAddress` evaluates to the address of `var` (which escaped through `store` before
|
||||
* returning through `call`) when control reaches `block`.
|
||||
*/
|
||||
predicate globalAddressPointsToStack(
|
||||
StoreInstruction store, StackVariable var, CallInstruction call, IRBlock block,
|
||||
TGlobalAddress globalAddress, boolean isCallBlock, boolean isStoreBlock
|
||||
) {
|
||||
(
|
||||
if blockStoresToAddress(block, _, _, globalAddress)
|
||||
then isStoreBlock = true
|
||||
else isStoreBlock = false
|
||||
) and
|
||||
(
|
||||
isCallBlock = true and
|
||||
exists(Function f |
|
||||
stackAddressEscapes(store, var, globalAddress, f) and
|
||||
call.getStaticCallTarget() = f and
|
||||
call.getBlock() = block
|
||||
)
|
||||
or
|
||||
isCallBlock = false and
|
||||
step(store, var, call, globalAddress, _, block)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[inline]
|
||||
int getInstructionIndex(Instruction instr, IRBlock block) { block.getInstruction(result) = instr }
|
||||
|
||||
predicate step(
|
||||
StoreInstruction store, StackVariable var, CallInstruction call, TGlobalAddress globalAddress,
|
||||
IRBlock pred, IRBlock succ
|
||||
) {
|
||||
exists(boolean isCallBlock, boolean isStoreBlock |
|
||||
// Only recurse if there is no store to `globalAddress` in `mid`.
|
||||
globalAddressPointsToStack(store, var, call, pred, globalAddress, isCallBlock, isStoreBlock)
|
||||
|
|
||||
// Post domination ensures that `block` is always executed after `mid`
|
||||
// Domination ensures that `mid` is always executed before `block`
|
||||
isStoreBlock = false and
|
||||
succ.immediatelyPostDominates(pred) and
|
||||
pred.immediatelyDominates(succ)
|
||||
or
|
||||
exists(CallInstruction anotherCall, int anotherCallIndex |
|
||||
anotherCall = pred.getInstruction(anotherCallIndex) and
|
||||
succ.getFirstInstruction() instanceof EnterFunctionInstruction and
|
||||
succ.getEnclosingFunction() = anotherCall.getStaticCallTarget() and
|
||||
(if isCallBlock = true then getInstructionIndex(call, _) < anotherCallIndex else any()) and
|
||||
(
|
||||
if isStoreBlock = true
|
||||
then
|
||||
forex(int storeIndex | blockStoresToAddress(pred, storeIndex, _, globalAddress) |
|
||||
anotherCallIndex < storeIndex
|
||||
)
|
||||
else any()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
newtype TPathElement =
|
||||
TStore(StoreInstruction store) { globalAddressPointsToStack(store, _, _, _, _, _, _) } or
|
||||
TCall(CallInstruction call, IRBlock block) {
|
||||
globalAddressPointsToStack(_, _, call, block, _, _, _)
|
||||
} or
|
||||
TMid(IRBlock block) { step(_, _, _, _, _, block) } or
|
||||
TSink(LoadInstruction load, IRBlock block) {
|
||||
exists(TGlobalAddress address |
|
||||
globalAddressPointsToStack(_, _, _, block, address, _, _) and
|
||||
block.getAnInstruction() = load and
|
||||
globalAddress(load.getSourceAddress()) = address
|
||||
)
|
||||
}
|
||||
|
||||
class PathElement extends TPathElement {
|
||||
StoreInstruction asStore() { this = TStore(result) }
|
||||
|
||||
CallInstruction asCall(IRBlock block) { this = TCall(result, block) }
|
||||
|
||||
predicate isCall(IRBlock block) { exists(this.asCall(block)) }
|
||||
|
||||
IRBlock asMid() { this = TMid(result) }
|
||||
|
||||
LoadInstruction asSink(IRBlock block) { this = TSink(result, block) }
|
||||
|
||||
predicate isSink(IRBlock block) { exists(this.asSink(block)) }
|
||||
|
||||
string toString() {
|
||||
result = [asStore().toString(), asCall(_).toString(), asMid().toString(), asSink(_).toString()]
|
||||
}
|
||||
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
this.asStore()
|
||||
.getLocation()
|
||||
.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
or
|
||||
this.asCall(_)
|
||||
.getLocation()
|
||||
.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
or
|
||||
this.asMid().getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
or
|
||||
this.asSink(_)
|
||||
.getLocation()
|
||||
.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
}
|
||||
|
||||
predicate isSink(LoadInstruction load, IRBlock block, int index, TGlobalAddress globalAddress) {
|
||||
block.getInstruction(index) = load and
|
||||
globalAddress(load.getSourceAddress()) = globalAddress
|
||||
}
|
||||
|
||||
query predicate edges(PathElement pred, PathElement succ) {
|
||||
// Store -> caller
|
||||
globalAddressPointsToStack(pred.asStore(), _, succ.asCall(_), _, _, _, _)
|
||||
or
|
||||
// Call -> basic block
|
||||
pred.isCall(succ.asMid())
|
||||
or
|
||||
// Special case for when the caller goes directly to the load with no steps
|
||||
// across basic blocks (i.e., caller -> sink)
|
||||
exists(IRBlock block |
|
||||
pred.isCall(block) and
|
||||
succ.isSink(block)
|
||||
)
|
||||
or
|
||||
// Basic block -> basic block
|
||||
step(_, _, _, _, pred.asMid(), succ.asMid())
|
||||
or
|
||||
// Basic block -> load
|
||||
succ.isSink(pred.asMid())
|
||||
}
|
||||
|
||||
from
|
||||
StoreInstruction store, StackVariable var, LoadInstruction load, CallInstruction call,
|
||||
IRBlock block, boolean isCallBlock, TGlobalAddress address, boolean isStoreBlock,
|
||||
PathElement source, PathElement sink, int loadIndex
|
||||
where
|
||||
globalAddressPointsToStack(store, var, call, block, address, isCallBlock, isStoreBlock) and
|
||||
isSink(load, block, loadIndex, address) and
|
||||
(
|
||||
// We know that we have a sequence:
|
||||
// (1) store to `address` -> (2) return from `f` -> (3) load from `address`.
|
||||
// But if (2) and (3) happen in the sam block we need to check the
|
||||
// block indices to ensure that (3) happens after (2).
|
||||
if isCallBlock = true
|
||||
then
|
||||
// If so, the load must happen after the call.
|
||||
getInstructionIndex(call, _) < loadIndex
|
||||
else any()
|
||||
) and
|
||||
(
|
||||
// If there is a store to the address we need to make sure that the load we found was
|
||||
// before that store (So that the load doesn't read an overwritten value).
|
||||
if isStoreBlock = true
|
||||
then
|
||||
forex(int storeIndex | blockStoresToAddress(block, storeIndex, _, address) |
|
||||
loadIndex < storeIndex
|
||||
)
|
||||
else any()
|
||||
) and
|
||||
source.asStore() = store and
|
||||
sink.asSink(_) = load
|
||||
select sink, source, sink, "Stack variable $@ escapes $@ and is used after it has expired.", var,
|
||||
var.toString(), store, "here"
|
||||
@@ -17,47 +17,169 @@
|
||||
import cpp
|
||||
// We don't actually use the global value numbering library in this query, but without it we end up
|
||||
// recomputing the IR.
|
||||
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
|
||||
import semmle.code.cpp.ir.IR
|
||||
import semmle.code.cpp.ir.dataflow.MustFlow
|
||||
import PathGraph
|
||||
private import semmle.code.cpp.valuenumbering.GlobalValueNumbering
|
||||
private import semmle.code.cpp.ir.IR
|
||||
|
||||
class UnsafeUseOfThisConfig extends MustFlowConfiguration {
|
||||
UnsafeUseOfThisConfig() { this = "UnsafeUseOfThisConfig" }
|
||||
bindingset[n, result]
|
||||
int unbind(int n) { result >= n and result <= n }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { isSource(source, _, _) }
|
||||
/** Holds if `p` is the `n`'th parameter of the non-virtual function `f`. */
|
||||
predicate parameterOf(Parameter p, Function f, int n) {
|
||||
not f.isVirtual() and f.getParameter(n) = p
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { isSink(sink, _) }
|
||||
/**
|
||||
* Holds if `instr` is the `n`'th argument to a call to the non-virtual function `f`, and
|
||||
* `init` is the corresponding initiazation instruction that receives the value of `instr` in `f`.
|
||||
*/
|
||||
predicate flowIntoParameter(
|
||||
CallInstruction call, Instruction instr, Function f, int n, InitializeParameterInstruction init
|
||||
) {
|
||||
not f.isVirtual() and
|
||||
call.getPositionalArgument(n) = instr and
|
||||
f = call.getStaticCallTarget() and
|
||||
getEnclosingNonVirtualFunctionInitializeParameter(init, f) and
|
||||
init.getParameter().getIndex() = unbind(n)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `instr` is an argument to a call to the function `f`, and `init` is the
|
||||
* corresponding initialization instruction that receives the value of `instr` in `f`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
predicate getPositionalArgumentInitParam(
|
||||
CallInstruction call, Instruction instr, InitializeParameterInstruction init, Function f
|
||||
) {
|
||||
exists(int n |
|
||||
parameterOf(_, f, n) and
|
||||
flowIntoParameter(call, instr, f, unbind(n), init)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `instr` is the qualifier to a call to the non-virtual function `f`, and
|
||||
* `init` is the corresponding initiazation instruction that receives the value of
|
||||
* `instr` in `f`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
predicate getThisArgumentInitParam(
|
||||
CallInstruction call, Instruction instr, InitializeParameterInstruction init, Function f
|
||||
) {
|
||||
not f.isVirtual() and
|
||||
call.getStaticCallTarget() = f and
|
||||
getEnclosingNonVirtualFunctionInitializeParameter(init, f) and
|
||||
call.getThisArgument() = instr and
|
||||
init.getIRVariable() instanceof IRThisVariable
|
||||
}
|
||||
|
||||
/** Holds if `instr` is a `this` pointer used by the call instruction `call`. */
|
||||
predicate isSink(DataFlow::Node sink, CallInstruction call) {
|
||||
predicate isSink(Instruction instr, CallInstruction call) {
|
||||
exists(PureVirtualFunction func |
|
||||
call.getStaticCallTarget() = func and
|
||||
call.getThisArgument() = sink.asInstruction() and
|
||||
call.getThisArgument() = instr and
|
||||
// Weed out implicit calls to destructors of a base class
|
||||
not func instanceof Destructor
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `init` initializes the `this` pointer in class `c`. */
|
||||
predicate isSource(DataFlow::Node source, string msg, Class c) {
|
||||
exists(InitializeParameterInstruction init | init = source.asInstruction() |
|
||||
(
|
||||
exists(Constructor func |
|
||||
not func instanceof CopyConstructor and
|
||||
not func instanceof MoveConstructor and
|
||||
func = init.getEnclosingFunction() and
|
||||
msg = "construction"
|
||||
)
|
||||
or
|
||||
init.getEnclosingFunction() instanceof Destructor and msg = "destruction"
|
||||
) and
|
||||
init.getIRVariable() instanceof IRThisVariable and
|
||||
init.getEnclosingFunction().getDeclaringType() = c
|
||||
predicate isSource(InitializeParameterInstruction init, string msg, Class c) {
|
||||
(
|
||||
exists(Constructor func |
|
||||
not func instanceof CopyConstructor and
|
||||
not func instanceof MoveConstructor and
|
||||
func = init.getEnclosingFunction() and
|
||||
msg = "construction"
|
||||
)
|
||||
or
|
||||
init.getEnclosingFunction() instanceof Destructor and msg = "destruction"
|
||||
) and
|
||||
init.getIRVariable() instanceof IRThisVariable and
|
||||
init.getEnclosingFunction().getDeclaringType() = c
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `instr` flows to a sink (which is a use of the value of `instr` as a `this` pointer).
|
||||
*/
|
||||
predicate flowsToSink(Instruction instr, Instruction sink) {
|
||||
flowsFromSource(instr) and
|
||||
(
|
||||
isSink(instr, _) and instr = sink
|
||||
or
|
||||
exists(Instruction mid |
|
||||
successor(instr, mid) and
|
||||
flowsToSink(mid, sink)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `instr` flows from a source. */
|
||||
predicate flowsFromSource(Instruction instr) {
|
||||
isSource(instr, _, _)
|
||||
or
|
||||
exists(Instruction mid |
|
||||
successor(mid, instr) and
|
||||
flowsFromSource(mid)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `f` is the enclosing non-virtual function of `init`. */
|
||||
predicate getEnclosingNonVirtualFunctionInitializeParameter(
|
||||
InitializeParameterInstruction init, Function f
|
||||
) {
|
||||
not f.isVirtual() and
|
||||
init.getEnclosingFunction() = f
|
||||
}
|
||||
|
||||
/** Holds if `f` is the enclosing non-virtual function of `init`. */
|
||||
predicate getEnclosingNonVirtualFunctionInitializeIndirection(
|
||||
InitializeIndirectionInstruction init, Function f
|
||||
) {
|
||||
not f.isVirtual() and
|
||||
init.getEnclosingFunction() = f
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `instr` is an argument (or argument indirection) to a call, and
|
||||
* `succ` is the corresponding initialization instruction in the call target.
|
||||
*/
|
||||
predicate flowThroughCallable(Instruction instr, Instruction succ) {
|
||||
// Flow from an argument to a parameter
|
||||
exists(CallInstruction call, InitializeParameterInstruction init | init = succ |
|
||||
getPositionalArgumentInitParam(call, instr, init, call.getStaticCallTarget())
|
||||
or
|
||||
getThisArgumentInitParam(call, instr, init, call.getStaticCallTarget())
|
||||
)
|
||||
or
|
||||
// Flow from argument indirection to parameter indirection
|
||||
exists(
|
||||
CallInstruction call, ReadSideEffectInstruction read, InitializeIndirectionInstruction init
|
||||
|
|
||||
init = succ and
|
||||
read.getPrimaryInstruction() = call and
|
||||
getEnclosingNonVirtualFunctionInitializeIndirection(init, call.getStaticCallTarget())
|
||||
|
|
||||
exists(int n |
|
||||
read.getSideEffectOperand().getAnyDef() = instr and
|
||||
read.getIndex() = n and
|
||||
init.getParameter().getIndex() = unbind(n)
|
||||
)
|
||||
or
|
||||
call.getThisArgument() = instr and
|
||||
init.getIRVariable() instanceof IRThisVariable
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `instr` flows to `succ`. */
|
||||
predicate successor(Instruction instr, Instruction succ) {
|
||||
succ.(CopyInstruction).getSourceValue() = instr or
|
||||
succ.(CheckedConvertOrNullInstruction).getUnary() = instr or
|
||||
succ.(ChiInstruction).getTotal() = instr or
|
||||
succ.(ConvertInstruction).getUnary() = instr or
|
||||
succ.(InheritanceConversionInstruction).getUnary() = instr or
|
||||
flowThroughCallable(instr, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if:
|
||||
* - `source` is an initialization of a `this` pointer of type `sourceClass`, and
|
||||
@@ -66,19 +188,22 @@ predicate isSource(DataFlow::Node source, string msg, Class c) {
|
||||
* - `msg` is a string describing whether `source` is from a constructor or destructor.
|
||||
*/
|
||||
predicate flows(
|
||||
MustFlowPathNode source, string msg, Class sourceClass, MustFlowPathNode sink,
|
||||
CallInstruction call
|
||||
Instruction source, string msg, Class sourceClass, Instruction sink, CallInstruction call
|
||||
) {
|
||||
exists(UnsafeUseOfThisConfig conf |
|
||||
conf.hasFlowPath(source, sink) and
|
||||
isSource(source.getNode(), msg, sourceClass) and
|
||||
isSink(sink.getNode(), call)
|
||||
)
|
||||
isSource(source, msg, sourceClass) and
|
||||
flowsToSink(source, sink) and
|
||||
isSink(sink, call)
|
||||
}
|
||||
|
||||
from
|
||||
MustFlowPathNode source, MustFlowPathNode sink, CallInstruction call, string msg,
|
||||
Class sourceClass
|
||||
query predicate edges(Instruction a, Instruction b) { successor(a, b) and flowsToSink(b, _) }
|
||||
|
||||
query predicate nodes(Instruction n, string key, string val) {
|
||||
flowsToSink(n, _) and
|
||||
key = "semmle.label" and
|
||||
val = n.toString()
|
||||
}
|
||||
|
||||
from Instruction source, Instruction sink, CallInstruction call, string msg, Class sourceClass
|
||||
where
|
||||
flows(source, msg, sourceClass, sink, call) and
|
||||
// Only raise an alert if there is no override of the pure virtual function in any base class.
|
||||
|
||||
6
cpp/ql/src/Metrics/Files/FLinesOfDuplicatedCode.qhelp
Normal file
6
cpp/ql/src/Metrics/Files/FLinesOfDuplicatedCode.qhelp
Normal file
@@ -0,0 +1,6 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<include src="FLinesOfDuplicatedCodeCommon.inc.qhelp" />
|
||||
</qhelp>
|
||||
27
cpp/ql/src/Metrics/Files/FLinesOfDuplicatedCode.ql
Normal file
27
cpp/ql/src/Metrics/Files/FLinesOfDuplicatedCode.ql
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @deprecated
|
||||
* @name Duplicated lines in files
|
||||
* @description The number of lines in a file, including code, comment
|
||||
* and whitespace lines, which are duplicated in at least
|
||||
* one other place.
|
||||
* @kind treemap
|
||||
* @treemap.warnOn highValues
|
||||
* @metricType file
|
||||
* @metricAggregate avg sum max
|
||||
* @id cpp/duplicated-lines-in-files
|
||||
* @tags testability
|
||||
* modularity
|
||||
*/
|
||||
|
||||
import external.CodeDuplication
|
||||
|
||||
from File f, int n
|
||||
where
|
||||
n =
|
||||
count(int line |
|
||||
exists(DuplicateBlock d | d.sourceFile() = f |
|
||||
line in [d.sourceStartLine() .. d.sourceEndLine()]
|
||||
) and
|
||||
not whitelistedLineForDuplication(f, line)
|
||||
)
|
||||
select f, n order by n desc
|
||||
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
|
||||
<p>
|
||||
This metric measures the number of lines in a file that are contained within a block that is duplicated elsewhere. These lines may include code, comments and whitespace, and the duplicate block may be in this file or in another file.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
A file that contains many lines that are duplicated within the code base is problematic
|
||||
for a number of reasons.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<include src="DuplicationProblems.inc.qhelp" />
|
||||
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
Refactor files with lots of duplicated code to extract the common code into
|
||||
a shared library or module.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<references>
|
||||
|
||||
|
||||
<li>Wikipedia: <a href="http://en.wikipedia.org/wiki/Duplicate_code">Duplicate code</a>.</li>
|
||||
<li>M. Fowler, <em>Refactoring</em>. Addison-Wesley, 1999.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -65,7 +65,6 @@ where
|
||||
midNode.getNode().asExpr() = mid and
|
||||
mid = w.getASource() and
|
||||
dest = w.getDest() and
|
||||
not dest.(VariableAccess).getTarget().getName() = ["stdin", "stdout", "stderr"] and // exclude calls with standard streams
|
||||
not isFileName(globalValueNumber(source)) and // file names are not passwords
|
||||
not exists(string convChar | convChar = w.getSourceConvChar(mid) | not convChar = ["s", "S"]) // ignore things written with other conversion characters
|
||||
select w, sourceNode, midNode,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 7.5
|
||||
* @precision high
|
||||
* @precision medium
|
||||
* @id cpp/cleartext-transmission
|
||||
* @tags security
|
||||
* external/cwe/cwe-319
|
||||
@@ -14,8 +14,8 @@
|
||||
import cpp
|
||||
import semmle.code.cpp.security.SensitiveExprs
|
||||
import semmle.code.cpp.dataflow.TaintTracking
|
||||
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
|
||||
import semmle.code.cpp.models.interfaces.FlowSource
|
||||
import semmle.code.cpp.commons.File
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
@@ -27,7 +27,6 @@ class SensitiveNode extends DataFlow::Node {
|
||||
this.asExpr() = any(SensitiveVariable sv).getInitializer().getExpr() or
|
||||
this.asExpr().(VariableAccess).getTarget() =
|
||||
any(SensitiveVariable sv).(GlobalOrNamespaceVariable) or
|
||||
this.asExpr().(VariableAccess).getTarget() = any(SensitiveVariable v | v instanceof Field) or
|
||||
this.asUninitialized() instanceof SensitiveVariable or
|
||||
this.asParameter() instanceof SensitiveVariable or
|
||||
this.asExpr().(FunctionCall).getTarget() instanceof SensitiveFunction
|
||||
@@ -59,10 +58,7 @@ class Send extends SendRecv instanceof RemoteFlowSinkFunction {
|
||||
call.getTarget() = this and
|
||||
exists(FunctionInput input, int arg |
|
||||
super.hasSocketInput(input) and
|
||||
(
|
||||
input.isParameter(arg) or
|
||||
input.isParameterDeref(arg)
|
||||
) and
|
||||
input.isParameter(arg) and
|
||||
result = call.getArgument(arg)
|
||||
)
|
||||
}
|
||||
@@ -85,10 +81,7 @@ class Recv extends SendRecv instanceof RemoteFlowSourceFunction {
|
||||
call.getTarget() = this and
|
||||
exists(FunctionInput input, int arg |
|
||||
super.hasSocketInput(input) and
|
||||
(
|
||||
input.isParameter(arg) or
|
||||
input.isParameterDeref(arg)
|
||||
) and
|
||||
input.isParameter(arg) and
|
||||
result = call.getArgument(arg)
|
||||
)
|
||||
}
|
||||
@@ -121,32 +114,24 @@ abstract class NetworkSendRecv extends FunctionCall {
|
||||
NetworkSendRecv() {
|
||||
this.getTarget() = target and
|
||||
// exclude calls based on the socket...
|
||||
not exists(DataFlow::Node src, DataFlow::Node dest |
|
||||
DataFlow::localFlow(src, dest) and
|
||||
dest.asExpr() = target.getSocketExpr(this) and
|
||||
not exists(GVN g |
|
||||
g = globalValueNumber(target.getSocketExpr(this)) and
|
||||
(
|
||||
// literal constant
|
||||
src.asExpr() instanceof Literal
|
||||
globalValueNumber(any(Literal l)) = g
|
||||
or
|
||||
// variable (such as a global) initialized to a literal constant
|
||||
exists(Variable v |
|
||||
v.getInitializer().getExpr() instanceof Literal and
|
||||
src.asExpr() = v.getAnAccess()
|
||||
g = globalValueNumber(v.getAnAccess())
|
||||
)
|
||||
or
|
||||
// result of a function call with literal inputs (likely constant)
|
||||
forex(Expr arg | arg = src.asExpr().(FunctionCall).getAnArgument() | arg instanceof Literal)
|
||||
or
|
||||
// variable called `stdin`, `stdout` or `stderr`
|
||||
src.asExpr().(VariableAccess).getTarget().getName() = ["stdin", "stdout", "stderr"]
|
||||
or
|
||||
// open of `"/dev/tty"`
|
||||
exists(FunctionCall fc |
|
||||
fopenCall(fc) and
|
||||
fc.getAnArgument().getValue() = "/dev/tty" and
|
||||
src.asExpr() = fc
|
||||
forex(Expr arg | arg = fc.getAnArgument() | arg instanceof Literal) and
|
||||
g = globalValueNumber(fc)
|
||||
)
|
||||
// (this is not exhaustive)
|
||||
// (this is far from exhaustive)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -168,16 +153,6 @@ class NetworkRecv extends NetworkSendRecv {
|
||||
override Recv target;
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
predicate encryptionFunction(Function f) {
|
||||
f.getName().toLowerCase().regexpMatch(".*(crypt|encode|decode|hash|securezero).*")
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
predicate encryptionType(UserType t) {
|
||||
t.getName().toLowerCase().regexpMatch(".*(crypt|encode|decode|hash|securezero).*")
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression that is an argument or return value from an encryption /
|
||||
* decryption call. This is quite inclusive to minimize false positives, for
|
||||
@@ -187,7 +162,10 @@ predicate encryptionType(UserType t) {
|
||||
class Encrypted extends Expr {
|
||||
Encrypted() {
|
||||
exists(FunctionCall fc |
|
||||
encryptionFunction(fc.getTarget()) and
|
||||
fc.getTarget()
|
||||
.getName()
|
||||
.toLowerCase()
|
||||
.regexpMatch(".*(crypt|encode|decode|hash|securezero).*") and
|
||||
(
|
||||
this = fc or
|
||||
this = fc.getAnArgument()
|
||||
@@ -196,7 +174,7 @@ class Encrypted extends Expr {
|
||||
or
|
||||
exists(Type t |
|
||||
this.getType().refersTo(t) and
|
||||
encryptionType(t)
|
||||
t.getName().toLowerCase().regexpMatch(".*(crypt|encode|decode|hash|securezero).*")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @description Non-HTTPS connections can be intercepted by third parties.
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @precision high
|
||||
* @precision medium
|
||||
* @id cpp/non-https-url
|
||||
* @tags security
|
||||
* external/cwe/cwe-319
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.dataflow.TaintTracking
|
||||
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
@@ -58,12 +57,7 @@ class HttpStringToUrlOpenConfig extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSource(DataFlow::Node src) {
|
||||
// Sources are strings containing an HTTP URL not in a private domain.
|
||||
src.asExpr() instanceof HttpStringLiteral and
|
||||
// block taint starting at `strstr`, which is likely testing an existing URL, rather than constructing an HTTP URL.
|
||||
not exists(FunctionCall fc |
|
||||
fc.getTarget().getName() = ["strstr", "strcasestr"] and
|
||||
fc.getArgument(1) = globalValueNumber(src.asExpr()).getAnExpr()
|
||||
)
|
||||
src.asExpr() instanceof HttpStringLiteral
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
void encrypt_with_openssl(EVP_PKEY_CTX *ctx) {
|
||||
|
||||
// BAD: only 1024 bits for an RSA key
|
||||
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 1024);
|
||||
|
||||
// GOOD: 2048 bits for an RSA key
|
||||
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>Using cryptographic algorithms with a small key size can leave data vulnerable to being decrypted.</p>
|
||||
|
||||
<p>Many cryptographic algorithms provided by cryptography libraries can be configured with key sizes that are
|
||||
vulnerable to brute force attacks. Using such a key size means that an attacker may be able to easily decrypt the
|
||||
encrypted data.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Ensure that you use a strong, modern cryptographic algorithm. Use at least AES-128 or RSA-2048.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>The following code shows an example of using the <code>openssl</code> library to generate an RSA key.
|
||||
When creating a key, you must specify which key size to use. The first example uses 1024 bits, which is not
|
||||
considered sufficient. The second example uses 2048 bits, which is currently considered sufficient.</p>
|
||||
|
||||
<sample src="InsufficientKeySize.c" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>NIST, FIPS 140 Annex a: <a href="http://csrc.nist.gov/publications/fips/fips140-2/fips1402annexa.pdf">
|
||||
Approved Security Functions</a>.</li>
|
||||
<li>NIST, SP 800-131A: <a href="https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar2.pdf">
|
||||
Transitions: Recommendation for Transitioning the Use of Cryptographic Algorithms and Key Lengths</a>.</li>
|
||||
|
||||
<!-- LocalWords: CWE
|
||||
-->
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
* @name Use of a cryptographic algorithm with insufficient key size
|
||||
* @description Using cryptographic algorithms with too small a key size can
|
||||
* allow an attacker to compromise security.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id cpp/insufficient-key-size
|
||||
* @tags security
|
||||
* external/cwe/cwe-326
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.ir.dataflow.DataFlow
|
||||
import semmle.code.cpp.ir.IR
|
||||
import DataFlow::PathGraph
|
||||
|
||||
// Gets the recommended minimum key size (in bits) of `func`, the name of an encryption function that accepts a key size as parameter `paramIndex`
|
||||
int getMinimumKeyStrength(string func, int paramIndex) {
|
||||
func =
|
||||
[
|
||||
"EVP_PKEY_CTX_set_dsa_paramgen_bits", "DSA_generate_parameters_ex",
|
||||
"EVP_PKEY_CTX_set_rsa_keygen_bits", "RSA_generate_key_ex", "RSA_generate_key_fips",
|
||||
"EVP_PKEY_CTX_set_dh_paramgen_prime_len", "DH_generate_parameters_ex"
|
||||
] and
|
||||
paramIndex = 1 and
|
||||
result = 2048
|
||||
}
|
||||
|
||||
class KeyStrengthFlow extends DataFlow::Configuration {
|
||||
KeyStrengthFlow() { this = "KeyStrengthFlow" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) {
|
||||
exists(int bits |
|
||||
node.asInstruction().(IntegerConstantInstruction).getValue().toInt() = bits and
|
||||
bits < getMinimumKeyStrength(_, _) and
|
||||
bits > 0 // exclude sentinel values
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node node) {
|
||||
exists(FunctionCall fc, string name, int param |
|
||||
node.asExpr() = fc.getArgument(param) and
|
||||
fc.getTarget().hasGlobalName(name) and
|
||||
exists(getMinimumKeyStrength(name, param))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from
|
||||
DataFlow::PathNode source, DataFlow::PathNode sink, KeyStrengthFlow conf, FunctionCall fc,
|
||||
int param, string name, int minimumBits, int bits
|
||||
where
|
||||
conf.hasFlowPath(source, sink) and
|
||||
sink.getNode().asExpr() = fc.getArgument(param) and
|
||||
fc.getTarget().hasGlobalName(name) and
|
||||
minimumBits = getMinimumKeyStrength(name, param) and
|
||||
bits = source.getNode().asInstruction().(ConstantValueInstruction).getValue().toInt() and
|
||||
bits < minimumBits and
|
||||
bits != 0
|
||||
select fc, source, sink,
|
||||
"The key size $@ is less than the recommended key size of " + minimumBits.toString() + " bits.",
|
||||
source, bits.toString()
|
||||
@@ -3,10 +3,10 @@
|
||||
* @description Exposing system data or debugging information helps
|
||||
* an adversary learn about the system and form an
|
||||
* attack plan.
|
||||
* @kind path-problem
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 6.5
|
||||
* @precision high
|
||||
* @precision medium
|
||||
* @id cpp/system-data-exposure
|
||||
* @tags security
|
||||
* external/cwe/cwe-497
|
||||
@@ -14,9 +14,7 @@
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.commons.Environment
|
||||
import semmle.code.cpp.ir.dataflow.TaintTracking
|
||||
import semmle.code.cpp.models.interfaces.FlowSource
|
||||
import DataFlow::PathGraph
|
||||
import semmle.code.cpp.security.OutputWrite
|
||||
|
||||
/**
|
||||
* An element that should not be exposed to an adversary.
|
||||
@@ -26,19 +24,42 @@ abstract class SystemData extends Element {
|
||||
* Gets an expression that is part of this `SystemData`.
|
||||
*/
|
||||
abstract Expr getAnExpr();
|
||||
|
||||
/**
|
||||
* Gets an expression whose value originates from, or is used by,
|
||||
* this `SystemData`.
|
||||
*/
|
||||
Expr getAnExprIndirect() {
|
||||
// direct SystemData
|
||||
result = this.getAnExpr() or
|
||||
// flow via global or member variable (conservative approximation)
|
||||
result = this.getAnAffectedVar().getAnAccess() or
|
||||
// flow via stack variable
|
||||
definitionUsePair(_, this.getAnExprIndirect(), result) or
|
||||
useUsePair(_, this.getAnExprIndirect(), result) or
|
||||
useUsePair(_, result, this.getAnExprIndirect()) or
|
||||
// flow from assigned value to assignment expression
|
||||
result.(AssignExpr).getRValue() = this.getAnExprIndirect()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a global or member variable that may be affected by this system
|
||||
* data (conservative approximation).
|
||||
*/
|
||||
private Variable getAnAffectedVar() {
|
||||
(
|
||||
result.getAnAssignedValue() = this.getAnExprIndirect() or
|
||||
result.getAnAccess() = this.getAnExprIndirect()
|
||||
) and
|
||||
not result instanceof LocalScopeVariable
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data originating from the environment.
|
||||
*/
|
||||
class EnvData extends SystemData {
|
||||
EnvData() {
|
||||
// identify risky looking environment variables only
|
||||
this.(EnvironmentRead)
|
||||
.getEnvironmentVariable()
|
||||
.toLowerCase()
|
||||
.regexpMatch(".*(user|host|admin|root|home|path|http|ssl|snmp|sock|port|proxy|pass|token|crypt|key).*")
|
||||
}
|
||||
EnvData() { this instanceof EnvironmentRead }
|
||||
|
||||
override Expr getAnExpr() { result = this }
|
||||
}
|
||||
@@ -70,6 +91,11 @@ class SQLConnectInfo extends SystemData {
|
||||
}
|
||||
|
||||
private predicate posixSystemInfo(FunctionCall source, Element use) {
|
||||
// long sysconf(int name)
|
||||
// - various OS / system values and limits
|
||||
source.getTarget().hasName("sysconf") and
|
||||
use = source
|
||||
or
|
||||
// size_t confstr(int name, char *buf, size_t len)
|
||||
// - various OS / system strings, such as the libc version
|
||||
// int statvfs(const char *__path, struct statvfs *__buf)
|
||||
@@ -285,31 +311,70 @@ class RegQuery extends SystemData {
|
||||
override Expr getAnExpr() { regQuery(this, result) }
|
||||
}
|
||||
|
||||
class ExposedSystemDataConfiguration extends TaintTracking::Configuration {
|
||||
ExposedSystemDataConfiguration() { this = "ExposedSystemDataConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source.asConvertedExpr() = any(SystemData sd).getAnExpr()
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(FunctionCall fc, FunctionInput input, int arg |
|
||||
fc.getTarget().(RemoteFlowSinkFunction).hasRemoteFlowSink(input, _) and
|
||||
input.isParameterDeref(arg) and
|
||||
fc.getArgument(arg).getAChild*() = sink.asExpr()
|
||||
)
|
||||
}
|
||||
/**
|
||||
* Somewhere data is output.
|
||||
*/
|
||||
abstract class DataOutput extends Element {
|
||||
/**
|
||||
* Get an expression containing data that is output.
|
||||
*/
|
||||
abstract Expr getASource();
|
||||
}
|
||||
|
||||
from ExposedSystemDataConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
not exists(
|
||||
DataFlow::Node alt // remove duplicate results on conversions
|
||||
|
|
||||
config.hasFlow(source.getNode(), alt) and
|
||||
alt.asConvertedExpr() = sink.getNode().asExpr() and
|
||||
alt != sink.getNode()
|
||||
/**
|
||||
* Data that is output via standard output or standard error.
|
||||
*/
|
||||
class StandardOutput extends DataOutput instanceof OutputWrite {
|
||||
override Expr getASource() { result = OutputWrite.super.getASource() }
|
||||
}
|
||||
|
||||
private predicate socketCallOrIndirect(FunctionCall call) {
|
||||
// direct socket call
|
||||
// int socket(int domain, int type, int protocol);
|
||||
call.getTarget().getName() = "socket"
|
||||
or
|
||||
exists(ReturnStmt rtn |
|
||||
// indirect socket call
|
||||
call.getTarget() = rtn.getEnclosingFunction() and
|
||||
(
|
||||
socketCallOrIndirect(rtn.getExpr()) or
|
||||
socketCallOrIndirect(rtn.getExpr().(VariableAccess).getTarget().getAnAssignedValue())
|
||||
)
|
||||
)
|
||||
select sink, source, sink, "This operation exposes system data from $@.", source,
|
||||
source.getNode().toString()
|
||||
}
|
||||
|
||||
private predicate socketFileDescriptor(Expr e) {
|
||||
exists(Variable var, FunctionCall socket |
|
||||
socketCallOrIndirect(socket) and
|
||||
var.getAnAssignedValue() = socket and
|
||||
e = var.getAnAccess()
|
||||
)
|
||||
}
|
||||
|
||||
private predicate socketOutput(FunctionCall call, Expr data) {
|
||||
(
|
||||
// ssize_t send(int sockfd, const void *buf, size_t len, int flags);
|
||||
// ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
|
||||
// const struct sockaddr *dest_addr, socklen_t addrlen);
|
||||
// ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
|
||||
// int write(int handle, void *buffer, int nbyte);
|
||||
call.getTarget().hasGlobalName(["send", "sendto", "sendmsg", "write"]) and
|
||||
data = call.getArgument(1) and
|
||||
socketFileDescriptor(call.getArgument(0))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Data that is output via a socket.
|
||||
*/
|
||||
class SocketOutput extends DataOutput {
|
||||
SocketOutput() { socketOutput(this, _) }
|
||||
|
||||
override Expr getASource() { socketOutput(this, result) }
|
||||
}
|
||||
|
||||
from SystemData sd, DataOutput ow
|
||||
where
|
||||
sd.getAnExprIndirect() = ow.getASource() or
|
||||
sd.getAnExprIndirect() = ow.getASource().getAChild*()
|
||||
select ow, "This operation exposes system data from $@.", sd, sd.toString()
|
||||
|
||||
@@ -12,16 +12,17 @@
|
||||
|
||||
import cpp
|
||||
import FilePermissions
|
||||
import semmle.code.cpp.commons.unix.Constants
|
||||
|
||||
predicate worldWritableCreation(FileCreationExpr fc, int mode) {
|
||||
mode = localUmask(fc).mask(fc.getMode()) and
|
||||
setsAnyBits(mode, UnixConstants::s_iwoth())
|
||||
sets(mode, s_iwoth())
|
||||
}
|
||||
|
||||
predicate setWorldWritable(FunctionCall fc, int mode) {
|
||||
fc.getTarget().getName() = ["chmod", "fchmod", "_chmod", "_wchmod"] and
|
||||
mode = fc.getArgument(1).getValue().toInt() and
|
||||
setsAnyBits(mode, UnixConstants::s_iwoth())
|
||||
sets(mode, s_iwoth())
|
||||
}
|
||||
|
||||
from Expr fc, int mode, string message
|
||||
|
||||
@@ -1,49 +1,5 @@
|
||||
import cpp
|
||||
import semmle.code.cpp.commons.unix.Constants as UnixConstants
|
||||
|
||||
/**
|
||||
* Gets the number corresponding to the contents of `input` in base-16.
|
||||
* Note: the first two characters of `input` must be `0x`. For example:
|
||||
* `parseHex("0x123abc") = 1194684`.
|
||||
*/
|
||||
bindingset[input]
|
||||
int parseHex(string input) {
|
||||
exists(string lowerCaseInput | lowerCaseInput = input.toLowerCase() |
|
||||
lowerCaseInput.regexpMatch("0x[0-9a-f]+") and
|
||||
result =
|
||||
strictsum(int ix |
|
||||
ix in [2 .. input.length()]
|
||||
|
|
||||
16.pow(input.length() - (ix + 1)) * "0123456789abcdef".indexOf(lowerCaseInput.charAt(ix))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value defined by the `O_CREAT` macro if the macro
|
||||
* exists and if every definition defines the same value.
|
||||
*/
|
||||
int o_creat() {
|
||||
result =
|
||||
unique(int v |
|
||||
exists(Macro m | m.getName() = "O_CREAT" |
|
||||
v = parseHex(m.getBody()) or v = UnixConstants::parseOctal(m.getBody())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value defined by the `O_TMPFILE` macro if the macro
|
||||
* exists and if every definition defines the same value.
|
||||
*/
|
||||
int o_tmpfile() {
|
||||
result =
|
||||
unique(int v |
|
||||
exists(Macro m | m.getName() = "O_TMPFILE" |
|
||||
v = parseHex(m.getBody()) or v = UnixConstants::parseOctal(m.getBody())
|
||||
)
|
||||
)
|
||||
}
|
||||
import semmle.code.cpp.commons.unix.Constants
|
||||
|
||||
bindingset[n, digit]
|
||||
private string octalDigit(int n, int digit) {
|
||||
@@ -64,17 +20,11 @@ string octalFileMode(int mode) {
|
||||
else result = "[non-standard mode: decimal " + mode + "]"
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the bitmask `value` sets the bits in `flag`.
|
||||
*/
|
||||
bindingset[value, flag]
|
||||
predicate setsFlag(int value, int flag) { value.bitAnd(flag) = flag }
|
||||
|
||||
/**
|
||||
* Holds if the bitmask `mask` sets any of the bit fields in `fields`.
|
||||
*/
|
||||
bindingset[mask, fields]
|
||||
predicate setsAnyBits(int mask, int fields) { mask.bitAnd(fields) != 0 }
|
||||
predicate sets(int mask, int fields) { mask.bitAnd(fields) != 0 }
|
||||
|
||||
/**
|
||||
* Gets the value that `fc` sets the umask to, if `fc` is a call to
|
||||
@@ -133,24 +83,16 @@ abstract class FileCreationExpr extends FunctionCall {
|
||||
abstract int getMode();
|
||||
}
|
||||
|
||||
abstract class FileCreationWithOptionalModeExpr extends FileCreationExpr {
|
||||
abstract predicate hasModeArgument();
|
||||
}
|
||||
|
||||
class OpenCreationExpr extends FileCreationWithOptionalModeExpr {
|
||||
class OpenCreationExpr extends FileCreationExpr {
|
||||
OpenCreationExpr() {
|
||||
this.getTarget().hasGlobalOrStdName(["open", "_open", "_wopen"]) and
|
||||
exists(int flag | flag = this.getArgument(1).getValue().toInt() |
|
||||
setsFlag(flag, o_creat()) or setsFlag(flag, o_tmpfile())
|
||||
)
|
||||
this.getTarget().getName() = ["open", "_open", "_wopen"] and
|
||||
sets(this.getArgument(1).getValue().toInt(), o_creat())
|
||||
}
|
||||
|
||||
override Expr getPath() { result = this.getArgument(0) }
|
||||
|
||||
override predicate hasModeArgument() { exists(this.getArgument(2)) }
|
||||
|
||||
override int getMode() {
|
||||
if this.hasModeArgument()
|
||||
if exists(this.getArgument(2))
|
||||
then result = this.getArgument(2).getValue().toInt()
|
||||
else
|
||||
// assume anything is permitted
|
||||
@@ -166,35 +108,20 @@ class CreatCreationExpr extends FileCreationExpr {
|
||||
override int getMode() { result = this.getArgument(1).getValue().toInt() }
|
||||
}
|
||||
|
||||
class OpenatCreationExpr extends FileCreationWithOptionalModeExpr {
|
||||
class OpenatCreationExpr extends FileCreationExpr {
|
||||
OpenatCreationExpr() {
|
||||
this.getTarget().hasGlobalOrStdName("openat") and
|
||||
exists(int flag | flag = this.getArgument(2).getValue().toInt() |
|
||||
setsFlag(flag, o_creat()) or setsFlag(flag, o_tmpfile())
|
||||
)
|
||||
this.getTarget().getName() = "openat" and
|
||||
this.getNumberOfArguments() = 4
|
||||
}
|
||||
|
||||
override Expr getPath() { result = this.getArgument(1) }
|
||||
|
||||
override predicate hasModeArgument() { exists(this.getArgument(3)) }
|
||||
|
||||
override int getMode() {
|
||||
if this.hasModeArgument()
|
||||
then result = this.getArgument(3).getValue().toInt()
|
||||
else
|
||||
// assume anything is permitted
|
||||
result = 0.bitNot()
|
||||
}
|
||||
override int getMode() { result = this.getArgument(3).getValue().toInt() }
|
||||
}
|
||||
|
||||
private int fopenMode() {
|
||||
result =
|
||||
UnixConstants::s_irusr()
|
||||
.bitOr(UnixConstants::s_irgrp())
|
||||
.bitOr(UnixConstants::s_iroth())
|
||||
.bitOr(UnixConstants::s_iwusr())
|
||||
.bitOr(UnixConstants::s_iwgrp())
|
||||
.bitOr(UnixConstants::s_iwoth())
|
||||
s_irusr().bitOr(s_irgrp()).bitOr(s_iroth()).bitOr(s_iwusr()).bitOr(s_iwgrp()).bitOr(s_iwoth())
|
||||
}
|
||||
|
||||
class FopenCreationExpr extends FileCreationExpr {
|
||||
@@ -226,6 +153,6 @@ class FopensCreationExpr extends FileCreationExpr {
|
||||
// fopen_s has restrictive permissions unless you have "u" in the mode
|
||||
if this.getArgument(2).getValue().charAt(_) = "u"
|
||||
then result = fopenMode()
|
||||
else result = UnixConstants::s_irusr().bitOr(UnixConstants::s_iwusr())
|
||||
else result = s_irusr().bitOr(s_iwusr())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
int open_file_bad() {
|
||||
// BAD - this uses arbitrary bytes from the stack as mode argument
|
||||
return open(FILE, O_CREAT)
|
||||
}
|
||||
|
||||
int open_file_good() {
|
||||
// GOOD - the mode argument is supplied
|
||||
return open(FILE, O_CREAT, S_IRUSR | S_IWUSR)
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
When opening a file with the <code>O_CREAT</code> or <code>O_TMPFILE</code> flag, the <code>mode</code> must
|
||||
be supplied. If the <code>mode</code> argument is omitted, some arbitrary bytes from the stack will be used
|
||||
as the file mode. This leaks some bits from the stack into the permissions of the file.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
The <code>mode</code> must be supplied when <code>O_CREAT</code> or <code>O_TMPFILE</code> is specified.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The first example opens a file with the <code>O_CREAT</code> flag without supplying the <code>mode</code>
|
||||
argument. In this case arbitrary bytes from the stack will be used as <code>mode</code> argument. The
|
||||
second example correctly supplies the <code>mode</code> argument and creates a file that is user readable
|
||||
and writable.
|
||||
</p>
|
||||
|
||||
<sample src="OpenCallMissingModeArgument.c" />
|
||||
|
||||
</example>
|
||||
</qhelp>
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* @name File opened with O_CREAT flag but without mode argument
|
||||
* @description Opening a file with the O_CREAT flag but without mode argument reads arbitrary bytes from the stack.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @security-severity 7.8
|
||||
* @precision high
|
||||
* @id cpp/open-call-with-mode-argument
|
||||
* @tags security
|
||||
* external/cwe/cwe-732
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import FilePermissions
|
||||
|
||||
from FileCreationWithOptionalModeExpr fc
|
||||
where not fc.hasModeArgument()
|
||||
select fc,
|
||||
"A file is created here without providing a mode argument, which may leak bits from the stack."
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: newQuery
|
||||
---
|
||||
* A new `cpp/very-likely-overruning-write` query has been added to the default query suite for C/C++. The query reports some results that were formerly flagged by `cpp/overruning-write`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added exception for GLib's gboolean to cpp/ambiguously-signed-bit-field.
|
||||
This change reduces the number of false positives in the query.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: newQuery
|
||||
---
|
||||
* The "Uncontrolled data in arithmetic expression" (cpp/uncontrolled-arithmetic) query has been enhanced to reduce false positive results and its @precision increased to high.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The "Potential improper null termination" (`cpp/improper-null-termination`) query now produces fewer false positive results around control flow branches and loops.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user