mirror of
https://github.com/github/codeql.git
synced 2026-07-20 10:48:17 +02:00
Merge branch 'main' of github.com:github/codeql into python-port-insecure-protocol
This commit is contained in:
30
.github/workflows/close-stale.yml
vendored
Normal file
30
.github/workflows/close-stale.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Mark stale issues
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "30 1 * * *"
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
if: github.repository == 'github/codeql'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: 'This issue is stale because it has been open 14 days with no activity. Comment or remove the `Stale` label in order to avoid having this issue closed in 7 days.'
|
||||
close-issue-message: 'This issue was closed because it has been inactive for 7 days.'
|
||||
days-before-stale: 14
|
||||
days-before-close: 7
|
||||
only-labels: awaiting-response
|
||||
|
||||
# do not mark PRs as stale
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
|
||||
# Uncomment for dry-run
|
||||
# debug-only: true
|
||||
# operations-per-run: 1000
|
||||
29
.github/workflows/docs-review.yml
vendored
29
.github/workflows/docs-review.yml
vendored
@@ -1,29 +0,0 @@
|
||||
# When a PR is labelled with 'ready-for-docs-review',
|
||||
# this workflow comments on the PR to notify the GitHub CodeQL docs team.
|
||||
name: Request docs review
|
||||
on:
|
||||
# Runs in the context of the base repo.
|
||||
# This gives the workflow write access to comment on PRs.
|
||||
# The workflow should not check out or build the given ref,
|
||||
# or use untrusted data from the event payload in a command line.
|
||||
pull_request_target:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
request-docs-review:
|
||||
name: Request docs review
|
||||
# Run only on labelled PRs to the main repository.
|
||||
# Do not run on PRs to forks.
|
||||
if:
|
||||
github.event.label.name == 'ready-for-docs-review'
|
||||
&& github.event.pull_request.draft == false
|
||||
&& github.event.pull_request.base.repo.full_name == 'github/codeql'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Comment to request docs review
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
gh pr comment "$PR_NUMBER" --repo "github/codeql" \
|
||||
--body "Hello @github/docs-content-codeql - this PR is ready for docs review."
|
||||
@@ -36,6 +36,7 @@
|
||||
"cpp/ql/src/semmle/code/cpp/dataflow/internal/tainttracking2/TaintTrackingImpl.qll",
|
||||
"cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/tainttracking1/TaintTrackingImpl.qll",
|
||||
"cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/tainttracking2/TaintTrackingImpl.qll",
|
||||
"cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/tainttracking3/TaintTrackingImpl.qll",
|
||||
"csharp/ql/src/semmle/code/csharp/dataflow/internal/tainttracking1/TaintTrackingImpl.qll",
|
||||
"csharp/ql/src/semmle/code/csharp/dataflow/internal/tainttracking2/TaintTrackingImpl.qll",
|
||||
"csharp/ql/src/semmle/code/csharp/dataflow/internal/tainttracking3/TaintTrackingImpl.qll",
|
||||
@@ -55,6 +56,10 @@
|
||||
"csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImplConsistency.qll",
|
||||
"python/ql/src/semmle/python/dataflow/new/internal/DataFlowImplConsistency.qll"
|
||||
],
|
||||
"DataFlow Java/C# Flow Summaries": [
|
||||
"java/ql/src/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll",
|
||||
"csharp/ql/src/semmle/code/csharp/dataflow/internal/FlowSummaryImpl.qll"
|
||||
],
|
||||
"SsaReadPosition Java/C#": [
|
||||
"java/ql/src/semmle/code/java/dataflow/internal/rangeanalysis/SsaReadPositionCommon.qll",
|
||||
"csharp/ql/src/semmle/code/csharp/dataflow/internal/rangeanalysis/SsaReadPositionCommon.qll"
|
||||
@@ -376,7 +381,6 @@
|
||||
],
|
||||
"DuplicationProblems.inc.qhelp": [
|
||||
"cpp/ql/src/Metrics/Files/DuplicationProblems.inc.qhelp",
|
||||
"csharp/ql/src/Metrics/Files/DuplicationProblems.inc.qhelp",
|
||||
"javascript/ql/src/Metrics/DuplicationProblems.inc.qhelp",
|
||||
"python/ql/src/Metrics/DuplicationProblems.inc.qhelp"
|
||||
],
|
||||
@@ -429,10 +433,11 @@
|
||||
"SSA C#": [
|
||||
"csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll",
|
||||
"csharp/ql/src/semmle/code/csharp/controlflow/internal/pressa/SsaImplCommon.qll",
|
||||
"csharp/ql/src/semmle/code/csharp/dataflow/internal/basessa/SsaImplCommon.qll"
|
||||
"csharp/ql/src/semmle/code/csharp/dataflow/internal/basessa/SsaImplCommon.qll",
|
||||
"csharp/ql/src/semmle/code/cil/internal/SsaImplCommon.qll"
|
||||
],
|
||||
"CryptoAlgorithms Python/JS": [
|
||||
"javascript/ql/src/semmle/javascript/security/CryptoAlgorithms.qll",
|
||||
"python/ql/src/semmle/crypto/Crypto.qll"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Linq;
|
||||
using Microsoft.Build.Construction;
|
||||
using System.Xml;
|
||||
using System.IO;
|
||||
|
||||
namespace Semmle.Autobuild.Cpp.Tests
|
||||
{
|
||||
@@ -43,6 +44,8 @@ namespace Semmle.Autobuild.Cpp.Tests
|
||||
public IDictionary<string, int> RunProcess = new Dictionary<string, int>();
|
||||
public IDictionary<string, string> RunProcessOut = new Dictionary<string, string>();
|
||||
public IDictionary<string, string> RunProcessWorkingDirectory = new Dictionary<string, string>();
|
||||
public HashSet<string> CreateDirectories { get; } = new HashSet<string>();
|
||||
public HashSet<(string, string)> DownloadFiles { get; } = new HashSet<(string, string)>();
|
||||
|
||||
int IBuildActions.RunProcess(string cmd, string args, string? workingDirectory, IDictionary<string, string>? env, out IList<string> stdOut)
|
||||
{
|
||||
@@ -135,6 +138,14 @@ namespace Semmle.Autobuild.Cpp.Tests
|
||||
|
||||
string IBuildActions.GetFullPath(string path) => path;
|
||||
|
||||
string? IBuildActions.GetFileName(string? path) => Path.GetFileName(path?.Replace('\\', '/'));
|
||||
|
||||
public string? GetDirectoryName(string? path)
|
||||
{
|
||||
var dir = Path.GetDirectoryName(path?.Replace('\\', '/'));
|
||||
return dir is null ? path : path?.Substring(0, dir.Length);
|
||||
}
|
||||
|
||||
void IBuildActions.WriteAllText(string filename, string contents)
|
||||
{
|
||||
}
|
||||
@@ -153,6 +164,18 @@ namespace Semmle.Autobuild.Cpp.Tests
|
||||
s = s.Replace($"%{kvp.Key}%", kvp.Value);
|
||||
return s;
|
||||
}
|
||||
|
||||
public void CreateDirectory(string path)
|
||||
{
|
||||
if (!CreateDirectories.Contains(path))
|
||||
throw new ArgumentException($"Missing CreateDirectory, {path}");
|
||||
}
|
||||
|
||||
public void DownloadFile(string address, string fileName)
|
||||
{
|
||||
if (!DownloadFiles.Contains((address, fileName)))
|
||||
throw new ArgumentException($"Missing DownloadFile, {address}, {fileName}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -213,6 +236,7 @@ namespace Semmle.Autobuild.Cpp.Tests
|
||||
Actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_SOURCE_ARCHIVE_DIR"] = "";
|
||||
Actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_ROOT"] = $@"C:\codeql\{codeqlUpperLanguage.ToLowerInvariant()}";
|
||||
Actions.GetEnvironmentVariable["CODEQL_JAVA_HOME"] = @"C:\codeql\tools\java";
|
||||
Actions.GetEnvironmentVariable["CODEQL_PLATFORM"] = "win64";
|
||||
Actions.GetEnvironmentVariable["SEMMLE_DIST"] = @"C:\odasa";
|
||||
Actions.GetEnvironmentVariable["SEMMLE_JAVA_HOME"] = @"C:\odasa\tools\java";
|
||||
Actions.GetEnvironmentVariable["SEMMLE_PLATFORM_TOOLS"] = @"C:\odasa\tools";
|
||||
@@ -273,7 +297,8 @@ namespace Semmle.Autobuild.Cpp.Tests
|
||||
[Fact]
|
||||
public void TestCppAutobuilderSuccess()
|
||||
{
|
||||
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\csharp\nuget\nuget.exe restore C:\Project\test.sln"] = 1;
|
||||
Actions.RunProcess[@"cmd.exe /C nuget restore C:\Project\test.sln -DisableParallelProcessing"] = 1;
|
||||
Actions.RunProcess[@"cmd.exe /C C:\Project\.nuget\nuget.exe restore C:\Project\test.sln -DisableParallelProcessing"] = 0;
|
||||
Actions.RunProcess[@"cmd.exe /C CALL ^""C:\Program Files ^(x86^)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat^"" && set Platform=&& type NUL && C:\odasa\tools\odasa index --auto msbuild C:\Project\test.sln /p:UseSharedCompilation=false /t:rebuild /p:Platform=""x86"" /p:Configuration=""Release"" /p:MvcBuildViews=true"] = 0;
|
||||
Actions.RunProcessOut[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationPath"] = "";
|
||||
Actions.RunProcess[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationPath"] = 1;
|
||||
@@ -286,11 +311,13 @@ namespace Semmle.Autobuild.Cpp.Tests
|
||||
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"] = true;
|
||||
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.slx";
|
||||
Actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
Actions.CreateDirectories.Add(@"C:\Project\.nuget");
|
||||
Actions.DownloadFiles.Add(("https://dist.nuget.org/win-x86-commandline/latest/nuget.exe", @"C:\Project\.nuget\nuget.exe"));
|
||||
|
||||
var autobuilder = CreateAutoBuilder(true);
|
||||
var solution = new TestSolution(@"C:\Project\test.sln");
|
||||
autobuilder.ProjectsOrSolutionsToBuild.Add(solution);
|
||||
TestAutobuilderScript(autobuilder, 0, 2);
|
||||
TestAutobuilderScript(autobuilder, 0, 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Build" Version="16.0.461" />
|
||||
<PackageReference Include="Microsoft.Build" Version="16.9.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
lgtm,codescanning
|
||||
* The 'Assignment where comparison was intended' (cpp/assign-where-compare-meant) query has been improved to flag fewer benign assignments in conditionals.
|
||||
2
cpp/change-notes/2021-04-13-arithmetic-queries.md
Normal file
2
cpp/change-notes/2021-04-13-arithmetic-queries.md
Normal file
@@ -0,0 +1,2 @@
|
||||
lgtm
|
||||
* The queries cpp/tainted-arithmetic, cpp/uncontrolled-arithmetic, and cpp/arithmetic-with-extreme-values have been improved to produce fewer false positives.
|
||||
16
cpp/ql/src/Diagnostics/ExtractionErrors.ql
Normal file
16
cpp/ql/src/Diagnostics/ExtractionErrors.ql
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @name Extraction errors
|
||||
* @description List all extraction errors for files in the source code directory.
|
||||
* @kind diagnostic
|
||||
* @id cpp/diagnostics/extraction-errors
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import ExtractionErrors
|
||||
|
||||
from ExtractionError error
|
||||
where
|
||||
error instanceof ExtractionUnknownError or
|
||||
exists(error.getFile().getRelativePath())
|
||||
select error, "Extraction failed in " + error.getFile() + " with error " + error.getErrorMessage(),
|
||||
error.getSeverity()
|
||||
137
cpp/ql/src/Diagnostics/ExtractionErrors.qll
Normal file
137
cpp/ql/src/Diagnostics/ExtractionErrors.qll
Normal file
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Provides a common hierarchy of all types of errors that can occur during extraction.
|
||||
*/
|
||||
|
||||
import cpp
|
||||
|
||||
/*
|
||||
* A note about how the C/C++ extractor emits diagnostics:
|
||||
* When the extractor frontend encounters an error, it emits a diagnostic message,
|
||||
* that includes a message, location and severity.
|
||||
* However, that process is best-effort and may fail (e.g. due to lack of memory).
|
||||
* Thus, if the extractor emitted at least one diagnostic of severity discretionary
|
||||
* error (or higher), it *also* emits a simple "There was an error during this compilation"
|
||||
* error diagnostic, without location information.
|
||||
* In the common case, this means that a compilation during which one or more errors happened also gets
|
||||
* the catch-all diagnostic.
|
||||
* This diagnostic has the empty string as file path.
|
||||
* We filter out these useless diagnostics if there is at least one error-level diagnostic
|
||||
* for the affected compilation in the database.
|
||||
* Otherwise, we show it to indicate that something went wrong and that we
|
||||
* don't know what exactly happened.
|
||||
*/
|
||||
|
||||
/**
|
||||
* An error that, if present, leads to a file being marked as non-successfully extracted.
|
||||
*/
|
||||
class ReportableError extends Diagnostic {
|
||||
ReportableError() {
|
||||
(
|
||||
this instanceof CompilerDiscretionaryError or
|
||||
this instanceof CompilerError or
|
||||
this instanceof CompilerCatastrophe
|
||||
) and
|
||||
// Filter for the catch-all diagnostic, see note above.
|
||||
not this.getFile().getAbsolutePath() = ""
|
||||
}
|
||||
}
|
||||
|
||||
private newtype TExtractionError =
|
||||
TReportableError(ReportableError err) or
|
||||
TCompilationFailed(Compilation c, File f) {
|
||||
f = c.getAFileCompiled() and not c.normalTermination()
|
||||
} or
|
||||
// Show the catch-all diagnostic (see note above) only if we haven't seen any other error-level diagnostic
|
||||
// for that compilation
|
||||
TUnknownError(CompilerError err) {
|
||||
not exists(ReportableError e | e.getCompilation() = err.getCompilation())
|
||||
}
|
||||
|
||||
/**
|
||||
* Superclass for the extraction error hierarchy.
|
||||
*/
|
||||
class ExtractionError extends TExtractionError {
|
||||
/** Gets the string representation of the error. */
|
||||
string toString() { none() }
|
||||
|
||||
/** Gets the error message for this error. */
|
||||
string getErrorMessage() { none() }
|
||||
|
||||
/** Gets the file this error occured in. */
|
||||
File getFile() { none() }
|
||||
|
||||
/** Gets the location this error occured in. */
|
||||
Location getLocation() { none() }
|
||||
|
||||
/** Gets the SARIF severity of this error. */
|
||||
int getSeverity() {
|
||||
// Unfortunately, we can't distinguish between errors and fatal errors in SARIF,
|
||||
// so all errors have severity 2.
|
||||
result = 2
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An unrecoverable extraction error, where extraction was unable to finish.
|
||||
* This can be caused by a multitude of reasons, for example:
|
||||
* - hitting a frontend assertion
|
||||
* - crashing due to dereferencing an invalid pointer
|
||||
* - stack overflow
|
||||
* - out of memory
|
||||
*/
|
||||
class ExtractionUnrecoverableError extends ExtractionError, TCompilationFailed {
|
||||
Compilation c;
|
||||
File f;
|
||||
|
||||
ExtractionUnrecoverableError() { this = TCompilationFailed(c, f) }
|
||||
|
||||
override string toString() {
|
||||
result = "Unrecoverable extraction error while compiling " + f.toString()
|
||||
}
|
||||
|
||||
override string getErrorMessage() { result = "unrecoverable compilation failure." }
|
||||
|
||||
override File getFile() { result = f }
|
||||
|
||||
override Location getLocation() { result = f.getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A recoverable extraction error.
|
||||
* These are compiler errors from the frontend.
|
||||
* Upon encountering one of these, we still continue extraction, but the
|
||||
* database will be incomplete for that file.
|
||||
*/
|
||||
class ExtractionRecoverableError extends ExtractionError, TReportableError {
|
||||
ReportableError err;
|
||||
|
||||
ExtractionRecoverableError() { this = TReportableError(err) }
|
||||
|
||||
override string toString() { result = "Recoverable extraction error: " + err }
|
||||
|
||||
override string getErrorMessage() { result = err.getFullMessage() }
|
||||
|
||||
override File getFile() { result = err.getFile() }
|
||||
|
||||
override Location getLocation() { result = err.getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An unknown error happened during extraction.
|
||||
* These are only displayed if we know that we encountered an error during extraction,
|
||||
* but, for some reason, failed to emit a proper diagnostic with location information
|
||||
* and error message.
|
||||
*/
|
||||
class ExtractionUnknownError extends ExtractionError, TUnknownError {
|
||||
CompilerError err;
|
||||
|
||||
ExtractionUnknownError() { this = TUnknownError(err) }
|
||||
|
||||
override string toString() { result = "Unknown extraction error: " + err }
|
||||
|
||||
override string getErrorMessage() { result = err.getFullMessage() }
|
||||
|
||||
override File getFile() { result = err.getFile() }
|
||||
|
||||
override Location getLocation() { result = err.getLocation() }
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* @name Failed extractions
|
||||
* @description Gives the command-line of compilations for which extraction did not run to completion.
|
||||
* @name Failed extractor invocations
|
||||
* @description Gives the command line of compilations for which extraction did not run to completion.
|
||||
* @kind diagnostic
|
||||
* @id cpp/diagnostics/failed-extractions
|
||||
* @id cpp/diagnostics/failed-extractor-invocations
|
||||
*/
|
||||
|
||||
import cpp
|
||||
@@ -19,4 +19,4 @@ string describe(Compilation c) {
|
||||
|
||||
from Compilation c
|
||||
where not c.normalTermination()
|
||||
select c, "Extraction failed for " + describe(c), 2
|
||||
select c, "Extraction aborted for " + describe(c), 2
|
||||
15
cpp/ql/src/Diagnostics/SuccessfullyExtractedFiles.ql
Normal file
15
cpp/ql/src/Diagnostics/SuccessfullyExtractedFiles.ql
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @name Successfully extracted files
|
||||
* @description Lists all files in the source code directory that were extracted without encountering an error in the file.
|
||||
* @kind diagnostic
|
||||
* @id cpp/diagnostics/successfully-extracted-files
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import ExtractionErrors
|
||||
|
||||
from File f
|
||||
where
|
||||
not exists(ExtractionError e | e.getFile() = f) and
|
||||
exists(f.getRelativePath())
|
||||
select f, ""
|
||||
@@ -54,7 +54,7 @@ class BooleanControllingAssignmentInExpr extends BooleanControllingAssignment {
|
||||
override predicate isWhitelisted() {
|
||||
this.getConversion().(ParenthesisExpr).isParenthesised()
|
||||
or
|
||||
// whitelist this assignment if all comparison operations in the expression that this
|
||||
// Allow this assignment if all comparison operations in the expression that this
|
||||
// assignment is part of, are not parenthesized. In that case it seems like programmer
|
||||
// is fine with unparenthesized comparison operands to binary logical operators, and
|
||||
// the parenthesis around this assignment was used to call it out as an assignment.
|
||||
@@ -62,6 +62,21 @@ class BooleanControllingAssignmentInExpr extends BooleanControllingAssignment {
|
||||
forex(ComparisonOperation op | op = getComparisonOperand*(this.getParent+()) |
|
||||
not op.isParenthesised()
|
||||
)
|
||||
or
|
||||
// Match a pattern like:
|
||||
// ```
|
||||
// if((a = b) && use_value(a)) { ... }
|
||||
// ```
|
||||
// where the assignment is meant to update the value of `a` before it's used in some other boolean
|
||||
// subexpression that is guarenteed to be evaluate _after_ the assignment.
|
||||
this.isParenthesised() and
|
||||
exists(LogicalAndExpr parent, Variable var, VariableAccess access |
|
||||
var = this.getLValue().(VariableAccess).getTarget() and
|
||||
access = var.getAnAccess() and
|
||||
not access.isUsedAsLValue() and
|
||||
parent.getRightOperand() = access.getParent*() and
|
||||
parent.getLeftOperand() = this.getParent*()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.dataflow.EscapesTree
|
||||
import semmle.code.cpp.models.interfaces.PointerWrapper
|
||||
import semmle.code.cpp.dataflow.DataFlow
|
||||
|
||||
/**
|
||||
@@ -39,6 +40,10 @@ predicate hasNontrivialConversion(Expr e) {
|
||||
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())
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* @kind treemap
|
||||
* @treemap.warnOn highValues
|
||||
* @metricType externalDependency
|
||||
* @precision medium
|
||||
* @id cpp/external-dependencies
|
||||
* @tags modularity
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
* @treemap.warnOn highValues
|
||||
* @metricType file
|
||||
* @metricAggregate avg sum max
|
||||
* @precision very-high
|
||||
* @id cpp/lines-of-code-in-files
|
||||
* @tags maintainability
|
||||
* complexity
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* @treemap.warnOn highValues
|
||||
* @metricType file
|
||||
* @metricAggregate avg sum max
|
||||
* @precision high
|
||||
* @id cpp/lines-of-commented-out-code-in-files
|
||||
* @tags documentation
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
* @treemap.warnOn lowValues
|
||||
* @metricType file
|
||||
* @metricAggregate avg sum max
|
||||
* @precision very-high
|
||||
* @id cpp/lines-of-comments-in-files
|
||||
* @tags maintainability
|
||||
* documentation
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
* @treemap.warnOn highValues
|
||||
* @metricType file
|
||||
* @metricAggregate avg sum max
|
||||
* @precision high
|
||||
* @id cpp/duplicated-lines-in-files
|
||||
* @tags testability
|
||||
* modularity
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* @treemap.warnOn lowValues
|
||||
* @metricType file
|
||||
* @metricAggregate avg sum max
|
||||
* @precision medium
|
||||
* @id cpp/tests-in-files
|
||||
* @tags maintainability
|
||||
*/
|
||||
|
||||
@@ -93,7 +93,7 @@ class QuotedCommandInCreateProcessFunctionConfiguration extends DataFlow2::Confi
|
||||
|
||||
bindingset[s]
|
||||
predicate isQuotedOrNoSpaceApplicationNameOnCmd(string s) {
|
||||
s.regexpMatch("\"([^\"])*\"(\\s|.)*") // The first element (path) is quoted
|
||||
s.regexpMatch("\"([^\"])*\"[\\s\\S]*") // The first element (path) is quoted
|
||||
or
|
||||
s.regexpMatch("[^\\s]+") // There are no spaces in the string
|
||||
}
|
||||
|
||||
11
cpp/ql/src/Summary/LinesOfCode.ql
Normal file
11
cpp/ql/src/Summary/LinesOfCode.ql
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @id cpp/summary/lines-of-code
|
||||
* @name Total lines of C/C++ code in the database
|
||||
* @description The total number of lines of C/C++ code across all files, including system headers, libraries, and auto-generated files. This is a useful metric of the size of a database. For all files that were seen during the build, this query counts the lines of code, excluding whitespace or comments.
|
||||
* @kind metric
|
||||
* @tags summary
|
||||
*/
|
||||
|
||||
import cpp
|
||||
|
||||
select sum(File f | f.fromSource() | f.getMetrics().getNumberOfLinesOfCode())
|
||||
@@ -72,9 +72,9 @@ class WrongCheckErrorOperatorNew extends FunctionCall {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `(std::nothrow)` exists in call `operator new`.
|
||||
* Holds if `(std::nothrow)` or `(std::noexcept)` exists in call `operator new`.
|
||||
*/
|
||||
predicate isExistsNothrow() { this.getAChild().toString() = "nothrow" }
|
||||
predicate isExistsNothrow() { getTarget().isNoExcept() or getTarget().isNoThrow() }
|
||||
}
|
||||
|
||||
from WrongCheckErrorOperatorNew op
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
if(len>0 & memset(buf,0,len)) return 1; // BAD: `memset` will be called regardless of the value of the `len` variable. moreover, one cannot be sure that it will happen after verification
|
||||
...
|
||||
if(len>0 && memset(buf,0,len)) return 1; // GOOD: `memset` will be called after the `len` variable has been checked.
|
||||
...
|
||||
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>Using bitwise operations can be a mistake in some situations. For example, if parameters are evaluated in an expression and the function should be called only upon certain test results. These bitwise operations look suspicious and require developer attention.</p>
|
||||
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>We recommend that you evaluate the correctness of using the specified bit operations.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>The following example demonstrates the erroneous and fixed use of bit and logical operations.</p>
|
||||
<sample src="InsufficientControlFlowManagementWhenUsingBitOperations.c" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>
|
||||
CWE Common Weakness Enumeration:
|
||||
<a href="https://cwe.mitre.org/data/definitions/691.html"> CWE-691: Insufficient Control Flow Management</a>.
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @name Errors When Using Bit Operations
|
||||
* @description Unlike the binary operations `||` and `&&`, there is no sequence point after evaluating an
|
||||
* operand of a bitwise operation like `|` or `&`. If left-to-right evaluation is expected this may be confusing.
|
||||
* @kind problem
|
||||
* @id cpp/errors-when-using-bit-operations
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
* @tags correctness
|
||||
* security
|
||||
* external/cwe/cwe-691
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
|
||||
|
||||
/**
|
||||
* Dangerous uses of bit operations.
|
||||
* For example: `if(intA>0 & intA<10 & charBuf&myFunc(charBuf[intA]))`.
|
||||
* In this case, the function will be called in any case, and even the sequence of the call is not guaranteed.
|
||||
*/
|
||||
class DangerousBitOperations extends BinaryBitwiseOperation {
|
||||
FunctionCall bfc;
|
||||
|
||||
/**
|
||||
* The assignment indicates the conscious use of the bit operator.
|
||||
* Use in comparison, conversion, or return value indicates conscious use of the bit operator.
|
||||
* The use of shifts and bitwise operations on any element of an expression indicates a conscious use of the bitwise operator.
|
||||
*/
|
||||
DangerousBitOperations() {
|
||||
bfc = this.getRightOperand() and
|
||||
not this.getParent*() instanceof Assignment and
|
||||
not this.getParent*() instanceof Initializer and
|
||||
not this.getParent*() instanceof ReturnStmt and
|
||||
not this.getParent*() instanceof EqualityOperation and
|
||||
not this.getParent*() instanceof UnaryLogicalOperation and
|
||||
not this.getParent*() instanceof BinaryLogicalOperation and
|
||||
not this.getAChild*() instanceof BitwiseXorExpr and
|
||||
not this.getAChild*() instanceof LShiftExpr and
|
||||
not this.getAChild*() instanceof RShiftExpr
|
||||
}
|
||||
|
||||
/** Holds when part of a bit expression is used in a logical operation. */
|
||||
predicate useInLogicalOperations() {
|
||||
exists(BinaryLogicalOperation blop, Expr exp |
|
||||
blop.getAChild*() = exp and
|
||||
exp.(FunctionCall).getTarget() = bfc.getTarget() and
|
||||
not exp.getParent() instanceof ComparisonOperation and
|
||||
not exp.getParent() instanceof BinaryBitwiseOperation
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds when part of a bit expression is used as part of another supply. For example, as an argument to another function. */
|
||||
predicate useInOtherCalls() {
|
||||
bfc.hasQualifier() or
|
||||
bfc.getTarget() instanceof Operator or
|
||||
exists(FunctionCall fc | fc.getAnArgument().getAChild*() = this) or
|
||||
bfc.getTarget() instanceof BuiltInFunction
|
||||
}
|
||||
|
||||
/** Holds when the bit expression contains both arguments and a function call. */
|
||||
predicate dangerousArgumentChecking() {
|
||||
not this.getLeftOperand() instanceof Call and
|
||||
globalValueNumber(this.getLeftOperand().getAChild*()) = globalValueNumber(bfc.getAnArgument())
|
||||
}
|
||||
|
||||
/** Holds when function calls are present in the bit expression. */
|
||||
predicate functionCallsInBitsExpression() {
|
||||
this.getLeftOperand().getAChild*() instanceof FunctionCall
|
||||
}
|
||||
}
|
||||
|
||||
from DangerousBitOperations dbo
|
||||
where
|
||||
not dbo.useInOtherCalls() and
|
||||
dbo.useInLogicalOperations() and
|
||||
(not dbo.functionCallsInBitsExpression() or dbo.dangerousArgumentChecking())
|
||||
select dbo, "This bitwise operation appears in a context where a Boolean operation is expected."
|
||||
@@ -0,0 +1,11 @@
|
||||
if(len=funcReadData()==0) return 1; // BAD: variable `len` will not equal the value returned by function `funcReadData()`
|
||||
...
|
||||
if((len=funcReadData())==0) return 1; // GOOD: variable `len` equal the value returned by function `funcReadData()`
|
||||
...
|
||||
bool a=true;
|
||||
a++;// BAD: variable `a` does not change its meaning
|
||||
bool b;
|
||||
b=-a;// BAD: variable `b` equal `true`
|
||||
...
|
||||
a=false;// GOOD: variable `a` equal `false`
|
||||
b=!a;// GOOD: variable `b` equal `false`
|
||||
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>Finding places of confusing use of boolean type. For example, a unary minus does not work before a boolean type and an increment always gives true.</p>
|
||||
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>we recommend making the code simpler.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>The following example demonstrates erroneous and fixed methods for using a boolean data type.</p>
|
||||
<sample src="OperatorPrecedenceLogicErrorWhenUseBoolType.c" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>
|
||||
CERT C Coding Standard:
|
||||
<a href="https://wiki.sei.cmu.edu/confluence/display/c/EXP00-C.+Use+parentheses+for+precedence+of+operation">EXP00-C. Use parentheses for precedence of operation</a>.
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* @name Operator Precedence Logic Error When Use Bool Type
|
||||
* @description --Finding places of confusing use of boolean type.
|
||||
* --For example, a unary minus does not work before a boolean type and an increment always gives true.
|
||||
* @kind problem
|
||||
* @id cpp/operator-precedence-logic-error-when-use-bool-type
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
* @tags correctness
|
||||
* security
|
||||
* external/cwe/cwe-783
|
||||
* external/cwe/cwe-480
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.valuenumbering.HashCons
|
||||
|
||||
/** Holds if `exp` increments a boolean value. */
|
||||
predicate incrementBoolType(IncrementOperation exp) {
|
||||
exp.getOperand().getType() instanceof BoolType
|
||||
}
|
||||
|
||||
/** Holds if `exp` applies the unary minus operator to a boolean type. */
|
||||
predicate revertSignBoolType(UnaryMinusExpr exp) {
|
||||
exp.getAnOperand().getType() instanceof BoolType and
|
||||
exp.getFullyConverted().getType() instanceof BoolType
|
||||
}
|
||||
|
||||
/** Holds, if this is an expression, uses comparison and assignment outside of execution precedence. */
|
||||
predicate assignBoolType(Expr exp) {
|
||||
exists(ComparisonOperation co |
|
||||
exp.(AssignExpr).getRValue() = co and
|
||||
exp.isCondition() and
|
||||
not co.isParenthesised() and
|
||||
not exp.(AssignExpr).getLValue().getType() instanceof BoolType and
|
||||
not exists(Expr exbl |
|
||||
hashCons(exbl.(AssignExpr).getLValue()) = hashCons(exp.(AssignExpr).getLValue()) and
|
||||
not exbl.isCondition() and
|
||||
exbl.(AssignExpr).getRValue().getType() instanceof BoolType and
|
||||
exbl.(AssignExpr).getLValue().getType() = exp.(AssignExpr).getLValue().getType()
|
||||
) and
|
||||
co.getLeftOperand() instanceof FunctionCall and
|
||||
not co.getRightOperand().getType() instanceof BoolType and
|
||||
not co.getRightOperand().getValue() = "0" and
|
||||
not co.getRightOperand().getValue() = "1"
|
||||
)
|
||||
}
|
||||
|
||||
from Expr exp
|
||||
where
|
||||
incrementBoolType(exp) or
|
||||
revertSignBoolType(exp) or
|
||||
assignBoolType(exp)
|
||||
select exp, "this expression needs attention"
|
||||
@@ -3,7 +3,7 @@
|
||||
* @description The expression `buffer [strlen (buffer)] = 0` is potentially dangerous, if the variable `buffer` does not have a terminal zero, then access beyond the bounds of the allocated memory is possible, which will lead to undefined behavior.
|
||||
* If terminal zero is present, then the specified expression is meaningless.
|
||||
* @kind problem
|
||||
* @id cpp/access-memory-location-after-end-buffer
|
||||
* @id cpp/access-memory-location-after-end-buffer-strlen
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
* @tags correctness
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @name Access Of Memory Location After The End Of A Buffer Using Strncat
|
||||
* @description Calls of the form `strncat(dest, source, sizeof (dest) - strlen (dest))` set the third argument to one more than possible. So when `dest` is full, the expression `sizeof(dest) - strlen (dest)` will be equal to one, and not zero as the programmer might think. Making a call of this type may result in a zero byte being written just outside the `dest` buffer.
|
||||
* @kind problem
|
||||
* @id cpp/access-memory-location-after-end-buffer
|
||||
* @id cpp/access-memory-location-after-end-buffer-strncat
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
* @tags correctness
|
||||
@@ -11,54 +11,32 @@
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.models.implementations.Strcat
|
||||
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
|
||||
|
||||
/**
|
||||
* A call to `strncat` of the form `strncat(buff, str, someExpr - strlen(buf))`, for some expression `someExpr` equal to `sizeof(buff)`.
|
||||
* Holds if `call` is a call to `strncat` such that `sizeArg` and `destArg` are the size and
|
||||
* destination arguments, respectively.
|
||||
*/
|
||||
class WrongCallStrncat extends FunctionCall {
|
||||
Expr leftsomeExpr;
|
||||
|
||||
WrongCallStrncat() {
|
||||
this.getTarget().hasGlobalOrStdName("strncat") and
|
||||
// the expression of the first argument in `strncat` and `strnlen` is identical
|
||||
globalValueNumber(this.getArgument(0)) =
|
||||
globalValueNumber(this.getArgument(2).(SubExpr).getRightOperand().(StrlenCall).getStringExpr()) and
|
||||
// using a string constant often speaks of manually calculating the length of the required buffer.
|
||||
(
|
||||
not this.getArgument(1) instanceof StringLiteral and
|
||||
not this.getArgument(1) instanceof CharLiteral
|
||||
) and
|
||||
// for use in predicates
|
||||
leftsomeExpr = this.getArgument(2).(SubExpr).getLeftOperand()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the left side of the expression `someExpr` equal to `sizeof(buf)`.
|
||||
*/
|
||||
predicate isExpressionEqualSizeof() {
|
||||
// the left side of the expression `someExpr` is `sizeof(buf)`.
|
||||
globalValueNumber(this.getArgument(0)) =
|
||||
globalValueNumber(leftsomeExpr.(SizeofExprOperator).getExprOperand())
|
||||
or
|
||||
// value of the left side of the expression `someExpr` equal `sizeof(buf)` value, and `buf` is array.
|
||||
leftsomeExpr.getValue().toInt() = this.getArgument(0).getType().getSize()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the left side of the expression `someExpr` equal to variable containing the length of the memory allocated for the buffer.
|
||||
*/
|
||||
predicate isVariableEqualValueSizegBuffer() {
|
||||
// the left side of expression `someExpr` is the variable that was used in the function of allocating memory for the buffer`.
|
||||
exists(AllocationExpr alc |
|
||||
leftsomeExpr.(VariableAccess).getTarget() =
|
||||
alc.(FunctionCall).getArgument(0).(VariableAccess).getTarget()
|
||||
)
|
||||
}
|
||||
predicate interestringCallWithArgs(Call call, Expr sizeArg, Expr destArg) {
|
||||
exists(StrcatFunction strcat |
|
||||
strcat = call.getTarget() and
|
||||
sizeArg = call.getArgument(strcat.getParamSize()) and
|
||||
destArg = call.getArgument(strcat.getParamDest())
|
||||
)
|
||||
}
|
||||
|
||||
from WrongCallStrncat sc
|
||||
from FunctionCall call, Expr sizeArg, Expr destArg, SubExpr sub, int n
|
||||
where
|
||||
sc.isExpressionEqualSizeof() or
|
||||
sc.isVariableEqualValueSizegBuffer()
|
||||
select sc, "if the used buffer is full, writing out of the buffer is possible"
|
||||
interestringCallWithArgs(call, sizeArg, destArg) and
|
||||
// The destination buffer is an array of size n
|
||||
destArg.getUnspecifiedType().(ArrayType).getSize() = n and
|
||||
// The size argument is equivalent to a subtraction
|
||||
globalValueNumber(sizeArg).getAnExpr() = sub and
|
||||
// ... where the left side of the subtraction is the constant n
|
||||
globalValueNumber(sub.getLeftOperand()).getAnExpr().getValue().toInt() = n and
|
||||
// ... and the right side of the subtraction is a call to `strlen` where the argument is the
|
||||
// destination buffer.
|
||||
globalValueNumber(sub.getRightOperand()).getAnExpr().(StrlenCall).getStringExpr() =
|
||||
globalValueNumber(destArg).getAnExpr()
|
||||
select call, "Possible out-of-bounds write due to incorrect size argument."
|
||||
|
||||
12
cpp/ql/src/external/tests/DefectFilter.ql
vendored
12
cpp/ql/src/external/tests/DefectFilter.ql
vendored
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* @name Defect filter
|
||||
* @description Only include results in large files (200) lines of code, and change the message.
|
||||
* @tags filter
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import external.DefectFilter
|
||||
|
||||
from DefectResult res
|
||||
where res.getFile().getMetrics().getNumberOfLinesOfCode() > 200
|
||||
select res, "Large files: " + res.getMessage()
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* @name Defect from external data
|
||||
* @description Insert description here...
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @tags external-data
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import external.ExternalArtifact
|
||||
|
||||
from ExternalData d, File u
|
||||
where
|
||||
d.getQueryPath() = "external-data.ql" and
|
||||
u.getShortName() = d.getField(0)
|
||||
select u,
|
||||
d.getField(5) + ", " + d.getFieldAsDate(1) + ", " + d.getField(2) + ", " + d.getFieldAsFloat(3) +
|
||||
", " + d.getFieldAsInt(4) + ": " + d.getNumFields()
|
||||
12
cpp/ql/src/external/tests/MetricFilter.ql
vendored
12
cpp/ql/src/external/tests/MetricFilter.ql
vendored
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* @name Metric filter
|
||||
* @description Only include results in large files (200) lines of code.
|
||||
* @tags filter
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import external.MetricFilter
|
||||
|
||||
from MetricResult res
|
||||
where res.getFile().getMetrics().getNumberOfLinesOfCode() > 200
|
||||
select res, res.getValue()
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* @name Filter: exclude results from files that are autogenerated
|
||||
* @description Use this filter to return results only if they are
|
||||
* located in files that are maintained manually.
|
||||
* @kind problem
|
||||
* @id cpp/autogenerated-filter
|
||||
* @tags filter
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.AutogeneratedFile
|
||||
import external.DefectFilter
|
||||
|
||||
from DefectResult res
|
||||
where not res.getFile() instanceof AutogeneratedFile
|
||||
select res, res.getMessage()
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* @name Metric filter: exclude results from files that are autogenerated
|
||||
* @description Use this filter to return results only if they are
|
||||
* located in files that are maintained manually.
|
||||
* @kind treemap
|
||||
* @id cpp/autogenerated-for-metric-filter
|
||||
* @tags filter
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.AutogeneratedFile
|
||||
import external.MetricFilter
|
||||
|
||||
from MetricResult res
|
||||
where not res.getFile() instanceof AutogeneratedFile
|
||||
select res, res.getValue()
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* @name Filter: exclude results from files for which we do not have
|
||||
* source code
|
||||
* @description Use this filter to return results only if they are
|
||||
* located in files for which we have source code.
|
||||
* @kind problem
|
||||
* @id cpp/from-source-filter
|
||||
* @tags filter
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import external.DefectFilter
|
||||
|
||||
from DefectResult res
|
||||
where res.getFile().fromSource()
|
||||
select res, res.getMessage()
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* @name Filter: exclude results on lines covered by a macro expansion
|
||||
* @description Use this filter to return results only when there is no
|
||||
* macro expansion whose location spans all the lines of
|
||||
* the result's location.
|
||||
* @kind problem
|
||||
* @id cpp/macros-filter
|
||||
* @tags filter
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import external.DefectFilter
|
||||
|
||||
predicate macroLocation(File f, int startLine, int endLine) {
|
||||
exists(MacroInvocation mi, Location l |
|
||||
l = mi.getLocation() and
|
||||
l.getFile() = f and
|
||||
l.getStartLine() = startLine and
|
||||
l.getEndLine() = endLine
|
||||
)
|
||||
}
|
||||
|
||||
predicate macroCovering(DefectResult r) {
|
||||
exists(File f, int macroStart, int macroEnd, int defectStart, int defectEnd |
|
||||
f = r.getFile() and
|
||||
defectStart = r.getStartLine() and
|
||||
defectEnd = r.getEndLine() and
|
||||
macroLocation(f, macroStart, macroEnd) and
|
||||
macroStart <= defectStart and
|
||||
macroEnd >= defectEnd
|
||||
)
|
||||
}
|
||||
|
||||
from DefectResult res
|
||||
where not macroCovering(res)
|
||||
select res, res.getMessage()
|
||||
@@ -91,16 +91,17 @@ private predicate exprReleases(Expr e, Expr released, string kind) {
|
||||
// `e` is a call to a release function and `released` is the released argument
|
||||
releaseExpr(e, released, kind)
|
||||
or
|
||||
exists(Function f, int arg |
|
||||
exists(int arg, VariableAccess access, Function f |
|
||||
// `e` is a call to a function that releases one of it's parameters,
|
||||
// and `released` is the corresponding argument
|
||||
(
|
||||
e.(FunctionCall).getTarget() = f or
|
||||
e.(FunctionCall).getTarget().(MemberFunction).getAnOverridingFunction+() = f
|
||||
) and
|
||||
access = f.getParameter(arg).getAnAccess() and
|
||||
e.(FunctionCall).getArgument(arg) = released and
|
||||
exprReleases(_,
|
||||
exprOrDereference(globalValueNumber(f.getParameter(arg).getAnAccess()).getAnExpr()), kind)
|
||||
pragma[only_bind_into](exprOrDereference(globalValueNumber(access).getAnExpr())), kind)
|
||||
)
|
||||
or
|
||||
exists(Function f, ThisExpr innerThis |
|
||||
@@ -112,7 +113,7 @@ private predicate exprReleases(Expr e, Expr released, string kind) {
|
||||
) and
|
||||
e.(FunctionCall).getQualifier() = exprOrDereference(released) and
|
||||
innerThis.getEnclosingFunction() = f and
|
||||
exprReleases(_, globalValueNumber(innerThis).getAnExpr(), kind)
|
||||
exprReleases(_, pragma[only_bind_into](globalValueNumber(innerThis).getAnExpr()), kind)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@ import semmle.code.cpp.Location
|
||||
|
||||
/** A compiler-generated error, warning or remark. */
|
||||
class Diagnostic extends Locatable, @diagnostic {
|
||||
/** Gets the compilation that generated this diagnostic. */
|
||||
Compilation getCompilation() { diagnostic_for(underlyingElement(this), result, _, _) }
|
||||
|
||||
/**
|
||||
* Gets the severity of the message, on a range from 1 to 5: 1=remark,
|
||||
* 2=warning, 3=discretionary error, 4=error, 5=catastrophic error.
|
||||
|
||||
@@ -72,6 +72,7 @@ class Location extends @location {
|
||||
}
|
||||
|
||||
/** Holds if `this` comes on a line strictly before `l`. */
|
||||
pragma[inline]
|
||||
predicate isBefore(Location l) {
|
||||
this.getFile() = l.getFile() and this.getEndLine() < l.getStartLine()
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
private import cpp
|
||||
private import semmle.code.cpp.models.interfaces.PointerWrapper
|
||||
|
||||
/**
|
||||
* Holds if `f` is an instantiation of the `std::move` or `std::forward`
|
||||
@@ -94,6 +95,12 @@ private predicate pointerToPointerStep(Expr pointerIn, Expr pointerOut) {
|
||||
|
||||
private predicate lvalueToReferenceStep(Expr lvalueIn, Expr referenceOut) {
|
||||
lvalueIn.getConversion() = referenceOut.(ReferenceToExpr)
|
||||
or
|
||||
exists(PointerWrapper wrapper, Call call | call = referenceOut |
|
||||
referenceOut.getUnspecifiedType() instanceof ReferenceType and
|
||||
call = wrapper.getAnUnwrapperFunction().getACallToThisFunction() and
|
||||
lvalueIn = call.getQualifier().getFullyConverted()
|
||||
)
|
||||
}
|
||||
|
||||
private predicate referenceToLvalueStep(Expr referenceIn, Expr lvalueOut) {
|
||||
@@ -106,6 +113,13 @@ private predicate referenceToPointerStep(Expr referenceIn, Expr pointerOut) {
|
||||
stdAddressOf(call.getTarget()) and
|
||||
referenceIn = call.getArgument(0).getFullyConverted()
|
||||
)
|
||||
or
|
||||
exists(CopyConstructor copy, Call call | call = pointerOut |
|
||||
copy.getDeclaringType() instanceof PointerWrapper and
|
||||
call.getTarget() = copy and
|
||||
// The 0'th argument is the value being copied.
|
||||
referenceIn = call.getArgument(0).getFullyConverted()
|
||||
)
|
||||
}
|
||||
|
||||
private predicate referenceToReferenceStep(Expr referenceIn, Expr referenceOut) {
|
||||
@@ -190,6 +204,19 @@ private predicate pointerToUpdate(Expr pointer, Expr outer, ControlFlowNode node
|
||||
// See the `lvalueToUpdate` case for an explanation of this conjunct.
|
||||
call.getType().isDeeplyConstBelow()
|
||||
)
|
||||
or
|
||||
// Pointer wrappers behave as raw pointers for dataflow purposes.
|
||||
outer = call.getAnArgument().getFullyConverted() and
|
||||
exists(PointerWrapper wrapper | wrapper = outer.getType().stripTopLevelSpecifiers() |
|
||||
not wrapper.pointsToConst()
|
||||
)
|
||||
or
|
||||
outer = call.getQualifier().getFullyConverted() and
|
||||
outer.getUnspecifiedType() instanceof PointerWrapper and
|
||||
not (
|
||||
call.getTarget().hasSpecifier("const") and
|
||||
call.getType().isDeeplyConstBelow()
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(PointerFieldAccess fa |
|
||||
@@ -218,7 +245,9 @@ private predicate referenceToUpdate(Expr reference, Expr outer, ControlFlowNode
|
||||
not stdIdentityFunction(call.getTarget()) and
|
||||
not stdAddressOf(call.getTarget()) and
|
||||
exists(ReferenceType rt | rt = outer.getType().stripTopLevelSpecifiers() |
|
||||
not rt.getBaseType().isConst()
|
||||
not rt.getBaseType().isConst() or
|
||||
rt.getBaseType().getUnspecifiedType() =
|
||||
any(PointerWrapper wrapper | not wrapper.pointsToConst())
|
||||
)
|
||||
) and
|
||||
reference = outer
|
||||
|
||||
@@ -2133,11 +2133,8 @@ private module Stage4 {
|
||||
|
||||
bindingset[node, cc, config]
|
||||
private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
|
||||
exists(Cc cc0 |
|
||||
cc = pragma[only_bind_into](cc0) and
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
|
||||
)
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
|
||||
}
|
||||
|
||||
private predicate localStep(
|
||||
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
sc = mid.getSummaryCtx() and
|
||||
localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
|
||||
localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, _, conf, localCC) and
|
||||
|
||||
@@ -2133,11 +2133,8 @@ private module Stage4 {
|
||||
|
||||
bindingset[node, cc, config]
|
||||
private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
|
||||
exists(Cc cc0 |
|
||||
cc = pragma[only_bind_into](cc0) and
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
|
||||
)
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
|
||||
}
|
||||
|
||||
private predicate localStep(
|
||||
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
sc = mid.getSummaryCtx() and
|
||||
localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
|
||||
localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, _, conf, localCC) and
|
||||
|
||||
@@ -2133,11 +2133,8 @@ private module Stage4 {
|
||||
|
||||
bindingset[node, cc, config]
|
||||
private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
|
||||
exists(Cc cc0 |
|
||||
cc = pragma[only_bind_into](cc0) and
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
|
||||
)
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
|
||||
}
|
||||
|
||||
private predicate localStep(
|
||||
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
sc = mid.getSummaryCtx() and
|
||||
localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
|
||||
localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, _, conf, localCC) and
|
||||
|
||||
@@ -2133,11 +2133,8 @@ private module Stage4 {
|
||||
|
||||
bindingset[node, cc, config]
|
||||
private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
|
||||
exists(Cc cc0 |
|
||||
cc = pragma[only_bind_into](cc0) and
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
|
||||
)
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
|
||||
}
|
||||
|
||||
private predicate localStep(
|
||||
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
sc = mid.getSummaryCtx() and
|
||||
localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
|
||||
localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, _, conf, localCC) and
|
||||
|
||||
@@ -26,15 +26,243 @@ predicate accessPathCostLimits(int apLimit, int tupleLimit) {
|
||||
tupleLimit = 1000
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a simple data-flow analysis for resolving lambda calls. The analysis
|
||||
* currently excludes read-steps, store-steps, and flow-through.
|
||||
*
|
||||
* The analysis uses non-linear recursion: When computing a flow path in or out
|
||||
* of a call, we use the results of the analysis recursively to resolve lamba
|
||||
* calls. For this reason, we cannot reuse the code from `DataFlowImpl.qll` directly.
|
||||
*/
|
||||
private module LambdaFlow {
|
||||
private predicate viableParamNonLambda(DataFlowCall call, int i, ParameterNode p) {
|
||||
p.isParameterOf(viableCallable(call), i)
|
||||
}
|
||||
|
||||
private predicate viableParamLambda(DataFlowCall call, int i, ParameterNode p) {
|
||||
p.isParameterOf(viableCallableLambda(call, _), i)
|
||||
}
|
||||
|
||||
private predicate viableParamArgNonLambda(DataFlowCall call, ParameterNode p, ArgumentNode arg) {
|
||||
exists(int i |
|
||||
viableParamNonLambda(call, i, p) and
|
||||
arg.argumentOf(call, i)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate viableParamArgLambda(DataFlowCall call, ParameterNode p, ArgumentNode arg) {
|
||||
exists(int i |
|
||||
viableParamLambda(call, i, p) and
|
||||
arg.argumentOf(call, i)
|
||||
)
|
||||
}
|
||||
|
||||
private newtype TReturnPositionSimple =
|
||||
TReturnPositionSimple0(DataFlowCallable c, ReturnKind kind) {
|
||||
exists(ReturnNode ret |
|
||||
c = getNodeEnclosingCallable(ret) and
|
||||
kind = ret.getKind()
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private TReturnPositionSimple getReturnPositionSimple(ReturnNode ret, ReturnKind kind) {
|
||||
result = TReturnPositionSimple0(getNodeEnclosingCallable(ret), kind)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private TReturnPositionSimple viableReturnPosNonLambda(DataFlowCall call, ReturnKind kind) {
|
||||
result = TReturnPositionSimple0(viableCallable(call), kind)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private TReturnPositionSimple viableReturnPosLambda(
|
||||
DataFlowCall call, DataFlowCallOption lastCall, ReturnKind kind
|
||||
) {
|
||||
result = TReturnPositionSimple0(viableCallableLambda(call, lastCall), kind)
|
||||
}
|
||||
|
||||
private predicate viableReturnPosOutNonLambda(
|
||||
DataFlowCall call, TReturnPositionSimple pos, OutNode out
|
||||
) {
|
||||
exists(ReturnKind kind |
|
||||
pos = viableReturnPosNonLambda(call, kind) and
|
||||
out = getAnOutNode(call, kind)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate viableReturnPosOutLambda(
|
||||
DataFlowCall call, DataFlowCallOption lastCall, TReturnPositionSimple pos, OutNode out
|
||||
) {
|
||||
exists(ReturnKind kind |
|
||||
pos = viableReturnPosLambda(call, lastCall, kind) and
|
||||
out = getAnOutNode(call, kind)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data can flow (inter-procedurally) from `node` (of type `t`) to
|
||||
* the lambda call `lambdaCall`.
|
||||
*
|
||||
* The parameter `toReturn` indicates whether the path from `node` to
|
||||
* `lambdaCall` goes through a return, and `toJump` whether the path goes
|
||||
* through a jump step.
|
||||
*
|
||||
* The call context `lastCall` records the last call on the path from `node`
|
||||
* to `lambdaCall`, if any. That is, `lastCall` is able to target the enclosing
|
||||
* callable of `lambdaCall`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate revLambdaFlow(
|
||||
DataFlowCall lambdaCall, LambdaCallKind kind, Node node, DataFlowType t, boolean toReturn,
|
||||
boolean toJump, DataFlowCallOption lastCall
|
||||
) {
|
||||
revLambdaFlow0(lambdaCall, kind, node, t, toReturn, toJump, lastCall) and
|
||||
if node instanceof CastNode or node instanceof ArgumentNode or node instanceof ReturnNode
|
||||
then compatibleTypes(t, getNodeType(node))
|
||||
else any()
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate revLambdaFlow0(
|
||||
DataFlowCall lambdaCall, LambdaCallKind kind, Node node, DataFlowType t, boolean toReturn,
|
||||
boolean toJump, DataFlowCallOption lastCall
|
||||
) {
|
||||
lambdaCall(lambdaCall, kind, node) and
|
||||
t = getNodeType(node) and
|
||||
toReturn = false and
|
||||
toJump = false and
|
||||
lastCall = TDataFlowCallNone()
|
||||
or
|
||||
// local flow
|
||||
exists(Node mid, DataFlowType t0 |
|
||||
revLambdaFlow(lambdaCall, kind, mid, t0, toReturn, toJump, lastCall)
|
||||
|
|
||||
simpleLocalFlowStep(node, mid) and
|
||||
t = t0
|
||||
or
|
||||
exists(boolean preservesValue |
|
||||
additionalLambdaFlowStep(node, mid, preservesValue) and
|
||||
getNodeEnclosingCallable(node) = getNodeEnclosingCallable(mid)
|
||||
|
|
||||
preservesValue = false and
|
||||
t = getNodeType(node)
|
||||
or
|
||||
preservesValue = true and
|
||||
t = t0
|
||||
)
|
||||
)
|
||||
or
|
||||
// jump step
|
||||
exists(Node mid, DataFlowType t0 |
|
||||
revLambdaFlow(lambdaCall, kind, mid, t0, _, _, _) and
|
||||
toReturn = false and
|
||||
toJump = true and
|
||||
lastCall = TDataFlowCallNone()
|
||||
|
|
||||
jumpStep(node, mid) and
|
||||
t = t0
|
||||
or
|
||||
exists(boolean preservesValue |
|
||||
additionalLambdaFlowStep(node, mid, preservesValue) and
|
||||
getNodeEnclosingCallable(node) != getNodeEnclosingCallable(mid)
|
||||
|
|
||||
preservesValue = false and
|
||||
t = getNodeType(node)
|
||||
or
|
||||
preservesValue = true and
|
||||
t = t0
|
||||
)
|
||||
)
|
||||
or
|
||||
// flow into a callable
|
||||
exists(ParameterNode p, DataFlowCallOption lastCall0, DataFlowCall call |
|
||||
revLambdaFlowIn(lambdaCall, kind, p, t, toJump, lastCall0) and
|
||||
(
|
||||
if lastCall0 = TDataFlowCallNone() and toJump = false
|
||||
then lastCall = TDataFlowCallSome(call)
|
||||
else lastCall = lastCall0
|
||||
) and
|
||||
toReturn = false
|
||||
|
|
||||
viableParamArgNonLambda(call, p, node)
|
||||
or
|
||||
viableParamArgLambda(call, p, node) // non-linear recursion
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(TReturnPositionSimple pos |
|
||||
revLambdaFlowOut(lambdaCall, kind, pos, t, toJump, lastCall) and
|
||||
getReturnPositionSimple(node, node.(ReturnNode).getKind()) = pos and
|
||||
toReturn = true
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate revLambdaFlowOutLambdaCall(
|
||||
DataFlowCall lambdaCall, LambdaCallKind kind, OutNode out, DataFlowType t, boolean toJump,
|
||||
DataFlowCall call, DataFlowCallOption lastCall
|
||||
) {
|
||||
revLambdaFlow(lambdaCall, kind, out, t, _, toJump, lastCall) and
|
||||
exists(ReturnKindExt rk |
|
||||
out = rk.getAnOutNode(call) and
|
||||
lambdaCall(call, _, _)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate revLambdaFlowOut(
|
||||
DataFlowCall lambdaCall, LambdaCallKind kind, TReturnPositionSimple pos, DataFlowType t,
|
||||
boolean toJump, DataFlowCallOption lastCall
|
||||
) {
|
||||
exists(DataFlowCall call, OutNode out |
|
||||
revLambdaFlow(lambdaCall, kind, out, t, _, toJump, lastCall) and
|
||||
viableReturnPosOutNonLambda(call, pos, out)
|
||||
or
|
||||
// non-linear recursion
|
||||
revLambdaFlowOutLambdaCall(lambdaCall, kind, out, t, toJump, call, lastCall) and
|
||||
viableReturnPosOutLambda(call, _, pos, out)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate revLambdaFlowIn(
|
||||
DataFlowCall lambdaCall, LambdaCallKind kind, ParameterNode p, DataFlowType t, boolean toJump,
|
||||
DataFlowCallOption lastCall
|
||||
) {
|
||||
revLambdaFlow(lambdaCall, kind, p, t, false, toJump, lastCall)
|
||||
}
|
||||
}
|
||||
|
||||
private DataFlowCallable viableCallableExt(DataFlowCall call) {
|
||||
result = viableCallable(call)
|
||||
or
|
||||
result = viableCallableLambda(call, _)
|
||||
}
|
||||
|
||||
cached
|
||||
private module Cached {
|
||||
/**
|
||||
* Gets a viable target for the lambda call `call`.
|
||||
*
|
||||
* `lastCall` records the call required to reach `call` in order for the result
|
||||
* to be a viable target, if any.
|
||||
*/
|
||||
cached
|
||||
DataFlowCallable viableCallableLambda(DataFlowCall call, DataFlowCallOption lastCall) {
|
||||
exists(Node creation, LambdaCallKind kind |
|
||||
LambdaFlow::revLambdaFlow(call, kind, creation, _, _, _, lastCall) and
|
||||
lambdaCreation(creation, kind, result)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `p` is the `i`th parameter of a viable dispatch target of `call`.
|
||||
* The instance parameter is considered to have index `-1`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate viableParam(DataFlowCall call, int i, ParameterNode p) {
|
||||
p.isParameterOf(viableCallable(call), i)
|
||||
p.isParameterOf(viableCallableExt(call), i)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,7 +280,7 @@ private module Cached {
|
||||
|
||||
pragma[nomagic]
|
||||
private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) {
|
||||
viableCallable(call) = result.getCallable() and
|
||||
viableCallableExt(call) = result.getCallable() and
|
||||
kind = result.getKind()
|
||||
}
|
||||
|
||||
@@ -317,6 +545,35 @@ private module Cached {
|
||||
|
||||
cached
|
||||
private module DispatchWithCallContext {
|
||||
/**
|
||||
* Holds if the set of viable implementations that can be called by `call`
|
||||
* might be improved by knowing the call context.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate mayBenefitFromCallContextExt(DataFlowCall call, DataFlowCallable callable) {
|
||||
mayBenefitFromCallContext(call, callable)
|
||||
or
|
||||
callable = call.getEnclosingCallable() and
|
||||
exists(viableCallableLambda(call, TDataFlowCallSome(_)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a viable dispatch target of `call` in the context `ctx`. This is
|
||||
* restricted to those `call`s for which a context might make a difference.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private DataFlowCallable viableImplInCallContextExt(DataFlowCall call, DataFlowCall ctx) {
|
||||
result = viableImplInCallContext(call, ctx)
|
||||
or
|
||||
result = viableCallableLambda(call, TDataFlowCallSome(ctx))
|
||||
or
|
||||
exists(DataFlowCallable enclosing |
|
||||
mayBenefitFromCallContextExt(call, enclosing) and
|
||||
enclosing = viableCallableExt(ctx) and
|
||||
result = viableCallableLambda(call, TDataFlowCallNone())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the call context `ctx` reduces the set of viable run-time
|
||||
* dispatch targets of call `call` in `c`.
|
||||
@@ -324,10 +581,10 @@ private module Cached {
|
||||
cached
|
||||
predicate reducedViableImplInCallContext(DataFlowCall call, DataFlowCallable c, DataFlowCall ctx) {
|
||||
exists(int tgts, int ctxtgts |
|
||||
mayBenefitFromCallContext(call, c) and
|
||||
c = viableCallable(ctx) and
|
||||
ctxtgts = count(viableImplInCallContext(call, ctx)) and
|
||||
tgts = strictcount(viableCallable(call)) and
|
||||
mayBenefitFromCallContextExt(call, c) and
|
||||
c = viableCallableExt(ctx) and
|
||||
ctxtgts = count(viableImplInCallContextExt(call, ctx)) and
|
||||
tgts = strictcount(viableCallableExt(call)) and
|
||||
ctxtgts < tgts
|
||||
)
|
||||
}
|
||||
@@ -339,7 +596,7 @@ private module Cached {
|
||||
*/
|
||||
cached
|
||||
DataFlowCallable prunedViableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
|
||||
result = viableImplInCallContext(call, ctx) and
|
||||
result = viableImplInCallContextExt(call, ctx) and
|
||||
reducedViableImplInCallContext(call, _, ctx)
|
||||
}
|
||||
|
||||
@@ -351,10 +608,10 @@ private module Cached {
|
||||
cached
|
||||
predicate reducedViableImplInReturn(DataFlowCallable c, DataFlowCall call) {
|
||||
exists(int tgts, int ctxtgts |
|
||||
mayBenefitFromCallContext(call, _) and
|
||||
c = viableCallable(call) and
|
||||
ctxtgts = count(DataFlowCall ctx | c = viableImplInCallContext(call, ctx)) and
|
||||
tgts = strictcount(DataFlowCall ctx | viableCallable(ctx) = call.getEnclosingCallable()) and
|
||||
mayBenefitFromCallContextExt(call, _) and
|
||||
c = viableCallableExt(call) and
|
||||
ctxtgts = count(DataFlowCall ctx | c = viableImplInCallContextExt(call, ctx)) and
|
||||
tgts = strictcount(DataFlowCall ctx | viableCallableExt(ctx) = call.getEnclosingCallable()) and
|
||||
ctxtgts < tgts
|
||||
)
|
||||
}
|
||||
@@ -367,7 +624,7 @@ private module Cached {
|
||||
*/
|
||||
cached
|
||||
DataFlowCallable prunedViableImplInCallContextReverse(DataFlowCall call, DataFlowCall ctx) {
|
||||
result = viableImplInCallContext(call, ctx) and
|
||||
result = viableImplInCallContextExt(call, ctx) and
|
||||
reducedViableImplInReturn(result, call)
|
||||
}
|
||||
}
|
||||
@@ -481,6 +738,11 @@ private module Cached {
|
||||
TBooleanNone() or
|
||||
TBooleanSome(boolean b) { b = true or b = false }
|
||||
|
||||
cached
|
||||
newtype TDataFlowCallOption =
|
||||
TDataFlowCallNone() or
|
||||
TDataFlowCallSome(DataFlowCall call)
|
||||
|
||||
cached
|
||||
newtype TTypedContent = MkTypedContent(Content c, DataFlowType t) { store(_, c, _, _, t) }
|
||||
|
||||
@@ -777,7 +1039,7 @@ ReturnPosition getReturnPosition(ReturnNodeExt ret) {
|
||||
|
||||
bindingset[cc, callable]
|
||||
predicate resolveReturn(CallContext cc, DataFlowCallable callable, DataFlowCall call) {
|
||||
cc instanceof CallContextAny and callable = viableCallable(call)
|
||||
cc instanceof CallContextAny and callable = viableCallableExt(call)
|
||||
or
|
||||
exists(DataFlowCallable c0, DataFlowCall call0 |
|
||||
call0.getEnclosingCallable() = callable and
|
||||
@@ -791,14 +1053,14 @@ DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) {
|
||||
exists(DataFlowCall ctx | cc = TSpecificCall(ctx) |
|
||||
if reducedViableImplInCallContext(call, _, ctx)
|
||||
then result = prunedViableImplInCallContext(call, ctx)
|
||||
else result = viableCallable(call)
|
||||
else result = viableCallableExt(call)
|
||||
)
|
||||
or
|
||||
result = viableCallable(call) and cc instanceof CallContextSomeCall
|
||||
result = viableCallableExt(call) and cc instanceof CallContextSomeCall
|
||||
or
|
||||
result = viableCallable(call) and cc instanceof CallContextAny
|
||||
result = viableCallableExt(call) and cc instanceof CallContextAny
|
||||
or
|
||||
result = viableCallable(call) and cc instanceof CallContextReturn
|
||||
result = viableCallableExt(call) and cc instanceof CallContextReturn
|
||||
}
|
||||
|
||||
predicate read = readStep/3;
|
||||
@@ -812,6 +1074,19 @@ class BooleanOption extends TBooleanOption {
|
||||
}
|
||||
}
|
||||
|
||||
/** An optional `DataFlowCall`. */
|
||||
class DataFlowCallOption extends TDataFlowCallOption {
|
||||
string toString() {
|
||||
this = TDataFlowCallNone() and
|
||||
result = "(none)"
|
||||
or
|
||||
exists(DataFlowCall call |
|
||||
this = TDataFlowCallSome(call) and
|
||||
result = call.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Content tagged with the type of a containing object. */
|
||||
class TypedContent extends MkTypedContent {
|
||||
private Content c;
|
||||
|
||||
@@ -2133,11 +2133,8 @@ private module Stage4 {
|
||||
|
||||
bindingset[node, cc, config]
|
||||
private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
|
||||
exists(Cc cc0 |
|
||||
cc = pragma[only_bind_into](cc0) and
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
|
||||
)
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
|
||||
}
|
||||
|
||||
private predicate localStep(
|
||||
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
sc = mid.getSummaryCtx() and
|
||||
localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
|
||||
localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, _, conf, localCC) and
|
||||
|
||||
@@ -312,3 +312,14 @@ predicate isImmutableOrUnobservable(Node n) {
|
||||
|
||||
/** Holds if `n` should be hidden from path explanations. */
|
||||
predicate nodeIsHidden(Node n) { none() }
|
||||
|
||||
class LambdaCallKind = Unit;
|
||||
|
||||
/** Holds if `creation` is an expression that creates a lambda of kind `kind` for `c`. */
|
||||
predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) { none() }
|
||||
|
||||
/** Holds if `call` is a lambda call of kind `kind` where `receiver` is the lambda expression. */
|
||||
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { none() }
|
||||
|
||||
/** Extra data-flow steps needed for lamba flow analysis. */
|
||||
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }
|
||||
|
||||
@@ -46,7 +46,7 @@ class Node extends TNode {
|
||||
/**
|
||||
* INTERNAL: Do not use. Alternative name for `getFunction`.
|
||||
*/
|
||||
final Function getEnclosingCallable() { result = unique(Function f | f = this.getFunction() | f) }
|
||||
final Function getEnclosingCallable() { result = this.getFunction() }
|
||||
|
||||
/** Gets the type of this node. */
|
||||
Type getType() { none() } // overridden in subclasses
|
||||
@@ -324,7 +324,7 @@ private class VariablePartialDefinitionNode extends PartialDefinitionNode {
|
||||
* A synthetic data flow node used for flow into a collection when an iterator
|
||||
* write occurs in a callee.
|
||||
*/
|
||||
class IteratorPartialDefinitionNode extends PartialDefinitionNode {
|
||||
private class IteratorPartialDefinitionNode extends PartialDefinitionNode {
|
||||
override IteratorPartialDefinition pd;
|
||||
|
||||
override Node getPreUpdateNode() { pd.definesExpressions(_, result.asExpr()) }
|
||||
@@ -715,6 +715,7 @@ private predicate exprToDefinitionByReferenceStep(Expr exprIn, Expr argOut) {
|
||||
}
|
||||
|
||||
private module FieldFlow {
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplLocal
|
||||
private import DataFlowPrivate
|
||||
|
||||
@@ -747,7 +748,7 @@ private module FieldFlow {
|
||||
exists(FieldConfiguration cfg | cfg.hasFlow(node1, node2)) and
|
||||
// This configuration should not be able to cross function boundaries, but
|
||||
// we double-check here just to be sure.
|
||||
node1.getEnclosingCallable() = node2.getEnclosingCallable()
|
||||
getNodeEnclosingCallable(node1) = getNodeEnclosingCallable(node2)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ private import semmle.code.cpp.controlflow.SSA
|
||||
private import semmle.code.cpp.dataflow.internal.SubBasicBlocks
|
||||
private import semmle.code.cpp.dataflow.internal.AddressFlow
|
||||
private import semmle.code.cpp.models.implementations.Iterator
|
||||
private import semmle.code.cpp.models.interfaces.PointerWrapper
|
||||
|
||||
/**
|
||||
* A conceptual variable that is assigned only once, like an SSA variable. This
|
||||
@@ -158,18 +159,14 @@ private module PartialDefinitions {
|
||||
Expr innerDefinedExpr;
|
||||
|
||||
IteratorPartialDefinition() {
|
||||
exists(Expr convertedInner |
|
||||
not this instanceof Conversion and
|
||||
valueToUpdate(convertedInner, this.getFullyConverted(), node) and
|
||||
innerDefinedExpr = convertedInner.getUnconverted() and
|
||||
(
|
||||
innerDefinedExpr.(Call).getQualifier() = getAnIteratorAccess(collection)
|
||||
or
|
||||
innerDefinedExpr.(Call).getQualifier() = collection.getAnAccess() and
|
||||
collection instanceof IteratorParameter
|
||||
) and
|
||||
innerDefinedExpr.(Call).getTarget() instanceof IteratorPointerDereferenceMemberOperator
|
||||
)
|
||||
innerDefinedExpr = getInnerDefinedExpr(this, node) and
|
||||
(
|
||||
innerDefinedExpr.(Call).getQualifier() = getAnIteratorAccess(collection)
|
||||
or
|
||||
innerDefinedExpr.(Call).getQualifier() = collection.getAnAccess() and
|
||||
collection instanceof IteratorParameter
|
||||
) and
|
||||
innerDefinedExpr.(Call).getTarget() instanceof IteratorPointerDereferenceMemberOperator
|
||||
or
|
||||
// iterators passed by value without a copy constructor
|
||||
exists(Call call |
|
||||
@@ -207,16 +204,18 @@ private module PartialDefinitions {
|
||||
}
|
||||
}
|
||||
|
||||
private Expr getInnerDefinedExpr(Expr e, ControlFlowNode node) {
|
||||
not e instanceof Conversion and
|
||||
exists(Expr convertedInner |
|
||||
valueToUpdate(convertedInner, e.getFullyConverted(), node) and
|
||||
result = convertedInner.getUnconverted()
|
||||
)
|
||||
}
|
||||
|
||||
class VariablePartialDefinition extends PartialDefinition {
|
||||
Expr innerDefinedExpr;
|
||||
|
||||
VariablePartialDefinition() {
|
||||
not this instanceof Conversion and
|
||||
exists(Expr convertedInner |
|
||||
valueToUpdate(convertedInner, this.getFullyConverted(), node) and
|
||||
innerDefinedExpr = convertedInner.getUnconverted()
|
||||
)
|
||||
}
|
||||
VariablePartialDefinition() { innerDefinedExpr = getInnerDefinedExpr(this, node) }
|
||||
|
||||
deprecated override predicate partiallyDefines(Variable v) {
|
||||
innerDefinedExpr = v.getAnAccess()
|
||||
@@ -296,7 +295,8 @@ module FlowVar_internal {
|
||||
// treating them as immutable, but for data flow it gives better results in
|
||||
// practice to make the variable synonymous with its contents.
|
||||
not v.getUnspecifiedType() instanceof ReferenceType and
|
||||
not v instanceof IteratorParameter
|
||||
not v instanceof IteratorParameter and
|
||||
not v instanceof PointerWrapperParameter
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -644,10 +644,19 @@ module FlowVar_internal {
|
||||
predicate parameterIsNonConstReference(Parameter p) {
|
||||
exists(ReferenceType refType |
|
||||
refType = p.getUnderlyingType() and
|
||||
not refType.getBaseType().isConst()
|
||||
(
|
||||
not refType.getBaseType().isConst()
|
||||
or
|
||||
// A field of a parameter of type `const std::shared_ptr<A>& p` can still be changed even though
|
||||
// the base type of the reference is `const`.
|
||||
refType.getBaseType().getUnspecifiedType() =
|
||||
any(PointerWrapper wrapper | not wrapper.pointsToConst())
|
||||
)
|
||||
)
|
||||
or
|
||||
p instanceof IteratorParameter
|
||||
or
|
||||
p instanceof PointerWrapperParameter
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -836,6 +845,10 @@ module FlowVar_internal {
|
||||
IteratorParameter() { this.getUnspecifiedType() instanceof Iterator }
|
||||
}
|
||||
|
||||
class PointerWrapperParameter extends Parameter {
|
||||
PointerWrapperParameter() { this.getUnspecifiedType() instanceof PointerWrapper }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `v` is initialized to have value `assignedExpr`.
|
||||
*/
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
private import semmle.code.cpp.models.interfaces.DataFlow
|
||||
private import semmle.code.cpp.models.interfaces.Taint
|
||||
private import semmle.code.cpp.models.interfaces.Iterator
|
||||
private import semmle.code.cpp.models.interfaces.PointerWrapper
|
||||
|
||||
private module DataFlow {
|
||||
import semmle.code.cpp.dataflow.internal.DataFlowUtil
|
||||
@@ -141,7 +142,10 @@ private predicate noFlowFromChildExpr(Expr e) {
|
||||
or
|
||||
e instanceof LogicalOrExpr
|
||||
or
|
||||
e instanceof Call
|
||||
// Allow taint from `operator*` on smart pointers.
|
||||
exists(Call call | e = call |
|
||||
not call.getTarget() = any(PointerWrapper wrapper).getAnUnwrapperFunction()
|
||||
)
|
||||
or
|
||||
e instanceof SizeofOperator
|
||||
or
|
||||
|
||||
@@ -300,17 +300,25 @@ class FunctionCall extends Call, @funbindexpr {
|
||||
}
|
||||
}
|
||||
|
||||
/** A _user-defined_ unary `operator*` function. */
|
||||
class OverloadedPointerDereferenceFunction extends Function {
|
||||
OverloadedPointerDereferenceFunction() {
|
||||
this.hasName("operator*") and
|
||||
this.getEffectiveNumberOfParameters() = 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An instance of a _user-defined_ unary `operator*` applied to its argument.
|
||||
* ```
|
||||
* T1 operator*(const T2 &);
|
||||
* T1 a; T2 b;
|
||||
* a = *b;
|
||||
* ```
|
||||
*/
|
||||
class OverloadedPointerDereferenceExpr extends FunctionCall {
|
||||
OverloadedPointerDereferenceExpr() {
|
||||
getTarget().hasName("operator*") and
|
||||
getTarget().getEffectiveNumberOfParameters() = 1
|
||||
this.getTarget() instanceof OverloadedPointerDereferenceFunction
|
||||
}
|
||||
|
||||
override string getAPrimaryQlClass() { result = "OverloadedPointerDereferenceExpr" }
|
||||
|
||||
@@ -2,13 +2,16 @@ import cpp
|
||||
import semmle.code.cpp.security.Security
|
||||
private import semmle.code.cpp.ir.dataflow.DataFlow
|
||||
private import semmle.code.cpp.ir.dataflow.internal.DataFlowUtil
|
||||
private import semmle.code.cpp.ir.dataflow.DataFlow2
|
||||
private import semmle.code.cpp.ir.dataflow.DataFlow3
|
||||
private import semmle.code.cpp.ir.IR
|
||||
private import semmle.code.cpp.ir.dataflow.internal.DataFlowDispatch as Dispatch
|
||||
private import semmle.code.cpp.controlflow.IRGuards
|
||||
private import semmle.code.cpp.models.interfaces.Taint
|
||||
private import semmle.code.cpp.models.interfaces.DataFlow
|
||||
private import semmle.code.cpp.ir.dataflow.TaintTracking
|
||||
private import semmle.code.cpp.ir.dataflow.TaintTracking2
|
||||
private import semmle.code.cpp.ir.dataflow.TaintTracking3
|
||||
private import semmle.code.cpp.ir.dataflow.internal.ModelUtil
|
||||
|
||||
/**
|
||||
* A predictable instruction is one where an external user can predict
|
||||
@@ -65,23 +68,19 @@ private DataFlow::Node getNodeForExpr(Expr node) {
|
||||
not argv(node.(VariableAccess).getTarget())
|
||||
}
|
||||
|
||||
private class DefaultTaintTrackingCfg extends DataFlow::Configuration {
|
||||
private class DefaultTaintTrackingCfg extends TaintTracking::Configuration {
|
||||
DefaultTaintTrackingCfg() { this = "DefaultTaintTrackingCfg" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source = getNodeForSource(_) }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { exists(adjustedSink(sink)) }
|
||||
|
||||
override predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) {
|
||||
commonTaintStep(n1, n2)
|
||||
}
|
||||
override predicate isSanitizer(DataFlow::Node node) { nodeIsBarrier(node) }
|
||||
|
||||
override predicate isBarrier(DataFlow::Node node) { nodeIsBarrier(node) }
|
||||
|
||||
override predicate isBarrierIn(DataFlow::Node node) { nodeIsBarrierIn(node) }
|
||||
override predicate isSanitizerIn(DataFlow::Node node) { nodeIsBarrierIn(node) }
|
||||
}
|
||||
|
||||
private class ToGlobalVarTaintTrackingCfg extends DataFlow::Configuration {
|
||||
private class ToGlobalVarTaintTrackingCfg extends TaintTracking::Configuration {
|
||||
ToGlobalVarTaintTrackingCfg() { this = "GlobalVarTaintTrackingCfg" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source = getNodeForSource(_) }
|
||||
@@ -90,20 +89,18 @@ private class ToGlobalVarTaintTrackingCfg extends DataFlow::Configuration {
|
||||
sink.asVariable() instanceof GlobalOrNamespaceVariable
|
||||
}
|
||||
|
||||
override predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) {
|
||||
commonTaintStep(n1, n2)
|
||||
or
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) {
|
||||
writesVariable(n1.asInstruction(), n2.asVariable().(GlobalOrNamespaceVariable))
|
||||
or
|
||||
readsVariable(n2.asInstruction(), n1.asVariable().(GlobalOrNamespaceVariable))
|
||||
}
|
||||
|
||||
override predicate isBarrier(DataFlow::Node node) { nodeIsBarrier(node) }
|
||||
override predicate isSanitizer(DataFlow::Node node) { nodeIsBarrier(node) }
|
||||
|
||||
override predicate isBarrierIn(DataFlow::Node node) { nodeIsBarrierIn(node) }
|
||||
override predicate isSanitizerIn(DataFlow::Node node) { nodeIsBarrierIn(node) }
|
||||
}
|
||||
|
||||
private class FromGlobalVarTaintTrackingCfg extends DataFlow2::Configuration {
|
||||
private class FromGlobalVarTaintTrackingCfg extends TaintTracking2::Configuration {
|
||||
FromGlobalVarTaintTrackingCfg() { this = "FromGlobalVarTaintTrackingCfg" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
@@ -114,18 +111,16 @@ private class FromGlobalVarTaintTrackingCfg extends DataFlow2::Configuration {
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { exists(adjustedSink(sink)) }
|
||||
|
||||
override predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) {
|
||||
commonTaintStep(n1, n2)
|
||||
or
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) {
|
||||
// Additional step for flow out of variables. There is no flow _into_
|
||||
// variables in this configuration, so this step only serves to take flow
|
||||
// out of a variable that's a source.
|
||||
readsVariable(n2.asInstruction(), n1.asVariable())
|
||||
}
|
||||
|
||||
override predicate isBarrier(DataFlow::Node node) { nodeIsBarrier(node) }
|
||||
override predicate isSanitizer(DataFlow::Node node) { nodeIsBarrier(node) }
|
||||
|
||||
override predicate isBarrierIn(DataFlow::Node node) { nodeIsBarrierIn(node) }
|
||||
override predicate isSanitizerIn(DataFlow::Node node) { nodeIsBarrierIn(node) }
|
||||
}
|
||||
|
||||
private predicate readsVariable(LoadInstruction load, Variable var) {
|
||||
@@ -202,206 +197,26 @@ private predicate nodeIsBarrierIn(DataFlow::Node node) {
|
||||
// `getNodeForSource`.
|
||||
node = DataFlow::definitionByReferenceNodeFromArgument(source)
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
private predicate commonTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
operandToInstructionTaintStep(fromNode.asOperand(), toNode.asInstruction())
|
||||
or
|
||||
instructionToOperandTaintStep(fromNode.asInstruction(), toNode.asOperand())
|
||||
}
|
||||
|
||||
private predicate instructionToOperandTaintStep(Instruction fromInstr, Operand toOperand) {
|
||||
// Propagate flow from the definition of an operand to the operand, even when the overlap is inexact.
|
||||
// We only do this in certain cases:
|
||||
// 1. The instruction's result must not be conflated, and
|
||||
// 2. The instruction's result type is one the types where we expect element-to-object flow. Currently
|
||||
// this is array types and union types. This matches the other two cases of element-to-object flow in
|
||||
// `DefaultTaintTracking`.
|
||||
toOperand.getAnyDef() = fromInstr and
|
||||
not fromInstr.isResultConflated() and
|
||||
(
|
||||
fromInstr.getResultType() instanceof ArrayType or
|
||||
fromInstr.getResultType() instanceof Union
|
||||
// don't use dataflow into binary instructions if both operands are unpredictable
|
||||
exists(BinaryInstruction iTo |
|
||||
iTo = node.asInstruction() and
|
||||
not predictableInstruction(iTo.getLeft()) and
|
||||
not predictableInstruction(iTo.getRight()) and
|
||||
// propagate taint from either the pointer or the offset, regardless of predictability
|
||||
not iTo instanceof PointerArithmeticInstruction
|
||||
)
|
||||
or
|
||||
exists(ReadSideEffectInstruction readInstr |
|
||||
fromInstr = readInstr.getArgumentDef() and
|
||||
toOperand = readInstr.getSideEffectOperand()
|
||||
)
|
||||
}
|
||||
|
||||
private predicate operandToInstructionTaintStep(Operand fromOperand, Instruction toInstr) {
|
||||
// Expressions computed from tainted data are also tainted
|
||||
exists(CallInstruction call, int argIndex | call = toInstr |
|
||||
isPureFunction(call.getStaticCallTarget().getName()) and
|
||||
fromOperand = getACallArgumentOrIndirection(call, argIndex) and
|
||||
forall(Operand argOperand | argOperand = call.getAnArgumentOperand() |
|
||||
argOperand = getACallArgumentOrIndirection(call, argIndex) or
|
||||
predictableInstruction(argOperand.getAnyDef())
|
||||
) and
|
||||
// flow through `strlen` tends to cause dubious results, if the length is
|
||||
// bounded.
|
||||
not call.getStaticCallTarget().getName() = "strlen"
|
||||
)
|
||||
or
|
||||
// Flow from argument to return value
|
||||
toInstr =
|
||||
any(CallInstruction call |
|
||||
exists(int indexIn |
|
||||
modelTaintToReturnValue(call.getStaticCallTarget(), indexIn) and
|
||||
fromOperand = getACallArgumentOrIndirection(call, indexIn) and
|
||||
not predictableOnlyFlow(call.getStaticCallTarget().getName())
|
||||
)
|
||||
)
|
||||
or
|
||||
// Flow from input argument to output argument
|
||||
// TODO: This won't work in practice as long as all aliased memory is tracked
|
||||
// together in a single virtual variable.
|
||||
// TODO: Will this work on the test for `TaintedPath.ql`, where the output arg
|
||||
// is a pointer addition expression?
|
||||
toInstr =
|
||||
any(WriteSideEffectInstruction outInstr |
|
||||
exists(CallInstruction call, int indexIn, int indexOut |
|
||||
modelTaintToParameter(call.getStaticCallTarget(), indexIn, indexOut) and
|
||||
fromOperand = getACallArgumentOrIndirection(call, indexIn) and
|
||||
outInstr.getIndex() = indexOut and
|
||||
outInstr.getPrimaryInstruction() = call
|
||||
)
|
||||
)
|
||||
or
|
||||
// Flow through pointer dereference
|
||||
toInstr.(LoadInstruction).getSourceAddressOperand() = fromOperand
|
||||
or
|
||||
// Flow through partial reads of arrays and unions
|
||||
toInstr.(LoadInstruction).getSourceValueOperand() = fromOperand and
|
||||
exists(Instruction fromInstr | fromInstr = fromOperand.getAnyDef() |
|
||||
not fromInstr.isResultConflated() and
|
||||
(
|
||||
fromInstr.getResultType() instanceof ArrayType or
|
||||
fromInstr.getResultType() instanceof Union
|
||||
)
|
||||
)
|
||||
or
|
||||
// Unary instructions tend to preserve enough information in practice that we
|
||||
// want taint to flow through.
|
||||
// The exception is `FieldAddressInstruction`. Together with the rule for
|
||||
// `LoadInstruction` above and for `ChiInstruction` below, flow through
|
||||
// `FieldAddressInstruction` could cause flow into one field to come out an
|
||||
// unrelated field. This would happen across function boundaries, where the IR
|
||||
// would not be able to match loads to stores.
|
||||
toInstr.(UnaryInstruction).getUnaryOperand() = fromOperand and
|
||||
(
|
||||
not toInstr instanceof FieldAddressInstruction
|
||||
or
|
||||
toInstr.(FieldAddressInstruction).getField().getDeclaringType() instanceof Union
|
||||
)
|
||||
or
|
||||
// Flow from an element to an array or union that contains it.
|
||||
toInstr.(ChiInstruction).getPartialOperand() = fromOperand and
|
||||
not toInstr.isResultConflated() and
|
||||
exists(Type t | toInstr.getResultLanguageType().hasType(t, false) |
|
||||
t instanceof Union
|
||||
or
|
||||
t instanceof ArrayType
|
||||
)
|
||||
or
|
||||
exists(BinaryInstruction bin |
|
||||
bin = toInstr and
|
||||
predictableInstruction(toInstr.getAnOperand().getDef()) and
|
||||
fromOperand = toInstr.getAnOperand()
|
||||
)
|
||||
or
|
||||
// This is part of the translation of `a[i]`, where we want taint to flow
|
||||
// from `a`.
|
||||
toInstr.(PointerAddInstruction).getLeftOperand() = fromOperand
|
||||
or
|
||||
// Until we have flow through indirections across calls, we'll take flow out
|
||||
// of the indirection and into the argument.
|
||||
// When we get proper flow through indirections across calls, this code can be
|
||||
// moved to `adjusedSink` or possibly into the `DataFlow::ExprNode` class.
|
||||
exists(ReadSideEffectInstruction read |
|
||||
read.getSideEffectOperand() = fromOperand and
|
||||
read.getArgumentDef() = toInstr
|
||||
)
|
||||
or
|
||||
// Until we have from through indirections across calls, we'll take flow out
|
||||
// of the parameter and into its indirection.
|
||||
// `InitializeIndirectionInstruction` only has a single operand: the address of the
|
||||
// value whose indirection we are initializing. When initializing an indirection of a parameter `p`,
|
||||
// the IR looks like this:
|
||||
// ```
|
||||
// m1 = InitializeParameter[p] : &r1
|
||||
// r2 = Load[p] : r2, m1
|
||||
// m3 = InitializeIndirection[p] : &r2
|
||||
// ```
|
||||
// So by having flow from `r2` to `m3` we're enabling flow from `m1` to `m3`. This relies on the
|
||||
// `LoadOperand`'s overlap being exact.
|
||||
toInstr.(InitializeIndirectionInstruction).getAnOperand() = fromOperand
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the side effect instruction corresponding to the specified function output,
|
||||
* if one exists.
|
||||
*/
|
||||
private int getWriteSideEffectIndex(FunctionOutput output) {
|
||||
output.isParameterDeref(result)
|
||||
or
|
||||
output.isQualifierObject() and result = -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an operand that goes into argument `argumentIndex` of `call`. This
|
||||
* can be either directly or through one pointer indirection.
|
||||
*/
|
||||
private Operand getACallArgumentOrIndirection(CallInstruction call, int argumentIndex) {
|
||||
result = call.getPositionalArgumentOperand(argumentIndex)
|
||||
or
|
||||
exists(ReadSideEffectInstruction readSE |
|
||||
// TODO: why are read side effect operands imprecise?
|
||||
result = readSE.getSideEffectOperand() and
|
||||
readSE.getPrimaryInstruction() = call and
|
||||
readSE.getIndex() = argumentIndex
|
||||
)
|
||||
}
|
||||
|
||||
private predicate modelTaintToParameter(Function f, int parameterIn, int parameterOut) {
|
||||
exists(FunctionInput modelIn, FunctionOutput modelOut |
|
||||
(
|
||||
f.(DataFlowFunction).hasDataFlow(modelIn, modelOut)
|
||||
or
|
||||
f.(TaintFunction).hasTaintFlow(modelIn, modelOut)
|
||||
) and
|
||||
(modelIn.isParameter(parameterIn) or modelIn.isParameterDeref(parameterIn)) and
|
||||
parameterOut = getWriteSideEffectIndex(modelOut)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate modelTaintToReturnValue(Function f, int parameterIn) {
|
||||
// Taint flow from parameter to return value
|
||||
exists(FunctionInput modelIn, FunctionOutput modelOut |
|
||||
f.(TaintFunction).hasTaintFlow(modelIn, modelOut) and
|
||||
(modelIn.isParameter(parameterIn) or modelIn.isParameterDeref(parameterIn)) and
|
||||
(modelOut.isReturnValue() or modelOut.isReturnValueDeref())
|
||||
)
|
||||
or
|
||||
// Data flow (not taint flow) to where the return value points. For the time
|
||||
// being we will conflate pointers and objects in taint tracking.
|
||||
exists(FunctionInput modelIn, FunctionOutput modelOut |
|
||||
f.(DataFlowFunction).hasDataFlow(modelIn, modelOut) and
|
||||
(modelIn.isParameter(parameterIn) or modelIn.isParameterDeref(parameterIn)) and
|
||||
modelOut.isReturnValueDeref()
|
||||
)
|
||||
or
|
||||
// Taint flow from one argument to another and data flow from an argument to a
|
||||
// return value. This happens in functions like `strcat` and `memcpy`. We
|
||||
// could model this flow in two separate steps, but that would add reverse
|
||||
// flow from the write side-effect to the call instruction, which may not be
|
||||
// desirable.
|
||||
exists(int parameterMid, InParameter modelMid, OutReturnValue returnOut |
|
||||
modelTaintToParameter(f, parameterIn, parameterMid) and
|
||||
modelMid.isParameter(parameterMid) and
|
||||
f.(DataFlowFunction).hasDataFlow(modelMid, returnOut)
|
||||
// don't use dataflow through calls to pure functions if two or more operands
|
||||
// are unpredictable
|
||||
exists(Instruction iFrom1, Instruction iFrom2, CallInstruction iTo |
|
||||
iTo = node.asInstruction() and
|
||||
isPureFunction(iTo.getStaticCallTarget().getName()) and
|
||||
iFrom1 = iTo.getAnArgument() and
|
||||
iFrom2 = iTo.getAnArgument() and
|
||||
not predictableInstruction(iFrom1) and
|
||||
not predictableInstruction(iFrom2) and
|
||||
iFrom1 != iFrom2
|
||||
)
|
||||
}
|
||||
|
||||
@@ -440,6 +255,14 @@ private Element adjustedSink(DataFlow::Node sink) {
|
||||
or
|
||||
// Taint `e1 += e2`, `e &= e2` and friends when `e1` or `e2` is tainted.
|
||||
result.(AssignOperation).getAnOperand() = sink.asExpr()
|
||||
or
|
||||
result =
|
||||
sink.asOperand()
|
||||
.(SideEffectOperand)
|
||||
.getUse()
|
||||
.(ReadSideEffectInstruction)
|
||||
.getArgumentDef()
|
||||
.getUnconvertedResultExpression()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -558,7 +381,7 @@ module TaintedWithPath {
|
||||
string toString() { result = "TaintTrackingConfiguration" }
|
||||
}
|
||||
|
||||
private class AdjustedConfiguration extends DataFlow3::Configuration {
|
||||
private class AdjustedConfiguration extends TaintTracking3::Configuration {
|
||||
AdjustedConfiguration() { this = "AdjustedConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
@@ -571,21 +394,34 @@ module TaintedWithPath {
|
||||
exists(TaintTrackingConfiguration cfg | cfg.isSink(adjustedSink(sink)))
|
||||
}
|
||||
|
||||
override predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) {
|
||||
commonTaintStep(n1, n2)
|
||||
or
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) {
|
||||
// Steps into and out of global variables
|
||||
exists(TaintTrackingConfiguration cfg | cfg.taintThroughGlobals() |
|
||||
writesVariable(n1.asInstruction(), n2.asVariable().(GlobalOrNamespaceVariable))
|
||||
or
|
||||
readsVariable(n2.asInstruction(), n1.asVariable().(GlobalOrNamespaceVariable))
|
||||
)
|
||||
or
|
||||
// Step to return value of a modeled function when an input taints the
|
||||
// dereference of the return value
|
||||
exists(CallInstruction call, Function func, FunctionInput modelIn, FunctionOutput modelOut |
|
||||
n1.asOperand() = callInput(call, modelIn) and
|
||||
(
|
||||
func.(TaintFunction).hasTaintFlow(modelIn, modelOut)
|
||||
or
|
||||
func.(DataFlowFunction).hasDataFlow(modelIn, modelOut)
|
||||
) and
|
||||
call.getStaticCallTarget() = func and
|
||||
modelOut.isReturnValueDeref() and
|
||||
call = n2.asInstruction()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isBarrier(DataFlow::Node node) {
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
exists(TaintTrackingConfiguration cfg, Expr e | cfg.isBarrier(e) and node = getNodeForExpr(e))
|
||||
}
|
||||
|
||||
override predicate isBarrierIn(DataFlow::Node node) { nodeIsBarrierIn(node) }
|
||||
override predicate isSanitizerIn(DataFlow::Node node) { nodeIsBarrierIn(node) }
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
15
cpp/ql/src/semmle/code/cpp/ir/dataflow/TaintTracking3.qll
Normal file
15
cpp/ql/src/semmle/code/cpp/ir/dataflow/TaintTracking3.qll
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Provides a `TaintTracking3` module, which is a copy of the `TaintTracking`
|
||||
* module. Use this class when data-flow configurations or taint-tracking
|
||||
* configurations must depend on each other. Two classes extending
|
||||
* `DataFlow::Configuration` should never depend on each other, but one of them
|
||||
* should instead depend on a `DataFlow2::Configuration`, a
|
||||
* `DataFlow3::Configuration`, or a `DataFlow4::Configuration`. The
|
||||
* `TaintTracking::Configuration` class extends `DataFlow::Configuration`, and
|
||||
* `TaintTracking2::Configuration` extends `DataFlow2::Configuration`.
|
||||
*
|
||||
* See `semmle.code.cpp.ir.dataflow.TaintTracking` for the full documentation.
|
||||
*/
|
||||
module TaintTracking3 {
|
||||
import semmle.code.cpp.ir.dataflow.internal.tainttracking3.TaintTrackingImpl
|
||||
}
|
||||
@@ -2133,11 +2133,8 @@ private module Stage4 {
|
||||
|
||||
bindingset[node, cc, config]
|
||||
private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
|
||||
exists(Cc cc0 |
|
||||
cc = pragma[only_bind_into](cc0) and
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
|
||||
)
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
|
||||
}
|
||||
|
||||
private predicate localStep(
|
||||
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
sc = mid.getSummaryCtx() and
|
||||
localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
|
||||
localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, _, conf, localCC) and
|
||||
|
||||
@@ -2133,11 +2133,8 @@ private module Stage4 {
|
||||
|
||||
bindingset[node, cc, config]
|
||||
private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
|
||||
exists(Cc cc0 |
|
||||
cc = pragma[only_bind_into](cc0) and
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
|
||||
)
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
|
||||
}
|
||||
|
||||
private predicate localStep(
|
||||
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
sc = mid.getSummaryCtx() and
|
||||
localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
|
||||
localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, _, conf, localCC) and
|
||||
|
||||
@@ -2133,11 +2133,8 @@ private module Stage4 {
|
||||
|
||||
bindingset[node, cc, config]
|
||||
private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
|
||||
exists(Cc cc0 |
|
||||
cc = pragma[only_bind_into](cc0) and
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
|
||||
)
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
|
||||
}
|
||||
|
||||
private predicate localStep(
|
||||
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
sc = mid.getSummaryCtx() and
|
||||
localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
|
||||
localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, _, conf, localCC) and
|
||||
|
||||
@@ -2133,11 +2133,8 @@ private module Stage4 {
|
||||
|
||||
bindingset[node, cc, config]
|
||||
private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
|
||||
exists(Cc cc0 |
|
||||
cc = pragma[only_bind_into](cc0) and
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
|
||||
)
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
|
||||
}
|
||||
|
||||
private predicate localStep(
|
||||
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
sc = mid.getSummaryCtx() and
|
||||
localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
|
||||
localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, _, conf, localCC) and
|
||||
|
||||
@@ -26,15 +26,243 @@ predicate accessPathCostLimits(int apLimit, int tupleLimit) {
|
||||
tupleLimit = 1000
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a simple data-flow analysis for resolving lambda calls. The analysis
|
||||
* currently excludes read-steps, store-steps, and flow-through.
|
||||
*
|
||||
* The analysis uses non-linear recursion: When computing a flow path in or out
|
||||
* of a call, we use the results of the analysis recursively to resolve lamba
|
||||
* calls. For this reason, we cannot reuse the code from `DataFlowImpl.qll` directly.
|
||||
*/
|
||||
private module LambdaFlow {
|
||||
private predicate viableParamNonLambda(DataFlowCall call, int i, ParameterNode p) {
|
||||
p.isParameterOf(viableCallable(call), i)
|
||||
}
|
||||
|
||||
private predicate viableParamLambda(DataFlowCall call, int i, ParameterNode p) {
|
||||
p.isParameterOf(viableCallableLambda(call, _), i)
|
||||
}
|
||||
|
||||
private predicate viableParamArgNonLambda(DataFlowCall call, ParameterNode p, ArgumentNode arg) {
|
||||
exists(int i |
|
||||
viableParamNonLambda(call, i, p) and
|
||||
arg.argumentOf(call, i)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate viableParamArgLambda(DataFlowCall call, ParameterNode p, ArgumentNode arg) {
|
||||
exists(int i |
|
||||
viableParamLambda(call, i, p) and
|
||||
arg.argumentOf(call, i)
|
||||
)
|
||||
}
|
||||
|
||||
private newtype TReturnPositionSimple =
|
||||
TReturnPositionSimple0(DataFlowCallable c, ReturnKind kind) {
|
||||
exists(ReturnNode ret |
|
||||
c = getNodeEnclosingCallable(ret) and
|
||||
kind = ret.getKind()
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private TReturnPositionSimple getReturnPositionSimple(ReturnNode ret, ReturnKind kind) {
|
||||
result = TReturnPositionSimple0(getNodeEnclosingCallable(ret), kind)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private TReturnPositionSimple viableReturnPosNonLambda(DataFlowCall call, ReturnKind kind) {
|
||||
result = TReturnPositionSimple0(viableCallable(call), kind)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private TReturnPositionSimple viableReturnPosLambda(
|
||||
DataFlowCall call, DataFlowCallOption lastCall, ReturnKind kind
|
||||
) {
|
||||
result = TReturnPositionSimple0(viableCallableLambda(call, lastCall), kind)
|
||||
}
|
||||
|
||||
private predicate viableReturnPosOutNonLambda(
|
||||
DataFlowCall call, TReturnPositionSimple pos, OutNode out
|
||||
) {
|
||||
exists(ReturnKind kind |
|
||||
pos = viableReturnPosNonLambda(call, kind) and
|
||||
out = getAnOutNode(call, kind)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate viableReturnPosOutLambda(
|
||||
DataFlowCall call, DataFlowCallOption lastCall, TReturnPositionSimple pos, OutNode out
|
||||
) {
|
||||
exists(ReturnKind kind |
|
||||
pos = viableReturnPosLambda(call, lastCall, kind) and
|
||||
out = getAnOutNode(call, kind)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data can flow (inter-procedurally) from `node` (of type `t`) to
|
||||
* the lambda call `lambdaCall`.
|
||||
*
|
||||
* The parameter `toReturn` indicates whether the path from `node` to
|
||||
* `lambdaCall` goes through a return, and `toJump` whether the path goes
|
||||
* through a jump step.
|
||||
*
|
||||
* The call context `lastCall` records the last call on the path from `node`
|
||||
* to `lambdaCall`, if any. That is, `lastCall` is able to target the enclosing
|
||||
* callable of `lambdaCall`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate revLambdaFlow(
|
||||
DataFlowCall lambdaCall, LambdaCallKind kind, Node node, DataFlowType t, boolean toReturn,
|
||||
boolean toJump, DataFlowCallOption lastCall
|
||||
) {
|
||||
revLambdaFlow0(lambdaCall, kind, node, t, toReturn, toJump, lastCall) and
|
||||
if node instanceof CastNode or node instanceof ArgumentNode or node instanceof ReturnNode
|
||||
then compatibleTypes(t, getNodeType(node))
|
||||
else any()
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate revLambdaFlow0(
|
||||
DataFlowCall lambdaCall, LambdaCallKind kind, Node node, DataFlowType t, boolean toReturn,
|
||||
boolean toJump, DataFlowCallOption lastCall
|
||||
) {
|
||||
lambdaCall(lambdaCall, kind, node) and
|
||||
t = getNodeType(node) and
|
||||
toReturn = false and
|
||||
toJump = false and
|
||||
lastCall = TDataFlowCallNone()
|
||||
or
|
||||
// local flow
|
||||
exists(Node mid, DataFlowType t0 |
|
||||
revLambdaFlow(lambdaCall, kind, mid, t0, toReturn, toJump, lastCall)
|
||||
|
|
||||
simpleLocalFlowStep(node, mid) and
|
||||
t = t0
|
||||
or
|
||||
exists(boolean preservesValue |
|
||||
additionalLambdaFlowStep(node, mid, preservesValue) and
|
||||
getNodeEnclosingCallable(node) = getNodeEnclosingCallable(mid)
|
||||
|
|
||||
preservesValue = false and
|
||||
t = getNodeType(node)
|
||||
or
|
||||
preservesValue = true and
|
||||
t = t0
|
||||
)
|
||||
)
|
||||
or
|
||||
// jump step
|
||||
exists(Node mid, DataFlowType t0 |
|
||||
revLambdaFlow(lambdaCall, kind, mid, t0, _, _, _) and
|
||||
toReturn = false and
|
||||
toJump = true and
|
||||
lastCall = TDataFlowCallNone()
|
||||
|
|
||||
jumpStep(node, mid) and
|
||||
t = t0
|
||||
or
|
||||
exists(boolean preservesValue |
|
||||
additionalLambdaFlowStep(node, mid, preservesValue) and
|
||||
getNodeEnclosingCallable(node) != getNodeEnclosingCallable(mid)
|
||||
|
|
||||
preservesValue = false and
|
||||
t = getNodeType(node)
|
||||
or
|
||||
preservesValue = true and
|
||||
t = t0
|
||||
)
|
||||
)
|
||||
or
|
||||
// flow into a callable
|
||||
exists(ParameterNode p, DataFlowCallOption lastCall0, DataFlowCall call |
|
||||
revLambdaFlowIn(lambdaCall, kind, p, t, toJump, lastCall0) and
|
||||
(
|
||||
if lastCall0 = TDataFlowCallNone() and toJump = false
|
||||
then lastCall = TDataFlowCallSome(call)
|
||||
else lastCall = lastCall0
|
||||
) and
|
||||
toReturn = false
|
||||
|
|
||||
viableParamArgNonLambda(call, p, node)
|
||||
or
|
||||
viableParamArgLambda(call, p, node) // non-linear recursion
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(TReturnPositionSimple pos |
|
||||
revLambdaFlowOut(lambdaCall, kind, pos, t, toJump, lastCall) and
|
||||
getReturnPositionSimple(node, node.(ReturnNode).getKind()) = pos and
|
||||
toReturn = true
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate revLambdaFlowOutLambdaCall(
|
||||
DataFlowCall lambdaCall, LambdaCallKind kind, OutNode out, DataFlowType t, boolean toJump,
|
||||
DataFlowCall call, DataFlowCallOption lastCall
|
||||
) {
|
||||
revLambdaFlow(lambdaCall, kind, out, t, _, toJump, lastCall) and
|
||||
exists(ReturnKindExt rk |
|
||||
out = rk.getAnOutNode(call) and
|
||||
lambdaCall(call, _, _)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate revLambdaFlowOut(
|
||||
DataFlowCall lambdaCall, LambdaCallKind kind, TReturnPositionSimple pos, DataFlowType t,
|
||||
boolean toJump, DataFlowCallOption lastCall
|
||||
) {
|
||||
exists(DataFlowCall call, OutNode out |
|
||||
revLambdaFlow(lambdaCall, kind, out, t, _, toJump, lastCall) and
|
||||
viableReturnPosOutNonLambda(call, pos, out)
|
||||
or
|
||||
// non-linear recursion
|
||||
revLambdaFlowOutLambdaCall(lambdaCall, kind, out, t, toJump, call, lastCall) and
|
||||
viableReturnPosOutLambda(call, _, pos, out)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate revLambdaFlowIn(
|
||||
DataFlowCall lambdaCall, LambdaCallKind kind, ParameterNode p, DataFlowType t, boolean toJump,
|
||||
DataFlowCallOption lastCall
|
||||
) {
|
||||
revLambdaFlow(lambdaCall, kind, p, t, false, toJump, lastCall)
|
||||
}
|
||||
}
|
||||
|
||||
private DataFlowCallable viableCallableExt(DataFlowCall call) {
|
||||
result = viableCallable(call)
|
||||
or
|
||||
result = viableCallableLambda(call, _)
|
||||
}
|
||||
|
||||
cached
|
||||
private module Cached {
|
||||
/**
|
||||
* Gets a viable target for the lambda call `call`.
|
||||
*
|
||||
* `lastCall` records the call required to reach `call` in order for the result
|
||||
* to be a viable target, if any.
|
||||
*/
|
||||
cached
|
||||
DataFlowCallable viableCallableLambda(DataFlowCall call, DataFlowCallOption lastCall) {
|
||||
exists(Node creation, LambdaCallKind kind |
|
||||
LambdaFlow::revLambdaFlow(call, kind, creation, _, _, _, lastCall) and
|
||||
lambdaCreation(creation, kind, result)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `p` is the `i`th parameter of a viable dispatch target of `call`.
|
||||
* The instance parameter is considered to have index `-1`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate viableParam(DataFlowCall call, int i, ParameterNode p) {
|
||||
p.isParameterOf(viableCallable(call), i)
|
||||
p.isParameterOf(viableCallableExt(call), i)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,7 +280,7 @@ private module Cached {
|
||||
|
||||
pragma[nomagic]
|
||||
private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) {
|
||||
viableCallable(call) = result.getCallable() and
|
||||
viableCallableExt(call) = result.getCallable() and
|
||||
kind = result.getKind()
|
||||
}
|
||||
|
||||
@@ -317,6 +545,35 @@ private module Cached {
|
||||
|
||||
cached
|
||||
private module DispatchWithCallContext {
|
||||
/**
|
||||
* Holds if the set of viable implementations that can be called by `call`
|
||||
* might be improved by knowing the call context.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate mayBenefitFromCallContextExt(DataFlowCall call, DataFlowCallable callable) {
|
||||
mayBenefitFromCallContext(call, callable)
|
||||
or
|
||||
callable = call.getEnclosingCallable() and
|
||||
exists(viableCallableLambda(call, TDataFlowCallSome(_)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a viable dispatch target of `call` in the context `ctx`. This is
|
||||
* restricted to those `call`s for which a context might make a difference.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private DataFlowCallable viableImplInCallContextExt(DataFlowCall call, DataFlowCall ctx) {
|
||||
result = viableImplInCallContext(call, ctx)
|
||||
or
|
||||
result = viableCallableLambda(call, TDataFlowCallSome(ctx))
|
||||
or
|
||||
exists(DataFlowCallable enclosing |
|
||||
mayBenefitFromCallContextExt(call, enclosing) and
|
||||
enclosing = viableCallableExt(ctx) and
|
||||
result = viableCallableLambda(call, TDataFlowCallNone())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the call context `ctx` reduces the set of viable run-time
|
||||
* dispatch targets of call `call` in `c`.
|
||||
@@ -324,10 +581,10 @@ private module Cached {
|
||||
cached
|
||||
predicate reducedViableImplInCallContext(DataFlowCall call, DataFlowCallable c, DataFlowCall ctx) {
|
||||
exists(int tgts, int ctxtgts |
|
||||
mayBenefitFromCallContext(call, c) and
|
||||
c = viableCallable(ctx) and
|
||||
ctxtgts = count(viableImplInCallContext(call, ctx)) and
|
||||
tgts = strictcount(viableCallable(call)) and
|
||||
mayBenefitFromCallContextExt(call, c) and
|
||||
c = viableCallableExt(ctx) and
|
||||
ctxtgts = count(viableImplInCallContextExt(call, ctx)) and
|
||||
tgts = strictcount(viableCallableExt(call)) and
|
||||
ctxtgts < tgts
|
||||
)
|
||||
}
|
||||
@@ -339,7 +596,7 @@ private module Cached {
|
||||
*/
|
||||
cached
|
||||
DataFlowCallable prunedViableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
|
||||
result = viableImplInCallContext(call, ctx) and
|
||||
result = viableImplInCallContextExt(call, ctx) and
|
||||
reducedViableImplInCallContext(call, _, ctx)
|
||||
}
|
||||
|
||||
@@ -351,10 +608,10 @@ private module Cached {
|
||||
cached
|
||||
predicate reducedViableImplInReturn(DataFlowCallable c, DataFlowCall call) {
|
||||
exists(int tgts, int ctxtgts |
|
||||
mayBenefitFromCallContext(call, _) and
|
||||
c = viableCallable(call) and
|
||||
ctxtgts = count(DataFlowCall ctx | c = viableImplInCallContext(call, ctx)) and
|
||||
tgts = strictcount(DataFlowCall ctx | viableCallable(ctx) = call.getEnclosingCallable()) and
|
||||
mayBenefitFromCallContextExt(call, _) and
|
||||
c = viableCallableExt(call) and
|
||||
ctxtgts = count(DataFlowCall ctx | c = viableImplInCallContextExt(call, ctx)) and
|
||||
tgts = strictcount(DataFlowCall ctx | viableCallableExt(ctx) = call.getEnclosingCallable()) and
|
||||
ctxtgts < tgts
|
||||
)
|
||||
}
|
||||
@@ -367,7 +624,7 @@ private module Cached {
|
||||
*/
|
||||
cached
|
||||
DataFlowCallable prunedViableImplInCallContextReverse(DataFlowCall call, DataFlowCall ctx) {
|
||||
result = viableImplInCallContext(call, ctx) and
|
||||
result = viableImplInCallContextExt(call, ctx) and
|
||||
reducedViableImplInReturn(result, call)
|
||||
}
|
||||
}
|
||||
@@ -481,6 +738,11 @@ private module Cached {
|
||||
TBooleanNone() or
|
||||
TBooleanSome(boolean b) { b = true or b = false }
|
||||
|
||||
cached
|
||||
newtype TDataFlowCallOption =
|
||||
TDataFlowCallNone() or
|
||||
TDataFlowCallSome(DataFlowCall call)
|
||||
|
||||
cached
|
||||
newtype TTypedContent = MkTypedContent(Content c, DataFlowType t) { store(_, c, _, _, t) }
|
||||
|
||||
@@ -777,7 +1039,7 @@ ReturnPosition getReturnPosition(ReturnNodeExt ret) {
|
||||
|
||||
bindingset[cc, callable]
|
||||
predicate resolveReturn(CallContext cc, DataFlowCallable callable, DataFlowCall call) {
|
||||
cc instanceof CallContextAny and callable = viableCallable(call)
|
||||
cc instanceof CallContextAny and callable = viableCallableExt(call)
|
||||
or
|
||||
exists(DataFlowCallable c0, DataFlowCall call0 |
|
||||
call0.getEnclosingCallable() = callable and
|
||||
@@ -791,14 +1053,14 @@ DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) {
|
||||
exists(DataFlowCall ctx | cc = TSpecificCall(ctx) |
|
||||
if reducedViableImplInCallContext(call, _, ctx)
|
||||
then result = prunedViableImplInCallContext(call, ctx)
|
||||
else result = viableCallable(call)
|
||||
else result = viableCallableExt(call)
|
||||
)
|
||||
or
|
||||
result = viableCallable(call) and cc instanceof CallContextSomeCall
|
||||
result = viableCallableExt(call) and cc instanceof CallContextSomeCall
|
||||
or
|
||||
result = viableCallable(call) and cc instanceof CallContextAny
|
||||
result = viableCallableExt(call) and cc instanceof CallContextAny
|
||||
or
|
||||
result = viableCallable(call) and cc instanceof CallContextReturn
|
||||
result = viableCallableExt(call) and cc instanceof CallContextReturn
|
||||
}
|
||||
|
||||
predicate read = readStep/3;
|
||||
@@ -812,6 +1074,19 @@ class BooleanOption extends TBooleanOption {
|
||||
}
|
||||
}
|
||||
|
||||
/** An optional `DataFlowCall`. */
|
||||
class DataFlowCallOption extends TDataFlowCallOption {
|
||||
string toString() {
|
||||
this = TDataFlowCallNone() and
|
||||
result = "(none)"
|
||||
or
|
||||
exists(DataFlowCall call |
|
||||
this = TDataFlowCallSome(call) and
|
||||
result = call.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Content tagged with the type of a containing object. */
|
||||
class TypedContent extends MkTypedContent {
|
||||
private Content c;
|
||||
|
||||
@@ -548,3 +548,14 @@ predicate isImmutableOrUnobservable(Node n) {
|
||||
|
||||
/** Holds if `n` should be hidden from path explanations. */
|
||||
predicate nodeIsHidden(Node n) { n instanceof OperandNode and not n instanceof ArgumentNode }
|
||||
|
||||
class LambdaCallKind = Unit;
|
||||
|
||||
/** Holds if `creation` is an expression that creates a lambda of kind `kind` for `c`. */
|
||||
predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) { none() }
|
||||
|
||||
/** Holds if `call` is a lambda call of kind `kind` where `receiver` is the lambda expression. */
|
||||
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { none() }
|
||||
|
||||
/** Extra data-flow steps needed for lamba flow analysis. */
|
||||
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }
|
||||
|
||||
@@ -362,15 +362,22 @@ private class ExplicitFieldStoreQualifierNode extends PartialDefinitionNode {
|
||||
|
||||
/**
|
||||
* Not every store instruction generates a chi instruction that we can attach a PostUpdateNode to.
|
||||
* For instance, an update to a field of a struct containing only one field. For these cases we
|
||||
* attach the PostUpdateNode to the store instruction. There's no obvious pre update node for this case
|
||||
* (as the entire memory is updated), so `getPreUpdateNode` is implemented as `none()`.
|
||||
* For instance, an update to a field of a struct containing only one field. Even if the store does
|
||||
* have a chi instruction, a subsequent use of the result of the store may be linked directly to the
|
||||
* result of the store as an inexact definition if the store totally overlaps the use. For these
|
||||
* cases we attach the PostUpdateNode to the store instruction. There's no obvious pre update node
|
||||
* for this case (as the entire memory is updated), so `getPreUpdateNode` is implemented as
|
||||
* `none()`.
|
||||
*/
|
||||
private class ExplicitSingleFieldStoreQualifierNode extends PartialDefinitionNode {
|
||||
override StoreInstruction instr;
|
||||
|
||||
ExplicitSingleFieldStoreQualifierNode() {
|
||||
not exists(ChiInstruction chi | chi.getPartial() = instr) and
|
||||
(
|
||||
instr.getAUse().isDefinitionInexact()
|
||||
or
|
||||
not exists(ChiInstruction chi | chi.getPartial() = instr)
|
||||
) and
|
||||
// Without this condition any store would create a `PostUpdateNode`.
|
||||
instr.getDestinationAddress() instanceof FieldAddressInstruction
|
||||
}
|
||||
|
||||
@@ -9,30 +9,18 @@ private import semmle.code.cpp.ir.dataflow.DataFlow
|
||||
/**
|
||||
* Gets the instruction that goes into `input` for `call`.
|
||||
*/
|
||||
DataFlow::Node callInput(CallInstruction call, FunctionInput input) {
|
||||
// A positional argument
|
||||
Operand callInput(CallInstruction call, FunctionInput input) {
|
||||
// An argument or qualifier
|
||||
exists(int index |
|
||||
result.asInstruction() = call.getPositionalArgument(index) and
|
||||
input.isParameter(index)
|
||||
result = call.getArgumentOperand(index) and
|
||||
input.isParameterOrQualifierAddress(index)
|
||||
)
|
||||
or
|
||||
// A value pointed to by a positional argument
|
||||
// A value pointed to by an argument or qualifier
|
||||
exists(ReadSideEffectInstruction read |
|
||||
result.asOperand() = read.getSideEffectOperand() and
|
||||
result = read.getSideEffectOperand() and
|
||||
read.getPrimaryInstruction() = call and
|
||||
input.isParameterDeref(read.getIndex())
|
||||
)
|
||||
or
|
||||
// The qualifier pointer
|
||||
result.asInstruction() = call.getThisArgument() and
|
||||
input.isQualifierAddress()
|
||||
or
|
||||
// The qualifier object
|
||||
exists(ReadSideEffectInstruction read |
|
||||
result.asOperand() = read.getSideEffectOperand() and
|
||||
read.getPrimaryInstruction() = call and
|
||||
read.getIndex() = -1 and
|
||||
input.isQualifierObject()
|
||||
input.isParameterDerefOrQualifierObject(read.getIndex())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -44,19 +32,11 @@ Instruction callOutput(CallInstruction call, FunctionOutput output) {
|
||||
result = call and
|
||||
output.isReturnValue()
|
||||
or
|
||||
// The side effect of a call on the value pointed to by a positional argument
|
||||
// The side effect of a call on the value pointed to by an argument or qualifier
|
||||
exists(WriteSideEffectInstruction effect |
|
||||
result = effect and
|
||||
effect.getPrimaryInstruction() = call and
|
||||
output.isParameterDeref(effect.getIndex())
|
||||
)
|
||||
or
|
||||
// The side effect of a call on the qualifier object
|
||||
exists(WriteSideEffectInstruction effect |
|
||||
result = effect and
|
||||
effect.getPrimaryInstruction() = call and
|
||||
effect.getIndex() = -1 and
|
||||
output.isQualifierObject()
|
||||
output.isParameterDerefOrQualifierObject(effect.getIndex())
|
||||
)
|
||||
// TODO: return value dereference
|
||||
}
|
||||
|
||||
@@ -6,34 +6,7 @@ private import semmle.code.cpp.ir.ValueNumbering
|
||||
private import semmle.code.cpp.ir.IR
|
||||
private import semmle.code.cpp.ir.dataflow.DataFlow
|
||||
private import semmle.code.cpp.ir.dataflow.internal.DataFlowUtil
|
||||
|
||||
/**
|
||||
* Gets a short ID for an IR dataflow node.
|
||||
* - For `Instruction`s, this is just the result ID of the instruction (e.g. `m128`).
|
||||
* - For `Operand`s, this is the label of the operand, prefixed with the result ID of the
|
||||
* instruction and a dot (e.g. `m128.left`).
|
||||
* - For `Variable`s, this is the qualified name of the variable.
|
||||
*/
|
||||
private string nodeId(DataFlow::Node node, int order1, int order2) {
|
||||
exists(Instruction instruction | instruction = node.asInstruction() |
|
||||
result = instruction.getResultId() and
|
||||
order1 = instruction.getBlock().getDisplayIndex() and
|
||||
order2 = instruction.getDisplayIndexInBlock()
|
||||
)
|
||||
or
|
||||
exists(Operand operand, Instruction instruction |
|
||||
operand = node.asOperand() and
|
||||
instruction = operand.getUse()
|
||||
|
|
||||
result = instruction.getResultId() + "." + operand.getDumpId() and
|
||||
order1 = instruction.getBlock().getDisplayIndex() and
|
||||
order2 = instruction.getDisplayIndexInBlock()
|
||||
)
|
||||
or
|
||||
result = "var(" + node.asVariable().getQualifiedName() + ")" and
|
||||
order1 = 1000000 and
|
||||
order2 = 0
|
||||
}
|
||||
private import PrintIRUtilities
|
||||
|
||||
/**
|
||||
* Gets the local dataflow from other nodes in the same function to this node.
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Print the dataflow local store steps in IR dumps.
|
||||
*/
|
||||
|
||||
private import cpp
|
||||
// The `ValueNumbering` library has to be imported right after `cpp` to ensure
|
||||
// that the cached IR gets the same checksum here as it does in queries that use
|
||||
// `ValueNumbering` without `DataFlow`.
|
||||
private import semmle.code.cpp.ir.ValueNumbering
|
||||
private import semmle.code.cpp.ir.IR
|
||||
private import semmle.code.cpp.ir.dataflow.DataFlow
|
||||
private import semmle.code.cpp.ir.dataflow.internal.DataFlowUtil
|
||||
private import semmle.code.cpp.ir.dataflow.internal.DataFlowPrivate
|
||||
private import PrintIRUtilities
|
||||
|
||||
/**
|
||||
* Property provider for local IR dataflow store steps.
|
||||
*/
|
||||
class LocalFlowPropertyProvider extends IRPropertyProvider {
|
||||
override string getInstructionProperty(Instruction instruction, string key) {
|
||||
exists(DataFlow::Node objectNode, Content content |
|
||||
key = "content[" + content.toString() + "]" and
|
||||
instruction = objectNode.asInstruction() and
|
||||
result =
|
||||
strictconcat(string element, DataFlow::Node fieldNode |
|
||||
storeStep(fieldNode, content, objectNode) and
|
||||
element = nodeId(fieldNode, _, _)
|
||||
|
|
||||
element, ", "
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Shared utilities used when printing dataflow annotations in IR dumps.
|
||||
*/
|
||||
|
||||
private import cpp
|
||||
// The `ValueNumbering` library has to be imported right after `cpp` to ensure
|
||||
// that the cached IR gets the same checksum here as it does in queries that use
|
||||
// `ValueNumbering` without `DataFlow`.
|
||||
private import semmle.code.cpp.ir.ValueNumbering
|
||||
private import semmle.code.cpp.ir.IR
|
||||
private import semmle.code.cpp.ir.dataflow.DataFlow
|
||||
|
||||
/**
|
||||
* Gets a short ID for an IR dataflow node.
|
||||
* - For `Instruction`s, this is just the result ID of the instruction (e.g. `m128`).
|
||||
* - For `Operand`s, this is the label of the operand, prefixed with the result ID of the
|
||||
* instruction and a dot (e.g. `m128.left`).
|
||||
* - For `Variable`s, this is the qualified name of the variable.
|
||||
*/
|
||||
string nodeId(DataFlow::Node node, int order1, int order2) {
|
||||
exists(Instruction instruction | instruction = node.asInstruction() |
|
||||
result = instruction.getResultId() and
|
||||
order1 = instruction.getBlock().getDisplayIndex() and
|
||||
order2 = instruction.getDisplayIndexInBlock()
|
||||
)
|
||||
or
|
||||
exists(Operand operand, Instruction instruction |
|
||||
operand = node.asOperand() and
|
||||
instruction = operand.getUse()
|
||||
|
|
||||
result = instruction.getResultId() + "." + operand.getDumpId() and
|
||||
order1 = instruction.getBlock().getDisplayIndex() and
|
||||
order2 = instruction.getDisplayIndexInBlock()
|
||||
)
|
||||
or
|
||||
result = "var(" + node.asVariable().getQualifiedName() + ")" and
|
||||
order1 = 1000000 and
|
||||
order2 = 0
|
||||
}
|
||||
@@ -21,53 +21,104 @@ predicate localTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
*/
|
||||
cached
|
||||
predicate localAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
localInstructionTaintStep(nodeFrom.asInstruction(), nodeTo.asInstruction())
|
||||
operandToInstructionTaintStep(nodeFrom.asOperand(), nodeTo.asInstruction())
|
||||
or
|
||||
modeledTaintStep(nodeFrom, nodeTo)
|
||||
instructionToOperandTaintStep(nodeFrom.asInstruction(), nodeTo.asOperand())
|
||||
}
|
||||
|
||||
private predicate instructionToOperandTaintStep(Instruction fromInstr, Operand toOperand) {
|
||||
// Propagate flow from the definition of an operand to the operand, even when the overlap is inexact.
|
||||
// We only do this in certain cases:
|
||||
// 1. The instruction's result must not be conflated, and
|
||||
// 2. The instruction's result type is one the types where we expect element-to-object flow. Currently
|
||||
// this is array types and union types. This matches the other two cases of element-to-object flow in
|
||||
// `DefaultTaintTracking`.
|
||||
toOperand.getAnyDef() = fromInstr and
|
||||
not fromInstr.isResultConflated() and
|
||||
(
|
||||
fromInstr.getResultType() instanceof ArrayType or
|
||||
fromInstr.getResultType() instanceof Union
|
||||
)
|
||||
or
|
||||
exists(ReadSideEffectInstruction readInstr |
|
||||
fromInstr = readInstr.getArgumentDef() and
|
||||
toOperand = readInstr.getSideEffectOperand()
|
||||
)
|
||||
or
|
||||
toOperand.(LoadOperand).getAnyDef() = fromInstr
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if taint propagates from `nodeFrom` to `nodeTo` in exactly one local
|
||||
* (intra-procedural) step.
|
||||
*/
|
||||
private predicate localInstructionTaintStep(Instruction nodeFrom, Instruction nodeTo) {
|
||||
private predicate operandToInstructionTaintStep(Operand opFrom, Instruction instrTo) {
|
||||
// Taint can flow through expressions that alter the value but preserve
|
||||
// more than one bit of it _or_ expressions that follow data through
|
||||
// pointer indirections.
|
||||
nodeTo.getAnOperand().getAnyDef() = nodeFrom and
|
||||
instrTo.getAnOperand() = opFrom and
|
||||
(
|
||||
nodeTo instanceof ArithmeticInstruction
|
||||
instrTo instanceof ArithmeticInstruction
|
||||
or
|
||||
nodeTo instanceof BitwiseInstruction
|
||||
instrTo instanceof BitwiseInstruction
|
||||
or
|
||||
nodeTo instanceof PointerArithmeticInstruction
|
||||
or
|
||||
nodeTo instanceof FieldAddressInstruction
|
||||
instrTo instanceof PointerArithmeticInstruction
|
||||
or
|
||||
// The `CopyInstruction` case is also present in non-taint data flow, but
|
||||
// that uses `getDef` rather than `getAnyDef`. For taint, we want flow
|
||||
// from a definition of `myStruct` to a `myStruct.myField` expression.
|
||||
nodeTo instanceof CopyInstruction
|
||||
instrTo instanceof CopyInstruction
|
||||
)
|
||||
or
|
||||
nodeTo.(LoadInstruction).getSourceAddress() = nodeFrom
|
||||
or
|
||||
// Flow through partial reads of arrays and unions
|
||||
nodeTo.(LoadInstruction).getSourceValueOperand().getAnyDef() = nodeFrom and
|
||||
not nodeFrom.isResultConflated() and
|
||||
// Unary instructions tend to preserve enough information in practice that we
|
||||
// want taint to flow through.
|
||||
// The exception is `FieldAddressInstruction`. Together with the rules below for
|
||||
// `LoadInstruction`s and `ChiInstruction`s, flow through `FieldAddressInstruction`
|
||||
// could cause flow into one field to come out an unrelated field.
|
||||
// This would happen across function boundaries, where the IR would not be able to
|
||||
// match loads to stores.
|
||||
instrTo.(UnaryInstruction).getUnaryOperand() = opFrom and
|
||||
(
|
||||
nodeFrom.getResultType() instanceof ArrayType or
|
||||
nodeFrom.getResultType() instanceof Union
|
||||
not instrTo instanceof FieldAddressInstruction
|
||||
or
|
||||
instrTo.(FieldAddressInstruction).getField().getDeclaringType() instanceof Union
|
||||
)
|
||||
or
|
||||
instrTo.(LoadInstruction).getSourceAddressOperand() = opFrom
|
||||
or
|
||||
// Flow from an element to an array or union that contains it.
|
||||
nodeTo.(ChiInstruction).getPartial() = nodeFrom and
|
||||
not nodeTo.isResultConflated() and
|
||||
exists(Type t | nodeTo.getResultLanguageType().hasType(t, false) |
|
||||
instrTo.(ChiInstruction).getPartialOperand() = opFrom and
|
||||
not instrTo.isResultConflated() and
|
||||
exists(Type t | instrTo.getResultLanguageType().hasType(t, false) |
|
||||
t instanceof Union
|
||||
or
|
||||
t instanceof ArrayType
|
||||
)
|
||||
or
|
||||
// Until we have flow through indirections across calls, we'll take flow out
|
||||
// of the indirection and into the argument.
|
||||
// When we get proper flow through indirections across calls, this code can be
|
||||
// moved to `adjusedSink` or possibly into the `DataFlow::ExprNode` class.
|
||||
exists(ReadSideEffectInstruction read |
|
||||
read.getSideEffectOperand() = opFrom and
|
||||
read.getArgumentDef() = instrTo
|
||||
)
|
||||
or
|
||||
// Until we have from through indirections across calls, we'll take flow out
|
||||
// of the parameter and into its indirection.
|
||||
// `InitializeIndirectionInstruction` only has a single operand: the address of the
|
||||
// value whose indirection we are initializing. When initializing an indirection of a parameter `p`,
|
||||
// the IR looks like this:
|
||||
// ```
|
||||
// m1 = InitializeParameter[p] : &r1
|
||||
// r2 = Load[p] : r2, m1
|
||||
// m3 = InitializeIndirection[p] : &r2
|
||||
// ```
|
||||
// So by having flow from `r2` to `m3` we're enabling flow from `m1` to `m3`. This relies on the
|
||||
// `LoadOperand`'s overlap being exact.
|
||||
instrTo.(InitializeIndirectionInstruction).getAnOperand() = opFrom
|
||||
or
|
||||
modeledTaintStep(opFrom, instrTo)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,17 +161,19 @@ predicate defaultTaintSanitizer(DataFlow::Node node) { none() }
|
||||
* Holds if taint can flow from `instrIn` to `instrOut` through a call to a
|
||||
* modeled function.
|
||||
*/
|
||||
predicate modeledTaintStep(DataFlow::Node nodeIn, DataFlow::Node nodeOut) {
|
||||
predicate modeledTaintStep(Operand nodeIn, Instruction nodeOut) {
|
||||
exists(CallInstruction call, TaintFunction func, FunctionInput modelIn, FunctionOutput modelOut |
|
||||
(
|
||||
nodeIn = callInput(call, modelIn)
|
||||
or
|
||||
exists(int n |
|
||||
modelIn.isParameterDeref(n) and
|
||||
nodeIn = callInput(call, any(InParameter inParam | inParam.getIndex() = n))
|
||||
modelIn.isParameterDerefOrQualifierObject(n) and
|
||||
if n = -1
|
||||
then nodeIn = callInput(call, any(InQualifierObject inQualifier))
|
||||
else nodeIn = callInput(call, any(InParameter inParam | inParam.getIndex() = n))
|
||||
)
|
||||
) and
|
||||
nodeOut.asInstruction() = callOutput(call, modelOut) and
|
||||
nodeOut = callOutput(call, modelOut) and
|
||||
call.getStaticCallTarget() = func and
|
||||
func.hasTaintFlow(modelIn, modelOut)
|
||||
)
|
||||
@@ -135,11 +188,29 @@ predicate modeledTaintStep(DataFlow::Node nodeIn, DataFlow::Node nodeOut) {
|
||||
int indexMid, InParameter modelMidIn, OutReturnValue modelOut
|
||||
|
|
||||
nodeIn = callInput(call, modelIn) and
|
||||
nodeOut.asInstruction() = callOutput(call, modelOut) and
|
||||
nodeOut = callOutput(call, modelOut) and
|
||||
call.getStaticCallTarget() = func and
|
||||
func.(TaintFunction).hasTaintFlow(modelIn, modelMidOut) and
|
||||
func.(DataFlowFunction).hasDataFlow(modelMidIn, modelOut) and
|
||||
modelMidOut.isParameterDeref(indexMid) and
|
||||
modelMidIn.isParameter(indexMid)
|
||||
)
|
||||
or
|
||||
// Taint flow from a pointer argument to an output, when the model specifies flow from the deref
|
||||
// to that output, but the deref is not modeled in the IR for the caller.
|
||||
exists(
|
||||
CallInstruction call, ReadSideEffectInstruction read, Function func, FunctionInput modelIn,
|
||||
FunctionOutput modelOut
|
||||
|
|
||||
read.getSideEffectOperand() = callInput(call, modelIn) and
|
||||
read.getArgumentDef() = nodeIn.getDef() and
|
||||
not read.getSideEffect().isResultModeled() and
|
||||
call.getStaticCallTarget() = func and
|
||||
(
|
||||
func.(DataFlowFunction).hasDataFlow(modelIn, modelOut)
|
||||
or
|
||||
func.(TaintFunction).hasTaintFlow(modelIn, modelOut)
|
||||
) and
|
||||
nodeOut = callOutput(call, modelOut)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Provides an implementation of global (interprocedural) taint tracking.
|
||||
* This file re-exports the local (intraprocedural) taint-tracking analysis
|
||||
* from `TaintTrackingParameter::Public` and adds a global analysis, mainly
|
||||
* exposed through the `Configuration` class. For some languages, this file
|
||||
* exists in several identical copies, allowing queries to use multiple
|
||||
* `Configuration` classes that depend on each other without introducing
|
||||
* mutual recursion among those configurations.
|
||||
*/
|
||||
|
||||
import TaintTrackingParameter::Public
|
||||
private import TaintTrackingParameter::Private
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural taint tracking analysis. This defines
|
||||
* sources, sinks, and any other configurable aspect of the analysis. Each
|
||||
* use of the taint tracking library must define its own unique extension of
|
||||
* this abstract class.
|
||||
*
|
||||
* A taint-tracking configuration is a special data flow configuration
|
||||
* (`DataFlow::Configuration`) that allows for flow through nodes that do not
|
||||
* necessarily preserve values but are still relevant from a taint tracking
|
||||
* perspective. (For example, string concatenation, where one of the operands
|
||||
* is tainted.)
|
||||
*
|
||||
* To create a configuration, extend this class with a subclass whose
|
||||
* characteristic predicate is a unique singleton string. For example, write
|
||||
*
|
||||
* ```ql
|
||||
* class MyAnalysisConfiguration extends TaintTracking::Configuration {
|
||||
* MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" }
|
||||
* // Override `isSource` and `isSink`.
|
||||
* // Optionally override `isSanitizer`.
|
||||
* // Optionally override `isSanitizerIn`.
|
||||
* // Optionally override `isSanitizerOut`.
|
||||
* // Optionally override `isSanitizerGuard`.
|
||||
* // Optionally override `isAdditionalTaintStep`.
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Then, to query whether there is flow between some `source` and `sink`,
|
||||
* write
|
||||
*
|
||||
* ```ql
|
||||
* exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink))
|
||||
* ```
|
||||
*
|
||||
* Multiple configurations can coexist, but it is unsupported to depend on
|
||||
* another `TaintTracking::Configuration` or a `DataFlow::Configuration` in the
|
||||
* overridden predicates that define sources, sinks, or additional steps.
|
||||
* Instead, the dependency should go to a `TaintTracking2::Configuration` or a
|
||||
* `DataFlow2::Configuration`, `DataFlow3::Configuration`, etc.
|
||||
*/
|
||||
abstract class Configuration extends DataFlow::Configuration {
|
||||
bindingset[this]
|
||||
Configuration() { any() }
|
||||
|
||||
/**
|
||||
* Holds if `source` is a relevant taint source.
|
||||
*
|
||||
* The smaller this predicate is, the faster `hasFlow()` will converge.
|
||||
*/
|
||||
// overridden to provide taint-tracking specific qldoc
|
||||
abstract override predicate isSource(DataFlow::Node source);
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant taint sink.
|
||||
*
|
||||
* The smaller this predicate is, the faster `hasFlow()` will converge.
|
||||
*/
|
||||
// overridden to provide taint-tracking specific qldoc
|
||||
abstract override predicate isSink(DataFlow::Node sink);
|
||||
|
||||
/** Holds if the node `node` is a taint sanitizer. */
|
||||
predicate isSanitizer(DataFlow::Node node) { none() }
|
||||
|
||||
final override predicate isBarrier(DataFlow::Node node) {
|
||||
isSanitizer(node) or
|
||||
defaultTaintSanitizer(node)
|
||||
}
|
||||
|
||||
/** Holds if taint propagation into `node` is prohibited. */
|
||||
predicate isSanitizerIn(DataFlow::Node node) { none() }
|
||||
|
||||
final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
|
||||
|
||||
/** Holds if taint propagation out of `node` is prohibited. */
|
||||
predicate isSanitizerOut(DataFlow::Node node) { none() }
|
||||
|
||||
final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
|
||||
|
||||
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
|
||||
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
|
||||
|
||||
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) }
|
||||
|
||||
/**
|
||||
* Holds if the additional taint propagation step from `node1` to `node2`
|
||||
* must be taken into account in the analysis.
|
||||
*/
|
||||
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
|
||||
|
||||
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
isAdditionalTaintStep(node1, node2) or
|
||||
defaultAdditionalTaintStep(node1, node2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if taint may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
// overridden to provide taint-tracking specific qldoc
|
||||
override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
|
||||
super.hasFlow(source, sink)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import semmle.code.cpp.ir.dataflow.internal.TaintTrackingUtil as Public
|
||||
|
||||
module Private {
|
||||
import semmle.code.cpp.ir.dataflow.DataFlow3::DataFlow3 as DataFlow
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Predicates to compute the modeled side effects of calls during IR construction.
|
||||
*
|
||||
* These are used in `TranslatedElement.qll` to generate the `TTranslatedSideEffect` instances, and
|
||||
* also in `TranslatedCall.qll` to inject the actual side effect instructions.
|
||||
*/
|
||||
|
||||
private import cpp
|
||||
private import semmle.code.cpp.ir.implementation.Opcode
|
||||
private import semmle.code.cpp.models.interfaces.SideEffect
|
||||
|
||||
/**
|
||||
* Holds if the specified call has a side effect that does not come from a `SideEffectFunction`
|
||||
* model.
|
||||
*/
|
||||
private predicate hasDefaultSideEffect(Call call, ParameterIndex i, boolean buffer, boolean isWrite) {
|
||||
not call.getTarget() instanceof SideEffectFunction and
|
||||
(
|
||||
exists(MemberFunction mfunc |
|
||||
// A non-static member function, including a constructor or destructor, may write to `*this`,
|
||||
// and may also read from `*this` if it is not a constructor.
|
||||
i = -1 and
|
||||
mfunc = call.getTarget() and
|
||||
not mfunc.isStatic() and
|
||||
buffer = false and
|
||||
(
|
||||
isWrite = false and not mfunc instanceof Constructor
|
||||
or
|
||||
isWrite = true and not mfunc instanceof ConstMemberFunction
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(Expr expr |
|
||||
// A pointer-like argument is assumed to read from the pointed-to buffer, and may write to the
|
||||
// buffer as well unless the pointer points to a `const` value.
|
||||
i >= 0 and
|
||||
buffer = true and
|
||||
expr = call.getArgument(i).getFullyConverted() and
|
||||
exists(Type t | t = expr.getUnspecifiedType() |
|
||||
t instanceof ArrayType or
|
||||
t instanceof PointerType or
|
||||
t instanceof ReferenceType
|
||||
) and
|
||||
(
|
||||
isWrite = true and
|
||||
not call.getTarget().getParameter(i).getType().isDeeplyConstBelow()
|
||||
or
|
||||
isWrite = false
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a side effect opcode for parameter index `i` of the specified call.
|
||||
*
|
||||
* This predicate will return at most two results: one read side effect, and one write side effect.
|
||||
*/
|
||||
Opcode getASideEffectOpcode(Call call, ParameterIndex i) {
|
||||
exists(boolean buffer |
|
||||
(
|
||||
call.getTarget().(SideEffectFunction).hasSpecificReadSideEffect(i, buffer)
|
||||
or
|
||||
not call.getTarget() instanceof SideEffectFunction and
|
||||
hasDefaultSideEffect(call, i, buffer, false)
|
||||
) and
|
||||
if exists(call.getTarget().(SideEffectFunction).getParameterSizeIndex(i))
|
||||
then (
|
||||
buffer = true and
|
||||
result instanceof Opcode::SizedBufferReadSideEffect
|
||||
) else (
|
||||
buffer = false and result instanceof Opcode::IndirectReadSideEffect
|
||||
or
|
||||
buffer = true and result instanceof Opcode::BufferReadSideEffect
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(boolean buffer, boolean mustWrite |
|
||||
(
|
||||
call.getTarget().(SideEffectFunction).hasSpecificWriteSideEffect(i, buffer, mustWrite)
|
||||
or
|
||||
not call.getTarget() instanceof SideEffectFunction and
|
||||
hasDefaultSideEffect(call, i, buffer, true) and
|
||||
mustWrite = false
|
||||
) and
|
||||
if exists(call.getTarget().(SideEffectFunction).getParameterSizeIndex(i))
|
||||
then (
|
||||
buffer = true and
|
||||
mustWrite = false and
|
||||
result instanceof Opcode::SizedBufferMayWriteSideEffect
|
||||
or
|
||||
buffer = true and
|
||||
mustWrite = true and
|
||||
result instanceof Opcode::SizedBufferMustWriteSideEffect
|
||||
) else (
|
||||
buffer = false and
|
||||
mustWrite = false and
|
||||
result instanceof Opcode::IndirectMayWriteSideEffect
|
||||
or
|
||||
buffer = false and
|
||||
mustWrite = true and
|
||||
result instanceof Opcode::IndirectMustWriteSideEffect
|
||||
or
|
||||
buffer = true and mustWrite = false and result instanceof Opcode::BufferMayWriteSideEffect
|
||||
or
|
||||
buffer = true and mustWrite = true and result instanceof Opcode::BufferMustWriteSideEffect
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ private import semmle.code.cpp.ir.implementation.internal.OperandTag
|
||||
private import semmle.code.cpp.ir.internal.CppType
|
||||
private import semmle.code.cpp.models.interfaces.SideEffect
|
||||
private import InstructionTag
|
||||
private import SideEffects
|
||||
private import TranslatedElement
|
||||
private import TranslatedExpr
|
||||
private import TranslatedFunction
|
||||
@@ -424,12 +425,15 @@ class TranslatedCallSideEffects extends TranslatedSideEffects, TTranslatedCallSi
|
||||
}
|
||||
|
||||
class TranslatedStructorCallSideEffects extends TranslatedCallSideEffects {
|
||||
TranslatedStructorCallSideEffects() { getParent().(TranslatedStructorCall).hasQualifier() }
|
||||
TranslatedStructorCallSideEffects() {
|
||||
getParent().(TranslatedStructorCall).hasQualifier() and
|
||||
getASideEffectOpcode(expr, -1) instanceof WriteSideEffectOpcode
|
||||
}
|
||||
|
||||
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType t) {
|
||||
opcode instanceof Opcode::IndirectMayWriteSideEffect and
|
||||
tag instanceof OnlyInstructionTag and
|
||||
t = getTypeForPRValue(expr.getTarget().getDeclaringType())
|
||||
t = getTypeForPRValue(expr.getTarget().getDeclaringType()) and
|
||||
opcode = getASideEffectOpcode(expr, -1).(WriteSideEffectOpcode)
|
||||
}
|
||||
|
||||
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
|
||||
@@ -460,9 +464,11 @@ class TranslatedSideEffect extends TranslatedElement, TTranslatedArgumentSideEff
|
||||
Call call;
|
||||
Expr arg;
|
||||
int index;
|
||||
boolean write;
|
||||
SideEffectOpcode sideEffectOpcode;
|
||||
|
||||
TranslatedSideEffect() { this = TTranslatedArgumentSideEffect(call, arg, index, write) }
|
||||
TranslatedSideEffect() {
|
||||
this = TTranslatedArgumentSideEffect(call, arg, index, sideEffectOpcode)
|
||||
}
|
||||
|
||||
override Locatable getAST() { result = arg }
|
||||
|
||||
@@ -472,13 +478,13 @@ class TranslatedSideEffect extends TranslatedElement, TTranslatedArgumentSideEff
|
||||
|
||||
int getArgumentIndex() { result = index }
|
||||
|
||||
predicate isWrite() { write = true }
|
||||
predicate isWrite() { sideEffectOpcode instanceof WriteSideEffectOpcode }
|
||||
|
||||
override string toString() {
|
||||
write = true and
|
||||
isWrite() and
|
||||
result = "(write side effect for " + arg.toString() + ")"
|
||||
or
|
||||
write = false and
|
||||
not isWrite() and
|
||||
result = "(read side effect for " + arg.toString() + ")"
|
||||
}
|
||||
|
||||
@@ -489,29 +495,29 @@ class TranslatedSideEffect extends TranslatedElement, TTranslatedArgumentSideEff
|
||||
override Instruction getFirstInstruction() { result = getInstruction(OnlyInstructionTag()) }
|
||||
|
||||
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType type) {
|
||||
isWrite() and
|
||||
hasSpecificWriteSideEffect(opcode) and
|
||||
tag = OnlyInstructionTag() and
|
||||
opcode = sideEffectOpcode 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)
|
||||
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
|
||||
index = -1 and
|
||||
not arg.getUnspecifiedType() instanceof DerivedType and
|
||||
type = getTypeForPRValueOrUnknown(arg.getUnspecifiedType())
|
||||
not isWrite() and
|
||||
type = getVoidType()
|
||||
)
|
||||
or
|
||||
not isWrite() and
|
||||
hasSpecificReadSideEffect(opcode) and
|
||||
tag = OnlyInstructionTag() and
|
||||
type = getVoidType()
|
||||
}
|
||||
|
||||
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
|
||||
@@ -535,7 +541,7 @@ class TranslatedSideEffect extends TranslatedElement, TTranslatedArgumentSideEff
|
||||
|
||||
override CppType getInstructionMemoryOperandType(InstructionTag tag, TypedOperandTag operandTag) {
|
||||
not isWrite() and
|
||||
if hasSpecificReadSideEffect(any(BufferAccessOpcode op))
|
||||
if sideEffectOpcode instanceof BufferAccessOpcode
|
||||
then
|
||||
result = getUnknownType() and
|
||||
tag instanceof OnlyInstructionTag and
|
||||
@@ -557,56 +563,6 @@ class TranslatedSideEffect extends TranslatedElement, TTranslatedArgumentSideEff
|
||||
)
|
||||
}
|
||||
|
||||
predicate hasSpecificWriteSideEffect(Opcode op) {
|
||||
exists(boolean buffer, boolean mustWrite |
|
||||
if exists(call.getTarget().(SideEffectFunction).getParameterSizeIndex(index))
|
||||
then
|
||||
call.getTarget().(SideEffectFunction).hasSpecificWriteSideEffect(index, true, mustWrite) and
|
||||
buffer = true and
|
||||
(
|
||||
mustWrite = false and op instanceof Opcode::SizedBufferMayWriteSideEffect
|
||||
or
|
||||
mustWrite = true and op instanceof Opcode::SizedBufferMustWriteSideEffect
|
||||
)
|
||||
else (
|
||||
call.getTarget().(SideEffectFunction).hasSpecificWriteSideEffect(index, buffer, mustWrite) and
|
||||
(
|
||||
buffer = true and mustWrite = false and op instanceof Opcode::BufferMayWriteSideEffect
|
||||
or
|
||||
buffer = false and mustWrite = false and op instanceof Opcode::IndirectMayWriteSideEffect
|
||||
or
|
||||
buffer = true and mustWrite = true and op instanceof Opcode::BufferMustWriteSideEffect
|
||||
or
|
||||
buffer = false and mustWrite = true and op instanceof Opcode::IndirectMustWriteSideEffect
|
||||
)
|
||||
)
|
||||
)
|
||||
or
|
||||
not call.getTarget() instanceof SideEffectFunction and
|
||||
getArgumentIndex() != -1 and
|
||||
op instanceof Opcode::BufferMayWriteSideEffect
|
||||
or
|
||||
not call.getTarget() instanceof SideEffectFunction and
|
||||
getArgumentIndex() = -1 and
|
||||
op instanceof Opcode::IndirectMayWriteSideEffect
|
||||
}
|
||||
|
||||
predicate hasSpecificReadSideEffect(Opcode op) {
|
||||
exists(boolean buffer |
|
||||
call.getTarget().(SideEffectFunction).hasSpecificReadSideEffect(index, buffer) and
|
||||
if exists(call.getTarget().(SideEffectFunction).getParameterSizeIndex(index))
|
||||
then buffer = true and op instanceof Opcode::SizedBufferReadSideEffect
|
||||
else (
|
||||
buffer = true and op instanceof Opcode::BufferReadSideEffect
|
||||
or
|
||||
buffer = false and op instanceof Opcode::IndirectReadSideEffect
|
||||
)
|
||||
)
|
||||
or
|
||||
not call.getTarget() instanceof SideEffectFunction and
|
||||
op instanceof Opcode::BufferReadSideEffect
|
||||
}
|
||||
|
||||
override Instruction getPrimaryInstructionForSideEffect(InstructionTag tag) {
|
||||
tag = OnlyInstructionTag() and
|
||||
result = getTranslatedCallInstruction(call)
|
||||
|
||||
@@ -12,6 +12,7 @@ private import TranslatedStmt
|
||||
private import TranslatedExpr
|
||||
private import IRConstruction
|
||||
private import semmle.code.cpp.models.interfaces.SideEffect
|
||||
private import SideEffects
|
||||
|
||||
/**
|
||||
* Gets the "real" parent of `expr`. This predicate treats conversions as if
|
||||
@@ -635,46 +636,15 @@ newtype TTranslatedElement =
|
||||
// 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`
|
||||
TTranslatedArgumentSideEffect(Call call, Expr expr, int n, boolean isWrite) {
|
||||
(
|
||||
expr = call.getArgument(n).getFullyConverted()
|
||||
or
|
||||
expr = call.getQualifier().getFullyConverted() and
|
||||
n = -1 and
|
||||
// Exclude calls to static member functions. They don't modify the qualifier
|
||||
not exists(MemberFunction func | func = call.getTarget() and func.isStatic())
|
||||
) and
|
||||
(
|
||||
call.getTarget().(SideEffectFunction).hasSpecificReadSideEffect(n, _) and
|
||||
isWrite = false
|
||||
or
|
||||
call.getTarget().(SideEffectFunction).hasSpecificWriteSideEffect(n, _, _) and
|
||||
isWrite = true
|
||||
or
|
||||
not call.getTarget() instanceof SideEffectFunction and
|
||||
exists(Type t | t = expr.getUnspecifiedType() |
|
||||
t instanceof ArrayType or
|
||||
t instanceof PointerType or
|
||||
t instanceof ReferenceType
|
||||
) and
|
||||
(
|
||||
isWrite = true and
|
||||
not call.getTarget().getParameter(n).getType().isDeeplyConstBelow()
|
||||
or
|
||||
isWrite = false
|
||||
)
|
||||
or
|
||||
not call.getTarget() instanceof SideEffectFunction and
|
||||
n = -1 and
|
||||
(
|
||||
isWrite = true and
|
||||
not call.getTarget() instanceof ConstMemberFunction
|
||||
or
|
||||
isWrite = false
|
||||
)
|
||||
) and
|
||||
TTranslatedArgumentSideEffect(Call call, Expr expr, int n, SideEffectOpcode opcode) {
|
||||
not ignoreExpr(expr) and
|
||||
not ignoreExpr(call)
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,9 +15,7 @@ import semmle.code.cpp.models.interfaces.SideEffect
|
||||
private class Accept extends ArrayFunction, AliasFunction, TaintFunction, SideEffectFunction {
|
||||
Accept() { this.hasGlobalName(["accept", "accept4", "WSAAccept"]) }
|
||||
|
||||
override predicate hasArrayWithVariableSize(int bufParam, int countParam) {
|
||||
bufParam = 1 and countParam = 2
|
||||
}
|
||||
override predicate hasArrayWithUnknownSize(int bufParam) { bufParam = 1 }
|
||||
|
||||
override predicate hasArrayInput(int bufParam) { bufParam = 1 }
|
||||
|
||||
@@ -46,8 +44,8 @@ private class Accept extends ArrayFunction, AliasFunction, TaintFunction, SideEf
|
||||
i = 1 and buffer = false
|
||||
}
|
||||
|
||||
override ParameterIndex getParameterSizeIndex(ParameterIndex i) { i = 1 and result = 2 }
|
||||
|
||||
// NOTE: The size parameter is a pointer to the size. So we can't implement `getParameterSizeIndex` for
|
||||
// this model.
|
||||
// NOTE: We implement thse two predicates as none because we can't model the low-level changes made to
|
||||
// the structure pointed to by the file-descriptor argument.
|
||||
override predicate hasOnlySpecificReadSideEffects() { none() }
|
||||
|
||||
@@ -1,10 +1,37 @@
|
||||
import semmle.code.cpp.models.interfaces.Taint
|
||||
import semmle.code.cpp.models.interfaces.DataFlow
|
||||
import semmle.code.cpp.models.interfaces.PointerWrapper
|
||||
|
||||
/**
|
||||
* The `std::shared_ptr` and `std::unique_ptr` template classes.
|
||||
*/
|
||||
private class UniqueOrSharedPtr extends Class {
|
||||
private class UniqueOrSharedPtr extends Class, PointerWrapper {
|
||||
UniqueOrSharedPtr() { this.hasQualifiedName(["std", "bsl"], ["shared_ptr", "unique_ptr"]) }
|
||||
|
||||
override MemberFunction getAnUnwrapperFunction() {
|
||||
result.(OverloadedPointerDereferenceFunction).getDeclaringType() = this
|
||||
or
|
||||
result.getClassAndName(["operator->", "get"]) = this
|
||||
}
|
||||
|
||||
override predicate pointsToConst() { this.getTemplateArgument(0).(Type).isConst() }
|
||||
}
|
||||
|
||||
/** Any function that unwraps a pointer wrapper class to reveal the underlying pointer. */
|
||||
private class PointerWrapperFlow extends TaintFunction, DataFlowFunction {
|
||||
PointerWrapperFlow() {
|
||||
this = any(PointerWrapper wrapper).getAnUnwrapperFunction() and
|
||||
not this.getUnspecifiedType() instanceof ReferenceType
|
||||
}
|
||||
|
||||
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
|
||||
input.isReturnValueDeref() and
|
||||
output.isQualifierObject()
|
||||
}
|
||||
|
||||
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
|
||||
input.isQualifierObject() and output.isReturnValue()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/** Provides classes for modeling pointer wrapper types and expressions. */
|
||||
|
||||
private import cpp
|
||||
|
||||
/** A class that wraps a pointer type. For example, `std::unique_ptr` and `std::shared_ptr`. */
|
||||
abstract class PointerWrapper extends Class {
|
||||
/**
|
||||
* Gets a member function of this class that returns the wrapped pointer, if any.
|
||||
*
|
||||
* This includes both functions that return the wrapped pointer by value, and functions
|
||||
* that return a reference to the pointed-to object.
|
||||
*/
|
||||
abstract MemberFunction getAnUnwrapperFunction();
|
||||
|
||||
/** Holds if the type of the data that is pointed to by this pointer wrapper is `const`. */
|
||||
abstract predicate pointsToConst();
|
||||
}
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.controlflow.Dominance
|
||||
import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
|
||||
import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils
|
||||
|
||||
/**
|
||||
* Holds if the value of `use` is guarded using `abs`.
|
||||
@@ -94,9 +96,15 @@ predicate guardedGreater(Operation e, Expr use) {
|
||||
VariableAccess varUse(LocalScopeVariable v) { result = v.getAnAccess() }
|
||||
|
||||
/**
|
||||
* Holds if `e` is not guarded against overflow by `use`.
|
||||
* Holds if `e` potentially overflows and `use` is an operand of `e` that is not guarded.
|
||||
*/
|
||||
predicate missingGuardAgainstOverflow(Operation e, VariableAccess use) {
|
||||
(
|
||||
convertedExprMightOverflowPositively(e)
|
||||
or
|
||||
// Ensure that the predicate holds when range analysis cannot determine an upper bound
|
||||
upperBound(e.getFullyConverted()) = exprMaxVal(e.getFullyConverted())
|
||||
) and
|
||||
use = e.getAnOperand() and
|
||||
exists(LocalScopeVariable v | use.getTarget() = v |
|
||||
// overflow possible if large
|
||||
@@ -115,9 +123,15 @@ predicate missingGuardAgainstOverflow(Operation e, VariableAccess use) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e` is not guarded against underflow by `use`.
|
||||
* Holds if `e` potentially underflows and `use` is an operand of `e` that is not guarded.
|
||||
*/
|
||||
predicate missingGuardAgainstUnderflow(Operation e, VariableAccess use) {
|
||||
(
|
||||
convertedExprMightOverflowNegatively(e)
|
||||
or
|
||||
// Ensure that the predicate holds when range analysis cannot determine a lower bound
|
||||
lowerBound(e.getFullyConverted()) = exprMinVal(e.getFullyConverted())
|
||||
) and
|
||||
use = e.getAnOperand() and
|
||||
exists(LocalScopeVariable v | use.getTarget() = v |
|
||||
// underflow possible if use is left operand and small
|
||||
|
||||
@@ -14,8 +14,8 @@ using namespace std;
|
||||
|
||||
void* operator new(std::size_t _Size);
|
||||
void* operator new[](std::size_t _Size);
|
||||
void* operator new( std::size_t count, const std::nothrow_t& tag );
|
||||
void* operator new[]( std::size_t count, const std::nothrow_t& tag );
|
||||
void* operator new( std::size_t count, const std::nothrow_t& tag ) noexcept;
|
||||
void* operator new[]( std::size_t count, const std::nothrow_t& tag ) noexcept;
|
||||
|
||||
void badNew_0_0()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
| test.c:8:6:8:51 | ... & ... | This bitwise operation appears in a context where a Boolean operation is expected. |
|
||||
| test.c:10:6:10:30 | ... & ... | This bitwise operation appears in a context where a Boolean operation is expected. |
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.ql
|
||||
@@ -0,0 +1,28 @@
|
||||
int tmpFunction(){
|
||||
return 5;
|
||||
}
|
||||
void workFunction_0(char *s) {
|
||||
int intSize;
|
||||
char buf[80];
|
||||
if(intSize>0 && intSize<80 && memset(buf,0,intSize)) return; // GOOD
|
||||
if(intSize>0 & intSize<80 & memset(buf,0,intSize)) return; // BAD
|
||||
if(intSize>0 && tmpFunction()) return;
|
||||
if(intSize<0 & tmpFunction()) return; // BAD
|
||||
}
|
||||
void workFunction_1(char *s) {
|
||||
int intA,intB;
|
||||
|
||||
if(intA + intB) return; // BAD [NOT DETECTED]
|
||||
if(intA + intB>4) return; // GOOD
|
||||
if(intA>0 && (intA + intB)) return; // BAD [NOT DETECTED]
|
||||
while(intA>0)
|
||||
{
|
||||
if(intB - intA<10) break;
|
||||
intA--;
|
||||
}while(intA>0); // BAD [NOT DETECTED]
|
||||
while(intA>0)
|
||||
{
|
||||
if(intB - intA<10) break;
|
||||
intA--;
|
||||
} // GOOD
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
| test.c:42:3:42:24 | ... = ... | potential unsafe or redundant assignment. |
|
||||
| test.c:43:3:43:40 | ... = ... | potential unsafe or redundant assignment. |
|
||||
| test.c:44:3:44:40 | ... = ... | potential unsafe or redundant assignment. |
|
||||
| test.c:45:3:45:44 | ... = ... | potential unsafe or redundant assignment. |
|
||||
| test.c:46:3:46:44 | ... = ... | potential unsafe or redundant assignment. |
|
||||
| test.c:47:3:47:48 | ... = ... | potential unsafe or redundant assignment. |
|
||||
| test.c:48:3:48:48 | ... = ... | potential unsafe or redundant assignment. |
|
||||
| test.c:49:3:49:50 | ... = ... | potential unsafe or redundant assignment. |
|
||||
| test.c:50:3:50:50 | ... = ... | potential unsafe or redundant assignment. |
|
||||
| test.c:54:3:54:24 | ... = ... | potential unsafe or redundant assignment. |
|
||||
| test.c:55:3:55:40 | ... = ... | potential unsafe or redundant assignment. |
|
||||
| test.c:56:3:56:44 | ... = ... | potential unsafe or redundant assignment. |
|
||||
| test.c:57:3:57:44 | ... = ... | potential unsafe or redundant assignment. |
|
||||
| test.c:58:3:58:48 | ... = ... | potential unsafe or redundant assignment. |
|
||||
| test.c:59:3:59:48 | ... = ... | potential unsafe or redundant assignment. |
|
||||
| test.c:60:3:60:52 | ... = ... | potential unsafe or redundant assignment. |
|
||||
| test.c:61:3:61:50 | ... = ... | potential unsafe or redundant assignment. |
|
||||
| test.c:62:3:62:54 | ... = ... | potential unsafe or redundant assignment. |
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
| test.c:4:3:4:9 | call to strncat | if the used buffer is full, writing out of the buffer is possible |
|
||||
| test.c:11:3:11:9 | call to strncat | if the used buffer is full, writing out of the buffer is possible |
|
||||
| test.c:19:3:19:9 | call to strncat | if the used buffer is full, writing out of the buffer is possible |
|
||||
| test.c:8:3:8:9 | call to strncat | Possible out-of-bounds write due to incorrect size argument. |
|
||||
| test.c:9:3:9:9 | call to strncat | Possible out-of-bounds write due to incorrect size argument. |
|
||||
| test.c:17:3:17:9 | call to strncat | Possible out-of-bounds write due to incorrect size argument. |
|
||||
| test.c:18:3:18:9 | call to strncat | Possible out-of-bounds write due to incorrect size argument. |
|
||||
| test.c:46:3:46:9 | call to strncat | Possible out-of-bounds write due to incorrect size argument. |
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
| test.cpp:10:8:10:10 | - ... | this expression needs attention |
|
||||
| test.cpp:12:3:12:6 | ... ++ | this expression needs attention |
|
||||
| test.cpp:13:3:13:6 | ++ ... | this expression needs attention |
|
||||
| test.cpp:14:6:14:21 | ... = ... | this expression needs attention |
|
||||
| test.cpp:16:6:16:21 | ... = ... | this expression needs attention |
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.ql
|
||||
@@ -1,70 +1,84 @@
|
||||
void workFunction_0(char *s) {
|
||||
char * strncat(char*, const char*, unsigned);
|
||||
unsigned strlen(const char*);
|
||||
void* malloc(unsigned);
|
||||
|
||||
void strncat_test1(char *s) {
|
||||
char buf[80];
|
||||
strncat(buf, s, sizeof(buf)-strlen(buf)-1); // GOOD
|
||||
strncat(buf, s, sizeof(buf)-strlen(buf)); // BAD
|
||||
strncat(buf, "fix", sizeof(buf)-strlen(buf)); // BAD [NOT DETECTED]
|
||||
strncat(buf, s, sizeof(buf) - strlen(buf) - 1); // GOOD
|
||||
strncat(buf, s, sizeof(buf) - strlen(buf)); // BAD
|
||||
strncat(buf, "fix", sizeof(buf)-strlen(buf)); // BAD
|
||||
}
|
||||
void workFunction_1(char *s) {
|
||||
|
||||
#define MAX_SIZE 80
|
||||
|
||||
void strncat_test2(char *s) {
|
||||
char buf[MAX_SIZE];
|
||||
strncat(buf, s, MAX_SIZE-strlen(buf)-1); // GOOD
|
||||
strncat(buf, s, MAX_SIZE-strlen(buf)); // BAD
|
||||
strncat(buf, "fix", MAX_SIZE-strlen(buf)); // BAD [NOT DETECTED]
|
||||
strncat(buf, s, MAX_SIZE - strlen(buf) - 1); // GOOD
|
||||
strncat(buf, s, MAX_SIZE - strlen(buf)); // BAD
|
||||
strncat(buf, "fix", MAX_SIZE - strlen(buf)); // BAD
|
||||
}
|
||||
void workFunction_2_0(char *s) {
|
||||
char * buf;
|
||||
int len=80;
|
||||
buf = (char *) malloc(len);
|
||||
strncat(buf, s, len-strlen(buf)-1); // GOOD
|
||||
strncat(buf, s, len-strlen(buf)); // BAD
|
||||
strncat(buf, "fix", len-strlen(buf)); // BAD [NOT DETECTED]
|
||||
|
||||
void strncat_test3(char *s) {
|
||||
int len = 80;
|
||||
char* buf = (char *) malloc(len);
|
||||
strncat(buf, s, len - strlen(buf) - 1); // GOOD
|
||||
strncat(buf, s, len - strlen(buf)); // BAD [NOT DETECTED]
|
||||
strncat(buf, "fix", len - strlen(buf)); // BAD [NOT DETECTED]
|
||||
}
|
||||
void workFunction_2_1(char *s) {
|
||||
char * buf;
|
||||
int len=80;
|
||||
buf = (char *) malloc(len+1);
|
||||
strncat(buf, s, len-strlen(buf)-1); // GOOD
|
||||
strncat(buf, s, len-strlen(buf)); // GOOD
|
||||
|
||||
void strncat_test4(char *s) {
|
||||
int len = 80;
|
||||
char* buf = (char *) malloc(len + 1);
|
||||
strncat(buf, s, len - strlen(buf) - 1); // GOOD
|
||||
strncat(buf, s, len - strlen(buf)); // GOOD
|
||||
}
|
||||
|
||||
struct buffers
|
||||
{
|
||||
unsigned char buff1[50];
|
||||
unsigned char *buff2;
|
||||
unsigned char array[50];
|
||||
unsigned char *pointer;
|
||||
} globalBuff1,*globalBuff2,globalBuff1_c,*globalBuff2_c;
|
||||
|
||||
void strncat_test5(char* s, struct buffers* buffers) {
|
||||
unsigned len_array = strlen(buffers->array);
|
||||
unsigned max_size = sizeof(buffers->array);
|
||||
unsigned free_size = max_size - len_array;
|
||||
strncat(buffers->array, s, free_size); // BAD
|
||||
}
|
||||
|
||||
void badFunc0(){
|
||||
void strlen_test1(){
|
||||
unsigned char buff1[12];
|
||||
struct buffers buffAll;
|
||||
struct buffers * buffAll1;
|
||||
|
||||
buff1[strlen(buff1)]=0; // BAD
|
||||
buffAll.buff1[strlen(buffAll.buff1)]=0; // BAD
|
||||
buffAll.buff2[strlen(buffAll.buff2)]=0; // BAD
|
||||
buffAll1->buff1[strlen(buffAll1->buff1)]=0; // BAD
|
||||
buffAll1->buff2[strlen(buffAll1->buff2)]=0; // BAD
|
||||
globalBuff1.buff1[strlen(globalBuff1.buff1)]=0; // BAD
|
||||
globalBuff1.buff2[strlen(globalBuff1.buff2)]=0; // BAD
|
||||
globalBuff2->buff1[strlen(globalBuff2->buff1)]=0; // BAD
|
||||
globalBuff2->buff2[strlen(globalBuff2->buff2)]=0; // BAD
|
||||
buffAll.array[strlen(buffAll.array)]=0; // BAD
|
||||
buffAll.pointer[strlen(buffAll.pointer)]=0; // BAD
|
||||
buffAll1->array[strlen(buffAll1->array)]=0; // BAD
|
||||
buffAll1->pointer[strlen(buffAll1->pointer)]=0; // BAD
|
||||
globalBuff1.array[strlen(globalBuff1.array)]=0; // BAD
|
||||
globalBuff1.pointer[strlen(globalBuff1.pointer)]=0; // BAD
|
||||
globalBuff2->array[strlen(globalBuff2->array)]=0; // BAD
|
||||
globalBuff2->pointer[strlen(globalBuff2->pointer)]=0; // BAD
|
||||
}
|
||||
void noBadFunc0(){
|
||||
|
||||
void strlen_test2(){
|
||||
unsigned char buff1[12],buff1_c[12];
|
||||
struct buffers buffAll,buffAll_c;
|
||||
struct buffers * buffAll1,*buffAll1_c;
|
||||
|
||||
buff1[strlen(buff1_c)]=0; // GOOD
|
||||
buffAll.buff1[strlen(buffAll_c.buff1)]=0; // GOOD
|
||||
buffAll.buff2[strlen(buffAll.buff1)]=0; // GOOD
|
||||
buffAll1->buff1[strlen(buffAll1_c->buff1)]=0; // GOOD
|
||||
buffAll1->buff2[strlen(buffAll1->buff1)]=0; // GOOD
|
||||
globalBuff1.buff1[strlen(globalBuff1_c.buff1)]=0; // GOOD
|
||||
globalBuff1.buff2[strlen(globalBuff1.buff1)]=0; // GOOD
|
||||
globalBuff2->buff1[strlen(globalBuff2_c->buff1)]=0; // GOOD
|
||||
globalBuff2->buff2[strlen(globalBuff2->buff1)]=0; // GOOD
|
||||
buffAll.array[strlen(buffAll_c.array)]=0; // GOOD
|
||||
buffAll.pointer[strlen(buffAll.array)]=0; // GOOD
|
||||
buffAll1->array[strlen(buffAll1_c->array)]=0; // GOOD
|
||||
buffAll1->pointer[strlen(buffAll1->array)]=0; // GOOD
|
||||
globalBuff1.array[strlen(globalBuff1_c.array)]=0; // GOOD
|
||||
globalBuff1.pointer[strlen(globalBuff1.array)]=0; // GOOD
|
||||
globalBuff2->array[strlen(globalBuff2_c->array)]=0; // GOOD
|
||||
globalBuff2->pointer[strlen(globalBuff2->array)]=0; // GOOD
|
||||
}
|
||||
void goodFunc0(){
|
||||
|
||||
void strlen_test3(){
|
||||
unsigned char buffer[12];
|
||||
int i;
|
||||
for(i = 0; i < 6; i++)
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
int tmpFunc()
|
||||
{
|
||||
return 12;
|
||||
}
|
||||
void testFunction()
|
||||
{
|
||||
int i1,i2,i3;
|
||||
bool b1,b2,b3;
|
||||
char c1,c2,c3;
|
||||
b1 = -b2; //BAD
|
||||
b1 = !b2; //GOOD
|
||||
b1++; //BAD
|
||||
++b1; //BAD
|
||||
if(i1=tmpFunc()!=i2) //BAD
|
||||
return;
|
||||
if(i1=tmpFunc()!=11) //BAD
|
||||
return;
|
||||
if((i1=tmpFunc())!=i2) //GOOD
|
||||
return;
|
||||
if((i1=tmpFunc())!=11) //GOOD
|
||||
return;
|
||||
if(i1=tmpFunc()!=1) //GOOD
|
||||
return;
|
||||
if(i1=tmpFunc()==b1) //GOOD
|
||||
return;
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
import cpp
|
||||
import semmle.code.cpp.security.TaintTrackingImpl as ASTTaintTracking
|
||||
import semmle.code.cpp.ir.dataflow.DefaultTaintTracking as IRDefaultTaintTracking
|
||||
import IRDefaultTaintTracking::TaintedWithPath as TaintedWithPath
|
||||
import TestUtilities.InlineExpectationsTest
|
||||
|
||||
predicate isSink(Element sink) {
|
||||
@@ -17,7 +18,13 @@ predicate isSink(Element sink) {
|
||||
|
||||
predicate astTaint(Expr source, Element sink) { ASTTaintTracking::tainted(source, sink) }
|
||||
|
||||
predicate irTaint(Expr source, Element sink) { IRDefaultTaintTracking::tainted(source, sink) }
|
||||
class SourceConfiguration extends TaintedWithPath::TaintTrackingConfiguration {
|
||||
override predicate isSink(Element e) { any() }
|
||||
}
|
||||
|
||||
predicate irTaint(Expr source, Element sink) {
|
||||
TaintedWithPath::taintedWithPath(source, sink, _, _)
|
||||
}
|
||||
|
||||
class IRDefaultTaintTrackingTest extends InlineExpectationsTest {
|
||||
IRDefaultTaintTrackingTest() { this = "IRDefaultTaintTrackingTest" }
|
||||
|
||||
@@ -19,7 +19,7 @@ int main() {
|
||||
|
||||
char untainted_buf[100] = "";
|
||||
char buf[100] = "VAR = ";
|
||||
sink(strcat(buf, getenv("VAR"))); // $ ast,ir
|
||||
sink(strcat(buf, getenv("VAR"))); // $ ast MISSING: ir
|
||||
|
||||
sink(buf); // $ ast,ir
|
||||
sink(untainted_buf); // the two buffers would be conflated if we added flow through all partial chi inputs
|
||||
@@ -250,12 +250,12 @@ void sink(iovec);
|
||||
int test_readv_and_writev(iovec* iovs) {
|
||||
readv(0, iovs, 16);
|
||||
sink(iovs); // $ast,ir
|
||||
sink(iovs[0]); // $ast MISSING: ir
|
||||
sink(*iovs); // $ast MISSING: ir
|
||||
sink(iovs[0]); // $ast,ir
|
||||
sink(*iovs); // $ast,ir
|
||||
|
||||
char* p = (char*)iovs[1].iov_base;
|
||||
sink(p); // $ MISSING: ast,ir
|
||||
sink(*p); // $ MISSING: ast,ir
|
||||
sink(p); // $ ir MISSING: ast
|
||||
sink(*p); // $ ir MISSING: ast
|
||||
|
||||
writev(0, iovs, 16); // $ remote
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ void test_string()
|
||||
sink(b); // clean
|
||||
sink(c); // $ ir MISSING: ast
|
||||
sink(b.c_str()); // clean
|
||||
sink(c.c_str()); // $ MISSING: ast,ir
|
||||
sink(c.c_str()); // $ ir MISSING: ast
|
||||
}
|
||||
|
||||
void test_stringstream()
|
||||
@@ -93,10 +93,10 @@ void test_stringstream()
|
||||
sink(ss4); // $ ir MISSING: ast
|
||||
sink(ss5); // $ ir MISSING: ast
|
||||
sink(ss1.str());
|
||||
sink(ss2.str()); // $ MISSING: ast,ir
|
||||
sink(ss2.str()); // $ ir MISSING: ast
|
||||
sink(ss3.str()); // $ MISSING: ast,ir
|
||||
sink(ss4.str()); // $ MISSING: ast,ir
|
||||
sink(ss5.str()); // $ MISSING: ast,ir
|
||||
sink(ss4.str()); // $ ir MISSING: ast
|
||||
sink(ss5.str()); // $ ir MISSING: ast
|
||||
}
|
||||
|
||||
void test_stringstream_int(int source)
|
||||
@@ -123,14 +123,14 @@ void sink(const char *filename, const char *mode);
|
||||
void test_strings2()
|
||||
{
|
||||
string path1 = user_input();
|
||||
sink(path1.c_str(), "r"); // $ MISSING: ast,ir
|
||||
sink(path1.c_str(), "r"); // $ ir MISSING: ast
|
||||
|
||||
string path2;
|
||||
path2 = user_input();
|
||||
sink(path2.c_str(), "r"); // $ MISSING: ast,ir
|
||||
sink(path2.c_str(), "r"); // $ ir MISSING: ast
|
||||
|
||||
string path3(user_input());
|
||||
sink(path3.c_str(), "r"); // $ MISSING: ast,ir
|
||||
sink(path3.c_str(), "r"); // $ ir MISSING: ast
|
||||
}
|
||||
|
||||
void test_string3()
|
||||
@@ -154,6 +154,6 @@ void test_string4()
|
||||
// convert back std::string -> char *
|
||||
cs = ss.c_str();
|
||||
|
||||
sink(cs); // $ ast MISSING: ir
|
||||
sink(cs); // $ ast,ir
|
||||
sink(ss); // $ ir MISSING: ast
|
||||
}
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
import cpp
|
||||
import semmle.code.cpp.security.TaintTrackingImpl as ASTTaintTracking
|
||||
import semmle.code.cpp.ir.dataflow.DefaultTaintTracking as IRDefaultTaintTracking
|
||||
import IRDefaultTaintTracking::TaintedWithPath as TaintedWithPath
|
||||
import TestUtilities.InlineExpectationsTest
|
||||
|
||||
predicate isSink(Element sink) {
|
||||
predicate argToSinkCall(Element sink) {
|
||||
exists(FunctionCall call |
|
||||
call.getTarget().getName() = "sink" and
|
||||
sink = call.getAnArgument()
|
||||
@@ -17,11 +18,15 @@ predicate isSink(Element sink) {
|
||||
}
|
||||
|
||||
predicate astTaint(Expr source, Element sink) {
|
||||
ASTTaintTracking::tainted(source, sink) and isSink(sink)
|
||||
ASTTaintTracking::tainted(source, sink) and argToSinkCall(sink)
|
||||
}
|
||||
|
||||
class SourceConfiguration extends TaintedWithPath::TaintTrackingConfiguration {
|
||||
override predicate isSink(Element e) { argToSinkCall(e) }
|
||||
}
|
||||
|
||||
predicate irTaint(Expr source, Element sink) {
|
||||
IRDefaultTaintTracking::tainted(source, sink) and isSink(sink)
|
||||
TaintedWithPath::taintedWithPath(source, sink, _, _)
|
||||
}
|
||||
|
||||
class IRDefaultTaintTrackingTest extends InlineExpectationsTest {
|
||||
|
||||
@@ -26,6 +26,7 @@ unreachableNodeCCtx
|
||||
localCallNodes
|
||||
postIsNotPre
|
||||
postHasUniquePre
|
||||
| test.cpp:373:5:373:20 | Store | PostUpdateNode should have one pre-update node but has 0. |
|
||||
uniquePostUpdate
|
||||
postIsInSameCallable
|
||||
reverseRead
|
||||
@@ -82,4 +83,5 @@ postWithInFlow
|
||||
| test.cpp:125:3:125:11 | Chi | PostUpdateNode should not be the target of local flow. |
|
||||
| test.cpp:359:5:359:20 | Chi | PostUpdateNode should not be the target of local flow. |
|
||||
| test.cpp:373:5:373:20 | Chi | PostUpdateNode should not be the target of local flow. |
|
||||
| test.cpp:373:5:373:20 | Store | PostUpdateNode should not be the target of local flow. |
|
||||
| test.cpp:465:3:465:15 | Chi | PostUpdateNode should not be the target of local flow. |
|
||||
|
||||
@@ -20,7 +20,9 @@ unreachableNodeCCtx
|
||||
localCallNodes
|
||||
postIsNotPre
|
||||
postHasUniquePre
|
||||
| D.cpp:57:5:57:42 | Store | PostUpdateNode should have one pre-update node but has 0. |
|
||||
| simple.cpp:65:5:65:22 | Store | PostUpdateNode should have one pre-update node but has 0. |
|
||||
| simple.cpp:83:9:83:28 | Store | PostUpdateNode should have one pre-update node but has 0. |
|
||||
| simple.cpp:92:5:92:22 | Store | PostUpdateNode should have one pre-update node but has 0. |
|
||||
uniquePostUpdate
|
||||
postIsInSameCallable
|
||||
@@ -54,6 +56,7 @@ postWithInFlow
|
||||
| D.cpp:49:15:49:24 | Chi | PostUpdateNode should not be the target of local flow. |
|
||||
| D.cpp:56:15:56:24 | Chi | PostUpdateNode should not be the target of local flow. |
|
||||
| D.cpp:57:5:57:42 | Chi | PostUpdateNode should not be the target of local flow. |
|
||||
| D.cpp:57:5:57:42 | Store | PostUpdateNode should not be the target of local flow. |
|
||||
| aliasing.cpp:9:3:9:22 | Chi | PostUpdateNode should not be the target of local flow. |
|
||||
| aliasing.cpp:13:3:13:21 | Chi | PostUpdateNode should not be the target of local flow. |
|
||||
| aliasing.cpp:17:3:17:21 | Chi | PostUpdateNode should not be the target of local flow. |
|
||||
@@ -150,6 +153,7 @@ postWithInFlow
|
||||
| simple.cpp:23:35:23:35 | Chi | PostUpdateNode should not be the target of local flow. |
|
||||
| simple.cpp:65:5:65:22 | Store | PostUpdateNode should not be the target of local flow. |
|
||||
| simple.cpp:83:9:83:28 | Chi | PostUpdateNode should not be the target of local flow. |
|
||||
| simple.cpp:83:9:83:28 | Store | PostUpdateNode should not be the target of local flow. |
|
||||
| simple.cpp:92:5:92:22 | Store | PostUpdateNode should not be the target of local flow. |
|
||||
| struct_init.c:20:20:20:29 | Chi | PostUpdateNode should not be the target of local flow. |
|
||||
| struct_init.c:20:34:20:34 | Chi | PostUpdateNode should not be the target of local flow. |
|
||||
|
||||
@@ -228,8 +228,8 @@ edges
|
||||
| simple.cpp:65:5:65:22 | Store [i] | simple.cpp:66:12:66:12 | Store [i] |
|
||||
| simple.cpp:65:11:65:20 | call to user_input | simple.cpp:65:5:65:22 | Store [i] |
|
||||
| simple.cpp:66:12:66:12 | Store [i] | simple.cpp:67:13:67:13 | i |
|
||||
| simple.cpp:83:9:83:28 | Chi [f1] | simple.cpp:84:14:84:20 | this indirection [f1] |
|
||||
| simple.cpp:83:17:83:26 | call to user_input | simple.cpp:83:9:83:28 | Chi [f1] |
|
||||
| simple.cpp:83:9:83:28 | Store [f1] | simple.cpp:84:14:84:20 | this indirection [f1] |
|
||||
| simple.cpp:83:17:83:26 | call to user_input | simple.cpp:83:9:83:28 | Store [f1] |
|
||||
| simple.cpp:84:14:84:20 | this indirection [f1] | simple.cpp:84:14:84:20 | call to getf2f1 |
|
||||
| simple.cpp:92:5:92:22 | Store [i] | simple.cpp:93:20:93:20 | Store [i] |
|
||||
| simple.cpp:92:11:92:20 | call to user_input | simple.cpp:92:5:92:22 | Store [i] |
|
||||
@@ -494,7 +494,7 @@ nodes
|
||||
| simple.cpp:65:11:65:20 | call to user_input | semmle.label | call to user_input |
|
||||
| simple.cpp:66:12:66:12 | Store [i] | semmle.label | Store [i] |
|
||||
| simple.cpp:67:13:67:13 | i | semmle.label | i |
|
||||
| simple.cpp:83:9:83:28 | Chi [f1] | semmle.label | Chi [f1] |
|
||||
| simple.cpp:83:9:83:28 | Store [f1] | semmle.label | Store [f1] |
|
||||
| simple.cpp:83:17:83:26 | call to user_input | semmle.label | call to user_input |
|
||||
| simple.cpp:84:14:84:20 | call to getf2f1 | semmle.label | call to getf2f1 |
|
||||
| simple.cpp:84:14:84:20 | this indirection [f1] | semmle.label | this indirection [f1] |
|
||||
|
||||
@@ -19,15 +19,19 @@ class IRPartialDefNode extends IRNode {
|
||||
override string toString() { result = n.asPartialDefinition().toString() }
|
||||
}
|
||||
|
||||
from Node node, AST::Node astNode, IR::Node irNode, string msg
|
||||
from Node node, string msg
|
||||
where
|
||||
node.asIR() = irNode and
|
||||
exists(irNode.asPartialDefinition()) and
|
||||
not exists(AST::Node otherNode | otherNode.asPartialDefinition() = irNode.asPartialDefinition()) and
|
||||
exists(IR::Node irNode, Expr partial |
|
||||
node.asIR() = irNode and
|
||||
partial = irNode.asPartialDefinition() and
|
||||
not exists(AST::Node otherNode | otherNode.asPartialDefinition() = partial)
|
||||
) and
|
||||
msg = "IR only"
|
||||
or
|
||||
node.asAST() = astNode and
|
||||
exists(astNode.asPartialDefinition()) and
|
||||
not exists(IR::Node otherNode | otherNode.asPartialDefinition() = astNode.asPartialDefinition()) and
|
||||
exists(AST::Node astNode, Expr partial |
|
||||
node.asAST() = astNode and
|
||||
partial = astNode.asPartialDefinition() and
|
||||
not exists(IR::Node otherNode | otherNode.asPartialDefinition() = partial)
|
||||
) and
|
||||
msg = "AST only"
|
||||
select node, msg
|
||||
|
||||
@@ -1,19 +1,42 @@
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:8:24:8:25 | s1 | AST only |
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:23:14:23:19 | envStr | AST only |
|
||||
| test.cpp:38:23:38:28 | call to getenv | test.cpp:8:24:8:25 | s1 | AST only |
|
||||
| test.cpp:38:23:38:28 | call to getenv | test.cpp:38:14:38:19 | envStr | AST only |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:8:24:8:25 | s1 | AST only |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:45:13:45:24 | envStrGlobal | AST only |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:49:14:49:19 | envStr | AST only |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:50:15:50:24 | envStr_ptr | AST only |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:50:28:50:40 | & ... | AST only |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:50:29:50:40 | envStrGlobal | AST only |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:52:2:52:12 | * ... | AST only |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:52:3:52:12 | envStr_ptr | AST only |
|
||||
| test.cpp:60:29:60:34 | call to getenv | test.cpp:10:27:10:27 | s | AST only |
|
||||
| test.cpp:60:29:60:34 | call to getenv | test.cpp:60:18:60:25 | userName | AST only |
|
||||
| test.cpp:68:28:68:33 | call to getenv | test.cpp:11:20:11:21 | s1 | AST only |
|
||||
| test.cpp:68:28:68:33 | call to getenv | test.cpp:11:36:11:37 | s2 | AST only |
|
||||
| test.cpp:68:28:68:33 | call to getenv | test.cpp:67:7:67:13 | copying | AST only |
|
||||
| test.cpp:68:28:68:33 | call to getenv | test.cpp:68:17:68:24 | userName | AST only |
|
||||
| test.cpp:68:28:68:33 | call to getenv | test.cpp:69:10:69:13 | copy | AST only |
|
||||
| test.cpp:68:28:68:33 | call to getenv | test.cpp:70:5:70:10 | call to strcpy | AST only |
|
||||
| test.cpp:68:28:68:33 | call to getenv | test.cpp:70:12:70:15 | copy | AST only |
|
||||
| test.cpp:68:28:68:33 | call to getenv | test.cpp:71:12:71:15 | copy | AST only |
|
||||
| test.cpp:75:20:75:25 | call to getenv | test.cpp:15:22:15:25 | nptr | AST only |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:8:24:8:25 | s1 | AST only |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:11:20:11:21 | s1 | AST only |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:11:36:11:37 | s2 | AST only |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:17:83:24 | userName | AST only |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:85:8:85:11 | copy | AST only |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:2:86:7 | call to strcpy | AST only |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:9:86:12 | copy | AST only |
|
||||
| test.cpp:100:12:100:15 | call to gets | test.cpp:98:8:98:14 | pointer | AST only |
|
||||
| test.cpp:100:12:100:15 | call to gets | test.cpp:100:2:100:8 | pointer | AST only |
|
||||
| test.cpp:100:17:100:22 | buffer | test.cpp:93:18:93:18 | s | AST only |
|
||||
| test.cpp:100:17:100:22 | buffer | test.cpp:97:7:97:12 | buffer | AST only |
|
||||
| test.cpp:100:17:100:22 | buffer | test.cpp:100:17:100:22 | array to pointer conversion | IR only |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:8:24:8:25 | s1 | AST only |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:11:20:11:21 | s1 | AST only |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:11:36:11:37 | s2 | AST only |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:106:17:106:24 | userName | AST only |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:108:8:108:11 | copy | AST only |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:109:2:109:7 | call to strcpy | AST only |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:109:9:109:12 | copy | AST only |
|
||||
|
||||
@@ -2,14 +2,18 @@ import semmle.code.cpp.security.TaintTrackingImpl as AST
|
||||
import semmle.code.cpp.ir.dataflow.DefaultTaintTracking as IR
|
||||
import cpp
|
||||
|
||||
class SourceConfiguration extends IR::TaintedWithPath::TaintTrackingConfiguration {
|
||||
override predicate isSink(Element e) { any() }
|
||||
}
|
||||
|
||||
from Expr source, Element tainted, string side
|
||||
where
|
||||
AST::taintedIncludingGlobalVars(source, tainted, _) and
|
||||
not IR::taintedIncludingGlobalVars(source, tainted, _) and
|
||||
not IR::TaintedWithPath::taintedWithPath(source, tainted, _, _) and
|
||||
not tainted.getLocation().getFile().getExtension() = "h" and
|
||||
side = "AST only"
|
||||
or
|
||||
IR::taintedIncludingGlobalVars(source, tainted, _) and
|
||||
IR::TaintedWithPath::taintedWithPath(source, tainted, _, _) and
|
||||
not AST::taintedIncludingGlobalVars(source, tainted, _) and
|
||||
not tainted.getLocation().getFile().getExtension() = "h" and
|
||||
side = "IR only"
|
||||
|
||||
@@ -1,71 +1,48 @@
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:8:24:8:25 | s1 | |
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:23:14:23:19 | envStr | |
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:23:23:23:28 | call to getenv | |
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:23:23:23:40 | (const char *)... | |
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:6:25:29 | ! ... | |
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:7:25:12 | call to strcmp | |
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:7:25:29 | (bool)... | |
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:14:25:19 | envStr | |
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:6:29:28 | ! ... | |
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:7:29:12 | call to strcmp | |
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:7:29:28 | (bool)... | |
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:14:29:19 | envStr | |
|
||||
| test.cpp:38:23:38:28 | call to getenv | test.cpp:8:24:8:25 | s1 | |
|
||||
| test.cpp:38:23:38:28 | call to getenv | test.cpp:38:14:38:19 | envStr | |
|
||||
| test.cpp:38:23:38:28 | call to getenv | test.cpp:38:23:38:28 | call to getenv | |
|
||||
| test.cpp:38:23:38:28 | call to getenv | test.cpp:38:23:38:40 | (const char *)... | |
|
||||
| test.cpp:38:23:38:28 | call to getenv | test.cpp:40:14:40:19 | envStr | |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:8:24:8:25 | s1 | |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:45:13:45:24 | envStrGlobal | |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:49:14:49:19 | envStr | |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:49:23:49:28 | call to getenv | |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:49:23:49:40 | (const char *)... | |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:52:16:52:21 | envStr | |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:6:54:35 | ! ... | |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:7:54:12 | call to strcmp | |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:7:54:35 | (bool)... | |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:14:54:25 | envStrGlobal | |
|
||||
| test.cpp:60:29:60:34 | call to getenv | test.cpp:10:27:10:27 | s | |
|
||||
| test.cpp:60:29:60:34 | call to getenv | test.cpp:60:18:60:25 | userName | |
|
||||
| test.cpp:60:29:60:34 | call to getenv | test.cpp:60:29:60:34 | call to getenv | |
|
||||
| test.cpp:60:29:60:34 | call to getenv | test.cpp:60:29:60:47 | (const char *)... | |
|
||||
| test.cpp:60:29:60:34 | call to getenv | test.cpp:64:25:64:32 | userName | |
|
||||
| test.cpp:68:28:68:33 | call to getenv | test.cpp:11:36:11:37 | s2 | |
|
||||
| test.cpp:68:28:68:33 | call to getenv | test.cpp:68:17:68:24 | userName | |
|
||||
| test.cpp:68:28:68:33 | call to getenv | test.cpp:68:28:68:33 | call to getenv | |
|
||||
| test.cpp:68:28:68:33 | call to getenv | test.cpp:68:28:68:46 | (const char *)... | |
|
||||
| test.cpp:68:28:68:33 | call to getenv | test.cpp:70:5:70:10 | call to strcpy | |
|
||||
| test.cpp:68:28:68:33 | call to getenv | test.cpp:70:18:70:25 | userName | |
|
||||
| test.cpp:75:20:75:25 | call to getenv | test.cpp:15:22:15:25 | nptr | |
|
||||
| test.cpp:75:20:75:25 | call to getenv | test.cpp:75:15:75:18 | call to atoi | |
|
||||
| test.cpp:75:20:75:25 | call to getenv | test.cpp:75:20:75:25 | call to getenv | |
|
||||
| test.cpp:75:20:75:25 | call to getenv | test.cpp:75:20:75:45 | (const char *)... | |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:8:24:8:25 | s1 | |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:11:36:11:37 | s2 | |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:17:83:24 | userName | |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:28:83:33 | call to getenv | |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:28:83:46 | (const char *)... | |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:2:86:7 | call to strcpy | |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:15:86:22 | userName | |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:6:88:27 | ! ... | |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:7:88:12 | call to strcmp | |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:7:88:27 | (bool)... | |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:14:88:17 | (const char *)... | |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:14:88:17 | copy | |
|
||||
| test.cpp:100:12:100:15 | call to gets | test.cpp:98:8:98:14 | pointer | |
|
||||
| test.cpp:100:12:100:15 | call to gets | test.cpp:100:12:100:15 | call to gets | |
|
||||
| test.cpp:100:17:100:22 | buffer | test.cpp:93:18:93:18 | s | |
|
||||
| test.cpp:100:17:100:22 | buffer | test.cpp:100:17:100:22 | array to pointer conversion | |
|
||||
| test.cpp:100:17:100:22 | buffer | test.cpp:100:17:100:22 | buffer | |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:8:24:8:25 | s1 | |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:11:36:11:37 | s2 | |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:106:17:106:24 | userName | |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:106:28:106:33 | call to getenv | |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:106:28:106:46 | (const char *)... | |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:109:2:109:7 | call to strcpy | |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:109:15:109:22 | userName | |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:111:6:111:27 | ! ... | |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:111:7:111:12 | call to strcmp | |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:111:7:111:27 | (bool)... | |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:111:14:111:17 | (const char *)... | |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:111:14:111:17 | copy | |
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:23:23:23:28 | call to getenv |
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:23:23:23:40 | (const char *)... |
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:6:25:29 | ! ... |
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:7:25:12 | call to strcmp |
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:7:25:29 | (bool)... |
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:14:25:19 | envStr |
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:6:29:28 | ! ... |
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:7:29:12 | call to strcmp |
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:7:29:28 | (bool)... |
|
||||
| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:14:29:19 | envStr |
|
||||
| test.cpp:38:23:38:28 | call to getenv | test.cpp:38:23:38:28 | call to getenv |
|
||||
| test.cpp:38:23:38:28 | call to getenv | test.cpp:38:23:38:40 | (const char *)... |
|
||||
| test.cpp:38:23:38:28 | call to getenv | test.cpp:40:14:40:19 | envStr |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:49:23:49:28 | call to getenv |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:49:23:49:40 | (const char *)... |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:52:16:52:21 | envStr |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:6:54:35 | ! ... |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:7:54:12 | call to strcmp |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:7:54:35 | (bool)... |
|
||||
| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:14:54:25 | envStrGlobal |
|
||||
| test.cpp:60:29:60:34 | call to getenv | test.cpp:60:29:60:34 | call to getenv |
|
||||
| test.cpp:60:29:60:34 | call to getenv | test.cpp:60:29:60:47 | (const char *)... |
|
||||
| test.cpp:60:29:60:34 | call to getenv | test.cpp:64:25:64:32 | userName |
|
||||
| test.cpp:68:28:68:33 | call to getenv | test.cpp:68:28:68:33 | call to getenv |
|
||||
| test.cpp:68:28:68:33 | call to getenv | test.cpp:68:28:68:46 | (const char *)... |
|
||||
| test.cpp:68:28:68:33 | call to getenv | test.cpp:70:18:70:25 | userName |
|
||||
| test.cpp:75:20:75:25 | call to getenv | test.cpp:75:15:75:18 | call to atoi |
|
||||
| test.cpp:75:20:75:25 | call to getenv | test.cpp:75:20:75:25 | call to getenv |
|
||||
| test.cpp:75:20:75:25 | call to getenv | test.cpp:75:20:75:45 | (const char *)... |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:28:83:33 | call to getenv |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:28:83:46 | (const char *)... |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:15:86:22 | userName |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:6:88:27 | ! ... |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:7:88:12 | call to strcmp |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:7:88:27 | (bool)... |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:14:88:17 | (const char *)... |
|
||||
| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:14:88:17 | copy |
|
||||
| test.cpp:100:12:100:15 | call to gets | test.cpp:100:12:100:15 | call to gets |
|
||||
| test.cpp:100:17:100:22 | buffer | test.cpp:100:17:100:22 | array to pointer conversion |
|
||||
| test.cpp:100:17:100:22 | buffer | test.cpp:100:17:100:22 | buffer |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:106:28:106:33 | call to getenv |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:106:28:106:46 | (const char *)... |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:109:15:109:22 | userName |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:111:6:111:27 | ! ... |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:111:7:111:12 | call to strcmp |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:111:7:111:27 | (bool)... |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:111:14:111:17 | (const char *)... |
|
||||
| test.cpp:106:28:106:33 | call to getenv | test.cpp:111:14:111:17 | copy |
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import semmle.code.cpp.ir.dataflow.DefaultTaintTracking
|
||||
|
||||
from Expr source, Element tainted, string globalVar
|
||||
class SourceConfiguration extends TaintedWithPath::TaintTrackingConfiguration {
|
||||
override predicate isSink(Element e) { any() }
|
||||
}
|
||||
|
||||
from Expr source, Element tainted
|
||||
where
|
||||
taintedIncludingGlobalVars(source, tainted, globalVar) and
|
||||
TaintedWithPath::taintedWithPath(source, tainted, _, _) and
|
||||
not tainted.getLocation().getFile().getExtension() = "h"
|
||||
select source, tainted, globalVar
|
||||
select source, tainted
|
||||
|
||||
127
cpp/ql/test/library-tests/dataflow/smart-pointers-taint/memory.h
Normal file
127
cpp/ql/test/library-tests/dataflow/smart-pointers-taint/memory.h
Normal file
@@ -0,0 +1,127 @@
|
||||
|
||||
namespace std {
|
||||
namespace detail {
|
||||
template<typename T>
|
||||
class compressed_pair_element {
|
||||
T element;
|
||||
|
||||
public:
|
||||
compressed_pair_element() = default;
|
||||
compressed_pair_element(const T& t) : element(t) {}
|
||||
|
||||
T& get() { return element; }
|
||||
|
||||
const T& get() const { return element; }
|
||||
};
|
||||
|
||||
template<typename T, typename U>
|
||||
struct compressed_pair : private compressed_pair_element<T>, private compressed_pair_element<U> {
|
||||
compressed_pair() = default;
|
||||
compressed_pair(T& t) : compressed_pair_element<T>(t), compressed_pair_element<U>() {}
|
||||
compressed_pair(const compressed_pair&) = delete;
|
||||
compressed_pair(compressed_pair<T, U>&&) noexcept = default;
|
||||
|
||||
T& first() { return static_cast<compressed_pair_element<T>&>(*this).get(); }
|
||||
U& second() { return static_cast<compressed_pair_element<U>&>(*this).get(); }
|
||||
|
||||
const T& first() const { return static_cast<const compressed_pair_element<T>&>(*this).get(); }
|
||||
const U& second() const { return static_cast<const compressed_pair_element<U>&>(*this).get(); }
|
||||
};
|
||||
}
|
||||
|
||||
template<class T>
|
||||
struct default_delete {
|
||||
void operator()(T* ptr) const { delete ptr; }
|
||||
};
|
||||
|
||||
template<class T>
|
||||
struct default_delete<T[]> {
|
||||
template<class U>
|
||||
void operator()(U* ptr) const { delete[] ptr; }
|
||||
};
|
||||
|
||||
template<class T, class Deleter = default_delete<T> >
|
||||
class unique_ptr {
|
||||
private:
|
||||
detail::compressed_pair<T*, Deleter> data;
|
||||
public:
|
||||
constexpr unique_ptr() noexcept {}
|
||||
explicit unique_ptr(T* ptr) noexcept : data(ptr) {}
|
||||
unique_ptr(const unique_ptr& ptr) = delete;
|
||||
unique_ptr(unique_ptr&& ptr) noexcept = default;
|
||||
|
||||
unique_ptr& operator=(unique_ptr&& ptr) noexcept = default;
|
||||
|
||||
T& operator*() const { return *get(); }
|
||||
T* operator->() const noexcept { return get(); }
|
||||
|
||||
T* get() const noexcept { return data.first(); }
|
||||
|
||||
~unique_ptr() {
|
||||
Deleter& d = data.second();
|
||||
d(data.first());
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, class... Args> unique_ptr<T> make_unique(Args&&... args) {
|
||||
return unique_ptr<T>(new T(args...)); // std::forward calls elided for simplicity.
|
||||
}
|
||||
|
||||
class ctrl_block {
|
||||
unsigned uses;
|
||||
|
||||
public:
|
||||
ctrl_block() : uses(1) {}
|
||||
|
||||
void inc() { ++uses; }
|
||||
bool dec() { return --uses == 0; }
|
||||
|
||||
virtual void destroy() = 0;
|
||||
virtual ~ctrl_block() {}
|
||||
};
|
||||
|
||||
template<typename T, class Deleter = default_delete<T> >
|
||||
struct ctrl_block_impl: public ctrl_block {
|
||||
T* ptr;
|
||||
Deleter d;
|
||||
|
||||
ctrl_block_impl(T* ptr, Deleter d) : ptr(ptr), d(d) {}
|
||||
virtual void destroy() override { d(ptr); }
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class shared_ptr {
|
||||
private:
|
||||
ctrl_block* ctrl;
|
||||
T* ptr;
|
||||
|
||||
void dec() {
|
||||
if(ctrl->dec()) {
|
||||
ctrl->destroy();
|
||||
delete ctrl;
|
||||
}
|
||||
}
|
||||
|
||||
void inc() {
|
||||
ctrl->inc();
|
||||
}
|
||||
|
||||
public:
|
||||
constexpr shared_ptr() noexcept = default;
|
||||
shared_ptr(T* ptr) : ctrl(new ctrl_block_impl<T>(ptr, default_delete<T>())) {}
|
||||
shared_ptr(const shared_ptr& s) noexcept : ptr(s.ptr), ctrl(s.ctrl) {
|
||||
inc();
|
||||
}
|
||||
shared_ptr(shared_ptr&& s) noexcept = default;
|
||||
|
||||
T* operator->() const { return ptr; }
|
||||
|
||||
T& operator*() const { return *ptr; }
|
||||
|
||||
~shared_ptr() { dec(); }
|
||||
};
|
||||
|
||||
template<typename T, class... Args> shared_ptr<T> make_shared(Args&&... args) {
|
||||
return shared_ptr<T>(new T(args...)); // std::forward calls elided for simplicity.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import TestUtilities.dataflow.FlowTestCommon
|
||||
|
||||
module ASTTest {
|
||||
private import semmle.code.cpp.dataflow.TaintTracking
|
||||
|
||||
class ASTSmartPointerTaintConfig extends TaintTracking::Configuration {
|
||||
ASTSmartPointerTaintConfig() { this = "ASTSmartPointerTaintConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source.asExpr().(FunctionCall).getTarget().getName() = "source"
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(FunctionCall call |
|
||||
call.getTarget().getName() = "sink" and
|
||||
sink.asExpr() = call.getAnArgument()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module IRTest {
|
||||
private import semmle.code.cpp.ir.dataflow.TaintTracking
|
||||
|
||||
class IRSmartPointerTaintConfig extends TaintTracking::Configuration {
|
||||
IRSmartPointerTaintConfig() { this = "IRSmartPointerTaintConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source.asExpr().(FunctionCall).getTarget().getName() = "source"
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(FunctionCall call |
|
||||
call.getTarget().getName() = "sink" and
|
||||
sink.asExpr() = call.getAnArgument()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user