mirror of
https://github.com/github/codeql.git
synced 2026-05-26 09:01:22 +02:00
Compare commits
253 Commits
tausbn/pyt
...
copilot/ma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a786ac4e0 | ||
|
|
a8997679b5 | ||
|
|
c0ebdd437a | ||
|
|
cb590537c6 | ||
|
|
341354f76c | ||
|
|
87f92f36d0 | ||
|
|
e467cf6482 | ||
|
|
e3bcd3bdd5 | ||
|
|
7c38dc34f3 | ||
|
|
fd8821fcb5 | ||
|
|
cc99867969 | ||
|
|
680ea0b960 | ||
|
|
a5763303fc | ||
|
|
8d16a2b4fa | ||
|
|
97ebc0e839 | ||
|
|
d82fc67b36 | ||
|
|
8cebf510dc | ||
|
|
b5723bd75d | ||
|
|
fef314e27f | ||
|
|
1363c54a9f | ||
|
|
09caeca7e9 | ||
|
|
0d0d34cc71 | ||
|
|
be245357cc | ||
|
|
ee00b98476 | ||
|
|
6ae32f22a8 | ||
|
|
a59c865328 | ||
|
|
d0c48893f5 | ||
|
|
d99247cf13 | ||
|
|
a9eb801fea | ||
|
|
9a4bc69843 | ||
|
|
d4fef1c68e | ||
|
|
d2fcced5ad | ||
|
|
093c27955f | ||
|
|
f99f26f908 | ||
|
|
f6c81ff30a | ||
|
|
4b364639a2 | ||
|
|
bde9378cee | ||
|
|
769b3a6aae | ||
|
|
7d65baccb2 | ||
|
|
77cb35380c | ||
|
|
c5457d3e30 | ||
|
|
2f0d3288ce | ||
|
|
93c656065d | ||
|
|
e86ce8feed | ||
|
|
d3177b9e82 | ||
|
|
f4550544ce | ||
|
|
f9521e9e88 | ||
|
|
f342bae962 | ||
|
|
bceab0b44e | ||
|
|
02f8984aff | ||
|
|
b63e34d467 | ||
|
|
ec726f5941 | ||
|
|
208ae7aa01 | ||
|
|
be746b775b | ||
|
|
bc518c08c7 | ||
|
|
06ea72ccc7 | ||
|
|
57086f60b9 | ||
|
|
6452cc549f | ||
|
|
f59bacab30 | ||
|
|
356905ba36 | ||
|
|
dc291ffad7 | ||
|
|
d191d09c55 | ||
|
|
9c6276ef48 | ||
|
|
21f2c81f24 | ||
|
|
d30aab47ea | ||
|
|
fef758998c | ||
|
|
b9592fef2d | ||
|
|
7a33e2f539 | ||
|
|
34101b5ca0 | ||
|
|
07db9cf3c4 | ||
|
|
92c9a8e146 | ||
|
|
21cb11ea5d | ||
|
|
4c525ce7ab | ||
|
|
0f794b57ed | ||
|
|
2e987f8d78 | ||
|
|
2139b97628 | ||
|
|
7fc1d53ede | ||
|
|
50d83ada95 | ||
|
|
10678d3a42 | ||
|
|
7d538988a6 | ||
|
|
7f17b7716d | ||
|
|
b57fa1bffa | ||
|
|
662b1e7df6 | ||
|
|
750f1ae8e9 | ||
|
|
e0b06c8e72 | ||
|
|
8d6aceb008 | ||
|
|
3fad6bdc0c | ||
|
|
518d170acd | ||
|
|
b9ad36c11d | ||
|
|
4ca071210b | ||
|
|
1ddf81c58c | ||
|
|
c155394f25 | ||
|
|
2c76e6e637 | ||
|
|
2ff5c2c234 | ||
|
|
98d8cd1d6d | ||
|
|
e8e46accc0 | ||
|
|
06f0c1189f | ||
|
|
082dc61620 | ||
|
|
2e7da72277 | ||
|
|
5b17d8cf76 | ||
|
|
f2a0724620 | ||
|
|
b8222167d2 | ||
|
|
6efd844180 | ||
|
|
34f405f465 | ||
|
|
d4a0846c6c | ||
|
|
d180900ab4 | ||
|
|
97670b3674 | ||
|
|
ff48ac5434 | ||
|
|
af63e63686 | ||
|
|
19faf8f30b | ||
|
|
3aaee9d981 | ||
|
|
8e19b05a25 | ||
|
|
1ac9e5a2a4 | ||
|
|
3f9ad14473 | ||
|
|
9cb1c89a02 | ||
|
|
a57f803b37 | ||
|
|
1130870168 | ||
|
|
8ddfee9971 | ||
|
|
8df4dfb585 | ||
|
|
2f7526d70b | ||
|
|
eeb09ae389 | ||
|
|
3c4a386f3f | ||
|
|
e3dbf5b022 | ||
|
|
72534e882b | ||
|
|
a99b3f2c3b | ||
|
|
92718a98d0 | ||
|
|
e70727524a | ||
|
|
d6055754b6 | ||
|
|
c24b43d01e | ||
|
|
179a4cd41a | ||
|
|
a929c0bf24 | ||
|
|
427ccee3b9 | ||
|
|
22f16dda85 | ||
|
|
db0a3e38e2 | ||
|
|
e7edf15031 | ||
|
|
4c77e0f315 | ||
|
|
7124cd4e6e | ||
|
|
c076992b83 | ||
|
|
659d8e7c90 | ||
|
|
1e8de0511b | ||
|
|
bce0a4d2a7 | ||
|
|
2160910d56 | ||
|
|
a5c8a5b5f8 | ||
|
|
f8a3ce7bf8 | ||
|
|
f9f1d9eecc | ||
|
|
d3066af2e2 | ||
|
|
8c03136c25 | ||
|
|
d52e9bc18c | ||
|
|
b8b841cfba | ||
|
|
df9f8ee386 | ||
|
|
99f4930e24 | ||
|
|
e9df9147ad | ||
|
|
f32f85399a | ||
|
|
c56feb7644 | ||
|
|
7ef60a8649 | ||
|
|
ca2838b361 | ||
|
|
7a6ab70091 | ||
|
|
7d6e08ecf1 | ||
|
|
dfa6d20072 | ||
|
|
821cc0e875 | ||
|
|
7094fb07a4 | ||
|
|
52809133f5 | ||
|
|
056aa342fe | ||
|
|
f58a6e5d3a | ||
|
|
f11815c633 | ||
|
|
52cfd49087 | ||
|
|
8c1c039edf | ||
|
|
c9e0927992 | ||
|
|
d5f667e585 | ||
|
|
b758732a28 | ||
|
|
ba3fadbf20 | ||
|
|
d7d1554461 | ||
|
|
12e0f3f359 | ||
|
|
0bb6ff58cc | ||
|
|
b9c0aca11a | ||
|
|
ee3674cb80 | ||
|
|
3ee369b710 | ||
|
|
e16bb226c0 | ||
|
|
48bf4fd82a | ||
|
|
c271755985 | ||
|
|
a16c43881b | ||
|
|
39e0382089 | ||
|
|
22e012c6f4 | ||
|
|
1b6f3a43ef | ||
|
|
b8c44be599 | ||
|
|
84d1828a9c | ||
|
|
ca7017f3d7 | ||
|
|
f2e7dca65c | ||
|
|
b9b3b3a0b5 | ||
|
|
6c792e69b3 | ||
|
|
4a39055322 | ||
|
|
6552c849f0 | ||
|
|
00d8a10051 | ||
|
|
da7da80b2b | ||
|
|
5db30c9947 | ||
|
|
6fb10555ff | ||
|
|
bbd02b855b | ||
|
|
48a03e2a04 | ||
|
|
a92d97744f | ||
|
|
ef6c1a9968 | ||
|
|
72142b51f7 | ||
|
|
c06d4d2647 | ||
|
|
4a001f960f | ||
|
|
1253553aec | ||
|
|
68dfa5c83b | ||
|
|
25a20f74f0 | ||
|
|
5a65282241 | ||
|
|
2e04d4b888 | ||
|
|
79499c240a | ||
|
|
267a46d01b | ||
|
|
341059d2d0 | ||
|
|
79841bbc00 | ||
|
|
3c3c58b0a9 | ||
|
|
9bf1072a01 | ||
|
|
a5f23ade8c | ||
|
|
017b6f2e44 | ||
|
|
6a6bb5ebf9 | ||
|
|
15f7a95209 | ||
|
|
b631138b63 | ||
|
|
093d36ebe6 | ||
|
|
c7349740f0 | ||
|
|
efa797a21d | ||
|
|
77d4f5a2dc | ||
|
|
edf88b34da | ||
|
|
0215ea3ee3 | ||
|
|
35ac66d3aa | ||
|
|
219fe03637 | ||
|
|
b7a5b08d61 | ||
|
|
87ec22db65 | ||
|
|
f52195e96d | ||
|
|
430ed055bc | ||
|
|
8e85c4c0ea | ||
|
|
017822b872 | ||
|
|
eea61ea821 | ||
|
|
b289266398 | ||
|
|
f5545516db | ||
|
|
216bc76694 | ||
|
|
5921dacf52 | ||
|
|
ca44c777f0 | ||
|
|
5283413055 | ||
|
|
dbb8bb86ba | ||
|
|
4013f00b19 | ||
|
|
e0e5319b11 | ||
|
|
d8007a85e6 | ||
|
|
512e27187e | ||
|
|
c5360ba46c | ||
|
|
097681e705 | ||
|
|
63e8061917 | ||
|
|
4dca9aa958 | ||
|
|
da99d3660d | ||
|
|
7f6fd34d46 | ||
|
|
d23a3f821e | ||
|
|
9bf4262dbb |
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -45,3 +45,5 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
exclude-paths:
|
||||
- "misc/bazel/registry/**"
|
||||
|
||||
78
.github/workflows/compile-queries.yml
vendored
78
.github/workflows/compile-queries.yml
vendored
@@ -1,78 +0,0 @@
|
||||
name: "Compile all queries using the latest stable CodeQL CLI"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: # makes sure the cache gets populated - running on the branches people tend to merge into.
|
||||
- main
|
||||
- "rc/*"
|
||||
- "codeql-cli-*"
|
||||
pull_request:
|
||||
paths:
|
||||
- '**.ql'
|
||||
- '**.qll'
|
||||
- '**/qlpack.yml'
|
||||
- '**.dbscheme'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
detect-changes:
|
||||
if: github.repository_owner == 'github'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
languages: ${{ steps.detect.outputs.languages }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Detect changed languages
|
||||
id: detect
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||
# For PRs, detect which languages have changes
|
||||
changed_files=$(gh pr view ${{ github.event.pull_request.number }} --json files --jq '.files.[].path')
|
||||
languages=()
|
||||
for lang in actions cpp csharp go java javascript python ql ruby rust swift; do
|
||||
if echo "$changed_files" | grep -qE "^($lang/|shared/)" ; then
|
||||
languages+=("$lang")
|
||||
fi
|
||||
done
|
||||
echo "languages=$(jq -c -n '$ARGS.positional' --args "${languages[@]}")" >> $GITHUB_OUTPUT
|
||||
else
|
||||
# For pushes to main/rc branches, run all languages
|
||||
echo 'languages=["actions","cpp","csharp","go","java","javascript","python","ql","ruby","rust","swift"]' >> $GITHUB_OUTPUT
|
||||
fi
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
compile-queries:
|
||||
needs: detect-changes
|
||||
if: github.repository_owner == 'github' && needs.detect-changes.outputs.languages != '[]'
|
||||
runs-on: ubuntu-latest-xl
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: ${{ fromJson(needs.detect-changes.outputs.languages) }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Setup CodeQL
|
||||
uses: ./.github/actions/fetch-codeql
|
||||
with:
|
||||
channel: 'release'
|
||||
- name: Cache compilation cache
|
||||
id: query-cache
|
||||
uses: ./.github/actions/cache-query-compilation
|
||||
with:
|
||||
key: ${{ matrix.language }}-queries
|
||||
- name: check formatting
|
||||
run: find shared ${{ matrix.language }}/ql -type f \( -name "*.qll" -o -name "*.ql" \) -print0 | xargs -0 -n 3000 -P 10 codeql query format -q --check-only
|
||||
- name: compile queries - check-only
|
||||
# run with --check-only if running in a PR (github.sha != main)
|
||||
if : ${{ github.event_name == 'pull_request' }}
|
||||
shell: bash
|
||||
run: codeql query compile -q -j0 ${{ matrix.language }}/ql/{src,examples} --keep-going --warnings=error --check-only --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}" --compilation-cache-size=500 --ram=56000
|
||||
- name: compile queries - full
|
||||
# do full compile if running on main - this populates the cache
|
||||
if : ${{ github.event_name != 'pull_request' }}
|
||||
shell: bash
|
||||
run: codeql query compile -q -j0 ${{ matrix.language }}/ql/{src,examples} --keep-going --warnings=error --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}" --compilation-cache-size=500 --ram=56000
|
||||
236
.github/workflows/ruby-build.yml
vendored
236
.github/workflows/ruby-build.yml
vendored
@@ -1,236 +0,0 @@
|
||||
name: "Ruby: Build"
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "ruby/**"
|
||||
- .github/workflows/ruby-build.yml
|
||||
- .github/actions/fetch-codeql/action.yml
|
||||
- codeql-workspace.yml
|
||||
- "shared/tree-sitter-extractor/**"
|
||||
branches:
|
||||
- main
|
||||
- "rc/*"
|
||||
pull_request:
|
||||
paths:
|
||||
- "ruby/**"
|
||||
- .github/workflows/ruby-build.yml
|
||||
- .github/actions/fetch-codeql/action.yml
|
||||
- codeql-workspace.yml
|
||||
- "shared/tree-sitter-extractor/**"
|
||||
branches:
|
||||
- main
|
||||
- "rc/*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Version tag to create"
|
||||
required: false
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ruby
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install GNU tar
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
brew install gnu-tar
|
||||
echo "/usr/local/opt/gnu-tar/libexec/gnubin" >> $GITHUB_PATH
|
||||
- name: Prepare Windows
|
||||
if: runner.os == 'Windows'
|
||||
shell: powershell
|
||||
run: |
|
||||
git config --global core.longpaths true
|
||||
- uses: ./.github/actions/os-version
|
||||
id: os_version
|
||||
- name: Cache entire extractor
|
||||
uses: actions/cache@v3
|
||||
id: cache-extractor
|
||||
with:
|
||||
path: |
|
||||
target/release/codeql-extractor-ruby
|
||||
target/release/codeql-extractor-ruby.exe
|
||||
ruby/extractor/ql/lib/codeql/ruby/ast/internal/TreeSitter.qll
|
||||
key: ${{ runner.os }}-${{ steps.os_version.outputs.version }}-ruby-extractor-${{ hashFiles('ruby/extractor/rust-toolchain.toml', 'ruby/extractor/Cargo.lock') }}-${{ hashFiles('shared/tree-sitter-extractor') }}-${{ hashFiles('ruby/extractor/**/*.rs') }}
|
||||
- uses: actions/cache@v3
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true'
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ runner.os }}-${{ steps.os_version.outputs.version }}-ruby-rust-cargo-${{ hashFiles('ruby/extractor/rust-toolchain.toml', 'ruby/extractor/**/Cargo.lock') }}
|
||||
- name: Check formatting
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true'
|
||||
run: cd extractor && cargo fmt -- --check
|
||||
- name: Build
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true'
|
||||
run: cd extractor && cargo build --verbose
|
||||
- name: Run tests
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true'
|
||||
run: cd extractor && cargo test --verbose
|
||||
- name: Release build
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true'
|
||||
run: cd extractor && cargo build --release
|
||||
- name: Generate dbscheme
|
||||
if: ${{ matrix.os == 'ubuntu-latest' && steps.cache-extractor.outputs.cache-hit != 'true'}}
|
||||
run: ../target/release/codeql-extractor-ruby generate --dbscheme ql/lib/ruby.dbscheme --library ql/lib/codeql/ruby/ast/internal/TreeSitter.qll
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
with:
|
||||
name: ruby.dbscheme
|
||||
path: ruby/ql/lib/ruby.dbscheme
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
with:
|
||||
name: TreeSitter.qll
|
||||
path: ruby/ql/lib/codeql/ruby/ast/internal/TreeSitter.qll
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: extractor-${{ matrix.os }}
|
||||
path: |
|
||||
target/release/codeql-extractor-ruby
|
||||
target/release/codeql-extractor-ruby.exe
|
||||
retention-days: 1
|
||||
compile-queries:
|
||||
if: github.repository_owner == 'github'
|
||||
runs-on: ubuntu-latest-xl
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Fetch CodeQL
|
||||
uses: ./.github/actions/fetch-codeql
|
||||
- name: Cache compilation cache
|
||||
id: query-cache
|
||||
uses: ./.github/actions/cache-query-compilation
|
||||
with:
|
||||
key: ruby-build
|
||||
- name: Build Query Pack
|
||||
run: |
|
||||
PACKS=${{ runner.temp }}/query-packs
|
||||
rm -rf $PACKS
|
||||
codeql pack create ../misc/suite-helpers --output "$PACKS"
|
||||
codeql pack create ../shared/regex --output "$PACKS"
|
||||
codeql pack create ../shared/ssa --output "$PACKS"
|
||||
codeql pack create ../shared/tutorial --output "$PACKS"
|
||||
codeql pack create ql/lib --output "$PACKS"
|
||||
codeql pack create -j0 ql/src --output "$PACKS" --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}"
|
||||
PACK_FOLDER=$(readlink -f "$PACKS"/codeql/ruby-queries/*)
|
||||
codeql generate query-help --format=sarifv2.1.0 --output="${PACK_FOLDER}/rules.sarif" ql/src
|
||||
(cd ql/src; find queries \( -name '*.qhelp' -o -name '*.rb' -o -name '*.erb' \) -exec bash -c 'mkdir -p "'"${PACK_FOLDER}"'/$(dirname "{}")"' \; -exec cp "{}" "${PACK_FOLDER}/{}" \;)
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: codeql-ruby-queries
|
||||
path: |
|
||||
${{ runner.temp }}/query-packs/*
|
||||
retention-days: 1
|
||||
include-hidden-files: true
|
||||
|
||||
package:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build, compile-queries]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ruby.dbscheme
|
||||
path: ruby/ruby
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: extractor-ubuntu-latest
|
||||
path: ruby/linux64
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: extractor-windows-latest
|
||||
path: ruby/win64
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: extractor-macos-latest
|
||||
path: ruby/osx64
|
||||
- run: |
|
||||
mkdir -p ruby
|
||||
cp -r codeql-extractor.yml tools ql/lib/ruby.dbscheme.stats ruby/
|
||||
mkdir -p ruby/tools/{linux64,osx64,win64}
|
||||
cp linux64/codeql-extractor-ruby ruby/tools/linux64/extractor
|
||||
cp osx64/codeql-extractor-ruby ruby/tools/osx64/extractor
|
||||
cp win64/codeql-extractor-ruby.exe ruby/tools/win64/extractor.exe
|
||||
chmod +x ruby/tools/{linux64,osx64}/extractor
|
||||
zip -rq codeql-ruby.zip ruby
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: codeql-ruby-pack
|
||||
path: ruby/codeql-ruby.zip
|
||||
retention-days: 1
|
||||
include-hidden-files: true
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: codeql-ruby-queries
|
||||
path: ruby/qlpacks
|
||||
- run: |
|
||||
echo '{
|
||||
"provide": [
|
||||
"ruby/codeql-extractor.yml",
|
||||
"qlpacks/*/*/*/qlpack.yml"
|
||||
]
|
||||
}' > .codeqlmanifest.json
|
||||
zip -rq codeql-ruby-bundle.zip .codeqlmanifest.json ruby qlpacks
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: codeql-ruby-bundle
|
||||
path: ruby/codeql-ruby-bundle.zip
|
||||
retention-days: 1
|
||||
include-hidden-files: true
|
||||
|
||||
test:
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ github.workspace }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: [package]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Fetch CodeQL
|
||||
uses: ./.github/actions/fetch-codeql
|
||||
|
||||
- name: Download Ruby bundle
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: codeql-ruby-bundle
|
||||
path: ${{ runner.temp }}
|
||||
- name: Unzip Ruby bundle
|
||||
shell: bash
|
||||
run: unzip -q -d "${{ runner.temp }}/ruby-bundle" "${{ runner.temp }}/codeql-ruby-bundle.zip"
|
||||
|
||||
- name: Run QL test
|
||||
shell: bash
|
||||
run: |
|
||||
codeql test run --search-path "${{ runner.temp }}/ruby-bundle" --additional-packs "${{ runner.temp }}/ruby-bundle" ruby/ql/test/library-tests/ast/constants/
|
||||
- name: Create database
|
||||
shell: bash
|
||||
run: |
|
||||
codeql database create --search-path "${{ runner.temp }}/ruby-bundle" --language ruby --source-root ruby/ql/test/library-tests/ast/constants/ ../database
|
||||
- name: Analyze database
|
||||
shell: bash
|
||||
run: |
|
||||
codeql database analyze --search-path "${{ runner.temp }}/ruby-bundle" --format=sarifv2.1.0 --output=out.sarif ../database ruby-code-scanning.qls
|
||||
75
.github/workflows/ruby-dataset-measure.yml
vendored
75
.github/workflows/ruby-dataset-measure.yml
vendored
@@ -1,75 +0,0 @@
|
||||
name: "Ruby: Collect database stats"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- "rc/*"
|
||||
paths:
|
||||
- ruby/ql/lib/ruby.dbscheme
|
||||
- .github/workflows/ruby-dataset-measure.yml
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- "rc/*"
|
||||
paths:
|
||||
- ruby/ql/lib/ruby.dbscheme
|
||||
- .github/workflows/ruby-dataset-measure.yml
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
measure:
|
||||
env:
|
||||
CODEQL_THREADS: 4 # TODO: remove this once it's set by the CLI
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
repo: [rails/rails, discourse/discourse, spree/spree, ruby/ruby]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- uses: ./.github/actions/fetch-codeql
|
||||
|
||||
- uses: ./ruby/actions/create-extractor-pack
|
||||
|
||||
- name: Checkout ${{ matrix.repo }}
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: ${{ matrix.repo }}
|
||||
path: ${{ github.workspace }}/repo
|
||||
- name: Create database
|
||||
run: |
|
||||
codeql database create \
|
||||
--search-path "${{ github.workspace }}" \
|
||||
--threads 4 \
|
||||
--language ruby --source-root "${{ github.workspace }}/repo" \
|
||||
"${{ runner.temp }}/database"
|
||||
- name: Measure database
|
||||
run: |
|
||||
mkdir -p "stats/${{ matrix.repo }}"
|
||||
codeql dataset measure --threads 4 --output "stats/${{ matrix.repo }}/stats.xml" "${{ runner.temp }}/database/db-ruby"
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: measurements-${{ hashFiles('stats/**') }}
|
||||
path: stats
|
||||
retention-days: 1
|
||||
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
needs: measure
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: stats
|
||||
- run: |
|
||||
python -m pip install --user lxml
|
||||
find stats -name 'stats.xml' | sort | xargs python ruby/scripts/merge_stats.py --output ruby/ql/lib/ruby.dbscheme.stats --normalise ruby_tokeninfo
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ruby.dbscheme.stats
|
||||
path: ruby/ql/lib/ruby.dbscheme.stats
|
||||
40
.github/workflows/ruby-qltest-rtjo.yml
vendored
40
.github/workflows/ruby-qltest-rtjo.yml
vendored
@@ -1,40 +0,0 @@
|
||||
name: "Ruby: Run RTJO Language Tests"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
- labeled
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ruby
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
qltest-rtjo:
|
||||
if: "github.repository_owner == 'github' && github.event.label.name == 'Run: RTJO Language Tests'"
|
||||
runs-on: ubuntu-latest-xl
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/actions/fetch-codeql
|
||||
- uses: ./ruby/actions/create-extractor-pack
|
||||
- name: Cache compilation cache
|
||||
id: query-cache
|
||||
uses: ./.github/actions/cache-query-compilation
|
||||
with:
|
||||
key: ruby-qltest
|
||||
- name: Run QL tests
|
||||
run: |
|
||||
codeql test run --dynamic-join-order-mode=all --threads=0 --ram 50000 --search-path "${{ github.workspace }}" --check-databases --check-diff-informed --check-undefined-labels --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition --consistency-queries ql/consistency-queries ql/test --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
73
.github/workflows/ruby-qltest.yml
vendored
73
.github/workflows/ruby-qltest.yml
vendored
@@ -1,73 +0,0 @@
|
||||
name: "Ruby: Run QL Tests"
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "ruby/**"
|
||||
- "shared/**"
|
||||
- .github/workflows/ruby-build.yml
|
||||
- .github/actions/fetch-codeql/action.yml
|
||||
- codeql-workspace.yml
|
||||
branches:
|
||||
- main
|
||||
- "rc/*"
|
||||
pull_request:
|
||||
paths:
|
||||
- "ruby/**"
|
||||
- "shared/**"
|
||||
- .github/workflows/ruby-qltest.yml
|
||||
- .github/actions/fetch-codeql/action.yml
|
||||
- codeql-workspace.yml
|
||||
branches:
|
||||
- main
|
||||
- "rc/*"
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ruby
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
qlupgrade:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- 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:
|
||||
if: github.repository_owner == 'github'
|
||||
runs-on: ubuntu-latest-xl
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/actions/fetch-codeql
|
||||
- uses: ./ruby/actions/create-extractor-pack
|
||||
- name: Cache compilation cache
|
||||
id: query-cache
|
||||
uses: ./.github/actions/cache-query-compilation
|
||||
with:
|
||||
key: ruby-qltest
|
||||
- name: Run QL tests
|
||||
run: |
|
||||
codeql test run --threads=0 --ram 50000 --search-path "${{ github.workspace }}" --check-databases --check-diff-informed --check-undefined-labels --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition --consistency-queries ql/consistency-queries ql/test --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
16
MODULE.bazel
16
MODULE.bazel
@@ -15,22 +15,22 @@ local_path_override(
|
||||
# see https://registry.bazel.build/ for a list of available packages
|
||||
|
||||
bazel_dep(name = "platforms", version = "1.0.0")
|
||||
bazel_dep(name = "rules_cc", version = "0.2.16")
|
||||
bazel_dep(name = "rules_go", version = "0.59.0")
|
||||
bazel_dep(name = "rules_java", version = "9.0.3")
|
||||
bazel_dep(name = "rules_pkg", version = "1.0.1")
|
||||
bazel_dep(name = "rules_cc", version = "0.2.17")
|
||||
bazel_dep(name = "rules_go", version = "0.60.0")
|
||||
bazel_dep(name = "rules_java", version = "9.6.1")
|
||||
bazel_dep(name = "rules_pkg", version = "1.2.0")
|
||||
bazel_dep(name = "rules_nodejs", version = "6.7.3")
|
||||
bazel_dep(name = "rules_python", version = "1.9.0")
|
||||
bazel_dep(name = "rules_shell", version = "0.5.0")
|
||||
bazel_dep(name = "bazel_skylib", version = "1.8.1")
|
||||
bazel_dep(name = "rules_shell", version = "0.6.1")
|
||||
bazel_dep(name = "bazel_skylib", version = "1.9.0")
|
||||
bazel_dep(name = "abseil-cpp", version = "20260107.1", repo_name = "absl")
|
||||
bazel_dep(name = "nlohmann_json", version = "3.11.3", repo_name = "json")
|
||||
bazel_dep(name = "fmt", version = "12.1.0-codeql.1")
|
||||
bazel_dep(name = "rules_kotlin", version = "2.2.2-codeql.1")
|
||||
bazel_dep(name = "gazelle", version = "0.47.0")
|
||||
bazel_dep(name = "rules_dotnet", version = "0.21.5-codeql.1")
|
||||
bazel_dep(name = "googletest", version = "1.14.0.bcr.1")
|
||||
bazel_dep(name = "rules_rust", version = "0.68.1.codeql.1")
|
||||
bazel_dep(name = "googletest", version = "1.17.0.bcr.2")
|
||||
bazel_dep(name = "rules_rust", version = "0.69.0")
|
||||
bazel_dep(name = "zstd", version = "1.5.7.bcr.1")
|
||||
|
||||
bazel_dep(name = "buildifier_prebuilt", version = "6.4.0", dev_dependency = True)
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
## 0.4.30
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.4.29
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
3
actions/ql/lib/change-notes/released/0.4.30.md
Normal file
3
actions/ql/lib/change-notes/released/0.4.30.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.4.30
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.4.29
|
||||
lastReleaseVersion: 0.4.30
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/actions-all
|
||||
version: 0.4.30-dev
|
||||
version: 0.4.31-dev
|
||||
library: true
|
||||
warnOnImplicitThis: true
|
||||
dependencies:
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
## 0.6.22
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.6.21
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
3
actions/ql/src/change-notes/released/0.6.22.md
Normal file
3
actions/ql/src/change-notes/released/0.6.22.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.6.22
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.6.21
|
||||
lastReleaseVersion: 0.6.22
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/actions-queries
|
||||
version: 0.6.22-dev
|
||||
version: 0.6.23-dev
|
||||
library: false
|
||||
warnOnImplicitThis: true
|
||||
groups: [actions, queries]
|
||||
|
||||
@@ -199,6 +199,7 @@ def annotate_as_appropriate(filename, lines):
|
||||
# as overlay[local?]. It is not clear that these heuristics are exactly what we want,
|
||||
# but they seem to work well enough for now (as determined by speed and accuracy numbers).
|
||||
if (filename.endswith("Test.qll") or
|
||||
re.search(r"go/ql/lib/semmle/go/security/[^/]+[.]qll$", filename.replace(os.sep, "/")) or
|
||||
((filename.endswith("Query.qll") or filename.endswith("Config.qll")) and
|
||||
any("implements DataFlow::ConfigSig" in line for line in lines))):
|
||||
return None
|
||||
|
||||
@@ -172,10 +172,6 @@
|
||||
"cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/reachability/PrintDominance.qll",
|
||||
"cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/reachability/PrintDominance.qll"
|
||||
],
|
||||
"C# ControlFlowReachability": [
|
||||
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/ControlFlowReachability.qll",
|
||||
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/rangeanalysis/ControlFlowReachability.qll"
|
||||
],
|
||||
"C++ ExternalAPIs": [
|
||||
"cpp/ql/src/Security/CWE/CWE-020/ExternalAPIs.qll",
|
||||
"cpp/ql/src/Security/CWE/CWE-020/ir/ExternalAPIs.qll"
|
||||
|
||||
@@ -52,5 +52,6 @@ ql/cpp/ql/src/Summary/LinesOfUserCode.ql
|
||||
ql/cpp/ql/src/Telemetry/CompilerErrors.ql
|
||||
ql/cpp/ql/src/Telemetry/DatabaseQuality.ql
|
||||
ql/cpp/ql/src/Telemetry/ExtractionMetrics.ql
|
||||
ql/cpp/ql/src/Telemetry/ExtractorInformation.ql
|
||||
ql/cpp/ql/src/Telemetry/MissingIncludes.ql
|
||||
ql/cpp/ql/src/Telemetry/SucceededIncludes.ql
|
||||
|
||||
@@ -160,6 +160,7 @@ ql/cpp/ql/src/Summary/LinesOfUserCode.ql
|
||||
ql/cpp/ql/src/Telemetry/CompilerErrors.ql
|
||||
ql/cpp/ql/src/Telemetry/DatabaseQuality.ql
|
||||
ql/cpp/ql/src/Telemetry/ExtractionMetrics.ql
|
||||
ql/cpp/ql/src/Telemetry/ExtractorInformation.ql
|
||||
ql/cpp/ql/src/Telemetry/MissingIncludes.ql
|
||||
ql/cpp/ql/src/Telemetry/SucceededIncludes.ql
|
||||
ql/cpp/ql/src/jsf/4.06 Pre-Processing Directives/AV Rule 32.ql
|
||||
|
||||
@@ -93,5 +93,6 @@ ql/cpp/ql/src/Summary/LinesOfUserCode.ql
|
||||
ql/cpp/ql/src/Telemetry/CompilerErrors.ql
|
||||
ql/cpp/ql/src/Telemetry/DatabaseQuality.ql
|
||||
ql/cpp/ql/src/Telemetry/ExtractionMetrics.ql
|
||||
ql/cpp/ql/src/Telemetry/ExtractorInformation.ql
|
||||
ql/cpp/ql/src/Telemetry/MissingIncludes.ql
|
||||
ql/cpp/ql/src/Telemetry/SucceededIncludes.ql
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
## 8.0.1
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Inline expectations test comments, which are of the form `// $ tag` or `// $ tag=value`, are now parsed more strictly and will not be recognized if there isn't a space after the `$` symbol.
|
||||
|
||||
## 8.0.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: feature
|
||||
---
|
||||
* Added a class `IndirectUninitializedNode` to represent the indirection of an uninitialized local variable as a dataflow node.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: feature
|
||||
---
|
||||
* Added a class `DataFlow::IndirectParameterNode` to represent the indirection of a parameter as a dataflow node.
|
||||
* Added a predicate `Node::asIndirectInstruction` which returns the `Instruction` that defines the indirect dataflow node, if any.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
## 8.0.1
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Inline expectations test comments, which are of the form `// $ tag` or `// $ tag=value`, are now parsed more strictly and will not be recognized if there isn't a space after the `$` symbol.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 8.0.0
|
||||
lastReleaseVersion: 8.0.1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/cpp-all
|
||||
version: 8.0.1-dev
|
||||
version: 8.0.2-dev
|
||||
groups: cpp
|
||||
dbscheme: semmlecode.cpp.dbscheme
|
||||
extractor: cpp
|
||||
|
||||
@@ -524,6 +524,12 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
|
||||
not exists(NewOrNewArrayExpr new | e = new.getAllocatorCall().getArgument(0))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this function has an ambiguous return type, meaning that zero or multiple return
|
||||
* types for this function are present in the database (this can occur in `build-mode: none`).
|
||||
*/
|
||||
predicate hasAmbiguousReturnType() { count(this.getType()) != 1 }
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
|
||||
@@ -1663,7 +1663,7 @@ private module Cached {
|
||||
private predicate compares_ge(
|
||||
ValueNumber test, Operand left, Operand right, int k, boolean isGe, GuardValue value
|
||||
) {
|
||||
exists(int onemk | k = 1 - onemk | compares_lt(test, right, left, onemk, isGe, value))
|
||||
compares_lt(test, right, left, 1 - k, isGe, value)
|
||||
}
|
||||
|
||||
/** Rearrange various simple comparisons into `left < right + k` form. */
|
||||
|
||||
@@ -353,12 +353,26 @@ module CsvValidation {
|
||||
)
|
||||
}
|
||||
|
||||
private string getIncorrectConstructorSummaryOutput() {
|
||||
exists(string namespace, string type, string name, string output |
|
||||
type = name or
|
||||
type = name + "<" + any(string s)
|
||||
|
|
||||
summaryModel(namespace, type, _, name, _, _, _, output, _, _, _) and
|
||||
output.matches("ReturnValue%") and
|
||||
result =
|
||||
"Constructor model for " + namespace + "." + type +
|
||||
" should use `Argument[this]` in the output, not `ReturnValue`."
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if some row in a CSV-based flow model appears to contain typos. */
|
||||
query predicate invalidModelRow(string msg) {
|
||||
msg =
|
||||
[
|
||||
getInvalidModelSignature(), getInvalidModelInput(), getInvalidModelOutput(),
|
||||
getInvalidModelSubtype(), getInvalidModelColumnCount(), KindVal::getInvalidModelKind()
|
||||
getInvalidModelSubtype(), getInvalidModelColumnCount(), KindVal::getInvalidModelKind(),
|
||||
getIncorrectConstructorSummaryOutput()
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,117 +6,67 @@ private import OverlayXml
|
||||
|
||||
/**
|
||||
* Holds always for the overlay variant and never for the base variant.
|
||||
* This local predicate is used to define local predicates that behave
|
||||
* differently for the base and overlay variant.
|
||||
*/
|
||||
overlay[local]
|
||||
predicate isOverlay() { databaseMetadata("isOverlay", "true") }
|
||||
|
||||
overlay[local]
|
||||
private string getLocationFilePath(@location_default loc) {
|
||||
exists(@file file | locations_default(loc, file, _, _, _, _) | files(file, result))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file path for an element with a single location.
|
||||
* Holds if the TRAP file or tag `t` is reachable from source file `sourceFile`
|
||||
* in the base (isOverlayVariant=false) or overlay (isOverlayVariant=true) variant.
|
||||
*/
|
||||
overlay[local]
|
||||
private string getSingleLocationFilePath(@element e) {
|
||||
exists(@location_default loc |
|
||||
var_decls(e, _, _, _, loc)
|
||||
or
|
||||
fun_decls(e, _, _, _, loc)
|
||||
or
|
||||
type_decls(e, _, loc)
|
||||
or
|
||||
namespace_decls(e, _, loc, _)
|
||||
or
|
||||
macroinvocations(e, _, loc, _)
|
||||
or
|
||||
preprocdirects(e, _, loc)
|
||||
or
|
||||
diagnostics(e, _, _, _, _, loc)
|
||||
or
|
||||
usings(e, _, loc, _)
|
||||
or
|
||||
static_asserts(e, _, _, loc, _)
|
||||
or
|
||||
derivations(e, _, _, _, loc)
|
||||
or
|
||||
frienddecls(e, _, _, loc)
|
||||
or
|
||||
comments(e, _, loc)
|
||||
or
|
||||
exprs(e, _, loc)
|
||||
or
|
||||
stmts(e, _, loc)
|
||||
or
|
||||
initialisers(e, _, _, loc)
|
||||
or
|
||||
attributes(e, _, _, _, loc)
|
||||
or
|
||||
attribute_args(e, _, _, _, loc)
|
||||
or
|
||||
namequalifiers(e, _, _, loc)
|
||||
or
|
||||
enumconstants(e, _, _, _, _, loc)
|
||||
or
|
||||
type_mentions(e, _, loc, _)
|
||||
or
|
||||
lambda_capture(e, _, _, _, _, _, loc)
|
||||
or
|
||||
concept_templates(e, _, loc)
|
||||
|
|
||||
result = getLocationFilePath(loc)
|
||||
private predicate locallyReachableTrapOrTag(
|
||||
boolean isOverlayVariant, string sourceFile, @trap_or_tag t
|
||||
) {
|
||||
exists(@source_file sf, @trap trap |
|
||||
(if isOverlay() then isOverlayVariant = true else isOverlayVariant = false) and
|
||||
source_file_uses_trap(sf, trap) and
|
||||
source_file_name(sf, sourceFile) and
|
||||
(t = trap or trap_uses_tag(trap, t))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file path for an element with potentially multiple locations.
|
||||
* Holds if element `e` is in TRAP file or tag `t`
|
||||
* in the base (isOverlayVariant=false) or overlay (isOverlayVariant=true) variant.
|
||||
*/
|
||||
overlay[local]
|
||||
private string getMultiLocationFilePath(@element e) {
|
||||
exists(@location_default loc |
|
||||
var_decls(_, e, _, _, loc)
|
||||
or
|
||||
fun_decls(_, e, _, _, loc)
|
||||
or
|
||||
type_decls(_, e, loc)
|
||||
or
|
||||
namespace_decls(_, e, loc, _)
|
||||
|
|
||||
result = getLocationFilePath(loc)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A local helper predicate that holds in the base variant and never in the
|
||||
* overlay variant.
|
||||
*/
|
||||
overlay[local]
|
||||
private predicate isBase() { not isOverlay() }
|
||||
|
||||
/**
|
||||
* Holds if `path` was extracted in the overlay database.
|
||||
*/
|
||||
overlay[local]
|
||||
private predicate overlayHasFile(string path) {
|
||||
isOverlay() and
|
||||
files(_, path) and
|
||||
path != ""
|
||||
private predicate locallyInTrapOrTag(boolean isOverlayVariant, @element e, @trap_or_tag t) {
|
||||
(if isOverlay() then isOverlayVariant = true else isOverlayVariant = false) and
|
||||
in_trap_or_tag(e, t)
|
||||
}
|
||||
|
||||
/**
|
||||
* Discards an element from the base variant if:
|
||||
* - It has a single location in a file extracted in the overlay, or
|
||||
* - All of its locations are in files extracted in the overlay.
|
||||
* - We have knowledge about what TRAP file or tag it is in (in the base).
|
||||
* - It is not in any overlay TRAP file or tag that is reachable from an overlay source file.
|
||||
* - For every base TRAP file or tag that contains it and is reachable from a base source file,
|
||||
* either the source file has changed, or the overlay has redefined the TRAP file or tag,
|
||||
* or the overlay runner has re-extracted the same source file.
|
||||
*/
|
||||
overlay[discard_entity]
|
||||
private predicate discardElement(@element e) {
|
||||
isBase() and
|
||||
(
|
||||
overlayHasFile(getSingleLocationFilePath(e))
|
||||
or
|
||||
forex(string path | path = getMultiLocationFilePath(e) | overlayHasFile(path))
|
||||
// If we don't have any knowledge about what TRAP file something
|
||||
// is in, then we don't want to discard it, so we only consider
|
||||
// entities that are known to be in a base TRAP file or tag.
|
||||
locallyInTrapOrTag(false, e, _) and
|
||||
// Anything that is reachable from an overlay source file should
|
||||
// not be discarded.
|
||||
not exists(@trap_or_tag t | locallyInTrapOrTag(true, e, t) |
|
||||
locallyReachableTrapOrTag(true, _, t)
|
||||
) and
|
||||
// Finally, we have to make sure the base variant does not retain it.
|
||||
// If it is reachable from a base source file, then that is
|
||||
// sufficient unless either the base source file has changed (in
|
||||
// particular, been deleted), or the overlay has redefined the TRAP
|
||||
// file or tag it is in, or the overlay runner has re-extracted the same
|
||||
// source file (e.g. because a header it includes has changed).
|
||||
forall(@trap_or_tag t, string sourceFile |
|
||||
locallyInTrapOrTag(false, e, t) and
|
||||
locallyReachableTrapOrTag(false, sourceFile, t)
|
||||
|
|
||||
overlayChangedFiles(sourceFile) or
|
||||
locallyReachableTrapOrTag(true, _, t) or
|
||||
locallyReachableTrapOrTag(true, sourceFile, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -321,6 +321,12 @@ module Public {
|
||||
*/
|
||||
Operand asIndirectOperand(int index) { hasOperandAndIndex(this, result, index) }
|
||||
|
||||
/**
|
||||
* Gets the instruction that is indirectly tracked by this node behind
|
||||
* `index` number of indirections.
|
||||
*/
|
||||
Instruction asIndirectInstruction(int index) { hasInstructionAndIndex(this, result, index) }
|
||||
|
||||
/**
|
||||
* Holds if this node is at index `i` in basic block `block`.
|
||||
*
|
||||
@@ -617,6 +623,25 @@ module Public {
|
||||
*/
|
||||
LocalVariable asUninitialized() { result = this.(UninitializedNode).getLocalVariable() }
|
||||
|
||||
/**
|
||||
* Gets the uninitialized local variable corresponding to this node behind
|
||||
* `index` number of indirections, if any.
|
||||
*/
|
||||
LocalVariable asIndirectUninitialized(int index) {
|
||||
exists(IndirectUninitializedNode indirectUninitializedNode |
|
||||
this = indirectUninitializedNode and
|
||||
indirectUninitializedNode.getIndirectionIndex() = index
|
||||
|
|
||||
result = indirectUninitializedNode.getLocalVariable()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the uninitialized local variable corresponding to this node behind
|
||||
* a number indirections, if any.
|
||||
*/
|
||||
LocalVariable asIndirectUninitialized() { result = this.asIndirectUninitialized(_) }
|
||||
|
||||
/**
|
||||
* Gets the positional parameter corresponding to the node that represents
|
||||
* the value of the parameter after `index` number of loads, if any. For
|
||||
@@ -761,16 +786,13 @@ module Public {
|
||||
final override Type getType() { result = this.getPreUpdateNode().getType() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of an uninitialized local variable, viewed as a node in a data
|
||||
* flow graph.
|
||||
*/
|
||||
class UninitializedNode extends Node {
|
||||
abstract private class AbstractUninitializedNode extends Node {
|
||||
LocalVariable v;
|
||||
int indirectionIndex;
|
||||
|
||||
UninitializedNode() {
|
||||
AbstractUninitializedNode() {
|
||||
exists(SsaImpl::Definition def, SsaImpl::SourceVariable sv |
|
||||
def.getIndirectionIndex() = 0 and
|
||||
def.getIndirectionIndex() = indirectionIndex and
|
||||
def.getValue().asInstruction() instanceof UninitializedInstruction and
|
||||
SsaImpl::defToNode(this, def, sv) and
|
||||
v = sv.getBaseVariable().(SsaImpl::BaseIRVariable).getIRVariable().getAst()
|
||||
@@ -781,6 +803,25 @@ module Public {
|
||||
LocalVariable getLocalVariable() { result = v }
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of an uninitialized local variable, viewed as a node in a data
|
||||
* flow graph.
|
||||
*/
|
||||
class UninitializedNode extends AbstractUninitializedNode {
|
||||
UninitializedNode() { indirectionIndex = 0 }
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of an uninitialized local variable behind one or more levels of
|
||||
* indirection, viewed as a node in a data flow graph.
|
||||
*/
|
||||
class IndirectUninitializedNode extends AbstractUninitializedNode {
|
||||
IndirectUninitializedNode() { indirectionIndex > 0 }
|
||||
|
||||
/** Gets the indirection index of this node. */
|
||||
int getIndirectionIndex() { result = indirectionIndex }
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of a parameter at function entry, viewed as a node in a data
|
||||
* flow graph. This includes both explicit parameters such as `x` in `f(x)`
|
||||
@@ -795,6 +836,12 @@ module Public {
|
||||
/** An explicit positional parameter, including `this`, but not `...`. */
|
||||
final class DirectParameterNode = AbstractDirectParameterNode;
|
||||
|
||||
/**
|
||||
* A node representing an indirection of a positional parameter,
|
||||
* including `*this`, but not `*...`.
|
||||
*/
|
||||
final class IndirectParameterNode = AbstractIndirectParameterNode;
|
||||
|
||||
final class ExplicitParameterNode = AbstractExplicitParameterNode;
|
||||
|
||||
/** An implicit `this` parameter. */
|
||||
@@ -954,11 +1001,6 @@ module Public {
|
||||
|
||||
private import Public
|
||||
|
||||
/**
|
||||
* A node representing an indirection of a parameter.
|
||||
*/
|
||||
final class IndirectParameterNode = AbstractIndirectParameterNode;
|
||||
|
||||
/**
|
||||
* A class that lifts pre-SSA dataflow nodes to regular dataflow nodes.
|
||||
*/
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
## 1.5.13
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.5.12
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -218,7 +218,9 @@ where
|
||||
// only report if we cannot prove that the result of the
|
||||
// multiplication will be less (resp. greater) than the
|
||||
// maximum (resp. minimum) number we can compute.
|
||||
overflows(me, t1)
|
||||
overflows(me, t1) and
|
||||
// exclude cases where the expression type may not have been extracted accurately
|
||||
not me.getParent().(Call).getTarget().hasAmbiguousReturnType()
|
||||
select me,
|
||||
"Multiplication result may overflow '" + me.getType().toString() + "' before it is converted to '"
|
||||
+ me.getFullyConverted().getType().toString() + "'."
|
||||
|
||||
@@ -168,9 +168,11 @@ where
|
||||
formatOtherArgType(ffc, n, expected, arg, actual) and
|
||||
not actual.getUnspecifiedType().(IntegralType).getSize() = sizeof_IntType()
|
||||
) and
|
||||
// Exclude some cases where we're less confident the result is correct / clear / valuable
|
||||
not arg.isAffectedByMacro() and
|
||||
not arg.isFromUninstantiatedTemplate(_) and
|
||||
not actual.stripType() instanceof ErroneousType and
|
||||
not arg.getType().stripType().(RoutineType).getReturnType() instanceof ErroneousType and
|
||||
not arg.(Call).mayBeFromImplicitlyDeclaredFunction() and
|
||||
// Make sure that the format function definition is consistent
|
||||
count(ffc.getTarget().getFormatParameterIndex()) = 1
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* allows for a cross-site scripting vulnerability.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 6.1
|
||||
* @security-severity 7.8
|
||||
* @precision high
|
||||
* @id cpp/cgi-xss
|
||||
* @tags security
|
||||
|
||||
@@ -23,13 +23,31 @@ import Flow::PathGraph
|
||||
|
||||
predicate isSource(FlowSource source, string sourceType) { sourceType = source.getSourceType() }
|
||||
|
||||
/**
|
||||
* Holds if `f` is a printf-like function or a (possibly nested) wrapper
|
||||
* that forwards a format-string parameter to one.
|
||||
*
|
||||
* Functions that *implement* printf-like behavior (e.g. a custom
|
||||
* `vsnprintf` variant) internally parse the caller-supplied format string
|
||||
* and build small, bounded, local format strings such as `"%d"` or `"%ld"`
|
||||
* for inner `sprintf` calls. Taint that reaches those inner calls via the
|
||||
* parsed format specifier is not exploitable, so sinks inside such
|
||||
* functions should be excluded.
|
||||
*/
|
||||
private predicate isPrintfImplementation(Function f) {
|
||||
f instanceof PrintfLikeFunction
|
||||
or
|
||||
exists(PrintfLikeFunction printf | printf.wrapperFunction(f, _, _))
|
||||
}
|
||||
|
||||
module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node node) { isSource(node, _) }
|
||||
|
||||
predicate isSink(DataFlow::Node node) {
|
||||
exists(PrintfLikeFunction printf |
|
||||
printf.outermostWrapperFunctionCall([node.asExpr(), node.asIndirectExpr()], _)
|
||||
)
|
||||
) and
|
||||
not isPrintfImplementation([node.asExpr(), node.asIndirectExpr()].getEnclosingFunction())
|
||||
}
|
||||
|
||||
private predicate isArithmeticNonCharType(ArithmeticType type) {
|
||||
|
||||
@@ -18,7 +18,8 @@ import IncorrectPointerScalingCommon
|
||||
private predicate isCharSzPtrExpr(Expr e) {
|
||||
exists(PointerType pt | pt = e.getFullyConverted().getUnspecifiedType() |
|
||||
pt.getBaseType() instanceof CharType or
|
||||
pt.getBaseType() instanceof VoidType
|
||||
pt.getBaseType() instanceof VoidType or
|
||||
pt.getBaseType() instanceof ErroneousType // this could be char / void type in a successful compilation
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
25
cpp/ql/src/Telemetry/DatabaseQuality.qll
Normal file
25
cpp/ql/src/Telemetry/DatabaseQuality.qll
Normal file
@@ -0,0 +1,25 @@
|
||||
import cpp
|
||||
import codeql.util.ReportStats
|
||||
|
||||
module CallTargetStats implements StatsSig {
|
||||
private class RelevantCall extends Call {
|
||||
RelevantCall() { this.getFile() = any(File f | f.fromSource() and exists(f.getRelativePath())) }
|
||||
}
|
||||
|
||||
// We assume that calls with an implicit target are calls that could not be
|
||||
// resolved. This is accurate in the vast majority of cases, but is inaccurate
|
||||
// for calls that deliberately rely on implicitly declared functions.
|
||||
private predicate hasImplicitTarget(RelevantCall call) {
|
||||
call.getTarget().getADeclarationEntry().isImplicit()
|
||||
}
|
||||
|
||||
int getNumberOfOk() { result = count(RelevantCall call | not hasImplicitTarget(call)) }
|
||||
|
||||
int getNumberOfNotOk() { result = count(RelevantCall call | hasImplicitTarget(call)) }
|
||||
|
||||
string getOkText() { result = "calls with call target" }
|
||||
|
||||
string getNotOkText() { result = "calls with missing call target" }
|
||||
}
|
||||
|
||||
module CallTargetStatsReport = ReportStats<CallTargetStats>;
|
||||
25
cpp/ql/src/Telemetry/ExtractorInformation.ql
Normal file
25
cpp/ql/src/Telemetry/ExtractorInformation.ql
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @name C/C++ extraction information
|
||||
* @description Information about the extraction for a C/C++ database
|
||||
* @kind metric
|
||||
* @tags summary telemetry
|
||||
* @id cpp/telemetry/extraction-information
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import DatabaseQuality
|
||||
|
||||
from string key, float value
|
||||
where
|
||||
(
|
||||
CallTargetStatsReport::numberOfOk(key, value) or
|
||||
CallTargetStatsReport::numberOfNotOk(key, value) or
|
||||
CallTargetStatsReport::percentageOfOk(key, value)
|
||||
) and
|
||||
/* Infinity */
|
||||
value != 1.0 / 0.0 and
|
||||
/* -Infinity */
|
||||
value != -1.0 / 0.0 and
|
||||
/* NaN */
|
||||
value != 0.0 / 0.0
|
||||
select key, value
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Fixed an issue with the "Multiplication result converted to larger type" (`cpp/integer-multiplication-cast-to-long`) query causing false positive results in `build-mode: none` databases.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: queryMetadata
|
||||
---
|
||||
* The `@security-severity` metadata of `cpp/cgi-xss` has been increased from 6.1 (medium) to 7.8 (high).
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Fixed an issue with the "Wrong type of arguments to formatting function" (`cpp/wrong-type-format-argument`) query causing false positive results in `build-mode: none` databases.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Fixed an issue with the "Suspicious add with sizeof" (`cpp/suspicious-add-sizeof`) query causing false positive results in `build-mode: none` databases.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Fixed an issue with the "Uncontrolled format string" (`cpp/tainted-format-string`) query involving certain kinds of formatting function implementations.
|
||||
3
cpp/ql/src/change-notes/released/1.5.13.md
Normal file
3
cpp/ql/src/change-notes/released/1.5.13.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 1.5.13
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 1.5.12
|
||||
lastReleaseVersion: 1.5.13
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/cpp-queries
|
||||
version: 1.5.13-dev
|
||||
version: 1.5.14-dev
|
||||
groups:
|
||||
- cpp
|
||||
- queries
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// semmle-extractor-options: --expect_errors
|
||||
|
||||
void test_float_double1(float f, double d) {
|
||||
float r1 = f * f; // GOOD
|
||||
float r2 = f * d; // GOOD
|
||||
double r3 = f * f; // BAD
|
||||
double r4 = f * d; // GOOD
|
||||
|
||||
float f1 = fabsf(f * f); // GOOD
|
||||
float f2 = fabsf(f * d); // GOOD
|
||||
double f3 = fabs(f * f); // BAD [NOT DETECTED]
|
||||
double f4 = fabs(f * d); // GOOD
|
||||
}
|
||||
|
||||
double fabs(double f);
|
||||
float fabsf(float f);
|
||||
|
||||
void test_float_double2(float f, double d) {
|
||||
float r1 = f * f; // GOOD
|
||||
float r2 = f * d; // GOOD
|
||||
double r3 = f * f; // BAD
|
||||
double r4 = f * d; // GOOD
|
||||
|
||||
float f1 = fabsf(f * f); // GOOD
|
||||
float f2 = fabsf(f * d); // GOOD
|
||||
double f3 = fabs(f * f); // BAD [NOT DETECTED]
|
||||
double f4 = fabs(f * d); // GOOD
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
| Buildless.c:6:17:6:21 | ... * ... | Multiplication result may overflow 'float' before it is converted to 'double'. |
|
||||
| Buildless.c:21:17:21:21 | ... * ... | Multiplication result may overflow 'float' before it is converted to 'double'. |
|
||||
| IntMultToLong.c:4:10:4:14 | ... * ... | Multiplication result may overflow 'int' before it is converted to 'long long'. |
|
||||
| IntMultToLong.c:7:16:7:20 | ... * ... | Multiplication result may overflow 'int' before it is converted to 'long long'. |
|
||||
| IntMultToLong.c:18:19:18:23 | ... * ... | Multiplication result may overflow 'float' before it is converted to 'double'. |
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
| second.cpp:26:18:26:39 | ... - ... | This format specifier for type 'int' does not match the argument type 'long'. |
|
||||
| second.cpp:29:18:29:39 | ... - ... | This format specifier for type 'unsigned int' does not match the argument type 'long'. |
|
||||
| tests.c:7:18:7:18 | 1 | This format specifier for type 'char *' does not match the argument type 'int'. |
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
// defines type size_t plausibly
|
||||
typedef unsigned long size_t;
|
||||
@@ -0,0 +1,32 @@
|
||||
// semmle-extractor-options: --expect_errors
|
||||
|
||||
int printf(const char * format, ...);
|
||||
|
||||
// defines type `myFunctionPointerType`, referencing `size_t`
|
||||
typedef size_t (*myFunctionPointerType) ();
|
||||
|
||||
void test_size_t() {
|
||||
size_t s = 0;
|
||||
|
||||
printf("%zd", s); // GOOD
|
||||
printf("%zi", s); // GOOD
|
||||
printf("%zu", s); // GOOD (we generally permit signedness changes)
|
||||
printf("%zx", s); // GOOD (we generally permit signedness changes)
|
||||
printf("%d", s); // BAD [NOT DETECTED]
|
||||
printf("%ld", s); // DUBIOUS [NOT DETECTED]
|
||||
printf("%lld", s); // DUBIOUS [NOT DETECTED]
|
||||
printf("%u", s); // BAD [NOT DETECTED]
|
||||
|
||||
char buffer[1024];
|
||||
|
||||
printf("%zd", &buffer[1023] - buffer); // GOOD
|
||||
printf("%zi", &buffer[1023] - buffer); // GOOD
|
||||
printf("%zu", &buffer[1023] - buffer); // GOOD
|
||||
printf("%zx", &buffer[1023] - buffer); // GOOD
|
||||
printf("%d", &buffer[1023] - buffer); // BAD
|
||||
printf("%ld", &buffer[1023] - buffer); // DUBIOUS [NOT DETECTED]
|
||||
printf("%lld", &buffer[1023] - buffer); // DUBIOUS [NOT DETECTED]
|
||||
printf("%u", &buffer[1023] - buffer); // BAD
|
||||
// (for the `%ld` and `%lld` cases, the signedness and type sizes match, `%zd` would be most correct
|
||||
// and robust but the developer may know enough to make this safe)
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
| buildless.cpp:5:15:5:25 | sizeof(int) | Suspicious sizeof offset in a pointer arithmetic expression. The type of the pointer is $@. | file://:0:0:0:0 | const short * | const short * |
|
||||
| buildless.cpp:6:13:6:23 | sizeof(int) | Suspicious sizeof offset in a pointer arithmetic expression. The type of the pointer is $@. | file://:0:0:0:0 | const int * | const int * |
|
||||
| test.cpp:6:30:6:40 | sizeof(int) | Suspicious sizeof offset in a pointer arithmetic expression. The type of the pointer is $@. | file://:0:0:0:0 | int * | int * |
|
||||
| test.cpp:14:30:14:40 | sizeof(int) | Suspicious sizeof offset in a pointer arithmetic expression. The type of the pointer is $@. | file://:0:0:0:0 | int * | int * |
|
||||
| test.cpp:22:25:22:35 | sizeof(int) | Suspicious sizeof offset in a pointer arithmetic expression. The type of the pointer is $@. | file://:0:0:0:0 | int * | int * |
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// semmle-extractor-options: --expect_errors
|
||||
|
||||
void test_buildless(const char *p_c, const short *p_short, const int *p_int, const uint8_t *p_8, const uint16_t *p_16, const uint32_t *p_32) {
|
||||
*(p_c + sizeof(int)); // GOOD (`sizeof(char)` is 1)
|
||||
*(p_short + sizeof(int)); // BAD
|
||||
*(p_int + sizeof(int)); // BAD
|
||||
*(p_8 + sizeof(int)); // GOOD (`sizeof(uint8_t)` is 1, but there's an error in the type)
|
||||
*(p_16 + sizeof(int)); // BAD [NOT DETECTED]
|
||||
*(p_32 + sizeof(int)); // BAD [NOT DETECTED]
|
||||
}
|
||||
@@ -93,3 +93,9 @@ private:
|
||||
myChar * const myCharsPointer;
|
||||
myInt * const myIntsPointer;
|
||||
};
|
||||
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef unsigned int uint32_t;
|
||||
|
||||
void test_buildless(const char *p_c, const short *p_short, const int *p_int, const uint8_t *p_8, const uint16_t *p_16, const uint32_t *p_32);
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
## 1.7.61
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.7.60
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
## 1.7.61
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 1.7.60
|
||||
lastReleaseVersion: 1.7.61
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/csharp-solorigate-all
|
||||
version: 1.7.61-dev
|
||||
version: 1.7.62-dev
|
||||
groups:
|
||||
- csharp
|
||||
- solorigate
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
## 1.7.61
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.7.60
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
## 1.7.61
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 1.7.60
|
||||
lastReleaseVersion: 1.7.61
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/csharp-solorigate-queries
|
||||
version: 1.7.61-dev
|
||||
version: 1.7.62-dev
|
||||
groups:
|
||||
- csharp
|
||||
- solorigate
|
||||
|
||||
@@ -1,63 +1,5 @@
|
||||
import csharp
|
||||
import semmle.code.csharp.controlflow.internal.Completion
|
||||
import semmle.code.csharp.controlflow.internal.PreBasicBlocks
|
||||
import ControlFlow
|
||||
import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl::Consistency
|
||||
import semmle.code.csharp.controlflow.internal.Splitting
|
||||
|
||||
private predicate splitBB(ControlFlow::BasicBlock bb) {
|
||||
exists(ControlFlow::Node first |
|
||||
first = bb.getFirstNode() and
|
||||
first.isJoin() and
|
||||
strictcount(first.getAPredecessor().getAstNode()) = 1
|
||||
)
|
||||
}
|
||||
|
||||
private class RelevantBasicBlock extends ControlFlow::BasicBlock {
|
||||
RelevantBasicBlock() { not splitBB(this) }
|
||||
}
|
||||
|
||||
predicate bbStartInconsistency(ControlFlowElement cfe) {
|
||||
exists(RelevantBasicBlock bb | bb.getFirstNode() = cfe.getAControlFlowNode()) and
|
||||
not cfe = any(PreBasicBlock bb).getFirstElement()
|
||||
}
|
||||
|
||||
predicate bbSuccInconsistency(ControlFlowElement pred, ControlFlowElement succ) {
|
||||
exists(RelevantBasicBlock predBB, RelevantBasicBlock succBB |
|
||||
predBB.getLastNode() = pred.getAControlFlowNode() and
|
||||
succBB = predBB.getASuccessor() and
|
||||
succBB.getFirstNode() = succ.getAControlFlowNode()
|
||||
) and
|
||||
not exists(PreBasicBlock predBB, PreBasicBlock succBB |
|
||||
predBB.getLastNode() = pred and
|
||||
succBB = predBB.getASuccessor() and
|
||||
succBB.getFirstElement() = succ
|
||||
)
|
||||
}
|
||||
|
||||
predicate bbIntraSuccInconsistency(ControlFlowElement pred, ControlFlowElement succ) {
|
||||
exists(ControlFlow::BasicBlock bb, int i |
|
||||
pred.getAControlFlowNode() = bb.getNode(i) and
|
||||
succ.getAControlFlowNode() = bb.getNode(i + 1)
|
||||
) and
|
||||
not exists(PreBasicBlock bb |
|
||||
bb.getLastNode() = pred and
|
||||
bb.getASuccessor().getFirstElement() = succ
|
||||
) and
|
||||
not exists(PreBasicBlock bb, int i |
|
||||
bb.getNode(i) = pred and
|
||||
bb.getNode(i + 1) = succ
|
||||
)
|
||||
}
|
||||
|
||||
query predicate preBasicBlockConsistency(ControlFlowElement cfe1, ControlFlowElement cfe2, string s) {
|
||||
bbStartInconsistency(cfe1) and
|
||||
cfe2 = cfe1 and
|
||||
s = "start inconsistency"
|
||||
or
|
||||
bbSuccInconsistency(cfe1, cfe2) and
|
||||
s = "succ inconsistency"
|
||||
or
|
||||
bbIntraSuccInconsistency(cfe1, cfe2) and
|
||||
s = "intra succ inconsistency"
|
||||
}
|
||||
|
||||
@@ -35,9 +35,7 @@ private module Input implements InputSig<Location, CsharpDataFlow> {
|
||||
or
|
||||
n.asExpr().(ObjectCreation).hasInitializer()
|
||||
or
|
||||
exists(
|
||||
n.(PostUpdateNode).getPreUpdateNode().asExprAtNode(LocalFlow::getPostUpdateReverseStep(_))
|
||||
)
|
||||
n.(PostUpdateNode).getPreUpdateNode().asExpr() = LocalFlow::getPostUpdateReverseStep(_)
|
||||
}
|
||||
|
||||
predicate argHasPostUpdateExclude(ArgumentNode n) {
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
## 5.4.9
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Inline expectations test comments, which are of the form `// $ tag` or `// $ tag=value`, are now parsed more strictly and will not be recognized if there isn't a space after the `$` symbol.
|
||||
* Added `System.Net.WebSockets::ReceiveAsync` as a remote flow source.
|
||||
* Added reverse taint flow from implicit conversion operator calls to their arguments.
|
||||
* Added post-update nodes for struct-type arguments, allowing data flow out of method calls via those arguments.
|
||||
* C# 14: Added support for partial constructors.
|
||||
|
||||
## 5.4.8
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* C# 14: Added support for partial constructors.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added post-update nodes for struct-type arguments, allowing data flow out of method calls via those arguments.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added reverse taint flow from implicit conversion operator calls to their arguments.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added `System.Net.WebSockets::ReceiveAsync` as a remote flow source.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The `cs/log-forging` query no longer treats arguments to extension methods with
|
||||
source code on `ILogger` types as sinks. Instead, taint is tracked interprocedurally
|
||||
through extension method bodies, reducing false positives when extension methods
|
||||
sanitize input internally.
|
||||
9
csharp/ql/lib/change-notes/released/5.4.9.md
Normal file
9
csharp/ql/lib/change-notes/released/5.4.9.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## 5.4.9
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Inline expectations test comments, which are of the form `// $ tag` or `// $ tag=value`, are now parsed more strictly and will not be recognized if there isn't a space after the `$` symbol.
|
||||
* Added `System.Net.WebSockets::ReceiveAsync` as a remote flow source.
|
||||
* Added reverse taint flow from implicit conversion operator calls to their arguments.
|
||||
* Added post-update nodes for struct-type arguments, allowing data flow out of method calls via those arguments.
|
||||
* C# 14: Added support for partial constructors.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 5.4.8
|
||||
lastReleaseVersion: 5.4.9
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/csharp-all
|
||||
version: 5.4.9-dev
|
||||
version: 5.4.10-dev
|
||||
groups: csharp
|
||||
dbscheme: semmlecode.csharp.dbscheme
|
||||
extractor: csharp
|
||||
|
||||
@@ -336,6 +336,22 @@ class ExtensionTypeExtensionMethod extends ExtensionMethodImpl {
|
||||
ExtensionTypeExtensionMethod() { this.isInExtension() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A non-static member with an initializer, for example a field `int Field = 0`.
|
||||
*/
|
||||
private class InitializedInstanceMember extends Member {
|
||||
private AssignExpr ae;
|
||||
|
||||
InitializedInstanceMember() {
|
||||
not this.isStatic() and
|
||||
expr_parent_top_level(ae, _, this) and
|
||||
not ae = getExpressionBody(_)
|
||||
}
|
||||
|
||||
/** Gets the initializer expression. */
|
||||
AssignExpr getInitializer() { result = ae }
|
||||
}
|
||||
|
||||
/**
|
||||
* An object initializer method.
|
||||
*
|
||||
@@ -347,6 +363,17 @@ class ExtensionTypeExtensionMethod extends ExtensionMethodImpl {
|
||||
*/
|
||||
class ObjectInitMethod extends Method {
|
||||
ObjectInitMethod() { this.getName() = "<object initializer>" }
|
||||
|
||||
/**
|
||||
* Holds if this object initializer method performs the initialization
|
||||
* of a member via assignment `init`.
|
||||
*/
|
||||
predicate initializes(AssignExpr init) {
|
||||
exists(InitializedInstanceMember m |
|
||||
this.getDeclaringType().getAMember() = m and
|
||||
init = m.getInitializer()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -214,6 +214,8 @@ private module Cached {
|
||||
parent*(enclosingStart(cfe), c.(Constructor).getInitializer())
|
||||
or
|
||||
parent*(cfe, c.(Constructor).getObjectInitializerCall())
|
||||
or
|
||||
parent*(cfe, any(AssignExpr init | c.(ObjectInitMethod).initializes(init)))
|
||||
}
|
||||
|
||||
/** Holds if the enclosing statement of expression `e` is `s`. */
|
||||
|
||||
@@ -183,9 +183,10 @@ class SwitchStmt extends SelectionStmt, Switch, @switch_stmt {
|
||||
* return 3;
|
||||
* }
|
||||
* ```
|
||||
* Note that this reorders the `default` case to always be at the end.
|
||||
*/
|
||||
override CaseStmt getCase(int i) { result = SwithStmtInternal::getCase(this, i) }
|
||||
override CaseStmt getCase(int i) {
|
||||
result = rank[i + 1](CaseStmt cs, int idx | cs = this.getChildStmt(idx) | cs order by idx)
|
||||
}
|
||||
|
||||
/** Gets a case of this `switch` statement. */
|
||||
override CaseStmt getACase() { result = this.getCase(_) }
|
||||
@@ -208,87 +209,29 @@ class SwitchStmt extends SelectionStmt, Switch, @switch_stmt {
|
||||
* ```csharp
|
||||
* switch (x) {
|
||||
* case "abc": // i = 0
|
||||
* return 0;
|
||||
* case int i when i > 0: // i = 1
|
||||
* return 1;
|
||||
* case string s: // i = 2
|
||||
* Console.WriteLine(s);
|
||||
* return 2; // i = 3
|
||||
* default: // i = 4
|
||||
* return 3; // i = 5
|
||||
* return 0; // i = 1
|
||||
* case int i when i > 0: // i = 2
|
||||
* return 1; // i = 3
|
||||
* case string s: // i = 4
|
||||
* Console.WriteLine(s); // i = 5
|
||||
* return 2; // i = 6
|
||||
* default: // i = 7
|
||||
* return 3; // i = 8
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Note that each non-`default` case is a labeled statement, so the statement
|
||||
* that follows is a child of the labeled statement, and not the `switch` block.
|
||||
*/
|
||||
Stmt getStmt(int i) { result = SwithStmtInternal::getStmt(this, i) }
|
||||
Stmt getStmt(int i) { result = this.getChildStmt(i) }
|
||||
|
||||
/** Gets a statement in the body of this `switch` statement. */
|
||||
Stmt getAStmt() { result = this.getStmt(_) }
|
||||
}
|
||||
|
||||
cached
|
||||
private module SwithStmtInternal {
|
||||
cached
|
||||
CaseStmt getCase(SwitchStmt ss, int i) {
|
||||
exists(int index, int rankIndex |
|
||||
caseIndex(ss, result, index) and
|
||||
rankIndex = i + 1 and
|
||||
index = rank[rankIndex](int j, CaseStmt cs | caseIndex(ss, cs, j) | j)
|
||||
)
|
||||
}
|
||||
|
||||
/** Implicitly reorder case statements to put the default case last if needed. */
|
||||
private predicate caseIndex(SwitchStmt ss, CaseStmt case, int index) {
|
||||
exists(int i | case = ss.getChildStmt(i) |
|
||||
if case instanceof DefaultCase
|
||||
then index = max(int j | exists(ss.getChildStmt(j))) + 1
|
||||
else index = i
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
Stmt getStmt(SwitchStmt ss, int i) {
|
||||
exists(int index, int rankIndex |
|
||||
result = ss.getChildStmt(index) and
|
||||
rankIndex = i + 1 and
|
||||
index =
|
||||
rank[rankIndex](int j, Stmt s |
|
||||
// `getChild` includes both labeled statements and the targeted
|
||||
// statements of labeled statement as separate children, but we
|
||||
// only want the labeled statement
|
||||
s = getLabeledStmt(ss, j)
|
||||
|
|
||||
j
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private Stmt getLabeledStmt(SwitchStmt ss, int i) {
|
||||
result = ss.getChildStmt(i) and
|
||||
not result = any(CaseStmt cs).getBody()
|
||||
}
|
||||
}
|
||||
|
||||
/** A `case` statement. */
|
||||
class CaseStmt extends Case, @case_stmt {
|
||||
override Expr getExpr() { result = any(SwitchStmt ss | ss.getACase() = this).getExpr() }
|
||||
|
||||
override PatternExpr getPattern() { result = this.getChild(0) }
|
||||
|
||||
override Stmt getBody() {
|
||||
exists(int i, Stmt next |
|
||||
this = this.getParent().getChild(i) and
|
||||
next = this.getParent().getChild(i + 1)
|
||||
|
|
||||
result = next and
|
||||
not result instanceof CaseStmt
|
||||
or
|
||||
result = next.(CaseStmt).getBody()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the condition on this case, if any. For example, the type case on line 3
|
||||
* has no condition, and the type case on line 4 has condition `s.Length > 0`, in
|
||||
|
||||
@@ -10,42 +10,15 @@ private import semmle.code.csharp.ExprOrStmtParent
|
||||
private import semmle.code.csharp.commons.Compilation
|
||||
|
||||
private module Initializers {
|
||||
/**
|
||||
* A non-static member with an initializer, for example a field `int Field = 0`.
|
||||
*/
|
||||
class InitializedInstanceMember extends Member {
|
||||
private AssignExpr ae;
|
||||
|
||||
InitializedInstanceMember() {
|
||||
not this.isStatic() and
|
||||
expr_parent_top_level(ae, _, this) and
|
||||
not ae = any(Callable c).getExpressionBody()
|
||||
}
|
||||
|
||||
/** Gets the initializer expression. */
|
||||
AssignExpr getInitializer() { result = ae }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `obinit` is an object initializer method that performs the initialization
|
||||
* of a member via assignment `init`.
|
||||
*/
|
||||
predicate obinitInitializes(ObjectInitMethod obinit, AssignExpr init) {
|
||||
exists(InitializedInstanceMember m |
|
||||
obinit.getDeclaringType().getAMember() = m and
|
||||
init = m.getInitializer()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `i`th member initializer expression for object initializer method `obinit`
|
||||
* in compilation `comp`.
|
||||
*/
|
||||
AssignExpr initializedInstanceMemberOrder(ObjectInitMethod obinit, CompilationExt comp, int i) {
|
||||
obinitInitializes(obinit, result) and
|
||||
obinit.initializes(result) and
|
||||
result =
|
||||
rank[i + 1](AssignExpr ae0, Location l |
|
||||
obinitInitializes(obinit, ae0) and
|
||||
obinit.initializes(ae0) and
|
||||
l = ae0.getLocation() and
|
||||
getCompilation(l.getFile()) = comp
|
||||
|
|
||||
@@ -74,7 +47,7 @@ class CfgScope extends Element, @top_level_exprorstmt_parent {
|
||||
any(Callable c |
|
||||
c.(Constructor).hasInitializer()
|
||||
or
|
||||
Initializers::obinitInitializes(c, _)
|
||||
c.(ObjectInitMethod).initializes(_)
|
||||
or
|
||||
c.hasBody()
|
||||
)
|
||||
@@ -308,6 +281,93 @@ private class ConstructorTree extends ControlFlowTree instanceof Constructor {
|
||||
}
|
||||
}
|
||||
|
||||
cached
|
||||
private module SwithStmtInternal {
|
||||
// Reorders default to be last if needed
|
||||
cached
|
||||
CaseStmt getCase(SwitchStmt ss, int i) {
|
||||
exists(int index, int rankIndex |
|
||||
caseIndex(ss, result, index) and
|
||||
rankIndex = i + 1 and
|
||||
index = rank[rankIndex](int j, CaseStmt cs | caseIndex(ss, cs, j) | j)
|
||||
)
|
||||
}
|
||||
|
||||
/** Implicitly reorder case statements to put the default case last if needed. */
|
||||
private predicate caseIndex(SwitchStmt ss, CaseStmt case, int index) {
|
||||
exists(int i | case = ss.getChildStmt(i) |
|
||||
if case instanceof DefaultCase
|
||||
then index = max(int j | exists(ss.getChildStmt(j))) + 1
|
||||
else index = i
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `i`th statement in the body of this `switch` statement.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```csharp
|
||||
* switch (x) {
|
||||
* case "abc": // i = 0
|
||||
* return 0;
|
||||
* case int i when i > 0: // i = 1
|
||||
* return 1;
|
||||
* case string s: // i = 2
|
||||
* Console.WriteLine(s);
|
||||
* return 2; // i = 3
|
||||
* default: // i = 4
|
||||
* return 3; // i = 5
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Note that each non-`default` case is a labeled statement, so the statement
|
||||
* that follows is a child of the labeled statement, and not the `switch` block.
|
||||
*/
|
||||
cached
|
||||
Stmt getStmt(SwitchStmt ss, int i) {
|
||||
exists(int index, int rankIndex |
|
||||
result = ss.getChildStmt(index) and
|
||||
rankIndex = i + 1 and
|
||||
index =
|
||||
rank[rankIndex](int j, Stmt s |
|
||||
// `getChild` includes both labeled statements and the targeted
|
||||
// statements of labeled statement as separate children, but we
|
||||
// only want the labeled statement
|
||||
s = getLabeledStmt(ss, j)
|
||||
|
|
||||
j
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private Stmt getLabeledStmt(SwitchStmt ss, int i) {
|
||||
result = ss.getChildStmt(i) and
|
||||
not result = caseStmtGetBody(_)
|
||||
}
|
||||
}
|
||||
|
||||
private ControlFlowElement caseGetBody(Case c) {
|
||||
result = c.getBody() or result = caseStmtGetBody(c)
|
||||
}
|
||||
|
||||
private ControlFlowElement caseStmtGetBody(CaseStmt c) {
|
||||
exists(int i, Stmt next |
|
||||
c = c.getParent().getChild(i) and
|
||||
next = c.getParent().getChild(i + 1)
|
||||
|
|
||||
result = next and
|
||||
not result instanceof CaseStmt
|
||||
or
|
||||
result = caseStmtGetBody(next)
|
||||
)
|
||||
}
|
||||
|
||||
// Reorders default to be last if needed
|
||||
private Case switchGetCase(Switch s, int i) {
|
||||
result = s.(SwitchExpr).getCase(i) or result = SwithStmtInternal::getCase(s, i)
|
||||
}
|
||||
|
||||
abstract private class SwitchTree extends ControlFlowTree instanceof Switch {
|
||||
override predicate propagatesAbnormal(AstNode child) { child = super.getExpr() }
|
||||
|
||||
@@ -315,27 +375,27 @@ abstract private class SwitchTree extends ControlFlowTree instanceof Switch {
|
||||
// Flow from last element of switch expression to first element of first case
|
||||
last(super.getExpr(), pred, c) and
|
||||
c instanceof NormalCompletion and
|
||||
first(super.getCase(0), succ)
|
||||
first(switchGetCase(this, 0), succ)
|
||||
or
|
||||
// Flow from last element of case pattern to next case
|
||||
exists(Case case, int i | case = super.getCase(i) |
|
||||
exists(Case case, int i | case = switchGetCase(this, i) |
|
||||
last(case.getPattern(), pred, c) and
|
||||
c.(MatchingCompletion).isNonMatch() and
|
||||
first(super.getCase(i + 1), succ)
|
||||
first(switchGetCase(this, i + 1), succ)
|
||||
)
|
||||
or
|
||||
// Flow from last element of condition to next case
|
||||
exists(Case case, int i | case = super.getCase(i) |
|
||||
exists(Case case, int i | case = switchGetCase(this, i) |
|
||||
last(case.getCondition(), pred, c) and
|
||||
c instanceof FalseCompletion and
|
||||
first(super.getCase(i + 1), succ)
|
||||
first(switchGetCase(this, i + 1), succ)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
abstract private class CaseTree extends ControlFlowTree instanceof Case {
|
||||
final override predicate propagatesAbnormal(AstNode child) {
|
||||
child in [super.getPattern().(ControlFlowElement), super.getCondition(), super.getBody()]
|
||||
child in [super.getPattern().(ControlFlowElement), super.getCondition(), caseGetBody(this)]
|
||||
}
|
||||
|
||||
override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
||||
@@ -348,13 +408,13 @@ abstract private class CaseTree extends ControlFlowTree instanceof Case {
|
||||
first(super.getCondition(), succ)
|
||||
else
|
||||
// Flow from last element of pattern to first element of body
|
||||
first(super.getBody(), succ)
|
||||
first(caseGetBody(this), succ)
|
||||
)
|
||||
or
|
||||
// Flow from last element of condition to first element of body
|
||||
last(super.getCondition(), pred, c) and
|
||||
c instanceof TrueCompletion and
|
||||
first(super.getBody(), succ)
|
||||
first(caseGetBody(this), succ)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1226,10 +1286,11 @@ module Statements {
|
||||
c instanceof NormalCompletion
|
||||
or
|
||||
// A statement exits with a `break` completion
|
||||
last(super.getStmt(_), last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion())
|
||||
last(SwithStmtInternal::getStmt(this, _), last,
|
||||
c.(NestedBreakCompletion).getAnInnerCompatibleCompletion())
|
||||
or
|
||||
// A statement exits abnormally
|
||||
last(super.getStmt(_), last, c) and
|
||||
last(SwithStmtInternal::getStmt(this, _), last, c) and
|
||||
not c instanceof BreakCompletion and
|
||||
not c instanceof NormalCompletion and
|
||||
not any(LabeledStmtTree t |
|
||||
@@ -1238,8 +1299,8 @@ module Statements {
|
||||
or
|
||||
// Last case exits with a non-match
|
||||
exists(CaseStmt cs, int last_ |
|
||||
last_ = max(int i | exists(super.getCase(i))) and
|
||||
cs = super.getCase(last_)
|
||||
last_ = max(int i | exists(SwithStmtInternal::getCase(this, i))) and
|
||||
cs = SwithStmtInternal::getCase(this, last_)
|
||||
|
|
||||
last(cs.getPattern(), last, c) and
|
||||
not c.(MatchingCompletion).isMatch()
|
||||
@@ -1258,22 +1319,22 @@ module Statements {
|
||||
c instanceof SimpleCompletion
|
||||
or
|
||||
// Flow from last element of non-`case` statement `i` to first element of statement `i+1`
|
||||
exists(int i | last(super.getStmt(i), pred, c) |
|
||||
not super.getStmt(i) instanceof CaseStmt and
|
||||
exists(int i | last(SwithStmtInternal::getStmt(this, i), pred, c) |
|
||||
not SwithStmtInternal::getStmt(this, i) instanceof CaseStmt and
|
||||
c instanceof NormalCompletion and
|
||||
first(super.getStmt(i + 1), succ)
|
||||
first(SwithStmtInternal::getStmt(this, i + 1), succ)
|
||||
)
|
||||
or
|
||||
// Flow from last element of `case` statement `i` to first element of statement `i+1`
|
||||
exists(int i, Stmt body |
|
||||
body = super.getStmt(i).(CaseStmt).getBody() and
|
||||
body = caseStmtGetBody(SwithStmtInternal::getStmt(this, i)) and
|
||||
// in case of fall-through cases, make sure to not jump from their shared body back
|
||||
// to one of the fall-through cases
|
||||
not body = super.getStmt(i + 1).(CaseStmt).getBody() and
|
||||
not body = caseStmtGetBody(SwithStmtInternal::getStmt(this, i + 1)) and
|
||||
last(body, pred, c)
|
||||
|
|
||||
c instanceof NormalCompletion and
|
||||
first(super.getStmt(i + 1), succ)
|
||||
first(SwithStmtInternal::getStmt(this, i + 1), succ)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1289,7 +1350,7 @@ module Statements {
|
||||
not c.(MatchingCompletion).isMatch()
|
||||
or
|
||||
// Case body exits with any completion
|
||||
last(super.getBody(), last, c)
|
||||
last(caseStmtGetBody(this), last, c)
|
||||
}
|
||||
|
||||
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Provides a basic block implementation on control flow elements. That is,
|
||||
* a "pre-CFG" where the nodes are (unsplit) control flow elements and the
|
||||
* successor relation is `succ = succ(pred, _)`.
|
||||
*
|
||||
* The logic is duplicated from the implementation in `BasicBlocks.qll`, and
|
||||
* being an internal class, all predicate documentation has been removed.
|
||||
*/
|
||||
|
||||
import csharp
|
||||
private import Completion
|
||||
private import ControlFlowGraphImpl
|
||||
private import semmle.code.csharp.controlflow.ControlFlowGraph::ControlFlow as Cfg
|
||||
private import codeql.controlflow.BasicBlock as BB
|
||||
|
||||
private predicate startsBB(ControlFlowElement cfe) {
|
||||
not succ(_, cfe, _) and
|
||||
(
|
||||
succ(cfe, _, _)
|
||||
or
|
||||
scopeLast(_, cfe, _)
|
||||
)
|
||||
or
|
||||
strictcount(ControlFlowElement pred, Completion c | succ(pred, cfe, c)) > 1
|
||||
or
|
||||
succ(_, cfe, any(ConditionalCompletion c))
|
||||
or
|
||||
exists(ControlFlowElement pred, int i |
|
||||
succ(pred, cfe, _) and
|
||||
i = count(ControlFlowElement succ, Completion c | succ(pred, succ, c))
|
||||
|
|
||||
i > 1
|
||||
or
|
||||
i = 1 and
|
||||
scopeLast(_, pred, _)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate intraBBSucc(ControlFlowElement pred, ControlFlowElement succ) {
|
||||
succ(pred, succ, _) and
|
||||
not startsBB(succ)
|
||||
}
|
||||
|
||||
private predicate bbIndex(ControlFlowElement bbStart, ControlFlowElement cfe, int i) =
|
||||
shortestDistances(startsBB/1, intraBBSucc/2)(bbStart, cfe, i)
|
||||
|
||||
private predicate succBB(PreBasicBlock pred, PreBasicBlock succ) { succ = pred.getASuccessor() }
|
||||
|
||||
private predicate entryBB(PreBasicBlock bb) { scopeFirst(_, bb) }
|
||||
|
||||
private predicate bbIDominates(PreBasicBlock dom, PreBasicBlock bb) =
|
||||
idominance(entryBB/1, succBB/2)(_, dom, bb)
|
||||
|
||||
class PreBasicBlock extends ControlFlowElement {
|
||||
PreBasicBlock() { startsBB(this) }
|
||||
|
||||
PreBasicBlock getASuccessor(Cfg::SuccessorType t) {
|
||||
succ(this.getLastNode(), result, any(Completion c | t = c.getAMatchingSuccessorType()))
|
||||
}
|
||||
|
||||
deprecated PreBasicBlock getASuccessorByType(Cfg::SuccessorType t) {
|
||||
result = this.getASuccessor(t)
|
||||
}
|
||||
|
||||
PreBasicBlock getASuccessor() { result = this.getASuccessor(_) }
|
||||
|
||||
PreBasicBlock getAPredecessor() { result.getASuccessor() = this }
|
||||
|
||||
ControlFlowElement getNode(int pos) { bbIndex(this, result, pos) }
|
||||
|
||||
deprecated ControlFlowElement getElement(int pos) { result = this.getNode(pos) }
|
||||
|
||||
ControlFlowElement getAnElement() { result = this.getNode(_) }
|
||||
|
||||
ControlFlowElement getFirstElement() { result = this }
|
||||
|
||||
ControlFlowElement getLastNode() { result = this.getNode(this.length() - 1) }
|
||||
|
||||
deprecated ControlFlowElement getLastElement() { result = this.getLastNode() }
|
||||
|
||||
int length() { result = strictcount(this.getAnElement()) }
|
||||
|
||||
PreBasicBlock getImmediateDominator() { bbIDominates(result, this) }
|
||||
|
||||
predicate immediatelyDominates(PreBasicBlock bb) { bbIDominates(this, bb) }
|
||||
|
||||
pragma[inline]
|
||||
predicate strictlyDominates(PreBasicBlock bb) { this.immediatelyDominates+(bb) }
|
||||
|
||||
pragma[inline]
|
||||
predicate dominates(PreBasicBlock bb) {
|
||||
bb = this
|
||||
or
|
||||
this.strictlyDominates(bb)
|
||||
}
|
||||
|
||||
predicate inDominanceFrontier(PreBasicBlock df) {
|
||||
this = df.getAPredecessor() and not bbIDominates(this, df)
|
||||
or
|
||||
exists(PreBasicBlock prev | prev.inDominanceFrontier(df) |
|
||||
bbIDominates(this, prev) and
|
||||
not bbIDominates(this, df)
|
||||
)
|
||||
}
|
||||
|
||||
/** Unsupported. Do not use. */
|
||||
predicate strictlyPostDominates(PreBasicBlock bb) { none() }
|
||||
|
||||
/** Unsupported. Do not use. */
|
||||
predicate postDominates(PreBasicBlock bb) {
|
||||
this.strictlyPostDominates(bb) or
|
||||
this = bb
|
||||
}
|
||||
}
|
||||
|
||||
private Completion getConditionalCompletion(ConditionalCompletion cc) {
|
||||
result.getInnerCompletion() = cc
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate conditionBlockImmediatelyControls(
|
||||
ConditionBlock cond, PreBasicBlock succ, ConditionalCompletion cc
|
||||
) {
|
||||
exists(ControlFlowElement last, Completion c |
|
||||
last = cond.getLastNode() and
|
||||
c = getConditionalCompletion(cc) and
|
||||
succ(last, succ, c) and
|
||||
// In the pre-CFG, we need to account for case where one predecessor node has
|
||||
// two edges to the same successor node. Assertion expressions are examples of
|
||||
// such nodes.
|
||||
not exists(Completion other |
|
||||
succ(last, succ, other) and
|
||||
other != c
|
||||
) and
|
||||
forall(PreBasicBlock pred | pred = succ.getAPredecessor() and pred != cond |
|
||||
succ.dominates(pred)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
class ConditionBlock extends PreBasicBlock {
|
||||
ConditionBlock() {
|
||||
exists(Completion c | c = getConditionalCompletion(_) |
|
||||
succ(this.getLastNode(), _, c)
|
||||
or
|
||||
scopeLast(_, this.getLastNode(), c)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate controls(PreBasicBlock controlled, Cfg::ConditionalSuccessor s) {
|
||||
exists(PreBasicBlock succ, ConditionalCompletion c |
|
||||
conditionBlockImmediatelyControls(this, succ, c)
|
||||
|
|
||||
succ.dominates(controlled) and
|
||||
s = c.getAMatchingSuccessorType()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module PreCfg implements BB::CfgSig<Location> {
|
||||
class ControlFlowNode = ControlFlowElement;
|
||||
|
||||
class BasicBlock = PreBasicBlock;
|
||||
|
||||
class EntryBasicBlock extends BasicBlock {
|
||||
EntryBasicBlock() { entryBB(this) }
|
||||
}
|
||||
|
||||
predicate dominatingEdge(BasicBlock bb1, BasicBlock bb2) {
|
||||
conditionBlockImmediatelyControls(bb1, bb2, _)
|
||||
}
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
import csharp
|
||||
|
||||
private class ControlFlowScope extends ControlFlowElement {
|
||||
private boolean exactScope;
|
||||
|
||||
ControlFlowScope() {
|
||||
exists(ControlFlowReachabilityConfiguration c |
|
||||
c.candidate(_, _, this, exactScope, _) or
|
||||
c.candidateDef(_, _, this, exactScope, _)
|
||||
)
|
||||
}
|
||||
|
||||
predicate isExact() { exactScope = true }
|
||||
|
||||
predicate isNonExact() { exactScope = false }
|
||||
}
|
||||
|
||||
private newtype TControlFlowElementOrBasicBlock =
|
||||
TControlFlowElement(ControlFlowElement cfe) or
|
||||
TBasicBlock(ControlFlow::BasicBlock bb)
|
||||
|
||||
class ControlFlowElementOrBasicBlock extends TControlFlowElementOrBasicBlock {
|
||||
ControlFlowElement asControlFlowElement() { this = TControlFlowElement(result) }
|
||||
|
||||
ControlFlow::BasicBlock asBasicBlock() { this = TBasicBlock(result) }
|
||||
|
||||
string toString() {
|
||||
result = this.asControlFlowElement().toString()
|
||||
or
|
||||
result = this.asBasicBlock().toString()
|
||||
}
|
||||
|
||||
Location getLocation() {
|
||||
result = this.asControlFlowElement().getLocation()
|
||||
or
|
||||
result = this.asBasicBlock().getLocation()
|
||||
}
|
||||
}
|
||||
|
||||
private predicate isBasicBlock(ControlFlowElementOrBasicBlock c) { c instanceof TBasicBlock }
|
||||
|
||||
private predicate isNonExactScope(ControlFlowElementOrBasicBlock c) {
|
||||
c.asControlFlowElement().(ControlFlowScope).isNonExact()
|
||||
}
|
||||
|
||||
private predicate step(ControlFlowElementOrBasicBlock pred, ControlFlowElementOrBasicBlock succ) {
|
||||
pred.asBasicBlock().getANode().getAstNode() = succ.asControlFlowElement()
|
||||
or
|
||||
pred.asControlFlowElement() = succ.asControlFlowElement().getAChild()
|
||||
}
|
||||
|
||||
private predicate basicBlockInNonExactScope(
|
||||
ControlFlowElementOrBasicBlock bb, ControlFlowElementOrBasicBlock scope
|
||||
) = doublyBoundedFastTC(step/2, isBasicBlock/1, isNonExactScope/1)(bb, scope)
|
||||
|
||||
pragma[noinline]
|
||||
private ControlFlow::BasicBlock getABasicBlockInScope(ControlFlowScope scope, boolean exactScope) {
|
||||
basicBlockInNonExactScope(TBasicBlock(result), TControlFlowElement(scope)) and
|
||||
exactScope = false
|
||||
or
|
||||
scope.isExact() and
|
||||
result.getANode().getAstNode() = scope and
|
||||
exactScope = true
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper class for determining control-flow reachability for pairs of
|
||||
* elements.
|
||||
*
|
||||
* This is useful when defining for example expression-based data-flow steps in
|
||||
* the presence of control-flow splitting, where a data-flow step should make
|
||||
* sure to stay in the same split.
|
||||
*
|
||||
* For example, in
|
||||
*
|
||||
* ```csharp
|
||||
* if (b)
|
||||
* ....
|
||||
* var x = "foo";
|
||||
* if (b)
|
||||
* ....
|
||||
* ```
|
||||
*
|
||||
* there should only be steps from `[b = true] "foo"` to `[b = true] SSA def(x)`
|
||||
* and `[b = false] "foo"` to `[b = false] SSA def(x)`, and for example not from
|
||||
* `[b = true] "foo"` to `[b = false] SSA def(x)`
|
||||
*/
|
||||
abstract class ControlFlowReachabilityConfiguration extends string {
|
||||
bindingset[this]
|
||||
ControlFlowReachabilityConfiguration() { any() }
|
||||
|
||||
/**
|
||||
* Holds if `e1` and `e2` are expressions for which we want to find a
|
||||
* control-flow path that follows control flow successors (resp.
|
||||
* predecessors, as specified by `isSuccessor`) inside the syntactic scope
|
||||
* `scope`. The Boolean `exactScope` indicates whether a transitive child
|
||||
* of `scope` is allowed (`exactScope = false`).
|
||||
*/
|
||||
predicate candidate(
|
||||
Expr e1, Expr e2, ControlFlowElement scope, boolean exactScope, boolean isSuccessor
|
||||
) {
|
||||
none()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e` and `def` are elements for which we want to find a
|
||||
* control-flow path that follows control flow successors (resp.
|
||||
* predecessors, as specified by `isSuccessor`) inside the syntactic scope
|
||||
* `scope`. The Boolean `exactScope` indicates whether a transitive child
|
||||
* of `scope` is allowed (`exactScope = false`).
|
||||
*/
|
||||
predicate candidateDef(
|
||||
Expr e, AssignableDefinition def, ControlFlowElement scope, boolean exactScope,
|
||||
boolean isSuccessor
|
||||
) {
|
||||
none()
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate reachesBasicBlockExprBase(
|
||||
Expr e1, Expr e2, boolean isSuccessor, ControlFlow::Nodes::ElementNode cfn1, int i,
|
||||
ControlFlow::BasicBlock bb
|
||||
) {
|
||||
this.candidate(e1, e2, _, _, isSuccessor) and
|
||||
cfn1 = e1.getAControlFlowNode() and
|
||||
bb.getNode(i) = cfn1
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate reachesBasicBlockExprRec(
|
||||
Expr e1, Expr e2, boolean isSuccessor, ControlFlow::Nodes::ElementNode cfn1,
|
||||
ControlFlow::BasicBlock bb
|
||||
) {
|
||||
exists(ControlFlow::BasicBlock mid |
|
||||
this.reachesBasicBlockExpr(e1, e2, isSuccessor, cfn1, mid)
|
||||
|
|
||||
isSuccessor = true and
|
||||
bb = mid.getASuccessor()
|
||||
or
|
||||
isSuccessor = false and
|
||||
bb = mid.getAPredecessor()
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate reachesBasicBlockExpr(
|
||||
Expr e1, Expr e2, boolean isSuccessor, ControlFlow::Nodes::ElementNode cfn1,
|
||||
ControlFlow::BasicBlock bb
|
||||
) {
|
||||
this.reachesBasicBlockExprBase(e1, e2, isSuccessor, cfn1, _, bb)
|
||||
or
|
||||
exists(ControlFlowElement scope, boolean exactScope |
|
||||
this.candidate(e1, e2, scope, exactScope, isSuccessor) and
|
||||
this.reachesBasicBlockExprRec(e1, e2, isSuccessor, cfn1, bb) and
|
||||
bb = getABasicBlockInScope(scope, exactScope)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate reachesBasicBlockDefinitionBase(
|
||||
Expr e, AssignableDefinition def, boolean isSuccessor, ControlFlow::Nodes::ElementNode cfn,
|
||||
int i, ControlFlow::BasicBlock bb
|
||||
) {
|
||||
this.candidateDef(e, def, _, _, isSuccessor) and
|
||||
cfn = e.getAControlFlowNode() and
|
||||
bb.getNode(i) = cfn
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate reachesBasicBlockDefinitionRec(
|
||||
Expr e, AssignableDefinition def, boolean isSuccessor, ControlFlow::Nodes::ElementNode cfn,
|
||||
ControlFlow::BasicBlock bb
|
||||
) {
|
||||
exists(ControlFlow::BasicBlock mid |
|
||||
this.reachesBasicBlockDefinition(e, def, isSuccessor, cfn, mid)
|
||||
|
|
||||
isSuccessor = true and
|
||||
bb = mid.getASuccessor()
|
||||
or
|
||||
isSuccessor = false and
|
||||
bb = mid.getAPredecessor()
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate reachesBasicBlockDefinition(
|
||||
Expr e, AssignableDefinition def, boolean isSuccessor, ControlFlow::Nodes::ElementNode cfn,
|
||||
ControlFlow::BasicBlock bb
|
||||
) {
|
||||
this.reachesBasicBlockDefinitionBase(e, def, isSuccessor, cfn, _, bb)
|
||||
or
|
||||
exists(ControlFlowElement scope, boolean exactScope |
|
||||
this.candidateDef(e, def, scope, exactScope, isSuccessor) and
|
||||
this.reachesBasicBlockDefinitionRec(e, def, isSuccessor, cfn, bb) and
|
||||
bb = getABasicBlockInScope(scope, exactScope)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a control-flow path from `cfn1` to `cfn2`, where `cfn1` is a
|
||||
* control-flow node for `e1` and `cfn2` is a control-flow node for `e2`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate hasExprPath(Expr e1, ControlFlow::Node cfn1, Expr e2, ControlFlow::Node cfn2) {
|
||||
exists(ControlFlow::BasicBlock bb, boolean isSuccessor, int i, int j |
|
||||
this.reachesBasicBlockExprBase(e1, e2, isSuccessor, cfn1, i, bb) and
|
||||
cfn2 = bb.getNode(j) and
|
||||
cfn2 = e2.getAControlFlowNode()
|
||||
|
|
||||
isSuccessor = true and j >= i
|
||||
or
|
||||
isSuccessor = false and i >= j
|
||||
)
|
||||
or
|
||||
exists(ControlFlow::BasicBlock bb |
|
||||
this.reachesBasicBlockExprRec(e1, e2, _, cfn1, bb) and
|
||||
cfn2 = bb.getANode() and
|
||||
cfn2 = e2.getAControlFlowNode()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a control-flow path from `cfn` to `cfnDef`, where `cfn` is a
|
||||
* control-flow node for `e` and `cfnDef` is a control-flow node for `def`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate hasDefPath(
|
||||
Expr e, ControlFlow::Node cfn, AssignableDefinition def, ControlFlow::Node cfnDef
|
||||
) {
|
||||
exists(ControlFlow::BasicBlock bb, boolean isSuccessor, int i, int j |
|
||||
this.reachesBasicBlockDefinitionBase(e, def, isSuccessor, cfn, i, bb) and
|
||||
cfnDef = bb.getNode(j) and
|
||||
def.getExpr().getAControlFlowNode() = cfnDef
|
||||
|
|
||||
isSuccessor = true and j >= i
|
||||
or
|
||||
isSuccessor = false and i >= j
|
||||
)
|
||||
or
|
||||
exists(ControlFlow::BasicBlock bb |
|
||||
this.reachesBasicBlockDefinitionRec(e, def, _, cfn, bb) and
|
||||
def.getExpr().getAControlFlowNode() = cfnDef and
|
||||
cfnDef = bb.getANode()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ private import csharp
|
||||
private import DataFlowPublic
|
||||
private import DataFlowDispatch
|
||||
private import DataFlowImplCommon
|
||||
private import ControlFlowReachability
|
||||
private import FlowSummaryImpl as FlowSummaryImpl
|
||||
private import semmle.code.csharp.dataflow.FlowSummary as FlowSummary
|
||||
private import semmle.code.csharp.dataflow.internal.ExternalFlow
|
||||
@@ -259,34 +258,16 @@ private module ThisFlow {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a control-flow path from `n1` to `n2`. `n2` is either an
|
||||
* expression node or an SSA definition node.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate hasNodePath(ControlFlowReachabilityConfiguration conf, ExprNode n1, Node n2) {
|
||||
exists(ControlFlow::Node cfn1, ControlFlow::Node cfn2 | conf.hasExprPath(_, cfn1, _, cfn2) |
|
||||
cfn1 = n1.getControlFlowNode() and
|
||||
cfn2 = n2.(ExprNode).getControlFlowNode()
|
||||
)
|
||||
or
|
||||
exists(ControlFlow::Node cfn, AssignableDefinition def, ControlFlow::Node cfnDef |
|
||||
conf.hasDefPath(_, cfn, def, cfnDef) and
|
||||
cfn = n1.getControlFlowNode() and
|
||||
n2 = TAssignableDefinitionNode(def, cfnDef)
|
||||
)
|
||||
}
|
||||
|
||||
/** Provides logic related to captured variables. */
|
||||
module VariableCapture {
|
||||
private import codeql.dataflow.VariableCapture as Shared
|
||||
private import semmle.code.csharp.controlflow.BasicBlocks as BasicBlocks
|
||||
|
||||
private predicate closureFlowStep(ControlFlow::Nodes::ExprNode e1, ControlFlow::Nodes::ExprNode e2) {
|
||||
e1 = LocalFlow::getALastEvalNode(e2)
|
||||
e1.getExpr() = LocalFlow::getALastEvalNode(e2.getExpr())
|
||||
or
|
||||
exists(Ssa::Definition def, AssignableDefinition adef |
|
||||
LocalFlow::defAssigns(adef, _, e1) and
|
||||
LocalFlow::defAssigns(adef, _, _, e1) and
|
||||
def.getAnUltimateDefinition().(Ssa::ExplicitDefinition).getADefinition() = adef and
|
||||
exists(def.getAReadAtNode(e2))
|
||||
)
|
||||
@@ -379,7 +360,7 @@ module VariableCapture {
|
||||
this = def.getExpr().getAControlFlowNode()
|
||||
}
|
||||
|
||||
ControlFlow::Node getRhs() { LocalFlow::defAssigns(def, this, result) }
|
||||
ControlFlow::Node getRhs() { LocalFlow::defAssigns(def, this, _, result) }
|
||||
|
||||
CapturedVariable getVariable() { result = v }
|
||||
}
|
||||
@@ -528,127 +509,74 @@ module SsaFlow {
|
||||
|
||||
/** Provides predicates related to local data flow. */
|
||||
module LocalFlow {
|
||||
class LocalExprStepConfiguration extends ControlFlowReachabilityConfiguration {
|
||||
LocalExprStepConfiguration() { this = "LocalExprStepConfiguration" }
|
||||
|
||||
override predicate candidate(
|
||||
Expr e1, Expr e2, ControlFlowElement scope, boolean exactScope, boolean isSuccessor
|
||||
) {
|
||||
exactScope = false and
|
||||
(
|
||||
e1 = e2.(ParenthesizedExpr).getExpr() and
|
||||
scope = e2 and
|
||||
isSuccessor = true
|
||||
or
|
||||
e1 = e2.(NullCoalescingExpr).getAnOperand() and
|
||||
scope = e2 and
|
||||
isSuccessor = true
|
||||
or
|
||||
e1 = e2.(SuppressNullableWarningExpr).getExpr() and
|
||||
scope = e2 and
|
||||
isSuccessor = true
|
||||
or
|
||||
e2 =
|
||||
any(ConditionalExpr ce |
|
||||
e1 = ce.getThen() or
|
||||
e1 = ce.getElse()
|
||||
) and
|
||||
scope = e2 and
|
||||
isSuccessor = true
|
||||
or
|
||||
e1 = e2.(Cast).getExpr() and
|
||||
scope = e2 and
|
||||
isSuccessor = true
|
||||
or
|
||||
// An `=` expression, where the result of the expression is used
|
||||
e2 =
|
||||
any(AssignExpr ae |
|
||||
ae.getParent() = any(ControlFlowElement cfe | not cfe instanceof ExprStmt) and
|
||||
e1 = ae.getRValue()
|
||||
) and
|
||||
scope = e2 and
|
||||
isSuccessor = true
|
||||
or
|
||||
e1 = e2.(ObjectCreation).getInitializer() and
|
||||
scope = e2 and
|
||||
isSuccessor = false
|
||||
or
|
||||
e1 = e2.(ArrayCreation).getInitializer() and
|
||||
scope = e2 and
|
||||
isSuccessor = false
|
||||
or
|
||||
e1 = e2.(SwitchExpr).getACase().getBody() and
|
||||
scope = e2 and
|
||||
isSuccessor = true
|
||||
or
|
||||
e1 = e2.(CheckedExpr).getExpr() and
|
||||
scope = e2 and
|
||||
isSuccessor = true
|
||||
or
|
||||
e1 = e2.(UncheckedExpr).getExpr() and
|
||||
scope = e2 and
|
||||
isSuccessor = true
|
||||
or
|
||||
e1 = e2.(CollectionExpression).getAnElement() and
|
||||
e1 instanceof SpreadElementExpr and
|
||||
scope = e2 and
|
||||
isSuccessor = true
|
||||
or
|
||||
e1 = e2.(SpreadElementExpr).getExpr() and
|
||||
scope = e2 and
|
||||
isSuccessor = true
|
||||
or
|
||||
exists(WithExpr we |
|
||||
scope = we and
|
||||
isSuccessor = true
|
||||
|
|
||||
e1 = we.getExpr() and
|
||||
e2 = we.getInitializer()
|
||||
or
|
||||
e1 = we.getInitializer() and
|
||||
e2 = we
|
||||
)
|
||||
or
|
||||
scope = any(AssignExpr ae | ae.getLValue().(TupleExpr) = e2 and ae.getRValue() = e1) and
|
||||
isSuccessor = false
|
||||
or
|
||||
isSuccessor = true and
|
||||
exists(ControlFlowElement cfe | cfe = e2.(TupleExpr).(PatternExpr).getPatternMatch() |
|
||||
cfe.(IsExpr).getExpr() = e1 and scope = cfe
|
||||
or
|
||||
exists(Switch sw | sw.getACase() = cfe and sw.getExpr() = e1 and scope = sw)
|
||||
)
|
||||
predicate localExprStep(Expr e1, Expr e2) {
|
||||
e1 = e2.(ParenthesizedExpr).getExpr()
|
||||
or
|
||||
e1 = e2.(NullCoalescingExpr).getAnOperand()
|
||||
or
|
||||
e1 = e2.(SuppressNullableWarningExpr).getExpr()
|
||||
or
|
||||
e2 =
|
||||
any(ConditionalExpr ce |
|
||||
e1 = ce.getThen() or
|
||||
e1 = ce.getElse()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate candidateDef(
|
||||
Expr e, AssignableDefinition def, ControlFlowElement scope, boolean exactScope,
|
||||
boolean isSuccessor
|
||||
) {
|
||||
// Flow from source to definition
|
||||
exactScope = false and
|
||||
def.getSource() = e and
|
||||
(
|
||||
scope = def.getExpr() and
|
||||
isSuccessor = true
|
||||
or
|
||||
scope = def.(AssignableDefinitions::PatternDefinition).getMatch().(IsExpr) and
|
||||
isSuccessor = false
|
||||
or
|
||||
exists(Switch s |
|
||||
s.getACase() = def.(AssignableDefinitions::PatternDefinition).getMatch() and
|
||||
isSuccessor = true
|
||||
|
|
||||
scope = s.getExpr()
|
||||
or
|
||||
scope = s.getACase()
|
||||
)
|
||||
or
|
||||
e1 = e2.(Cast).getExpr()
|
||||
or
|
||||
// An `=` expression, where the result of the expression is used
|
||||
e2 =
|
||||
any(AssignExpr ae |
|
||||
ae.getParent() = any(ControlFlowElement cfe | not cfe instanceof ExprStmt) and
|
||||
e1 = ae.getRValue()
|
||||
)
|
||||
}
|
||||
or
|
||||
e1 = e2.(ObjectCreation).getInitializer()
|
||||
or
|
||||
e1 = e2.(ArrayCreation).getInitializer()
|
||||
or
|
||||
e1 = e2.(SwitchExpr).getACase().getBody()
|
||||
or
|
||||
e1 = e2.(CheckedExpr).getExpr()
|
||||
or
|
||||
e1 = e2.(UncheckedExpr).getExpr()
|
||||
or
|
||||
e1 = e2.(CollectionExpression).getAnElement() and
|
||||
e1 instanceof SpreadElementExpr
|
||||
or
|
||||
e1 = e2.(SpreadElementExpr).getExpr()
|
||||
or
|
||||
exists(WithExpr we |
|
||||
e1 = we.getExpr() and
|
||||
e2 = we.getInitializer()
|
||||
or
|
||||
e1 = we.getInitializer() and
|
||||
e2 = we
|
||||
)
|
||||
or
|
||||
exists(AssignExpr ae | ae.getLValue().(TupleExpr) = e2 and ae.getRValue() = e1)
|
||||
or
|
||||
exists(ControlFlowElement cfe | cfe = e2.(TupleExpr).(PatternExpr).getPatternMatch() |
|
||||
cfe.(IsExpr).getExpr() = e1
|
||||
or
|
||||
exists(Switch sw | sw.getACase() = cfe and sw.getExpr() = e1)
|
||||
)
|
||||
}
|
||||
|
||||
predicate defAssigns(AssignableDefinition def, ControlFlow::Node cfnDef, ControlFlow::Node value) {
|
||||
any(LocalExprStepConfiguration x).hasDefPath(_, value, def, cfnDef)
|
||||
predicate defAssigns(
|
||||
AssignableDefinition def, ControlFlow::Node cfnDef, Expr value, ControlFlow::Node valueCfn
|
||||
) {
|
||||
def.getSource() = value and
|
||||
valueCfn = value.getControlFlowNode() and
|
||||
cfnDef = def.getExpr().getAControlFlowNode()
|
||||
}
|
||||
|
||||
private predicate defAssigns(ExprNode value, AssignableDefinitionNode defNode) {
|
||||
exists(ControlFlow::Node cfn, AssignableDefinition def, ControlFlow::Node cfnDef |
|
||||
defAssigns(def, cfnDef, value.getExpr(), _) and
|
||||
cfn = value.getControlFlowNode() and
|
||||
defNode = TAssignableDefinitionNode(def, cfnDef)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -659,7 +587,9 @@ module LocalFlow {
|
||||
}
|
||||
|
||||
predicate localFlowStepCommon(Node nodeFrom, Node nodeTo) {
|
||||
hasNodePath(any(LocalExprStepConfiguration x), nodeFrom, nodeTo)
|
||||
localExprStep(nodeFrom.asExpr(), nodeTo.asExpr())
|
||||
or
|
||||
defAssigns(nodeFrom, nodeTo)
|
||||
or
|
||||
ThisFlow::adjacentThisRefs(nodeFrom, nodeTo) and
|
||||
nodeFrom != nodeTo
|
||||
@@ -685,11 +615,12 @@ module LocalFlow {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that may execute last in `n`, and which, when it executes last,
|
||||
* will be the value of `n`.
|
||||
* Gets a node that may execute last in `e`, and which, when it executes last,
|
||||
* will be the value of `e`.
|
||||
*/
|
||||
ControlFlow::Nodes::ExprNode getALastEvalNode(ControlFlow::Nodes::ExprNode cfn) {
|
||||
exists(Expr e | any(LocalExprStepConfiguration x).hasExprPath(_, result, e, cfn) |
|
||||
Expr getALastEvalNode(Expr e) {
|
||||
localExprStep(result, e) and
|
||||
(
|
||||
e instanceof ConditionalExpr or
|
||||
e instanceof Cast or
|
||||
e instanceof NullCoalescingExpr or
|
||||
@@ -713,9 +644,7 @@ module LocalFlow {
|
||||
* we add a reverse flow step from `[post] b ? x : y` to `[post] x` and to
|
||||
* `[post] y`, in order for the side-effect of `m` to reach both `x` and `y`.
|
||||
*/
|
||||
ControlFlow::Nodes::ExprNode getPostUpdateReverseStep(ControlFlow::Nodes::ExprNode e) {
|
||||
result = getALastEvalNode(e)
|
||||
}
|
||||
Expr getPostUpdateReverseStep(Expr e) { result = getALastEvalNode(e) }
|
||||
|
||||
/**
|
||||
* Holds if the value of `node2` is given by `node1`.
|
||||
@@ -729,9 +658,10 @@ module LocalFlow {
|
||||
e instanceof ThisAccess or e instanceof BaseAccess
|
||||
)
|
||||
or
|
||||
hasNodePath(any(LocalExprStepConfiguration x), node1, node2) and
|
||||
defAssigns(node1, node2)
|
||||
or
|
||||
localExprStep(node1.asExpr(), node2.asExpr()) and
|
||||
(
|
||||
node2 instanceof AssignableDefinitionNode or
|
||||
node2.asExpr() instanceof Cast or
|
||||
node2.asExpr() instanceof AssignExpr
|
||||
)
|
||||
@@ -775,12 +705,8 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo, string model) {
|
||||
or
|
||||
nodeTo = nodeFrom.(LocalFunctionCreationNode).getAnAccess(true)
|
||||
or
|
||||
nodeTo.(PostUpdateNode).getPreUpdateNode().(ExprNode).getControlFlowNode() =
|
||||
LocalFlow::getPostUpdateReverseStep(nodeFrom
|
||||
.(PostUpdateNode)
|
||||
.getPreUpdateNode()
|
||||
.(ExprNode)
|
||||
.getControlFlowNode())
|
||||
nodeTo.(PostUpdateNode).getPreUpdateNode().asExpr() =
|
||||
LocalFlow::getPostUpdateReverseStep(nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr())
|
||||
) and
|
||||
model = ""
|
||||
or
|
||||
@@ -834,11 +760,11 @@ private class Argument extends Expr {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e` is an assignment of `src` to field or property `c` of `q`.
|
||||
* Holds if there is an assignment of `src` to field or property `c` of `q`.
|
||||
*
|
||||
* `postUpdate` indicates whether the store targets a post-update node.
|
||||
*/
|
||||
private predicate fieldOrPropertyStore(Expr e, ContentSet c, Expr src, Expr q, boolean postUpdate) {
|
||||
private predicate fieldOrPropertyStore(ContentSet c, Expr src, Expr q, boolean postUpdate) {
|
||||
exists(FieldOrProperty f |
|
||||
c = f.getContentSet() and
|
||||
(
|
||||
@@ -861,25 +787,20 @@ private predicate fieldOrPropertyStore(Expr e, ContentSet c, Expr src, Expr q, b
|
||||
f = fa.getTarget() and
|
||||
src = def.getSource() and
|
||||
q = fa.getQualifier() and
|
||||
e = def.getExpr() and
|
||||
postUpdate = true
|
||||
)
|
||||
or
|
||||
// `with` expression initializer, `x with { f = src }`
|
||||
e =
|
||||
any(WithExpr we |
|
||||
exists(MemberInitializer mi |
|
||||
q = we and
|
||||
mi = we.getInitializer().getAMemberInitializer() and
|
||||
f = mi.getInitializedMember() and
|
||||
src = mi.getRValue() and
|
||||
postUpdate = false
|
||||
)
|
||||
)
|
||||
exists(WithExpr we, MemberInitializer mi |
|
||||
q = we and
|
||||
mi = we.getInitializer().getAMemberInitializer() and
|
||||
f = mi.getInitializedMember() and
|
||||
src = mi.getRValue() and
|
||||
postUpdate = false
|
||||
)
|
||||
or
|
||||
// Object initializer, `new C() { f = src }`
|
||||
exists(MemberInitializer mi |
|
||||
e = q and
|
||||
mi = q.(ObjectInitializer).getAMemberInitializer() and
|
||||
q.getParent() instanceof ObjectCreation and
|
||||
f = mi.getInitializedMember() and
|
||||
@@ -888,16 +809,13 @@ private predicate fieldOrPropertyStore(Expr e, ContentSet c, Expr src, Expr q, b
|
||||
)
|
||||
or
|
||||
// Tuple element, `(..., src, ...)` `f` is `ItemX` of tuple `q`
|
||||
e =
|
||||
any(TupleExpr te |
|
||||
exists(int i |
|
||||
e = q and
|
||||
src = te.getArgument(i) and
|
||||
te.isConstruction() and
|
||||
f = q.getType().(TupleType).getElement(i) and
|
||||
postUpdate = false
|
||||
)
|
||||
)
|
||||
exists(TupleExpr te, int i |
|
||||
te = q and
|
||||
src = te.getArgument(i) and
|
||||
te.isConstruction() and
|
||||
f = q.getType().(TupleType).getElement(i) and
|
||||
postUpdate = false
|
||||
)
|
||||
)
|
||||
or
|
||||
// A write to a dynamic property
|
||||
@@ -907,7 +825,6 @@ private predicate fieldOrPropertyStore(Expr e, ContentSet c, Expr src, Expr q, b
|
||||
c.isDynamicProperty(dp) and
|
||||
src = def.getSource() and
|
||||
q = dma.getQualifier() and
|
||||
e = def.getExpr() and
|
||||
postUpdate = true
|
||||
)
|
||||
}
|
||||
@@ -943,22 +860,20 @@ private predicate collectionStore(Expr src, CollectionExpression ce) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e` is an expression that adds `src` to array `a`.
|
||||
* Holds if there is an expression that adds `src` to array `a`.
|
||||
*
|
||||
* `postUpdate` indicates whether the store targets a post-update node.
|
||||
*/
|
||||
private predicate arrayStore(Expr e, Expr src, Expr a, boolean postUpdate) {
|
||||
private predicate arrayStore(Expr src, Expr a, boolean postUpdate) {
|
||||
// Direct assignment, `a[i] = src`
|
||||
exists(AssignableDefinition def |
|
||||
a = def.getTargetAccess().(ArrayWrite).getQualifier() and
|
||||
src = def.getSource() and
|
||||
e = def.getExpr() and
|
||||
postUpdate = true
|
||||
)
|
||||
or
|
||||
// Array initializer, `new [] { src }`
|
||||
src = a.(ArrayInitializer).getAnElement() and
|
||||
e = a and
|
||||
postUpdate = false
|
||||
or
|
||||
// Member initializer, `new C { Array = { [i] = src } }`
|
||||
@@ -966,7 +881,6 @@ private predicate arrayStore(Expr e, Expr src, Expr a, boolean postUpdate) {
|
||||
mi = a.(ObjectInitializer).getAMemberInitializer() and
|
||||
mi.getLValue() instanceof ArrayAccess and
|
||||
mi.getRValue() = src and
|
||||
e = a and
|
||||
postUpdate = false
|
||||
)
|
||||
}
|
||||
@@ -1141,17 +1055,17 @@ private module Cached {
|
||||
(
|
||||
cfn.getExpr() instanceof Argument
|
||||
or
|
||||
cfn =
|
||||
LocalFlow::getPostUpdateReverseStep(any(ControlFlow::Nodes::ExprNode e |
|
||||
exists(any(SourcePostUpdateNode p).getPreUpdateNode().asExprAtNode(e))
|
||||
))
|
||||
cfn.getExpr() =
|
||||
LocalFlow::getPostUpdateReverseStep(any(SourcePostUpdateNode p)
|
||||
.getPreUpdateNode()
|
||||
.asExpr())
|
||||
) and
|
||||
exprMayHavePostUpdateNode(cfn.getExpr())
|
||||
or
|
||||
exists(Expr e | e = cfn.getExpr() |
|
||||
fieldOrPropertyStore(_, _, _, e, true)
|
||||
fieldOrPropertyStore(_, _, e, true)
|
||||
or
|
||||
arrayStore(_, _, e, true)
|
||||
arrayStore(_, e, true)
|
||||
or
|
||||
// needed for reverse stores; e.g. `x.f1.f2 = y` induces
|
||||
// a store step of `f1` into `x`
|
||||
@@ -1166,7 +1080,7 @@ private module Cached {
|
||||
)
|
||||
)
|
||||
or
|
||||
lambdaCallExpr(_, cfn)
|
||||
lambdaCallExpr(_, _, cfn)
|
||||
} or
|
||||
TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) {
|
||||
sn.getSummarizedCallable() instanceof CallableUsedInSource
|
||||
@@ -1563,35 +1477,15 @@ abstract private class ArgumentNodeImpl extends Node {
|
||||
}
|
||||
|
||||
private module ArgumentNodes {
|
||||
private class ArgumentConfiguration extends ControlFlowReachabilityConfiguration {
|
||||
ArgumentConfiguration() { this = "ArgumentConfiguration" }
|
||||
|
||||
override predicate candidate(
|
||||
Expr e1, Expr e2, ControlFlowElement scope, boolean exactScope, boolean isSuccessor
|
||||
) {
|
||||
e1.(Argument).isArgumentOf(e2, _) and
|
||||
exactScope = false and
|
||||
isSuccessor = true and
|
||||
if e2 instanceof PropertyWrite
|
||||
then
|
||||
exists(AssignableDefinition def |
|
||||
def.getTargetAccess() = e2 and
|
||||
scope = def.getExpr()
|
||||
)
|
||||
else scope = e2
|
||||
}
|
||||
}
|
||||
|
||||
/** A data-flow node that represents an explicit call argument. */
|
||||
class ExplicitArgumentNode extends ArgumentNodeImpl {
|
||||
ExplicitArgumentNode() { this.asExpr() instanceof Argument }
|
||||
|
||||
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
exists(ArgumentConfiguration x, Expr c, Argument arg |
|
||||
exists(Expr c, Argument arg |
|
||||
arg = this.asExpr() and
|
||||
c = call.getExpr() and
|
||||
arg.isArgumentOf(c, pos) and
|
||||
x.hasExprPath(_, this.getControlFlowNode(), _, call.getControlFlowNode())
|
||||
arg.isArgumentOf(c, pos)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1600,7 +1494,7 @@ private module ArgumentNodes {
|
||||
class DelegateSelfArgumentNode extends ArgumentNodeImpl, ExprNode {
|
||||
private DataFlowCall call_;
|
||||
|
||||
DelegateSelfArgumentNode() { lambdaCallExpr(call_, this.getControlFlowNode()) }
|
||||
DelegateSelfArgumentNode() { lambdaCallExpr(call_, this.getExpr(), _) }
|
||||
|
||||
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
call = call_ and
|
||||
@@ -1857,27 +1751,6 @@ private module OutNodes {
|
||||
}
|
||||
}
|
||||
|
||||
class ObjectOrCollectionInitializerConfiguration extends ControlFlowReachabilityConfiguration {
|
||||
ObjectOrCollectionInitializerConfiguration() {
|
||||
this = "ObjectOrCollectionInitializerConfiguration"
|
||||
}
|
||||
|
||||
override predicate candidate(
|
||||
Expr e1, Expr e2, ControlFlowElement scope, boolean exactScope, boolean isSuccessor
|
||||
) {
|
||||
exactScope = false and
|
||||
scope = e1 and
|
||||
isSuccessor = true and
|
||||
exists(ObjectOrCollectionInitializer init | init = e1.(ObjectCreation).getInitializer() |
|
||||
// E.g. `new Dictionary<int, string>{ {0, "a"}, {1, "b"} }`
|
||||
e2 = init.(CollectionInitializer).getAnElementInitializer()
|
||||
or
|
||||
// E.g. `new Dictionary<int, string>() { [0] = "a", [1] = "b" }`
|
||||
e2 = init.(ObjectInitializer).getAMemberInitializer().getLValue()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that reads a value returned by a callable using an
|
||||
* `out` or `ref` parameter.
|
||||
@@ -2236,30 +2109,6 @@ predicate jumpStep(Node pred, Node succ) {
|
||||
succ = pred.(LocalFunctionCreationNode).getAnAccess(false)
|
||||
}
|
||||
|
||||
private class StoreStepConfiguration extends ControlFlowReachabilityConfiguration {
|
||||
StoreStepConfiguration() { this = "StoreStepConfiguration" }
|
||||
|
||||
override predicate candidate(
|
||||
Expr e1, Expr e2, ControlFlowElement scope, boolean exactScope, boolean isSuccessor
|
||||
) {
|
||||
exactScope = false and
|
||||
fieldOrPropertyStore(scope, _, e1, e2, isSuccessor.booleanNot())
|
||||
or
|
||||
exactScope = false and
|
||||
arrayStore(scope, e1, e2, isSuccessor.booleanNot())
|
||||
or
|
||||
exactScope = false and
|
||||
isSuccessor = true and
|
||||
collectionStore(e1, e2) and
|
||||
scope = e2
|
||||
or
|
||||
exactScope = false and
|
||||
isSuccessor = true and
|
||||
isParamsArg(e2, e1, _) and
|
||||
scope = e2
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private ContentSet getResultContent() {
|
||||
result.isProperty(any(SystemThreadingTasksTaskTClass c_).getResultProperty())
|
||||
@@ -2282,21 +2131,17 @@ private predicate recordParameter(RecordType t, Parameter p, string name) {
|
||||
}
|
||||
|
||||
private predicate storeContentStep(Node node1, Content c, Node node2) {
|
||||
exists(StoreStepConfiguration x, ExprNode node, boolean postUpdate |
|
||||
hasNodePath(x, node1, node) and
|
||||
exists(ExprNode node, boolean postUpdate |
|
||||
if postUpdate = true then node = node2.(PostUpdateNode).getPreUpdateNode() else node = node2
|
||||
|
|
||||
arrayStore(_, node1.asExpr(), node.getExpr(), postUpdate) and c instanceof ElementContent
|
||||
arrayStore(node1.asExpr(), node.getExpr(), postUpdate) and c instanceof ElementContent
|
||||
)
|
||||
or
|
||||
exists(StoreStepConfiguration x | hasNodePath(x, node1, node2) |
|
||||
collectionStore(node1.asExpr(), node2.asExpr()) and c instanceof ElementContent
|
||||
)
|
||||
collectionStore(node1.asExpr(), node2.asExpr()) and c instanceof ElementContent
|
||||
or
|
||||
exists(StoreStepConfiguration x, Expr arg, ControlFlow::Node callCfn |
|
||||
x.hasExprPath(arg, node1.(ExprNode).getControlFlowNode(), _, callCfn) and
|
||||
node2 = TParamsArgumentNode(callCfn) and
|
||||
isParamsArg(_, arg, _) and
|
||||
exists(Call call |
|
||||
node2 = TParamsArgumentNode(call.getControlFlowNode()) and
|
||||
isParamsArg(call, node1.asExpr(), _) and
|
||||
c instanceof ElementContent
|
||||
)
|
||||
or
|
||||
@@ -2352,11 +2197,10 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
|
||||
c.isSingleton(cont)
|
||||
)
|
||||
or
|
||||
exists(StoreStepConfiguration x, ExprNode node, boolean postUpdate |
|
||||
hasNodePath(x, node1, node) and
|
||||
exists(ExprNode node, boolean postUpdate |
|
||||
if postUpdate = true then node = node2.(PostUpdateNode).getPreUpdateNode() else node = node2
|
||||
|
|
||||
fieldOrPropertyStore(_, c, node1.asExpr(), node.getExpr(), postUpdate)
|
||||
fieldOrPropertyStore(c, node1.asExpr(), node.getExpr(), postUpdate)
|
||||
)
|
||||
or
|
||||
exists(Expr e |
|
||||
@@ -2378,133 +2222,51 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
|
||||
storeStepDelegateCall(node1, c, node2)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate isAssignExprLValueDescendant(Expr e) {
|
||||
e = any(AssignExpr ae).getLValue()
|
||||
or
|
||||
exists(Expr parent |
|
||||
isAssignExprLValueDescendant(parent) and
|
||||
e = parent.getAChildExpr()
|
||||
)
|
||||
}
|
||||
|
||||
private class ReadStepConfiguration extends ControlFlowReachabilityConfiguration {
|
||||
ReadStepConfiguration() { this = "ReadStepConfiguration" }
|
||||
|
||||
override predicate candidate(
|
||||
Expr e1, Expr e2, ControlFlowElement scope, boolean exactScope, boolean isSuccessor
|
||||
) {
|
||||
exactScope = false and
|
||||
isSuccessor = true and
|
||||
fieldOrPropertyRead(e1, _, e2) and
|
||||
scope = e2
|
||||
or
|
||||
exactScope = false and
|
||||
isSuccessor = true and
|
||||
dynamicPropertyRead(e1, _, e2) and
|
||||
scope = e2
|
||||
or
|
||||
exactScope = false and
|
||||
isSuccessor = true and
|
||||
arrayRead(e1, e2) and
|
||||
scope = e2
|
||||
or
|
||||
exactScope = false and
|
||||
e1 = e2.(AwaitExpr).getExpr() and
|
||||
scope = e2 and
|
||||
isSuccessor = true
|
||||
or
|
||||
exactScope = false and
|
||||
e2 = e1.(TupleExpr).getAnArgument() and
|
||||
scope = e1 and
|
||||
isSuccessor = false
|
||||
}
|
||||
|
||||
override predicate candidateDef(
|
||||
Expr e, AssignableDefinition defTo, ControlFlowElement scope, boolean exactScope,
|
||||
boolean isSuccessor
|
||||
) {
|
||||
exists(ForeachStmt fs |
|
||||
e = fs.getIterableExpr() and
|
||||
defTo.(AssignableDefinitions::LocalVariableDefinition).getDeclaration() =
|
||||
fs.getVariableDeclExpr() and
|
||||
isSuccessor = true
|
||||
|
|
||||
scope = fs and
|
||||
exactScope = true
|
||||
or
|
||||
scope = fs.getIterableExpr() and
|
||||
exactScope = false
|
||||
or
|
||||
scope = fs.getVariableDeclExpr() and
|
||||
exactScope = false
|
||||
)
|
||||
or
|
||||
scope =
|
||||
any(AssignExpr ae |
|
||||
ae = defTo.(AssignableDefinitions::TupleAssignmentDefinition).getAssignment() and
|
||||
isAssignExprLValueDescendant(e.(TupleExpr)) and
|
||||
exactScope = false and
|
||||
isSuccessor = true
|
||||
)
|
||||
or
|
||||
scope =
|
||||
any(TupleExpr te |
|
||||
te.getAnArgument() = defTo.(AssignableDefinitions::LocalVariableDefinition).getDeclaration() and
|
||||
e = te and
|
||||
exactScope = false and
|
||||
isSuccessor = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private predicate readContentStep(Node node1, Content c, Node node2) {
|
||||
exists(ReadStepConfiguration x |
|
||||
hasNodePath(x, node1, node2) and
|
||||
arrayRead(node1.asExpr(), node2.asExpr()) and
|
||||
arrayRead(node1.asExpr(), node2.asExpr()) and
|
||||
c instanceof ElementContent
|
||||
or
|
||||
exists(
|
||||
ForeachStmt fs, Ssa::ExplicitDefinition def,
|
||||
AssignableDefinitions::LocalVariableDefinition defTo
|
||||
|
|
||||
node1.asExpr() = fs.getIterableExpr() and
|
||||
defTo.getDeclaration() = fs.getVariableDeclExpr() and
|
||||
def.getADefinition() = defTo and
|
||||
node2.(SsaDefinitionNode).getDefinition() = def and
|
||||
c instanceof ElementContent
|
||||
)
|
||||
or
|
||||
node1 =
|
||||
any(InstanceParameterAccessPreNode n |
|
||||
n.getUnderlyingControlFlowNode() = node2.(ExprNode).getControlFlowNode() and
|
||||
n.getParameter() = c.(PrimaryConstructorParameterContent).getParameter()
|
||||
) and
|
||||
node2.asExpr() instanceof ParameterRead
|
||||
or
|
||||
// node1 = (..., node2, ...)
|
||||
// node1.ItemX flows to node2
|
||||
exists(TupleExpr te, int i, Expr item |
|
||||
te = node1.asExpr() and
|
||||
not te.isConstruction() and
|
||||
c.(FieldContent).getField() = te.getType().(TupleType).getElement(i).getUnboundDeclaration() and
|
||||
// node1 = (..., item, ...)
|
||||
te.getArgument(i) = item
|
||||
|
|
||||
// item = (..., ..., ...) in node1 = (..., (..., ..., ...), ...)
|
||||
node2.asExpr().(TupleExpr) = item
|
||||
or
|
||||
exists(ForeachStmt fs, Ssa::ExplicitDefinition def |
|
||||
x.hasDefPath(fs.getIterableExpr(), node1.getControlFlowNode(), def.getADefinition(),
|
||||
def.getControlFlowNode()) and
|
||||
node2.(SsaDefinitionNode).getDefinition() = def and
|
||||
c instanceof ElementContent
|
||||
// item = variable in node1 = (..., variable, ...)
|
||||
exists(AssignableDefinitions::TupleAssignmentDefinition tad |
|
||||
node2.(AssignableDefinitionNode).getDefinition() = tad and
|
||||
tad.getLeaf() = item
|
||||
)
|
||||
or
|
||||
node1 =
|
||||
any(InstanceParameterAccessPreNode n |
|
||||
n.getUnderlyingControlFlowNode() = node2.(ExprNode).getControlFlowNode() and
|
||||
n.getParameter() = c.(PrimaryConstructorParameterContent).getParameter()
|
||||
) and
|
||||
node2.asExpr() instanceof ParameterRead
|
||||
or
|
||||
// node1 = (..., node2, ...)
|
||||
// node1.ItemX flows to node2
|
||||
exists(TupleExpr te, int i, Expr item |
|
||||
te = node1.asExpr() and
|
||||
not te.isConstruction() and
|
||||
c.(FieldContent).getField() = te.getType().(TupleType).getElement(i).getUnboundDeclaration() and
|
||||
// node1 = (..., item, ...)
|
||||
te.getArgument(i) = item
|
||||
|
|
||||
// item = (..., ..., ...) in node1 = (..., (..., ..., ...), ...)
|
||||
node2.asExpr().(TupleExpr) = item and
|
||||
hasNodePath(x, node1, node2)
|
||||
or
|
||||
// item = variable in node1 = (..., variable, ...)
|
||||
exists(AssignableDefinitions::TupleAssignmentDefinition tad |
|
||||
node2.(AssignableDefinitionNode).getDefinition() = tad and
|
||||
tad.getLeaf() = item and
|
||||
hasNodePath(x, node1, node2)
|
||||
)
|
||||
or
|
||||
// item = variable in node1 = (..., variable, ...) in a case/is var (..., ...)
|
||||
isPatternExprDescendant(te) and
|
||||
exists(AssignableDefinitions::LocalVariableDefinition lvd |
|
||||
node2.(AssignableDefinitionNode).getDefinition() = lvd and
|
||||
lvd.getDeclaration() = item and
|
||||
hasNodePath(x, node1, node2)
|
||||
)
|
||||
// item = variable in node1 = (..., variable, ...) in a case/is var (..., ...)
|
||||
isPatternExprDescendant(te) and
|
||||
exists(AssignableDefinitions::LocalVariableDefinition lvd |
|
||||
node2.(AssignableDefinitionNode).getDefinition() = lvd and
|
||||
lvd.getDeclaration() = item
|
||||
)
|
||||
)
|
||||
or
|
||||
@@ -2535,14 +2297,12 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
|
||||
c.isSingleton(cont)
|
||||
)
|
||||
or
|
||||
exists(ReadStepConfiguration x | hasNodePath(x, node1, node2) |
|
||||
fieldOrPropertyRead(node1.asExpr(), c, node2.asExpr())
|
||||
or
|
||||
dynamicPropertyRead(node1.asExpr(), c, node2.asExpr())
|
||||
or
|
||||
node2.asExpr().(AwaitExpr).getExpr() = node1.asExpr() and
|
||||
c = getResultContent()
|
||||
)
|
||||
fieldOrPropertyRead(node1.asExpr(), c, node2.asExpr())
|
||||
or
|
||||
dynamicPropertyRead(node1.asExpr(), c, node2.asExpr())
|
||||
or
|
||||
node2.asExpr().(AwaitExpr).getExpr() = node1.asExpr() and
|
||||
c = getResultContent()
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(), c,
|
||||
node2.(FlowSummaryNode).getSummaryNode())
|
||||
@@ -2576,9 +2336,9 @@ predicate clearsContent(Node n, ContentSet c) {
|
||||
c.isSingleton(cont)
|
||||
)
|
||||
or
|
||||
fieldOrPropertyStore(_, c, _, n.asExpr(), true)
|
||||
fieldOrPropertyStore(c, _, n.asExpr(), true)
|
||||
or
|
||||
fieldOrPropertyStore(_, c, _, n.(ObjectInitializerNode).getInitializer(), false)
|
||||
fieldOrPropertyStore(c, _, n.(ObjectInitializerNode).getInitializer(), false)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryClearsContent(n.(FlowSummaryNode).getSummaryNode(), c)
|
||||
or
|
||||
@@ -2817,8 +2577,13 @@ module PostUpdateNodes {
|
||||
|
||||
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
pos.isQualifier() and
|
||||
any(ObjectOrCollectionInitializerConfiguration x)
|
||||
.hasExprPath(_, cfn, _, call.getControlFlowNode())
|
||||
exists(ObjectOrCollectionInitializer init | init = oc.getInitializer() |
|
||||
// E.g. `new Dictionary<int, string>{ {0, "a"}, {1, "b"} }`
|
||||
call.getExpr() = init.(CollectionInitializer).getAnElementInitializer()
|
||||
or
|
||||
// E.g. `new Dictionary<int, string>() { [0] = "a", [1] = "b" }`
|
||||
call.getExpr() = init.(ObjectInitializer).getAMemberInitializer().getLValue()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlowCallable getEnclosingCallableImpl() {
|
||||
@@ -2980,45 +2745,26 @@ private predicate isLocalFunctionCallReceiver(
|
||||
f = receiver.getTarget().getUnboundDeclaration()
|
||||
}
|
||||
|
||||
private class LambdaConfiguration extends ControlFlowReachabilityConfiguration {
|
||||
LambdaConfiguration() { this = "LambdaConfiguration" }
|
||||
|
||||
override predicate candidate(
|
||||
Expr e1, Expr e2, ControlFlowElement scope, boolean exactScope, boolean isSuccessor
|
||||
) {
|
||||
e1 = e2.(DelegateLikeCall).getExpr() and
|
||||
exactScope = false and
|
||||
scope = e2 and
|
||||
isSuccessor = true
|
||||
or
|
||||
e1 = e2.(DelegateCreation).getArgument() and
|
||||
exactScope = false and
|
||||
scope = e2 and
|
||||
isSuccessor = true
|
||||
or
|
||||
isLocalFunctionCallReceiver(e2, e1, _) and
|
||||
exactScope = false and
|
||||
scope = e2 and
|
||||
isSuccessor = true
|
||||
}
|
||||
}
|
||||
|
||||
private predicate lambdaCallExpr(DataFlowCall call, ControlFlow::Node receiver) {
|
||||
exists(LambdaConfiguration x, DelegateLikeCall dc |
|
||||
x.hasExprPath(dc.getExpr(), receiver, dc, call.getControlFlowNode())
|
||||
private predicate lambdaCallExpr(DataFlowCall call, Expr receiver, ControlFlow::Node receiverCfn) {
|
||||
exists(DelegateLikeCall dc |
|
||||
call.(ExplicitDelegateLikeDataFlowCall).getCall() = dc and
|
||||
receiver = dc.getExpr() and
|
||||
receiverCfn = receiver.getControlFlowNode()
|
||||
)
|
||||
or
|
||||
// In local function calls, `F()`, we use the local function access `F`
|
||||
// to represent the receiver. Only needed for flow through captured variables.
|
||||
exists(LambdaConfiguration x, LocalFunctionCall fc |
|
||||
x.hasExprPath(fc.getAChild(), receiver, fc, call.getControlFlowNode())
|
||||
exists(LocalFunctionCall fc |
|
||||
receiver = fc.getAChild() and
|
||||
receiverCfn = receiver.getControlFlowNode() and
|
||||
fc.getControlFlowNode() = call.getControlFlowNode()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `call` is a lambda call where `receiver` is the lambda expression. */
|
||||
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
|
||||
(
|
||||
lambdaCallExpr(call, receiver.(ExprNode).getControlFlowNode()) and
|
||||
lambdaCallExpr(call, receiver.asExpr(), _) and
|
||||
// local function calls can be resolved directly without a flow analysis
|
||||
not call.getControlFlowNode().getAstNode() instanceof LocalFunctionCall
|
||||
or
|
||||
@@ -3028,9 +2774,9 @@ predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
|
||||
}
|
||||
|
||||
private predicate delegateCreationStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(LambdaConfiguration x, DelegateCreation dc |
|
||||
x.hasExprPath(dc.getArgument(), nodeFrom.(ExprNode).getControlFlowNode(), dc,
|
||||
nodeTo.(ExprNode).getControlFlowNode())
|
||||
exists(DelegateCreation dc |
|
||||
dc.getArgument() = nodeFrom.asExpr() and
|
||||
dc = nodeTo.asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -239,7 +239,7 @@ module ModelValidation {
|
||||
)
|
||||
}
|
||||
|
||||
string getIncorrectConstructorSummaryOutput() {
|
||||
private string getIncorrectConstructorSummaryOutput() {
|
||||
exists(string namespace, string type, string name, string output |
|
||||
type = name or
|
||||
type = name + "<" + any(string s)
|
||||
|
||||
@@ -4,7 +4,6 @@ private import FlowSummaryImpl as FlowSummaryImpl
|
||||
private import semmle.code.csharp.Caching
|
||||
private import semmle.code.csharp.dataflow.internal.DataFlowDispatch
|
||||
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
|
||||
private import semmle.code.csharp.dataflow.internal.ControlFlowReachability
|
||||
private import semmle.code.csharp.dispatch.Dispatch
|
||||
private import semmle.code.csharp.commons.ComparisonTest
|
||||
// import `TaintedMember` definitions from other files to avoid potential reevaluation
|
||||
@@ -45,82 +44,58 @@ predicate defaultImplicitTaintRead(DataFlow::Node node, DataFlow::ContentSet c)
|
||||
)
|
||||
}
|
||||
|
||||
private class LocalTaintExprStepConfiguration extends ControlFlowReachabilityConfiguration {
|
||||
LocalTaintExprStepConfiguration() { this = "LocalTaintExprStepConfiguration" }
|
||||
|
||||
override predicate candidate(
|
||||
Expr e1, Expr e2, ControlFlowElement scope, boolean exactScope, boolean isSuccessor
|
||||
) {
|
||||
exactScope = false and
|
||||
isSuccessor = true and
|
||||
(
|
||||
e1 = e2.(ElementAccess).getQualifier() and
|
||||
scope = e2
|
||||
or
|
||||
e1 = e2.(AddExpr).getAnOperand() and
|
||||
scope = e2
|
||||
or
|
||||
// A comparison expression where taint can flow from one of the
|
||||
// operands if the other operand is a constant value.
|
||||
exists(ComparisonTest ct, Expr other |
|
||||
ct.getExpr() = e2 and
|
||||
e1 = ct.getAnArgument() and
|
||||
other = ct.getAnArgument() and
|
||||
other.stripCasts().hasValue() and
|
||||
e1 != other and
|
||||
scope = e2
|
||||
)
|
||||
or
|
||||
e1 = e2.(UnaryLogicalOperation).getAnOperand() and
|
||||
scope = e2
|
||||
or
|
||||
e1 = e2.(BinaryLogicalOperation).getAnOperand() and
|
||||
scope = e2
|
||||
or
|
||||
e1 = e2.(InterpolatedStringExpr).getAChild() and
|
||||
scope = e2
|
||||
or
|
||||
e1 = e2.(InterpolatedStringInsertExpr).getInsert() and
|
||||
scope = e2
|
||||
or
|
||||
e2 =
|
||||
any(OperatorCall oc |
|
||||
oc.getTarget().(ConversionOperator).fromLibrary() and
|
||||
e1 = oc.getAnArgument() and
|
||||
scope = e2
|
||||
)
|
||||
or
|
||||
e1 = e2.(AwaitExpr).getExpr() and
|
||||
scope = e2
|
||||
or
|
||||
// Taint flows from the operand of a cast to the cast expression if the cast is to an interpolated string handler.
|
||||
e2 =
|
||||
any(CastExpr ce |
|
||||
e1 = ce.getExpr() and
|
||||
scope = ce and
|
||||
ce.getTargetType()
|
||||
.(Attributable)
|
||||
.getAnAttribute()
|
||||
.getType()
|
||||
.hasFullyQualifiedName("System.Runtime.CompilerServices",
|
||||
"InterpolatedStringHandlerAttribute")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private ControlFlow::Nodes::ExprNode getALastEvalNode(ControlFlow::Nodes::ExprNode cfn) {
|
||||
exists(OperatorCall oc | any(LocalTaintExprStepConfiguration x).hasExprPath(_, result, oc, cfn) |
|
||||
oc.getTarget() instanceof ImplicitConversionOperator
|
||||
private predicate localTaintExprStep(Expr e1, Expr e2) {
|
||||
e1 = e2.(ElementAccess).getQualifier()
|
||||
or
|
||||
e1 = e2.(AddExpr).getAnOperand()
|
||||
or
|
||||
// A comparison expression where taint can flow from one of the
|
||||
// operands if the other operand is a constant value.
|
||||
exists(ComparisonTest ct, Expr other |
|
||||
ct.getExpr() = e2 and
|
||||
e1 = ct.getAnArgument() and
|
||||
other = ct.getAnArgument() and
|
||||
other.stripCasts().hasValue() and
|
||||
e1 != other
|
||||
)
|
||||
or
|
||||
e1 = e2.(UnaryLogicalOperation).getAnOperand()
|
||||
or
|
||||
e1 = e2.(BinaryLogicalOperation).getAnOperand()
|
||||
or
|
||||
e1 = e2.(InterpolatedStringExpr).getAChild()
|
||||
or
|
||||
e1 = e2.(InterpolatedStringInsertExpr).getInsert()
|
||||
or
|
||||
e2 =
|
||||
any(OperatorCall oc |
|
||||
oc.getTarget().(ConversionOperator).fromLibrary() and
|
||||
e1 = oc.getAnArgument()
|
||||
)
|
||||
or
|
||||
e1 = e2.(AwaitExpr).getExpr()
|
||||
or
|
||||
// Taint flows from the operand of a cast to the cast expression if the cast is to an interpolated string handler.
|
||||
e2 =
|
||||
any(CastExpr ce |
|
||||
e1 = ce.getExpr() and
|
||||
ce.getTargetType()
|
||||
.(Attributable)
|
||||
.getAnAttribute()
|
||||
.getType()
|
||||
.hasFullyQualifiedName("System.Runtime.CompilerServices",
|
||||
"InterpolatedStringHandlerAttribute")
|
||||
)
|
||||
}
|
||||
|
||||
private ControlFlow::Nodes::ExprNode getPostUpdateReverseStep(ControlFlow::Nodes::ExprNode e) {
|
||||
result = getALastEvalNode(e)
|
||||
private Expr getALastEvalNode(OperatorCall oc) {
|
||||
localTaintExprStep(result, oc) and oc.getTarget() instanceof ImplicitConversionOperator
|
||||
}
|
||||
|
||||
private Expr getPostUpdateReverseStep(Expr e) { result = getALastEvalNode(e) }
|
||||
|
||||
private predicate localTaintStepCommon(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
hasNodePath(any(LocalTaintExprStepConfiguration x), nodeFrom, nodeTo)
|
||||
localTaintExprStep(nodeFrom.asExpr(), nodeTo.asExpr())
|
||||
}
|
||||
|
||||
cached
|
||||
@@ -191,12 +166,8 @@ private module Cached {
|
||||
// Allow reverse update flow for implicit conversion operator calls.
|
||||
// This is needed to support flow out of method call arguments, where an implicit conversion is applied
|
||||
// to a call argument.
|
||||
nodeTo.(PostUpdateNode).getPreUpdateNode().(DataFlow::ExprNode).getControlFlowNode() =
|
||||
getPostUpdateReverseStep(nodeFrom
|
||||
.(PostUpdateNode)
|
||||
.getPreUpdateNode()
|
||||
.(DataFlow::ExprNode)
|
||||
.getControlFlowNode())
|
||||
nodeTo.(PostUpdateNode).getPreUpdateNode().asExpr() =
|
||||
getPostUpdateReverseStep(nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr())
|
||||
) and
|
||||
model = ""
|
||||
or
|
||||
|
||||
@@ -1,246 +0,0 @@
|
||||
import csharp
|
||||
|
||||
private class ControlFlowScope extends ControlFlowElement {
|
||||
private boolean exactScope;
|
||||
|
||||
ControlFlowScope() {
|
||||
exists(ControlFlowReachabilityConfiguration c |
|
||||
c.candidate(_, _, this, exactScope, _) or
|
||||
c.candidateDef(_, _, this, exactScope, _)
|
||||
)
|
||||
}
|
||||
|
||||
predicate isExact() { exactScope = true }
|
||||
|
||||
predicate isNonExact() { exactScope = false }
|
||||
}
|
||||
|
||||
private newtype TControlFlowElementOrBasicBlock =
|
||||
TControlFlowElement(ControlFlowElement cfe) or
|
||||
TBasicBlock(ControlFlow::BasicBlock bb)
|
||||
|
||||
class ControlFlowElementOrBasicBlock extends TControlFlowElementOrBasicBlock {
|
||||
ControlFlowElement asControlFlowElement() { this = TControlFlowElement(result) }
|
||||
|
||||
ControlFlow::BasicBlock asBasicBlock() { this = TBasicBlock(result) }
|
||||
|
||||
string toString() {
|
||||
result = this.asControlFlowElement().toString()
|
||||
or
|
||||
result = this.asBasicBlock().toString()
|
||||
}
|
||||
|
||||
Location getLocation() {
|
||||
result = this.asControlFlowElement().getLocation()
|
||||
or
|
||||
result = this.asBasicBlock().getLocation()
|
||||
}
|
||||
}
|
||||
|
||||
private predicate isBasicBlock(ControlFlowElementOrBasicBlock c) { c instanceof TBasicBlock }
|
||||
|
||||
private predicate isNonExactScope(ControlFlowElementOrBasicBlock c) {
|
||||
c.asControlFlowElement().(ControlFlowScope).isNonExact()
|
||||
}
|
||||
|
||||
private predicate step(ControlFlowElementOrBasicBlock pred, ControlFlowElementOrBasicBlock succ) {
|
||||
pred.asBasicBlock().getANode().getAstNode() = succ.asControlFlowElement()
|
||||
or
|
||||
pred.asControlFlowElement() = succ.asControlFlowElement().getAChild()
|
||||
}
|
||||
|
||||
private predicate basicBlockInNonExactScope(
|
||||
ControlFlowElementOrBasicBlock bb, ControlFlowElementOrBasicBlock scope
|
||||
) = doublyBoundedFastTC(step/2, isBasicBlock/1, isNonExactScope/1)(bb, scope)
|
||||
|
||||
pragma[noinline]
|
||||
private ControlFlow::BasicBlock getABasicBlockInScope(ControlFlowScope scope, boolean exactScope) {
|
||||
basicBlockInNonExactScope(TBasicBlock(result), TControlFlowElement(scope)) and
|
||||
exactScope = false
|
||||
or
|
||||
scope.isExact() and
|
||||
result.getANode().getAstNode() = scope and
|
||||
exactScope = true
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper class for determining control-flow reachability for pairs of
|
||||
* elements.
|
||||
*
|
||||
* This is useful when defining for example expression-based data-flow steps in
|
||||
* the presence of control-flow splitting, where a data-flow step should make
|
||||
* sure to stay in the same split.
|
||||
*
|
||||
* For example, in
|
||||
*
|
||||
* ```csharp
|
||||
* if (b)
|
||||
* ....
|
||||
* var x = "foo";
|
||||
* if (b)
|
||||
* ....
|
||||
* ```
|
||||
*
|
||||
* there should only be steps from `[b = true] "foo"` to `[b = true] SSA def(x)`
|
||||
* and `[b = false] "foo"` to `[b = false] SSA def(x)`, and for example not from
|
||||
* `[b = true] "foo"` to `[b = false] SSA def(x)`
|
||||
*/
|
||||
abstract class ControlFlowReachabilityConfiguration extends string {
|
||||
bindingset[this]
|
||||
ControlFlowReachabilityConfiguration() { any() }
|
||||
|
||||
/**
|
||||
* Holds if `e1` and `e2` are expressions for which we want to find a
|
||||
* control-flow path that follows control flow successors (resp.
|
||||
* predecessors, as specified by `isSuccessor`) inside the syntactic scope
|
||||
* `scope`. The Boolean `exactScope` indicates whether a transitive child
|
||||
* of `scope` is allowed (`exactScope = false`).
|
||||
*/
|
||||
predicate candidate(
|
||||
Expr e1, Expr e2, ControlFlowElement scope, boolean exactScope, boolean isSuccessor
|
||||
) {
|
||||
none()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e` and `def` are elements for which we want to find a
|
||||
* control-flow path that follows control flow successors (resp.
|
||||
* predecessors, as specified by `isSuccessor`) inside the syntactic scope
|
||||
* `scope`. The Boolean `exactScope` indicates whether a transitive child
|
||||
* of `scope` is allowed (`exactScope = false`).
|
||||
*/
|
||||
predicate candidateDef(
|
||||
Expr e, AssignableDefinition def, ControlFlowElement scope, boolean exactScope,
|
||||
boolean isSuccessor
|
||||
) {
|
||||
none()
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate reachesBasicBlockExprBase(
|
||||
Expr e1, Expr e2, boolean isSuccessor, ControlFlow::Nodes::ElementNode cfn1, int i,
|
||||
ControlFlow::BasicBlock bb
|
||||
) {
|
||||
this.candidate(e1, e2, _, _, isSuccessor) and
|
||||
cfn1 = e1.getAControlFlowNode() and
|
||||
bb.getNode(i) = cfn1
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate reachesBasicBlockExprRec(
|
||||
Expr e1, Expr e2, boolean isSuccessor, ControlFlow::Nodes::ElementNode cfn1,
|
||||
ControlFlow::BasicBlock bb
|
||||
) {
|
||||
exists(ControlFlow::BasicBlock mid |
|
||||
this.reachesBasicBlockExpr(e1, e2, isSuccessor, cfn1, mid)
|
||||
|
|
||||
isSuccessor = true and
|
||||
bb = mid.getASuccessor()
|
||||
or
|
||||
isSuccessor = false and
|
||||
bb = mid.getAPredecessor()
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate reachesBasicBlockExpr(
|
||||
Expr e1, Expr e2, boolean isSuccessor, ControlFlow::Nodes::ElementNode cfn1,
|
||||
ControlFlow::BasicBlock bb
|
||||
) {
|
||||
this.reachesBasicBlockExprBase(e1, e2, isSuccessor, cfn1, _, bb)
|
||||
or
|
||||
exists(ControlFlowElement scope, boolean exactScope |
|
||||
this.candidate(e1, e2, scope, exactScope, isSuccessor) and
|
||||
this.reachesBasicBlockExprRec(e1, e2, isSuccessor, cfn1, bb) and
|
||||
bb = getABasicBlockInScope(scope, exactScope)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate reachesBasicBlockDefinitionBase(
|
||||
Expr e, AssignableDefinition def, boolean isSuccessor, ControlFlow::Nodes::ElementNode cfn,
|
||||
int i, ControlFlow::BasicBlock bb
|
||||
) {
|
||||
this.candidateDef(e, def, _, _, isSuccessor) and
|
||||
cfn = e.getAControlFlowNode() and
|
||||
bb.getNode(i) = cfn
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate reachesBasicBlockDefinitionRec(
|
||||
Expr e, AssignableDefinition def, boolean isSuccessor, ControlFlow::Nodes::ElementNode cfn,
|
||||
ControlFlow::BasicBlock bb
|
||||
) {
|
||||
exists(ControlFlow::BasicBlock mid |
|
||||
this.reachesBasicBlockDefinition(e, def, isSuccessor, cfn, mid)
|
||||
|
|
||||
isSuccessor = true and
|
||||
bb = mid.getASuccessor()
|
||||
or
|
||||
isSuccessor = false and
|
||||
bb = mid.getAPredecessor()
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate reachesBasicBlockDefinition(
|
||||
Expr e, AssignableDefinition def, boolean isSuccessor, ControlFlow::Nodes::ElementNode cfn,
|
||||
ControlFlow::BasicBlock bb
|
||||
) {
|
||||
this.reachesBasicBlockDefinitionBase(e, def, isSuccessor, cfn, _, bb)
|
||||
or
|
||||
exists(ControlFlowElement scope, boolean exactScope |
|
||||
this.candidateDef(e, def, scope, exactScope, isSuccessor) and
|
||||
this.reachesBasicBlockDefinitionRec(e, def, isSuccessor, cfn, bb) and
|
||||
bb = getABasicBlockInScope(scope, exactScope)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a control-flow path from `cfn1` to `cfn2`, where `cfn1` is a
|
||||
* control-flow node for `e1` and `cfn2` is a control-flow node for `e2`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate hasExprPath(Expr e1, ControlFlow::Node cfn1, Expr e2, ControlFlow::Node cfn2) {
|
||||
exists(ControlFlow::BasicBlock bb, boolean isSuccessor, int i, int j |
|
||||
this.reachesBasicBlockExprBase(e1, e2, isSuccessor, cfn1, i, bb) and
|
||||
cfn2 = bb.getNode(j) and
|
||||
cfn2 = e2.getAControlFlowNode()
|
||||
|
|
||||
isSuccessor = true and j >= i
|
||||
or
|
||||
isSuccessor = false and i >= j
|
||||
)
|
||||
or
|
||||
exists(ControlFlow::BasicBlock bb |
|
||||
this.reachesBasicBlockExprRec(e1, e2, _, cfn1, bb) and
|
||||
cfn2 = bb.getANode() and
|
||||
cfn2 = e2.getAControlFlowNode()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a control-flow path from `cfn` to `cfnDef`, where `cfn` is a
|
||||
* control-flow node for `e` and `cfnDef` is a control-flow node for `def`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate hasDefPath(
|
||||
Expr e, ControlFlow::Node cfn, AssignableDefinition def, ControlFlow::Node cfnDef
|
||||
) {
|
||||
exists(ControlFlow::BasicBlock bb, boolean isSuccessor, int i, int j |
|
||||
this.reachesBasicBlockDefinitionBase(e, def, isSuccessor, cfn, i, bb) and
|
||||
cfnDef = bb.getNode(j) and
|
||||
def.getExpr().getAControlFlowNode() = cfnDef
|
||||
|
|
||||
isSuccessor = true and j >= i
|
||||
or
|
||||
isSuccessor = false and i >= j
|
||||
)
|
||||
or
|
||||
exists(ControlFlow::BasicBlock bb |
|
||||
this.reachesBasicBlockDefinitionRec(e, def, _, cfn, bb) and
|
||||
def.getExpr().getAControlFlowNode() = cfnDef and
|
||||
cfnDef = bb.getANode()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -8,26 +8,14 @@ private module Impl {
|
||||
private import ConstantUtils
|
||||
private import SsaReadPositionCommon
|
||||
private import semmle.code.csharp.controlflow.Guards as G
|
||||
private import ControlFlowReachability
|
||||
|
||||
private class ExprNode = ControlFlow::Nodes::ExprNode;
|
||||
|
||||
private class ExprChildReachability extends ControlFlowReachabilityConfiguration {
|
||||
ExprChildReachability() { this = "ExprChildReachability" }
|
||||
|
||||
override predicate candidate(
|
||||
Expr e1, Expr e2, ControlFlowElement scope, boolean exactScope, boolean isSuccessor
|
||||
) {
|
||||
e2 = e1.getAChild() and
|
||||
scope = e1 and
|
||||
exactScope = false and
|
||||
isSuccessor in [false, true]
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if `parent` having child `child` implies `parentNode` having child `childNode`. */
|
||||
predicate hasChild(Expr parent, Expr child, ExprNode parentNode, ExprNode childNode) {
|
||||
any(ExprChildReachability x).hasExprPath(parent, parentNode, child, childNode)
|
||||
parent.getAChild() = child and
|
||||
parentNode = parent.getControlFlowNode() and
|
||||
childNode = child.getControlFlowNode()
|
||||
}
|
||||
|
||||
/** Holds if SSA definition `def` equals `e + delta`. */
|
||||
|
||||
@@ -52,9 +52,17 @@ private class HtmlSanitizer extends Sanitizer {
|
||||
}
|
||||
|
||||
/**
|
||||
* An argument to a call to a method on a logger class.
|
||||
* An argument to a call to a method on a logger class, excluding extension methods
|
||||
* with source code which are analyzed interprocedurally.
|
||||
*/
|
||||
private class LogForgingLogMessageSink extends Sink, LogMessageSink { }
|
||||
private class LogForgingLogMessageSink extends Sink, LogMessageSink {
|
||||
LogForgingLogMessageSink() {
|
||||
not exists(ExtensionMethodCall mc |
|
||||
this.getExpr() = mc.getAnArgument() and
|
||||
mc.getTarget().fromSource()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An argument to a call to a method on a trace class.
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
## 1.6.4
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.6.3
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* allows for a cross-site scripting vulnerability.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 6.1
|
||||
* @security-severity 7.8
|
||||
* @precision high
|
||||
* @id cs/web/xss
|
||||
* @tags security
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* insertion of forged log entries by a malicious user.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 7.8
|
||||
* @security-severity 6.1
|
||||
* @precision high
|
||||
* @id cs/log-forging
|
||||
* @tags security
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: queryMetadata
|
||||
---
|
||||
* The `@security-severity` metadata of `cs/log-forging` has been reduced from 7.8 (high) to 6.1 (medium).
|
||||
* The `@security-severity` metadata of `cs/web/xss` has been increased from 6.1 (medium) to 7.8 (high).
|
||||
3
csharp/ql/src/change-notes/released/1.6.4.md
Normal file
3
csharp/ql/src/change-notes/released/1.6.4.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 1.6.4
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 1.6.3
|
||||
lastReleaseVersion: 1.6.4
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/csharp-queries
|
||||
version: 1.6.4-dev
|
||||
version: 1.6.5-dev
|
||||
groups:
|
||||
- csharp
|
||||
- queries
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
| CSharp7.cs:31:16:31:16 | access to parameter i | CSharp7.cs:31:16:31:20 | ... > ... |
|
||||
| CSharp7.cs:31:16:31:16 | access to parameter i | CSharp7.cs:31:24:31:24 | access to parameter i |
|
||||
| CSharp7.cs:31:24:31:24 | access to parameter i | CSharp7.cs:31:16:31:59 | ... ? ... : ... |
|
||||
| CSharp7.cs:31:28:31:59 | throw ... | CSharp7.cs:31:16:31:59 | ... ? ... : ... |
|
||||
| CSharp7.cs:35:7:35:18 | this | CSharp7.cs:35:7:35:18 | this access |
|
||||
| CSharp7.cs:39:9:39:9 | access to parameter x | CSharp7.cs:39:9:39:21 | SSA def(x) |
|
||||
| CSharp7.cs:39:13:39:21 | "tainted" | CSharp7.cs:39:9:39:9 | access to parameter x |
|
||||
@@ -253,6 +254,7 @@
|
||||
| CSharp7.cs:233:13:233:13 | access to local variable o | CSharp7.cs:235:13:235:42 | [input] SSA phi read(o) |
|
||||
| CSharp7.cs:233:13:233:13 | access to local variable o | CSharp7.cs:237:18:237:18 | access to local variable o |
|
||||
| CSharp7.cs:233:13:233:23 | [false] ... is ... | CSharp7.cs:233:13:233:33 | [false] ... && ... |
|
||||
| CSharp7.cs:233:13:233:23 | [false] ... is ... | CSharp7.cs:233:13:233:33 | [true] ... && ... |
|
||||
| CSharp7.cs:233:13:233:23 | [true] ... is ... | CSharp7.cs:233:13:233:33 | [false] ... && ... |
|
||||
| CSharp7.cs:233:13:233:23 | [true] ... is ... | CSharp7.cs:233:13:233:33 | [true] ... && ... |
|
||||
| CSharp7.cs:233:18:233:23 | Int32 i1 | CSharp7.cs:233:18:233:23 | SSA def(i1) |
|
||||
@@ -338,6 +340,8 @@
|
||||
| CSharp7.cs:297:35:297:35 | access to local variable x | CSharp7.cs:297:40:297:44 | Int32 y |
|
||||
| CSharp7.cs:297:35:297:35 | access to local variable x | CSharp7.cs:297:49:297:49 | access to local variable x |
|
||||
| CSharp7.cs:297:35:297:44 | [false] ... is ... | CSharp7.cs:297:25:297:44 | [false] ... && ... |
|
||||
| CSharp7.cs:297:35:297:44 | [false] ... is ... | CSharp7.cs:297:25:297:44 | [true] ... && ... |
|
||||
| CSharp7.cs:297:35:297:44 | [true] ... is ... | CSharp7.cs:297:25:297:44 | [false] ... && ... |
|
||||
| CSharp7.cs:297:35:297:44 | [true] ... is ... | CSharp7.cs:297:25:297:44 | [true] ... && ... |
|
||||
| CSharp7.cs:297:40:297:44 | Int32 y | CSharp7.cs:297:40:297:44 | SSA def(y) |
|
||||
| CSharp7.cs:297:40:297:44 | SSA def(y) | CSharp7.cs:299:31:299:31 | access to local variable y |
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
| ExactCallable.cs:15:25:15:35 | Run`2 | ExactCallable.cs:172:21:172:33 | MethodWithOut |
|
||||
| ExactCallable.cs:15:25:15:35 | Run`2 | ExactCallable.cs:177:21:177:34 | MethodWithOut2 |
|
||||
| ExactCallable.cs:182:21:182:22 | M1 | ExactCallable.cs:187:21:187:22 | M2 |
|
||||
| TypeFlow.cs:3:7:3:14 | <object initializer> | TypeFlow.cs:22:20:22:22 | set_Prop |
|
||||
| TypeFlow.cs:5:5:5:12 | TypeFlow | TypeFlow.cs:24:10:24:12 | Run |
|
||||
| TypeFlow.cs:24:10:24:12 | Run | TypeFlow.cs:12:29:12:34 | Method |
|
||||
| TypeFlow.cs:24:10:24:12 | Run | TypeFlow.cs:17:30:17:35 | Method |
|
||||
|
||||
@@ -56,11 +56,11 @@ gvn
|
||||
| StructuralComparison.cs:3:14:3:18 | this access | (kind:Expr(12),false,Class) |
|
||||
| StructuralComparison.cs:3:14:3:18 | {...} | (kind:Stmt(1)) |
|
||||
| StructuralComparison.cs:5:26:5:26 | access to field x | (kind:Expr(16),true,x) |
|
||||
| StructuralComparison.cs:5:26:5:26 | this access | (kind:Expr(12)) |
|
||||
| StructuralComparison.cs:5:26:5:26 | this access | (kind:Expr(12),false,Class) |
|
||||
| StructuralComparison.cs:5:26:5:30 | ... = ... | ((kind:Expr(16),true,x) :: (0 :: (kind:Expr(63)))) |
|
||||
| StructuralComparison.cs:5:30:5:30 | 0 | 0 |
|
||||
| StructuralComparison.cs:6:26:6:26 | access to field y | (kind:Expr(16),true,y) |
|
||||
| StructuralComparison.cs:6:26:6:26 | this access | (kind:Expr(12)) |
|
||||
| StructuralComparison.cs:6:26:6:26 | this access | (kind:Expr(12),false,Class) |
|
||||
| StructuralComparison.cs:6:26:6:30 | ... = ... | ((kind:Expr(16),true,y) :: (1 :: (kind:Expr(63)))) |
|
||||
| StructuralComparison.cs:6:30:6:30 | 1 | 1 |
|
||||
| StructuralComparison.cs:8:24:8:24 | 0 | 0 |
|
||||
|
||||
@@ -33,6 +33,11 @@ public class LogForgingHandler : IHttpHandler
|
||||
Microsoft.Extensions.Logging.ILogger logger2 = null;
|
||||
// BAD: Logged as-is
|
||||
logger2.LogError(username); // $ Alert
|
||||
|
||||
// GOOD: uses safe extension method that sanitizes internally
|
||||
logger.WarnSafe(username + " logged in");
|
||||
// BAD: uses unsafe extension method that does not sanitize
|
||||
logger.WarnUnsafe(username + " logged in");
|
||||
}
|
||||
|
||||
public bool IsReusable
|
||||
@@ -43,3 +48,16 @@ public class LogForgingHandler : IHttpHandler
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class UserLoggerExtensions
|
||||
{
|
||||
public static void WarnSafe(this ILogger logger, string message)
|
||||
{
|
||||
logger.Warn(message.ReplaceLineEndings(""));
|
||||
}
|
||||
|
||||
public static void WarnUnsafe(this ILogger logger, string message)
|
||||
{
|
||||
logger.Warn(message); // $ Alert
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,18 @@
|
||||
| LogForging.cs:21:21:21:43 | ... + ... | LogForging.cs:18:27:18:49 | access to property QueryString : NameValueCollection | LogForging.cs:21:21:21:43 | ... + ... | This log entry depends on a $@. | LogForging.cs:18:27:18:49 | access to property QueryString | user-provided value |
|
||||
| LogForging.cs:31:50:31:72 | ... + ... | LogForging.cs:18:27:18:49 | access to property QueryString : NameValueCollection | LogForging.cs:31:50:31:72 | ... + ... | This log entry depends on a $@. | LogForging.cs:18:27:18:49 | access to property QueryString | user-provided value |
|
||||
| LogForging.cs:35:26:35:33 | access to local variable username | LogForging.cs:18:27:18:49 | access to property QueryString : NameValueCollection | LogForging.cs:35:26:35:33 | access to local variable username | This log entry depends on a $@. | LogForging.cs:18:27:18:49 | access to property QueryString | user-provided value |
|
||||
| LogForging.cs:61:21:61:27 | access to parameter message | LogForging.cs:18:27:18:49 | access to property QueryString : NameValueCollection | LogForging.cs:61:21:61:27 | access to parameter message | This log entry depends on a $@. | LogForging.cs:18:27:18:49 | access to property QueryString | user-provided value |
|
||||
| LogForgingAsp.cs:17:21:17:43 | ... + ... | LogForgingAsp.cs:13:32:13:39 | username : String | LogForgingAsp.cs:17:21:17:43 | ... + ... | This log entry depends on a $@. | LogForgingAsp.cs:13:32:13:39 | username | user-provided value |
|
||||
edges
|
||||
| LogForging.cs:18:16:18:23 | access to local variable username : String | LogForging.cs:21:21:21:43 | ... + ... | provenance | |
|
||||
| LogForging.cs:18:16:18:23 | access to local variable username : String | LogForging.cs:31:50:31:72 | ... + ... | provenance | |
|
||||
| LogForging.cs:18:16:18:23 | access to local variable username : String | LogForging.cs:35:26:35:33 | access to local variable username | provenance | |
|
||||
| LogForging.cs:18:16:18:23 | access to local variable username : String | LogForging.cs:40:27:40:49 | ... + ... : String | provenance | |
|
||||
| LogForging.cs:18:27:18:49 | access to property QueryString : NameValueCollection | LogForging.cs:18:16:18:23 | access to local variable username : String | provenance | |
|
||||
| LogForging.cs:18:27:18:49 | access to property QueryString : NameValueCollection | LogForging.cs:18:27:18:61 | access to indexer : String | provenance | MaD:1 |
|
||||
| LogForging.cs:18:27:18:61 | access to indexer : String | LogForging.cs:18:16:18:23 | access to local variable username : String | provenance | |
|
||||
| LogForging.cs:40:27:40:49 | ... + ... : String | LogForging.cs:59:63:59:69 | message : String | provenance | |
|
||||
| LogForging.cs:59:63:59:69 | message : String | LogForging.cs:61:21:61:27 | access to parameter message | provenance | |
|
||||
| LogForgingAsp.cs:13:32:13:39 | username : String | LogForgingAsp.cs:17:21:17:43 | ... + ... | provenance | |
|
||||
models
|
||||
| 1 | Summary: System.Collections.Specialized; NameValueCollection; false; get_Item; (System.String); ; Argument[this]; ReturnValue; taint; df-generated |
|
||||
@@ -20,6 +24,9 @@ nodes
|
||||
| LogForging.cs:21:21:21:43 | ... + ... | semmle.label | ... + ... |
|
||||
| LogForging.cs:31:50:31:72 | ... + ... | semmle.label | ... + ... |
|
||||
| LogForging.cs:35:26:35:33 | access to local variable username | semmle.label | access to local variable username |
|
||||
| LogForging.cs:40:27:40:49 | ... + ... : String | semmle.label | ... + ... : String |
|
||||
| LogForging.cs:59:63:59:69 | message : String | semmle.label | message : String |
|
||||
| LogForging.cs:61:21:61:27 | access to parameter message | semmle.label | access to parameter message |
|
||||
| LogForgingAsp.cs:13:32:13:39 | username : String | semmle.label | username : String |
|
||||
| LogForgingAsp.cs:17:21:17:43 | ... + ... | semmle.label | ... + ... |
|
||||
subpaths
|
||||
|
||||
@@ -406,7 +406,7 @@ Adds a new taint source. Most taint-tracking queries will use the new source.
|
||||
|
||||
- **type**: Name of a type from which to evaluate **path**.
|
||||
- **path**: Access path leading to the source.
|
||||
- **kind**: Kind of source to add. Currently only **remote** is used.
|
||||
- **kind**: Kind of source to add. See the section on source kinds for a list of supported kinds.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -553,7 +553,16 @@ Kinds
|
||||
Source kinds
|
||||
~~~~~~~~~~~~
|
||||
|
||||
See documentation below for :ref:`Threat models <threat-models-javascript>`.
|
||||
- **remote**: A general source of remote flow.
|
||||
- **browser**: A source in the browser environment that does not fit a more specific browser kind.
|
||||
- **browser-url-query**: A source derived from the query parameters of the browser URL, such as ``location.search``.
|
||||
- **browser-url-fragment**: A source derived from the fragment part of the browser URL, such as ``location.hash``.
|
||||
- **browser-url-path**: A source derived from the pathname of the browser URL, such as ``location.pathname``.
|
||||
- **browser-url**: A source derived from the browser URL, where the untrusted part is prefixed by trusted data such as the scheme and hostname.
|
||||
- **browser-window-name**: A source derived from the window name, such as ``window.name``.
|
||||
- **browser-message-event**: A source derived from cross-window message passing, such as ``event`` in ``window.onmessage = event => {...}``.
|
||||
|
||||
See also :ref:`Threat models <threat-models-javascript>`.
|
||||
|
||||
Sink kinds
|
||||
~~~~~~~~~~
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
.. _codeql-cli-2.24.3:
|
||||
|
||||
==========================
|
||||
CodeQL 2.24.3 (2026-03-05)
|
||||
==========================
|
||||
|
||||
.. contents:: Contents
|
||||
:depth: 2
|
||||
:local:
|
||||
:backlinks: none
|
||||
|
||||
This is an overview of changes in the CodeQL CLI and relevant CodeQL query and library packs. For additional updates on changes to the CodeQL code scanning experience, check out the `code scanning section on the GitHub blog <https://github.blog/tag/code-scanning/>`__, `relevant GitHub Changelog updates <https://github.blog/changelog/label/application-security/>`__, `changes in the CodeQL extension for Visual Studio Code <https://marketplace.visualstudio.com/items/GitHub.vscode-codeql/changelog>`__, and the `CodeQL Action changelog <https://github.com/github/codeql-action/blob/main/CHANGELOG.md>`__.
|
||||
|
||||
Security Coverage
|
||||
-----------------
|
||||
|
||||
CodeQL 2.24.3 runs a total of 491 security queries when configured with the Default suite (covering 166 CWE). The Extended suite enables an additional 135 queries (covering 35 more CWE).
|
||||
|
||||
CodeQL CLI
|
||||
----------
|
||||
|
||||
Bug Fixes
|
||||
~~~~~~~~~
|
||||
|
||||
* Fixed a race condition that could cause flaky failures in overlay CodeQL tests. Test extraction now skips :code:`*.testproj` directories by name, preventing interference from concurrently cleaned-up test databases.
|
||||
* Fixed spurious "OOPS" warnings that could appear in help output for commands using mutually exclusive option groups, such as :code:`codeql query run`.
|
||||
|
||||
Query Packs
|
||||
-----------
|
||||
|
||||
Minor Analysis Improvements
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Java/Kotlin
|
||||
"""""""""""
|
||||
|
||||
* The Java extractor and QL libraries now support Java 26.
|
||||
* Java analysis now selects the Java version to use informed by Maven POM files across all project modules. It also tries to use Java 17 or higher for all Maven projects if possible, for improved build compatibility.
|
||||
|
||||
Rust
|
||||
""""
|
||||
|
||||
* The macro resolution metric has been removed from :code:`rust/diagnostic/database-quality`. This metric was found to be an unreliable indicator of database quality in many cases, leading to false alarms on the tool status page.
|
||||
|
||||
Language Libraries
|
||||
------------------
|
||||
|
||||
Bug Fixes
|
||||
~~~~~~~~~
|
||||
|
||||
C/C++
|
||||
"""""
|
||||
|
||||
* The :code:`allowInterproceduralFlow` predicate of must-flow data flow configurations now correctly handles direct recursion.
|
||||
|
||||
C#
|
||||
""
|
||||
|
||||
* Fixed an issue where the body of a partial member could be extracted twice. When both a *defining* and an *implementing* declaration exist, only the *implementing* declaration is now extracted.
|
||||
|
||||
Breaking Changes
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
C/C++
|
||||
"""""
|
||||
|
||||
* CodeQL version 2.24.2 accidentally introduced a syntactical breaking change to :code:`BarrierGuard<...>::getAnIndirectBarrierNode` and :code:`InstructionBarrierGuard<...>::getAnIndirectBarrierNode`. These breaking changes have now been reverted so that the original code compiles again.
|
||||
* :code:`MustFlow`, the inter-procedural must-flow data flow analysis library, has been re-worked to use parameterized modules. Like in the case of data flow and taint tracking, instead of extending the :code:`MustFlowConfiguration` class, the user should now implement a module with the :code:`MustFlow::ConfigSig` signature, and instantiate the :code:`MustFlow::Global` parameterized module with the implemented module.
|
||||
|
||||
Python
|
||||
""""""
|
||||
|
||||
* The :code:`Metrics` library no longer contains code that depends on the points-to analysis. The removed functionality has instead been moved to the :code:`LegacyPointsTo` module, to classes like :code:`ModuleMetricsWithPointsTo` etc. If you depend on any of these classes, you must now remember to import :code:`LegacyPointsTo`, and use the appropriate types in order to use the points-to-based functionality.
|
||||
|
||||
Major Analysis Improvements
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Python
|
||||
""""""
|
||||
|
||||
* The CodeQL Python libraries have been updated to be compatible with overlay evaluation. This should result in a significant speedup on analyses for which a base database already exists. Note that it may be necessary to add :code:`overlay[local?] module;` to user-managed libraries that extend classes that are now marked as :code:`overlay[local]`.
|
||||
|
||||
Minor Analysis Improvements
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
C/C++
|
||||
"""""
|
||||
|
||||
* Refactored the "Year field changed using an arithmetic operation without checking for leap year" query (:code:`cpp/leap-year/unchecked-after-arithmetic-year-modification`) to address large numbers of false positive results.
|
||||
|
||||
C#
|
||||
""
|
||||
|
||||
* C# 14: Added support for partial events.
|
||||
* C# 14: Added support for the :code:`field` keyword in properties.
|
||||
|
||||
Java/Kotlin
|
||||
"""""""""""
|
||||
|
||||
* Some modelling which previously only worked for Java EE packages beginning with "javax" will now also work for Java EE packages beginning with "jakarta" as well. This may lead to some alert changes.
|
||||
|
||||
JavaScript/TypeScript
|
||||
"""""""""""""""""""""
|
||||
|
||||
* Added support for React components wrapped by :code:`observer` from :code:`mobx-react` and :code:`mobx-react-lite`.
|
||||
|
||||
Python
|
||||
""""""
|
||||
|
||||
* Added new full SSRF sanitization barrier from the new AntiSSRF library.
|
||||
* When a guard such as :code:`isSafe(x)` is defined, we now also automatically handle :code:`isSafe(x) == true` and :code:`isSafe(x) != false`.
|
||||
|
||||
Ruby
|
||||
""""
|
||||
|
||||
* We now track taint flow through :code:`Shellwords.escape` and :code:`Shellwords.shellescape` for all queries except command injection, for which they are sanitizers.
|
||||
|
||||
Rust
|
||||
""""
|
||||
|
||||
* Added support for neutral models (:code:`extensible: neutralModel`) to control where generated source, sink and flow summary models apply.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user