Compare commits

..

1 Commits

Author SHA1 Message Date
Alex Denisov
979cc85e5f Swift: produce malformed bundle 2023-06-26 14:46:09 +02:00
1370 changed files with 64347 additions and 60419 deletions

View File

@@ -1 +1 @@
6.3.1
6.1.2

View File

@@ -1,29 +0,0 @@
name: "Check implicit this warnings"
on:
workflow_dispatch:
pull_request:
paths:
- "**qlpack.yml"
branches:
- main
- "rc/*"
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Check that implicit this warnings is enabled for all packs
shell: bash
run: |
EXIT_CODE=0
packs="$(find . -iname 'qlpack.yml')"
for pack_file in ${packs}; do
option="$(yq '.warnOnImplicitThis' ${pack_file})"
if [ "${option}" != "true" ]; then
echo "::error file=${pack_file}::warnOnImplicitThis property must be set to 'true' for pack ${pack_file}"
EXIT_CODE=1
fi
done
exit "${EXIT_CODE}"

View File

@@ -5,9 +5,9 @@ repos:
rev: v3.2.0
hooks:
- id: trailing-whitespace
exclude: /test/.*$(?<!\.ql)(?<!\.qll)(?<!\.qlref)|.*\.patch
exclude: /test/.*$(?<!\.ql)(?<!\.qll)(?<!\.qlref)
- id: end-of-file-fixer
exclude: /test/.*$(?<!\.ql)(?<!\.qll)(?<!\.qlref)|.*\.patch
exclude: /test/.*$(?<!\.ql)(?<!\.qll)(?<!\.qlref)
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v13.0.1
@@ -21,11 +21,6 @@ repos:
- id: autopep8
files: ^misc/codegen/.*\.py
- repo: https://github.com/warchant/pre-commit-buildifier
rev: 0.0.2
hooks:
- id: buildifier
- repo: local
hooks:
- id: codeql-format

View File

@@ -14,16 +14,14 @@ If you have an idea for a query that you would like to share with other CodeQL u
1. **Directory structure**
There are eight language-specific query directories in this repository:
There are six language-specific query directories in this repository:
* C/C++: `cpp/ql/src`
* C#: `csharp/ql/src`
* Go: `go/ql/src`
* Java/Kotlin: `java/ql/src`
* Java: `java/ql/src`
* JavaScript: `javascript/ql/src`
* Python: `python/ql/src`
* Ruby: `ruby/ql/src`
* Swift: `swift/ql/src`
Each language-specific directory contains further subdirectories that group queries based on their `@tags` or purpose.
- Experimental queries and libraries are stored in the `experimental` subdirectory within each language-specific directory in the [CodeQL repository](https://github.com/github/codeql). For example, experimental Java queries and libraries are stored in `java/ql/src/experimental` and any corresponding tests in `java/ql/test/experimental`.

View File

@@ -1,4 +1,24 @@
{
"DataFlow Java/C++/C#/Go/Python/Ruby/Swift": [
"java/ql/lib/semmle/code/java/dataflow/internal/DataFlow.qll",
"cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlow.qll",
"cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlow.qll",
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlow.qll",
"go/ql/lib/semmle/go/dataflow/internal/DataFlow.qll",
"python/ql/lib/semmle/python/dataflow/new/internal/DataFlow.qll",
"ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlow.qll",
"swift/ql/lib/codeql/swift/dataflow/internal/DataFlow.qll"
],
"DataFlowImpl Java/C++/C#/Go/Python/Ruby/Swift": [
"java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll",
"cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll",
"cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll",
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll",
"go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl.qll",
"python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll",
"ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl.qll",
"swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImpl.qll"
],
"DataFlow Java/C++/C#/Go/Python/Ruby/Swift Legacy Configuration": [
"java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl1.qll",
"java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll",
@@ -33,6 +53,16 @@
"ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplForPathname.qll",
"swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImpl1.qll"
],
"DataFlow Java/C++/C#/Go/Python/Ruby/Swift Common": [
"java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll",
"cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll",
"cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll",
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll",
"go/ql/lib/semmle/go/dataflow/internal/DataFlowImplCommon.qll",
"python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImplCommon.qll",
"ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplCommon.qll",
"swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImplCommon.qll"
],
"TaintTracking Java/C++/C#/Go/Python/Ruby/Swift": [
"cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking1/TaintTracking.qll",
"cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking1/TaintTracking.qll",
@@ -486,6 +516,7 @@
],
"CFG": [
"csharp/ql/lib/semmle/code/csharp/controlflow/internal/ControlFlowGraphImplShared.qll",
"ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImplShared.qll",
"swift/ql/lib/codeql/swift/controlflow/internal/ControlFlowGraphImplShared.qll"
],
"TypeTracker": [
@@ -572,4 +603,4 @@
"python/ql/lib/semmle/python/security/internal/EncryptionKeySizes.qll",
"java/ql/lib/semmle/code/java/security/internal/EncryptionKeySizes.qll"
]
}
}

View File

@@ -1,2 +0,0 @@
description: Remove _Float128 type
compatibility: full

View File

@@ -2,4 +2,3 @@ name: codeql/cpp-downgrades
groups: cpp
downgrades: .
library: true
warnOnImplicitThis: true

View File

@@ -4,4 +4,3 @@ groups:
- examples
dependencies:
codeql/cpp-all: ${workspace}
warnOnImplicitThis: true

View File

@@ -1,52 +1,3 @@
## 0.9.0
### Breaking Changes
* The `shouldPrintFunction` predicate from `PrintAstConfiguration` has been replaced by `shouldPrintDeclaration`. Users should now override `shouldPrintDeclaration` if they want to limit the declarations that should be printed.
* The `shouldPrintFunction` predicate from `PrintIRConfiguration` has been replaced by `shouldPrintDeclaration`. Users should now override `shouldPrintDeclaration` if they want to limit the declarations that should be printed.
### Major Analysis Improvements
* The `PrintAST` library now also prints global and namespace variables and their initializers.
### Minor Analysis Improvements
* The `_Float128x` type is no longer exposed as a builtin type. As this type could not occur any code base, this should only affect queries that explicitly looked at the builtin types.
## 0.8.1
### Deprecated APIs
* The library `semmle.code.cpp.dataflow.DataFlow` has been deprecated. Please use `semmle.code.cpp.dataflow.new.DataFlow` instead.
### New Features
* The `DataFlow::StateConfigSig` signature module has gained default implementations for `isBarrier/2` and `isAdditionalFlowStep/4`.
Hence it is no longer needed to provide `none()` implementations of these predicates if they are not needed.
### Minor Analysis Improvements
* Data flow configurations can now include a predicate `neverSkip(Node node)`
in order to ensure inclusion of certain nodes in the path explanations. The
predicate defaults to the end-points of the additional flow steps provided in
the configuration, which means that such steps now always are visible by
default in path explanations.
* The `IRGuards` library has improved handling of pointer addition and subtraction operations.
## 0.8.0
### New Features
* The `ProductFlow::StateConfigSig` signature now includes default predicates for `isBarrier1`, `isBarrier2`, `isAdditionalFlowStep1`, and `isAdditionalFlowStep1`. Hence, it is no longer needed to provide `none()` implementations of these predicates if they are not needed.
### Minor Analysis Improvements
* Deleted the deprecated `getURL` predicate from the `Container`, `Folder`, and `File` classes. Use the `getLocation` predicate instead.
## 0.7.4
No user-facing changes.
## 0.7.3
### Minor Analysis Improvements

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Deleted the deprecated `getURL` predicate from the `Container`, `Folder`, and `File` classes. Use the `getLocation` predicate instead.

View File

@@ -1,3 +0,0 @@
## 0.7.4
No user-facing changes.

View File

@@ -1,9 +0,0 @@
## 0.8.0
### New Features
* The `ProductFlow::StateConfigSig` signature now includes default predicates for `isBarrier1`, `isBarrier2`, `isAdditionalFlowStep1`, and `isAdditionalFlowStep1`. Hence, it is no longer needed to provide `none()` implementations of these predicates if they are not needed.
### Minor Analysis Improvements
* Deleted the deprecated `getURL` predicate from the `Container`, `Folder`, and `File` classes. Use the `getLocation` predicate instead.

View File

@@ -1,19 +0,0 @@
## 0.8.1
### Deprecated APIs
* The library `semmle.code.cpp.dataflow.DataFlow` has been deprecated. Please use `semmle.code.cpp.dataflow.new.DataFlow` instead.
### New Features
* The `DataFlow::StateConfigSig` signature module has gained default implementations for `isBarrier/2` and `isAdditionalFlowStep/4`.
Hence it is no longer needed to provide `none()` implementations of these predicates if they are not needed.
### Minor Analysis Improvements
* Data flow configurations can now include a predicate `neverSkip(Node node)`
in order to ensure inclusion of certain nodes in the path explanations. The
predicate defaults to the end-points of the additional flow steps provided in
the configuration, which means that such steps now always are visible by
default in path explanations.
* The `IRGuards` library has improved handling of pointer addition and subtraction operations.

View File

@@ -1,14 +0,0 @@
## 0.9.0
### Breaking Changes
* The `shouldPrintFunction` predicate from `PrintAstConfiguration` has been replaced by `shouldPrintDeclaration`. Users should now override `shouldPrintDeclaration` if they want to limit the declarations that should be printed.
* The `shouldPrintFunction` predicate from `PrintIRConfiguration` has been replaced by `shouldPrintDeclaration`. Users should now override `shouldPrintDeclaration` if they want to limit the declarations that should be printed.
### Major Analysis Improvements
* The `PrintAST` library now also prints global and namespace variables and their initializers.
### Minor Analysis Improvements
* The `_Float128x` type is no longer exposed as a builtin type. As this type could not occur any code base, this should only affect queries that explicitly looked at the builtin types.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.9.0
lastReleaseVersion: 0.7.3

View File

@@ -18,10 +18,10 @@ external string selectedSourceFile();
class Cfg extends PrintAstConfiguration {
/**
* Holds if the AST for `decl` should be printed.
* Print All declarations from the selected file.
* Holds if the AST for `func` should be printed.
* Print All functions from the selected file.
*/
override predicate shouldPrintDeclaration(Declaration decl) {
decl.getFile() = getFileBySourceArchiveName(selectedSourceFile())
override predicate shouldPrintFunction(Function func) {
func.getFile() = getFileBySourceArchiveName(selectedSourceFile())
}
}

View File

@@ -1,12 +1,11 @@
name: codeql/cpp-all
version: 0.9.0
version: 0.7.4-dev
groups: cpp
dbscheme: semmlecode.cpp.dbscheme
extractor: cpp
library: true
upgrades: upgrades
dependencies:
codeql/dataflow: ${workspace}
codeql/ssa: ${workspace}
codeql/tutorial: ${workspace}
codeql/util: ${workspace}

View File

@@ -6,9 +6,11 @@ private import PrintAST
* that requests that function, or no `PrintASTConfiguration` exists.
*/
private predicate shouldPrintDeclaration(Declaration decl) {
not (decl instanceof Function or decl instanceof GlobalOrNamespaceVariable)
not decl instanceof Function
or
exists(PrintAstConfiguration config | config.shouldPrintDeclaration(decl))
not exists(PrintAstConfiguration c)
or
exists(PrintAstConfiguration config | config.shouldPrintFunction(decl))
}
/**

View File

@@ -9,13 +9,13 @@ import cpp
import PrintAST
/**
* Temporarily tweak this class or make a copy to control which declarations are
* Temporarily tweak this class or make a copy to control which functions are
* printed.
*/
class Cfg extends PrintAstConfiguration {
/**
* TWEAK THIS PREDICATE AS NEEDED.
* Holds if the AST for `decl` should be printed.
* Holds if the AST for `func` should be printed.
*/
override predicate shouldPrintDeclaration(Declaration decl) { any() }
override predicate shouldPrintFunction(Function func) { any() }
}

View File

@@ -1,9 +1,9 @@
/**
* Provides queries to pretty-print a C++ AST as a graph.
*
* By default, this will print the AST for all functions and global and namespace variables in
* the database. To change this behavior, extend `PrintASTConfiguration` and override
* `shouldPrintDeclaration` to hold for only the declarations you wish to view the AST for.
* By default, this will print the AST for all functions in the database. To change this behavior,
* extend `PrintASTConfiguration` and override `shouldPrintFunction` to hold for only the functions
* you wish to view the AST for.
*/
import cpp
@@ -12,7 +12,7 @@ private import semmle.code.cpp.Print
private newtype TPrintAstConfiguration = MkPrintAstConfiguration()
/**
* The query can extend this class to control which declarations are printed.
* The query can extend this class to control which functions are printed.
*/
class PrintAstConfiguration extends TPrintAstConfiguration {
/**
@@ -21,16 +21,14 @@ class PrintAstConfiguration extends TPrintAstConfiguration {
string toString() { result = "PrintASTConfiguration" }
/**
* Holds if the AST for `decl` should be printed. By default, holds for all
* functions and global and namespace variables. Currently, does not support any
* other declaration types.
* Holds if the AST for `func` should be printed. By default, holds for all
* functions.
*/
predicate shouldPrintDeclaration(Declaration decl) { any() }
predicate shouldPrintFunction(Function func) { any() }
}
private predicate shouldPrintDeclaration(Declaration decl) {
exists(PrintAstConfiguration config | config.shouldPrintDeclaration(decl)) and
(decl instanceof Function or decl instanceof GlobalOrNamespaceVariable)
private predicate shouldPrintFunction(Function func) {
exists(PrintAstConfiguration config | config.shouldPrintFunction(func))
}
bindingset[s]
@@ -71,7 +69,7 @@ private predicate locationSortKeys(Locatable ast, string file, int line, int col
)
}
private Declaration getAnEnclosingDeclaration(Locatable ast) {
private Function getEnclosingFunction(Locatable ast) {
result = ast.(Expr).getEnclosingFunction()
or
result = ast.(Stmt).getEnclosingFunction()
@@ -80,10 +78,6 @@ private Declaration getAnEnclosingDeclaration(Locatable ast) {
or
result = ast.(Parameter).getFunction()
or
result = ast.(Expr).getEnclosingDeclaration()
or
result = ast.(Initializer).getDeclaration()
or
result = ast
}
@@ -92,21 +86,21 @@ private Declaration getAnEnclosingDeclaration(Locatable ast) {
* nodes for things like parameter lists and constructor init lists.
*/
private newtype TPrintAstNode =
TAstNode(Locatable ast) { shouldPrintDeclaration(getAnEnclosingDeclaration(ast)) } or
TAstNode(Locatable ast) { shouldPrintFunction(getEnclosingFunction(ast)) } or
TDeclarationEntryNode(DeclStmt stmt, DeclarationEntry entry) {
// We create a unique node for each pair of (stmt, entry), to avoid having one node with
// multiple parents due to extractor bug CPP-413.
stmt.getADeclarationEntry() = entry and
shouldPrintDeclaration(stmt.getEnclosingFunction())
shouldPrintFunction(stmt.getEnclosingFunction())
} or
TParametersNode(Function func) { shouldPrintDeclaration(func) } or
TParametersNode(Function func) { shouldPrintFunction(func) } or
TConstructorInitializersNode(Constructor ctor) {
ctor.hasEntryPoint() and
shouldPrintDeclaration(ctor)
shouldPrintFunction(ctor)
} or
TDestructorDestructionsNode(Destructor dtor) {
dtor.hasEntryPoint() and
shouldPrintDeclaration(dtor)
shouldPrintFunction(dtor)
}
/**
@@ -164,10 +158,10 @@ class PrintAstNode extends TPrintAstNode {
/**
* Holds if this node should be printed in the output. By default, all nodes
* within functions and global and namespace variables are printed, but the query
* can override `PrintASTConfiguration.shouldPrintDeclaration` to filter the output.
* within a function are printed, but the query can override
* `PrintASTConfiguration.shouldPrintFunction` to filter the output.
*/
final predicate shouldPrint() { shouldPrintDeclaration(this.getEnclosingDeclaration()) }
final predicate shouldPrint() { shouldPrintFunction(this.getEnclosingFunction()) }
/**
* Gets the children of this node.
@@ -235,15 +229,10 @@ class PrintAstNode extends TPrintAstNode {
abstract string getChildAccessorPredicateInternal(int childIndex);
/**
* Gets the `Declaration` that contains this node.
* Gets the `Function` that contains this node.
*/
private Declaration getEnclosingDeclaration() { result = this.getParent*().getDeclaration() }
/**
* Gets the `Declaration` this node represents.
*/
private Declaration getDeclaration() {
result = this.(AstNode).getAst() and shouldPrintDeclaration(result)
private Function getEnclosingFunction() {
result = this.getParent*().(FunctionNode).getFunction()
}
}
@@ -582,53 +571,16 @@ class DestructorDestructionsNode extends PrintAstNode, TDestructorDestructionsNo
final Destructor getDestructor() { result = dtor }
}
abstract private class FunctionOrGlobalOrNamespaceVariableNode extends AstNode {
override string toString() { result = qlClass(ast) + getIdentityString(ast) }
private int getOrder() {
this =
rank[result](FunctionOrGlobalOrNamespaceVariableNode node, Declaration decl, string file,
int line, int column |
node.getAst() = decl and
locationSortKeys(decl, file, line, column)
|
node order by file, line, column, getIdentityString(decl)
)
}
override string getProperty(string key) {
result = super.getProperty(key)
or
key = "semmle.order" and result = this.getOrder().toString()
}
}
/**
* A node representing a `GlobalOrNamespaceVariable`.
*/
class GlobalOrNamespaceVariableNode extends FunctionOrGlobalOrNamespaceVariableNode {
GlobalOrNamespaceVariable var;
GlobalOrNamespaceVariableNode() { var = ast }
override PrintAstNode getChildInternal(int childIndex) {
childIndex = 0 and
result.(AstNode).getAst() = var.getInitializer()
}
override string getChildAccessorPredicateInternal(int childIndex) {
childIndex = 0 and result = "getInitializer()"
}
}
/**
* A node representing a `Function`.
*/
class FunctionNode extends FunctionOrGlobalOrNamespaceVariableNode {
class FunctionNode extends AstNode {
Function func;
FunctionNode() { func = ast }
override string toString() { result = qlClass(func) + getIdentityString(func) }
override PrintAstNode getChildInternal(int childIndex) {
childIndex = 0 and
result.(ParametersNode).getFunction() = func
@@ -652,10 +604,31 @@ class FunctionNode extends FunctionOrGlobalOrNamespaceVariableNode {
or
childIndex = 3 and result = "<destructions>"
}
private int getOrder() {
this =
rank[result](FunctionNode node, Function function, string file, int line, int column |
node.getAst() = function and
locationSortKeys(function, file, line, column)
|
node order by file, line, column, getIdentityString(function)
)
}
override string getProperty(string key) {
result = super.getProperty(key)
or
key = "semmle.order" and result = this.getOrder().toString()
}
/**
* Gets the `Function` this node represents.
*/
final Function getFunction() { result = func }
}
private string getChildAccessorWithoutConversions(Locatable parent, Element child) {
shouldPrintDeclaration(getAnEnclosingDeclaration(parent)) and
shouldPrintFunction(getEnclosingFunction(parent)) and
(
exists(Stmt s | s = parent |
namedStmtChildPredicates(s, child, result)
@@ -674,7 +647,7 @@ private string getChildAccessorWithoutConversions(Locatable parent, Element chil
}
private predicate namedStmtChildPredicates(Locatable s, Element e, string pred) {
shouldPrintDeclaration(getAnEnclosingDeclaration(s)) and
shouldPrintFunction(getEnclosingFunction(s)) and
(
exists(int n | s.(BlockStmt).getStmt(n) = e and pred = "getStmt(" + n + ")")
or
@@ -762,14 +735,12 @@ private predicate namedStmtChildPredicates(Locatable s, Element e, string pred)
}
private predicate namedExprChildPredicates(Expr expr, Element ele, string pred) {
shouldPrintDeclaration(expr.getEnclosingDeclaration()) and
shouldPrintFunction(expr.getEnclosingFunction()) and
(
expr.(Access).getTarget() = ele and pred = "getTarget()"
or
expr.(VariableAccess).getQualifier() = ele and pred = "getQualifier()"
or
expr.(FunctionAccess).getQualifier() = ele and pred = "getQualifier()"
or
exists(Field f |
expr.(ClassAggregateLiteral).getAFieldExpr(f) = ele and
pred = "getAFieldExpr(" + f.toString() + ")"

View File

@@ -814,6 +814,9 @@ private predicate floatingPointTypeMapping(
// _Float128
kind = 49 and base = 2 and domain = TRealDomain() and realKind = 49 and extended = false
or
// _Float128x
kind = 50 and base = 2 and domain = TRealDomain() and realKind = 50 and extended = true
or
// _Float16
kind = 52 and base = 2 and domain = TRealDomain() and realKind = 52 and extended = false
or

View File

@@ -627,20 +627,6 @@ private predicate sub_lt(
x = int_value(rhs.getRight()) and
k = c - x
)
or
exists(PointerSubInstruction lhs, int c, int x |
compares_lt(cmp, lhs.getAUse(), right, c, isLt, testIsTrue) and
left = lhs.getLeftOperand() and
x = int_value(lhs.getRight()) and
k = c + x
)
or
exists(PointerSubInstruction rhs, int c, int x |
compares_lt(cmp, left, rhs.getAUse(), c, isLt, testIsTrue) and
right = rhs.getLeftOperand() and
x = int_value(rhs.getRight()) and
k = c - x
)
}
// left + x < right + c => left < right + (c-x)
@@ -667,26 +653,6 @@ private predicate add_lt(
) and
k = c + x
)
or
exists(PointerAddInstruction lhs, int c, int x |
compares_lt(cmp, lhs.getAUse(), right, c, isLt, testIsTrue) and
(
left = lhs.getLeftOperand() and x = int_value(lhs.getRight())
or
left = lhs.getRightOperand() and x = int_value(lhs.getLeft())
) and
k = c - x
)
or
exists(PointerAddInstruction rhs, int c, int x |
compares_lt(cmp, left, rhs.getAUse(), c, isLt, testIsTrue) and
(
right = rhs.getLeftOperand() and x = int_value(rhs.getRight())
or
right = rhs.getRightOperand() and x = int_value(rhs.getLeft())
) and
k = c + x
)
}
// left - x == right + c => left == right + (c+x)
@@ -707,20 +673,6 @@ private predicate sub_eq(
x = int_value(rhs.getRight()) and
k = c - x
)
or
exists(PointerSubInstruction lhs, int c, int x |
compares_eq(cmp, lhs.getAUse(), right, c, areEqual, testIsTrue) and
left = lhs.getLeftOperand() and
x = int_value(lhs.getRight()) and
k = c + x
)
or
exists(PointerSubInstruction rhs, int c, int x |
compares_eq(cmp, left, rhs.getAUse(), c, areEqual, testIsTrue) and
right = rhs.getLeftOperand() and
x = int_value(rhs.getRight()) and
k = c - x
)
}
// left + x == right + c => left == right + (c-x)
@@ -747,26 +699,6 @@ private predicate add_eq(
) and
k = c + x
)
or
exists(PointerAddInstruction lhs, int c, int x |
compares_eq(cmp, lhs.getAUse(), right, c, areEqual, testIsTrue) and
(
left = lhs.getLeftOperand() and x = int_value(lhs.getRight())
or
left = lhs.getRightOperand() and x = int_value(lhs.getLeft())
) and
k = c - x
)
or
exists(PointerAddInstruction rhs, int c, int x |
compares_eq(cmp, left, rhs.getAUse(), c, areEqual, testIsTrue) and
(
right = rhs.getLeftOperand() and x = int_value(rhs.getRight())
or
right = rhs.getRightOperand() and x = int_value(rhs.getLeft())
) and
k = c + x
)
}
/** The int value of integer constant expression. */

View File

@@ -20,14 +20,10 @@
import cpp
/**
* DEPRECATED: Use `semmle.code.cpp.dataflow.new.DataFlow` instead.
*
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) data flow analyses.
*/
deprecated module DataFlow {
private import semmle.code.cpp.dataflow.internal.DataFlowImplSpecific
private import codeql.dataflow.DataFlow
import DataFlowMake<CppOldDataFlow>
module DataFlow {
import semmle.code.cpp.dataflow.internal.DataFlow
import semmle.code.cpp.dataflow.internal.DataFlowImpl1
}

View File

@@ -12,11 +12,9 @@
import cpp
/**
* DEPRECATED: Use `semmle.code.cpp.dataflow.new.DataFlow2` instead.
*
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) data flow analyses.
*/
deprecated module DataFlow2 {
module DataFlow2 {
import semmle.code.cpp.dataflow.internal.DataFlowImpl2
}

View File

@@ -12,11 +12,9 @@
import cpp
/**
* DEPRECATED: Use `semmle.code.cpp.dataflow.new.DataFlow3` instead.
*
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) data flow analyses.
*/
deprecated module DataFlow3 {
module DataFlow3 {
import semmle.code.cpp.dataflow.internal.DataFlowImpl3
}

View File

@@ -12,11 +12,9 @@
import cpp
/**
* DEPRECATED: Use `semmle.code.cpp.dataflow.new.DataFlow4` instead.
*
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) data flow analyses.
*/
deprecated module DataFlow4 {
module DataFlow4 {
import semmle.code.cpp.dataflow.internal.DataFlowImpl4
}

View File

@@ -19,12 +19,10 @@ import semmle.code.cpp.dataflow.DataFlow
import semmle.code.cpp.dataflow.DataFlow2
/**
* DEPRECATED: Use `semmle.code.cpp.dataflow.new.TaintTracking` instead.
*
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) taint-tracking analyses.
*/
deprecated module TaintTracking {
module TaintTracking {
import semmle.code.cpp.dataflow.internal.tainttracking1.TaintTracking
import semmle.code.cpp.dataflow.internal.tainttracking1.TaintTrackingImpl
}

View File

@@ -12,11 +12,9 @@
*/
/**
* DEPRECATED: Use `semmle.code.cpp.dataflow.new.TaintTracking2` instead.
*
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) taint-tracking analyses.
*/
deprecated module TaintTracking2 {
module TaintTracking2 {
import semmle.code.cpp.dataflow.internal.tainttracking2.TaintTrackingImpl
}

View File

@@ -0,0 +1,412 @@
/**
* Provides an implementation of global (interprocedural) data flow. This file
* re-exports the local (intraprocedural) data flow analysis from
* `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed
* through the `Global` and `GlobalWithState` modules.
*/
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
import DataFlowImplCommonPublic
private import DataFlowImpl
/** An input configuration for data flow. */
signature module ConfigSig {
/**
* Holds if `source` is a relevant data flow source.
*/
predicate isSource(Node source);
/**
* Holds if `sink` is a relevant data flow sink.
*/
predicate isSink(Node sink);
/**
* Holds if data flow through `node` is prohibited. This completely removes
* `node` from the data flow graph.
*/
default predicate isBarrier(Node node) { none() }
/** Holds if data flow into `node` is prohibited. */
default predicate isBarrierIn(Node node) { none() }
/** Holds if data flow out of `node` is prohibited. */
default predicate isBarrierOut(Node node) { none() }
/**
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
*/
default predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
/**
* Holds if an arbitrary number of implicit read steps of content `c` may be
* taken at `node`.
*/
default predicate allowImplicitRead(Node node, ContentSet c) { none() }
/**
* Gets the virtual dispatch branching limit when calculating field flow.
* This can be overridden to a smaller value to improve performance (a
* value of 0 disables field flow), or a larger value to get more results.
*/
default int fieldFlowBranchLimit() { result = 2 }
/**
* Gets a data flow configuration feature to add restrictions to the set of
* valid flow paths.
*
* - `FeatureHasSourceCallContext`:
* Assume that sources have some existing call context to disallow
* conflicting return-flow directly following the source.
* - `FeatureHasSinkCallContext`:
* Assume that sinks have some existing call context to disallow
* conflicting argument-to-parameter flow directly preceding the sink.
* - `FeatureEqualSourceSinkCallContext`:
* Implies both of the above and additionally ensures that the entire flow
* path preserves the call context.
*
* These features are generally not relevant for typical end-to-end data flow
* queries, but should only be used for constructing paths that need to
* somehow be pluggable in another path context.
*/
default FlowFeature getAFeature() { none() }
/** Holds if sources should be grouped in the result of `flowPath`. */
default predicate sourceGrouping(Node source, string sourceGroup) { none() }
/** Holds if sinks should be grouped in the result of `flowPath`. */
default predicate sinkGrouping(Node sink, string sinkGroup) { none() }
/**
* Holds if hidden nodes should be included in the data flow graph.
*
* This feature should only be used for debugging or when the data flow graph
* is not visualized (as it is in a `path-problem` query).
*/
default predicate includeHiddenNodes() { none() }
}
/** An input configuration for data flow using flow state. */
signature module StateConfigSig {
bindingset[this]
class FlowState;
/**
* Holds if `source` is a relevant data flow source with the given initial
* `state`.
*/
predicate isSource(Node source, FlowState state);
/**
* Holds if `sink` is a relevant data flow sink accepting `state`.
*/
predicate isSink(Node sink, FlowState state);
/**
* Holds if data flow through `node` is prohibited. This completely removes
* `node` from the data flow graph.
*/
default predicate isBarrier(Node node) { none() }
/**
* Holds if data flow through `node` is prohibited when the flow state is
* `state`.
*/
predicate isBarrier(Node node, FlowState state);
/** Holds if data flow into `node` is prohibited. */
default predicate isBarrierIn(Node node) { none() }
/** Holds if data flow out of `node` is prohibited. */
default predicate isBarrierOut(Node node) { none() }
/**
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
*/
default predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
/**
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
* This step is only applicable in `state1` and updates the flow state to `state2`.
*/
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2);
/**
* Holds if an arbitrary number of implicit read steps of content `c` may be
* taken at `node`.
*/
default predicate allowImplicitRead(Node node, ContentSet c) { none() }
/**
* Gets the virtual dispatch branching limit when calculating field flow.
* This can be overridden to a smaller value to improve performance (a
* value of 0 disables field flow), or a larger value to get more results.
*/
default int fieldFlowBranchLimit() { result = 2 }
/**
* Gets a data flow configuration feature to add restrictions to the set of
* valid flow paths.
*
* - `FeatureHasSourceCallContext`:
* Assume that sources have some existing call context to disallow
* conflicting return-flow directly following the source.
* - `FeatureHasSinkCallContext`:
* Assume that sinks have some existing call context to disallow
* conflicting argument-to-parameter flow directly preceding the sink.
* - `FeatureEqualSourceSinkCallContext`:
* Implies both of the above and additionally ensures that the entire flow
* path preserves the call context.
*
* These features are generally not relevant for typical end-to-end data flow
* queries, but should only be used for constructing paths that need to
* somehow be pluggable in another path context.
*/
default FlowFeature getAFeature() { none() }
/** Holds if sources should be grouped in the result of `flowPath`. */
default predicate sourceGrouping(Node source, string sourceGroup) { none() }
/** Holds if sinks should be grouped in the result of `flowPath`. */
default predicate sinkGrouping(Node sink, string sinkGroup) { none() }
/**
* Holds if hidden nodes should be included in the data flow graph.
*
* This feature should only be used for debugging or when the data flow graph
* is not visualized (as it is in a `path-problem` query).
*/
default predicate includeHiddenNodes() { none() }
}
/**
* Gets the exploration limit for `partialFlow` and `partialFlowRev`
* measured in approximate number of interprocedural steps.
*/
signature int explorationLimitSig();
/**
* The output of a global data flow computation.
*/
signature module GlobalFlowSig {
/**
* A `Node` augmented with a call context (except for sinks) and an access path.
* Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated.
*/
class PathNode;
/**
* Holds if data can flow from `source` to `sink`.
*
* The corresponding paths are generated from the end-points and the graph
* included in the module `PathGraph`.
*/
predicate flowPath(PathNode source, PathNode sink);
/**
* Holds if data can flow from `source` to `sink`.
*/
predicate flow(Node source, Node sink);
/**
* Holds if data can flow from some source to `sink`.
*/
predicate flowTo(Node sink);
/**
* Holds if data can flow from some source to `sink`.
*/
predicate flowToExpr(DataFlowExpr sink);
}
/**
* Constructs a global data flow computation.
*/
module Global<ConfigSig Config> implements GlobalFlowSig {
private module C implements FullStateConfigSig {
import DefaultState<Config>
import Config
}
import Impl<C>
}
/** DEPRECATED: Use `Global` instead. */
deprecated module Make<ConfigSig Config> implements GlobalFlowSig {
import Global<Config>
}
/**
* Constructs a global data flow computation using flow state.
*/
module GlobalWithState<StateConfigSig Config> implements GlobalFlowSig {
private module C implements FullStateConfigSig {
import Config
}
import Impl<C>
}
/** DEPRECATED: Use `GlobalWithState` instead. */
deprecated module MakeWithState<StateConfigSig Config> implements GlobalFlowSig {
import GlobalWithState<Config>
}
signature class PathNodeSig {
/** Gets a textual representation of this element. */
string toString();
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
);
/** Gets the underlying `Node`. */
Node getNode();
}
signature module PathGraphSig<PathNodeSig PathNode> {
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
predicate edges(PathNode a, PathNode b);
/** Holds if `n` is a node in the graph of data flow path explanations. */
predicate nodes(PathNode n, string key, string val);
/**
* Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out);
}
/**
* Constructs a `PathGraph` from two `PathGraph`s by disjoint union.
*/
module MergePathGraph<
PathNodeSig PathNode1, PathNodeSig PathNode2, PathGraphSig<PathNode1> Graph1,
PathGraphSig<PathNode2> Graph2>
{
private newtype TPathNode =
TPathNode1(PathNode1 p) or
TPathNode2(PathNode2 p)
/** A node in a graph of path explanations that is formed by disjoint union of the two given graphs. */
class PathNode extends TPathNode {
/** Gets this as a projection on the first given `PathGraph`. */
PathNode1 asPathNode1() { this = TPathNode1(result) }
/** Gets this as a projection on the second given `PathGraph`. */
PathNode2 asPathNode2() { this = TPathNode2(result) }
/** Gets a textual representation of this element. */
string toString() {
result = this.asPathNode1().toString() or
result = this.asPathNode2().toString()
}
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this.asPathNode1().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) or
this.asPathNode2().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/** Gets the underlying `Node`. */
Node getNode() {
result = this.asPathNode1().getNode() or
result = this.asPathNode2().getNode()
}
}
/**
* Provides the query predicates needed to include a graph in a path-problem query.
*/
module PathGraph implements PathGraphSig<PathNode> {
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
query predicate edges(PathNode a, PathNode b) {
Graph1::edges(a.asPathNode1(), b.asPathNode1()) or
Graph2::edges(a.asPathNode2(), b.asPathNode2())
}
/** Holds if `n` is a node in the graph of data flow path explanations. */
query predicate nodes(PathNode n, string key, string val) {
Graph1::nodes(n.asPathNode1(), key, val) or
Graph2::nodes(n.asPathNode2(), key, val)
}
/**
* Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) {
Graph1::subpaths(arg.asPathNode1(), par.asPathNode1(), ret.asPathNode1(), out.asPathNode1()) or
Graph2::subpaths(arg.asPathNode2(), par.asPathNode2(), ret.asPathNode2(), out.asPathNode2())
}
}
}
/**
* Constructs a `PathGraph` from three `PathGraph`s by disjoint union.
*/
module MergePathGraph3<
PathNodeSig PathNode1, PathNodeSig PathNode2, PathNodeSig PathNode3,
PathGraphSig<PathNode1> Graph1, PathGraphSig<PathNode2> Graph2, PathGraphSig<PathNode3> Graph3>
{
private module MergedInner = MergePathGraph<PathNode1, PathNode2, Graph1, Graph2>;
private module Merged =
MergePathGraph<MergedInner::PathNode, PathNode3, MergedInner::PathGraph, Graph3>;
/** A node in a graph of path explanations that is formed by disjoint union of the three given graphs. */
class PathNode instanceof Merged::PathNode {
/** Gets this as a projection on the first given `PathGraph`. */
PathNode1 asPathNode1() { result = super.asPathNode1().asPathNode1() }
/** Gets this as a projection on the second given `PathGraph`. */
PathNode2 asPathNode2() { result = super.asPathNode1().asPathNode2() }
/** Gets this as a projection on the third given `PathGraph`. */
PathNode3 asPathNode3() { result = super.asPathNode2() }
/** Gets a textual representation of this element. */
string toString() { result = super.toString() }
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/** Gets the underlying `Node`. */
Node getNode() { result = super.getNode() }
}
/**
* Provides the query predicates needed to include a graph in a path-problem query.
*/
module PathGraph = Merged::PathGraph;
}

View File

@@ -5,8 +5,8 @@ private import DataFlowUtil
/**
* Gets a function that might be called by `call`.
*/
Function viableCallable(DataFlowCall call) {
result = call.(Call).getTarget()
Function viableCallable(Call call) {
result = call.getTarget()
or
// If the target of the call does not have a body in the snapshot, it might
// be because the target is just a header declaration, and the real target
@@ -58,13 +58,13 @@ private predicate functionSignature(Function f, string qualifiedName, int nparam
* Holds if the set of viable implementations that can be called by `call`
* might be improved by knowing the call context.
*/
predicate mayBenefitFromCallContext(DataFlowCall call, Function f) { none() }
predicate mayBenefitFromCallContext(Call call, Function f) { none() }
/**
* 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.
*/
Function viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) { none() }
Function viableImplInCallContext(Call call, Call ctx) { none() }
/** A parameter position represented by an integer. */
class ParameterPosition extends int {

File diff suppressed because it is too large Load Diff

View File

@@ -276,8 +276,6 @@ private module Config implements FullStateConfigSig {
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
}
predicate isSink(Node sink) { none() }
predicate isSink(Node sink, FlowState state) {
getConfig(state).isSink(sink, getState(state))
or
@@ -315,8 +313,6 @@ private module Config implements FullStateConfigSig {
any(Configuration config).allowImplicitRead(node, c)
}
predicate neverSkip(Node node) { none() }
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }

View File

@@ -276,8 +276,6 @@ private module Config implements FullStateConfigSig {
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
}
predicate isSink(Node sink) { none() }
predicate isSink(Node sink, FlowState state) {
getConfig(state).isSink(sink, getState(state))
or
@@ -315,8 +313,6 @@ private module Config implements FullStateConfigSig {
any(Configuration config).allowImplicitRead(node, c)
}
predicate neverSkip(Node node) { none() }
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }

View File

@@ -276,8 +276,6 @@ private module Config implements FullStateConfigSig {
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
}
predicate isSink(Node sink) { none() }
predicate isSink(Node sink, FlowState state) {
getConfig(state).isSink(sink, getState(state))
or
@@ -315,8 +313,6 @@ private module Config implements FullStateConfigSig {
any(Configuration config).allowImplicitRead(node, c)
}
predicate neverSkip(Node node) { none() }
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }

View File

@@ -276,8 +276,6 @@ private module Config implements FullStateConfigSig {
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
}
predicate isSink(Node sink) { none() }
predicate isSink(Node sink, FlowState state) {
getConfig(state).isSink(sink, getState(state))
or
@@ -315,8 +313,6 @@ private module Config implements FullStateConfigSig {
any(Configuration config).allowImplicitRead(node, c)
}
predicate neverSkip(Node node) { none() }
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }

View File

@@ -276,8 +276,6 @@ private module Config implements FullStateConfigSig {
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
}
predicate isSink(Node sink) { none() }
predicate isSink(Node sink, FlowState state) {
getConfig(state).isSink(sink, getState(state))
or
@@ -315,8 +313,6 @@ private module Config implements FullStateConfigSig {
any(Configuration config).allowImplicitRead(node, c)
}
predicate neverSkip(Node node) { none() }
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }

View File

@@ -1,9 +1,6 @@
/**
* Provides C++-specific definitions for use in the data flow library.
*/
private import codeql.dataflow.DataFlow
module Private {
import DataFlowPrivate
import DataFlowDispatch
@@ -12,10 +9,3 @@ module Private {
module Public {
import DataFlowUtil
}
module CppOldDataFlow implements InputSig {
import Private
import Public
Node exprNode(DataFlowExpr e) { result = Public::exprNode(e) }
}

View File

@@ -153,11 +153,10 @@ predicate jumpStep(Node n1, Node n2) { none() }
* Thus, `node2` references an object with a field `f` that contains the
* value of `node1`.
*/
predicate storeStep(Node node1, ContentSet f, Node node2) {
predicate storeStep(Node node1, Content f, PostUpdateNode node2) {
exists(ClassAggregateLiteral aggr, Field field |
// The following lines requires `node2` to be both an `ExprNode` and a
// The following line requires `node2` to be both an `ExprNode` and a
// `PostUpdateNode`, which means it must be an `ObjectInitializerNode`.
node2 instanceof PostUpdateNode and
node2.asExpr() = aggr and
f.(FieldContent).getField() = field and
aggr.getAFieldExpr(field) = node1.asExpr()
@@ -168,13 +167,12 @@ predicate storeStep(Node node1, ContentSet f, Node node2) {
node1.asExpr() = a and
a.getLValue() = fa
) and
node2.(PostUpdateNode).getPreUpdateNode().asExpr() = fa.getQualifier() and
node2.getPreUpdateNode().asExpr() = fa.getQualifier() and
f.(FieldContent).getField() = fa.getTarget()
)
or
exists(ConstructorFieldInit cfi |
node2.(PostUpdateNode).getPreUpdateNode().(PreConstructorInitThis).getConstructorFieldInit() =
cfi and
node2.getPreUpdateNode().(PreConstructorInitThis).getConstructorFieldInit() = cfi and
f.(FieldContent).getField() = cfi.getTarget() and
node1.asExpr() = cfi.getExpr()
)
@@ -185,7 +183,7 @@ predicate storeStep(Node node1, ContentSet f, Node node2) {
* Thus, `node1` references an object with a field `f` whose value ends up in
* `node2`.
*/
predicate readStep(Node node1, ContentSet f, Node node2) {
predicate readStep(Node node1, Content f, Node node2) {
exists(FieldAccess fr |
node1.asExpr() = fr.getQualifier() and
fr.getTarget() = f.(FieldContent).getField() and
@@ -197,7 +195,7 @@ predicate readStep(Node node1, ContentSet f, Node node2) {
/**
* Holds if values stored inside content `c` are cleared at node `n`.
*/
predicate clearsContent(Node n, ContentSet c) {
predicate clearsContent(Node n, Content c) {
none() // stub implementation
}
@@ -237,6 +235,12 @@ class CastNode extends Node {
CastNode() { none() } // stub implementation
}
/**
* Holds if `n` should never be skipped over in the `PathGraph` and in path
* explanations.
*/
predicate neverSkipInPathGraph(Node n) { none() }
class DataFlowCallable = Function;
class DataFlowExpr = Expr;
@@ -261,6 +265,8 @@ class DataFlowCall extends Expr instanceof Call {
predicate isUnreachableInCall(Node n, DataFlowCall call) { none() } // stub implementation
int accessPathLimit() { result = 5 }
/**
* Holds if access paths with `c` at their head always should be tracked at high
* precision. This disables adaptive access path precision for such access paths.

View File

@@ -24,7 +24,6 @@ private module AddTaintDefaults<DataFlowInternal::FullStateConfigSig Config> imp
Config::allowImplicitRead(node, c)
or
(
Config::isSink(node) or
Config::isSink(node, _) or
Config::isAdditionalFlowStep(node, _) or
Config::isAdditionalFlowStep(node, _, _, _)

View File

@@ -26,8 +26,6 @@ import cpp
* global (inter-procedural) data flow analyses.
*/
module DataFlow {
private import semmle.code.cpp.ir.dataflow.internal.DataFlowImplSpecific
private import codeql.dataflow.DataFlow
import DataFlowMake<CppDataFlow>
import semmle.code.cpp.ir.dataflow.internal.DataFlow
import semmle.code.cpp.ir.dataflow.internal.DataFlowImpl1
}

View File

@@ -368,11 +368,6 @@ class FunctionAccess extends Access, @routineexpr {
/** Gets the accessed function. */
override Function getTarget() { funbind(underlyingElement(this), unresolveElement(result)) }
/**
* Gets the expression generating the function being accessed.
*/
Expr getQualifier() { this.getChild(-1) = result }
/** Gets a textual representation of this function access. */
override string toString() {
if exists(this.getTarget())

View File

@@ -152,19 +152,7 @@ class Expr extends StmtParent, @expr {
else result = this.getValue()
}
/**
* Holds if this expression has a value that can be determined at compile time.
*
* An expression has a value that can be determined at compile time when:
* - it is a compile-time constant, e.g., a literal value or the result of a constexpr
* compile-time constant;
* - it is an address of a (member) function, an address of a constexpr variable
* initialized to a constant address, or an address of an lvalue, or any of the
* previous with a constant value added to or subtracted from the address;
* - it is a reference to a (member) function, a reference to a constexpr variable
* initialized to a constant address, or a reference to an lvalue;
* - it is a non-template parameter of a uninstantiated template.
*/
/** Holds if this expression has a value that can be determined at compile time. */
cached
predicate isConstant() {
valuebind(_, underlyingElement(this))

View File

@@ -4,8 +4,8 @@
* This file contains the actual implementation of `PrintIR.ql`. For test cases and very small
* databases, `PrintIR.ql` can be run directly to dump the IR for the entire database. For most
* uses, however, it is better to write a query that imports `PrintIR.qll`, extends
* `PrintIRConfiguration`, and overrides `shouldPrintDeclaration()` to select a subset of declarations
* to dump.
* `PrintIRConfiguration`, and overrides `shouldPrintFunction()` to select a subset of functions to
* dump.
*/
import implementation.aliased_ssa.PrintIR

View File

@@ -22,8 +22,6 @@
import cpp
module DataFlow {
private import semmle.code.cpp.ir.dataflow.internal.DataFlowImplSpecific
private import codeql.dataflow.DataFlow
import DataFlowMake<CppDataFlow>
import semmle.code.cpp.ir.dataflow.internal.DataFlow
import semmle.code.cpp.ir.dataflow.internal.DataFlowImpl1
}

View File

@@ -0,0 +1,412 @@
/**
* Provides an implementation of global (interprocedural) data flow. This file
* re-exports the local (intraprocedural) data flow analysis from
* `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed
* through the `Global` and `GlobalWithState` modules.
*/
private import DataFlowImplCommon
private import DataFlowImplSpecific::Private
import DataFlowImplSpecific::Public
import DataFlowImplCommonPublic
private import DataFlowImpl
/** An input configuration for data flow. */
signature module ConfigSig {
/**
* Holds if `source` is a relevant data flow source.
*/
predicate isSource(Node source);
/**
* Holds if `sink` is a relevant data flow sink.
*/
predicate isSink(Node sink);
/**
* Holds if data flow through `node` is prohibited. This completely removes
* `node` from the data flow graph.
*/
default predicate isBarrier(Node node) { none() }
/** Holds if data flow into `node` is prohibited. */
default predicate isBarrierIn(Node node) { none() }
/** Holds if data flow out of `node` is prohibited. */
default predicate isBarrierOut(Node node) { none() }
/**
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
*/
default predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
/**
* Holds if an arbitrary number of implicit read steps of content `c` may be
* taken at `node`.
*/
default predicate allowImplicitRead(Node node, ContentSet c) { none() }
/**
* Gets the virtual dispatch branching limit when calculating field flow.
* This can be overridden to a smaller value to improve performance (a
* value of 0 disables field flow), or a larger value to get more results.
*/
default int fieldFlowBranchLimit() { result = 2 }
/**
* Gets a data flow configuration feature to add restrictions to the set of
* valid flow paths.
*
* - `FeatureHasSourceCallContext`:
* Assume that sources have some existing call context to disallow
* conflicting return-flow directly following the source.
* - `FeatureHasSinkCallContext`:
* Assume that sinks have some existing call context to disallow
* conflicting argument-to-parameter flow directly preceding the sink.
* - `FeatureEqualSourceSinkCallContext`:
* Implies both of the above and additionally ensures that the entire flow
* path preserves the call context.
*
* These features are generally not relevant for typical end-to-end data flow
* queries, but should only be used for constructing paths that need to
* somehow be pluggable in another path context.
*/
default FlowFeature getAFeature() { none() }
/** Holds if sources should be grouped in the result of `flowPath`. */
default predicate sourceGrouping(Node source, string sourceGroup) { none() }
/** Holds if sinks should be grouped in the result of `flowPath`. */
default predicate sinkGrouping(Node sink, string sinkGroup) { none() }
/**
* Holds if hidden nodes should be included in the data flow graph.
*
* This feature should only be used for debugging or when the data flow graph
* is not visualized (as it is in a `path-problem` query).
*/
default predicate includeHiddenNodes() { none() }
}
/** An input configuration for data flow using flow state. */
signature module StateConfigSig {
bindingset[this]
class FlowState;
/**
* Holds if `source` is a relevant data flow source with the given initial
* `state`.
*/
predicate isSource(Node source, FlowState state);
/**
* Holds if `sink` is a relevant data flow sink accepting `state`.
*/
predicate isSink(Node sink, FlowState state);
/**
* Holds if data flow through `node` is prohibited. This completely removes
* `node` from the data flow graph.
*/
default predicate isBarrier(Node node) { none() }
/**
* Holds if data flow through `node` is prohibited when the flow state is
* `state`.
*/
predicate isBarrier(Node node, FlowState state);
/** Holds if data flow into `node` is prohibited. */
default predicate isBarrierIn(Node node) { none() }
/** Holds if data flow out of `node` is prohibited. */
default predicate isBarrierOut(Node node) { none() }
/**
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
*/
default predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
/**
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
* This step is only applicable in `state1` and updates the flow state to `state2`.
*/
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2);
/**
* Holds if an arbitrary number of implicit read steps of content `c` may be
* taken at `node`.
*/
default predicate allowImplicitRead(Node node, ContentSet c) { none() }
/**
* Gets the virtual dispatch branching limit when calculating field flow.
* This can be overridden to a smaller value to improve performance (a
* value of 0 disables field flow), or a larger value to get more results.
*/
default int fieldFlowBranchLimit() { result = 2 }
/**
* Gets a data flow configuration feature to add restrictions to the set of
* valid flow paths.
*
* - `FeatureHasSourceCallContext`:
* Assume that sources have some existing call context to disallow
* conflicting return-flow directly following the source.
* - `FeatureHasSinkCallContext`:
* Assume that sinks have some existing call context to disallow
* conflicting argument-to-parameter flow directly preceding the sink.
* - `FeatureEqualSourceSinkCallContext`:
* Implies both of the above and additionally ensures that the entire flow
* path preserves the call context.
*
* These features are generally not relevant for typical end-to-end data flow
* queries, but should only be used for constructing paths that need to
* somehow be pluggable in another path context.
*/
default FlowFeature getAFeature() { none() }
/** Holds if sources should be grouped in the result of `flowPath`. */
default predicate sourceGrouping(Node source, string sourceGroup) { none() }
/** Holds if sinks should be grouped in the result of `flowPath`. */
default predicate sinkGrouping(Node sink, string sinkGroup) { none() }
/**
* Holds if hidden nodes should be included in the data flow graph.
*
* This feature should only be used for debugging or when the data flow graph
* is not visualized (as it is in a `path-problem` query).
*/
default predicate includeHiddenNodes() { none() }
}
/**
* Gets the exploration limit for `partialFlow` and `partialFlowRev`
* measured in approximate number of interprocedural steps.
*/
signature int explorationLimitSig();
/**
* The output of a global data flow computation.
*/
signature module GlobalFlowSig {
/**
* A `Node` augmented with a call context (except for sinks) and an access path.
* Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated.
*/
class PathNode;
/**
* Holds if data can flow from `source` to `sink`.
*
* The corresponding paths are generated from the end-points and the graph
* included in the module `PathGraph`.
*/
predicate flowPath(PathNode source, PathNode sink);
/**
* Holds if data can flow from `source` to `sink`.
*/
predicate flow(Node source, Node sink);
/**
* Holds if data can flow from some source to `sink`.
*/
predicate flowTo(Node sink);
/**
* Holds if data can flow from some source to `sink`.
*/
predicate flowToExpr(DataFlowExpr sink);
}
/**
* Constructs a global data flow computation.
*/
module Global<ConfigSig Config> implements GlobalFlowSig {
private module C implements FullStateConfigSig {
import DefaultState<Config>
import Config
}
import Impl<C>
}
/** DEPRECATED: Use `Global` instead. */
deprecated module Make<ConfigSig Config> implements GlobalFlowSig {
import Global<Config>
}
/**
* Constructs a global data flow computation using flow state.
*/
module GlobalWithState<StateConfigSig Config> implements GlobalFlowSig {
private module C implements FullStateConfigSig {
import Config
}
import Impl<C>
}
/** DEPRECATED: Use `GlobalWithState` instead. */
deprecated module MakeWithState<StateConfigSig Config> implements GlobalFlowSig {
import GlobalWithState<Config>
}
signature class PathNodeSig {
/** Gets a textual representation of this element. */
string toString();
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
);
/** Gets the underlying `Node`. */
Node getNode();
}
signature module PathGraphSig<PathNodeSig PathNode> {
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
predicate edges(PathNode a, PathNode b);
/** Holds if `n` is a node in the graph of data flow path explanations. */
predicate nodes(PathNode n, string key, string val);
/**
* Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out);
}
/**
* Constructs a `PathGraph` from two `PathGraph`s by disjoint union.
*/
module MergePathGraph<
PathNodeSig PathNode1, PathNodeSig PathNode2, PathGraphSig<PathNode1> Graph1,
PathGraphSig<PathNode2> Graph2>
{
private newtype TPathNode =
TPathNode1(PathNode1 p) or
TPathNode2(PathNode2 p)
/** A node in a graph of path explanations that is formed by disjoint union of the two given graphs. */
class PathNode extends TPathNode {
/** Gets this as a projection on the first given `PathGraph`. */
PathNode1 asPathNode1() { this = TPathNode1(result) }
/** Gets this as a projection on the second given `PathGraph`. */
PathNode2 asPathNode2() { this = TPathNode2(result) }
/** Gets a textual representation of this element. */
string toString() {
result = this.asPathNode1().toString() or
result = this.asPathNode2().toString()
}
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this.asPathNode1().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) or
this.asPathNode2().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/** Gets the underlying `Node`. */
Node getNode() {
result = this.asPathNode1().getNode() or
result = this.asPathNode2().getNode()
}
}
/**
* Provides the query predicates needed to include a graph in a path-problem query.
*/
module PathGraph implements PathGraphSig<PathNode> {
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
query predicate edges(PathNode a, PathNode b) {
Graph1::edges(a.asPathNode1(), b.asPathNode1()) or
Graph2::edges(a.asPathNode2(), b.asPathNode2())
}
/** Holds if `n` is a node in the graph of data flow path explanations. */
query predicate nodes(PathNode n, string key, string val) {
Graph1::nodes(n.asPathNode1(), key, val) or
Graph2::nodes(n.asPathNode2(), key, val)
}
/**
* Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
* `ret -> out` is summarized as the edge `arg -> out`.
*/
query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) {
Graph1::subpaths(arg.asPathNode1(), par.asPathNode1(), ret.asPathNode1(), out.asPathNode1()) or
Graph2::subpaths(arg.asPathNode2(), par.asPathNode2(), ret.asPathNode2(), out.asPathNode2())
}
}
}
/**
* Constructs a `PathGraph` from three `PathGraph`s by disjoint union.
*/
module MergePathGraph3<
PathNodeSig PathNode1, PathNodeSig PathNode2, PathNodeSig PathNode3,
PathGraphSig<PathNode1> Graph1, PathGraphSig<PathNode2> Graph2, PathGraphSig<PathNode3> Graph3>
{
private module MergedInner = MergePathGraph<PathNode1, PathNode2, Graph1, Graph2>;
private module Merged =
MergePathGraph<MergedInner::PathNode, PathNode3, MergedInner::PathGraph, Graph3>;
/** A node in a graph of path explanations that is formed by disjoint union of the three given graphs. */
class PathNode instanceof Merged::PathNode {
/** Gets this as a projection on the first given `PathGraph`. */
PathNode1 asPathNode1() { result = super.asPathNode1().asPathNode1() }
/** Gets this as a projection on the second given `PathGraph`. */
PathNode2 asPathNode2() { result = super.asPathNode1().asPathNode2() }
/** Gets this as a projection on the third given `PathGraph`. */
PathNode3 asPathNode3() { result = super.asPathNode2() }
/** Gets a textual representation of this element. */
string toString() { result = super.toString() }
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/** Gets the underlying `Node`. */
Node getNode() { result = super.getNode() }
}
/**
* Provides the query predicates needed to include a graph in a path-problem query.
*/
module PathGraph = Merged::PathGraph;
}

View File

@@ -9,7 +9,7 @@ private import DataFlowImplCommon as DataFlowImplCommon
* Gets a function that might be called by `call`.
*/
cached
DataFlowCallable viableCallable(DataFlowCall call) {
Function viableCallable(CallInstruction call) {
DataFlowImplCommon::forceCachingInSameStage() and
result = call.getStaticCallTarget()
or
@@ -235,7 +235,7 @@ private predicate functionSignature(Function f, string qualifiedName, int nparam
* Holds if the set of viable implementations that can be called by `call`
* might be improved by knowing the call context.
*/
predicate mayBenefitFromCallContext(DataFlowCall call, DataFlowCallable f) {
predicate mayBenefitFromCallContext(CallInstruction call, Function f) {
mayBenefitFromCallContext(call, f, _)
}
@@ -259,7 +259,7 @@ private predicate mayBenefitFromCallContext(
* 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.
*/
DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
Function viableImplInCallContext(CallInstruction call, CallInstruction ctx) {
result = viableCallable(call) and
exists(int i, Function f |
mayBenefitFromCallContext(pragma[only_bind_into](call), f, i) and

View File

@@ -276,8 +276,6 @@ private module Config implements FullStateConfigSig {
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
}
predicate isSink(Node sink) { none() }
predicate isSink(Node sink, FlowState state) {
getConfig(state).isSink(sink, getState(state))
or
@@ -315,8 +313,6 @@ private module Config implements FullStateConfigSig {
any(Configuration config).allowImplicitRead(node, c)
}
predicate neverSkip(Node node) { none() }
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }

View File

@@ -276,8 +276,6 @@ private module Config implements FullStateConfigSig {
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
}
predicate isSink(Node sink) { none() }
predicate isSink(Node sink, FlowState state) {
getConfig(state).isSink(sink, getState(state))
or
@@ -315,8 +313,6 @@ private module Config implements FullStateConfigSig {
any(Configuration config).allowImplicitRead(node, c)
}
predicate neverSkip(Node node) { none() }
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }

View File

@@ -276,8 +276,6 @@ private module Config implements FullStateConfigSig {
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
}
predicate isSink(Node sink) { none() }
predicate isSink(Node sink, FlowState state) {
getConfig(state).isSink(sink, getState(state))
or
@@ -315,8 +313,6 @@ private module Config implements FullStateConfigSig {
any(Configuration config).allowImplicitRead(node, c)
}
predicate neverSkip(Node node) { none() }
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }

View File

@@ -276,8 +276,6 @@ private module Config implements FullStateConfigSig {
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
}
predicate isSink(Node sink) { none() }
predicate isSink(Node sink, FlowState state) {
getConfig(state).isSink(sink, getState(state))
or
@@ -315,8 +313,6 @@ private module Config implements FullStateConfigSig {
any(Configuration config).allowImplicitRead(node, c)
}
predicate neverSkip(Node node) { none() }
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }

View File

@@ -1,9 +1,6 @@
/**
* Provides IR-specific definitions for use in the data flow library.
*/
private import codeql.dataflow.DataFlow
module Private {
import DataFlowPrivate
import DataFlowDispatch
@@ -12,10 +9,3 @@ module Private {
module Public {
import DataFlowUtil
}
module CppDataFlow implements InputSig {
import Private
import Public
Node exprNode(DataFlowExpr e) { result = Public::exprNode(e) }
}

View File

@@ -321,11 +321,9 @@ private class PrimaryArgumentNode extends ArgumentNode, OperandNode {
private class SideEffectArgumentNode extends ArgumentNode, SideEffectOperandNode {
override predicate argumentOf(DataFlowCall dfCall, ArgumentPosition pos) {
exists(int indirectionIndex |
pos = TIndirectionPosition(argumentIndex, pragma[only_bind_into](indirectionIndex)) and
this.getCallInstruction() = dfCall and
super.hasAddressOperandAndIndirectionIndex(_, pragma[only_bind_into](indirectionIndex))
)
this.getCallInstruction() = dfCall and
pos.(IndirectionPosition).getArgumentIndex() = this.getArgumentIndex() and
super.hasAddressOperandAndIndirectionIndex(_, pos.(IndirectionPosition).getIndirectionIndex())
}
}
@@ -653,16 +651,13 @@ predicate jumpStep(Node n1, Node n2) {
* Holds if data can flow from `node1` to `node2` via an assignment to `f`.
* Thus, `node2` references an object with a field `f` that contains the
* value of `node1`.
*
* The boolean `certain` is true if the destination address does not involve
* any pointer arithmetic, and false otherwise.
*/
predicate storeStepImpl(Node node1, Content c, PostFieldUpdateNode node2, boolean certain) {
predicate storeStep(Node node1, Content c, PostFieldUpdateNode node2) {
exists(int indirectionIndex1, int numberOfLoads, StoreInstruction store |
nodeHasInstruction(node1, store, pragma[only_bind_into](indirectionIndex1)) and
node2.getIndirectionIndex() = 1 and
numberOfLoadsFromOperand(node2.getFieldAddress(), store.getDestinationAddressOperand(),
numberOfLoads, certain)
numberOfLoads)
|
exists(FieldContent fc | fc = c |
fc.getField() = node2.getUpdatedField() and
@@ -676,32 +671,21 @@ predicate storeStepImpl(Node node1, Content c, PostFieldUpdateNode node2, boolea
)
}
/**
* Holds if data can flow from `node1` to `node2` via an assignment to `f`.
* Thus, `node2` references an object with a field `f` that contains the
* value of `node1`.
*/
predicate storeStep(Node node1, ContentSet c, Node node2) { storeStepImpl(node1, c, node2, _) }
/**
* Holds if `operandFrom` flows to `operandTo` using a sequence of conversion-like
* operations and exactly `n` `LoadInstruction` operations.
*/
private predicate numberOfLoadsFromOperandRec(
Operand operandFrom, Operand operandTo, int ind, boolean certain
) {
private predicate numberOfLoadsFromOperandRec(Operand operandFrom, Operand operandTo, int ind) {
exists(Instruction load | Ssa::isDereference(load, operandFrom) |
operandTo = operandFrom and ind = 0 and certain = true
operandTo = operandFrom and ind = 0
or
numberOfLoadsFromOperand(load.getAUse(), operandTo, ind - 1, certain)
numberOfLoadsFromOperand(load.getAUse(), operandTo, ind - 1)
)
or
exists(Operand op, Instruction instr, boolean isPointerArith, boolean certain0 |
exists(Operand op, Instruction instr |
instr = op.getDef() and
conversionFlow(operandFrom, instr, isPointerArith, _) and
numberOfLoadsFromOperand(op, operandTo, ind, certain0)
|
if isPointerArith = true then certain = false else certain = certain0
conversionFlow(operandFrom, instr, _, _) and
numberOfLoadsFromOperand(op, operandTo, ind)
)
}
@@ -709,16 +693,13 @@ private predicate numberOfLoadsFromOperandRec(
* Holds if `operandFrom` flows to `operandTo` using a sequence of conversion-like
* operations and exactly `n` `LoadInstruction` operations.
*/
private predicate numberOfLoadsFromOperand(
Operand operandFrom, Operand operandTo, int n, boolean certain
) {
numberOfLoadsFromOperandRec(operandFrom, operandTo, n, certain)
private predicate numberOfLoadsFromOperand(Operand operandFrom, Operand operandTo, int n) {
numberOfLoadsFromOperandRec(operandFrom, operandTo, n)
or
not Ssa::isDereference(_, operandFrom) and
not conversionFlow(operandFrom, _, _, _) and
operandFrom = operandTo and
n = 0 and
certain = true
n = 0
}
// Needed to join on both an operand and an index at the same time.
@@ -742,13 +723,13 @@ predicate nodeHasInstruction(Node node, Instruction instr, int indirectionIndex)
* Thus, `node1` references an object with a field `f` whose value ends up in
* `node2`.
*/
predicate readStep(Node node1, ContentSet c, Node node2) {
predicate readStep(Node node1, Content c, Node node2) {
exists(FieldAddress fa1, Operand operand, int numberOfLoads, int indirectionIndex2 |
nodeHasOperand(node2, operand, indirectionIndex2) and
// The `1` here matches the `node2.getIndirectionIndex() = 1` conjunct
// in `storeStep`.
nodeHasOperand(node1, fa1.getObjectAddressOperand(), 1) and
numberOfLoadsFromOperand(fa1, operand, numberOfLoads, _)
numberOfLoadsFromOperand(fa1, operand, numberOfLoads)
|
exists(FieldContent fc | fc = c |
fc.getField() = fa1.getField() and
@@ -765,34 +746,8 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
/**
* Holds if values stored inside content `c` are cleared at node `n`.
*/
predicate clearsContent(Node n, ContentSet c) {
n =
any(PostUpdateNode pun, Content d | d.impliesClearOf(c) and storeStepImpl(_, d, pun, true) | pun)
.getPreUpdateNode() and
(
// The crement operations and pointer addition and subtraction self-assign. We do not
// want to clear the contents if it is indirectly pointed at by any of these operations,
// as part of the contents might still be accessible afterwards. If there is no such
// indirection clearing the contents is safe.
not exists(Operand op, Cpp::Operation p |
n.(IndirectOperand).hasOperandAndIndirectionIndex(op, _) and
(
p instanceof Cpp::AssignPointerAddExpr or
p instanceof Cpp::AssignPointerSubExpr or
p instanceof Cpp::CrementOperation
)
|
p.getAnOperand() = op.getUse().getAst()
)
or
forex(PostUpdateNode pun, Content d |
pragma[only_bind_into](d).impliesClearOf(pragma[only_bind_into](c)) and
storeStepImpl(_, d, pun, true) and
pun.getPreUpdateNode() = n
|
c.(Content).getIndirectionIndex() = d.getIndirectionIndex()
)
)
predicate clearsContent(Node n, Content c) {
none() // stub implementation
}
/**
@@ -831,6 +786,12 @@ class CastNode extends Node {
CastNode() { none() } // stub implementation
}
/**
* Holds if `n` should never be skipped over in the `PathGraph` and in path
* explanations.
*/
predicate neverSkipInPathGraph(Node n) { none() }
/**
* A function that may contain code or a variable that may contain itself. When
* flow crosses from one _enclosing callable_ to another, the interprocedural
@@ -845,76 +806,12 @@ class DataFlowType = Type;
/** A function call relevant for data flow. */
class DataFlowCall extends CallInstruction {
DataFlowCallable getEnclosingCallable() { result = this.getEnclosingFunction() }
Function getEnclosingCallable() { result = this.getEnclosingFunction() }
}
module IsUnreachableInCall {
private import semmle.code.cpp.ir.ValueNumbering
private import semmle.code.cpp.controlflow.IRGuards as G
predicate isUnreachableInCall(Node n, DataFlowCall call) { none() } // stub implementation
private class ConstantIntegralTypeArgumentNode extends PrimaryArgumentNode {
int value;
ConstantIntegralTypeArgumentNode() {
value = op.getDef().(IntegerConstantInstruction).getValue().toInt()
}
int getValue() { result = value }
}
pragma[nomagic]
private predicate ensuresEq(Operand left, Operand right, int k, IRBlock block, boolean areEqual) {
any(G::IRGuardCondition guard).ensuresEq(left, right, k, block, areEqual)
}
pragma[nomagic]
private predicate ensuresLt(Operand left, Operand right, int k, IRBlock block, boolean areEqual) {
any(G::IRGuardCondition guard).ensuresLt(left, right, k, block, areEqual)
}
predicate isUnreachableInCall(Node n, DataFlowCall call) {
exists(
DirectParameterNode paramNode, ConstantIntegralTypeArgumentNode arg,
IntegerConstantInstruction constant, int k, Operand left, Operand right, IRBlock block
|
// arg flows into `paramNode`
DataFlowImplCommon::viableParamArg(call, paramNode, arg) and
left = constant.getAUse() and
right = valueNumber(paramNode.getInstruction()).getAUse() and
block = n.getBasicBlock()
|
// and there's a guard condition which ensures that the result of `left == right + k` is `areEqual`
exists(boolean areEqual |
ensuresEq(pragma[only_bind_into](left), pragma[only_bind_into](right),
pragma[only_bind_into](k), pragma[only_bind_into](block), areEqual)
|
// this block ensures that left = right + k, but it holds that `left != right + k`
areEqual = true and
constant.getValue().toInt() != arg.getValue() + k
or
// this block ensures that or `left != right + k`, but it holds that `left = right + k`
areEqual = false and
constant.getValue().toInt() = arg.getValue() + k
)
or
// or there's a guard condition which ensures that the result of `left < right + k` is `isLessThan`
exists(boolean isLessThan |
ensuresLt(pragma[only_bind_into](left), pragma[only_bind_into](right),
pragma[only_bind_into](k), pragma[only_bind_into](block), isLessThan)
|
isLessThan = true and
// this block ensures that `left < right + k`, but it holds that `left >= right + k`
constant.getValue().toInt() >= arg.getValue() + k
or
// this block ensures that `left >= right + k`, but it holds that `left < right + k`
isLessThan = false and
constant.getValue().toInt() < arg.getValue() + k
)
)
}
}
import IsUnreachableInCall
int accessPathLimit() { result = 5 }
/**
* Holds if access paths with `c` at their head always should be tracked at high

View File

@@ -1832,20 +1832,6 @@ class Content extends TContent {
predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
path = "" and sl = 0 and sc = 0 and el = 0 and ec = 0
}
/** Gets the indirection index of this `Content`. */
abstract int getIndirectionIndex();
/**
* INTERNAL: Do not use.
*
* Holds if a write to this `Content` implies that `c` is
* also cleared.
*
* For example, a write to a field `f` implies that any content of
* the form `*f` is also cleared.
*/
abstract predicate impliesClearOf(Content c);
}
/** A reference through a non-union instance field. */
@@ -1863,21 +1849,10 @@ class FieldContent extends Content, TFieldContent {
Field getField() { result = f }
/** Gets the indirection index of this `FieldContent`. */
pragma[inline]
override int getIndirectionIndex() {
int getIndirectionIndex() {
pragma[only_bind_into](result) = pragma[only_bind_out](indirectionIndex)
}
override predicate impliesClearOf(Content c) {
exists(FieldContent fc |
fc = c and
fc.getField() = f and
// If `this` is `f` then `c` is cleared if it's of the
// form `*f`, `**f`, etc.
fc.getIndirectionIndex() >= indirectionIndex
)
}
}
/** A reference through an instance field of a union. */
@@ -1902,21 +1877,9 @@ class UnionContent extends Content, TUnionContent {
/** Gets the indirection index of this `UnionContent`. */
pragma[inline]
override int getIndirectionIndex() {
int getIndirectionIndex() {
pragma[only_bind_into](result) = pragma[only_bind_out](indirectionIndex)
}
override predicate impliesClearOf(Content c) {
exists(UnionContent uc |
uc = c and
uc.getUnion() = u and
// If `this` is `u` then `c` is cleared if it's of the
// form `*u`, `**u`, etc. (and we ignore `bytes` because
// we know the entire union is overwritten because it's a
// union).
uc.getIndirectionIndex() >= indirectionIndex
)
}
}
/**

View File

@@ -448,8 +448,6 @@ module TaintedWithPath {
}
predicate isBarrierIn(DataFlow::Node node) { nodeIsBarrierIn(node) }
predicate neverSkip(Node node) { none() }
}
private module AdjustedFlow = TaintTracking::Global<AdjustedConfig>;

View File

@@ -192,13 +192,13 @@ module ProductFlow {
* Holds if data flow through `node` is prohibited through the first projection of the product
* dataflow graph when the flow state is `state`.
*/
default predicate isBarrier1(DataFlow::Node node, FlowState1 state) { none() }
predicate isBarrier1(DataFlow::Node node, FlowState1 state);
/**
* Holds if data flow through `node` is prohibited through the second projection of the product
* dataflow graph when the flow state is `state`.
*/
default predicate isBarrier2(DataFlow::Node node, FlowState2 state) { none() }
predicate isBarrier2(DataFlow::Node node, FlowState2 state);
/**
* Holds if data flow through `node` is prohibited through the first projection of the product
@@ -237,11 +237,9 @@ module ProductFlow {
*
* This step is only applicable in `state1` and updates the flow state to `state2`.
*/
default predicate isAdditionalFlowStep1(
predicate isAdditionalFlowStep1(
DataFlow::Node node1, FlowState1 state1, DataFlow::Node node2, FlowState1 state2
) {
none()
}
);
/**
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps in
@@ -255,11 +253,9 @@ module ProductFlow {
*
* This step is only applicable in `state1` and updates the flow state to `state2`.
*/
default predicate isAdditionalFlowStep2(
predicate isAdditionalFlowStep2(
DataFlow::Node node1, FlowState2 state1, DataFlow::Node node2, FlowState2 state2
) {
none()
}
);
/**
* Holds if data flow into `node` is prohibited in the first projection of the product
@@ -297,22 +293,6 @@ module ProductFlow {
reachable(source1, source2, sink1, sink2)
}
/** Holds if data can flow from `(source1, source2)` to `(sink1, sink2)`. */
predicate flow(
DataFlow::Node source1, DataFlow::Node source2, DataFlow::Node sink1, DataFlow::Node sink2
) {
exists(
Flow1::PathNode pSource1, Flow2::PathNode pSource2, Flow1::PathNode pSink1,
Flow2::PathNode pSink2
|
pSource1.getNode() = source1 and
pSource2.getNode() = source2 and
pSink1.getNode() = sink1 and
pSink2.getNode() = sink2 and
flowPath(pSource1, pSource2, pSink1, pSink2)
)
}
private module Config1 implements DataFlow::StateConfigSig {
class FlowState = FlowState1;
@@ -379,6 +359,7 @@ module ProductFlow {
Config::isSinkPair(node1.getNode(), node1.getState(), node2.getNode(), node2.getState())
}
pragma[assume_small_delta]
pragma[nomagic]
private predicate fwdReachableInterprocEntry(Flow1::PathNode node1, Flow2::PathNode node2) {
isSourcePair(node1, node2)
@@ -415,6 +396,7 @@ module ProductFlow {
fwdIsSuccessorExit(pragma[only_bind_into](mid1), pragma[only_bind_into](mid2), succ1, succ2)
}
pragma[assume_small_delta]
private predicate fwdIsSuccessor(
Flow1::PathNode pred1, Flow2::PathNode pred2, Flow1::PathNode succ1, Flow2::PathNode succ2
) {
@@ -424,6 +406,7 @@ module ProductFlow {
)
}
pragma[assume_small_delta]
pragma[nomagic]
private predicate revReachableInterprocEntry(Flow1::PathNode node1, Flow2::PathNode node2) {
fwdReachableInterprocEntry(node1, node2) and

View File

@@ -588,6 +588,7 @@ private module Cached {
)
}
pragma[assume_small_delta]
private predicate convertsIntoArgumentRev(Instruction instr) {
convertsIntoArgumentFwd(instr) and
(

View File

@@ -24,7 +24,6 @@ private module AddTaintDefaults<DataFlowInternal::FullStateConfigSig Config> imp
Config::allowImplicitRead(node, c)
or
(
Config::isSink(node) or
Config::isSink(node, _) or
Config::isAdditionalFlowStep(node, _) or
Config::isAdditionalFlowStep(node, _, _, _)

View File

@@ -4,8 +4,8 @@
* This file contains the actual implementation of `PrintIR.ql`. For test cases and very small
* databases, `PrintIR.ql` can be run directly to dump the IR for the entire database. For most
* uses, however, it is better to write a query that imports `PrintIR.qll`, extends
* `PrintIRConfiguration`, and overrides `shouldPrintDeclaration()` to select a subset of declarations
* to dump.
* `PrintIRConfiguration`, and overrides `shouldPrintFunction()` to select a subset of functions to
* dump.
*/
private import internal.IRInternal
@@ -16,7 +16,7 @@ import Imports::IRConfiguration
private newtype TPrintIRConfiguration = MkPrintIRConfiguration()
/**
* The query can extend this class to control which declarations are printed.
* The query can extend this class to control which functions are printed.
*/
class PrintIRConfiguration extends TPrintIRConfiguration {
/** Gets a textual representation of this configuration. */
@@ -24,9 +24,9 @@ class PrintIRConfiguration extends TPrintIRConfiguration {
/**
* Holds if the IR for `func` should be printed. By default, holds for all
* functions, global and namespace variables, and static local variables.
* functions.
*/
predicate shouldPrintDeclaration(Language::Declaration decl) { any() }
predicate shouldPrintFunction(Language::Declaration decl) { any() }
}
/**
@@ -34,12 +34,12 @@ class PrintIRConfiguration extends TPrintIRConfiguration {
*/
private class FilteredIRConfiguration extends IRConfiguration {
override predicate shouldEvaluateDebugStringsForFunction(Language::Declaration func) {
shouldPrintDeclaration(func)
shouldPrintFunction(func)
}
}
private predicate shouldPrintDeclaration(Language::Declaration decl) {
exists(PrintIRConfiguration config | config.shouldPrintDeclaration(decl))
private predicate shouldPrintFunction(Language::Declaration decl) {
exists(PrintIRConfiguration config | config.shouldPrintFunction(decl))
}
private predicate shouldPrintInstruction(Instruction i) {
@@ -90,10 +90,10 @@ private string getOperandPropertyString(Operand operand) {
}
private newtype TPrintableIRNode =
TPrintableIRFunction(IRFunction irFunc) { shouldPrintDeclaration(irFunc.getFunction()) } or
TPrintableIRBlock(IRBlock block) { shouldPrintDeclaration(block.getEnclosingFunction()) } or
TPrintableIRFunction(IRFunction irFunc) { shouldPrintFunction(irFunc.getFunction()) } or
TPrintableIRBlock(IRBlock block) { shouldPrintFunction(block.getEnclosingFunction()) } or
TPrintableInstruction(Instruction instr) {
shouldPrintInstruction(instr) and shouldPrintDeclaration(instr.getEnclosingFunction())
shouldPrintInstruction(instr) and shouldPrintFunction(instr.getEnclosingFunction())
}
/**

View File

@@ -176,6 +176,7 @@ private predicate binaryValueNumber0(
)
}
pragma[assume_small_delta]
private predicate binaryValueNumber(
BinaryInstruction instr, IRFunction irFunc, Opcode opcode, TValueNumber leftOperand,
TValueNumber rightOperand
@@ -201,6 +202,7 @@ private predicate pointerArithmeticValueNumber0(
)
}
pragma[assume_small_delta]
private predicate pointerArithmeticValueNumber(
PointerArithmeticInstruction instr, IRFunction irFunc, Opcode opcode, int elementSize,
TValueNumber leftOperand, TValueNumber rightOperand
@@ -247,6 +249,7 @@ private predicate loadTotalOverlapValueNumber0(
)
}
pragma[assume_small_delta]
private predicate loadTotalOverlapValueNumber(
LoadTotalOverlapInstruction instr, IRFunction irFunc, IRType type, TValueNumber memOperand,
TValueNumber operand

View File

@@ -4,8 +4,8 @@
* This file contains the actual implementation of `PrintIR.ql`. For test cases and very small
* databases, `PrintIR.ql` can be run directly to dump the IR for the entire database. For most
* uses, however, it is better to write a query that imports `PrintIR.qll`, extends
* `PrintIRConfiguration`, and overrides `shouldPrintDeclaration()` to select a subset of declarations
* to dump.
* `PrintIRConfiguration`, and overrides `shouldPrintFunction()` to select a subset of functions to
* dump.
*/
private import internal.IRInternal
@@ -16,7 +16,7 @@ import Imports::IRConfiguration
private newtype TPrintIRConfiguration = MkPrintIRConfiguration()
/**
* The query can extend this class to control which declarations are printed.
* The query can extend this class to control which functions are printed.
*/
class PrintIRConfiguration extends TPrintIRConfiguration {
/** Gets a textual representation of this configuration. */
@@ -24,9 +24,9 @@ class PrintIRConfiguration extends TPrintIRConfiguration {
/**
* Holds if the IR for `func` should be printed. By default, holds for all
* functions, global and namespace variables, and static local variables.
* functions.
*/
predicate shouldPrintDeclaration(Language::Declaration decl) { any() }
predicate shouldPrintFunction(Language::Declaration decl) { any() }
}
/**
@@ -34,12 +34,12 @@ class PrintIRConfiguration extends TPrintIRConfiguration {
*/
private class FilteredIRConfiguration extends IRConfiguration {
override predicate shouldEvaluateDebugStringsForFunction(Language::Declaration func) {
shouldPrintDeclaration(func)
shouldPrintFunction(func)
}
}
private predicate shouldPrintDeclaration(Language::Declaration decl) {
exists(PrintIRConfiguration config | config.shouldPrintDeclaration(decl))
private predicate shouldPrintFunction(Language::Declaration decl) {
exists(PrintIRConfiguration config | config.shouldPrintFunction(decl))
}
private predicate shouldPrintInstruction(Instruction i) {
@@ -90,10 +90,10 @@ private string getOperandPropertyString(Operand operand) {
}
private newtype TPrintableIRNode =
TPrintableIRFunction(IRFunction irFunc) { shouldPrintDeclaration(irFunc.getFunction()) } or
TPrintableIRBlock(IRBlock block) { shouldPrintDeclaration(block.getEnclosingFunction()) } or
TPrintableIRFunction(IRFunction irFunc) { shouldPrintFunction(irFunc.getFunction()) } or
TPrintableIRBlock(IRBlock block) { shouldPrintFunction(block.getEnclosingFunction()) } or
TPrintableInstruction(Instruction instr) {
shouldPrintInstruction(instr) and shouldPrintDeclaration(instr.getEnclosingFunction())
shouldPrintInstruction(instr) and shouldPrintFunction(instr.getEnclosingFunction())
}
/**

View File

@@ -176,6 +176,7 @@ private predicate binaryValueNumber0(
)
}
pragma[assume_small_delta]
private predicate binaryValueNumber(
BinaryInstruction instr, IRFunction irFunc, Opcode opcode, TValueNumber leftOperand,
TValueNumber rightOperand
@@ -201,6 +202,7 @@ private predicate pointerArithmeticValueNumber0(
)
}
pragma[assume_small_delta]
private predicate pointerArithmeticValueNumber(
PointerArithmeticInstruction instr, IRFunction irFunc, Opcode opcode, int elementSize,
TValueNumber leftOperand, TValueNumber rightOperand
@@ -247,6 +249,7 @@ private predicate loadTotalOverlapValueNumber0(
)
}
pragma[assume_small_delta]
private predicate loadTotalOverlapValueNumber(
LoadTotalOverlapInstruction instr, IRFunction irFunc, IRType type, TValueNumber memOperand,
TValueNumber operand

View File

@@ -991,19 +991,9 @@ class TranslatedStructuredBindingVariableAccess extends TranslatedNonConstantExp
class TranslatedFunctionAccess extends TranslatedNonConstantExpr {
override FunctionAccess expr;
override TranslatedElement getChild(int id) {
id = 0 and result = this.getQualifier() // Might not exist
}
override TranslatedElement getChild(int id) { none() }
final TranslatedExpr getQualifier() {
result = getTranslatedExpr(expr.getQualifier().getFullyConverted())
}
override Instruction getFirstInstruction() {
if exists(this.getQualifier())
then result = this.getQualifier().getFirstInstruction()
else result = this.getInstruction(OnlyInstructionTag())
}
override Instruction getFirstInstruction() { result = this.getInstruction(OnlyInstructionTag()) }
override Instruction getResult() { result = this.getInstruction(OnlyInstructionTag()) }
@@ -1024,9 +1014,7 @@ class TranslatedFunctionAccess extends TranslatedNonConstantExpr {
result = expr.getTarget()
}
override Instruction getChildSuccessor(TranslatedElement child) {
child = this.getQualifier() and result = this.getInstruction(OnlyInstructionTag())
}
override Instruction getChildSuccessor(TranslatedElement child) { none() }
}
/**

View File

@@ -4,8 +4,8 @@
* This file contains the actual implementation of `PrintIR.ql`. For test cases and very small
* databases, `PrintIR.ql` can be run directly to dump the IR for the entire database. For most
* uses, however, it is better to write a query that imports `PrintIR.qll`, extends
* `PrintIRConfiguration`, and overrides `shouldPrintDeclaration()` to select a subset of declarations
* to dump.
* `PrintIRConfiguration`, and overrides `shouldPrintFunction()` to select a subset of functions to
* dump.
*/
private import internal.IRInternal
@@ -16,7 +16,7 @@ import Imports::IRConfiguration
private newtype TPrintIRConfiguration = MkPrintIRConfiguration()
/**
* The query can extend this class to control which declarations are printed.
* The query can extend this class to control which functions are printed.
*/
class PrintIRConfiguration extends TPrintIRConfiguration {
/** Gets a textual representation of this configuration. */
@@ -24,9 +24,9 @@ class PrintIRConfiguration extends TPrintIRConfiguration {
/**
* Holds if the IR for `func` should be printed. By default, holds for all
* functions, global and namespace variables, and static local variables.
* functions.
*/
predicate shouldPrintDeclaration(Language::Declaration decl) { any() }
predicate shouldPrintFunction(Language::Declaration decl) { any() }
}
/**
@@ -34,12 +34,12 @@ class PrintIRConfiguration extends TPrintIRConfiguration {
*/
private class FilteredIRConfiguration extends IRConfiguration {
override predicate shouldEvaluateDebugStringsForFunction(Language::Declaration func) {
shouldPrintDeclaration(func)
shouldPrintFunction(func)
}
}
private predicate shouldPrintDeclaration(Language::Declaration decl) {
exists(PrintIRConfiguration config | config.shouldPrintDeclaration(decl))
private predicate shouldPrintFunction(Language::Declaration decl) {
exists(PrintIRConfiguration config | config.shouldPrintFunction(decl))
}
private predicate shouldPrintInstruction(Instruction i) {
@@ -90,10 +90,10 @@ private string getOperandPropertyString(Operand operand) {
}
private newtype TPrintableIRNode =
TPrintableIRFunction(IRFunction irFunc) { shouldPrintDeclaration(irFunc.getFunction()) } or
TPrintableIRBlock(IRBlock block) { shouldPrintDeclaration(block.getEnclosingFunction()) } or
TPrintableIRFunction(IRFunction irFunc) { shouldPrintFunction(irFunc.getFunction()) } or
TPrintableIRBlock(IRBlock block) { shouldPrintFunction(block.getEnclosingFunction()) } or
TPrintableInstruction(Instruction instr) {
shouldPrintInstruction(instr) and shouldPrintDeclaration(instr.getEnclosingFunction())
shouldPrintInstruction(instr) and shouldPrintFunction(instr.getEnclosingFunction())
}
/**

View File

@@ -176,6 +176,7 @@ private predicate binaryValueNumber0(
)
}
pragma[assume_small_delta]
private predicate binaryValueNumber(
BinaryInstruction instr, IRFunction irFunc, Opcode opcode, TValueNumber leftOperand,
TValueNumber rightOperand
@@ -201,6 +202,7 @@ private predicate pointerArithmeticValueNumber0(
)
}
pragma[assume_small_delta]
private predicate pointerArithmeticValueNumber(
PointerArithmeticInstruction instr, IRFunction irFunc, Opcode opcode, int elementSize,
TValueNumber leftOperand, TValueNumber rightOperand
@@ -247,6 +249,7 @@ private predicate loadTotalOverlapValueNumber0(
)
}
pragma[assume_small_delta]
private predicate loadTotalOverlapValueNumber(
LoadTotalOverlapInstruction instr, IRFunction irFunc, IRType type, TValueNumber memOperand,
TValueNumber operand

View File

@@ -188,9 +188,6 @@ module SemanticExprConfig {
none()
}
/** Holds if no range analysis should be performed on the phi edges in `f`. */
private predicate excludeFunction(Cpp::Function f) { count(f.getEntryPoint()) > 1 }
SemType getUnknownExprType(Expr expr) { result = getSemanticType(expr.getResultIRType()) }
class BasicBlock = IR::IRBlock;
@@ -273,13 +270,7 @@ module SemanticExprConfig {
getSemanticExpr(v.asInstruction()) = sourceExpr
}
predicate phi(SsaVariable v) {
exists(IR::PhiInstruction phi, Cpp::Function f |
phi = v.asInstruction() and
f = phi.getEnclosingFunction() and
not excludeFunction(f)
)
}
predicate phi(SsaVariable v) { v.asInstruction() instanceof IR::PhiInstruction }
SsaVariable getAPhiInput(SsaVariable v) {
exists(IR::PhiInstruction instr | v.asInstruction() = instr |

View File

@@ -70,27 +70,6 @@ predicate semBackEdge(SemSsaPhiNode phi, SemSsaVariable inp, SemSsaReadPositionP
// Conservatively assume that every edge is a back edge if we don't have dominance information.
(
phi.getBasicBlock().bbDominates(edge.getOrigBlock()) or
irreducibleSccEdge(edge.getOrigBlock(), phi.getBasicBlock()) or
not edge.getOrigBlock().hasDominanceInformation()
)
}
/**
* Holds if the edge from b1 to b2 is part of a multiple-entry cycle in an irreducible control flow
* graph.
*
* An ireducible control flow graph is one where the usual dominance-based back edge detection does
* not work, because there is a cycle with multiple entry points, meaning there are
* mutually-reachable basic blocks where neither dominates the other. For such a graph, we first
* remove all detectable back-edges using the normal condition that the predecessor block is
* dominated by the successor block, then mark all edges in a cycle in the resulting graph as back
* edges.
*/
private predicate irreducibleSccEdge(SemBasicBlock b1, SemBasicBlock b2) {
trimmedEdge(b1, b2) and trimmedEdge+(b2, b1)
}
private predicate trimmedEdge(SemBasicBlock pred, SemBasicBlock succ) {
pred.getASuccessor() = succ and
not succ.bbDominates(pred)
}

View File

@@ -877,6 +877,7 @@ module RangeStage<
)
}
pragma[assume_small_delta]
pragma[nomagic]
private predicate boundedPhiRankStep(
SemSsaPhiNode phi, SemBound b, D::Delta delta, boolean upper, boolean fromBackEdge,

View File

@@ -1,284 +0,0 @@
/**
* This file provides the first phase of the `cpp/invalid-pointer-deref` query that identifies flow
* from an allocation to a pointer-arithmetic instruction that constructs a pointer that is out of bounds.
*
* Consider the following snippet:
* ```cpp
* 1. char* base = (char*)malloc(size);
* 2. char* end = base + size;
* 3. for(int *p = base; p <= end; p++) {
* 4. use(*p); // BUG: Should have been bounded by `p < end`.
* 5. }
* ```
* this file identifies the flow from `new int[size]` to `base + size`.
*
* This is done using the product-flow library. The configuration tracks flow from the pair
* `(allocation, size of allocation)` to a pair `(a, b)` where there exists a pointer-arithmetic instruction
* `pai = a + r` such that `b` is a dataflow node where `b <= r`. Because there will be a dataflow-path from
* `allocation` to `a` this means that the `pai` will compute a pointer that is some number of elements beyond
* the end position of the allocation. See `pointerAddInstructionHasBounds` for the implementation of this.
*
* In the above example, the pair `(a, b)` is `(base, size)` with `base` and `size` coming from the expression
* `base + size` on line 2, which is also the pointer-arithmetic instruction. In general, the pair does not necessarily
* correspond directly to the operands of the pointer-arithmetic instruction.
* In the following example, the pair is again `(base, size)`, but with `base` coming from line 3 and `size` from line 2,
* and the pointer-arithmetic instruction being `base + n` on line 3:
* ```cpp
* 1. int* base = new int[size];
* 2. if(n <= size) {
* 3. int* end = base + n;
* 4. for(int* p = base; p <= end; ++p) {
* 5. *p = 0; // BUG: Should have been bounded by `p < end`.
* 6. }
* 7. }
* ```
*
* Handling false positives:
*
* Consider a snippet such as:
* ```cpp
* 1. int* base = new int[size];
* 2. int n = condition() ? size : 0;
* 3. if(n >= size) return;
* 4. int* end = base + n;
* 5. for(int* p = base; p <= end; ++p) {
* 6. *p = 0; // This is fine since `end < base + size`
* 7. }
* ```
* In order to remove this false positive we define a barrier (see `SizeBarrier::SizeBarrierConfig`) that finds the
* possible guards that compares a value to the size of the allocation. In the above example, this is the `(n >= size)`
* guard on line 3. `SizeBarrier::getABarrierNode` then defines any node that is guarded by such a guard as a barrier
* in the dataflow configuration.
*/
private import cpp
private import semmle.code.cpp.ir.dataflow.internal.ProductFlow
private import semmle.code.cpp.ir.ValueNumbering
private import semmle.code.cpp.controlflow.IRGuards
private import codeql.util.Unit
private import RangeAnalysisUtil
private VariableAccess getAVariableAccess(Expr e) { e.getAChild*() = result }
/**
* Holds if the `(n, state)` pair represents the source of flow for the size
* expression associated with `alloc`.
*/
predicate hasSize(HeuristicAllocationExpr alloc, DataFlow::Node n, int state) {
exists(VariableAccess va, Expr size, int delta |
size = alloc.getSizeExpr() and
// Get the unique variable in a size expression like `x` in `malloc(x + 1)`.
va = unique( | | getAVariableAccess(size)) and
// Compute `delta` as the constant difference between `x` and `x + 1`.
bounded1(any(Instruction instr | instr.getUnconvertedResultExpression() = size),
any(LoadInstruction load | load.getUnconvertedResultExpression() = va), delta) and
n.asConvertedExpr() = va.getFullyConverted() and
state = delta
)
}
/**
* A module that encapsulates a barrier guard to remove false positives from flow like:
* ```cpp
* char *p = new char[size];
* // ...
* unsigned n = size;
* // ...
* if(n < size) {
* use(*p[n]);
* }
* ```
* In this case, the sink pair identified by the product flow library (without any additional barriers)
* would be `(p, n)` (where `n` is the `n` in `p[n]`), because there exists a pointer-arithmetic
* instruction `pai = a + b` such that:
* 1. the allocation flows to `a`, and
* 2. `b <= n` where `n` is the `n` in `p[n]`
* but because there's a strict comparison that compares `n` against the size of the allocation this
* snippet is fine.
*/
module SizeBarrier {
private module SizeBarrierConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
// The sources is the same as in the sources for the second
// projection in the `AllocToInvalidPointerConfig` module.
hasSize(_, source, _)
}
additional predicate isSink(
DataFlow::Node left, DataFlow::Node right, IRGuardCondition g, int k, boolean testIsTrue
) {
// The sink is any "large" side of a relational comparison. i.e., the `right` expression
// in a guard such as `left < right + k`.
g.comparesLt(left.asOperand(), right.asOperand(), k, true, testIsTrue)
}
predicate isSink(DataFlow::Node sink) { isSink(_, sink, _, _, _) }
}
private import DataFlow::Global<SizeBarrierConfig>
private int getAFlowStateForNode(DataFlow::Node node) {
exists(DataFlow::Node source |
flow(source, node) and
hasSize(_, source, result)
)
}
private predicate operandGuardChecks(
IRGuardCondition g, Operand left, Operand right, int state, boolean edge
) {
exists(DataFlow::Node nLeft, DataFlow::Node nRight, int k |
nRight.asOperand() = right and
nLeft.asOperand() = left and
SizeBarrierConfig::isSink(nLeft, nRight, g, k, edge) and
state = getAFlowStateForNode(nRight) and
k <= state
)
}
/**
* Gets an instruction that is guarded by a guard condition which ensures that
* the value of the instruction is upper-bounded by size of some allocation.
*/
Instruction getABarrierInstruction(int state) {
exists(IRGuardCondition g, ValueNumber value, Operand use, boolean edge |
use = value.getAUse() and
operandGuardChecks(pragma[only_bind_into](g), pragma[only_bind_into](use), _,
pragma[only_bind_into](state), pragma[only_bind_into](edge)) and
result = value.getAnInstruction() and
g.controls(result.getBlock(), edge)
)
}
/**
* Gets a `DataFlow::Node` that is guarded by a guard condition which ensures that
* the value of the node is upper-bounded by size of some allocation.
*/
DataFlow::Node getABarrierNode(int state) {
result.asOperand() = getABarrierInstruction(state).getAUse()
}
/**
* Gets the block of a node that is guarded (see `getABarrierInstruction` or
* `getABarrierNode` for the definition of what it means to be guarded).
*/
IRBlock getABarrierBlock(int state) { result.getAnInstruction() = getABarrierInstruction(state) }
}
private module InterestingPointerAddInstruction {
private module PointerAddInstructionConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
// The sources is the same as in the sources for the second
// projection in the `AllocToInvalidPointerConfig` module.
hasSize(source.asConvertedExpr(), _, _)
}
predicate isSink(DataFlow::Node sink) {
sink.asInstruction() = any(PointerAddInstruction pai).getLeft()
}
}
private import DataFlow::Global<PointerAddInstructionConfig>
/**
* Holds if `pai` is a pointer-arithmetic instruction such that the
* result of an allocation flows to the left-hand side of `pai`.
*
* This predicate is used to reduce the set of tuples in `isSinkPair`.
*/
predicate isInteresting(PointerAddInstruction pai) {
exists(DataFlow::Node n |
n.asInstruction() = pai.getLeft() and
flowTo(n)
)
}
}
/**
* A product-flow configuration for flow from an `(allocation, size)` pair to a
* pointer-arithmetic operation `pai` such that `pai <= allocation + size`.
*/
private module Config implements ProductFlow::StateConfigSig {
class FlowState1 = Unit;
class FlowState2 = int;
predicate isSourcePair(
DataFlow::Node allocSource, FlowState1 unit, DataFlow::Node sizeSource, FlowState2 sizeAddend
) {
// In the case of an allocation like
// ```cpp
// malloc(size + 1);
// ```
// we use `state2` to remember that there was an offset (in this case an offset of `1`) added
// to the size of the allocation. This state is then checked in `isSinkPair`.
exists(unit) and
hasSize(allocSource.asConvertedExpr(), sizeSource, sizeAddend)
}
predicate isSinkPair(
DataFlow::Node allocSink, FlowState1 unit, DataFlow::Node sizeSink, FlowState2 sizeAddend
) {
exists(unit) and
// We check that the delta computed by the range analysis matches the
// state value that we set in `isSourcePair`.
pointerAddInstructionHasBounds0(_, allocSink, sizeSink, sizeAddend)
}
predicate isBarrier2(DataFlow::Node node, FlowState2 state) {
node = SizeBarrier::getABarrierNode(state)
}
predicate isBarrierIn1(DataFlow::Node node) { isSourcePair(node, _, _, _) }
predicate isBarrierOut2(DataFlow::Node node) {
node = any(DataFlow::SsaPhiNode phi).getAnInput(true)
}
}
private module AllocToInvalidPointerFlow = ProductFlow::GlobalWithState<Config>;
/**
* Holds if `pai` is non-strictly upper bounded by `sizeSink + delta` and `allocSink` is the
* left operand of the pointer-arithmetic operation.
*
* For example in,
* ```cpp
* char* end = p + (size + 1);
* ```
* We will have:
* - `pai` is `p + (size + 1)`,
* - `allocSink` is `p`
* - `sizeSink` is `size`
* - `delta` is `1`.
*/
pragma[nomagic]
private predicate pointerAddInstructionHasBounds0(
PointerAddInstruction pai, DataFlow::Node allocSink, DataFlow::Node sizeSink, int delta
) {
InterestingPointerAddInstruction::isInteresting(pragma[only_bind_into](pai)) and
exists(Instruction right, Instruction sizeInstr |
pai.getRight() = right and
pai.getLeft() = allocSink.asInstruction() and
sizeInstr = sizeSink.asInstruction() and
// pai.getRight() <= sizeSink + delta
bounded1(right, sizeInstr, delta) and
not right = SizeBarrier::getABarrierInstruction(delta) and
not sizeInstr = SizeBarrier::getABarrierInstruction(delta)
)
}
/**
* Holds if `allocation` flows to `allocSink` and `allocSink` represents the left operand
* of the pointer-arithmetic instruction `pai = a + b` (i.e., `allocSink = a`), and
* `b <= allocation + delta`.
*/
pragma[nomagic]
predicate pointerAddInstructionHasBounds(
DataFlow::Node allocation, PointerAddInstruction pai, DataFlow::Node allocSink, int delta
) {
exists(DataFlow::Node sizeSink |
AllocToInvalidPointerFlow::flow(allocation, _, allocSink, sizeSink) and
pointerAddInstructionHasBounds0(pai, allocSink, sizeSink, delta)
)
}

View File

@@ -1,266 +0,0 @@
/**
* This file provides the second phase of the `cpp/invalid-pointer-deref` query that identifies flow
* from the out-of-bounds pointer identified by the `AllocationToInvalidPointer.qll` library to
* a dereference of the out-of-bounds pointer.
*
* Consider the following snippet:
* ```cpp
* 1. char* base = (char*)malloc(size);
* 2. char* end = base + size;
* 3. for(char *p = base; p <= end; p++) {
* 4. use(*p); // BUG: Should have been bounded by `p < end`.
* 5. }
* ```
* this file identifies the flow from `base + size` to `end`. We call `base + size` the "dereference source" and `end`
* the "dereference sink" (even though `end` is not actually dereferenced we will use this term because we will perform
* dataflow to find a use of a pointer `x` such that `x <= end` which is dereferenced. In the above example, `x` is `p`
* on line 4).
*
* Merely _constructing_ a pointer that's out-of-bounds is fine if the pointer is never dereferenced (in reality, the
* standard only guarantees that it is safe to move the pointer one element past the last element, but we ignore that
* here). So this step is about identifying which of the out-of-bounds pointers found by `pointerAddInstructionHasBounds`
* in `AllocationToInvalidPointer.qll` are actually being dereferenced. We do this using a regular dataflow
* configuration (see `InvalidPointerToDerefConfig`).
*
* The dataflow traversal defines the set of sources as any dataflow node `n` such that there exists a pointer-arithmetic
* instruction `pai` found by `AllocationToInvalidPointer.qll` and a `n.asInstruction() >= pai + deltaDerefSourceAndPai`.
* Here, `deltaDerefSourceAndPai` is the constant difference between the source we track for finding a dereference and the
* pointer-arithmetic instruction.
*
* The set of sinks is defined as any dataflow node `n` such that `addr <= n.asInstruction() + deltaDerefSinkAndDerefAddress`
* for some address operand `addr` and constant difference `deltaDerefSinkAndDerefAddress`. Since an address operand is
* always consumed by an instruction that performs a dereference this lets us identify a "bad dereference". We call the
* instruction that consumes the address operand the "operation".
*
* For example, consider the flow from `base + size` to `end` above. The sink is `end` on line 3 because
* `p <= end.asInstruction() + deltaDerefSinkAndDerefAddress`, where `p` is the address operand in `use(*p)` and
* `deltaDerefSinkAndDerefAddress >= 0`. The load attached to `*p` is the "operation". To ensure that the path makes
* intuitive sense, we only pick operations that are control-flow reachable from the dereference sink.
*
* To compute how many elements the dereference is beyond the end position of the allocation, we sum the two deltas
* `deltaDerefSourceAndPai` and `deltaDerefSinkAndDerefAddress`. This is done in the `operationIsOffBy` predicate
* (which is the only predicate exposed by this file).
*
* Handling false positives:
*
* Consider the following snippet:
* ```cpp
* 1. char *p = new char[size];
* 2. char *end = p + size;
* 3. if (p < end) {
* 4. p += 1;
* 5. }
* 6. if (p < end) {
* 7. int val = *p; // GOOD
* 8. }
* ```
* this is safe because `p` is guarded to be strictly less than `end` on line 6 before the dereference on line 7. However, if we
* run the query on the above without further modifications we would see an alert on line 7. This is because range analysis infers
* that `p <= end` after the increment on line 4, and thus the result of `p += 1` is seen as a valid dereference source. This
* node then flows to `p` on line 6 (which is a valid dereference sink since it non-strictly upper bounds an address operand), and
* range analysis then infers that the address operand of `*p` (i.e., `p`) is non-strictly upper bounded by `p`, and thus reports
* an alert on line 7.
*
* In order to handle the above false positive, we define a barrier that identifies guards such as `p < end` that ensures that a value
* is less than the pointer-arithmetic instruction that computed the invalid pointer. This is done in the `InvalidPointerToDerefBarrier`
* module. Since the node we are tracking is not necessarily _equal_ to the pointer-arithmetic instruction, but rather satisfies
* `node.asInstruction() <= pai + deltaDerefSourceAndPai`, we need to account for the delta when checking if a guard is sufficiently
* strong to infer that a future dereference is safe. To do this, we check that the guard guarantees that a node `n` satisfies
* `n < node + k` where `node` is a node we know is equal to the value of the dereference source (i.e., it satisfies
* `node.asInstruction() <= pai + deltaDerefSourceAndPai`) and `k <= deltaDerefSourceAndPai`. Combining this we have
* `n < node + k <= node + deltaDerefSourceAndPai <= pai + 2*deltaDerefSourceAndPai` (TODO: Oops. This math doesn't quite work out.
* I think this is because we need to redefine the `BarrierConfig` to start flow at the pointer-arithmetic instruction instead of
* at the dereference source. When combined with TODO above it's easy to show that this guard ensures that the dereference is safe).
*/
private import cpp
private import semmle.code.cpp.dataflow.new.DataFlow
private import semmle.code.cpp.ir.ValueNumbering
private import semmle.code.cpp.controlflow.IRGuards
private import AllocationToInvalidPointer as AllocToInvalidPointer
private import RangeAnalysisUtil
private module InvalidPointerToDerefBarrier {
private module BarrierConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
// The sources is the same as in the sources for `InvalidPointerToDerefConfig`.
invalidPointerToDerefSource(_, _, source, _)
}
additional predicate isSink(
DataFlow::Node left, DataFlow::Node right, IRGuardCondition g, int k, boolean testIsTrue
) {
// The sink is any "large" side of a relational comparison.
g.comparesLt(left.asOperand(), right.asOperand(), k, true, testIsTrue)
}
predicate isSink(DataFlow::Node sink) { isSink(_, sink, _, _, _) }
}
private module BarrierFlow = DataFlow::Global<BarrierConfig>;
private int getInvalidPointerToDerefSourceDelta(DataFlow::Node node) {
exists(DataFlow::Node source |
BarrierFlow::flow(source, node) and
invalidPointerToDerefSource(_, _, source, result)
)
}
private predicate operandGuardChecks(
IRGuardCondition g, Operand left, Operand right, int state, boolean edge
) {
exists(DataFlow::Node nLeft, DataFlow::Node nRight, int k |
nRight.asOperand() = right and
nLeft.asOperand() = left and
BarrierConfig::isSink(nLeft, nRight, g, k, edge) and
state = getInvalidPointerToDerefSourceDelta(nRight) and
k <= state
)
}
Instruction getABarrierInstruction(int state) {
exists(IRGuardCondition g, ValueNumber value, Operand use, boolean edge |
use = value.getAUse() and
operandGuardChecks(pragma[only_bind_into](g), pragma[only_bind_into](use), _, state,
pragma[only_bind_into](edge)) and
result = value.getAnInstruction() and
g.controls(result.getBlock(), edge)
)
}
DataFlow::Node getABarrierNode() { result.asOperand() = getABarrierInstruction(_).getAUse() }
pragma[nomagic]
IRBlock getABarrierBlock(int state) { result.getAnInstruction() = getABarrierInstruction(state) }
}
/**
* A configuration to track flow from a pointer-arithmetic operation found
* by `AllocToInvalidPointerConfig` to a dereference of the pointer.
*/
private module InvalidPointerToDerefConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { invalidPointerToDerefSource(_, _, source, _) }
pragma[inline]
predicate isSink(DataFlow::Node sink) { isInvalidPointerDerefSink(sink, _, _, _) }
predicate isBarrier(DataFlow::Node node) {
node = any(DataFlow::SsaPhiNode phi | not phi.isPhiRead()).getAnInput(true)
or
node = InvalidPointerToDerefBarrier::getABarrierNode()
}
}
private import DataFlow::Global<InvalidPointerToDerefConfig>
/**
* Holds if `allocSource` is dataflow node that represents an allocation that flows to the
* left-hand side of the pointer-arithmetic `pai`, and `derefSource <= pai + derefSourcePaiDelta`.
*
* For example, if `pai` is a pointer-arithmetic operation `p + size` in an expression such
* as `(p + size) + 1` and `derefSource` is the node representing `(p + size) + 1`. In this
* case `derefSourcePaiDelta` is 1.
*/
private predicate invalidPointerToDerefSource(
DataFlow::Node allocSource, PointerArithmeticInstruction pai, DataFlow::Node derefSource,
int deltaDerefSourceAndPai
) {
exists(int rhsSizeDelta |
// Note that `deltaDerefSourceAndPai` is not necessarily equal to `rhsSizeDelta`:
// `rhsSizeDelta` is the constant offset added to the size of the allocation, and
// `deltaDerefSourceAndPai` is the constant difference between the pointer-arithmetic instruction
// and the instruction computing the address for which we will search for a dereference.
AllocToInvalidPointer::pointerAddInstructionHasBounds(allocSource, pai, _, rhsSizeDelta) and
bounded2(derefSource.asInstruction(), pai, deltaDerefSourceAndPai) and
deltaDerefSourceAndPai >= 0 and
// TODO: This condition will go away once #13725 is merged, and then we can make `SizeBarrier`
// private to `AllocationToInvalidPointer.qll`.
not derefSource.getBasicBlock() =
AllocToInvalidPointer::SizeBarrier::getABarrierBlock(rhsSizeDelta)
)
}
/**
* Holds if `sink` is a sink for `InvalidPointerToDerefConfig` and `i` is a `StoreInstruction` that
* writes to an address `addr` such that `addr <= sink`, or `i` is a `LoadInstruction` that
* reads from an address `addr` such that `addr <= sink`.
*/
pragma[inline]
private predicate isInvalidPointerDerefSink(
DataFlow::Node sink, Instruction i, string operation, int deltaDerefSinkAndDerefAddress
) {
exists(AddressOperand addr, Instruction s, IRBlock b |
s = sink.asInstruction() and
bounded(addr.getDef(), s, deltaDerefSinkAndDerefAddress) and
deltaDerefSinkAndDerefAddress >= 0 and
i.getAnOperand() = addr and
b = i.getBlock() and
not b = InvalidPointerToDerefBarrier::getABarrierBlock(deltaDerefSinkAndDerefAddress)
|
i instanceof StoreInstruction and
operation = "write"
or
i instanceof LoadInstruction and
operation = "read"
)
}
/**
* Yields any instruction that is control-flow reachable from `instr`.
*/
bindingset[instr, result]
pragma[inline_late]
private Instruction getASuccessor(Instruction instr) {
exists(IRBlock b, int instrIndex, int resultIndex |
b.getInstruction(instrIndex) = instr and
b.getInstruction(resultIndex) = result
|
resultIndex >= instrIndex
)
or
instr.getBlock().getASuccessor+() = result.getBlock()
}
private predicate paiForDereferenceSink(PointerArithmeticInstruction pai, DataFlow::Node derefSink) {
exists(DataFlow::Node derefSource |
invalidPointerToDerefSource(_, pai, derefSource, _) and
flow(derefSource, derefSink)
)
}
/**
* Holds if `derefSink` is a dataflow node that represents an out-of-bounds address that is about to
* be dereferenced by `operation` (which is either a `StoreInstruction` or `LoadInstruction`), and
* `pai` is the pointer-arithmetic operation that caused the `derefSink` to be out-of-bounds.
*/
private predicate derefSinkToOperation(
DataFlow::Node derefSink, PointerArithmeticInstruction pai, DataFlow::Node operation,
string description, int deltaDerefSinkAndDerefAddress
) {
exists(Instruction operationInstr |
paiForDereferenceSink(pai, pragma[only_bind_into](derefSink)) and
isInvalidPointerDerefSink(derefSink, operationInstr, description, deltaDerefSinkAndDerefAddress) and
operationInstr = getASuccessor(derefSink.asInstruction()) and
operation.asInstruction() = operationInstr
)
}
/**
* Holds if `allocation` is the result of an allocation that flows to the left-hand side of `pai`, and where
* the right-hand side of `pai` is an offset such that the result of `pai` points to an out-of-bounds pointer.
*
* Furthermore, `derefSource` is at least as large as `pai` and flows to `derefSink` before being dereferenced
* by `operation` (which is either a `StoreInstruction` or `LoadInstruction`). The result is that `operation`
* dereferences a pointer that's "off by `delta`" number of elements.
*/
predicate operationIsOffBy(
DataFlow::Node allocation, PointerArithmeticInstruction pai, DataFlow::Node derefSource,
DataFlow::Node derefSink, string description, DataFlow::Node operation, int delta
) {
exists(int deltaDerefSourceAndPai, int deltaDerefSinkAndDerefAddress |
invalidPointerToDerefSource(allocation, pai, derefSource, deltaDerefSourceAndPai) and
flow(derefSource, derefSink) and
derefSinkToOperation(derefSink, pai, operation, description, deltaDerefSinkAndDerefAddress) and
delta = deltaDerefSourceAndPai + deltaDerefSinkAndDerefAddress
)
}

View File

@@ -1,48 +0,0 @@
/**
* This file contains the range-analysis specific parts of the `cpp/invalid-pointer-deref` query
* that is used by both `AllocationToInvalidPointer.qll` and `InvalidPointerToDereference.qll`.
*/
private import cpp
private import semmle.code.cpp.rangeanalysis.new.internal.semantic.analysis.RangeAnalysis
private import semmle.code.cpp.rangeanalysis.new.internal.semantic.SemanticExprSpecific
private import semmle.code.cpp.ir.IR
pragma[nomagic]
private Instruction getABoundIn(SemBound b, IRFunction func) {
getSemanticExpr(result) = b.getExpr(0) and
result.getEnclosingIRFunction() = func
}
/**
* Holds if `i <= b + delta`.
*/
pragma[inline]
private predicate boundedImpl(Instruction i, Instruction b, int delta) {
exists(SemBound bound, IRFunction func |
semBounded(getSemanticExpr(i), bound, delta, true, _) and
b = getABoundIn(bound, func) and
i.getEnclosingIRFunction() = func
)
}
/**
* Holds if `i <= b + delta`.
*
* This predicate enforces a join-order that ensures that `i` has already been bound.
*/
bindingset[i]
pragma[inline_late]
predicate bounded1(Instruction i, Instruction b, int delta) { boundedImpl(i, b, delta) }
/**
* Holds if `i <= b + delta`.
*
* This predicate enforces a join-order that ensures that `b` has already been bound.
*/
bindingset[b]
pragma[inline_late]
predicate bounded2(Instruction i, Instruction b, int delta) { boundedImpl(i, b, delta) }
/** Holds if `i <= b + delta`. */
predicate bounded = boundedImpl/3;

View File

@@ -608,7 +608,7 @@ case @builtintype.kind of
| 47 = @std_float64 // _Float64
| 48 = @float64x // _Float64x
| 49 = @std_float128 // _Float128
// ... 50 _Float128x
| 50 = @float128x // _Float128x
| 51 = @char8_t
| 52 = @float16 // _Float16
| 53 = @complex_float16 // _Complex _Float16

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +0,0 @@
class BuiltinType extends @builtintype {
string toString() { none() }
}
predicate isFloat128xBuiltinType(BuiltinType type) {
exists(int kind | builtintypes(type, _, kind, _, _, _) | kind = 50)
}
from BuiltinType type, string name, int kind, int kind_new, int size, int sign, int alignment
where
builtintypes(type, name, kind, size, sign, alignment) and
if isFloat128xBuiltinType(type) then kind_new = 1 else kind_new = kind
select type, name, kind_new, size, sign, alignment

View File

@@ -1,3 +0,0 @@
description: Remove _Float128 type
compatibility: partial
builtintypes.rel: run builtintypes.qlo

View File

@@ -1,23 +1,3 @@
## 0.7.2
No user-facing changes.
## 0.7.1
### Minor Analysis Improvements
* The `cpp/uninitialized-local` query now excludes uninitialized uses that are explicitly cast to void and are expression statements. As a result, the query will report less false positives.
## 0.7.0
### Minor Analysis Improvements
* The `cpp/comparison-with-wider-type` query now correctly handles relational operations on signed operators. As a result the query may find more results.
## 0.6.4
No user-facing changes.
## 0.6.3
### New Queries

View File

@@ -88,6 +88,14 @@ module FlowFromFree<isSinkSig/2 isASink, isExcludedSig/2 isExcluded> {
e = any(StoreInstruction store).getDestinationAddress().getUnconvertedResultExpression()
)
}
predicate isBarrier(DataFlow::Node n, FlowState state) { none() }
predicate isAdditionalFlowStep(
DataFlow::Node n1, FlowState state1, DataFlow::Node n2, FlowState state2
) {
none()
}
}
import DataFlow::GlobalWithState<FlowFromFreeConfig>

View File

@@ -1,10 +1,9 @@
void f() {
int f() {
char* buf = new char[SIZE];
...
....
if (error) {
delete buf; //error handling has freed the buffer
free(buf); //error handling has freed the buffer
}
...
log_contents(buf); //but it is still used here for logging
...
}

View File

@@ -135,24 +135,18 @@ module ParameterSinks {
}
}
module IsUse {
private import semmle.code.cpp.ir.dataflow.internal.DataFlowImplCommon
predicate isUse(DataFlow::Node n, Expr e) {
isUse0(n, e)
or
exists(CallInstruction call, InitializeParameterInstruction init |
n.asOperand().getDef().getUnconvertedResultExpression() = e and
pragma[only_bind_into](init) = ParameterSinks::getAnAlwaysDereferencedParameter() and
viableParamArg(call, DataFlow::instructionNode(init), n) and
pragma[only_bind_out](init.getEnclosingFunction()) =
pragma[only_bind_out](call.getStaticCallTarget())
)
}
predicate isUse(DataFlow::Node n, Expr e) {
isUse0(n, e)
or
exists(CallInstruction call, int i, InitializeParameterInstruction init |
n.asOperand().getDef().getUnconvertedResultExpression() = e and
init = ParameterSinks::getAnAlwaysDereferencedParameter() and
call.getArgumentOperand(i) = n.asOperand() and
init.hasIndex(i) and
init.getEnclosingFunction() = call.getStaticCallTarget()
)
}
import IsUse
/**
* `dealloc1` is a deallocation expression, `e` is an expression that dereferences a
* pointer, and the `(dealloc1, e)` pair should be excluded by the `FlowFromFree` library.

View File

@@ -44,6 +44,14 @@ module CastToPointerArithFlowConfig implements DataFlow::StateConfigSig {
) and
getFullyConvertedType(node) = state
}
predicate isBarrier(DataFlow::Node node, FlowState state) { none() }
predicate isAdditionalFlowStep(
DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2
) {
none()
}
}
/**

View File

@@ -72,11 +72,6 @@ VariableAccess commonException() {
or
result.getParent() instanceof BuiltInOperation
or
// Ignore any uninitialized use that is explicitly cast to void and
// is an expression statement.
result.getActualType() instanceof VoidType and
result.getParent() instanceof ExprStmt
or
// Finally, exclude functions that contain assembly blocks. It's
// anyone's guess what happens in those.
containsInlineAssembly(result.getEnclosingFunction())

View File

@@ -134,6 +134,8 @@ module ExecTaintConfig implements DataFlow::StateConfigSig {
predicate isBarrier(DataFlow::Node node) { isBarrierImpl(node) }
predicate isBarrier(DataFlow::Node node, FlowState state) { none() }
predicate isBarrierOut(DataFlow::Node node) {
isSink(node, _) // Prevent duplicates along a call chain, since `shellCommand` will include wrappers
}

View File

@@ -5,7 +5,7 @@
* @kind path-problem
* @problem.severity error
* @security-severity 9.3
* @precision medium
* @precision low
* @id cpp/overrun-write
* @tags reliability
* security
@@ -118,6 +118,8 @@ module ValidState {
state = [false, true]
}
predicate isBarrier(DataFlow::Node node, FlowState state) { none() }
predicate isAdditionalFlowStep(
DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2
) {
@@ -231,8 +233,7 @@ module StringSizeConfig implements ProductFlow::StateConfigSig {
// we use `state2` to remember that there was an offset (in this case an offset of `1`) added
// to the size of the allocation. This state is then checked in `isSinkPair`.
exists(state1) and
hasSize(bufSource.asConvertedExpr(), sizeSource, state2) and
validState(sizeSource, state2)
hasSize(bufSource.asConvertedExpr(), sizeSource, state2)
}
predicate isSinkPair(
@@ -246,10 +247,20 @@ module StringSizeConfig implements ProductFlow::StateConfigSig {
)
}
predicate isBarrier1(DataFlow::Node node, FlowState1 state) { none() }
predicate isBarrier2(DataFlow::Node node, FlowState2 state) { none() }
predicate isBarrierOut2(DataFlow::Node node) {
node = any(DataFlow::SsaPhiNode phi).getAnInput(true)
}
predicate isAdditionalFlowStep1(
DataFlow::Node node1, FlowState1 state1, DataFlow::Node node2, FlowState1 state2
) {
none()
}
predicate isAdditionalFlowStep2(
DataFlow::Node node1, FlowState2 state1, DataFlow::Node node2, FlowState2 state2
) {

View File

@@ -45,20 +45,13 @@ Element friendlyLoc(Expr e) {
not e instanceof Access and not e instanceof Call and result = e
}
int getComparisonSizeAdjustment(Expr e) {
if e.getType().(IntegralType).isSigned() then result = 1 else result = 0
}
from Loop l, RelationalOperation rel, VariableAccess small, Expr large
where
small = rel.getLesserOperand() and
large = rel.getGreaterOperand() and
rel = l.getCondition().getAChild*() and
forall(Expr conv | conv = large.getConversion*() |
// We adjust the comparison size in the case of a signed integer type.
// This is to exclude the sign bit from the comparison that determines if the small type's size is sufficient to hold
// the value of the larger type determined with range analysis.
upperBound(conv).log2() > (getComparisonSize(small) * 8 - getComparisonSizeAdjustment(small))
upperBound(conv).log2() > getComparisonSize(small) * 8
) and
// Ignore cases where the smaller type is int or larger
// These are still bugs, but you should need a very large string or array to

View File

@@ -43,8 +43,6 @@ module XxeConfig implements DataFlow::StateConfigSig {
// flowstate value.
node.asIndirectExpr().(XxeFlowStateTransformer).transform(flowstate) != flowstate
}
predicate neverSkip(DataFlow::Node node) { none() }
}
module XxeFlow = DataFlow::GlobalWithState<XxeConfig>;

View File

@@ -1,3 +0,0 @@
## 0.6.4
No user-facing changes.

View File

@@ -1,5 +0,0 @@
## 0.7.0
### Minor Analysis Improvements
* The `cpp/comparison-with-wider-type` query now correctly handles relational operations on signed operators. As a result the query may find more results.

View File

@@ -1,5 +0,0 @@
## 0.7.1
### Minor Analysis Improvements
* The `cpp/uninitialized-local` query now excludes uninitialized uses that are explicitly cast to void and are expression statements. As a result, the query will report less false positives.

View File

@@ -1,3 +0,0 @@
## 0.7.2
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.7.2
lastReleaseVersion: 0.6.3

View File

@@ -14,7 +14,7 @@ import semmle.code.cpp.rangeanalysis.new.internal.semantic.analysis.RangeAnalysi
import semmle.code.cpp.rangeanalysis.new.internal.semantic.SemanticExprSpecific
import semmle.code.cpp.ir.IR
import semmle.code.cpp.ir.dataflow.DataFlow
import ArrayAddressToDerefFlow::PathGraph
import FieldAddressToDerefFlow::PathGraph
pragma[nomagic]
Instruction getABoundIn(SemBound b, IRFunction func) {
@@ -78,45 +78,28 @@ predicate isInvalidPointerDerefSink2(DataFlow::Node sink, Instruction i, string
)
}
predicate arrayTypeCand(ArrayType arrayType) {
any(Variable v).getUnspecifiedType() = arrayType and
exists(arrayType.getByteSize())
}
bindingset[baseTypeSize]
pragma[inline_late]
predicate arrayTypeHasSizes(ArrayType arr, int baseTypeSize, int size) {
arrayTypeCand(arr) and
arr.getByteSize() / baseTypeSize = size
}
bindingset[pai]
pragma[inline_late]
predicate constantUpperBounded(PointerArithmeticInstruction pai, int delta) {
semBounded(getSemanticExpr(pai.getRight()), any(SemZeroBound b), delta, true, _)
}
bindingset[pai, size]
predicate pointerArithOverflow0Impl(PointerArithmeticInstruction pai, int size, int delta) {
exists(int bound |
constantUpperBounded(pai, bound) and
delta = bound - size and
delta >= 0 and
size != 0 and
size != 1
)
}
pragma[nomagic]
predicate pointerArithOverflow0(PointerArithmeticInstruction pai, int delta) {
exists(int size |
arrayTypeHasSizes(_, pai.getElementSize(), size) and
pointerArithOverflow0Impl(pai, size, delta)
)
predicate arrayTypeHasSizes(ArrayType arr, int baseTypeSize, int arraySize) {
arr.getBaseType().getSize() = baseTypeSize and
arr.getArraySize() = arraySize
}
predicate pointerArithOverflow0(
PointerArithmeticInstruction pai, Field f, int size, int bound, int delta
) {
not f.getNamespace() instanceof StdNamespace and
arrayTypeHasSizes(f.getUnspecifiedType(), pai.getElementSize(), size) and
semBounded(getSemanticExpr(pai.getRight()), any(SemZeroBound b), bound, true, _) and
delta = bound - size and
delta >= 0 and
size != 0 and
size != 1
}
module PointerArithmeticToDerefConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { pointerArithOverflow0(source.asInstruction(), _) }
predicate isSource(DataFlow::Node source) {
pointerArithOverflow0(source.asInstruction(), _, _, _, _)
}
predicate isBarrierIn(DataFlow::Node node) { isSource(node) }
@@ -127,38 +110,25 @@ module PointerArithmeticToDerefConfig implements DataFlow::ConfigSig {
module PointerArithmeticToDerefFlow = DataFlow::Global<PointerArithmeticToDerefConfig>;
predicate pointerArithOverflow(PointerArithmeticInstruction pai, int delta) {
pointerArithOverflow0(pai, delta) and
predicate pointerArithOverflow(
PointerArithmeticInstruction pai, Field f, int size, int bound, int delta
) {
pointerArithOverflow0(pai, f, size, bound, delta) and
PointerArithmeticToDerefFlow::flow(DataFlow::instructionNode(pai), _)
}
bindingset[v]
predicate finalPointerArithOverflow(Variable v, PointerArithmeticInstruction pai, int delta) {
exists(int size |
arrayTypeHasSizes(pragma[only_bind_out](v.getUnspecifiedType()), pai.getElementSize(), size) and
pointerArithOverflow0Impl(pai, size, delta)
)
}
predicate isSourceImpl(DataFlow::Node source, Variable v) {
(
source.asInstruction().(FieldAddressInstruction).getField() = v
or
source.asInstruction().(VariableAddressInstruction).getAstVariable() = v
) and
arrayTypeCand(v.getUnspecifiedType())
}
module ArrayAddressToDerefConfig implements DataFlow::StateConfigSig {
module FieldAddressToDerefConfig implements DataFlow::StateConfigSig {
newtype FlowState =
additional TArray() or
additional TArray(Field f) { pointerArithOverflow(_, f, _, _, _) } or
additional TOverflowArithmetic(PointerArithmeticInstruction pai) {
pointerArithOverflow(pai, _)
pointerArithOverflow(pai, _, _, _, _)
}
predicate isSource(DataFlow::Node source, FlowState state) {
isSourceImpl(source, _) and
state = TArray()
exists(Field f |
source.asInstruction().(FieldAddressInstruction).getField() = f and
state = TArray(f)
)
}
predicate isSink(DataFlow::Node sink, FlowState state) {
@@ -168,6 +138,8 @@ module ArrayAddressToDerefConfig implements DataFlow::StateConfigSig {
)
}
predicate isBarrier(DataFlow::Node node, FlowState state) { none() }
predicate isBarrierIn(DataFlow::Node node) { isSource(node, _) }
predicate isBarrierOut(DataFlow::Node node) { isSink(node, _) }
@@ -175,27 +147,27 @@ module ArrayAddressToDerefConfig implements DataFlow::StateConfigSig {
predicate isAdditionalFlowStep(
DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2
) {
exists(PointerArithmeticInstruction pai |
state1 = TArray() and
exists(PointerArithmeticInstruction pai, Field f |
state1 = TArray(f) and
state2 = TOverflowArithmetic(pai) and
pai.getLeft() = node1.asInstruction() and
node2.asInstruction() = pai and
pointerArithOverflow(pai, _)
pointerArithOverflow(pai, f, _, _, _)
)
}
}
module ArrayAddressToDerefFlow = DataFlow::GlobalWithState<ArrayAddressToDerefConfig>;
module FieldAddressToDerefFlow = DataFlow::GlobalWithState<FieldAddressToDerefConfig>;
from
Variable v, ArrayAddressToDerefFlow::PathNode source, PointerArithmeticInstruction pai,
ArrayAddressToDerefFlow::PathNode sink, Instruction deref, string operation, int delta
Field f, FieldAddressToDerefFlow::PathNode source, PointerArithmeticInstruction pai,
FieldAddressToDerefFlow::PathNode sink, Instruction deref, string operation, int delta
where
ArrayAddressToDerefFlow::flowPath(pragma[only_bind_into](source), pragma[only_bind_into](sink)) and
FieldAddressToDerefFlow::flowPath(source, sink) and
isInvalidPointerDerefSink2(sink.getNode(), deref, operation) and
pragma[only_bind_out](sink.getState()) = ArrayAddressToDerefConfig::TOverflowArithmetic(pai) and
isSourceImpl(source.getNode(), v) and
finalPointerArithOverflow(v, pai, delta)
source.getState() = FieldAddressToDerefConfig::TArray(f) and
sink.getState() = FieldAddressToDerefConfig::TOverflowArithmetic(pai) and
pointerArithOverflow(pai, f, _, _, delta)
select pai, source, sink,
"This pointer arithmetic may have an off-by-" + (delta + 1) +
" error allowing it to overrun $@ at this $@.", v, v.getName(), deref, operation
" error allowing it to overrun $@ at this $@.", f, f.getName(), deref, operation

View File

@@ -15,138 +15,401 @@
* external/cwe/cwe-787
*/
/*
* High-level description of the query:
*
* The goal of this query is to identify issues such as:
* ```cpp
* 1. int* base = new int[size];
* 2. int* end = base + size;
* 3. for(int* p = base; p <= end; ++p) {
* 4. *p = 0; // BUG: Should have been bounded by `p < end`.
* 5. }
* ```
* In order to do this, we split the problem into three subtasks:
* 1. First, we find flow from `new int[size]` to `base + size`.
* 2. Then, we find flow from `base + size` to `end` (on line 3).
* 3. Finally, we use range-analysis to find a write to (or read from) a pointer that may be greater than or equal to `end`.
*
* Step 1 is implemented in `AllocationToInvalidPointer.qll`, and step 2 is implemented by
* `InvalidPointerToDereference.qll`. See those files for the description of these.
*
* This file imports both libraries and defines a final dataflow configuration that constructs the full path from
* the allocation to the dereference of the out-of-bounds pointer. This is done for several reasons:
* 1. It means the user is able to inspect the entire path from the allocation to the dereference, which can be useful
* to understand the problem highlighted.
* 2. It ensures that the call-contexts line up correctly when we transition from step 1 to step 2. See the
* `test_missing_call_context_1` and `test_missing_call_context_2` tests for how this may flag false positives
* without this final configuration.
*
* The source of the final path is an allocation that is:
* 1. identified as flowing to an invalid pointer (by `AllocationToInvalidPointer`), and
* 2. for which the invalid pointer flows to a dereference (as identified by `InvalidPointerToDereference`).
*
* The path can be described in 3 "chunks":
* 1. One path from the allocation to the construction of the invalid pointer
* 2. Another path from the construction of the invalid pointer to the final pointer that is about to be dereferenced.
* 3. Finally, a single step from the dataflow node that represents the final pointer to the dereference.
*
* Step 1 happens when the flow state is `TInitial`, and step 2 and 3 happen when the flow state is `TPointerArith(pai)`
* where the pointer-arithmetic instruction `pai` tracks the instruction that generated the out-of-bounds pointer. This
* instruction is used in the construction of the alert message.
*
* The set of pointer-arithmetic instructions that define the `TPointerArith` flow state is restricted to be the pointer-
* arithmetic instructions that both receive flow from the allocation (as identified by `AllocationToInvalidPointer.qll`),
* and further flow to a dereference (as identified by `InvalidPointerToDereference.qll`).
*/
import cpp
import semmle.code.cpp.dataflow.new.DataFlow
import semmle.code.cpp.ir.dataflow.internal.ProductFlow
import semmle.code.cpp.rangeanalysis.new.internal.semantic.analysis.RangeAnalysis
import semmle.code.cpp.rangeanalysis.new.internal.semantic.SemanticExprSpecific
import semmle.code.cpp.ir.IR
import FinalFlow::PathGraph
import semmle.code.cpp.security.InvalidPointerDereference.AllocationToInvalidPointer
import semmle.code.cpp.security.InvalidPointerDereference.InvalidPointerToDereference
import codeql.util.Unit
pragma[nomagic]
Instruction getABoundIn(SemBound b, IRFunction func) {
getSemanticExpr(result) = b.getExpr(0) and
result.getEnclosingIRFunction() = func
}
/**
* A configuration that represents the full dataflow path all the way from
* the allocation to the dereference. We need this final dataflow traversal
* to ensure that the transition from the sink in `AllocToInvalidPointerConfig`
* to the source in `InvalidPointerToDerefFlow` did not make us construct an
* infeasible path (which can happen since the transition from one configuration
* to the next does not preserve information about call contexts).
* Holds if `i <= b + delta`.
*/
module FinalConfig implements DataFlow::StateConfigSig {
newtype FlowState =
additional TInitial() or
additional TPointerArith(PointerArithmeticInstruction pai) {
operationIsOffBy(_, pai, _, _, _, _, _)
}
pragma[inline]
predicate boundedImpl(Instruction i, Instruction b, int delta) {
exists(SemBound bound, IRFunction func |
semBounded(getSemanticExpr(i), bound, delta, true, _) and
b = getABoundIn(bound, func) and
i.getEnclosingIRFunction() = func
)
}
predicate isSource(DataFlow::Node source, FlowState state) {
state = TInitial() and
operationIsOffBy(source, _, _, _, _, _, _)
}
bindingset[i]
pragma[inline_late]
predicate bounded1(Instruction i, Instruction b, int delta) { boundedImpl(i, b, delta) }
predicate isSink(DataFlow::Node sink, FlowState state) {
exists(PointerArithmeticInstruction pai |
operationIsOffBy(_, pai, _, _, _, sink, _) and
state = TPointerArith(pai)
)
}
bindingset[b]
pragma[inline_late]
predicate bounded2(Instruction i, Instruction b, int delta) { boundedImpl(i, b, delta) }
predicate isAdditionalFlowStep(
DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2
VariableAccess getAVariableAccess(Expr e) { e.getAChild*() = result }
/**
* Holds if `(n, state)` pair represents the source of flow for the size
* expression associated with `alloc`.
*/
predicate hasSize(HeuristicAllocationExpr alloc, DataFlow::Node n, int state) {
exists(VariableAccess va, Expr size, int delta |
size = alloc.getSizeExpr() and
// Get the unique variable in a size expression like `x` in `malloc(x + 1)`.
va = unique( | | getAVariableAccess(size)) and
// Compute `delta` as the constant difference between `x` and `x + 1`.
bounded1(any(Instruction instr | instr.getUnconvertedResultExpression() = size),
any(LoadInstruction load | load.getUnconvertedResultExpression() = va), delta) and
n.asConvertedExpr() = va.getFullyConverted() and
state = delta
)
}
/**
* A product-flow configuration for flow from an (allocation, size) pair to a
* pointer-arithmetic operation that is non-strictly upper-bounded by `allocation + size`.
*
* The goal of this query is to find patterns such as:
* ```cpp
* 1. char* begin = (char*)malloc(size);
* 2. char* end = begin + size;
* 3. for(int *p = begin; p <= end; p++) {
* 4. use(*p);
* 5. }
* ```
*
* We do this by splitting the task up into two configurations:
* 1. `AllocToInvalidPointerConfig` find flow from `malloc(size)` to `begin + size`, and
* 2. `InvalidPointerToDerefConfig` finds flow from `begin + size` to an `end` (on line 3).
*
* Finally, the range-analysis library will find a load from (or store to) an address that
* is non-strictly upper-bounded by `end` (which in this case is `*p`).
*/
module AllocToInvalidPointerConfig implements ProductFlow::StateConfigSig {
class FlowState1 = Unit;
class FlowState2 = int;
predicate isSourcePair(
DataFlow::Node source1, FlowState1 state1, DataFlow::Node source2, FlowState2 state2
) {
// A step from the left-hand side of a pointer-arithmetic operation that has been
// identified as creating an out-of-bounds pointer to the result of the pointer-arithmetic
// operation.
exists(PointerArithmeticInstruction pai |
pointerAddInstructionHasBounds(_, pai, node1, _) and
operationIsOffBy(_, pai, node2, _, _, _, _) and
state1 = TInitial() and
state2 = TPointerArith(pai)
)
or
// A step from an out-of-bounds address to the operation (which is either a `StoreInstruction`
// or a `LoadInstruction`) that dereferences the address.
// This step exists purely for aesthetic reasons: we want the alert to be placed at the operation
// that causes the dereference, and not at the address that flows into the operation.
state1 = state2 and
exists(PointerArithmeticInstruction pai |
state1 = TPointerArith(pai) and
operationIsOffBy(_, pai, _, node1, _, node2, _)
// In the case of an allocation like
// ```cpp
// malloc(size + 1);
// ```
// we use `state2` to remember that there was an offset (in this case an offset of `1`) added
// to the size of the allocation. This state is then checked in `isSinkPair`.
exists(state1) and
hasSize(source1.asConvertedExpr(), source2, state2)
}
predicate isSinkPair(
DataFlow::Node sink1, FlowState1 state1, DataFlow::Node sink2, FlowState2 state2
) {
exists(state1) and
// We check that the delta computed by the range analysis matches the
// state value that we set in `isSourcePair`.
exists(int delta |
isSinkImpl(_, sink1, sink2, delta) and
state2 = delta
)
}
predicate isBarrier1(DataFlow::Node node, FlowState1 state) { none() }
predicate isBarrier2(DataFlow::Node node, FlowState2 state) { none() }
predicate isBarrierIn1(DataFlow::Node node) { isSourcePair(node, _, _, _) }
predicate isBarrierOut2(DataFlow::Node node) {
node = any(DataFlow::SsaPhiNode phi).getAnInput(true)
}
predicate isAdditionalFlowStep1(
DataFlow::Node node1, FlowState1 state1, DataFlow::Node node2, FlowState1 state2
) {
none()
}
predicate isAdditionalFlowStep2(
DataFlow::Node node1, FlowState2 state1, DataFlow::Node node2, FlowState2 state2
) {
none()
}
}
module FinalFlow = DataFlow::GlobalWithState<FinalConfig>;
module AllocToInvalidPointerFlow = ProductFlow::GlobalWithState<AllocToInvalidPointerConfig>;
/**
* Holds if `source` is an allocation that flows into the left-hand side of `pai`, which produces an out-of-bounds
* pointer that flows into an address that is dereferenced by `sink` (which is either a `LoadInstruction` or a
* `StoreInstruction`). The end result is that `sink` writes to an address that is off-by-`delta` from the end of
* the allocation. The string `operation` describes whether the `sink` is a load or a store (which is then used
* to produce the alert message).
* Holds if `pai` is non-strictly upper bounded by `sink2 + delta` and `sink1` is the
* left operand of the pointer-arithmetic operation.
*
* Note that multiple `delta`s can exist for a given `(source, pai, sink)` triplet.
* For example in,
* ```cpp
* char* end = p + (size + 1);
* ```
* We will have:
* - `pai` is `p + (size + 1)`,
* - `sink1` is `p`
* - `sink2` is `size`
* - `delta` is `1`.
*/
predicate hasFlowPath(
FinalFlow::PathNode source, FinalFlow::PathNode sink, PointerArithmeticInstruction pai,
string operation, int delta
pragma[nomagic]
predicate pointerAddInstructionHasBounds(
PointerAddInstruction pai, DataFlow::Node sink1, DataFlow::Node sink2, int delta
) {
FinalFlow::flowPath(source, sink) and
operationIsOffBy(source.getNode(), pai, _, _, operation, sink.getNode(), delta) and
sink.getState() = FinalConfig::TPointerArith(pai)
exists(Instruction right |
pai.getRight() = right and
pai.getLeft() = sink1.asInstruction() and
bounded1(right, sink2.asInstruction(), delta)
)
}
/**
* Holds if `pai` is non-strictly upper bounded by `sink2 + delta` and `sink1` is the
* left operand of the pointer-arithmetic operation.
*
* See `pointerAddInstructionHasBounds` for an example.
*/
predicate isSinkImpl(
PointerAddInstruction pai, DataFlow::Node sink1, DataFlow::Node sink2, int delta
) {
pointerAddInstructionHasBounds(pai, sink1, sink2, delta)
}
/**
* Yields any instruction that is control-flow reachable from `instr`.
*/
bindingset[instr, result]
pragma[inline_late]
Instruction getASuccessor(Instruction instr) {
exists(IRBlock b, int instrIndex, int resultIndex |
result.getBlock() = b and
instr.getBlock() = b and
b.getInstruction(instrIndex) = instr and
b.getInstruction(resultIndex) = result
|
resultIndex >= instrIndex
)
or
instr.getBlock().getASuccessor+() = result.getBlock()
}
/**
* Holds if `sink` is a sink for `InvalidPointerToDerefConfig` and `i` is a `StoreInstruction` that
* writes to an address that non-strictly upper-bounds `sink`, or `i` is a `LoadInstruction` that
* reads from an address that non-strictly upper-bounds `sink`.
*/
pragma[inline]
predicate isInvalidPointerDerefSink(DataFlow::Node sink, Instruction i, string operation, int delta) {
exists(AddressOperand addr, Instruction s |
s = sink.asInstruction() and
bounded1(addr.getDef(), s, delta) and
delta >= 0 and
i.getAnOperand() = addr
|
i instanceof StoreInstruction and
operation = "write"
or
i instanceof LoadInstruction and
operation = "read"
)
}
/**
* A configuration to track flow from a pointer-arithmetic operation found
* by `AllocToInvalidPointerConfig` to a dereference of the pointer.
*/
module InvalidPointerToDerefConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { invalidPointerToDerefSource(_, source, _) }
pragma[inline]
predicate isSink(DataFlow::Node sink) { isInvalidPointerDerefSink(sink, _, _, _) }
predicate isBarrier(DataFlow::Node node) {
node = any(DataFlow::SsaPhiNode phi | not phi.isPhiRead()).getAnInput(true)
}
}
module InvalidPointerToDerefFlow = DataFlow::Global<InvalidPointerToDerefConfig>;
/**
* Holds if `pai` is a pointer-arithmetic operation and `source` is a dataflow node with a
* pointer-value that is non-strictly upper bounded by `pai + delta`.
*
* For example, if `pai` is a pointer-arithmetic operation `p + size` in an expression such
* as `(p + size) + 1` and `source` is the node representing `(p + size) + 1`. In this
* case `delta` is 1.
*/
predicate invalidPointerToDerefSource(
PointerArithmeticInstruction pai, DataFlow::Node source, int delta
) {
exists(AllocToInvalidPointerFlow::PathNode1 p, DataFlow::Node sink1 |
pragma[only_bind_out](p.getNode()) = sink1 and
AllocToInvalidPointerFlow::flowPath(_, _, pragma[only_bind_into](p), _) and
isSinkImpl(pai, sink1, _, _) and
bounded2(source.asInstruction(), pai, delta) and
delta >= 0
)
}
newtype TMergedPathNode =
// The path nodes computed by the first projection of `AllocToInvalidPointerConfig`
TPathNode1(AllocToInvalidPointerFlow::PathNode1 p) or
// The path nodes computed by `InvalidPointerToDerefConfig`
TPathNode3(InvalidPointerToDerefFlow::PathNode p) or
// The read/write that uses the invalid pointer identified by `InvalidPointerToDerefConfig`.
// This one is needed because the sink identified by `InvalidPointerToDerefConfig` is the
// pointer, but we want to raise an alert at the dereference.
TPathNodeSink(Instruction i) {
exists(DataFlow::Node n |
InvalidPointerToDerefFlow::flowTo(n) and
isInvalidPointerDerefSink(n, i, _, _) and
i = getASuccessor(n.asInstruction())
)
}
class MergedPathNode extends TMergedPathNode {
string toString() { none() }
final AllocToInvalidPointerFlow::PathNode1 asPathNode1() { this = TPathNode1(result) }
final InvalidPointerToDerefFlow::PathNode asPathNode3() { this = TPathNode3(result) }
final Instruction asSinkNode() { this = TPathNodeSink(result) }
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
none()
}
}
class PathNode1 extends MergedPathNode, TPathNode1 {
override string toString() {
exists(AllocToInvalidPointerFlow::PathNode1 p |
this = TPathNode1(p) and
result = p.toString()
)
}
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this.asPathNode1().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
}
class PathNode3 extends MergedPathNode, TPathNode3 {
override string toString() {
exists(InvalidPointerToDerefFlow::PathNode p |
this = TPathNode3(p) and
result = p.toString()
)
}
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this.asPathNode3().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
}
class PathSinkNode extends MergedPathNode, TPathNodeSink {
override string toString() {
exists(Instruction i |
this = TPathNodeSink(i) and
result = i.toString()
)
}
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this.asSinkNode()
.getLocation()
.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
}
query predicate edges(MergedPathNode node1, MergedPathNode node2) {
node1.asPathNode1().getASuccessor() = node2.asPathNode1()
or
joinOn1(_, node1.asPathNode1(), node2.asPathNode3())
or
node1.asPathNode3().getASuccessor() = node2.asPathNode3()
or
joinOn2(node1.asPathNode3(), node2.asSinkNode(), _, _)
}
query predicate nodes(MergedPathNode n, string key, string val) {
AllocToInvalidPointerFlow::PathGraph1::nodes(n.asPathNode1(), key, val)
or
InvalidPointerToDerefFlow::PathGraph::nodes(n.asPathNode3(), key, val)
or
key = "semmle.label" and val = n.asSinkNode().toString()
}
query predicate subpaths(
MergedPathNode arg, MergedPathNode par, MergedPathNode ret, MergedPathNode out
) {
AllocToInvalidPointerFlow::PathGraph1::subpaths(arg.asPathNode1(), par.asPathNode1(),
ret.asPathNode1(), out.asPathNode1())
or
InvalidPointerToDerefFlow::PathGraph::subpaths(arg.asPathNode3(), par.asPathNode3(),
ret.asPathNode3(), out.asPathNode3())
}
/**
* Holds if `p1` is a sink of `AllocToInvalidPointerConfig` and `p2` is a source
* of `InvalidPointerToDerefConfig`, and they are connected through `pai`.
*/
predicate joinOn1(
PointerArithmeticInstruction pai, AllocToInvalidPointerFlow::PathNode1 p1,
InvalidPointerToDerefFlow::PathNode p2
) {
isSinkImpl(pai, p1.getNode(), _, _) and
invalidPointerToDerefSource(pai, p2.getNode(), _)
}
/**
* Holds if `p1` is a sink of `InvalidPointerToDerefConfig` and `i` is the instruction
* that dereferences `p1`. The string `operation` describes whether the `i` is
* a `StoreInstruction` or `LoadInstruction`.
*/
pragma[inline]
predicate joinOn2(InvalidPointerToDerefFlow::PathNode p1, Instruction i, string operation, int delta) {
isInvalidPointerDerefSink(p1.getNode(), i, operation, delta)
}
predicate hasFlowPath(
MergedPathNode source1, MergedPathNode sink, InvalidPointerToDerefFlow::PathNode source3,
PointerArithmeticInstruction pai, string operation, int delta
) {
exists(InvalidPointerToDerefFlow::PathNode sink3, AllocToInvalidPointerFlow::PathNode1 sink1 |
AllocToInvalidPointerFlow::flowPath(source1.asPathNode1(), _, sink1, _) and
joinOn1(pai, sink1, source3) and
InvalidPointerToDerefFlow::flowPath(source3, sink3) and
joinOn2(sink3, sink.asSinkNode(), operation, delta)
)
}
from
FinalFlow::PathNode source, FinalFlow::PathNode sink, int k, string kstr,
PointerArithmeticInstruction pai, string operation, Expr offset, DataFlow::Node n
MergedPathNode source, MergedPathNode sink, int k, string kstr, PointerArithmeticInstruction pai,
string operation, Expr offset, DataFlow::Node n
where
k = min(int cand | hasFlowPath(source, sink, pai, operation, cand)) and
k =
min(int k2, int k3, InvalidPointerToDerefFlow::PathNode source3 |
hasFlowPath(source, sink, source3, pai, operation, k3) and
invalidPointerToDerefSource(pai, source3.getNode(), k2)
|
k2 + k3
) and
offset = pai.getRight().getUnconvertedResultExpression() and
n = source.getNode() and
n = source.asPathNode1().getNode() and
if k = 0 then kstr = "" else kstr = " + " + k
select sink.getNode(), source, sink,
select sink, source, sink,
"This " + operation + " might be out of bounds, as the pointer might be equal to $@ + $@" + kstr +
".", n, n.toString(), offset, offset.toString()

View File

@@ -1,5 +1,5 @@
name: codeql/cpp-queries
version: 0.7.2
version: 0.6.4-dev
groups:
- cpp
- queries

View File

@@ -1308,11 +1308,6 @@ union_etc.cpp:
# 6| Type = [IntType] int
# 6| ValueCategory = prvalue(load)
# 6| getStmt(1): [ReturnStmt] return ...
# 7| [GlobalVariable] S s
# 7| getInitializer(): [Initializer] initializer for s
# 7| getExpr(): [ConstructorCall] call to S
# 7| Type = [VoidType] void
# 7| ValueCategory = prvalue
# 9| [CopyAssignmentOperator] C& C::operator=(C const&)
# 9| <params>:
#-----| getParameter(0): [Parameter] (unnamed parameter 0)
@@ -1337,7 +1332,6 @@ union_etc.cpp:
# 12| <params>:
#-----| getParameter(0): [Parameter] (unnamed parameter 0)
#-----| Type = [RValueReferenceType] U &&
# 14| [GlobalVariable] C c
# 16| [CopyAssignmentOperator] U& U::operator=(U const&)
# 16| <params>:
#-----| getParameter(0): [Parameter] (unnamed parameter 0)
@@ -1362,7 +1356,6 @@ union_etc.cpp:
# 18| <params>:
#-----| getParameter(0): [Parameter] (unnamed parameter 0)
#-----| Type = [RValueReferenceType] C &&
# 20| [GlobalVariable] U u
# 22| [TopLevelFunction] int foo()
# 22| <params>:
# 22| getEntryPoint(): [BlockStmt] { ... }

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