Compare commits

..

17 Commits

Author SHA1 Message Date
Max Schaefer
52eb7c440b Revert "JS: Recognize DomSanitizer from @angular/core"
This reverts commit ff1d0cc4c7.
2022-01-12 12:26:52 +00:00
Henry Mercer
46686834ff Remove NoSQL sinks since September 2018 2022-01-12 12:26:52 +00:00
Esben Sparre Andreasen
b0d42e03ef Remove additional Xss sinks 2022-01-12 12:26:52 +00:00
Esben Sparre Andreasen
afeb7d1a4a Remove additional SQL sinks 2022-01-12 12:26:52 +00:00
Esben Sparre Andreasen
6cf15448ae Remove additional path-injection sinks 2022-01-12 12:26:52 +00:00
Esben Sparre Andreasen
4fe17ef60f Add benjamin-button.md 2022-01-12 12:26:52 +00:00
Esben Sparre Andreasen
0198bd5d97 Remove pseudo-properties 2022-01-12 12:26:52 +00:00
Esben Sparre Andreasen
0a553d1992 Remove 2020 sinks from SqlInjection.ql 2022-01-12 12:26:51 +00:00
Esben Sparre Andreasen
042bef069e Remove 2020 sinks from Xss.ql 2022-01-12 12:26:51 +00:00
Esben Sparre Andreasen
4731d4fe41 Remove 2020 sinks from TaintedPath.ql 2022-01-12 12:26:51 +00:00
Henry Mercer
5a692f2293 JS: Update featurization for absent features optimization
Absent features are now represented implicitly by the absence of a row
in the `tokenFeatures` relation, rather than explicitly by an empty
string. This leads to improved runtime performance. To enable this
implicit representation, we pass the set of supported token features to
the `scoreEndpoints` HOP. Requires CodeQL CLI v2.7.4.
2022-01-12 12:26:51 +00:00
Henry Mercer
7c5dce4799 Remove CodeToFeatures AST library 2022-01-12 12:24:16 +00:00
Henry Mercer
af34c81441 Make function body features library independent of CodeToFeatures AST 2022-01-12 12:24:16 +00:00
Henry Mercer
e6b7b91379 Move function body features into their own file 2022-01-12 12:24:16 +00:00
Henry Mercer
dd009d81a4 Migrate representative entity -> representative function 2022-01-12 12:24:16 +00:00
Henry Mercer
2edfb24c70 Remove CodeToFeatures AST consistency checks
We no longer use the `CodeToFeatures` AST, therefore these checks are
defunct.
2022-01-12 12:24:16 +00:00
Henry Mercer
6900323ced Extract body tokens from the JS AST, not the CodeToFeatures AST 2022-01-12 12:24:15 +00:00
1587 changed files with 85298 additions and 307390 deletions

View File

@@ -6,7 +6,6 @@
"*/ql/examples/qlpack.yml",
"cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/tainted/qlpack.yml",
"javascript/ql/experimental/adaptivethreatmodeling/lib/qlpack.yml",
"javascript/ql/experimental/adaptivethreatmodeling/modelbuilding/qlpack.yml",
"javascript/ql/experimental/adaptivethreatmodeling/src/qlpack.yml",
"csharp/ql/campaigns/Solorigate/lib/qlpack.yml",
"csharp/ql/campaigns/Solorigate/src/qlpack.yml",

7
.gitattributes vendored
View File

@@ -50,9 +50,4 @@
*.pdb -text
java/ql/test/stubs/**/*.java linguist-generated=true
java/ql/test/experimental/stubs/**/*.java linguist-generated=true
# Generated test files - these are synced from the standard JavaScript libraries using
# `javascript/ql/experimental/adaptivethreatmodeling/test/update_endpoint_test_files.py`.
javascript/ql/experimental/adaptivethreatmodeling/test/endpoint_large_scale/autogenerated/**/*.js linguist-generated=true -merge
javascript/ql/experimental/adaptivethreatmodeling/test/endpoint_large_scale/autogenerated/**/*.ts linguist-generated=true -merge
java/ql/test/experimental/stubs/**/*.java linguist-generated=true

View File

@@ -27,11 +27,6 @@ jobs:
pull-requests: read
steps:
- name: Setup dotnet
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.101
- name: Checkout repository
uses: actions/checkout@v2
@@ -56,7 +51,7 @@ jobs:
# uses a compiled language
- run: |
dotnet build csharp /p:UseSharedCompilation=false
dotnet build csharp
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@main

View File

@@ -1,67 +0,0 @@
name: JS ML-powered queries tests
on:
push:
paths:
- "javascript/ql/experimental/adaptivethreatmodeling/**"
- .github/workflows/js-ml-tests.yml
branches:
- main
- "rc/*"
pull_request:
paths:
- "javascript/ql/experimental/adaptivethreatmodeling/**"
- .github/workflows/js-ml-tests.yml
defaults:
run:
working-directory: javascript/ql/experimental/adaptivethreatmodeling
jobs:
qlformat:
name: Check QL formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: ./.github/actions/fetch-codeql
- name: Check QL formatting
run: |
find . "(" -name "*.ql" -or -name "*.qll" ")" -print0 | \
xargs -0 codeql query format --check-only
qlcompile:
name: Check QL compilation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: ./.github/actions/fetch-codeql
- name: Check QL compilation
run: |
codeql query compile \
--check-only \
--ram 5120 \
--additional-packs "${{ github.workspace }}" \
--threads=0 \
-- \
lib modelbuilding src
qltest:
name: Run QL tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: ./.github/actions/fetch-codeql
- name: Run QL tests
run: |
codeql test run \
--threads=0 \
--ram 5120 \
--additional-packs "${{ github.workspace }}" \
-- \
test

View File

@@ -1,103 +0,0 @@
name: Models as Data - Diff
on:
workflow_dispatch:
inputs:
projects:
description: "The projects to generate models for"
required: true
default: '["netty/netty"]'
pull_request:
branches:
- main
paths:
- "java/ql/src/utils/model-generator/**/*.*"
- ".github/workflows/mad_modelDiff.yml"
permissions:
contents: read
jobs:
model-diff:
name: Model Difference
runs-on: ubuntu-latest
if: github.repository == 'github/codeql'
strategy:
matrix:
slug: ${{fromJson(github.event.inputs.projects || '["apache/commons-codec", "apache/commons-io", "apache/commons-beanutils", "apache/commons-logging", "apache/commons-fileupload", "apache/commons-lang", "apache/commons-validator", "apache/commons-csv", "apache/dubbo"]' )}}
steps:
- name: Clone github/codeql from PR
uses: actions/checkout@v2
if: github.event.pull_request
with:
path: codeql-pr
- name: Clone github/codeql from main
uses: actions/checkout@v2
with:
path: codeql-main
ref: main
- uses: ./codeql-main/.github/actions/fetch-codeql
- name: Download database
env:
SLUG: ${{ matrix.slug }}
run: |
set -x
mkdir lib-dbs
SHORTNAME=${SLUG//[^a-zA-Z0-9_]/}
projectId=`curl -s https://lgtm.com/api/v1.0/projects/g/${SLUG} | jq .id`
curl -L "https://lgtm.com/api/v1.0/snapshots/$projectId/java" -o "$SHORTNAME.zip"
unzip -q -d "${SHORTNAME}-db" "${SHORTNAME}.zip"
mkdir "lib-dbs/$SHORTNAME/"
mv "${SHORTNAME}-db/"$(ls -1 "${SHORTNAME}"-db)/* "lib-dbs/${SHORTNAME}/"
- name: Generate Models (PR and main)
run: |
set -x
mkdir tmp-models
MODELS=`pwd`/tmp-models
DATABASES=`pwd`/lib-dbs
analyzeDatabaseWithCheckout() {
QL_VARIANT=$1
DATABASE=$2
cd codeql-$QL_VARIANT
SHORTNAME=`basename $DATABASE`
python java/ql/src/utils/model-generator/GenerateFlowModel.py $DATABASE $MODELS/${SHORTNAME}.qll
mv $MODELS/${SHORTNAME}.qll $MODELS/${SHORTNAME}Generated_${QL_VARIANT}.qll
cd ..
}
for d in $DATABASES/*/ ; do
ls -1 "$d"
analyzeDatabaseWithCheckout "main" $d
if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]
then
analyzeDatabaseWithCheckout "pr" $d
fi
done
- name: Install diff2html
if: github.event.pull_request
run: |
npm install -g diff2html-cli
- name: Generate Model Diff
if: github.event.pull_request
run: |
set -x
MODELS=`pwd`/tmp-models
ls -1 tmp-models/
for m in $MODELS/*_main.qll ; do
t="${m/main/"pr"}"
basename=`basename $m`
name="diff_${basename/_main.qll/""}"
(diff -w -u $m $t | diff2html -i stdin -F $MODELS/$name.html) || true
done
- uses: actions/upload-artifact@v2
with:
name: models
path: tmp-models/*.qll
retention-days: 20
- uses: actions/upload-artifact@v2
with:
name: diffs
path: tmp-models/*.html
retention-days: 20

View File

@@ -1,62 +0,0 @@
name: Regenerate framework models
on:
workflow_dispatch:
schedule:
- cron: "30 2 * * *"
pull_request:
branches:
- main
paths:
- ".github/workflows/mad_regenerate-models.yml"
jobs:
regenerate-models:
runs-on: ubuntu-latest
strategy:
matrix:
# placeholder required for each axis, excluded below, replaced by the actual combinations (see include)
slug: ["placeholder"]
ref: ["placeholder"]
include:
- slug: "apache/commons-io"
ref: "8985de8fe74f6622a419b37a6eed0dbc484dc128"
exclude:
- slug: "placeholder"
ref: "placeholder"
steps:
- name: Clone self (github/codeql)
uses: actions/checkout@v2
- name: Setup CodeQL binaries
uses: ./.github/actions/fetch-codeql
- name: Clone repositories
uses: actions/checkout@v2
with:
path: repos/${{ matrix.ref }}
ref: ${{ matrix.ref }}
repository: ${{ matrix.slug }}
- name: Build database
env:
SLUG: ${{ matrix.slug }}
REF: ${{ matrix.ref }}
run: |
mkdir dbs
cd repos/${REF}
SHORTNAME=${SLUG//[^a-zA-Z0-9_]/}
codeql database create --language=java ../../dbs/${SHORTNAME}
- name: Regenerate models in-place
env:
SLUG: ${{ matrix.slug }}
run: |
SHORTNAME=${SLUG//[^a-zA-Z0-9_]/}
java/ql/src/utils/model-generator/RegenerateModels.py "${SLUG}" dbs/${SHORTNAME}
- name: Stage changes
run: |
find java -name "*.qll" -print0 | xargs -0 git add
git status
git diff --cached > models.patch
- uses: actions/upload-artifact@v2
with:
name: patch
path: models.patch
retention-days: 7

View File

@@ -189,11 +189,4 @@ jobs:
uses: github/codeql-action/analyze@erik-krogh/ql
with:
category: "ql-for-ql-${{ matrix.folder }}"
- name: Copy sarif file to CWD
run: cp ../results/ql.sarif ./${{ matrix.folder }}.sarif
- name: Sarif as artifact
uses: actions/upload-artifact@v2
with:
name: ${{ matrix.folder }}.sarif
path: ${{ matrix.folder }}.sarif

View File

@@ -17,7 +17,7 @@ jobs:
CODEQL_THREADS: 4 # TODO: remove this once it's set by the CLI
strategy:
matrix:
repo:
repo:
- github/codeql
- github/codeql-go
runs-on: ubuntu-latest
@@ -35,7 +35,7 @@ jobs:
~/.cargo/registry
~/.cargo/git
ql/target
key: ${{ runner.os }}-qltest-cargo-${{ hashFiles('ql/**/Cargo.lock') }}
key: ${{ runner.os }}-qltest-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Build Extractor
run: cd ql; env "PATH=$PATH:`dirname ${CODEQL}`" ./create-extractor-pack.sh
env:

View File

@@ -29,24 +29,24 @@ jobs:
~/.cargo/registry
~/.cargo/git
ql/target
key: ${{ runner.os }}-qltest-cargo-${{ hashFiles('ql/**/Cargo.lock') }}
key: ${{ runner.os }}-qltest-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Build extractor
run: |
cd ql;
codeqlpath=$(dirname ${{ steps.find-codeql.outputs.codeql-path }});
env "PATH=$PATH:$codeqlpath" ./create-extractor-pack.sh
- name: Run QL tests
run: |
run: |
"${CODEQL}" test run --check-databases --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition --search-path "${{ github.workspace }}/ql/extractor-pack" --consistency-queries ql/ql/consistency-queries ql/ql/test
env:
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
- name: Check QL formatting
run: |
run: |
find ql/ql "(" -name "*.ql" -or -name "*.qll" ")" -print0 | xargs -0 "${CODEQL}" query format --check-only
env:
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
- name: Check QL compilation
run: |
run: |
"${CODEQL}" query compile --check-only --threads=4 --warnings=error --search-path "${{ github.workspace }}/ql/extractor-pack" "ql/ql/src" "ql/ql/examples"
env:
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}

View File

@@ -50,7 +50,7 @@ jobs:
~/.cargo/registry
~/.cargo/git
ruby/target
key: ${{ runner.os }}-ruby-rust-cargo-${{ hashFiles('ruby/rust-toolchain.toml', 'ruby/**/Cargo.lock') }}
key: ${{ runner.os }}-rust-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Check formatting
run: cargo fmt --all -- --check
- name: Build

View File

@@ -24,45 +24,27 @@ defaults:
working-directory: ruby
jobs:
qlformat:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: ./.github/actions/fetch-codeql
- name: Check QL formatting
run: find ql "(" -name "*.ql" -or -name "*.qll" ")" -print0 | xargs -0 codeql query format --check-only
qlcompile:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: ./.github/actions/fetch-codeql
- name: Check QL compilation
run: |
codeql query compile --check-only --threads=0 --ram 5000 --warnings=error "ql/src" "ql/examples"
env:
GITHUB_TOKEN: ${{ github.token }}
qlupgrade:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: ./.github/actions/fetch-codeql
- name: Check DB upgrade scripts
run: |
echo >empty.trap
codeql dataset import -S ql/lib/upgrades/initial/ruby.dbscheme testdb empty.trap
codeql dataset upgrade testdb --additional-packs ql/lib
diff -q testdb/ruby.dbscheme ql/lib/ruby.dbscheme
qltest:
runs-on: ubuntu-latest
strategy:
matrix:
slice: ["1/2", "2/2"]
steps:
- uses: actions/checkout@v2
- uses: ./.github/actions/fetch-codeql
- uses: ./ruby/actions/create-extractor-pack
- name: Run QL tests
run: |
codeql test run --threads=0 --ram 5000 --slice ${{ matrix.slice }} --search-path "${{ github.workspace }}/ruby/extractor-pack" --check-databases --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition --consistency-queries ql/consistency-queries ql/test
codeql test run --search-path "${{ github.workspace }}/ruby/extractor-pack" --check-databases --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition --consistency-queries ql/consistency-queries ql/test
env:
GITHUB_TOKEN: ${{ github.token }}
- name: Check QL formatting
run: find ql "(" -name "*.ql" -or -name "*.qll" ")" -print0 | xargs -0 codeql query format --check-only
- name: Check QL compilation
run: |
codeql query compile --check-only --threads=4 --warnings=error "ql/src" "ql/examples"
env:
GITHUB_TOKEN: ${{ github.token }}
- name: Check DB upgrade scripts
run: |
echo >empty.trap
codeql dataset import -S ql/lib/upgrades/initial/ruby.dbscheme testdb empty.trap
codeql dataset upgrade testdb --additional-packs ql/lib
diff -q testdb/ruby.dbscheme ql/lib/ruby.dbscheme

View File

@@ -27,4 +27,4 @@
/docs/query-*-style-guide.md @github/codeql-analysis-reviewers
# QL for QL reviewers
/ql/ @github/codeql-ql-for-ql-reviewers
/ql/ @erik-krogh @tausbn

View File

@@ -4,9 +4,6 @@ We welcome contributions to our CodeQL libraries and queries. Got an idea for a
There is lots of useful documentation to help you write queries, ranging from information about query file structure to tutorials for specific target languages. For more information on the documentation available, see [CodeQL queries](https://help.semmle.com/QL/learn-ql/writing-queries/writing-queries.html) on [help.semmle.com](https://help.semmle.com).
## Change notes
Any nontrivial user-visible change to a query pack or library pack should have a change note. For details on how to add a change note for your change, see [this guide](docs/change-notes.md).
## Submitting a new experimental query

View File

@@ -7,7 +7,6 @@
"java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl5.qll",
"java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll",
"java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForSerializability.qll",
"java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForOnActivityResult.qll",
"cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll",
"cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll",
"cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll",

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<RuntimeIdentifiers>win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
<Nullable>enable</Nullable>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
<AssemblyName>Semmle.Autobuild.Cpp</AssemblyName>
<RootNamespace>Semmle.Autobuild.Cpp</RootNamespace>
<ApplicationIcon />

View File

@@ -5,11 +5,9 @@
@name Badly bounded write (CWE-120)
+ semmlecode-cpp-queries/Security/CWE/CWE-120/OverrunWrite.ql: /CWE/CWE-120
@name Potentially overrunning write (CWE-120)
+ semmlecode-cpp-queries/Security/CWE/CWE-120/VeryLikelyOverrunWrite.ql: /CWE/CWE-120
@name Likely overrunning write
+ semmlecode-cpp-queries/Security/CWE/CWE-120/OverrunWriteFloat.ql: /CWE/CWE-120
@name Potentially overrunning write with float to string conversion (CWE-120)
+ semmlecode-cpp-queries/Best Practices/Likely Errors/OffsetUseBeforeRangeCheck.ql: /CWE/CWE-120
@name Array offset used before range check (CWE-120)
+ semmlecode-cpp-queries/Likely Bugs/Memory Management/UnsafeUseOfStrcat.ql: /CWE/CWE-120
@name Potentially unsafe use of strcat (CWE-120)
@name Potentially unsafe use of strcat (CWE-120)

View File

@@ -1,6 +1,4 @@
name: codeql/cpp-examples
groups:
- cpp
- examples
version: 0.0.2
dependencies:
codeql/cpp-all: "*"

View File

@@ -1,7 +1,3 @@
## 0.0.7
## 0.0.6
## 0.0.5
## 0.0.4

View File

@@ -1,4 +0,0 @@
---
category: deprecated
---
* The `codeql/cpp-upgrades` CodeQL pack has been removed. All upgrades scripts have been merged into the `codeql/cpp-all` CodeQL pack.

View File

@@ -1,5 +0,0 @@
---
category: minorAnalysis
---
* `FormatLiteral::getMaxConvertedLength` now uses range analysis to provide a
more accurate length for integers formatted with `%x`

View File

@@ -1 +0,0 @@
## 0.0.6

View File

@@ -1 +0,0 @@
## 0.0.7

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.0.7
lastReleaseVersion: 0.0.5

View File

@@ -1,5 +1,5 @@
name: codeql/cpp-all
version: 0.0.8-dev
version: 0.0.6-dev
groups: cpp
dbscheme: semmlecode.cpp.dbscheme
extractor: cpp

View File

@@ -206,7 +206,9 @@ class Class extends UserType {
* it is callable by a particular caller. For C++11, there's also a question
* of whether to include members that are defaulted or deleted.
*/
deprecated predicate hasCopyConstructor() { this.getAMemberFunction() instanceof CopyConstructor }
deprecated predicate hasCopyConstructor() {
exists(CopyConstructor cc | cc = this.getAMemberFunction())
}
/**
* Holds if this class has a copy assignment operator that is either
@@ -222,7 +224,7 @@ class Class extends UserType {
* or deleted.
*/
deprecated predicate hasCopyAssignmentOperator() {
this.getAMemberFunction() instanceof CopyAssignmentOperator
exists(CopyAssignmentOperator coa | coa = this.getAMemberFunction())
}
/**
@@ -885,7 +887,7 @@ class NestedClass extends Class {
* pure virtual function.
*/
class AbstractClass extends Class {
AbstractClass() { this.getAMemberFunction() instanceof PureVirtualFunction }
AbstractClass() { exists(PureVirtualFunction f | this.getAMemberFunction() = f) }
override string getAPrimaryQlClass() { result = "AbstractClass" }
}

View File

@@ -286,13 +286,13 @@ class AttributeArgument extends Element, @attribute_arg {
override Location getLocation() { attribute_args(underlyingElement(this), _, _, _, result) }
override string toString() {
if underlyingElement(this) instanceof @attribute_arg_empty
if exists(@attribute_arg_empty self | self = underlyingElement(this))
then result = "empty argument"
else
exists(string prefix, string tail |
(if exists(this.getName()) then prefix = this.getName() + "=" else prefix = "") and
(
if underlyingElement(this) instanceof @attribute_arg_type
if exists(@attribute_arg_type self | self = underlyingElement(this))
then tail = this.getValueType().getName()
else tail = this.getValueText()
) and

View File

@@ -233,7 +233,7 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
XMLAttribute getAttribute(string name) { result.getElement() = this and result.getName() = name }
/** Holds if this XML element has an attribute with the specified `name`. */
predicate hasAttribute(string name) { exists(this.getAttribute(name)) }
predicate hasAttribute(string name) { exists(XMLAttribute a | a = this.getAttribute(name)) }
/** Gets the value of the attribute with the specified `name`, if any. */
string getAttributeValue(string name) { result = this.getAttribute(name).getValue() }

View File

@@ -101,21 +101,6 @@ predicate functionArgumentMustBeNullTerminated(Function f, int i) {
f instanceof StrcatFunction and i = 0
}
/**
* Holds if `arg` is a string format argument to a formatting function call
* `ffc`.
*/
predicate formatArgumentMustBeNullTerminated(FormattingFunctionCall ffc, Expr arg) {
// String argument to a formatting function (such as `printf`)
exists(int n, FormatLiteral fl |
ffc.getConversionArgument(n) = arg and
fl = ffc.getFormat() and
fl.getConversionType(n) instanceof PointerType and // `%s`, `%ws` etc
not fl.getConversionType(n) instanceof VoidPointerType and // exclude: `%p`
not fl.hasPrecision(n) // exclude: `%.*s`
)
}
/**
* Holds if `va` is a variable access where the contents must be null terminated.
*/
@@ -128,7 +113,13 @@ predicate variableMustBeNullTerminated(VariableAccess va) {
)
or
// String argument to a formatting function (such as `printf`)
formatArgumentMustBeNullTerminated(fc, va)
exists(int n, FormatLiteral fl |
fc.(FormattingFunctionCall).getConversionArgument(n) = va and
fl = fc.(FormattingFunctionCall).getFormat() and
fl.getConversionType(n) instanceof PointerType and // `%s`, `%ws` etc
not fl.getConversionType(n) instanceof VoidPointerType and // exclude: `%p`
not fl.hasPrecision(n) // exclude: `%.*s`
)
or
// Call to a wrapper function that requires null termination
// (not itself adding a null terminator)

View File

@@ -10,22 +10,10 @@ private import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
private import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils
private newtype TBufferWriteEstimationReason =
TUnspecifiedEstimateReason() or
TNoSpecifiedEstimateReason() or
TTypeBoundsAnalysis() or
TWidenedValueFlowAnalysis() or
TValueFlowAnalysis()
private predicate gradeToReason(int grade, TBufferWriteEstimationReason reason) {
// when combining reasons, lower grade takes precedence
grade = 0 and reason = TUnspecifiedEstimateReason()
or
grade = 1 and reason = TTypeBoundsAnalysis()
or
grade = 2 and reason = TWidenedValueFlowAnalysis()
or
grade = 3 and reason = TValueFlowAnalysis()
}
/**
* A reason for a specific buffer write size estimate.
*/
@@ -44,13 +32,7 @@ abstract class BufferWriteEstimationReason extends TBufferWriteEstimationReason
* Combine estimate reasons. Used to give a reason for the size of a format string
* conversion given reasons coming from its individual specifiers.
*/
BufferWriteEstimationReason combineWith(BufferWriteEstimationReason other) {
exists(int grade, int otherGrade |
gradeToReason(grade, this) and gradeToReason(otherGrade, other)
|
if otherGrade < grade then result = other else result = this
)
}
abstract BufferWriteEstimationReason combineWith(BufferWriteEstimationReason other);
}
/**
@@ -58,10 +40,16 @@ abstract class BufferWriteEstimationReason extends TBufferWriteEstimationReason
* classes derived from BufferWrite and overriding `getMaxData/0` still work with the
* queries as intended.
*/
class UnspecifiedEstimateReason extends BufferWriteEstimationReason, TUnspecifiedEstimateReason {
override string toString() { result = "UnspecifiedEstimateReason" }
class NoSpecifiedEstimateReason extends BufferWriteEstimationReason, TNoSpecifiedEstimateReason {
override string toString() { result = "NoSpecifiedEstimateReason" }
override string getDescription() { result = "no reason specified" }
override BufferWriteEstimationReason combineWith(BufferWriteEstimationReason other) {
// this reason should not be used in format specifiers, so it should not be combined
// with other reasons
none()
}
}
/**
@@ -72,24 +60,9 @@ class TypeBoundsAnalysis extends BufferWriteEstimationReason, TTypeBoundsAnalysi
override string toString() { result = "TypeBoundsAnalysis" }
override string getDescription() { result = "based on type bounds" }
}
/**
* The estimation comes from non trivial bounds found via actual flow analysis,
* but a widening aproximation might have been used for variables in loops.
* For example
* ```
* for (int i = 0; i < 10; ++i) {
* int j = i + i;
* //... <- estimation done here based on j
* }
* ```
*/
class WidenedValueFlowAnalysis extends BufferWriteEstimationReason, TWidenedValueFlowAnalysis {
override string toString() { result = "WidenedValueFlowAnalysis" }
override string getDescription() {
result = "based on flow analysis of value bounds with a widening approximation"
override BufferWriteEstimationReason combineWith(BufferWriteEstimationReason other) {
other != TNoSpecifiedEstimateReason() and result = TTypeBoundsAnalysis()
}
}
@@ -107,6 +80,10 @@ class ValueFlowAnalysis extends BufferWriteEstimationReason, TValueFlowAnalysis
override string toString() { result = "ValueFlowAnalysis" }
override string getDescription() { result = "based on flow analysis of value bounds" }
override BufferWriteEstimationReason combineWith(BufferWriteEstimationReason other) {
other != TNoSpecifiedEstimateReason() and result = other
}
}
class PrintfFormatAttribute extends FormatAttribute {
@@ -382,38 +359,6 @@ private int lengthInBase10(float f) {
result = f.log10().floor() + 1
}
pragma[nomagic]
private predicate isPointerTypeWithBase(Type base, PointerType pt) { base = pt.getBaseType() }
bindingset[expr]
private BufferWriteEstimationReason getEstimationReasonForIntegralExpression(Expr expr) {
// we consider the range analysis non trivial if it
// * constrained non-trivially both sides of a signed value, or
// * constrained non-trivially the positive side of an unsigned value
// expr should already be given as getFullyConverted
if
upperBound(expr) < exprMaxVal(expr) and
(exprMinVal(expr) >= 0 or lowerBound(expr) > exprMinVal(expr))
then
// next we check whether the estimate may have been widened
if upperBoundMayBeWidened(expr)
then result = TWidenedValueFlowAnalysis()
else result = TValueFlowAnalysis()
else result = TTypeBoundsAnalysis()
}
/**
* Gets the number of hex digits required to represent the integer represented by `f`.
*
* `f` is assumed to be nonnegative.
*/
bindingset[f]
private int lengthInBase16(float f) {
f = 0 and result = 1
or
result = (f.log2() / 4.0).floor() + 1
}
/**
* A class to represent format strings that occur as arguments to invocations of formatting functions.
*/
@@ -965,19 +910,19 @@ class FormatLiteral extends Literal {
(
conv = ["s", "S"] and
len = "h" and
isPointerTypeWithBase(any(PlainCharType plainCharType), result)
result.(PointerType).getBaseType() instanceof PlainCharType
or
conv = ["s", "S"] and
len = ["l", "w"] and
isPointerTypeWithBase(this.getWideCharType(), result)
result.(PointerType).getBaseType() = this.getWideCharType()
or
conv = "s" and
(len != "l" and len != "w" and len != "h") and
isPointerTypeWithBase(this.getDefaultCharType(), result)
result.(PointerType).getBaseType() = this.getDefaultCharType()
or
conv = "S" and
(len != "l" and len != "w" and len != "h") and
isPointerTypeWithBase(this.getNonDefaultCharType(), result)
result.(PointerType).getBaseType() = this.getNonDefaultCharType()
)
)
}
@@ -1122,7 +1067,7 @@ class FormatLiteral extends Literal {
* conversion specifier of this format string; has no result if this cannot
* be determined.
*/
int getMaxConvertedLength(int n) { result = max(this.getMaxConvertedLength(n, _)) }
int getMaxConvertedLength(int n) { result = max(getMaxConvertedLength(n, _)) }
/**
* Gets the maximum length of the string that can be produced by the nth
@@ -1212,10 +1157,12 @@ class FormatLiteral extends Literal {
1 + lengthInBase10(2.pow(this.getIntegralDisplayType(n).getSize() * 8 - 1)) and
// The second case uses range analysis to deduce a length that's shorter than the length
// of the number -2^31.
exists(Expr arg, float lower, float upper |
exists(Expr arg, float lower, float upper, float typeLower, float typeUpper |
arg = this.getUse().getConversionArgument(n) and
lower = lowerBound(arg.getFullyConverted()) and
upper = upperBound(arg.getFullyConverted())
upper = upperBound(arg.getFullyConverted()) and
typeLower = exprMinVal(arg.getFullyConverted()) and
typeUpper = exprMaxVal(arg.getFullyConverted())
|
valueBasedBound =
max(int cand |
@@ -1232,9 +1179,11 @@ class FormatLiteral extends Literal {
else cand = lengthInBase10(upper)
)
) and
// we don't want to call this on `arg.getFullyConverted()` as we want
// to detect non-trivial range analysis without taking into account up-casting
reason = getEstimationReasonForIntegralExpression(arg)
(
if lower > typeLower or upper < typeUpper
then reason = TValueFlowAnalysis()
else reason = TTypeBoundsAnalysis()
)
) and
len = valueBasedBound.minimum(typeBasedBound)
)
@@ -1246,40 +1195,6 @@ class FormatLiteral extends Literal {
typeBasedBound = lengthInBase10(2.pow(this.getIntegralDisplayType(n).getSize() * 8) - 1) and
// The second case uses range analysis to deduce a length that's shorter than
// the length of the number 2^31 - 1.
exists(Expr arg, float lower, float upper |
arg = this.getUse().getConversionArgument(n) and
lower = lowerBound(arg.getFullyConverted()) and
upper = upperBound(arg.getFullyConverted())
|
valueBasedBound =
lengthInBase10(max(float cand |
// If lower can be negative we use `(unsigned)-1` as the candidate value.
lower < 0 and
cand = 2.pow(any(IntType t | t.isUnsigned()).getSize() * 8)
or
cand = upper
)) and
// we don't want to call this on `arg.getFullyConverted()` as we want
// to detect non-trivial range analysis without taking into account up-casting
reason = getEstimationReasonForIntegralExpression(arg)
) and
len = valueBasedBound.minimum(typeBasedBound)
)
or
this.getConversionChar(n).toLowerCase() = "x" and
// e.g. "12345678"
exists(int baseLen, int typeBasedBound, int valueBasedBound |
typeBasedBound =
min(int digits |
digits = 2 * this.getIntegralDisplayType(n).getSize()
or
exists(IntegralType t |
t = this.getUse().getConversionArgument(n).getType().getUnderlyingType()
|
t.isUnsigned() and
digits = 2 * t.getSize()
)
) and
exists(Expr arg, float lower, float upper, float typeLower, float typeUpper |
arg = this.getUse().getConversionArgument(n) and
lower = lowerBound(arg.getFullyConverted()) and
@@ -1288,7 +1203,7 @@ class FormatLiteral extends Literal {
typeUpper = exprMaxVal(arg.getFullyConverted())
|
valueBasedBound =
lengthInBase16(max(float cand |
lengthInBase10(max(float cand |
// If lower can be negative we use `(unsigned)-1` as the candidate value.
lower < 0 and
cand = 2.pow(any(IntType t | t.isUnsigned()).getSize() * 8)
@@ -1301,10 +1216,29 @@ class FormatLiteral extends Literal {
else reason = TTypeBoundsAnalysis()
)
) and
baseLen = valueBasedBound.minimum(typeBasedBound) and
if this.hasAlternateFlag(n) then len = 2 + baseLen else len = baseLen // "0x"
len = valueBasedBound.minimum(typeBasedBound)
)
or
this.getConversionChar(n).toLowerCase() = "x" and
// e.g. "12345678"
exists(int sizeBytes, int baseLen |
sizeBytes =
min(int bytes |
bytes = this.getIntegralDisplayType(n).getSize()
or
exists(IntegralType t |
t = this.getUse().getConversionArgument(n).getType().getUnderlyingType()
|
t.isUnsigned() and bytes = t.getSize()
)
) and
baseLen = sizeBytes * 2 and
(
if this.hasAlternateFlag(n) then len = 2 + baseLen else len = baseLen // "0x"
)
) and
reason = TTypeBoundsAnalysis()
or
this.getConversionChar(n).toLowerCase() = "p" and
exists(PointerType ptrType, int baseLen |
ptrType = this.getFullyConverted().getType() and
@@ -1353,7 +1287,7 @@ class FormatLiteral extends Literal {
* determining whether a buffer overflow is caused by long float to string
* conversions.
*/
int getMaxConvertedLengthLimited(int n) { result = max(this.getMaxConvertedLengthLimited(n, _)) }
int getMaxConvertedLengthLimited(int n) { result = max(getMaxConvertedLengthLimited(n, _)) }
/**
* Gets the maximum length of the string that can be produced by the nth

View File

@@ -29,7 +29,7 @@ class GuardCondition extends Expr {
exists(IRGuardCondition ir | this = ir.getUnconvertedResultExpression())
or
// no binary operators in the IR
this.(BinaryLogicalOperation).getAnOperand() instanceof GuardCondition
exists(GuardCondition gc | this.(BinaryLogicalOperation).getAnOperand() = gc)
or
// the IR short-circuits if(!x)
// don't produce a guard condition for `y = !x` and other non-short-circuited cases
@@ -98,7 +98,7 @@ class GuardCondition extends Expr {
*/
private class GuardConditionFromBinaryLogicalOperator extends GuardCondition {
GuardConditionFromBinaryLogicalOperator() {
this.(BinaryLogicalOperation).getAnOperand() instanceof GuardCondition
exists(GuardCondition gc | this.(BinaryLogicalOperation).getAnOperand() = gc)
}
override predicate controls(BasicBlock controlled, boolean testIsTrue) {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -3,17 +3,6 @@ private import DataFlowImplSpecific::Public
import Cached
module DataFlowImplCommonPublic {
/** A state value to track during data flow. */
class FlowState = string;
/**
* The default state, which is used when the state is unspecified for a source
* or a sink.
*/
class FlowStateEmpty extends FlowState {
FlowStateEmpty() { this = "" }
}
private newtype TFlowFeature =
TFeatureHasSourceCallContext() or
TFeatureHasSinkCallContext() or
@@ -1290,7 +1279,7 @@ class DataFlowCallOption extends TDataFlowCallOption {
}
}
/** A `Content` tagged with the type of a containing object. */
/** Content tagged with the type of a containing object. */
class TypedContent extends MkTypedContent {
private Content c;
private DataFlowType t;

View File

@@ -48,7 +48,7 @@ private class Argument extends Expr {
*/
class ArgumentNode extends Node {
ArgumentNode() {
this.asExpr() instanceof Argument or
exists(Argument arg | this.asExpr() = arg) or
this = getInstanceArgument(_)
}

View File

@@ -592,14 +592,12 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
* Holds if data flows from `source` to `sink` in zero or more local
* (intra-procedural) steps.
*/
pragma[inline]
predicate localFlow(Node source, Node sink) { localFlowStep*(source, sink) }
/**
* Holds if data can flow from `e1` to `e2` in zero or more
* local (intra-procedural) steps.
*/
pragma[inline]
predicate localExprFlow(Expr e1, Expr e2) { localFlow(exprNode(e1), exprNode(e2)) }
/**

View File

@@ -435,7 +435,7 @@ module FlowVar_internal {
parameterIsNonConstReference(p) and
p = v and
// This definition reaches the exit node of the function CFG
getAReachedBlockVarSBB(this).getEnd() = p.getFunction()
getAReachedBlockVarSBB(this).getANode() = p.getFunction()
}
override predicate definedByInitialValue(StackVariable lsv) {

View File

@@ -47,12 +47,6 @@ predicate defaultImplicitTaintRead(DataFlow::Node node, DataFlow::Content c) { n
*/
predicate defaultTaintSanitizer(DataFlow::Node node) { none() }
/**
* Holds if `guard` should be a sanitizer guard in all global taint flow configurations
* but not in local taint.
*/
predicate defaultTaintSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
/**
* Holds if taint can flow in one local step from `nodeFrom` to `nodeTo` excluding
* local data flow steps. That is, `nodeFrom` and `nodeTo` are likely to represent
@@ -124,14 +118,12 @@ predicate localAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeT
* Holds if taint may propagate from `source` to `sink` in zero or more local
* (intra-procedural) steps.
*/
pragma[inline]
predicate localTaint(DataFlow::Node source, DataFlow::Node sink) { localTaintStep*(source, sink) }
/**
* Holds if taint can flow from `e1` to `e2` in zero or more
* local (intra-procedural) steps.
*/
pragma[inline]
predicate localExprTaint(Expr e1, Expr e2) {
localTaint(DataFlow::exprNode(e1), DataFlow::exprNode(e2))
}

View File

@@ -61,7 +61,7 @@ abstract class Configuration extends DataFlow::Configuration {
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
override predicate isSource(DataFlow::Node source) { none() }
abstract override predicate isSource(DataFlow::Node source);
/**
* Holds if `sink` is a relevant taint sink.
@@ -69,7 +69,7 @@ abstract class Configuration extends DataFlow::Configuration {
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
override predicate isSink(DataFlow::Node sink) { none() }
abstract override predicate isSink(DataFlow::Node sink);
/** Holds if the node `node` is a taint sanitizer. */
predicate isSanitizer(DataFlow::Node node) { none() }
@@ -93,7 +93,7 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
this.isSanitizerGuard(guard) or defaultTaintSanitizerGuard(guard)
this.isSanitizerGuard(guard)
}
/**

View File

@@ -61,7 +61,7 @@ abstract class Configuration extends DataFlow::Configuration {
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
override predicate isSource(DataFlow::Node source) { none() }
abstract override predicate isSource(DataFlow::Node source);
/**
* Holds if `sink` is a relevant taint sink.
@@ -69,7 +69,7 @@ abstract class Configuration extends DataFlow::Configuration {
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
override predicate isSink(DataFlow::Node sink) { none() }
abstract override predicate isSink(DataFlow::Node sink);
/** Holds if the node `node` is a taint sanitizer. */
predicate isSanitizer(DataFlow::Node node) { none() }
@@ -93,7 +93,7 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
this.isSanitizerGuard(guard) or defaultTaintSanitizerGuard(guard)
this.isSanitizerGuard(guard)
}
/**

View File

@@ -84,8 +84,8 @@ class VariableAccess extends Access, @varaccess {
exists(Assignment a | a.getLValue() = this) or
exists(CrementOperation c | c.getOperand() = this) or
exists(AddressOfExpr addof | addof.getOperand() = this) or
this.getConversion() instanceof ReferenceToExpr or
this.getConversion() instanceof ArrayToPointerConversion
exists(ReferenceToExpr rte | this.getConversion() = rte) or
exists(ArrayToPointerConversion atpc | this.getConversion() = atpc)
}
/**
@@ -104,8 +104,8 @@ class VariableAccess extends Access, @varaccess {
predicate isRValue() {
not exists(AssignExpr ae | ae.getLValue() = this) and
not exists(AddressOfExpr addof | addof.getOperand() = this) and
not this.getConversion() instanceof ReferenceToExpr and
not this.getConversion() instanceof ArrayToPointerConversion
not exists(ReferenceToExpr rte | this.getConversion() = rte) and
not exists(ArrayToPointerConversion atpc | this.getConversion() = atpc)
}
/**
@@ -218,7 +218,9 @@ class PointerFieldAccess extends FieldAccess {
class DotFieldAccess extends FieldAccess {
override string getAPrimaryQlClass() { result = "DotFieldAccess" }
DotFieldAccess() { this.getQualifier().getFullyConverted().getUnspecifiedType() instanceof Class }
DotFieldAccess() {
exists(Class c | c = this.getQualifier().getFullyConverted().getUnspecifiedType())
}
}
/**

View File

@@ -35,7 +35,7 @@ class Call extends Expr, NameQualifiableElement, TCall {
*
* For example, `ptr->f()` has a qualifier, whereas plain `f()` does not.
*/
predicate hasQualifier() { exists(this.getChild(-1)) }
predicate hasQualifier() { exists(Expr e | this.getChild(-1) = e) }
/**
* Gets the expression to the left of the function name or function pointer variable name.

View File

@@ -724,7 +724,7 @@ class SizeofOperator extends Expr, @runtime_sizeof {
* ```
*/
class SizeofExprOperator extends SizeofOperator {
SizeofExprOperator() { exists(this.getChild(0)) }
SizeofExprOperator() { exists(Expr e | this.getChild(0) = e) }
override string getAPrimaryQlClass() { result = "SizeofExprOperator" }
@@ -787,7 +787,7 @@ class AlignofOperator extends Expr, @runtime_alignof {
* ```
*/
class AlignofExprOperator extends AlignofOperator {
AlignofExprOperator() { exists(this.getChild(0)) }
AlignofExprOperator() { exists(Expr e | this.getChild(0) = e) }
/**
* Gets the contained expression.

View File

@@ -1,8 +1,3 @@
/**
* An IR taint tracking library that uses an IR DataFlow configuration to track
* taint from user inputs as defined by `semmle.code.cpp.security.Security`.
*/
import cpp
import semmle.code.cpp.security.Security
private import semmle.code.cpp.ir.dataflow.DataFlow

View File

@@ -3,17 +3,6 @@ private import DataFlowImplSpecific::Public
import Cached
module DataFlowImplCommonPublic {
/** A state value to track during data flow. */
class FlowState = string;
/**
* The default state, which is used when the state is unspecified for a source
* or a sink.
*/
class FlowStateEmpty extends FlowState {
FlowStateEmpty() { this = "" }
}
private newtype TFlowFeature =
TFeatureHasSourceCallContext() or
TFeatureHasSinkCallContext() or
@@ -1290,7 +1279,7 @@ class DataFlowCallOption extends TDataFlowCallOption {
}
}
/** A `Content` tagged with the type of a containing object. */
/** Content tagged with the type of a containing object. */
class TypedContent extends MkTypedContent {
private Content c;
private DataFlowType t;

View File

@@ -1032,14 +1032,12 @@ SideEffectInstruction getSideEffectFor(CallInstruction call, int argument) {
* Holds if data flows from `source` to `sink` in zero or more local
* (intra-procedural) steps.
*/
pragma[inline]
predicate localFlow(Node source, Node sink) { localFlowStep*(source, sink) }
/**
* Holds if data can flow from `i1` to `i2` in zero or more
* local (intra-procedural) steps.
*/
pragma[inline]
predicate localInstructionFlow(Instruction e1, Instruction e2) {
localFlow(instructionNode(e1), instructionNode(e2))
}
@@ -1048,7 +1046,6 @@ predicate localInstructionFlow(Instruction e1, Instruction e2) {
* Holds if data can flow from `e1` to `e2` in zero or more
* local (intra-procedural) steps.
*/
pragma[inline]
predicate localExprFlow(Expr e1, Expr e2) { localFlow(exprNode(e1), exprNode(e2)) }
private newtype TContent =

View File

@@ -170,16 +170,10 @@ private class ReturnParameterIndirection extends Use, TReturnParamIndirection {
}
private predicate isExplicitUse(Operand op) {
exists(VariableAddressInstruction vai | vai = op.getDef() |
// Don't include this operand as a use if it only exists to initialize the
// indirection of a parameter.
not exists(LoadInstruction load |
load.getSourceAddressOperand() = op and
load.getAUse().getUse() instanceof InitializeIndirectionInstruction
) and
// Don't include this operand as a use if the only use of the address is for a write
// that definitely overrides a variable.
not (explicitWrite(true, _, vai) and exists(unique( | | vai.getAUse())))
op.getDef() instanceof VariableAddressInstruction and
not exists(LoadInstruction load |
load.getSourceAddressOperand() = op and
load.getAUse().getUse() instanceof InitializeIndirectionInstruction
)
}

View File

@@ -121,14 +121,12 @@ private predicate operandToInstructionTaintStep(Operand opFrom, Instruction inst
* Holds if taint may propagate from `source` to `sink` in zero or more local
* (intra-procedural) steps.
*/
pragma[inline]
predicate localTaint(DataFlow::Node source, DataFlow::Node sink) { localTaintStep*(source, sink) }
/**
* Holds if taint can flow from `i1` to `i2` in zero or more
* local (intra-procedural) steps.
*/
pragma[inline]
predicate localInstructionTaint(Instruction i1, Instruction i2) {
localTaint(DataFlow::instructionNode(i1), DataFlow::instructionNode(i2))
}
@@ -137,7 +135,6 @@ predicate localInstructionTaint(Instruction i1, Instruction i2) {
* Holds if taint can flow from `e1` to `e2` in zero or more
* local (intra-procedural) steps.
*/
pragma[inline]
predicate localExprTaint(Expr e1, Expr e2) {
localTaint(DataFlow::exprNode(e1), DataFlow::exprNode(e2))
}
@@ -163,12 +160,6 @@ predicate defaultImplicitTaintRead(DataFlow::Node node, DataFlow::Content c) { n
*/
predicate defaultTaintSanitizer(DataFlow::Node node) { none() }
/**
* Holds if `guard` should be a sanitizer guard in all global taint flow configurations
* but not in local taint.
*/
predicate defaultTaintSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
/**
* Holds if taint can flow from `instrIn` to `instrOut` through a call to a
* modeled function.

View File

@@ -61,7 +61,7 @@ abstract class Configuration extends DataFlow::Configuration {
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
override predicate isSource(DataFlow::Node source) { none() }
abstract override predicate isSource(DataFlow::Node source);
/**
* Holds if `sink` is a relevant taint sink.
@@ -69,7 +69,7 @@ abstract class Configuration extends DataFlow::Configuration {
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
override predicate isSink(DataFlow::Node sink) { none() }
abstract override predicate isSink(DataFlow::Node sink);
/** Holds if the node `node` is a taint sanitizer. */
predicate isSanitizer(DataFlow::Node node) { none() }
@@ -93,7 +93,7 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
this.isSanitizerGuard(guard) or defaultTaintSanitizerGuard(guard)
this.isSanitizerGuard(guard)
}
/**

View File

@@ -61,7 +61,7 @@ abstract class Configuration extends DataFlow::Configuration {
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
override predicate isSource(DataFlow::Node source) { none() }
abstract override predicate isSource(DataFlow::Node source);
/**
* Holds if `sink` is a relevant taint sink.
@@ -69,7 +69,7 @@ abstract class Configuration extends DataFlow::Configuration {
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
override predicate isSink(DataFlow::Node sink) { none() }
abstract override predicate isSink(DataFlow::Node sink);
/** Holds if the node `node` is a taint sanitizer. */
predicate isSanitizer(DataFlow::Node node) { none() }
@@ -93,7 +93,7 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
this.isSanitizerGuard(guard) or defaultTaintSanitizerGuard(guard)
this.isSanitizerGuard(guard)
}
/**

View File

@@ -61,7 +61,7 @@ abstract class Configuration extends DataFlow::Configuration {
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
override predicate isSource(DataFlow::Node source) { none() }
abstract override predicate isSource(DataFlow::Node source);
/**
* Holds if `sink` is a relevant taint sink.
@@ -69,7 +69,7 @@ abstract class Configuration extends DataFlow::Configuration {
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
override predicate isSink(DataFlow::Node sink) { none() }
abstract override predicate isSink(DataFlow::Node sink);
/** Holds if the node `node` is a taint sanitizer. */
predicate isSanitizer(DataFlow::Node node) { none() }
@@ -93,7 +93,7 @@ abstract class Configuration extends DataFlow::Configuration {
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
this.isSanitizerGuard(guard) or defaultTaintSanitizerGuard(guard)
this.isSanitizerGuard(guard)
}
/**

View File

@@ -111,45 +111,6 @@ private predicate hasDefaultSideEffect(Call call, ParameterIndex i, boolean buff
)
}
/**
* A `Call` or `NewOrNewArrayExpr`.
*
* Both kinds of expression invoke a function as part of their evaluation. This class provides a
* way to treat both kinds of function similarly, and to get the invoked `Function`.
*/
class CallOrAllocationExpr extends Expr {
CallOrAllocationExpr() {
this instanceof Call
or
this instanceof NewOrNewArrayExpr
}
/** Gets the `Function` invoked by this expression, if known. */
final Function getTarget() {
result = this.(Call).getTarget()
or
result = this.(NewOrNewArrayExpr).getAllocator()
}
}
/**
* Returns the side effect opcode, if any, that represents any side effects not specifically modeled
* by an argument side effect.
*/
Opcode getCallSideEffectOpcode(CallOrAllocationExpr expr) {
not exists(expr.getTarget().(SideEffectFunction)) and result instanceof Opcode::CallSideEffect
or
exists(SideEffectFunction sideEffectFunction |
sideEffectFunction = expr.getTarget() and
if not sideEffectFunction.hasOnlySpecificWriteSideEffects()
then result instanceof Opcode::CallSideEffect
else (
not sideEffectFunction.hasOnlySpecificReadSideEffects() and
result instanceof Opcode::CallReadSideEffect
)
)
}
/**
* Returns a side effect opcode for parameter index `i` of the specified call.
*

View File

@@ -49,6 +49,19 @@ abstract class TranslatedCall extends TranslatedExpr {
tag = CallTag() and
opcode instanceof Opcode::Call and
resultType = getTypeForPRValue(getCallResultType())
or
hasSideEffect() and
tag = CallSideEffectTag() and
(
if hasWriteSideEffect()
then (
opcode instanceof Opcode::CallSideEffect and
resultType = getUnknownType()
) else (
opcode instanceof Opcode::CallReadSideEffect and
resultType = getVoidType()
)
)
}
override Instruction getChildSuccessor(TranslatedElement child) {
@@ -71,8 +84,25 @@ abstract class TranslatedCall extends TranslatedExpr {
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
kind instanceof GotoEdge and
tag = CallTag() and
result = getSideEffects().getFirstInstruction()
(
(
tag = CallTag() and
if hasSideEffect()
then result = getInstruction(CallSideEffectTag())
else
if hasPreciseSideEffect()
then result = getSideEffects().getFirstInstruction()
else result = getParent().getChildSuccessor(this)
)
or
(
hasSideEffect() and
tag = CallSideEffectTag() and
if hasPreciseSideEffect()
then result = getSideEffects().getFirstInstruction()
else result = getParent().getChildSuccessor(this)
)
)
}
override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
@@ -91,6 +121,15 @@ abstract class TranslatedCall extends TranslatedExpr {
)
}
final override CppType getInstructionMemoryOperandType(
InstructionTag tag, TypedOperandTag operandTag
) {
tag = CallSideEffectTag() and
hasSideEffect() and
operandTag instanceof SideEffectOperandTag and
result = getUnknownType()
}
final override Instruction getResult() { result = getInstruction(CallTag()) }
/**
@@ -161,31 +200,40 @@ abstract class TranslatedCall extends TranslatedExpr {
*/
abstract predicate hasArguments();
predicate hasReadSideEffect() { any() }
predicate hasWriteSideEffect() { any() }
private predicate hasSideEffect() { hasReadSideEffect() or hasWriteSideEffect() }
override Instruction getPrimaryInstructionForSideEffect(InstructionTag tag) {
hasSideEffect() and
tag = CallSideEffectTag() and
result = getResult()
}
predicate hasPreciseSideEffect() { exists(getSideEffects()) }
final TranslatedSideEffects getSideEffects() { result.getExpr() = expr }
}
/**
* The IR translation of the side effects of the parent `TranslatedElement`.
*
* This object does not itself generate the side effect instructions. Instead, its children provide
* the actual side effects, with this object acting as a placeholder so the parent only needs to
* insert this one element at the point where all the side effects are supposed to occur.
*/
abstract class TranslatedSideEffects extends TranslatedElement {
/** Gets the expression whose side effects are being modeled. */
abstract Expr getExpr();
final override Locatable getAST() { result = getExpr() }
final override Function getFunction() { result = getExpr().getEnclosingFunction() }
final override TranslatedElement getChild(int i) {
override TranslatedElement getChild(int i) {
result =
rank[i + 1](TranslatedSideEffect tse, int group, int indexInGroup |
tse.getPrimaryExpr() = getExpr() and
tse.sortOrder(group, indexInGroup)
rank[i + 1](TranslatedSideEffect tse, int isWrite, int index |
(
tse.getCall() = getExpr() and
tse.getArgumentIndex() = index and
if tse.isWrite() then isWrite = 1 else isWrite = 0
)
|
tse order by group, indexInGroup
tse order by isWrite, index
)
}
@@ -198,21 +246,12 @@ abstract class TranslatedSideEffects extends TranslatedElement {
)
}
final override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType type) {
none()
/**
* Gets the `TranslatedFunction` containing this expression.
*/
final TranslatedFunction getEnclosingFunction() {
result = getTranslatedFunction(getExpr().getEnclosingFunction())
}
final override Instruction getFirstInstruction() {
result = getChild(0).getFirstInstruction()
or
// Some functions, like `std::move()`, have no side effects whatsoever.
not exists(getChild(0)) and result = getParent().getChildSuccessor(this)
}
final override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) { none() }
/** Gets the primary instruction to be associated with each side effect instruction. */
abstract Instruction getPrimaryInstruction();
}
/**
@@ -286,6 +325,14 @@ class TranslatedFunctionCall extends TranslatedCallExpr, TranslatedDirectCall {
tag = CallTargetTag() and result = expr.getTarget()
}
override predicate hasReadSideEffect() {
not expr.getTarget().(SideEffectFunction).hasOnlySpecificReadSideEffects()
}
override predicate hasWriteSideEffect() {
not expr.getTarget().(SideEffectFunction).hasOnlySpecificWriteSideEffects()
}
override Instruction getQualifierResult() {
hasQualifier() and
result = getQualifier().getResult()
@@ -316,116 +363,209 @@ class TranslatedStructorCall extends TranslatedFunctionCall {
override predicate hasQualifier() { any() }
}
/**
* The IR translation of the side effects of a function call, including the implicit allocator
* call in a `new` or `new[]` expression.
*/
class TranslatedAllocationSideEffects extends TranslatedSideEffects,
TTranslatedAllocationSideEffects {
AllocationExpr expr;
TranslatedAllocationSideEffects() { this = TTranslatedAllocationSideEffects(expr) }
final override AllocationExpr getExpr() { result = expr }
override string toString() { result = "(allocation side effects for " + expr.toString() + ")" }
override Instruction getFirstInstruction() { result = getInstruction(OnlyInstructionTag()) }
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType type) {
opcode instanceof Opcode::InitializeDynamicAllocation and
tag = OnlyInstructionTag() and
type = getUnknownType()
}
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
tag = OnlyInstructionTag() and
kind = EdgeKind::gotoEdge() and
if exists(getChild(0))
then result = getChild(0).getFirstInstruction()
else result = getParent().getChildSuccessor(this)
}
override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag = OnlyInstructionTag() and
operandTag = addressOperand() and
result = getPrimaryInstructionForSideEffect(OnlyInstructionTag())
}
override Instruction getPrimaryInstructionForSideEffect(InstructionTag tag) {
tag = OnlyInstructionTag() and
if expr instanceof NewOrNewArrayExpr
then result = getTranslatedAllocatorCall(expr).getInstruction(CallTag())
else result = getTranslatedCallInstruction(expr)
}
}
class TranslatedCallSideEffects extends TranslatedSideEffects, TTranslatedCallSideEffects {
Expr expr;
Call expr;
TranslatedCallSideEffects() { this = TTranslatedCallSideEffects(expr) }
final override string toString() { result = "(side effects for " + expr.toString() + ")" }
override string toString() { result = "(side effects for " + expr.toString() + ")" }
final override Expr getExpr() { result = expr }
override Call getExpr() { result = expr }
final override Instruction getPrimaryInstruction() {
expr instanceof Call and result = getTranslatedCallInstruction(expr)
or
expr instanceof NewOrNewArrayExpr and
result = getTranslatedAllocatorCall(expr).getInstruction(CallTag())
}
}
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType type) { none() }
/** Returns the sort group index for argument read side effects. */
private int argumentReadGroup() { result = 1 }
override Instruction getFirstInstruction() { result = getChild(0).getFirstInstruction() }
/** Returns the sort group index for conservative call side effects. */
private int callSideEffectGroup() {
result = 0 // Make this group first for now to preserve the existing ordering
}
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) { none() }
/** Returns the sort group index for argument write side effects. */
private int argumentWriteGroup() { result = 2 }
/** Returns the sort group index for dynamic allocation side effects. */
private int initializeAllocationGroup() { result = 3 }
/**
* The IR translation of a single side effect of a call.
*/
abstract class TranslatedSideEffect extends TranslatedElement {
final override TranslatedElement getChild(int n) { none() }
final override Instruction getChildSuccessor(TranslatedElement child) { none() }
final override Instruction getFirstInstruction() { result = getInstruction(OnlyInstructionTag()) }
final override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType type) {
override Instruction getPrimaryInstructionForSideEffect(InstructionTag tag) {
tag = OnlyInstructionTag() and
sideEffectInstruction(opcode, type)
result = getTranslatedCallInstruction(expr)
}
}
class TranslatedStructorCallSideEffects extends TranslatedCallSideEffects {
TranslatedStructorCallSideEffects() {
getParent().(TranslatedStructorCall).hasQualifier() and
getASideEffectOpcode(expr, -1) instanceof WriteSideEffectOpcode
}
final override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType t) {
tag instanceof OnlyInstructionTag and
t = getTypeForPRValue(expr.getTarget().getDeclaringType()) and
opcode = getASideEffectOpcode(expr, -1).(WriteSideEffectOpcode)
}
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
(
if exists(getChild(0))
then result = getChild(0).getFirstInstruction()
else result = getParent().getChildSuccessor(this)
) and
tag = OnlyInstructionTag() and
kind instanceof GotoEdge
}
override Instruction getFirstInstruction() { result = getInstruction(OnlyInstructionTag()) }
override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag instanceof OnlyInstructionTag and
operandTag instanceof AddressOperandTag and
result = getParent().(TranslatedStructorCall).getQualifierResult()
}
final override int getInstructionIndex(InstructionTag tag) {
tag = OnlyInstructionTag() and
result = -1
}
}
class TranslatedSideEffect extends TranslatedElement, TTranslatedArgumentSideEffect {
Call call;
Expr arg;
int index;
SideEffectOpcode sideEffectOpcode;
TranslatedSideEffect() {
this = TTranslatedArgumentSideEffect(call, arg, index, sideEffectOpcode)
}
override Locatable getAST() { result = arg }
Expr getExpr() { result = arg }
Call getCall() { result = call }
int getArgumentIndex() { result = index }
predicate isWrite() { sideEffectOpcode instanceof WriteSideEffectOpcode }
override string toString() {
isWrite() and
result = "(write side effect for " + arg.toString() + ")"
or
not isWrite() and
result = "(read side effect for " + arg.toString() + ")"
}
override TranslatedElement getChild(int n) { none() }
override Instruction getChildSuccessor(TranslatedElement child) { none() }
override Instruction getFirstInstruction() { result = getInstruction(OnlyInstructionTag()) }
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType type) {
tag = OnlyInstructionTag() and
opcode = sideEffectOpcode and
(
isWrite() and
(
opcode instanceof BufferAccessOpcode and
type = getUnknownType()
or
not opcode instanceof BufferAccessOpcode and
exists(Type baseType | baseType = arg.getUnspecifiedType().(DerivedType).getBaseType() |
if baseType instanceof VoidType
then type = getUnknownType()
else type = getTypeForPRValueOrUnknown(baseType)
)
or
index = -1 and
not arg.getUnspecifiedType() instanceof DerivedType and
type = getTypeForPRValueOrUnknown(arg.getUnspecifiedType())
)
or
not isWrite() and
type = getVoidType()
)
}
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
result = getParent().getChildSuccessor(this) and
tag = OnlyInstructionTag() and
kind instanceof GotoEdge
}
final override Function getFunction() { result = getParent().getFunction() }
final override Instruction getPrimaryInstructionForSideEffect(InstructionTag tag) {
tag = OnlyInstructionTag() and
result = getParent().(TranslatedSideEffects).getPrimaryInstruction()
}
/**
* Gets the expression that caused this side effect.
*
* All side effects with the same `getPrimaryExpr()` will appear in the same contiguous sequence
* in the IR.
*/
abstract Expr getPrimaryExpr();
/**
* Gets the order in which this side effect should be sorted with respect to other side effects
* for the same expression.
*
* Side effects are sorted first by `group`, and then by `indexInGroup`.
*/
abstract predicate sortOrder(int group, int indexInGroup);
/**
* Gets the opcode and result type for the side effect instruction.
*/
abstract predicate sideEffectInstruction(Opcode opcode, CppType type);
}
/**
* The IR translation of a single argument side effect for a call.
*/
abstract class TranslatedArgumentSideEffect extends TranslatedSideEffect {
Call call;
int index;
SideEffectOpcode sideEffectOpcode;
// All subclass charpreds must bind the `index` field.
bindingset[index]
TranslatedArgumentSideEffect() { any() }
override string toString() {
isWrite() and
result = "(write side effect for " + getArgString() + ")"
override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag instanceof OnlyInstructionTag and
operandTag instanceof AddressOperandTag and
result = getTranslatedExpr(arg).getResult()
or
not isWrite() and
result = "(read side effect for " + getArgString() + ")"
tag instanceof OnlyInstructionTag and
operandTag instanceof BufferSizeOperandTag and
result =
getTranslatedExpr(call.getArgument(call.getTarget()
.(SideEffectFunction)
.getParameterSizeIndex(index)).getFullyConverted()).getResult()
}
override Call getPrimaryExpr() { result = call }
override CppType getInstructionMemoryOperandType(InstructionTag tag, TypedOperandTag operandTag) {
not isWrite() and
if sideEffectOpcode instanceof BufferAccessOpcode
then
result = getUnknownType() and
tag instanceof OnlyInstructionTag and
operandTag instanceof SideEffectOperandTag
else
exists(Type operandType |
tag instanceof OnlyInstructionTag and
operandType = arg.getType().getUnspecifiedType().(DerivedType).getBaseType() and
operandTag instanceof SideEffectOperandTag
or
tag instanceof OnlyInstructionTag and
operandType = arg.getType().getUnspecifiedType() and
not operandType instanceof DerivedType and
operandTag instanceof SideEffectOperandTag
|
// If the type we select is an incomplete type (e.g. a forward-declared `struct`), there will
// not be a `CppType` that represents that type. In that case, fall back to `UnknownCppType`.
result = getTypeForPRValueOrUnknown(operandType)
)
}
override predicate sortOrder(int group, int indexInGroup) {
indexInGroup = index and
if isWrite() then group = argumentWriteGroup() else group = argumentReadGroup()
override Instruction getPrimaryInstructionForSideEffect(InstructionTag tag) {
tag = OnlyInstructionTag() and
result = getTranslatedCallInstruction(call)
}
final override int getInstructionIndex(InstructionTag tag) {
@@ -437,199 +577,11 @@ abstract class TranslatedArgumentSideEffect extends TranslatedSideEffect {
* Gets the `TranslatedFunction` containing this expression.
*/
final TranslatedFunction getEnclosingFunction() {
result = getTranslatedFunction(call.getEnclosingFunction())
result = getTranslatedFunction(arg.getEnclosingFunction())
}
final override predicate sideEffectInstruction(Opcode opcode, CppType type) {
opcode = sideEffectOpcode and
(
isWrite() and
(
opcode instanceof BufferAccessOpcode and
type = getUnknownType()
or
not opcode instanceof BufferAccessOpcode and
exists(Type indirectionType | indirectionType = getIndirectionType() |
if indirectionType instanceof VoidType
then type = getUnknownType()
else type = getTypeForPRValueOrUnknown(indirectionType)
)
)
or
not isWrite() and
type = getVoidType()
)
}
final override CppType getInstructionMemoryOperandType(
InstructionTag tag, TypedOperandTag operandTag
) {
not isWrite() and
if sideEffectOpcode instanceof BufferAccessOpcode
then
result = getUnknownType() and
tag instanceof OnlyInstructionTag and
operandTag instanceof SideEffectOperandTag
else
exists(Type operandType |
tag instanceof OnlyInstructionTag and
operandType = getIndirectionType() and
operandTag instanceof SideEffectOperandTag
|
// If the type we select is an incomplete type (e.g. a forward-declared `struct`), there will
// not be a `CppType` that represents that type. In that case, fall back to `UnknownCppType`.
result = getTypeForPRValueOrUnknown(operandType)
)
}
final override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag instanceof OnlyInstructionTag and
operandTag instanceof AddressOperandTag and
result = getArgInstruction()
or
tag instanceof OnlyInstructionTag and
operandTag instanceof BufferSizeOperandTag and
result =
getTranslatedExpr(call.getArgument(call.getTarget()
.(SideEffectFunction)
.getParameterSizeIndex(index)).getFullyConverted()).getResult()
}
/** Holds if this side effect is a write side effect, rather than a read side effect. */
final predicate isWrite() { sideEffectOpcode instanceof WriteSideEffectOpcode }
/** Gets a text representation of the argument. */
abstract string getArgString();
/** Gets the `Instruction` whose result is the value of the argument. */
abstract Instruction getArgInstruction();
/** Gets the type pointed to by the argument. */
abstract Type getIndirectionType();
}
/**
* The IR translation of an argument side effect where the argument has an `Expr` object in the AST.
*
* This generally applies to all positional arguments, as well as qualifier (`this`) arguments for
* calls other than constructor calls.
*/
class TranslatedArgumentExprSideEffect extends TranslatedArgumentSideEffect,
TTranslatedArgumentExprSideEffect {
Expr arg;
TranslatedArgumentExprSideEffect() {
this = TTranslatedArgumentExprSideEffect(call, arg, index, sideEffectOpcode)
}
final override Locatable getAST() { result = arg }
final override Type getIndirectionType() {
result = arg.getUnspecifiedType().(DerivedType).getBaseType()
or
// Sometimes the qualifier type gets the type of the class itself, rather than a pointer to the
// class.
index = -1 and
not arg.getUnspecifiedType() instanceof DerivedType and
result = arg.getUnspecifiedType()
}
final override string getArgString() { result = arg.toString() }
final override Instruction getArgInstruction() { result = getTranslatedExpr(arg).getResult() }
}
/**
* The IR translation of an argument side effect for `*this` on a call, where there is no `Expr`
* object that represents the `this` argument.
*
* The applies only to constructor calls, as the AST has explioit qualifier `Expr`s for all other
* calls to non-static member functions.
*/
class TranslatedStructorQualifierSideEffect extends TranslatedArgumentSideEffect,
TTranslatedStructorQualifierSideEffect {
TranslatedStructorQualifierSideEffect() {
this = TTranslatedStructorQualifierSideEffect(call, sideEffectOpcode) and
index = -1
}
final override Locatable getAST() { result = call }
final override Type getIndirectionType() { result = call.getTarget().getDeclaringType() }
final override string getArgString() { result = "this" }
final override Instruction getArgInstruction() {
exists(TranslatedStructorCall structorCall |
structorCall.getExpr() = call and
result = structorCall.getQualifierResult()
)
}
}
/** The IR translation of the non-argument-specific side effect of a call. */
class TranslatedCallSideEffect extends TranslatedSideEffect, TTranslatedCallSideEffect {
Expr expr;
SideEffectOpcode sideEffectOpcode;
TranslatedCallSideEffect() { this = TTranslatedCallSideEffect(expr, sideEffectOpcode) }
override Locatable getAST() { result = expr }
override Expr getPrimaryExpr() { result = expr }
override predicate sortOrder(int group, int indexInGroup) {
group = callSideEffectGroup() and indexInGroup = 0
}
override string toString() { result = "(call side effect for '" + expr.toString() + "')" }
override predicate sideEffectInstruction(Opcode opcode, CppType type) {
opcode = sideEffectOpcode and
(
opcode instanceof Opcode::CallSideEffect and
type = getUnknownType()
or
opcode instanceof Opcode::CallReadSideEffect and
type = getVoidType()
)
}
override CppType getInstructionMemoryOperandType(InstructionTag tag, TypedOperandTag operandTag) {
tag instanceof OnlyInstructionTag and
operandTag instanceof SideEffectOperandTag and
result = getUnknownType()
}
}
/**
* The IR translation of the allocation side effect of a call to a memory allocation function.
*
* This side effect provides a definition for the newly-allocated memory.
*/
class TranslatedAllocationSideEffect extends TranslatedSideEffect, TTranslatedAllocationSideEffect {
AllocationExpr expr;
TranslatedAllocationSideEffect() { this = TTranslatedAllocationSideEffect(expr) }
override Locatable getAST() { result = expr }
override Expr getPrimaryExpr() { result = expr }
override predicate sortOrder(int group, int indexInGroup) {
group = initializeAllocationGroup() and indexInGroup = 0
}
override string toString() { result = "(allocation side effect for '" + expr.toString() + "')" }
override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag = OnlyInstructionTag() and
operandTag = addressOperand() and
result = getPrimaryInstructionForSideEffect(OnlyInstructionTag())
}
override predicate sideEffectInstruction(Opcode opcode, CppType type) {
opcode instanceof Opcode::InitializeDynamicAllocation and
type = getUnknownType()
}
/**
* Gets the `Function` containing this expression.
*/
override Function getFunction() { result = arg.getEnclosingFunction() }
}

View File

@@ -135,20 +135,6 @@ private predicate ignoreExpr(Expr expr) {
ignoreExprAndDescendants(expr)
}
/**
* Holds if the side effects of `expr` should be ignoredf for the purposes of IR generation.
*
* In cases involving `constexpr`, a call can wind up as a constant expression. `ignoreExpr()` will
* not hold for such a call, since we do need to translate the call (as a constant), but we need to
* ignore all of the side effects of that call, since we will not actually be generating a `Call`
* instruction.
*/
private predicate ignoreSideEffects(Expr expr) {
ignoreExpr(expr)
or
isIRConstant(expr)
}
/**
* Holds if `func` contains an AST that cannot be translated into IR. This is mostly used to work
* around extractor bugs. Once the relevant extractor bugs are fixed, this predicate can be removed.
@@ -567,13 +553,6 @@ newtype TTranslatedElement =
} or
// The initialization of a base class from within a constructor.
TTranslatedConstructorBaseInit(ConstructorBaseInit init) { not ignoreExpr(init) } or
// Workaround for a case where no base constructor is generated but a targetless base
// constructor call is present.
TTranslatedConstructorBareInit(ConstructorInit init) {
not ignoreExpr(init) and
not init instanceof ConstructorBaseInit and
not init instanceof ConstructorFieldInit
} or
// The destruction of a base class from within a destructor.
TTranslatedDestructorBaseDestruction(DestructorBaseDestruction destruction) {
not ignoreExpr(destruction)
@@ -642,34 +621,32 @@ newtype TTranslatedElement =
// The declaration/initialization part of a `ConditionDeclExpr`
TTranslatedConditionDecl(ConditionDeclExpr expr) { not ignoreExpr(expr) } or
// The side effects of a `Call`
TTranslatedCallSideEffects(CallOrAllocationExpr expr) { not ignoreSideEffects(expr) } or
// The non-argument-specific side effect of a `Call`
TTranslatedCallSideEffect(Expr expr, SideEffectOpcode opcode) {
not ignoreSideEffects(expr) and
opcode = getCallSideEffectOpcode(expr)
TTranslatedCallSideEffects(Call expr) {
// Exclude allocations such as `malloc` (which happen to also be function calls).
// Both `TranslatedCallSideEffects` and `TranslatedAllocationSideEffects` generate
// the same side effects for its children as they both extend the `TranslatedSideEffects`
// class.
// Note: We can separate allocation side effects and call side effects into two
// translated elements as no call can be both a `ConstructorCall` and an `AllocationExpr`.
not expr instanceof AllocationExpr and
(
exists(TTranslatedArgumentSideEffect(expr, _, _, _)) or
expr instanceof ConstructorCall
)
} or
// The side effects of an allocation, i.e. `new`, `new[]` or `malloc`
TTranslatedAllocationSideEffects(AllocationExpr expr) { not ignoreExpr(expr) } or
// A precise side effect of an argument to a `Call`
TTranslatedArgumentExprSideEffect(Call call, Expr expr, int n, SideEffectOpcode opcode) {
TTranslatedArgumentSideEffect(Call call, Expr expr, int n, SideEffectOpcode opcode) {
not ignoreExpr(expr) and
not ignoreSideEffects(call) and
not ignoreExpr(call) and
(
n >= 0 and expr = call.getArgument(n).getFullyConverted()
or
n = -1 and expr = call.getQualifier().getFullyConverted()
) and
opcode = getASideEffectOpcode(call, n)
} or
// Constructor calls lack a qualifier (`this`) expression, so we need to handle the side effects
// on `*this` without an `Expr`.
TTranslatedStructorQualifierSideEffect(Call call, SideEffectOpcode opcode) {
not ignoreSideEffects(call) and
// Don't bother with destructor calls for now, since we won't see very many of them in the IR
// until we start injecting implicit destructor calls.
call instanceof ConstructorCall and
opcode = getASideEffectOpcode(call, -1)
} or
// The side effect that initializes newly-allocated memory.
TTranslatedAllocationSideEffect(AllocationExpr expr) { not ignoreSideEffects(expr) }
}
/**
* Gets the index of the first explicitly initialized element in `initList`

View File

@@ -573,11 +573,6 @@ class TranslatedConstructorInitList extends TranslatedElement, InitializationCon
baseInit = func.(Constructor).getInitializer(id) and
result = getTranslatedConstructorBaseInit(baseInit)
)
or
exists(ConstructorInit bareInit |
bareInit = func.(Constructor).getInitializer(id) and
result = getTranslatedConstructorBareInit(bareInit)
)
}
override Instruction getFirstInstruction() {

View File

@@ -917,36 +917,3 @@ class TranslatedDestructorBaseDestruction extends TranslatedBaseStructorCall,
final override string toString() { result = "destroy base: " + call.toString() }
}
/**
* A constructor base init call where no base constructor has been generated.
*
* Workaround for an extractor issue.
*/
class TranslatedConstructorBareInit extends TranslatedElement, TTranslatedConstructorBareInit {
ConstructorInit init;
TranslatedConstructorBareInit() { this = TTranslatedConstructorBareInit(init) }
override Locatable getAST() { result = init }
final override string toString() { result = "construct base (no constructor)" }
override Instruction getFirstInstruction() { result = getParent().getChildSuccessor(this) }
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
none()
}
override TranslatedElement getChild(int id) { none() }
override Function getFunction() { result = getParent().getFunction() }
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) { none() }
override Instruction getChildSuccessor(TranslatedElement child) { none() }
}
TranslatedConstructorBareInit getTranslatedConstructorBareInit(ConstructorInit init) {
result.getAST() = init
}

View File

@@ -308,45 +308,45 @@ class MetricClass extends Class {
}
private string getAUsedHalsteadN1Operator() {
this.getAnEnclosedExpression() instanceof CommaExpr and result = "comma"
exists(CommaExpr e | e = this.getAnEnclosedExpression()) and result = "comma"
or
this.getAnEnclosedExpression() instanceof ReferenceToExpr and result = "refTo"
exists(ReferenceToExpr e | e = this.getAnEnclosedExpression()) and result = "refTo"
or
this.getAnEnclosedExpression() instanceof PointerDereferenceExpr and result = "dereference"
exists(PointerDereferenceExpr e | e = this.getAnEnclosedExpression()) and result = "dereference"
or
this.getAnEnclosedExpression() instanceof CStyleCast and result = "cCast"
exists(CStyleCast e | e = this.getAnEnclosedExpression()) and result = "cCast"
or
this.getAnEnclosedExpression() instanceof StaticCast and result = "staticCast"
exists(StaticCast e | e = this.getAnEnclosedExpression()) and result = "staticCast"
or
this.getAnEnclosedExpression() instanceof ConstCast and result = "constCast"
exists(ConstCast e | e = this.getAnEnclosedExpression()) and result = "constCast"
or
this.getAnEnclosedExpression() instanceof ReinterpretCast and result = "reinterpretCast"
exists(ReinterpretCast e | e = this.getAnEnclosedExpression()) and result = "reinterpretCast"
or
this.getAnEnclosedExpression() instanceof DynamicCast and result = "dynamicCast"
exists(DynamicCast e | e = this.getAnEnclosedExpression()) and result = "dynamicCast"
or
this.getAnEnclosedExpression() instanceof SizeofExprOperator and result = "sizeofExpr"
exists(SizeofExprOperator e | e = this.getAnEnclosedExpression()) and result = "sizeofExpr"
or
this.getAnEnclosedExpression() instanceof SizeofTypeOperator and result = "sizeofType"
exists(SizeofTypeOperator e | e = this.getAnEnclosedExpression()) and result = "sizeofType"
or
this.getAnEnclosedStmt() instanceof IfStmt and result = "ifVal"
exists(IfStmt e | e = this.getAnEnclosedStmt()) and result = "ifVal"
or
this.getAnEnclosedStmt() instanceof SwitchStmt and result = "switchVal"
exists(SwitchStmt e | e = this.getAnEnclosedStmt()) and result = "switchVal"
or
this.getAnEnclosedStmt() instanceof ForStmt and result = "forVal"
exists(ForStmt e | e = this.getAnEnclosedStmt()) and result = "forVal"
or
this.getAnEnclosedStmt() instanceof DoStmt and result = "doVal"
exists(DoStmt e | e = this.getAnEnclosedStmt()) and result = "doVal"
or
this.getAnEnclosedStmt() instanceof WhileStmt and result = "whileVal"
exists(WhileStmt e | e = this.getAnEnclosedStmt()) and result = "whileVal"
or
this.getAnEnclosedStmt() instanceof GotoStmt and result = "gotoVal"
exists(GotoStmt e | e = this.getAnEnclosedStmt()) and result = "gotoVal"
or
this.getAnEnclosedStmt() instanceof ContinueStmt and result = "continueVal"
exists(ContinueStmt e | e = this.getAnEnclosedStmt()) and result = "continueVal"
or
this.getAnEnclosedStmt() instanceof BreakStmt and result = "breakVal"
exists(BreakStmt e | e = this.getAnEnclosedStmt()) and result = "breakVal"
or
this.getAnEnclosedStmt() instanceof ReturnStmt and result = "returnVal"
exists(ReturnStmt e | e = this.getAnEnclosedStmt()) and result = "returnVal"
or
this.getAnEnclosedStmt() instanceof SwitchCase and result = "caseVal"
exists(SwitchCase e | e = this.getAnEnclosedStmt()) and result = "caseVal"
or
exists(IfStmt s | s = this.getAnEnclosedStmt() and s.hasElse()) and
result = "elseVal"

View File

@@ -11,14 +11,15 @@ import semmle.code.cpp.models.interfaces.SideEffect
import semmle.code.cpp.models.interfaces.FlowSource
/**
* The standard functions `fgets` and `fgetws`.
* The standard functions `gets` and `fgets`.
*/
private class FgetsFunction extends DataFlowFunction, TaintFunction, ArrayFunction, AliasFunction,
private class GetsFunction extends DataFlowFunction, TaintFunction, ArrayFunction, AliasFunction,
SideEffectFunction, RemoteFlowSourceFunction {
FgetsFunction() {
GetsFunction() {
// gets(str)
// fgets(str, num, stream)
// fgetws(wstr, num, stream)
this.hasGlobalOrStdOrBslName(["fgets", "fgetws"])
this.hasGlobalOrStdOrBslName(["gets", "fgets", "fgetws"])
}
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
@@ -50,61 +51,18 @@ private class FgetsFunction extends DataFlowFunction, TaintFunction, ArrayFuncti
override predicate hasRemoteFlowSource(FunctionOutput output, string description) {
output.isParameterDeref(0) and
description = "String read by " + this.getName()
or
output.isReturnValue() and
description = "String read by " + this.getName()
}
override predicate hasArrayWithVariableSize(int bufParam, int countParam) {
not this.hasName("gets") and
bufParam = 0 and
countParam = 1
}
override predicate hasArrayOutput(int bufParam) { bufParam = 0 }
override predicate hasSocketInput(FunctionInput input) { input.isParameterDeref(2) }
}
/**
* The standard functions `gets`.
*/
private class GetsFunction extends DataFlowFunction, ArrayFunction, AliasFunction,
SideEffectFunction, LocalFlowSourceFunction {
GetsFunction() {
// gets(str)
this.hasGlobalOrStdOrBslName("gets")
override predicate hasArrayWithUnknownSize(int bufParam) {
this.hasName("gets") and
bufParam = 0
}
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
input.isParameter(0) and
output.isReturnValue()
}
override predicate parameterNeverEscapes(int index) { none() }
override predicate parameterEscapesOnlyViaReturn(int index) { index = 0 }
override predicate parameterIsAlwaysReturned(int index) { index = 0 }
override predicate hasOnlySpecificReadSideEffects() { any() }
override predicate hasOnlySpecificWriteSideEffects() { any() }
override predicate hasSpecificWriteSideEffect(ParameterIndex i, boolean buffer, boolean mustWrite) {
i = 0 and
buffer = true and
mustWrite = true
}
override predicate hasLocalFlowSource(FunctionOutput output, string description) {
output.isParameterDeref(0) and
description = "String read by " + this.getName()
or
output.isReturnValue() and
description = "String read by " + this.getName()
}
override predicate hasArrayWithUnknownSize(int bufParam) { bufParam = 0 }
override predicate hasArrayOutput(int bufParam) { bufParam = 0 }
}

View File

@@ -20,9 +20,8 @@ abstract class RemoteFlowSourceFunction extends Function {
abstract predicate hasRemoteFlowSource(FunctionOutput output, string description);
/**
* Holds if remote data from this source comes from a socket or stream
* described by `input`. There is no result if none is specified by a
* parameter.
* Holds if remote data from this source comes from a socket described by
* `input`. There is no result if a socket is not specified.
*/
predicate hasSocketInput(FunctionInput input) { none() }
}
@@ -60,9 +59,8 @@ abstract class RemoteFlowSinkFunction extends Function {
abstract predicate hasRemoteFlowSink(FunctionInput input, string description);
/**
* Holds if data put into this sink is transmitted through a socket or stream
* described by `input`. There is no result if none is specified by a
* parameter.
* Holds if data put into this sink is transmitted through a socket described
* by `input`. There is no result if a socket is not specified.
*/
predicate hasSocketInput(FunctionInput input) { none() }
}

View File

@@ -397,7 +397,7 @@ class PaddedType extends Class {
// Support only single inheritance for now. If multiple inheritance is
// supported, be sure to fix up the calls to getABaseClass*() to correctly
// handle the presence of multiple base class subojects with the same type.
not exists(this.getDerivation(1))
not exists(ClassDerivation cd | cd = this.getDerivation(1))
}
/**

View File

@@ -72,7 +72,7 @@ predicate lvalue(Element e) {
or
exists(Cast c | lvalue(c) and e.(Expr).getConversion() = c)
or
e.(Expr).getConversion() instanceof ReferenceToExpr
exists(ReferenceToExpr toref | e.(Expr).getConversion() = toref)
or
// If f is a function-pointer, then the following two
// calls are equivalent: f() and (*f)()

View File

@@ -76,7 +76,7 @@ abstract class BufferWrite extends Expr {
* can be found), specifying the reason for the estimation.
*/
int getMaxData(BufferWriteEstimationReason reason) {
reason instanceof UnspecifiedEstimateReason and result = this.getMaxData()
reason instanceof NoSpecifiedEstimateReason and result = getMaxData()
}
/**
@@ -85,7 +85,7 @@ abstract class BufferWrite extends Expr {
* much smaller (8 bytes) than their true maximum length. This can be
* helpful in determining the cause of a buffer overflow issue.
*/
int getMaxDataLimited() { result = this.getMaxData() }
int getMaxDataLimited() { result = getMaxData() }
/**
* Gets an upper bound to the amount of data that's being written (if one
@@ -94,7 +94,7 @@ abstract class BufferWrite extends Expr {
* than their true maximum length. This can be helpful in determining the
* cause of a buffer overflow issue.
*/
int getMaxDataLimited(BufferWriteEstimationReason reason) { result = this.getMaxData(reason) }
int getMaxDataLimited(BufferWriteEstimationReason reason) { result = getMaxData(reason) }
/**
* Gets the size of a single character of the type this
@@ -159,11 +159,9 @@ class StrCopyBW extends BufferWriteCall {
this.getArgument(this.getParamSrc()).(AnalysedString).getMaxLength() * this.getCharSize()
}
override int getMaxData(BufferWriteEstimationReason reason) {
result = this.getMaxDataImpl(reason)
}
override int getMaxData(BufferWriteEstimationReason reason) { result = getMaxDataImpl(reason) }
override int getMaxData() { result = max(this.getMaxDataImpl(_)) }
override int getMaxData() { result = max(getMaxDataImpl(_)) }
}
/**
@@ -205,11 +203,9 @@ class StrCatBW extends BufferWriteCall {
this.getArgument(this.getParamSrc()).(AnalysedString).getMaxLength() * this.getCharSize()
}
override int getMaxData(BufferWriteEstimationReason reason) {
result = this.getMaxDataImpl(reason)
}
override int getMaxData(BufferWriteEstimationReason reason) { result = getMaxDataImpl(reason) }
override int getMaxData() { result = max(this.getMaxDataImpl(_)) }
override int getMaxData() { result = max(getMaxDataImpl(_)) }
}
/**
@@ -273,11 +269,9 @@ class SprintfBW extends BufferWriteCall {
)
}
override int getMaxData(BufferWriteEstimationReason reason) {
result = this.getMaxDataImpl(reason)
}
override int getMaxData(BufferWriteEstimationReason reason) { result = getMaxDataImpl(reason) }
override int getMaxData() { result = max(this.getMaxDataImpl(_)) }
override int getMaxData() { result = max(getMaxDataImpl(_)) }
private int getMaxDataLimitedImpl(BufferWriteEstimationReason reason) {
exists(FormatLiteral fl |
@@ -287,10 +281,10 @@ class SprintfBW extends BufferWriteCall {
}
override int getMaxDataLimited(BufferWriteEstimationReason reason) {
result = this.getMaxDataLimitedImpl(reason)
result = getMaxDataLimitedImpl(reason)
}
override int getMaxDataLimited() { result = max(this.getMaxDataLimitedImpl(_)) }
override int getMaxDataLimited() { result = max(getMaxDataLimitedImpl(_)) }
}
/**
@@ -388,11 +382,9 @@ class SnprintfBW extends BufferWriteCall {
)
}
override int getMaxData(BufferWriteEstimationReason reason) {
result = this.getMaxDataImpl(reason)
}
override int getMaxData(BufferWriteEstimationReason reason) { result = getMaxDataImpl(reason) }
override int getMaxData() { result = max(this.getMaxDataImpl(_)) }
override int getMaxData() { result = max(getMaxDataImpl(_)) }
private int getMaxDataLimitedImpl(BufferWriteEstimationReason reason) {
exists(FormatLiteral fl |
@@ -402,10 +394,10 @@ class SnprintfBW extends BufferWriteCall {
}
override int getMaxDataLimited(BufferWriteEstimationReason reason) {
result = this.getMaxDataLimitedImpl(reason)
result = getMaxDataLimitedImpl(reason)
}
override int getMaxDataLimited() { result = max(this.getMaxDataLimitedImpl(_)) }
override int getMaxDataLimited() { result = max(getMaxDataLimitedImpl(_)) }
}
/**
@@ -503,11 +495,9 @@ class ScanfBW extends BufferWrite {
)
}
override int getMaxData(BufferWriteEstimationReason reason) {
result = this.getMaxDataImpl(reason)
}
override int getMaxData(BufferWriteEstimationReason reason) { result = getMaxDataImpl(reason) }
override int getMaxData() { result = max(this.getMaxDataImpl(_)) }
override int getMaxData() { result = max(getMaxDataImpl(_)) }
override string getBWDesc() {
exists(FunctionCall fc |
@@ -546,9 +536,7 @@ class RealpathBW extends BufferWriteCall {
this = this // Suppress a compiler warning
}
override int getMaxData(BufferWriteEstimationReason reason) {
result = this.getMaxDataImpl(reason)
}
override int getMaxData(BufferWriteEstimationReason reason) { result = getMaxDataImpl(reason) }
override int getMaxData() { result = max(this.getMaxDataImpl(_)) }
override int getMaxData() { result = max(getMaxDataImpl(_)) }
}

View File

@@ -1,10 +1,7 @@
/*
* Support for tracking tainted data through the program. This is an alias for
* `semmle.code.cpp.ir.dataflow.DefaultTaintTracking` provided for backwards
* compatibility.
* Support for tracking tainted data through the program.
*
* Prefer to use `semmle.code.cpp.dataflow.TaintTracking` or
* `semmle.code.cpp.ir.dataflow.TaintTracking` when designing new queries.
* Prefer to use `semmle.code.cpp.dataflow.TaintTracking` when designing new queries.
*/
import semmle.code.cpp.ir.dataflow.DefaultTaintTracking

View File

@@ -258,7 +258,7 @@ private predicate insideFunctionValueMoveTo(Element src, Element dest) {
format.getConversionChar(sourceArg - ffc.getTarget().getNumberOfParameters()) = ["s", "S"]
)
or
not c.(FormattingFunctionCall).getFormat() instanceof FormatLiteral
not exists(FormatLiteral fl | fl = c.(FormattingFunctionCall).getFormat())
or
not c instanceof FormattingFunctionCall
) and

View File

@@ -271,7 +271,7 @@ class IfStmt extends ConditionalStmt, @stmt_if {
* if (b) { x = 1; }
* ```
*/
predicate hasElse() { exists(this.getElse()) }
predicate hasElse() { exists(Stmt s | this.getElse() = s) }
override string toString() { result = "if (...) ... " }
@@ -357,7 +357,7 @@ class ConstexprIfStmt extends ConditionalStmt, @stmt_constexpr_if {
* if constexpr (b) { x = 1; }
* ```
*/
predicate hasElse() { exists(this.getElse()) }
predicate hasElse() { exists(Stmt s | this.getElse() = s) }
override string toString() { result = "if constexpr (...) ... " }

View File

@@ -6,22 +6,122 @@
*/
class Person extends string {
Person() {
this =
[
"Ronil", "Dina", "Ravi", "Bruce", "Jo", "Aida", "Esme", "Charlie", "Fred", "Meera", "Maya",
"Chad", "Tiana", "Laura", "George", "Will", "Mary", "Almira", "Susannah", "Rhoda",
"Cynthia", "Eunice", "Olive", "Virginia", "Angeline", "Helen", "Cornelia", "Harriet",
"Mahala", "Abby", "Margaret", "Deb", "Minerva", "Severus", "Lavina", "Adeline", "Cath",
"Elisa", "Lucretia", "Anne", "Eleanor", "Joanna", "Adam", "Agnes", "Rosanna", "Clara",
"Melissa", "Amy", "Isabel", "Jemima", "Cordelia", "Melinda", "Delila", "Jeremiah", "Elijah",
"Hester", "Walter", "Oliver", "Hugh", "Aaron", "Reuben", "Eli", "Amos", "Augustus",
"Theodore", "Ira", "Timothy", "Cyrus", "Horace", "Simon", "Asa", "Frank", "Nelson",
"Leonard", "Harrison", "Anthony", "Louis", "Milton", "Noah", "Cornelius", "Abdul", "Warren",
"Harvey", "Dennis", "Wesley", "Sylvester", "Gilbert", "Sullivan", "Edmund", "Wilson",
"Perry", "Matthew", "Simba", "Nala", "Rafiki", "Shenzi", "Ernest", "Gertrude", "Oscar",
"Lilian", "Raymond", "Elgar", "Elmer", "Herbert", "Maude", "Mae", "Otto", "Edwin",
"Ophelia", "Parsley", "Sage", "Rosemary", "Thyme", "Garfunkel", "King Basil", "Stephen"
]
this = "Ronil" or
this = "Dina" or
this = "Ravi" or
this = "Bruce" or
this = "Jo" or
this = "Aida" or
this = "Esme" or
this = "Charlie" or
this = "Fred" or
this = "Meera" or
this = "Maya" or
this = "Chad" or
this = "Tiana" or
this = "Laura" or
this = "George" or
this = "Will" or
this = "Mary" or
this = "Almira" or
this = "Susannah" or
this = "Rhoda" or
this = "Cynthia" or
this = "Eunice" or
this = "Olive" or
this = "Virginia" or
this = "Angeline" or
this = "Helen" or
this = "Cornelia" or
this = "Harriet" or
this = "Mahala" or
this = "Abby" or
this = "Margaret" or
this = "Deb" or
this = "Minerva" or
this = "Severus" or
this = "Lavina" or
this = "Adeline" or
this = "Cath" or
this = "Elisa" or
this = "Lucretia" or
this = "Anne" or
this = "Eleanor" or
this = "Joanna" or
this = "Adam" or
this = "Agnes" or
this = "Rosanna" or
this = "Clara" or
this = "Melissa" or
this = "Amy" or
this = "Isabel" or
this = "Jemima" or
this = "Cordelia" or
this = "Melinda" or
this = "Delila" or
this = "Jeremiah" or
this = "Elijah" or
this = "Hester" or
this = "Walter" or
this = "Oliver" or
this = "Hugh" or
this = "Aaron" or
this = "Reuben" or
this = "Eli" or
this = "Amos" or
this = "Augustus" or
this = "Theodore" or
this = "Ira" or
this = "Timothy" or
this = "Cyrus" or
this = "Horace" or
this = "Simon" or
this = "Asa" or
this = "Frank" or
this = "Nelson" or
this = "Leonard" or
this = "Harrison" or
this = "Anthony" or
this = "Louis" or
this = "Milton" or
this = "Noah" or
this = "Cornelius" or
this = "Abdul" or
this = "Warren" or
this = "Harvey" or
this = "Dennis" or
this = "Wesley" or
this = "Sylvester" or
this = "Gilbert" or
this = "Sullivan" or
this = "Edmund" or
this = "Wilson" or
this = "Perry" or
this = "Matthew" or
this = "Simba" or
this = "Nala" or
this = "Rafiki" or
this = "Shenzi" or
this = "Ernest" or
this = "Gertrude" or
this = "Oscar" or
this = "Lilian" or
this = "Raymond" or
this = "Elgar" or
this = "Elmer" or
this = "Herbert" or
this = "Maude" or
this = "Mae" or
this = "Otto" or
this = "Edwin" or
this = "Ophelia" or
this = "Parsley" or
this = "Sage" or
this = "Rosemary" or
this = "Thyme" or
this = "Garfunkel" or
this = "King Basil" or
this = "Stephen"
}
/** Gets the hair color of the person. If the person is bald, there is no result. */
@@ -836,12 +936,25 @@ class Person extends string {
/** Holds if the person is deceased. */
predicate isDeceased() {
this =
[
"Ernest", "Gertrude", "Oscar", "Lilian", "Edwin", "Raymond", "Elgar", "Elmer", "Herbert",
"Maude", "Mae", "Otto", "Ophelia", "Parsley", "Sage", "Rosemary", "Thyme", "Garfunkel",
"King Basil"
]
this = "Ernest" or
this = "Gertrude" or
this = "Oscar" or
this = "Lilian" or
this = "Edwin" or
this = "Raymond" or
this = "Elgar" or
this = "Elmer" or
this = "Herbert" or
this = "Maude" or
this = "Mae" or
this = "Otto" or
this = "Ophelia" or
this = "Parsley" or
this = "Sage" or
this = "Rosemary" or
this = "Thyme" or
this = "Garfunkel" or
this = "King Basil"
}
/** Gets a parent of the person (alive or deceased). */
@@ -1082,7 +1195,12 @@ class Person extends string {
}
/** Holds if the person is allowed in the region. Initially, all villagers are allowed in every region. */
predicate isAllowedIn(string region) { region = ["north", "south", "east", "west"] }
predicate isAllowedIn(string region) {
region = "north" or
region = "south" or
region = "east" or
region = "west"
}
}
/** Returns a parent of the person. */

View File

@@ -18,7 +18,6 @@ where
not lv1.isCompilerGenerated() and
not lv2.isCompilerGenerated() and
not lv1.getParentScope().(BlockStmt).isInMacroExpansion() and
not lv2.getParentScope().(BlockStmt).isInMacroExpansion() and
not lv1.getName() = "(unnamed local variable)"
not lv2.getParentScope().(BlockStmt).isInMacroExpansion()
select lv1, "Variable " + lv1.getName() + " hides another variable of the same name (on $@).", lv2,
"line " + lv2.getLocation().getStartLine().toString()

View File

@@ -1,7 +1,3 @@
## 0.0.7
## 0.0.6
## 0.0.5
### New Queries

View File

@@ -30,8 +30,8 @@ where
// the next statement isn't breaking out of a switch
not s.(BreakStmt).getBreakable() instanceof SwitchStmt and
// the next statement isn't a loop that can be jumped into
not s.(Loop).getStmt().getAChild*() instanceof LabelStmt and
not s.(Loop).getStmt().getAChild*() instanceof SwitchCase and
not exists(LabelStmt ls | s.(Loop).getStmt().getAChild*() = ls) and
not exists(SwitchCase sc | s.(Loop).getStmt().getAChild*() = sc) and
// no preprocessor logic applies
not functionContainsPreprocCode(js.getEnclosingFunction())
select js, "This statement makes $@ unreachable.", s, s.toString()

View File

@@ -55,7 +55,7 @@ abstract class LeapYearFieldAccess extends YearFieldAccess {
op.getAnOperand() = this and
(
op instanceof AssignArithmeticOperation or
op.getAnOperand() instanceof BinaryArithmeticOperation or
exists(BinaryArithmeticOperation bao | bao = op.getAnOperand()) or
op instanceof CrementOperation
)
)
@@ -212,7 +212,9 @@ class ChecksForLeapYearFunctionCall extends FunctionCall {
class LeapYearCheckConfiguration extends DataFlow::Configuration {
LeapYearCheckConfiguration() { this = "LeapYearCheckConfiguration" }
override predicate isSource(DataFlow::Node source) { source.asExpr() instanceof VariableAccess }
override predicate isSource(DataFlow::Node source) {
exists(VariableAccess va | va = source.asExpr())
}
override predicate isSink(DataFlow::Node sink) {
exists(ChecksForLeapYearFunctionCall fc | sink.asExpr() = fc.getAnArgument())

View File

@@ -23,10 +23,6 @@ DeclStmt declWithNoInit(LocalVariable v) {
not exists(v.getInitializer())
}
/**
* Control flow reachability from a buffer that is not not null terminated to a
* sink that requires null termination.
*/
class ImproperNullTerminationReachability extends StackVariableReachabilityWithReassignment {
ImproperNullTerminationReachability() { this = "ImproperNullTerminationReachability" }
@@ -56,44 +52,12 @@ class ImproperNullTerminationReachability extends StackVariableReachabilityWithR
override predicate isBarrier(ControlFlowNode node, StackVariable v) {
exprDefinition(v, node, _) or
mayAddNullTerminator(node, v.getAnAccess()) or
node.(AddressOfExpr).getOperand() = v.getAnAccess() or // address taken
isSinkActual(node, v) // only report first use
}
}
/**
* Flow from a place where null termination is added, to a sink of
* `ImproperNullTerminationReachability`. This was previously implemented as a
* simple barrier in `ImproperNullTerminationReachability`, but there were
* false positive results involving multiple paths from source to sink. We'd
* prefer to report only the results we are sure of.
*/
class NullTerminationReachability extends StackVariableReachabilityWithReassignment {
NullTerminationReachability() { this = "NullTerminationReachability" }
override predicate isSourceActual(ControlFlowNode node, StackVariable v) {
mayAddNullTerminator(node, v.getAnAccess()) or // null termination
node.(AddressOfExpr).getOperand() = v.getAnAccess() // address taken (possible null termination)
}
override predicate isSinkActual(ControlFlowNode node, StackVariable v) {
// have the same sinks as `ImproperNullTerminationReachability`.
exists(ImproperNullTerminationReachability r | r.isSinkActual(node, v))
}
override predicate isBarrier(ControlFlowNode node, StackVariable v) {
// don't look further back than the source, or further forward than the sink
exists(ImproperNullTerminationReachability r | r.isSourceActual(node, v)) or
exists(ImproperNullTerminationReachability r | r.isSinkActual(node, v))
}
}
from
ImproperNullTerminationReachability reaches, NullTerminationReachability nullTermReaches,
ControlFlowNode source, LocalVariable v, VariableAccess sink
where
reaches.reaches(source, v, sink) and
not exists(ControlFlowNode termination |
nullTermReaches.reaches(termination, _, sink) and
termination != source
)
select sink, "Variable $@ may not be null terminated.", v, v.getName()
from ImproperNullTerminationReachability r, LocalVariable v, VariableAccess va
where r.reaches(_, v, va)
select va, "Variable $@ may not be null terminated.", v, v.getName()

View File

@@ -4,4 +4,4 @@ Record* fixRecord(Record* r) {
myRecord.fix();
return &myRecord; //returns reference to myRecord, which is a stack-allocated object
}
}

View File

@@ -3,172 +3,68 @@
* @description A function returns a pointer to a stack-allocated region of
* memory. This memory is deallocated at the end of the function,
* which may lead the caller to dereference a dangling pointer.
* @kind path-problem
* @kind problem
* @id cpp/return-stack-allocated-memory
* @problem.severity warning
* @security-severity 9.3
* @precision high
* @tags reliability
* security
* external/cwe/cwe-825
*/
import cpp
// We don't actually use the global value numbering library in this query, but without it we end up
// recomputing the IR.
private import semmle.code.cpp.valuenumbering.GlobalValueNumbering
import semmle.code.cpp.ir.IR
import semmle.code.cpp.ir.dataflow.DataFlow::DataFlow
import semmle.code.cpp.dataflow.EscapesTree
import semmle.code.cpp.models.interfaces.PointerWrapper
import semmle.code.cpp.dataflow.DataFlow
/** Holds if `f` has a name that we intrepret as evidence of intentionally returning the value of the stack pointer. */
predicate intentionallyReturnsStackPointer(Function f) {
f.getName().toLowerCase().matches(["%stack%", "%sp%"])
/**
* Holds if `n1` may flow to `n2`, ignoring flow through fields because these
* are currently modeled as an overapproximation that assumes all objects may
* alias.
*/
predicate conservativeDataFlowStep(DataFlow::Node n1, DataFlow::Node n2) {
DataFlow::localFlowStep(n1, n2) and
not n2.asExpr() instanceof FieldAccess and
not hasNontrivialConversion(n2.asExpr())
}
/**
* Holds if `source` is a node that represents the use of a stack variable
* Holds if `e` has a conversion that changes it from lvalue to pointer or
* back. As the data-flow library does not support conversions, we cannot track
* data flow through such expressions.
*/
predicate isSource(Node source) {
exists(VariableAddressInstruction var, Function func |
var = source.asInstruction() and
func = var.getEnclosingFunction() and
var.getASTVariable() instanceof StackVariable and
// Pointer-to-member types aren't properly handled in the dbscheme.
not var.getResultType() instanceof PointerToMemberType and
// Rule out FPs caused by extraction errors.
not any(ErrorExpr e).getEnclosingFunction() = func and
not intentionallyReturnsStackPointer(func)
)
}
/**
* Holds if `sink` is a node that represents the `StoreInstruction` that is subsequently used in
* a `ReturnValueInstruction`. We use the `StoreInstruction` instead of the instruction that defines the
* `ReturnValueInstruction`'s source value oprand because the former has better location information.
*/
predicate isSink(Node sink) {
exists(StoreInstruction store |
store.getDestinationAddress().(VariableAddressInstruction).getIRVariable() instanceof
IRReturnVariable and
sink.asOperand() = store.getSourceValueOperand()
)
}
/** Holds if `node1` _must_ flow to `node2`. */
predicate step(Node node1, Node node2) {
instructionToOperandStep(node1.asInstruction(), node2.asOperand())
or
operandToInstructionStep(node1.asOperand(), node2.asInstruction())
}
predicate instructionToOperandStep(Instruction instr, Operand operand) { operand.getDef() = instr }
/**
* Holds if `operand` flows to the result of `instr`.
*
* This predicate ignores flow through `PhiInstruction`s to create a 'must flow' relation. It also
* intentionally conflates addresses of fields and their object, and pointer offsets with their
* base pointer as this allows us to detect cases where an object's address flows to a return statement
* via a field. For example:
*
* ```cpp
* struct S { int x, y };
* int* test() {
* S s;
* return &s.x; // BAD: &s.x is an address of a variable on the stack.
* }
* ```
*/
predicate operandToInstructionStep(Operand operand, Instruction instr) {
instr.(CopyInstruction).getSourceValueOperand() = operand
or
instr.(ConvertInstruction).getUnaryOperand() = operand
or
instr.(CheckedConvertOrNullInstruction).getUnaryOperand() = operand
or
instr.(InheritanceConversionInstruction).getUnaryOperand() = operand
or
instr.(FieldAddressInstruction).getObjectAddressOperand() = operand
or
instr.(PointerOffsetInstruction).getLeftOperand() = operand
}
/** Holds if a source node flows to `n`. */
predicate branchlessLocalFlow0(Node n) {
isSource(n)
or
exists(Node mid |
branchlessLocalFlow0(mid) and
step(mid, n)
)
}
/** Holds if `n` is reachable through some source node, and `n` also eventually reaches a sink. */
predicate branchlessLocalFlow1(Node n) {
branchlessLocalFlow0(n) and
(
isSink(n)
predicate hasNontrivialConversion(Expr e) {
e instanceof Conversion and
not (
e instanceof Cast
or
exists(Node mid |
branchlessLocalFlow1(mid) and
step(n, mid)
e instanceof ParenthesisExpr
)
or
// A smart pointer can be stack-allocated while the data it points to is heap-allocated.
// So we exclude such "conversions" from this predicate.
e = any(PointerWrapper wrapper).getAnUnwrapperFunction().getACallToThisFunction()
or
hasNontrivialConversion(e.getConversion())
}
from StackVariable var, VariableAccess va, ReturnStmt r
where
not var.getUnspecifiedType() instanceof ReferenceType and
not r.isFromUninstantiatedTemplate(_) and
va = var.getAnAccess() and
(
// To check if the address escapes directly from `e` in `return e`, we need
// to check the fully-converted `e` in case there are implicit
// array-to-pointer conversions or reference conversions.
variableAddressEscapesTree(va, r.getExpr().getFullyConverted())
or
// The data flow library doesn't support conversions, so here we check that
// the address escapes into some expression `pointerToLocal`, which flows
// in one or more steps to a returned expression.
exists(Expr pointerToLocal |
variableAddressEscapesTree(va, pointerToLocal.getFullyConverted()) and
not hasNontrivialConversion(pointerToLocal) and
conservativeDataFlowStep+(DataFlow::exprNode(pointerToLocal), DataFlow::exprNode(r.getExpr()))
)
)
}
newtype TLocalPathNode =
TLocalPathNodeMid(Node n) {
branchlessLocalFlow1(n) and
(
isSource(n) or
exists(LocalPathNodeMid mid | step(mid.getNode(), n))
)
}
abstract class LocalPathNode extends TLocalPathNode {
Node n;
/** Gets the underlying node. */
Node getNode() { result = n }
/** Gets a textual representation of this node. */
string toString() { result = n.toString() }
/** Gets the location of this element. */
Location getLocation() { result = n.getLocation() }
/** Gets a successor `LocalPathNode`, if any. */
LocalPathNode getASuccessor() { step(this.getNode(), result.getNode()) }
}
class LocalPathNodeMid extends LocalPathNode, TLocalPathNodeMid {
LocalPathNodeMid() { this = TLocalPathNodeMid(n) }
}
class LocalPathNodeSink extends LocalPathNodeMid {
LocalPathNodeSink() { isSink(this.getNode()) }
}
/**
* Holds if `source` is a source node, `sink` is a sink node, and there's flow
* from `source` to `sink` using `step` relation.
*/
predicate hasFlow(LocalPathNode source, LocalPathNodeSink sink) {
isSource(source.getNode()) and
source.getASuccessor+() = sink
}
predicate reach(LocalPathNode n) { n instanceof LocalPathNodeSink or reach(n.getASuccessor()) }
query predicate edges(LocalPathNode a, LocalPathNode b) { a.getASuccessor() = b and reach(b) }
query predicate nodes(LocalPathNode n, string key, string val) {
reach(n) and key = "semmle.label" and val = n.toString()
}
from LocalPathNode source, LocalPathNodeSink sink, VariableAddressInstruction var
where
hasFlow(source, sink) and
source.getNode().asInstruction() = var
select sink.getNode(), source, sink, "May return stack-allocated memory from $@.", var.getAST(),
var.getAST().toString()
select r, "May return stack-allocated memory from $@.", va, va.toString()

View File

@@ -18,5 +18,8 @@
<p>To fix the problem, either the second argument to <code>snprintf</code> should be changed to 80, or the buffer extended to 256 characters. A further improvement is to use a preprocessor define so that the size is only specified in one place, potentially preventing future recurrence of this issue.</p>
</example>
<include src="OverrunWriteReferences.inc.qhelp" />
<references>
</references>
</qhelp>

View File

@@ -1,9 +1,9 @@
void sayHello(uint32_t userId)
void sayHello()
{
char buffer[18];
char buffer[10];
// BAD: this message overflows the buffer if userId >= 10000
sprintf(buffer, "Hello, user %d!", userId);
// BAD: this message overflows the buffer
strcpy(buffer, "Hello, world!");
MessageBox(hWnd, buffer, "New Message", MB_OK);
}

View File

@@ -6,20 +6,30 @@
<p>The program performs a buffer copy or write operation with no upper limit on the size of the copy, and it appears that certain inputs will cause a buffer overflow to occur in this case. In addition to causing program instability, techniques exist which may allow an attacker to use this vulnerability to execute arbitrary code.</p>
</overview>
<include src="OverrunWriteRecommendation.inc.qhelp" />
<recommendation>
<p>Always control the length of buffer copy and buffer write operations. <code>strncpy</code> should be used over <code>strcpy</code>, <code>snprintf</code> over <code>sprintf</code>, and in other cases 'n-variant' functions should be preferred.</p>
</recommendation>
<example>
<sample src="OverrunWrite.c" />
<p>In this example, the call to <code>sprintf</code> writes a message of 14 characters (including the terminating null) plus the length of the string conversion of `userId` into a buffer with space for just 18 characters. As such, if `userId` is greater or equal to `10000`, the last characters overflow the buffer resulting in undefined behavior.</p>
<p>In this example, the call to <code>strcpy</code> copies a message of 14 characters (including the terminating null) into a buffer with space for just 10 characters. As such, the last four characters overflow the buffer resulting in undefined behavior.</p>
<p>To fix this issue these changes should be made:</p>
<p>To fix this issue three changes should be made:</p>
<ul>
<li>Control the size of the buffer by declaring it with a compile time constant.</li>
<li>Preferably, replace the call to <code>sprintf</code> with <code>snprintf</code>, using the defined constant size of the buffer or `sizeof(buffer)` as maximum length to write. This will prevent the buffer overflow.</li>
<li>Optionally, if `userId` is expected to be less than `10000`, then return or throw an error if `userId` is out of bounds.</li>
<li>Otherwise, consider increasing the buffer size to at least 25 characters, so that the message is displayed correctly regardless of the value of `userId`.</li>
<li>Control the size of the buffer using a preprocessor define.</li>
<li>Replace the call to <code>strcpy</code> with <code>strncpy</code>, specifying the define as the maximum length to copy. This will prevent the buffer overflow.</li>
<li>Consider increasing the buffer size, say to 20 characters, so that the message is displayed correctly.</li>
</ul>
</example>
<include src="OverrunWriteReferences.inc.qhelp" />
<references>
<li>CERT C Coding Standard: <a href="https://www.securecoding.cert.org/confluence/display/c/STR31-C.+Guarantee+that+storage+for+strings+has+sufficient+space+for+character+data+and+the+null+terminator">STR31-C. Guarantee that storage for strings has sufficient space for character data and the null terminator</a>.</li>
<!-- LocalWords: preprocessor CWE STR
-->
</references>
</qhelp>

View File

@@ -21,14 +21,12 @@ import semmle.code.cpp.commons.Alloc
* See CWE-120/UnboundedWrite.ql for a summary of CWE-120 alert cases.
*/
from BufferWrite bw, Expr dest, int destSize, int estimated, BufferWriteEstimationReason reason
from BufferWrite bw, Expr dest, int destSize, int estimated
where
not bw.hasExplicitLimit() and // has no explicit size limit
dest = bw.getDest() and
destSize = getBufferSize(dest, _) and
estimated = bw.getMaxDataLimited(reason) and
// we exclude ValueFlowAnalysis as it is reported in cpp/very-likely-overruning-write
not reason instanceof ValueFlowAnalysis and
estimated = bw.getMaxDataLimited(_) and
// we can deduce that too much data may be copied (even without
// long '%f' conversions)
estimated > destSize

View File

@@ -6,19 +6,30 @@
<p>The program performs a buffer copy or write operation that includes one or more float to string conversions (i.e. the %f format specifier), which may overflow the destination buffer if extreme inputs are given. In addition to causing program instability, techniques exist which may allow an attacker to use this vulnerability to execute arbitrary code.</p>
</overview>
<include src="OverrunWriteRecommendation.inc.qhelp" />
<recommendation>
<p>Always control the length of buffer copy and buffer write operations. <code>strncpy</code> should be used over <code>strcpy</code>, <code>snprintf</code> over <code>sprintf</code>, and in other cases 'n-variant' functions should be preferred.</p>
</recommendation>
<example>
<sample src="OverrunWriteFloat.c" />
<p>In this example, the call to <code>sprintf</code> contains a <code>%f</code> format specifier. Though a 256 character buffer has been allowed, it is not sufficient for the most extreme floating point inputs. For example the representation of double value 1e304 (that is 1 with 304 zeroes after it) will overflow a buffer of this length.</p>
<p>In this example, the call to <code>sprintf</code> contains a %f format specifier. Though a 256 character buffer has been allowed, it is not sufficient for the most extreme floating point inputs. For example the representation of double value 1e304 (that is 1 with 304 zeroes after it) will overflow a buffer of this length.</p>
<p>To fix this issue three changes should be made:</p>
<ul>
<li>Control the size of the buffer using a preprocessor define.</li>
<li>Replace the call to <code>sprintf</code> with <code>snprintf</code>, specifying the define as the maximum length to copy. This will prevent the buffer overflow.</li>
<li>Consider using the <code>%g</code> format specifier instead of <code>%f</code>.</li>
<li>Consider using the %g format specifier instead of %f.</li>
</ul>
</example>
<include src="OverrunWriteReferences.inc.qhelp" />
<references>
<li>CERT C Coding
Standard: <a href="https://www.securecoding.cert.org/confluence/display/c/STR31-C.+Guarantee+that+storage+for+strings+has+sufficient+space+for+character+data+and+the+null+terminator">STR31-C. Guarantee
that storage for strings has sufficient space for character data and
the null terminator</a>.</li>
</references>
</qhelp>

View File

@@ -1,8 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<recommendation>
<p>Always control the length of buffer copy and buffer write operations. <code>strncpy</code> should be used over <code>strcpy</code>, <code>snprintf</code> over <code>sprintf</code>, and in other cases 'n-variant' functions should be preferred.</p>
</recommendation>
</qhelp>

View File

@@ -1,14 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<references>
<li>CERT C Coding Standard: <a href="https://www.securecoding.cert.org/confluence/display/c/STR31-C.+Guarantee+that+storage+for+strings+has+sufficient+space+for+character+data+and+the+null+terminator">STR31-C. Guarantee that storage for strings has sufficient space for character data and the null terminator</a>.</li>
<li>CERT C++ Coding Standard: <a href="https://www.securecoding.cert.org/confluence/display/cplusplus/STR50-CPP.+Guarantee+that+storage+for+strings+has+sufficient+space+for+character+data+and+the+null+terminator">STR50-CPP. Guarantee that storage for strings has sufficient space for character data and the null terminator</a>.</li>
<!-- LocalWords: preprocessor CWE STR
-->
</references>
</qhelp>

View File

@@ -6,7 +6,10 @@
<p>The program performs a buffer copy or write operation with no upper limit on the size of the copy. An unexpectedly long input that reaches this code will cause the buffer to overflow. In addition to causing program instability, techniques exist which may allow an attacker to use this vulnerability to execute arbitrary code.</p>
</overview>
<include src="OverrunWriteRecommendation.inc.qhelp" />
<recommendation>
<p>Always control the length of buffer copy and buffer write operations. <code>strncpy</code> should be used over <code>strcpy</code>, <code>snprintf</code> over <code>sprintf</code> etc. In general 'n-variant' functions should be preferred.</p>
</recommendation>
<example>
<sample src="UnboundedWrite.c" />
@@ -15,5 +18,13 @@
<p>To fix the problem the call to <code>sprintf</code> should be replaced with <code>snprintf</code>, specifying a maximum length of 80 characters.</p>
</example>
<include src="OverrunWriteReferences.inc.qhelp" />
<references>
<li>CERT C++ Coding Standard: <a href="https://www.securecoding.cert.org/confluence/display/cplusplus/STR50-CPP.+Guarantee+that+storage+for+strings+has+sufficient+space+for+character+data+and+the+null+terminator">STR50-CPP. Guarantee that storage for strings has sufficient space for character data and the null terminator</a>.</li>
<!-- LocalWords: CWE STR
-->
</references>
</qhelp>

View File

@@ -33,7 +33,7 @@ import TaintedWithPath
* hasExplicitLimit() exists(getMaxData()) exists(getBufferSize(bw.getDest(), _))) handled by
* NO NO either UnboundedWrite.ql isUnboundedWrite()
* NO YES NO UnboundedWrite.ql isMaybeUnboundedWrite()
* NO YES YES VeryLikelyOverrunWrite.ql, OverrunWrite.ql, OverrunWriteFloat.ql
* NO YES YES OverrunWrite.ql, OverrunWriteFloat.ql
* YES either YES BadlyBoundedWrite.ql
* YES either NO (assumed OK)
*/

View File

@@ -1,14 +0,0 @@
int sayHello(uint32_t userId)
{
char buffer[17];
if (userId > 9999) return USER_ID_OUT_OF_BOUNDS;
// BAD: this message overflows the buffer if userId >= 1000,
// as no space for the null terminator was accounted for
sprintf(buffer, "Hello, user %d!", userId);
MessageBox(hWnd, buffer, "New Message", MB_OK);
return SUCCESS;
}

View File

@@ -1,24 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>The program performs a buffer copy or write operation with no upper limit on the size of the copy. By analyzing the bounds of the expressions involved, it appears that certain inputs will cause a buffer overflow to occur in this case. In addition to causing program instability, techniques exist which may allow an attacker to use this vulnerability to execute arbitrary code.</p>
</overview>
<include src="OverrunWriteRecommendation.inc.qhelp" />
<example>
<sample src="VeryLikelyOverrunWrite.c" />
<p>In this example, the call to <code>sprintf</code> writes a message of 14 characters (including the terminating null) plus the length of the string conversion of `userId` into a buffer with space for just 17 characters. While `userId` is checked to occupy no more than 4 characters when converted, there is no space in the buffer for the terminating null character if `userId >= 1000`. In this case, the null character overflows the buffer resulting in undefined behavior.</p>
<p>To fix this issue these changes should be made:</p>
<ul>
<li>Control the size of the buffer by declaring it with a compile time constant.</li>
<li>Preferably, replace the call to <code>sprintf</code> with <code>snprintf</code>, using the defined constant size of the buffer or `sizeof(buffer)` as maximum length to write. This will prevent the buffer overflow.</li>
<li>Increasing the buffer size to account for the full range of `userId` and the terminating null character.</li>
</ul>
</example>
<include src="OverrunWriteReferences.inc.qhelp" />
</qhelp>

View File

@@ -1,34 +0,0 @@
/**
* @name Likely overrunning write
* @description Buffer write operations that do not control the length
* of data written may overflow
* @kind problem
* @problem.severity error
* @security-severity 9.3
* @precision high
* @id cpp/very-likely-overrunning-write
* @tags reliability
* security
* external/cwe/cwe-120
* external/cwe/cwe-787
* external/cwe/cwe-805
*/
import semmle.code.cpp.security.BufferWrite
import semmle.code.cpp.commons.Alloc
/*
* See CWE-120/UnboundedWrite.ql for a summary of CWE-120 alert cases.
*/
from BufferWrite bw, Expr dest, int destSize, int estimated, ValueFlowAnalysis reason
where
not bw.hasExplicitLimit() and // has no explicit size limit
dest = bw.getDest() and
destSize = getBufferSize(dest, _) and
estimated = bw.getMaxDataLimited(reason) and
// we can deduce from non-trivial range analysis that too much data may be copied
estimated > destSize
select bw,
"This '" + bw.getBWDesc() + "' operation requires " + estimated +
" bytes but the destination is only " + destSize + " bytes."

View File

@@ -19,7 +19,6 @@ import cpp
import semmle.code.cpp.dataflow.DataFlow
import semmle.code.cpp.models.interfaces.ArrayFunction
import semmle.code.cpp.models.interfaces.Allocation
import semmle.code.cpp.commons.NullTermination
predicate terminationProblem(AllocationExpr malloc, string msg) {
// malloc(strlen(...))
@@ -36,7 +35,13 @@ predicate terminationProblem(AllocationExpr malloc, string msg) {
af.hasArrayWithUnknownSize(arg)
or
// flows into string argument to a formatting function (such as `printf`)
formatArgumentMustBeNullTerminated(fc, fc.getArgument(arg))
exists(int n, FormatLiteral fl |
fc.getArgument(arg) = fc.(FormattingFunctionCall).getConversionArgument(n) and
fl = fc.(FormattingFunctionCall).getFormat() and
fl.getConversionType(n) instanceof PointerType and // `%s`, `%ws` etc
not fl.getConversionType(n) instanceof VoidPointerType and // exclude: `%p`
not fl.hasPrecision(n) // exclude: `%.*s`
)
)
) and
msg = "This allocation does not include space to null-terminate the string."

View File

@@ -22,13 +22,11 @@ import semmle.code.cpp.dataflow.DataFlow
* Holds if `sub` is guarded by a condition which ensures that
* `left >= right`.
*/
pragma[nomagic]
pragma[noinline]
predicate isGuarded(SubExpr sub, Expr left, Expr right) {
exprIsSubLeftOrLess(pragma[only_bind_into](sub), _) and // Manual magic
exists(GuardCondition guard, int k, BasicBlock bb |
pragma[only_bind_into](bb) = sub.getBasicBlock() and
guard.controls(pragma[only_bind_into](bb), _) and
guard.ensuresLt(left, right, k, bb, false) and
exists(GuardCondition guard, int k |
guard.controls(sub.getBasicBlock(), _) and
guard.ensuresLt(left, right, k, sub.getBasicBlock(), false) and
k >= 0
)
}
@@ -38,56 +36,47 @@ predicate isGuarded(SubExpr sub, Expr left, Expr right) {
* `sub.getLeftOperand()`.
*/
predicate exprIsSubLeftOrLess(SubExpr sub, DataFlow::Node n) {
interestingSubExpr(sub, _) and // Manual magic
(
n.asExpr() = sub.getLeftOperand()
or
exists(DataFlow::Node other |
// dataflow
exprIsSubLeftOrLess(sub, other) and
(
DataFlow::localFlowStep(n, other) or
DataFlow::localFlowStep(other, n)
)
)
or
exists(DataFlow::Node other |
// guard constraining `sub`
exprIsSubLeftOrLess(sub, other) and
isGuarded(sub, other.asExpr(), n.asExpr()) // other >= n
)
or
exists(DataFlow::Node other, float p, float q |
// linear access of `other`
exprIsSubLeftOrLess(sub, other) and
linearAccess(n.asExpr(), other.asExpr(), p, q) and // n = p * other + q
p <= 1 and
q <= 0
)
or
exists(DataFlow::Node other, float p, float q |
// linear access of `n`
exprIsSubLeftOrLess(sub, other) and
linearAccess(other.asExpr(), n.asExpr(), p, q) and // other = p * n + q
p >= 1 and
q >= 0
n.asExpr() = sub.getLeftOperand()
or
exists(DataFlow::Node other |
// dataflow
exprIsSubLeftOrLess(sub, other) and
(
DataFlow::localFlowStep(n, other) or
DataFlow::localFlowStep(other, n)
)
)
}
predicate interestingSubExpr(SubExpr sub, RelationalOperation ro) {
not isFromMacroDefinition(sub) and
ro.getLesserOperand().getValue().toInt() = 0 and
ro.getGreaterOperand() = sub and
sub.getFullyConverted().getUnspecifiedType().(IntegralType).isUnsigned() and
// generally catches false positives involving constants
exprMightOverflowNegatively(sub.getFullyConverted())
or
exists(DataFlow::Node other |
// guard constraining `sub`
exprIsSubLeftOrLess(sub, other) and
isGuarded(sub, other.asExpr(), n.asExpr()) // other >= n
)
or
exists(DataFlow::Node other, float p, float q |
// linear access of `other`
exprIsSubLeftOrLess(sub, other) and
linearAccess(n.asExpr(), other.asExpr(), p, q) and // n = p * other + q
p <= 1 and
q <= 0
)
or
exists(DataFlow::Node other, float p, float q |
// linear access of `n`
exprIsSubLeftOrLess(sub, other) and
linearAccess(other.asExpr(), n.asExpr(), p, q) and // other = p * n + q
p >= 1 and
q >= 0
)
}
from RelationalOperation ro, SubExpr sub
where
interestingSubExpr(sub, ro) and
not isFromMacroDefinition(ro) and
// generally catches false positives where there's a relation between the left and right operands
not exprIsSubLeftOrLess(sub, DataFlow::exprNode(sub.getRightOperand()))
not isFromMacroDefinition(sub) and
ro.getLesserOperand().getValue().toInt() = 0 and
ro.getGreaterOperand() = sub and
sub.getFullyConverted().getUnspecifiedType().(IntegralType).isUnsigned() and
exprMightOverflowNegatively(sub.getFullyConverted()) and // generally catches false positives involving constants
not exprIsSubLeftOrLess(sub, DataFlow::exprNode(sub.getRightOperand())) // generally catches false positives where there's a relation between the left and right operands
select ro, "Unsigned subtraction can never be negative."

View File

@@ -12,33 +12,23 @@
*/
import cpp
import semmle.code.cpp.security.BufferWrite as BufferWrite
import semmle.code.cpp.security.BufferWrite
import semmle.code.cpp.security.TaintTracking
import semmle.code.cpp.security.SensitiveExprs
import semmle.code.cpp.security.FlowSources
import semmle.code.cpp.ir.dataflow.TaintTracking
import DataFlow::PathGraph
import TaintedWithPath
/**
* Taint flow from user input to a buffer write.
*/
class ToBufferConfiguration extends TaintTracking::Configuration {
ToBufferConfiguration() { this = "ToBufferConfiguration" }
override predicate isSource(DataFlow::Node source) { source instanceof FlowSource }
override predicate isSink(DataFlow::Node sink) {
exists(BufferWrite::BufferWrite w | w.getASource() = sink.asExpr())
}
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element tainted) { exists(BufferWrite w | w.getASource() = tainted) }
}
from
ToBufferConfiguration config, BufferWrite::BufferWrite w, DataFlow::PathNode sourceNode,
DataFlow::PathNode sinkNode, FlowSource source, SensitiveExpr dest
BufferWrite w, Expr taintedArg, Expr taintSource, PathNode sourceNode, PathNode sinkNode,
string taintCause, SensitiveExpr dest
where
config.hasFlowPath(sourceNode, sinkNode) and
sourceNode.getNode() = source and
w.getASource() = sinkNode.getNode().asExpr() and
taintedWithPath(taintSource, taintedArg, sourceNode, sinkNode) and
isUserInput(taintSource, taintCause) and
w.getASource() = taintedArg and
dest = w.getDest()
select w, sourceNode, sinkNode,
"This write into buffer '" + dest.toString() + "' may contain unencrypted data from $@", source,
"user input (" + source.getSourceType() + ")"
"This write into buffer '" + dest.toString() + "' may contain unencrypted data from $@",
taintSource, "user input (" + taintCause + ")"

View File

@@ -2,7 +2,7 @@
* @name Cleartext storage of sensitive information in file
* @description Storing sensitive information in cleartext can expose it
* to an attacker.
* @kind path-problem
* @kind problem
* @problem.severity warning
* @security-severity 7.5
* @precision high
@@ -17,19 +17,6 @@ import semmle.code.cpp.security.SensitiveExprs
import semmle.code.cpp.security.FileWrite
import semmle.code.cpp.dataflow.DataFlow
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
import semmle.code.cpp.dataflow.TaintTracking
import DataFlow::PathGraph
/**
* Taint flow from a sensitive expression to a `FileWrite` sink.
*/
class FromSensitiveConfiguration extends TaintTracking::Configuration {
FromSensitiveConfiguration() { this = "FromSensitiveConfiguration" }
override predicate isSource(DataFlow::Node source) { source.asExpr() instanceof SensitiveExpr }
override predicate isSink(DataFlow::Node sink) { any(FileWrite w).getASource() = sink.asExpr() }
}
/**
* An operation on a filename.
@@ -56,18 +43,12 @@ predicate isFileName(GVN gvn) {
)
}
from
FromSensitiveConfiguration config, SensitiveExpr source, DataFlow::PathNode sourceNode, Expr mid,
DataFlow::PathNode midNode, FileWrite w, Expr dest
from FileWrite w, SensitiveExpr source, Expr mid, Expr dest
where
config.hasFlowPath(sourceNode, midNode) and
sourceNode.getNode().asExpr() = source and
midNode.getNode().asExpr() = mid and
DataFlow::localFlow(DataFlow::exprNode(source), DataFlow::exprNode(mid)) and
mid = w.getASource() and
dest = w.getDest() and
not dest.(VariableAccess).getTarget().getName() = ["stdin", "stdout", "stderr"] and // exclude calls with standard streams
not isFileName(globalValueNumber(source)) and // file names are not passwords
not exists(string convChar | convChar = w.getSourceConvChar(mid) | not convChar = ["s", "S"]) // ignore things written with other conversion characters
select w, sourceNode, midNode,
"This write into file '" + dest.toString() + "' may contain unencrypted data from $@", source,
"this source."
select w, "This write into file '" + dest.toString() + "' may contain unencrypted data from $@",
source, "this source."

View File

@@ -5,7 +5,7 @@
* @kind path-problem
* @problem.severity warning
* @security-severity 7.5
* @precision high
* @precision medium
* @id cpp/cleartext-transmission
* @tags security
* external/cwe/cwe-319
@@ -14,8 +14,8 @@
import cpp
import semmle.code.cpp.security.SensitiveExprs
import semmle.code.cpp.dataflow.TaintTracking
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
import semmle.code.cpp.models.interfaces.FlowSource
import semmle.code.cpp.commons.File
import DataFlow::PathGraph
/**
@@ -27,7 +27,6 @@ class SensitiveNode extends DataFlow::Node {
this.asExpr() = any(SensitiveVariable sv).getInitializer().getExpr() or
this.asExpr().(VariableAccess).getTarget() =
any(SensitiveVariable sv).(GlobalOrNamespaceVariable) or
this.asExpr().(VariableAccess).getTarget() = any(SensitiveVariable v | v instanceof Field) or
this.asUninitialized() instanceof SensitiveVariable or
this.asParameter() instanceof SensitiveVariable or
this.asExpr().(FunctionCall).getTarget() instanceof SensitiveFunction
@@ -59,10 +58,7 @@ class Send extends SendRecv instanceof RemoteFlowSinkFunction {
call.getTarget() = this and
exists(FunctionInput input, int arg |
super.hasSocketInput(input) and
(
input.isParameter(arg) or
input.isParameterDeref(arg)
) and
input.isParameter(arg) and
result = call.getArgument(arg)
)
}
@@ -85,10 +81,7 @@ class Recv extends SendRecv instanceof RemoteFlowSourceFunction {
call.getTarget() = this and
exists(FunctionInput input, int arg |
super.hasSocketInput(input) and
(
input.isParameter(arg) or
input.isParameterDeref(arg)
) and
input.isParameter(arg) and
result = call.getArgument(arg)
)
}
@@ -112,8 +105,8 @@ class Recv extends SendRecv instanceof RemoteFlowSourceFunction {
* practice it usually isn't very important which query reports a result as
* long as its reported exactly once.
*
* We do exclude function calls that specify an apparently constant socket,
* which is likely to mean standard input, standard output or a similar channel.
* We do exclude function calls that specify a constant socket, which is
* likely to mean standard input, standard output or a similar channel.
*/
abstract class NetworkSendRecv extends FunctionCall {
SendRecv target;
@@ -121,32 +114,17 @@ abstract class NetworkSendRecv extends FunctionCall {
NetworkSendRecv() {
this.getTarget() = target and
// exclude calls based on the socket...
not exists(DataFlow::Node src, DataFlow::Node dest |
DataFlow::localFlow(src, dest) and
dest.asExpr() = target.getSocketExpr(this) and
not exists(GVN g |
g = globalValueNumber(target.getSocketExpr(this)) and
(
// literal constant
src.asExpr() instanceof Literal
globalValueNumber(any(Literal l)) = g
or
// variable (such as a global) initialized to a literal constant
exists(Variable v |
v.getInitializer().getExpr() instanceof Literal and
src.asExpr() = v.getAnAccess()
g = globalValueNumber(v.getAnAccess())
)
or
// result of a function call with literal inputs (likely constant)
forex(Expr arg | arg = src.asExpr().(FunctionCall).getAnArgument() | arg instanceof Literal)
or
// variable called `stdin`, `stdout` or `stderr`
src.asExpr().(VariableAccess).getTarget().getName() = ["stdin", "stdout", "stderr"]
or
// open of `"/dev/tty"`
exists(FunctionCall fc |
fopenCall(fc) and
fc.getAnArgument().getValue() = "/dev/tty" and
src.asExpr() = fc
)
// (this is not exhaustive)
)
)
}
@@ -169,28 +147,18 @@ class NetworkRecv extends NetworkSendRecv {
}
/**
* An expression that is an argument or return value from an encryption /
* decryption call. This is quite inclusive to minimize false positives, for
* example `SecureZeroMemory` is not an encryption routine but a clue that
* encryption may be present.
* An expression that is an argument or return value from an encryption or
* decryption call.
*/
class Encrypted extends Expr {
Encrypted() {
exists(FunctionCall fc |
fc.getTarget()
.getName()
.toLowerCase()
.regexpMatch(".*(crypt|encode|decode|hash|securezero).*") and
fc.getTarget().getName().toLowerCase().regexpMatch(".*(crypt|encode|decode).*") and
(
this = fc or
this = fc.getAnArgument()
)
)
or
exists(Type t |
this.getType().refersTo(t) and
t.getName().toLowerCase().regexpMatch(".*(crypt|encode|decode|hash|securezero).*")
)
}
}

View File

@@ -34,7 +34,9 @@ class SetSecurityDescriptorDaclFunctionCall extends FunctionCall {
class NullDaclConfig extends DataFlow::Configuration {
NullDaclConfig() { this = "NullDaclConfig" }
override predicate isSource(DataFlow::Node source) { source.asExpr() instanceof NullValue }
override predicate isSource(DataFlow::Node source) {
exists(NullValue nullExpr | source.asExpr() = nullExpr)
}
override predicate isSink(DataFlow::Node sink) {
exists(SetSecurityDescriptorDaclFunctionCall call, VariableAccess val | val = sink.asExpr() |

View File

@@ -1,4 +0,0 @@
---
category: newQuery
---
* A new `cpp/very-likely-overruning-write` query has been added to the default query suite for C/C++. The query reports some results that were formerly flagged by `cpp/overruning-write`.

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* The "Potential improper null termination" (`cpp/improper-null-termination`) query now produces fewer false positive results around control flow branches and loops.

Some files were not shown because too many files have changed in this diff Show More