Merge branch 'main' of github.com:github/codeql into python-port-insecure-protocol

This commit is contained in:
Rasmus Lerchedahl Petersen
2021-04-20 16:33:03 +02:00
1161 changed files with 30996 additions and 17022 deletions

30
.github/workflows/close-stale.yml vendored Normal file
View 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

View File

@@ -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."

View File

@@ -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"
]
}
}

View File

@@ -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);
}
}
}

View File

@@ -17,7 +17,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build" Version="16.0.461" />
<PackageReference Include="Microsoft.Build" Version="16.9.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -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.

View 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.

View 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()

View 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() }
}

View File

@@ -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

View 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, ""

View File

@@ -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*()
)
}
}

View File

@@ -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())
}

View File

@@ -5,7 +5,6 @@
* @kind treemap
* @treemap.warnOn highValues
* @metricType externalDependency
* @precision medium
* @id cpp/external-dependencies
* @tags modularity
*/

View File

@@ -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

View File

@@ -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
*/

View File

@@ -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

View File

@@ -8,7 +8,6 @@
* @treemap.warnOn highValues
* @metricType file
* @metricAggregate avg sum max
* @precision high
* @id cpp/duplicated-lines-in-files
* @tags testability
* modularity

View File

@@ -5,7 +5,6 @@
* @treemap.warnOn lowValues
* @metricType file
* @metricAggregate avg sum max
* @precision medium
* @id cpp/tests-in-files
* @tags maintainability
*/

View File

@@ -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
}

View 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())

View File

@@ -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

View File

@@ -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.
...

View File

@@ -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>

View File

@@ -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."

View File

@@ -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`

View File

@@ -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>

View File

@@ -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"

View File

@@ -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

View File

@@ -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."

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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)
)
}

View File

@@ -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.

View File

@@ -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()
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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() }

View File

@@ -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)
}
}

View File

@@ -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`.
*/

View File

@@ -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

View File

@@ -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" }

View File

@@ -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) }
}
/*

View 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
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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() }

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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.

View File

@@ -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, ", "
)
)
}
}

View File

@@ -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
}

View File

@@ -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)
)
}

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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
)
)
}

View File

@@ -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)

View File

@@ -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)
}
/**

View File

@@ -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() }

View File

@@ -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()
}
}
/**

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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()
{

View File

@@ -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. |

View File

@@ -0,0 +1 @@
experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.ql

View File

@@ -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
}

View File

@@ -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. |

View File

@@ -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. |

View File

@@ -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 |

View File

@@ -0,0 +1 @@
experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.ql

View File

@@ -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++)

View File

@@ -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;
}

View File

@@ -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" }

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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. |

View File

@@ -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. |

View File

@@ -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] |

View File

@@ -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

View File

@@ -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 |

View File

@@ -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"

View File

@@ -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 |

View File

@@ -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

View 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.
}
}

View File

@@ -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